diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index bff8480ff..d0a0b4580 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -108,6 +108,7 @@ var NodeFlags = []Flag{ QuaiCoinbaseFlag, QiCoinbaseFlag, MinerPreferenceFlag, + CoinbaseLockupFlag, EnvironmentFlag, QuaiStatsURLFlag, SendFullStatsFlag, @@ -531,6 +532,12 @@ var ( Usage: "Indicates preference towards mining Quai or Qi. Any value between 0 and 1 is valid. Neutral: 0.5, Quai only: 0, Qi only: 1" + generateEnvDoc(c_NodeFlagPrefix+"miner-preference"), } + CoinbaseLockupFlag = Flag{ + Name: c_NodeFlagPrefix + "coinbase-lockup", + Value: 0, + Usage: "Lockup byte used to determine the number of blocks that coinbase rewards are locked for" + generateEnvDoc(c_NodeFlagPrefix+"coinbase-lockup"), + } + IndexAddressUtxos = Flag{ Name: c_NodeFlagPrefix + "index-address-utxos", Value: false, @@ -1386,6 +1393,14 @@ func SetQuaiConfig(stack *node.Node, cfg *quaiconfig.Config, slicesRunning []com cfg.Miner.MinerPreference = minerPreference } + coinbaseLockup := viper.GetUint64(CoinbaseLockupFlag.Name) + if coinbaseLockup < 0 || coinbaseLockup > uint64(len(params.LockupByteToBlockDepth))-1 { + log.Global.WithField("CoinbaseLockup", coinbaseLockup).Error("Invalid CoinbaseLockup field. Must be [0,3]. Setting to default value 0") + cfg.Miner.CoinbaseLockup = params.DefaultCoinbaseLockup + } else { + cfg.Miner.CoinbaseLockup = uint8(coinbaseLockup) + } + // Override any default configs for hard coded networks. switch viper.GetString(EnvironmentFlag.Name) { case params.ColosseumName: diff --git a/core/core.go b/core/core.go index 3561c6bc8..ba471defe 100644 --- a/core/core.go +++ b/core/core.go @@ -1161,6 +1161,14 @@ func (c *Core) SetSecondaryCoinbase(addr common.Address) { c.sl.miner.SetSecondaryCoinbase(addr) } +func (c *Core) SetLockupByte(lockupByte uint8) { + c.sl.miner.worker.SetLockupByte(lockupByte) +} + +func (c *Core) SetMinerPreference(minerPreference float64) { + c.sl.miner.worker.SetMinerPreference(minerPreference) +} + // SubscribePendingLogs starts delivering logs from pending transactions // to the given channel. func (c *Core) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscription { diff --git a/core/state_processor.go b/core/state_processor.go index 07a16dcdc..630a2b8dc 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -452,7 +452,7 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty } lockupByte := tx.Data()[0] if tx.To().IsInQiLedgerScope() { - if int(lockupByte) > len(params.LockupByteToBlockDepth) { + if int(lockupByte) > len(params.LockupByteToBlockDepth)-1 { return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("coinbase lockup byte %d is out of range", lockupByte) } lockup := new(big.Int).SetUint64(params.LockupByteToBlockDepth[lockupByte]) diff --git a/core/worker.go b/core/worker.go index 9383257d0..0639c74eb 100644 --- a/core/worker.go +++ b/core/worker.go @@ -51,8 +51,6 @@ const ( chainSideChanSize = 10 c_uncleCacheSize = 100 - - defaultCoinbaseLockup = 0 ) // environment is the worker's current environment and holds all @@ -104,6 +102,7 @@ func (env *environment) unclelist() []*types.WorkObjectHeader { type Config struct { QuaiCoinbase common.Address `toml:",omitempty"` // Public address for Quai mining rewards QiCoinbase common.Address `toml:",omitempty"` // Public address for Qi mining rewards + CoinbaseLockup uint8 `toml:",omitempty"` // Lockup byte the determines number of blocks before mining rewards can be spent MinerPreference float64 // Determines the relative preference of Qi or Quai [0, 1] respectively Notify []string `toml:",omitempty"` // HTTP URL list to be notified of new work packages (only useful in ethash). NotifyFull bool `toml:",omitempty"` // Notify with pending block headers instead of work packages @@ -161,6 +160,8 @@ type worker struct { qiCoinbase common.Address primaryCoinbase common.Address secondaryCoinbase common.Address + coinbaseLockup uint8 + minerPreference float64 extra []byte workerDb ethdb.Database @@ -235,6 +236,12 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, db ethdb.Databas resubmitIntervalCh: make(chan time.Duration), fillTransactionsRollingAverage: &RollingAverage{windowSize: 100}, logger: logger, + coinbaseLockup: config.CoinbaseLockup, + minerPreference: config.MinerPreference, + } + if worker.coinbaseLockup > uint8(len(params.LockupByteToBlockDepth))-1 { + logger.Errorf("Invalid coinbase lockup value %d, using default value %d", worker.coinbaseLockup, params.DefaultCoinbaseLockup) + worker.coinbaseLockup = params.DefaultCoinbaseLockup } // initialize a uncle cache uncles, _ := lru.New[common.Hash, types.WorkObjectHeader](c_uncleCacheSize) @@ -281,7 +288,7 @@ func (w *worker) pickCoinbases() { defer w.mu.Unlock() // Use the MinerPreference to bias the decision - if rand.Float64() > w.config.MinerPreference { + if rand.Float64() > w.minerPreference { // if MinerPreference < 0.5, bias is towards Quai w.primaryCoinbase = w.quaiCoinbase w.secondaryCoinbase = w.qiCoinbase @@ -320,6 +327,32 @@ func (w *worker) GetSecondaryCoinbase() common.Address { return w.secondaryCoinbase } +func (w *worker) SetMinerPreference(preference float64) { + w.mu.Lock() + defer w.mu.Unlock() + w.minerPreference = preference +} + +func (w *worker) GetMinerPreference() float64 { + w.mu.RLock() + defer w.mu.RUnlock() + return w.minerPreference +} + +func (w *worker) GetLockupByte() uint8 { + w.mu.RLock() + defer w.mu.RUnlock() + + return w.coinbaseLockup +} + +func (w *worker) SetLockupByte(lockupByte uint8) { + w.mu.Lock() + defer w.mu.Unlock() + + w.coinbaseLockup = lockupByte +} + func (w *worker) setGasCeil(ceil uint64) { w.mu.Lock() defer w.mu.Unlock() @@ -598,6 +631,7 @@ func (w *worker) GeneratePendingHeader(block *types.WorkObject, fill bool, txs t if err != nil { return nil, err } + lockupByte := work.wo.Lock() // If the primary coinbase belongs to a ledger and there is no fees // for other ledger, there is no etxs emitted for the other ledger @@ -605,22 +639,22 @@ func (w *worker) GeneratePendingHeader(block *types.WorkObject, fill bool, txs t coinbaseReward := misc.CalculateReward(block, work.wo.WorkObjectHeader()) blockReward := new(big.Int).Add(coinbaseReward, work.quaiFees) primaryCoinbase := w.GetPrimaryCoinbase() - coinbaseEtx := types.NewTx(&types.ExternalTx{To: &primaryCoinbase, Gas: params.TxGas, Value: blockReward, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQuai(block.Hash(), w.hc.NodeLocation()), ETXIndex: uint16(len(work.etxs)), Sender: primaryCoinbase, Data: []byte{defaultCoinbaseLockup}}) + coinbaseEtx := types.NewTx(&types.ExternalTx{To: &primaryCoinbase, Gas: params.TxGas, Value: blockReward, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQuai(block.Hash(), w.hc.NodeLocation()), ETXIndex: uint16(len(work.etxs)), Sender: primaryCoinbase, Data: []byte{lockupByte}}) work.etxs = append(work.etxs, coinbaseEtx) if work.utxoFees.Cmp(big.NewInt(0)) != 0 { secondaryCoinbase := w.GetSecondaryCoinbase() - coinbaseEtx := types.NewTx(&types.ExternalTx{To: &secondaryCoinbase, Gas: params.TxGas, Value: work.utxoFees, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQi(block.Hash(), w.hc.NodeLocation()), ETXIndex: uint16(len(work.etxs)), Sender: w.secondaryCoinbase, Data: []byte{defaultCoinbaseLockup}}) + coinbaseEtx := types.NewTx(&types.ExternalTx{To: &secondaryCoinbase, Gas: params.TxGas, Value: work.utxoFees, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQi(block.Hash(), w.hc.NodeLocation()), ETXIndex: uint16(len(work.etxs)), Sender: w.secondaryCoinbase, Data: []byte{lockupByte}}) work.etxs = append(work.etxs, coinbaseEtx) } } else if bytes.Equal(work.wo.PrimaryCoinbase().Bytes(), qiCoinbase.Bytes()) { coinbaseReward := misc.CalculateReward(block, work.wo.WorkObjectHeader()) blockReward := new(big.Int).Add(coinbaseReward, work.utxoFees) primaryCoinbase := w.GetPrimaryCoinbase() - coinbaseEtx := types.NewTx(&types.ExternalTx{To: &primaryCoinbase, Gas: params.TxGas, Value: blockReward, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQi(block.Hash(), w.hc.NodeLocation()), ETXIndex: uint16(len(work.etxs)), Sender: primaryCoinbase, Data: []byte{defaultCoinbaseLockup}}) + coinbaseEtx := types.NewTx(&types.ExternalTx{To: &primaryCoinbase, Gas: params.TxGas, Value: blockReward, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQi(block.Hash(), w.hc.NodeLocation()), ETXIndex: uint16(len(work.etxs)), Sender: primaryCoinbase, Data: []byte{lockupByte}}) work.etxs = append(work.etxs, coinbaseEtx) if work.quaiFees.Cmp(big.NewInt(0)) != 0 { secondaryCoinbase := w.GetSecondaryCoinbase() - coinbaseEtx := types.NewTx(&types.ExternalTx{To: &secondaryCoinbase, Gas: params.TxGas, Value: work.quaiFees, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQuai(block.Hash(), w.hc.NodeLocation()), ETXIndex: uint16(len(work.etxs)), Sender: secondaryCoinbase, Data: []byte{defaultCoinbaseLockup}}) + coinbaseEtx := types.NewTx(&types.ExternalTx{To: &secondaryCoinbase, Gas: params.TxGas, Value: work.quaiFees, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQuai(block.Hash(), w.hc.NodeLocation()), ETXIndex: uint16(len(work.etxs)), Sender: secondaryCoinbase, Data: []byte{lockupByte}}) work.etxs = append(work.etxs, coinbaseEtx) } } @@ -1427,7 +1461,7 @@ func (w *worker) prepareWork(genParams *generateParams, wo *types.WorkObject) (* // Construct the sealing block header, set the extra field if it's allowed num := parent.Number(nodeCtx) newWo := types.EmptyWorkObject(nodeCtx) - newWo.WorkObjectHeader().SetLock(defaultCoinbaseLockup) + newWo.WorkObjectHeader().SetLock(w.GetLockupByte()) newWo.SetParentHash(wo.Hash(), nodeCtx) if w.hc.IsGenesisHash(parent.Hash()) { newWo.SetNumber(big.NewInt(1), nodeCtx) diff --git a/internal/quaiapi/quai_api.go b/internal/quaiapi/quai_api.go index 85f990179..93ca528f1 100644 --- a/internal/quaiapi/quai_api.go +++ b/internal/quaiapi/quai_api.go @@ -832,7 +832,7 @@ func (s *PublicBlockChainQuaiAPI) GetProtocolExpansionNumber() hexutil.Uint { } // Calculate the amount of Quai that Qi can be converted to. Expect the current Header and the Qi amount in "qits", returns the quai amount in "its" -func (s *PublicBlockChainQuaiAPI) QiRateAtBlock(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, qiAmount uint64) *hexutil.Big { +func (s *PublicBlockChainQuaiAPI) QiRateAtBlock(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, qiAmount hexutil.Big) *hexutil.Big { var header *types.WorkObject var err error if blockNr, ok := blockNrOrHash.Number(); ok { @@ -852,11 +852,11 @@ func (s *PublicBlockChainQuaiAPI) QiRateAtBlock(ctx context.Context, blockNrOrHa } else if header == nil { return nil } - return (*hexutil.Big)(misc.QiToQuai(header, new(big.Int).SetUint64(qiAmount))) + return (*hexutil.Big)(misc.QiToQuai(header, qiAmount.ToInt())) } // Calculate the amount of Qi that Quai can be converted to. Expect the current Header and the Quai amount in "its", returns the Qi amount in "qits" -func (s *PublicBlockChainQuaiAPI) QuaiRateAtBlock(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, quaiAmount uint64) *hexutil.Big { +func (s *PublicBlockChainQuaiAPI) QuaiRateAtBlock(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, quaiAmount hexutil.Big) *hexutil.Big { var header *types.WorkObject var err error if blockNr, ok := blockNrOrHash.Number(); ok { @@ -876,7 +876,7 @@ func (s *PublicBlockChainQuaiAPI) QuaiRateAtBlock(ctx context.Context, blockNrOr } else if header == nil { return nil } - return (*hexutil.Big)(misc.QuaiToQi(header, new(big.Int).SetUint64(quaiAmount))) + return (*hexutil.Big)(misc.QuaiToQi(header, quaiAmount.ToInt())) } func (s *PublicBlockChainQuaiAPI) CalcOrder(ctx context.Context, raw hexutil.Bytes) (hexutil.Uint, error) { diff --git a/params/protocol_params.go b/params/protocol_params.go index 41c26de77..fd78d3967 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -152,9 +152,10 @@ const ( ConversionLockPeriod uint64 = 10 // The number of zone blocks that a conversion output is locked for MinQiConversionDenomination = 10 ConversionConfirmationContext = common.PRIME_CTX // A conversion requires a single coincident Dom confirmation - SoftMaxUTXOSetSize = 10000000 // The soft maximum number of UTXOs that can be stored in the UTXO set + SoftMaxUTXOSetSize = math.MaxInt // The soft maximum number of UTXOs that can be stored in the UTXO set MinimumTrimDepth = math.MaxInt // The minimum block depth of the chain to begin trimming QiToQuaiConversionGas = 100000 // The gas used to convert Qi to Quai + DefaultCoinbaseLockup = 0 // The default lockup byte for coinbase rewards ) var ( diff --git a/quai/api.go b/quai/api.go index 335e012de..ab7d4bd87 100644 --- a/quai/api.go +++ b/quai/api.go @@ -24,6 +24,7 @@ import ( "io" "math/big" "os" + "strconv" "strings" "time" @@ -33,6 +34,7 @@ import ( "github.com/dominant-strategies/go-quai/core/rawdb" "github.com/dominant-strategies/go-quai/core/state" "github.com/dominant-strategies/go-quai/core/types" + "github.com/dominant-strategies/go-quai/params" "github.com/dominant-strategies/go-quai/rlp" "github.com/dominant-strategies/go-quai/rpc" "github.com/dominant-strategies/go-quai/trie" @@ -117,6 +119,26 @@ func (api *PrivateMinerAPI) SetSecondaryCoinbase(secondaryCoinbase common.Addres return true } +func (api *PrivateMinerAPI) SetLockupByte(lockupByte hexutil.Uint64) (bool, error) { + if uint8(lockupByte) > uint8(len(params.LockupByteToBlockDepth)-1) { + return false, fmt.Errorf("lockup byte %d out of range", lockupByte) + } + api.e.Core().SetLockupByte(uint8(lockupByte)) + return true, nil +} + +func (api *PrivateMinerAPI) SetMinerPreference(minerPreference string) (bool, error) { + preferenceFloat, err := strconv.ParseFloat(minerPreference, 64) + if err != nil { + return false, err + } + if preferenceFloat < 0 || preferenceFloat > 1 { + return false, errors.New("miner preference must be between 0 and 1") + } + api.e.Core().SetMinerPreference(preferenceFloat) + return true, nil +} + // SetRecommitInterval updates the interval for miner sealing work recommitting. func (api *PrivateMinerAPI) SetRecommitInterval(interval int) { api.e.Core().SetRecommitInterval(time.Duration(interval) * time.Millisecond)