From d15699b37a4438e5686d6ac3a0ffc7a59d37ae5b Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Wed, 21 Jul 2021 14:12:39 +0800 Subject: [PATCH 01/26] eth, miner: remove duplicated code This PR removes the duplicated code in the catalyst package, reuse the miner package code instead. --- eth/catalyst/api.go | 148 +------------- miner/miner.go | 6 + miner/worker.go | 476 ++++++++++++++++++++++++++------------------ 3 files changed, 291 insertions(+), 339 deletions(-) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 2622c4a148f6b..0c2bcf6bf5da2 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -21,12 +21,9 @@ import ( "errors" "fmt" "math/big" - "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/misc" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/log" @@ -65,153 +62,11 @@ func newConsensusAPI(eth *eth.Ethereum) *consensusAPI { return &consensusAPI{eth: eth} } -// blockExecutionEnv gathers all the data required to execute -// a block, either when assembling it or when inserting it. -type blockExecutionEnv struct { - chain *core.BlockChain - state *state.StateDB - tcount int - gasPool *core.GasPool - - header *types.Header - txs []*types.Transaction - receipts []*types.Receipt -} - -func (env *blockExecutionEnv) commitTransaction(tx *types.Transaction, coinbase common.Address) error { - vmconfig := *env.chain.GetVMConfig() - snap := env.state.Snapshot() - receipt, err := core.ApplyTransaction(env.chain.Config(), env.chain, &coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, vmconfig) - if err != nil { - env.state.RevertToSnapshot(snap) - return err - } - env.txs = append(env.txs, tx) - env.receipts = append(env.receipts, receipt) - return nil -} - -func (api *consensusAPI) makeEnv(parent *types.Block, header *types.Header) (*blockExecutionEnv, error) { - state, err := api.eth.BlockChain().StateAt(parent.Root()) - if err != nil { - return nil, err - } - env := &blockExecutionEnv{ - chain: api.eth.BlockChain(), - state: state, - header: header, - gasPool: new(core.GasPool).AddGas(header.GasLimit), - } - return env, nil -} - // AssembleBlock creates a new block, inserts it into the chain, and returns the "execution // data" required for eth2 clients to process the new block. func (api *consensusAPI) AssembleBlock(params assembleBlockParams) (*executableData, error) { log.Info("Producing block", "parentHash", params.ParentHash) - - bc := api.eth.BlockChain() - parent := bc.GetBlockByHash(params.ParentHash) - if parent == nil { - log.Warn("Cannot assemble block with parent hash to unknown block", "parentHash", params.ParentHash) - return nil, fmt.Errorf("cannot assemble block with unknown parent %s", params.ParentHash) - } - - pool := api.eth.TxPool() - - if parent.Time() >= params.Timestamp { - return nil, fmt.Errorf("child timestamp lower than parent's: %d >= %d", parent.Time(), params.Timestamp) - } - if now := uint64(time.Now().Unix()); params.Timestamp > now+1 { - wait := time.Duration(params.Timestamp-now) * time.Second - log.Info("Producing block too far in the future", "wait", common.PrettyDuration(wait)) - time.Sleep(wait) - } - - pending, err := pool.Pending(true) - if err != nil { - return nil, err - } - - coinbase, err := api.eth.Etherbase() - if err != nil { - return nil, err - } - num := parent.Number() - header := &types.Header{ - ParentHash: parent.Hash(), - Number: num.Add(num, common.Big1), - Coinbase: coinbase, - GasLimit: parent.GasLimit(), // Keep the gas limit constant in this prototype - Extra: []byte{}, - Time: params.Timestamp, - } - if config := api.eth.BlockChain().Config(); config.IsLondon(header.Number) { - header.BaseFee = misc.CalcBaseFee(config, parent.Header()) - } - err = api.eth.Engine().Prepare(bc, header) - if err != nil { - return nil, err - } - - env, err := api.makeEnv(parent, header) - if err != nil { - return nil, err - } - - var ( - signer = types.MakeSigner(bc.Config(), header.Number) - txHeap = types.NewTransactionsByPriceAndNonce(signer, pending, nil) - transactions []*types.Transaction - ) - for { - if env.gasPool.Gas() < chainParams.TxGas { - log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", chainParams.TxGas) - break - } - tx := txHeap.Peek() - if tx == nil { - break - } - - // The sender is only for logging purposes, and it doesn't really matter if it's correct. - from, _ := types.Sender(signer, tx) - - // Execute the transaction - env.state.Prepare(tx.Hash(), env.tcount) - err = env.commitTransaction(tx, coinbase) - switch err { - case core.ErrGasLimitReached: - // Pop the current out-of-gas transaction without shifting in the next from the account - log.Trace("Gas limit exceeded for current block", "sender", from) - txHeap.Pop() - - case core.ErrNonceTooLow: - // New head notification data race between the transaction pool and miner, shift - log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce()) - txHeap.Shift() - - case core.ErrNonceTooHigh: - // Reorg notification data race between the transaction pool and miner, skip account = - log.Trace("Skipping account with high nonce", "sender", from, "nonce", tx.Nonce()) - txHeap.Pop() - - case nil: - // Everything ok, collect the logs and shift in the next transaction from the same account - env.tcount++ - txHeap.Shift() - transactions = append(transactions, tx) - - default: - // Strange error, discard the transaction and get the next in line (note, the - // nonce-too-high clause will prevent us from executing in vain). - log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err) - txHeap.Shift() - } - } - - // Create the block. - block, err := api.eth.Engine().FinalizeAndAssemble(bc, header, env.state, transactions, nil /* uncles */, env.receipts) + block, err := api.eth.Miner().GetSealingBlock(params.ParentHash, params.Timestamp) if err != nil { return nil, err } @@ -255,7 +110,6 @@ func insertBlockParamsToBlock(config *chainParams.ChainConfig, parent *types.Hea if err != nil { return nil, err } - number := big.NewInt(0) number.SetUint64(params.Number) header := &types.Header{ diff --git a/miner/miner.go b/miner/miner.go index a4a01b9f4ff70..87e809f34f124 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -227,6 +227,12 @@ func (miner *Miner) DisablePreseal() { miner.worker.disablePreseal() } +// GetSealingBlock retrieves a sealing block based on the given parameters. +// The returned block is not sealed but all other fields should be filled. +func (miner *Miner) GetSealingBlock(parent common.Hash, timestamp uint64) (*types.Block, error) { + return miner.worker.getSealingBlock(parent, timestamp) +} + // SubscribePendingLogs starts delivering logs from pending transactions // to the given channel. func (miner *Miner) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscription { diff --git a/miner/worker.go b/miner/worker.go index accf3dac90964..3cb2137c5a71f 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -17,8 +17,8 @@ package miner import ( - "bytes" "errors" + "fmt" "math/big" "sync" "sync/atomic" @@ -77,20 +77,56 @@ const ( staleThreshold = 7 ) -// environment is the worker's current environment and holds all of the current state information. +// environment is the worker's current environment and holds all +// information of the sealing block generation. type environment struct { signer types.Signer - state *state.StateDB // apply state changes here - ancestors mapset.Set // ancestor set (used for checking uncle parent validity) - family mapset.Set // family set (used for checking uncle invalidity) - uncles mapset.Set // uncle set - tcount int // tx count in cycle - gasPool *core.GasPool // available gas used to pack transactions + state *state.StateDB // apply state changes here + ancestors mapset.Set // ancestor set (used for checking uncle parent validity) + family mapset.Set // family set (used for checking uncle invalidity) + uncleHashes mapset.Set // uncle set + tcount int // tx count in cycle + gasPool *core.GasPool // available gas used to pack transactions header *types.Header txs []*types.Transaction receipts []*types.Receipt + uncles []*types.Header +} + +// copy creates a deep copy of environment. +func (env *environment) copy() *environment { + cpy := &environment{ + signer: env.signer, + state: env.state.Copy(), + ancestors: env.ancestors.Clone(), + family: env.family.Clone(), + uncleHashes: env.uncleHashes.Clone(), + tcount: env.tcount, + header: types.CopyHeader(env.header), + receipts: copyReceipts(env.receipts), + } + if env.gasPool != nil { + cpy.gasPool = &(*env.gasPool) + } + // the content of txs and uncles are immutable, unnecessary + // to do the expensive deep copy for them. + cpy.txs = make([]*types.Transaction, len(env.txs)) + copy(cpy.txs, env.txs) + cpy.uncles = make([]*types.Header, len(env.uncles)) + copy(cpy.uncles, env.uncles) + return cpy +} + +// discard terminates the background prefetcher go-routine. It should +// always be called for all created environment instances otherwise +// the go-routine leak can happen. +func (env *environment) discard() { + if env.state == nil { + return + } + env.state.StopPrefetcher() } // task contains all information for consensus engine sealing and result submitting. @@ -114,6 +150,13 @@ type newWorkReq struct { timestamp int64 } +// getWorkReq represents a request for getting a unsealed block with provided parameters. +type getWorkReq struct { + params *generateParams + err error + result chan *types.Block +} + // intervalAdjust represents a resubmitting interval adjustment. type intervalAdjust struct { ratio float64 @@ -143,6 +186,7 @@ type worker struct { // Channels newWorkCh chan *newWorkReq + getWorkCh chan *getWorkReq taskCh chan *task resultCh chan *types.Block startCh chan struct{} @@ -205,6 +249,7 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize), chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize), newWorkCh: make(chan *newWorkReq), + getWorkCh: make(chan *getWorkReq), taskCh: make(chan *task), resultCh: make(chan *types.Block, resultQueueSize), exitCh: make(chan struct{}), @@ -259,7 +304,10 @@ func (w *worker) setExtra(extra []byte) { // setRecommitInterval updates the interval for miner sealing work recommitting. func (w *worker) setRecommitInterval(interval time.Duration) { - w.resubmitIntervalCh <- interval + select { + case w.resubmitIntervalCh <- interval: + case <-w.exitCh: + } } // disablePreseal disables pre-sealing mining feature @@ -318,8 +366,8 @@ func (w *worker) isRunning() bool { // close terminates all background threads maintained by the worker. // Note the worker does not support being closed multiple times. func (w *worker) close() { - if w.current != nil && w.current.state != nil { - w.current.state.StopPrefetcher() + if w.current != nil { + w.current.discard() } atomic.StoreInt32(&w.running, 0) close(w.exitCh) @@ -347,7 +395,7 @@ func recalcRecommit(minRecommit, prev time.Duration, target float64, inc bool) t return time.Duration(int64(next)) } -// newWorkLoop is a standalone goroutine to submit new mining work upon received events. +// newWorkLoop is a standalone goroutine to submit new sealing work upon received events. func (w *worker) newWorkLoop(recommit time.Duration) { var ( interrupt *int32 @@ -444,16 +492,30 @@ func (w *worker) newWorkLoop(recommit time.Duration) { } } -// mainLoop is a standalone goroutine to regenerate the sealing task based on the received event. +// mainLoop is responsible for generating and submitting sealing work based on +// the received event. It can support two modes: automatically generate task and +// submit it or return task according to given parameters for various proposes. func (w *worker) mainLoop() { defer w.txsSub.Unsubscribe() defer w.chainHeadSub.Unsubscribe() defer w.chainSideSub.Unsubscribe() + cleanTicker := time.NewTicker(time.Minute) + defer cleanTicker.Stop() + for { select { case req := <-w.newWorkCh: - w.commitNewWork(req.interrupt, req.noempty, req.timestamp) + w.commitWork(req.interrupt, req.noempty, req.timestamp) + + case req := <-w.getWorkCh: + unsealed, err := w.generateWork(req.params) + if err != nil { + req.err = err + req.result <- nil + } else { + req.result <- unsealed + } case ev := <-w.chainSideCh: // Short circuit for duplicate side blocks @@ -469,28 +531,26 @@ func (w *worker) mainLoop() { } else { w.remoteUncles[ev.Block.Hash()] = ev.Block } - // If our mining block contains less than 2 uncle blocks, - // add the new uncle block if valid and regenerate a mining block. - if w.isRunning() && w.current != nil && w.current.uncles.Cardinality() < 2 { + // If our sealing block contains less than 2 uncle blocks, + // add the new uncle block if valid and regenerate a new + // sealing block for higher profit. + if w.isRunning() && w.current != nil && w.current.uncleHashes.Cardinality() < 2 { start := time.Now() if err := w.commitUncle(w.current, ev.Block.Header()); err == nil { - var uncles []*types.Header - w.current.uncles.Each(func(item interface{}) bool { - hash, ok := item.(common.Hash) - if !ok { - return false - } - uncle, exist := w.localUncles[hash] - if !exist { - uncle, exist = w.remoteUncles[hash] - } - if !exist { - return false - } - uncles = append(uncles, uncle.Header()) - return false - }) - w.commit(uncles, nil, true, start) + w.commit(w.current.copy(), nil, true, start) + } + } + + case <-cleanTicker.C: + chainHead := w.chain.CurrentBlock() + for hash, uncle := range w.localUncles { + if uncle.NumberU64()+staleThreshold <= chainHead.NumberU64() { + delete(w.localUncles, hash) + } + } + for hash, uncle := range w.remoteUncles { + if uncle.NumberU64()+staleThreshold <= chainHead.NumberU64() { + delete(w.remoteUncles, hash) } } @@ -516,18 +576,18 @@ func (w *worker) mainLoop() { } txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs, w.current.header.BaseFee) tcount := w.current.tcount - w.commitTransactions(txset, coinbase, nil) + w.commitTransactions(w.current, txset, coinbase, nil) // Only update the snapshot if any new transactons were added // to the pending block if tcount != w.current.tcount { - w.updateSnapshot() + w.updateSnapshot(w.current) } } else { // Special case, if the consensus engine is 0 period clique(dev mode), // submit mining work here since all empty submission will be rejected // by clique. Of course the advance sealing(empty submission) is disabled. if w.chainConfig.Clique != nil && w.chainConfig.Clique.Period == 0 { - w.commitNewWork(nil, true, time.Now().Unix()) + w.commitWork(nil, true, time.Now().Unix()) } } atomic.AddInt32(&w.newTxs, int32(len(ev.Txs))) @@ -658,23 +718,23 @@ func (w *worker) resultLoop() { } } -// makeCurrent creates a new environment for the current cycle. -func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error { +// makeEnv creates a new environment for the sealing block. +func (w *worker) makeEnv(parent *types.Block, header *types.Header) (*environment, error) { // Retrieve the parent state to execute on top and start a prefetcher for // the miner to speed block sealing up a bit state, err := w.chain.StateAt(parent.Root()) if err != nil { - return err + return nil, err } state.StartPrefetcher("miner") env := &environment{ - signer: types.MakeSigner(w.chainConfig, header.Number), - state: state, - ancestors: mapset.NewSet(), - family: mapset.NewSet(), - uncles: mapset.NewSet(), - header: header, + signer: types.MakeSigner(w.chainConfig, header.Number), + state: state, + ancestors: mapset.NewSet(), + family: mapset.NewSet(), + uncleHashes: mapset.NewSet(), + header: header, } // when 08 is processed ancestors contain 07 (quick block) for _, ancestor := range w.chain.GetBlocksFromHash(parent.Hash(), 7) { @@ -686,20 +746,13 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error { } // Keep track of transactions which return errors so they can be removed env.tcount = 0 - - // Swap out the old work with the new one, terminating any leftover prefetcher - // processes in the mean time and starting a new one. - if w.current != nil && w.current.state != nil { - w.current.state.StopPrefetcher() - } - w.current = env - return nil + return env, nil } // commitUncle adds the given block to uncle block set, returns error if failed to add. func (w *worker) commitUncle(env *environment, uncle *types.Header) error { hash := uncle.Hash() - if env.uncles.Contains(hash) { + if env.uncleHashes.Contains(hash) { return errors.New("uncle not unique") } if env.header.ParentHash == uncle.ParentHash { @@ -711,69 +764,47 @@ func (w *worker) commitUncle(env *environment, uncle *types.Header) error { if env.family.Contains(hash) { return errors.New("uncle already included") } - env.uncles.Add(uncle.Hash()) + env.uncleHashes.Add(uncle.Hash()) + env.uncles = append(env.uncles, uncle) return nil } // updateSnapshot updates pending snapshot block and state. // Note this function assumes the current variable is thread safe. -func (w *worker) updateSnapshot() { +func (w *worker) updateSnapshot(env *environment) { w.snapshotMu.Lock() defer w.snapshotMu.Unlock() - var uncles []*types.Header - w.current.uncles.Each(func(item interface{}) bool { - hash, ok := item.(common.Hash) - if !ok { - return false - } - uncle, exist := w.localUncles[hash] - if !exist { - uncle, exist = w.remoteUncles[hash] - } - if !exist { - return false - } - uncles = append(uncles, uncle.Header()) - return false - }) - w.snapshotBlock = types.NewBlock( - w.current.header, - w.current.txs, - uncles, - w.current.receipts, + env.header, + env.txs, + env.uncles, + env.receipts, trie.NewStackTrie(nil), ) - w.snapshotReceipts = copyReceipts(w.current.receipts) - w.snapshotState = w.current.state.Copy() + w.snapshotReceipts = copyReceipts(env.receipts) + w.snapshotState = env.state.Copy() } -func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) { - snap := w.current.state.Snapshot() +func (w *worker) commitTransaction(env *environment, tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) { + snap := env.state.Snapshot() - receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, *w.chain.GetVMConfig()) + receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *w.chain.GetVMConfig()) if err != nil { - w.current.state.RevertToSnapshot(snap) + env.state.RevertToSnapshot(snap) return nil, err } - w.current.txs = append(w.current.txs, tx) - w.current.receipts = append(w.current.receipts, receipt) + env.txs = append(env.txs, tx) + env.receipts = append(env.receipts, receipt) return receipt.Logs, nil } -func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coinbase common.Address, interrupt *int32) bool { - // Short circuit if current is nil - if w.current == nil { - return true - } - - gasLimit := w.current.header.GasLimit - if w.current.gasPool == nil { - w.current.gasPool = new(core.GasPool).AddGas(gasLimit) +func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByPriceAndNonce, coinbase common.Address, interrupt *int32) bool { + gasLimit := env.header.GasLimit + if env.gasPool == nil { + env.gasPool = new(core.GasPool).AddGas(gasLimit) } - var coalescedLogs []*types.Log for { @@ -786,7 +817,7 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone { // Notify resubmit loop to increase resubmitting interval due to too frequent commits. if atomic.LoadInt32(interrupt) == commitInterruptResubmit { - ratio := float64(gasLimit-w.current.gasPool.Gas()) / float64(gasLimit) + ratio := float64(gasLimit-env.gasPool.Gas()) / float64(gasLimit) if ratio < 0.1 { ratio = 0.1 } @@ -798,8 +829,8 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin return atomic.LoadInt32(interrupt) == commitInterruptNewHead } // If we don't have enough gas for any further transactions then we're done - if w.current.gasPool.Gas() < params.TxGas { - log.Trace("Not enough gas for further transactions", "have", w.current.gasPool, "want", params.TxGas) + if env.gasPool.Gas() < params.TxGas { + log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", params.TxGas) break } // Retrieve the next transaction and abort if all done @@ -811,19 +842,19 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin // during transaction acceptance is the transaction pool. // // We use the eip155 signer regardless of the current hf. - from, _ := types.Sender(w.current.signer, tx) + from, _ := types.Sender(env.signer, tx) // Check whether the tx is replay protected. If we're not in the EIP155 hf // phase, start ignoring the sender until we do. - if tx.Protected() && !w.chainConfig.IsEIP155(w.current.header.Number) { + if tx.Protected() && !w.chainConfig.IsEIP155(env.header.Number) { log.Trace("Ignoring reply protected transaction", "hash", tx.Hash(), "eip155", w.chainConfig.EIP155Block) txs.Pop() continue } // Start executing the transaction - w.current.state.Prepare(tx.Hash(), w.current.tcount) + env.state.Prepare(tx.Hash(), env.tcount) - logs, err := w.commitTransaction(tx, coinbase) + logs, err := w.commitTransaction(env, tx, coinbase) switch { case errors.Is(err, core.ErrGasLimitReached): // Pop the current out-of-gas transaction without shifting in the next from the account @@ -843,7 +874,7 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin case errors.Is(err, nil): // Everything ok, collect the logs and shift in the next transaction from the same account coalescedLogs = append(coalescedLogs, logs...) - w.current.tcount++ + env.tcount++ txs.Shift() case errors.Is(err, core.ErrTxTypeNotSupported): @@ -882,116 +913,111 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin return false } -// commitNewWork generates several new sealing tasks based on the parent block. -func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) { +// generateParams wraps various of settings for generating sealing task. +type generateParams struct { + timestamp uint64 // The timstamp for sealing task + forceTime bool // Flag whether the given timestamp is immutable or not + parentHash common.Hash // Parent block hash, empty means the latest chain head + coinbase bool // Flag whether the coinbase field is required + noUncle bool // Flag whether the uncle block inclusion is allowed + noExtra bool // Flag whether the extra field assignment is allowed +} + +// prepareWork constructs the sealing task according to the given parameters, +// either based on the last chain head or specified parent. In this function +// the pending transactions are not filled yet, only the empty task returned. +func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { w.mu.RLock() defer w.mu.RUnlock() - tstart := time.Now() + // Find the parent block for sealing task parent := w.chain.CurrentBlock() - - if parent.Time() >= uint64(timestamp) { - timestamp = int64(parent.Time() + 1) + if genParams.parentHash != (common.Hash{}) { + parent = w.chain.GetBlockByHash(genParams.parentHash) + } + if parent == nil { + return nil, fmt.Errorf("missing parent") + } + // Sanity check the timestamp correctness, recap the timestamp + // to parent+1 if the mutation is allowed. + timestamp := genParams.timestamp + if parent.Time() >= timestamp { + if genParams.forceTime { + return nil, fmt.Errorf("invalid timestamp, parent %d given %d", parent.Time(), timestamp) + } + timestamp = parent.Time() + 1 } + // Construct the sealing block header, assign the extra field if it's allowed num := parent.Number() header := &types.Header{ ParentHash: parent.Hash(), Number: num.Add(num, common.Big1), GasLimit: core.CalcGasLimit(parent.GasUsed(), parent.GasLimit(), w.config.GasFloor, w.config.GasCeil), - Extra: w.extra, - Time: uint64(timestamp), + Time: timestamp, + } + if !genParams.noExtra && len(w.extra) != 0 { + header.Extra = w.extra } // Set baseFee and GasLimit if we are on an EIP-1559 chain if w.chainConfig.IsLondon(header.Number) { header.BaseFee = misc.CalcBaseFee(w.chainConfig, parent.Header()) parentGasLimit := parent.GasLimit() if !w.chainConfig.IsLondon(parent.Number()) { - // Bump by 2x parentGasLimit = parent.GasLimit() * params.ElasticityMultiplier } header.GasLimit = core.CalcGasLimit1559(parentGasLimit, w.config.GasCeil) } - // Only set the coinbase if our consensus engine is running (avoid spurious block rewards) - if w.isRunning() { + // Set the coinbase if the worker is running or it's required + if w.isRunning() || genParams.coinbase { if w.coinbase == (common.Address{}) { log.Error("Refusing to mine without etherbase") - return + return nil, errors.New("no etherbase specified") } header.Coinbase = w.coinbase } + // Run the consensus preparation with the default or customized consensus engine. if err := w.engine.Prepare(w.chain, header); err != nil { log.Error("Failed to prepare header for mining", "err", err) - return - } - // If we are care about TheDAO hard-fork check whether to override the extra-data or not - if daoBlock := w.chainConfig.DAOForkBlock; daoBlock != nil { - // Check whether the block is among the fork extra-override range - limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange) - if header.Number.Cmp(daoBlock) >= 0 && header.Number.Cmp(limit) < 0 { - // Depending whether we support or oppose the fork, override differently - if w.chainConfig.DAOForkSupport { - header.Extra = common.CopyBytes(params.DAOForkBlockExtra) - } else if bytes.Equal(header.Extra, params.DAOForkBlockExtra) { - header.Extra = []byte{} // If miner opposes, don't let it use the reserved extra-data - } - } + return nil, err } // Could potentially happen if starting to mine in an odd state. - err := w.makeCurrent(parent, header) + env, err := w.makeEnv(parent, header) if err != nil { log.Error("Failed to create mining context", "err", err) - return - } - // Create the current work task and check any fork transitions needed - env := w.current - if w.chainConfig.DAOForkSupport && w.chainConfig.DAOForkBlock != nil && w.chainConfig.DAOForkBlock.Cmp(header.Number) == 0 { - misc.ApplyDAOHardFork(env.state) + return nil, err } - // Accumulate the uncles for the current block - uncles := make([]*types.Header, 0, 2) - commitUncles := func(blocks map[common.Hash]*types.Block) { - // Clean up stale uncle blocks first - for hash, uncle := range blocks { - if uncle.NumberU64()+staleThreshold <= header.Number.Uint64() { - delete(blocks, hash) - } - } - for hash, uncle := range blocks { - if len(uncles) == 2 { - break - } - if err := w.commitUncle(env, uncle.Header()); err != nil { - log.Trace("Possible uncle rejected", "hash", hash, "reason", err) - } else { - log.Debug("Committing new uncle to block", "hash", hash) - uncles = append(uncles, uncle.Header()) + // Accumulate the uncles for the sealing work only if it's allowed. + if !genParams.noUncle { + commitUncles := func(blocks map[common.Hash]*types.Block) { + for hash, uncle := range blocks { + if env.uncleHashes.Cardinality() == 2 { + break + } + if err := w.commitUncle(env, uncle.Header()); err != nil { + log.Trace("Possible uncle rejected", "hash", hash, "reason", err) + } else { + log.Debug("Committing new uncle to block", "hash", hash) + } } } + // Prefer to locally generated uncle + commitUncles(w.localUncles) + commitUncles(w.remoteUncles) } - // Prefer to locally generated uncle - commitUncles(w.localUncles) - commitUncles(w.remoteUncles) - - // Create an empty block based on temporary copied state for - // sealing in advance without waiting block execution finished. - if !noempty && atomic.LoadUint32(&w.noempty) == 0 { - w.commit(uncles, nil, false, tstart) - } + return env, nil +} +// fillTransactions retrieves the pending transactions from the txpool and fills them +// into the given sealing block. The transaction selection and ordering strategy can +// be customized with the plugin in the future. +func (w *worker) fillTransactions(interrupt *int32, env *environment) error { + // Split the pending transactions into locals and remotes // Fill the block with all available pending transactions. pending, err := w.eth.TxPool().Pending(true) if err != nil { log.Error("Failed to fetch pending transactions", "err", err) - return - } - // Short circuit if there is no available pending transactions. - // But if we disable empty precommit already, ignore it. Since - // empty block is necessary to keep the liveness of the network. - if len(pending) == 0 && atomic.LoadUint32(&w.noempty) == 0 { - w.updateSnapshot() - return + return err } - // Split the pending transactions into locals and remotes localTxs, remoteTxs := make(map[common.Address]types.Transactions), pending for _, account := range w.eth.TxPool().Locals() { if txs := remoteTxs[account]; len(txs) > 0 { @@ -1000,40 +1026,81 @@ func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) } } if len(localTxs) > 0 { - txs := types.NewTransactionsByPriceAndNonce(w.current.signer, localTxs, header.BaseFee) - if w.commitTransactions(txs, w.coinbase, interrupt) { - return + txs := types.NewTransactionsByPriceAndNonce(env.signer, localTxs, env.header.BaseFee) + if w.commitTransactions(env, txs, w.coinbase, interrupt) { + return nil } } if len(remoteTxs) > 0 { - txs := types.NewTransactionsByPriceAndNonce(w.current.signer, remoteTxs, header.BaseFee) - if w.commitTransactions(txs, w.coinbase, interrupt) { - return + txs := types.NewTransactionsByPriceAndNonce(env.signer, remoteTxs, env.header.BaseFee) + if w.commitTransactions(env, txs, w.coinbase, interrupt) { + return nil } } - w.commit(uncles, w.fullTaskHook, true, tstart) + return nil } -// commit runs any post-transaction state modifications, assembles the final block -// and commits new work if consensus engine is running. -func (w *worker) commit(uncles []*types.Header, interval func(), update bool, start time.Time) error { - // Deep copy receipts here to avoid interaction between different tasks. - receipts := copyReceipts(w.current.receipts) - s := w.current.state.Copy() - block, err := w.engine.FinalizeAndAssemble(w.chain, w.current.header, s, w.current.txs, uncles, receipts) +// generateWork generates a sealing block based on the given parameters. +func (w *worker) generateWork(params *generateParams) (*types.Block, error) { + work, err := w.prepareWork(params) if err != nil { - return err + return nil, err + } + defer work.discard() + + if err := w.fillTransactions(nil, work); err != nil { + return nil, err + } + return w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.uncles, work.receipts) +} + +// commitWork generates several new sealing tasks based on the parent block +// and submit them to the sealer. +func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) { + start := time.Now() + work, err := w.prepareWork(&generateParams{timestamp: uint64(timestamp)}) + if err != nil { + return + } + // Create an empty block based on temporary copied state for + // sealing in advance without waiting block execution finished. + if !noempty && atomic.LoadUint32(&w.noempty) == 0 { + w.commit(work.copy(), nil, false, start) + } + // Fill pending transactions from the txpool + if err := w.fillTransactions(interrupt, work); err != nil { + return + } + w.commit(work.copy(), w.fullTaskHook, true, start) + + // Swap out the old work with the new one, terminating any leftover + // prefetcher processes in the mean time and starting a new one. + if w.current != nil { + w.current.discard() } + w.current = work +} + +// commit runs any post-transaction state modifications, assembles the final block +// and commits new work if consensus engine is running. +// Note the assumption is held that the mutation is allowed to the passed env, do +// the deep copy first. +func (w *worker) commit(env *environment, interval func(), update bool, start time.Time) error { if w.isRunning() { if interval != nil { interval() } + // Deep copy receipts here to avoid interaction between different tasks. + block, err := w.engine.FinalizeAndAssemble(w.chain, env.header, env.state, env.txs, env.uncles, env.receipts) + if err != nil { + return err + } select { - case w.taskCh <- &task{receipts: receipts, state: s, block: block, createdAt: time.Now()}: + case w.taskCh <- &task{receipts: env.receipts, state: env.state, block: block, createdAt: time.Now()}: w.unconfirmed.Shift(block.NumberU64() - 1) log.Info("Commit new mining work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()), - "uncles", len(uncles), "txs", w.current.tcount, - "gas", block.GasUsed(), "fees", totalFees(block, receipts), + "uncles", len(env.uncles), "txs", env.tcount, + "gas", block.GasUsed(), "fees", totalFees(block, env.receipts), "elapsed", common.PrettyDuration(time.Since(start))) case <-w.exitCh: @@ -1041,11 +1108,36 @@ func (w *worker) commit(uncles []*types.Header, interval func(), update bool, st } } if update { - w.updateSnapshot() + w.updateSnapshot(env) } return nil } +// getSealingBlock generates the sealing block based on the given parameters. +func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64) (*types.Block, error) { + req := &getWorkReq{ + params: &generateParams{ + timestamp: timestamp, + forceTime: true, + parentHash: parent, + coinbase: true, + noUncle: true, + noExtra: true, + }, + result: make(chan *types.Block, 1), + } + select { + case w.getWorkCh <- req: + block := <-req.result + if block == nil { + return nil, req.err + } + return block, nil + case <-w.exitCh: + return nil, errors.New("miner closed") + } +} + // copyReceipts makes a deep copy of the given receipts. func copyReceipts(receipts []*types.Receipt) []*types.Receipt { result := make([]*types.Receipt, len(receipts)) From 723ff866d404f3d19b5acd1387a4d1956d7d038d Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Fri, 23 Jul 2021 14:35:02 +0800 Subject: [PATCH 02/26] miner: rename mining to sealing --- miner/worker.go | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index 3cb2137c5a71f..efe3b6b895177 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -54,14 +54,14 @@ const ( // resubmitAdjustChanSize is the size of resubmitting interval adjustment channel. resubmitAdjustChanSize = 10 - // miningLogAtDepth is the number of confirmations before logging successful mining. - miningLogAtDepth = 7 + // sealingLogAtDepth is the number of confirmations before logging successful sealing. + sealingLogAtDepth = 7 - // minRecommitInterval is the minimal time interval to recreate the mining block with + // minRecommitInterval is the minimal time interval to recreate the sealing block with // any newly arrived transactions. minRecommitInterval = 1 * time.Second - // maxRecommitInterval is the maximum time interval to recreate the mining block with + // maxRecommitInterval is the maximum time interval to recreate the sealing block with // any newly arrived transactions. maxRecommitInterval = 15 * time.Second @@ -243,7 +243,7 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus isLocalBlock: isLocalBlock, localUncles: make(map[common.Hash]*types.Block), remoteUncles: make(map[common.Hash]*types.Block), - unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), miningLogAtDepth), + unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), sealingLogAtDepth), pendingTasks: make(map[common.Hash]*task), txsCh: make(chan core.NewTxsEvent, txChanSize), chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize), @@ -310,12 +310,12 @@ func (w *worker) setRecommitInterval(interval time.Duration) { } } -// disablePreseal disables pre-sealing mining feature +// disablePreseal disables pre-sealing feature func (w *worker) disablePreseal() { atomic.StoreUint32(&w.noempty, 1) } -// enablePreseal enables pre-sealing mining feature +// enablePreseal enables pre-sealing feature func (w *worker) enablePreseal() { atomic.StoreUint32(&w.noempty, 0) } @@ -400,7 +400,7 @@ func (w *worker) newWorkLoop(recommit time.Duration) { var ( interrupt *int32 minRecommit = recommit // minimal resubmit interval specified by user. - timestamp int64 // timestamp for each round of mining. + timestamp int64 // timestamp for each round of sealing. ) timer := time.NewTimer(0) @@ -445,7 +445,7 @@ func (w *worker) newWorkLoop(recommit time.Duration) { commit(false, commitInterruptNewHead) case <-timer.C: - // If mining is running resubmit a new work cycle periodically to pull in + // If sealing is running resubmit a new work cycle periodically to pull in // higher priced transactions. Disable this overhead for pending blocks. if w.isRunning() && (w.chainConfig.Clique == nil || w.chainConfig.Clique.Period > 0) { // Short circuit if no new transaction arrives. @@ -555,10 +555,10 @@ func (w *worker) mainLoop() { } case ev := <-w.txsCh: - // Apply transactions to the pending state if we're not mining. + // Apply transactions to the pending state if we're not sealing // // Note all transactions received may not be continuous with transactions - // already included in the current mining block. These transactions will + // already included in the current sealing block. These transactions will // be automatically eliminated. if !w.isRunning() && w.current != nil { // If block is already full, abort @@ -584,7 +584,7 @@ func (w *worker) mainLoop() { } } else { // Special case, if the consensus engine is 0 period clique(dev mode), - // submit mining work here since all empty submission will be rejected + // submit sealing work here since all empty submission will be rejected // by clique. Of course the advance sealing(empty submission) is disabled. if w.chainConfig.Clique != nil && w.chainConfig.Clique.Period == 0 { w.commitWork(nil, true, time.Now().Unix()) @@ -811,7 +811,7 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP // In the following three cases, we will interrupt the execution of the transaction. // (1) new head block event arrival, the interrupt signal is 1 // (2) worker start or restart, the interrupt signal is 1 - // (3) worker recreate the mining block with any newly arrived transactions, the interrupt signal is 2. + // (3) worker recreate the sealing block with any newly arrived transactions, the interrupt signal is 2. // For the first two cases, the semi-finished work will be discarded. // For the third case, the semi-finished work will be submitted to the consensus engine. if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone { @@ -891,8 +891,8 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP } if !w.isRunning() && len(coalescedLogs) > 0 { - // We don't push the pendingLogsEvent while we are mining. The reason is that - // when we are mining, the worker will regenerate a mining block every 3 seconds. + // We don't push the pendingLogsEvent while we are sealing. The reason is that + // when we are sealing, the worker will regenerate a sealing block every 3 seconds. // In order to avoid pushing the repeated pendingLog, we disable the pending log pushing. // make a copy, the state caches the logs and these logs get "upgraded" from pending to mined @@ -977,13 +977,13 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { } // Run the consensus preparation with the default or customized consensus engine. if err := w.engine.Prepare(w.chain, header); err != nil { - log.Error("Failed to prepare header for mining", "err", err) + log.Error("Failed to prepare header for sealing", "err", err) return nil, err } // Could potentially happen if starting to mine in an odd state. env, err := w.makeEnv(parent, header) if err != nil { - log.Error("Failed to create mining context", "err", err) + log.Error("Failed to create sealing context", "err", err) return nil, err } // Accumulate the uncles for the sealing work only if it's allowed. @@ -1098,7 +1098,7 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti select { case w.taskCh <- &task{receipts: env.receipts, state: env.state, block: block, createdAt: time.Now()}: w.unconfirmed.Shift(block.NumberU64() - 1) - log.Info("Commit new mining work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()), + log.Info("Commit new sealing work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()), "uncles", len(env.uncles), "txs", env.tcount, "gas", block.GasUsed(), "fees", totalFees(block, env.receipts), "elapsed", common.PrettyDuration(time.Since(start))) From f740a26397b694de9a43e842ad7fdf7ad5d2dabd Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Mon, 26 Jul 2021 14:32:48 +0800 Subject: [PATCH 03/26] miner: add test --- miner/worker.go | 15 ++++--- miner/worker_test.go | 104 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 6 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index efe3b6b895177..5cd351d7da9e1 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -110,7 +110,7 @@ func (env *environment) copy() *environment { if env.gasPool != nil { cpy.gasPool = &(*env.gasPool) } - // the content of txs and uncles are immutable, unnecessary + // The content of txs and uncles are immutable, unnecessary // to do the expensive deep copy for them. cpy.txs = make([]*types.Transaction, len(env.txs)) copy(cpy.txs, env.txs) @@ -150,7 +150,7 @@ type newWorkReq struct { timestamp int64 } -// getWorkReq represents a request for getting a unsealed block with provided parameters. +// getWorkReq represents a request for getting a new sealing work with provided parameters. type getWorkReq struct { params *generateParams err error @@ -500,7 +500,7 @@ func (w *worker) mainLoop() { defer w.chainHeadSub.Unsubscribe() defer w.chainSideSub.Unsubscribe() - cleanTicker := time.NewTicker(time.Minute) + cleanTicker := time.NewTicker(time.Second * 10) defer cleanTicker.Stop() for { @@ -509,12 +509,12 @@ func (w *worker) mainLoop() { w.commitWork(req.interrupt, req.noempty, req.timestamp) case req := <-w.getWorkCh: - unsealed, err := w.generateWork(req.params) + block, err := w.generateWork(req.params) if err != nil { req.err = err req.result <- nil } else { - req.result <- unsealed + req.result <- block } case ev := <-w.chainSideCh: @@ -721,7 +721,10 @@ func (w *worker) resultLoop() { // makeEnv creates a new environment for the sealing block. func (w *worker) makeEnv(parent *types.Block, header *types.Header) (*environment, error) { // Retrieve the parent state to execute on top and start a prefetcher for - // the miner to speed block sealing up a bit + // the miner to speed block sealing up a bit. Note since the sealing block + // can be created upon the arbitrary parent block, but the state of parent + // block may already be pruned, so the necessary state recovery is needed + // here in the future. TODO(rjl493456442). state, err := w.chain.StateAt(parent.Root()) if err != nil { return nil, err diff --git a/miner/worker_test.go b/miner/worker_test.go index 2bb6c9407bbec..00f46faa37c71 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -520,3 +520,107 @@ func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine co t.Error("interval reset timeout") } } + +func TestGetSealingWorkEthash(t *testing.T) { + testGetSealingWork(t, ethashChainConfig, ethash.NewFaker()) +} + +func TestGetSealingWorkClique(t *testing.T) { + testGetSealingWork(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase())) +} + +func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) { + defer engine.Close() + + w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0) + defer w.close() + + w.setExtra([]byte{0x01, 0x02}) + w.postSideBlock(core.ChainSideEvent{Block: b.uncleBlock}) + + w.skipSealHook = func(task *task) bool { + return true + } + w.fullTaskHook = func() { + time.Sleep(100 * time.Millisecond) + } + timestamp := uint64(time.Now().Unix()) + _, clique := engine.(*clique.Clique) + assertBlock := func(block *types.Block, number uint64) { + if block.Time() != timestamp { + t.Errorf("Invalid timestamp, want %d, get %d", timestamp, block.Time()) + } + if len(block.Uncles()) != 0 { + t.Error("Unexpected uncle block") + } + if !clique { + if len(block.Extra()) != 0 { + t.Error("Unexpected extra field") + } + if block.Coinbase() != w.coinbase { + t.Errorf("Invalid coinbase, want %x, get %x", w.coinbase, block.Coinbase()) + } + } + if block.MixDigest() != (common.Hash{}) { + t.Error("Unexpected mix digest") + } + if block.Nonce() != 0 { + t.Error("Unexpected block nonce") + } + if block.NumberU64() != number { + t.Errorf("Mismatched block number, want %d got %d", number, block.NumberU64()) + } + } + var cases = []struct { + parent common.Hash + expectNumber uint64 + expectErr bool + }{ + { + b.chain.Genesis().Hash(), + uint64(1), + false, + }, + { + b.chain.CurrentBlock().Hash(), + b.chain.CurrentBlock().NumberU64() + 1, + false, + }, + { + common.HexToHash("0xdeadbeef"), + 0, + true, + }, + } + + // This API should work even the automatic sealing is not enabled + for _, c := range cases { + block, err := w.getSealingBlock(c.parent, timestamp) + if c.expectErr { + if err == nil { + t.Error("Expect error but get nil") + } + } else { + if err != nil { + t.Errorf("Unexpected error %v", err) + } + assertBlock(block, c.expectNumber) + } + } + + // This API should work when the automatic sealing is enabled + w.start() + for _, c := range cases { + block, err := w.getSealingBlock(c.parent, timestamp) + if c.expectErr { + if err == nil { + t.Error("Expect error but get nil") + } + } else { + if err != nil { + t.Errorf("Unexpected error %v", err) + } + assertBlock(block, c.expectNumber) + } + } +} From 78212c881677ccb05a3216af42b407ab92418e86 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Mon, 9 Aug 2021 11:26:39 +0800 Subject: [PATCH 04/26] miner: address comments --- miner/worker.go | 70 ++++++++++++++++++++++++-------------------- miner/worker_test.go | 4 +-- 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index 5cd351d7da9e1..5120e58571a2a 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -82,30 +82,28 @@ const ( type environment struct { signer types.Signer - state *state.StateDB // apply state changes here - ancestors mapset.Set // ancestor set (used for checking uncle parent validity) - family mapset.Set // family set (used for checking uncle invalidity) - uncleHashes mapset.Set // uncle set - tcount int // tx count in cycle - gasPool *core.GasPool // available gas used to pack transactions + state *state.StateDB // apply state changes here + ancestors mapset.Set // ancestor set (used for checking uncle parent validity) + family mapset.Set // family set (used for checking uncle invalidity) + tcount int // tx count in cycle + gasPool *core.GasPool // available gas used to pack transactions header *types.Header txs []*types.Transaction receipts []*types.Receipt - uncles []*types.Header + uncles map[common.Hash]*types.Header } // copy creates a deep copy of environment. func (env *environment) copy() *environment { cpy := &environment{ - signer: env.signer, - state: env.state.Copy(), - ancestors: env.ancestors.Clone(), - family: env.family.Clone(), - uncleHashes: env.uncleHashes.Clone(), - tcount: env.tcount, - header: types.CopyHeader(env.header), - receipts: copyReceipts(env.receipts), + signer: env.signer, + state: env.state.Copy(), + ancestors: env.ancestors.Clone(), + family: env.family.Clone(), + tcount: env.tcount, + header: types.CopyHeader(env.header), + receipts: copyReceipts(env.receipts), } if env.gasPool != nil { cpy.gasPool = &(*env.gasPool) @@ -114,11 +112,22 @@ func (env *environment) copy() *environment { // to do the expensive deep copy for them. cpy.txs = make([]*types.Transaction, len(env.txs)) copy(cpy.txs, env.txs) - cpy.uncles = make([]*types.Header, len(env.uncles)) - copy(cpy.uncles, env.uncles) + cpy.uncles = make(map[common.Hash]*types.Header) + for hash, uncle := range env.uncles { + cpy.uncles[hash] = uncle + } return cpy } +// unclelist returns the contained uncles as the list format. +func (env *environment) unclelist() []*types.Header { + var uncles []*types.Header + for _, uncle := range env.uncles { + uncles = append(uncles, uncle) + } + return uncles +} + // discard terminates the background prefetcher go-routine. It should // always be called for all created environment instances otherwise // the go-routine leak can happen. @@ -534,7 +543,7 @@ func (w *worker) mainLoop() { // If our sealing block contains less than 2 uncle blocks, // add the new uncle block if valid and regenerate a new // sealing block for higher profit. - if w.isRunning() && w.current != nil && w.current.uncleHashes.Cardinality() < 2 { + if w.isRunning() && w.current != nil && len(w.current.uncles) < 2 { start := time.Now() if err := w.commitUncle(w.current, ev.Block.Header()); err == nil { w.commit(w.current.copy(), nil, true, start) @@ -732,12 +741,12 @@ func (w *worker) makeEnv(parent *types.Block, header *types.Header) (*environmen state.StartPrefetcher("miner") env := &environment{ - signer: types.MakeSigner(w.chainConfig, header.Number), - state: state, - ancestors: mapset.NewSet(), - family: mapset.NewSet(), - uncleHashes: mapset.NewSet(), - header: header, + signer: types.MakeSigner(w.chainConfig, header.Number), + state: state, + ancestors: mapset.NewSet(), + family: mapset.NewSet(), + header: header, + uncles: make(map[common.Hash]*types.Header), } // when 08 is processed ancestors contain 07 (quick block) for _, ancestor := range w.chain.GetBlocksFromHash(parent.Hash(), 7) { @@ -755,7 +764,7 @@ func (w *worker) makeEnv(parent *types.Block, header *types.Header) (*environmen // commitUncle adds the given block to uncle block set, returns error if failed to add. func (w *worker) commitUncle(env *environment, uncle *types.Header) error { hash := uncle.Hash() - if env.uncleHashes.Contains(hash) { + if _, exist := env.uncles[hash]; exist { return errors.New("uncle not unique") } if env.header.ParentHash == uncle.ParentHash { @@ -767,8 +776,7 @@ func (w *worker) commitUncle(env *environment, uncle *types.Header) error { if env.family.Contains(hash) { return errors.New("uncle already included") } - env.uncleHashes.Add(uncle.Hash()) - env.uncles = append(env.uncles, uncle) + env.uncles[hash] = uncle return nil } @@ -781,7 +789,7 @@ func (w *worker) updateSnapshot(env *environment) { w.snapshotBlock = types.NewBlock( env.header, env.txs, - env.uncles, + env.unclelist(), env.receipts, trie.NewStackTrie(nil), ) @@ -993,7 +1001,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { if !genParams.noUncle { commitUncles := func(blocks map[common.Hash]*types.Block) { for hash, uncle := range blocks { - if env.uncleHashes.Cardinality() == 2 { + if len(env.uncles) == 2 { break } if err := w.commitUncle(env, uncle.Header()); err != nil { @@ -1054,7 +1062,7 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) { if err := w.fillTransactions(nil, work); err != nil { return nil, err } - return w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.uncles, work.receipts) + return w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts) } // commitWork generates several new sealing tasks based on the parent block @@ -1094,7 +1102,7 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti interval() } // Deep copy receipts here to avoid interaction between different tasks. - block, err := w.engine.FinalizeAndAssemble(w.chain, env.header, env.state, env.txs, env.uncles, env.receipts) + block, err := w.engine.FinalizeAndAssemble(w.chain, env.header, env.state, env.txs, env.unclelist(), env.receipts) if err != nil { return err } diff --git a/miner/worker_test.go b/miner/worker_test.go index 00f46faa37c71..c9b2564d1c3d9 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -593,7 +593,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co }, } - // This API should work even the automatic sealing is not enabled + // This API should work even when the automatic sealing is not enabled for _, c := range cases { block, err := w.getSealingBlock(c.parent, timestamp) if c.expectErr { @@ -608,7 +608,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co } } - // This API should work when the automatic sealing is enabled + // This API should work even when the automatic sealing is enabled w.start() for _, c := range cases { block, err := w.getSealingBlock(c.parent, timestamp) From 7907fcc2238c945ced2386e7fcc72330242eaec2 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 9 Aug 2021 13:45:53 +0200 Subject: [PATCH 05/26] wip: multicollator --- miner/multi_collator.go | 110 ++++++++++++++++++++++++++++++++++++++++ miner/worker.go | 3 ++ 2 files changed, 113 insertions(+) create mode 100644 miner/multi_collator.go diff --git a/miner/multi_collator.go b/miner/multi_collator.go new file mode 100644 index 0000000000000..c8c3b0b1b0abb --- /dev/null +++ b/miner/multi_collator.go @@ -0,0 +1,110 @@ +type collatorWork { + env *environment + counter uint64 +} + +type collator struct { + newWorkCh chan<- collatorWork + workResultCh chan<-collatorWork + // channel signalling collator loop should exit + exitCh chan<-interface{} + newHeadCh chan<-types.Header + collatorImpl Collator +} + +func (c *collator) mainLoop() { + for { + case newWork := <-c.newWorkCh: + // pass a wrapped CollatorBlockState object to the collator + // implementation. collator calls Commit() to flush new work to the + // result channel. + // TODO... + + // signal to the exitCh that the collator is done + // computing this work. + // TODO... + c.workResultCh <- collatorWork{nil, newWork.counter} + fallthrough + case newHead := <-newHeadCh: + fallthrough + case <-c.exitCh: + // TODO any cleanup needed? + break + default: + fallthrough + } +} + +type MultiCollator struct { + workResultCh + counter uint64 + responsiveCollatorCount uint + collators []collator +} + +func (m *MultiCollator) Start() { + for c := range m.collators { + go c.mainLoop() + } +} + +func (m *MultiCollator) Stop() { + for c := range m.collators { + select { + case c.exitCh + } + } +} + +func (m *MultiCollator) CollateBlock(work *environment) { + m.responsiveCollatorCount = 0 + for c := range m.collators { + select { + case c.newWorkCh <- collatorWork{env: work.copy(), counter: m.counter} + m.responsiveCollatorCount++ + } + } +} + + +func (m *MultiCollator) collect(work *environment, cb workResult) { + finishedCollators := 0 + for ; finishedCollators != m.responsiveCollatorCount; { + for c := range m.collators { + select { + case response := m.workResultCh: + // ignore collators responding from old work rounds + if response.counter != m.counter { + continue + } + + // ignore responses from collators that have already signalled they are done + // TODO + + cb(response.work) + default: + continue + } + } + } +} + +// Collect sends the work request to all collators and blocks until all have +// sent their result, or times out after x seconds +func (m *MultiCollator) Collect(work *environment) { + +} + +// StreamAndCommit sends the work request to all collator goroutines. +// It collects the results and calls worker.commit() on them as they come back +func (m *MultiCollator) StreamAndCommit(w *worker, work *environment) { + +} + +func (m *MultiCollator) NewHeadHook() { + +} + +func (m *Multicollator) SideChainHook() { + +} diff --git a/miner/worker.go b/miner/worker.go index 5120e58571a2a..7d2a332fd5675 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1059,9 +1059,12 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) { } defer work.discard() + w.multiCollator.CollateBlock(work) if err := w.fillTransactions(nil, work); err != nil { return nil, err } + + candidates := w.multiCollator.Collect() return w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts) } From 6ab531b9a7d79f8a9c5e4208eef98b456cd410e4 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 9 Aug 2021 20:42:23 +0200 Subject: [PATCH 06/26] more --- miner/multi_collator.go | 85 +++++++++++++++++++++++++++++++---------- miner/worker.go | 8 ++++ 2 files changed, 73 insertions(+), 20 deletions(-) diff --git a/miner/multi_collator.go b/miner/multi_collator.go index c8c3b0b1b0abb..4735601b81130 100644 --- a/miner/multi_collator.go +++ b/miner/multi_collator.go @@ -1,6 +1,29 @@ type collatorWork { env *environment counter uint64 + interrupt *int32 +} + +type collatorBlockState struct { + work collatorWork + c *collator +} + +func (w *collatorWork) Copy() collatorWork { + newEnv := w.copy() + return collatorWork{ + env: newEnv, + counter: w.counter, + interrupt: w.interrupt, + } +} + +func (b *collatorBlockState) AddTransactions() { + +} + +func (bs *collatorBlockState) Commit() { + bs.c.workResultch <- bs.work } type collator struct { @@ -9,7 +32,7 @@ type collator struct { // channel signalling collator loop should exit exitCh chan<-interface{} newHeadCh chan<-types.Header - collatorImpl Collator + collateBlockImpl BlockCollator } func (c *collator) mainLoop() { @@ -19,6 +42,7 @@ func (c *collator) mainLoop() { // implementation. collator calls Commit() to flush new work to the // result channel. // TODO... + c.collateBlockImpl(collatorBlockState{newWork, c}) // signal to the exitCh that the collator is done // computing this work. @@ -56,21 +80,37 @@ func (m *MultiCollator) Stop() { } } -func (m *MultiCollator) CollateBlock(work *environment) { +func (m *MultiCollator) CollateBlock(work *environment, interrupt *int32) { + if m.counter == math.Uint64Max { + m.counter = 0 + } else { + m.counter++ + } m.responsiveCollatorCount = 0 + m.interrupt = interrupt for c := range m.collators { select { - case c.newWorkCh <- collatorWork{env: work.copy(), counter: m.counter} + case c.newWorkCh <- collatorWork{env: work.copy(), counter: m.counter, interrupt: interrupt} m.responsiveCollatorCount++ } } } -func (m *MultiCollator) collect(work *environment, cb workResult) { - finishedCollators := 0 - for ; finishedCollators != m.responsiveCollatorCount; { - for c := range m.collators { +func (m *MultiCollator) Collect(work *environment, cb workResult) { + finishedCollators := []uint{} + shouldAdjustRecommitDown := true + for { + if finishedCollators == m.responsiveCollatorcount { + break + } + + if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone { + // TODO: if the interrupt was recommit, signal to the worker to adjust up + shouldAdjustRecommitDown = false + } + + for i, c := range m.collators { select { case response := m.workResultCh: // ignore collators responding from old work rounds @@ -79,26 +119,31 @@ func (m *MultiCollator) collect(work *environment, cb workResult) { } // ignore responses from collators that have already signalled they are done - // TODO + shouldIgnore := false + for _, finishedCollator := range finishedCollators { + if i == finishedCollator { + shouldIgnore = true + } + } + if shouldIgnore { + break + } - cb(response.work) + // nil for work signals the collator won't send back any more blocks for this round + if response.work == nil { + finishedCollators = append(finishedCollators, i) + } else { + cb(response.work) + } default: continue } } } -} - -// Collect sends the work request to all collators and blocks until all have -// sent their result, or times out after x seconds -func (m *MultiCollator) Collect(work *environment) { - -} - -// StreamAndCommit sends the work request to all collator goroutines. -// It collects the results and calls worker.commit() on them as they come back -func (m *MultiCollator) StreamAndCommit(w *worker, work *environment) { + if shouldAdjustRecommitDown { + // TODO signal to worker to adjust recommit interval down + } } func (m *MultiCollator) NewHeadHook() { diff --git a/miner/worker.go b/miner/worker.go index 7d2a332fd5675..dcc41aa35a6a0 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1081,11 +1081,19 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) { if !noempty && atomic.LoadUint32(&w.noempty) == 0 { w.commit(work.copy(), nil, false, start) } + // start concurrent block collation for custom collators + w.multiCollator.CollateBlock(&work) // Fill pending transactions from the txpool if err := w.fillTransactions(interrupt, work); err != nil { return } w.commit(work.copy(), w.fullTaskHook, true, start) + // + cb := func(work *environment) { + // probably don't need to copy again here but do it for now just to be safe + w.commit(work.copy(), w.fullTaskHooke, true, start) + } + w.multiCollator.Collect(cb) // Swap out the old work with the new one, terminating any leftover // prefetcher processes in the mean time and starting a new one. From d2f078c920e3943f295e9e8a0ba76bd6356671c9 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 9 Aug 2021 22:25:28 +0200 Subject: [PATCH 07/26] moar --- miner/multi_collator.go | 104 ++++++++++++++++++++++++++++++++++++++-- miner/worker.go | 24 +++++----- 2 files changed, 111 insertions(+), 17 deletions(-) diff --git a/miner/multi_collator.go b/miner/multi_collator.go index 4735601b81130..f647ecbe396dd 100644 --- a/miner/multi_collator.go +++ b/miner/multi_collator.go @@ -1,3 +1,23 @@ +type AddTransactionsResultFunc func(error, []*types.Receipt) bool + +// BlockState represents a block-to-be-mined, which is being assembled. +// A collator can add transactions by calling AddTransactions +type BlockState interface { + // AddTransactions adds the sequence of transactions to the blockstate. Either all + // transactions are added, or none of them. In the latter case, the error + // describes the reason why the txs could not be included. + // if ErrRecommit, the collator should not attempt to add more transactions to the + // block and submit the block for sealing. + // If ErrAbort is returned, the collator should immediately abort and return a + // value (true) from CollateBlock which indicates to the miner to discard the + // block + AddTransactions(sequence types.Transactions, cb AddTransactionsResultFunc) + Gas() (remaining uint64) + Coinbase() common.Address + BaseFee() *big.Int + Signer() types.Signer +} + type collatorWork { env *environment counter uint64 @@ -7,6 +27,7 @@ type collatorWork { type collatorBlockState struct { work collatorWork c *collator + done bool } func (w *collatorWork) Copy() collatorWork { @@ -18,11 +39,85 @@ func (w *collatorWork) Copy() collatorWork { } } -func (b *collatorBlockState) AddTransactions() { - +func (b *collatorBlockState) AddTransactions(sequence types.Transactions, cb AddTransactionsResultFunc) { + var ( + interrupt = bs.work.interrupt + header = bs.work.env.header + gasPool = bs.work.env.gasPool + signer = bs.work.env.signer + chainConfig = bs.c.chainConfig + chain = bs.c.chain + +// --------------- + w = bs.worker + snap = w.current.state.Snapshot() + err error + logs []*types.Log + tcount = w.current.tcount + startTCount = w.current.tcount + ) + if bs.done { + cb(ErrRecommit, nil) + return + } + + for _, tx := range sequence { + if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone { + bs.done = true + break + } + if gasPool.Gas() < params.TxGas { + log.Trace("Not enough gas for further transactions", "have", gasPool, "want", params.TxGas) + err = core.ErrGasLimitReached + break + } + from, _ := types.Sender(signer, tx) + // Check whether the tx is replay protected. If we're not in the EIP155 hf + // phase, start ignoring the sender until we do. + if tx.Protected() && !w.chainConfig.IsEIP155(header.Number) { + log.Trace("encountered replay-protected transaction when chain doesn't support replay protection", "hash", tx.Hash(), "eip155", w.chainConfig.EIP155Block) + err = ErrUnsupportedEIP155Tx + break + } + // Start executing the transaction + bs.state.Prepare(tx.Hash(), w.current.tcount) + + var txLogs []*types.Log + txLogs, err = commitTransaction(chain, chainConfig, tx, bs.Coinbase()) + if err == nil { + logs = append(logs, txLogs...) + tcount++ + } else { + log.Trace("Tx block inclusion failed", "sender", from, "nonce", tx.Nonce(), + "type", tx.Type(), "hash", tx.Hash(), "err", err) + break + } + } + var txReceipts []*types.Receipt = nil + if err == nil { + txReceipts = w.current.receipts[startTCount:tcount] + } + // TODO: deep copy the tx receipts here or add a disclaimer to implementors not to modify them? + shouldRevert := cb(err, txReceipts) + + if err != nil || shouldRevert { + bs.state.RevertToSnapshot(snap) + + // remove the txs and receipts that were added + for i := startTCount; i < tcount; i++ { + w.current.txs[i] = nil + w.current.receipts[i] = nil + } + w.current.txs = w.current.txs[:startTCount] + w.current.receipts = w.current.receipts[:startTCount] + } else { + bs.logs = append(bs.logs, logs...) + w.current.tcount = tcount + } } func (bs *collatorBlockState) Commit() { + bs.done = true bs.c.workResultch <- bs.work } @@ -33,6 +128,8 @@ type collator struct { exitCh chan<-interface{} newHeadCh chan<-types.Header collateBlockImpl BlockCollator + chainConfig *params.ChainConfig + chain *core.BlockChain } func (c *collator) mainLoop() { @@ -41,14 +138,11 @@ func (c *collator) mainLoop() { // pass a wrapped CollatorBlockState object to the collator // implementation. collator calls Commit() to flush new work to the // result channel. - // TODO... c.collateBlockImpl(collatorBlockState{newWork, c}) // signal to the exitCh that the collator is done // computing this work. - // TODO... c.workResultCh <- collatorWork{nil, newWork.counter} - fallthrough case newHead := <-newHeadCh: fallthrough case <-c.exitCh: diff --git a/miner/worker.go b/miner/worker.go index dcc41aa35a6a0..1d4259db8a657 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -797,18 +797,18 @@ func (w *worker) updateSnapshot(env *environment) { w.snapshotState = env.state.Copy() } -func (w *worker) commitTransaction(env *environment, tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) { - snap := env.state.Snapshot() +func commitTransaction(chain *core.BlockChain, chainConfig *core.ChainConfig, env *environment, tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) { + snap := env.state.Snapshot() - receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *w.chain.GetVMConfig()) - if err != nil { - env.state.RevertToSnapshot(snap) - return nil, err - } - env.txs = append(env.txs, tx) - env.receipts = append(env.receipts, receipt) + receipt, err := core.ApplyTransaction(chainConfig, chain, &coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *chain.GetVMConfig()) + if err != nil { + env.state.RevertToSnapshot(snap) + return nil, err + } + env.txs = append(env.txs, tx) + env.receipts = append(env.receipts, receipt) - return receipt.Logs, nil + return receipt.Logs, nil } func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByPriceAndNonce, coinbase common.Address, interrupt *int32) bool { @@ -865,7 +865,7 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP // Start executing the transaction env.state.Prepare(tx.Hash(), env.tcount) - logs, err := w.commitTransaction(env, tx, coinbase) + logs, err := commitTransaction(w.chain, w.chainConfig, env, tx, coinbase) switch { case errors.Is(err, core.ErrGasLimitReached): // Pop the current out-of-gas transaction without shifting in the next from the account @@ -1059,7 +1059,7 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) { } defer work.discard() - w.multiCollator.CollateBlock(work) + w.multiCollator.CollateBlock(work, nil) if err := w.fillTransactions(nil, work); err != nil { return nil, err } From 2b6a16fbb7b8f05e32907d8309f7e0e282563bc4 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 9 Aug 2021 23:49:43 +0200 Subject: [PATCH 08/26] wip --- miner/multi_collator.go | 72 +++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/miner/multi_collator.go b/miner/multi_collator.go index f647ecbe396dd..b0aed63b26f83 100644 --- a/miner/multi_collator.go +++ b/miner/multi_collator.go @@ -39,7 +39,7 @@ func (w *collatorWork) Copy() collatorWork { } } -func (b *collatorBlockState) AddTransactions(sequence types.Transactions, cb AddTransactionsResultFunc) { +func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb AddTransactionsResultFunc) { var ( interrupt = bs.work.interrupt header = bs.work.env.header @@ -47,10 +47,10 @@ func (b *collatorBlockState) AddTransactions(sequence types.Transactions, cb Add signer = bs.work.env.signer chainConfig = bs.c.chainConfig chain = bs.c.chain - + state = bs.work.env.state + snap = state.Snapshot() // --------------- w = bs.worker - snap = w.current.state.Snapshot() err error logs []*types.Log tcount = w.current.tcount @@ -80,12 +80,12 @@ func (b *collatorBlockState) AddTransactions(sequence types.Transactions, cb Add break } // Start executing the transaction - bs.state.Prepare(tx.Hash(), w.current.tcount) + state.Prepare(tx.Hash(), w.current.tcount) var txLogs []*types.Log txLogs, err = commitTransaction(chain, chainConfig, tx, bs.Coinbase()) if err == nil { - logs = append(logs, txLogs...) + //logs = append(logs, txLogs...) tcount++ } else { log.Trace("Tx block inclusion failed", "sender", from, "nonce", tx.Nonce(), @@ -95,23 +95,23 @@ func (b *collatorBlockState) AddTransactions(sequence types.Transactions, cb Add } var txReceipts []*types.Receipt = nil if err == nil { - txReceipts = w.current.receipts[startTCount:tcount] + txReceipts = bs.work.env.receipts[startTCount:tcount] } // TODO: deep copy the tx receipts here or add a disclaimer to implementors not to modify them? shouldRevert := cb(err, txReceipts) if err != nil || shouldRevert { - bs.state.RevertToSnapshot(snap) + state.RevertToSnapshot(snap) // remove the txs and receipts that were added for i := startTCount; i < tcount; i++ { - w.current.txs[i] = nil - w.current.receipts[i] = nil + bs.work.env.txs[i] = nil + bs.work.env.receipts[i] = nil } - w.current.txs = w.current.txs[:startTCount] - w.current.receipts = w.current.receipts[:startTCount] + bs.work.env.txs = bs.work.env.txs[:startTCount] + bs.work.env.receipts = bs.work.env.receipts[:startTCount] } else { - bs.logs = append(bs.logs, logs...) + //bs.logs = append(bs.logs, logs...) w.current.tcount = tcount } } @@ -134,22 +134,25 @@ type collator struct { func (c *collator) mainLoop() { for { - case newWork := <-c.newWorkCh: - // pass a wrapped CollatorBlockState object to the collator - // implementation. collator calls Commit() to flush new work to the - // result channel. - c.collateBlockImpl(collatorBlockState{newWork, c}) - - // signal to the exitCh that the collator is done - // computing this work. - c.workResultCh <- collatorWork{nil, newWork.counter} - case newHead := <-newHeadCh: - fallthrough - case <-c.exitCh: - // TODO any cleanup needed? - break - default: - fallthrough + select { + case newWork := <-c.newWorkCh: + // pass a wrapped CollatorBlockState object to the collator + // implementation. collator calls Commit() to flush new work to the + // result channel. + c.collateBlockImpl(collatorBlockState{newWork, c}) + + // signal to the exitCh that the collator is done + // computing this work. + c.workResultCh <- collatorWork{nil, newWork.counter} + case <-c.exitCh: + // TODO any cleanup needed? + return + case newHead := <-newHeadCh: + // TODO call hook here + fallthrough + default: + fallthrough + } } } @@ -169,7 +172,10 @@ func (m *MultiCollator) Start() { func (m *MultiCollator) Stop() { for c := range m.collators { select { - case c.exitCh + case c.exitCh<-true: + fallthrough + default: + continue } } } @@ -194,6 +200,7 @@ func (m *MultiCollator) CollateBlock(work *environment, interrupt *int32) { func (m *MultiCollator) Collect(work *environment, cb workResult) { finishedCollators := []uint{} shouldAdjustRecommitDown := true + for { if finishedCollators == m.responsiveCollatorcount { break @@ -209,7 +216,7 @@ func (m *MultiCollator) Collect(work *environment, cb workResult) { case response := m.workResultCh: // ignore collators responding from old work rounds if response.counter != m.counter { - continue + break } // ignore responses from collators that have already signalled they are done @@ -230,7 +237,7 @@ func (m *MultiCollator) Collect(work *environment, cb workResult) { cb(response.work) } default: - continue + fallthrough } } } @@ -240,6 +247,8 @@ func (m *MultiCollator) Collect(work *environment, cb workResult) { } } +/* +TODO implement and hook these into the miner func (m *MultiCollator) NewHeadHook() { } @@ -247,3 +256,4 @@ func (m *MultiCollator) NewHeadHook() { func (m *Multicollator) SideChainHook() { } +/* From 1e57ec2b139b749602e64bcb89463ff6ef3bd6f6 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Tue, 10 Aug 2021 00:14:07 +0200 Subject: [PATCH 09/26] more --- miner/multi_collator.go | 3 ++- miner/worker.go | 25 ++++++++++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/miner/multi_collator.go b/miner/multi_collator.go index b0aed63b26f83..85c2361670434 100644 --- a/miner/multi_collator.go +++ b/miner/multi_collator.go @@ -196,8 +196,9 @@ func (m *MultiCollator) CollateBlock(work *environment, interrupt *int32) { } } +type WorkResult func(environment) -func (m *MultiCollator) Collect(work *environment, cb workResult) { +func (m *MultiCollator) Collect(work *environment, cb WorkResult) { finishedCollators := []uint{} shouldAdjustRecommitDown := true diff --git a/miner/worker.go b/miner/worker.go index 1d4259db8a657..8e41961af40a5 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1064,7 +1064,17 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) { return nil, err } - candidates := w.multiCollator.Collect() + var candidate environment + profit := big.NewInt(0) + + chooseMostProfitableBlock := func(e environment) { + if e.profit.Gt(profit) { + candidate = e + profit.Set(e.profit) + } + } + w.multiCollator.Collect(chooseMostProfitableBlock) + return w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts) } @@ -1088,10 +1098,15 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) { return } w.commit(work.copy(), w.fullTaskHook, true, start) - // - cb := func(work *environment) { - // probably don't need to copy again here but do it for now just to be safe - w.commit(work.copy(), w.fullTaskHooke, true, start) + + profit = new(big.Int) + profit.Set(work.profit) + cb := func(newWork environment) { + if newWork.profit.Gt(profit) { + // probably don't need to copy work again here but do it for now just to be safe + w.commit(newWork.copy(), w.fullTaskHooke, true, start) + profit.Set(newWork.profit) + } } w.multiCollator.Collect(cb) From 83bf92a0b307ebcf4ba5eef4230215a483ccf931 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Tue, 10 Aug 2021 13:58:42 +0200 Subject: [PATCH 10/26] moar --- miner/multi_collator.go | 21 ++++++++++++++++++--- miner/worker.go | 12 ++++++++---- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/miner/multi_collator.go b/miner/multi_collator.go index 85c2361670434..a6600cb6d8926 100644 --- a/miner/multi_collator.go +++ b/miner/multi_collator.go @@ -49,6 +49,8 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad chain = bs.c.chain state = bs.work.env.state snap = state.Snapshot() + curProfit = big.NewInt(0) + coinbaseBalanceBefore = bs.work.state.GetBalance(bs.work.header.Coinbase) // --------------- w = bs.worker err error @@ -61,6 +63,11 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad return } + gasPrice, err := tx.EffectiveGasTip(bs.work.env.header.BaseFee) + if err != nil { + return nil, err + } + for _, tx := range sequence { if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone { bs.done = true @@ -86,6 +93,8 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad txLogs, err = commitTransaction(chain, chainConfig, tx, bs.Coinbase()) if err == nil { //logs = append(logs, txLogs...) + gasUsed := new(big.Int).SetUint64(bs.work.env.receipts[-1].GasUsed) + curProfit.Add(curProfit, gasUsed.Mul(gasUsed, gasPrice)) tcount++ } else { log.Trace("Tx block inclusion failed", "sender", from, "nonce", tx.Nonce(), @@ -111,14 +120,20 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad bs.work.env.txs = bs.work.env.txs[:startTCount] bs.work.env.receipts = bs.work.env.receipts[:startTCount] } else { + coinbaseBalanceAfter := bs.work.state.GetBalance(bs.work.header.Coinbase) + coinbaseTransfer := big.NewInt(0).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore) + curProfit.Add(curProfit, coinbaseTransfer) //bs.logs = append(bs.logs, logs...) - w.current.tcount = tcount + bs.env.profit = curProfit + bs.env.tcount = tcount } } func (bs *collatorBlockState) Commit() { - bs.done = true - bs.c.workResultch <- bs.work + if !bs.done { + bs.done = true + bs.c.workResultch <- bs.work + } } type collator struct { diff --git a/miner/worker.go b/miner/worker.go index 8e41961af40a5..f9a0d1d30d973 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -92,6 +92,7 @@ type environment struct { txs []*types.Transaction receipts []*types.Receipt uncles map[common.Hash]*types.Header + profit *big.Int } // copy creates a deep copy of environment. @@ -1064,18 +1065,19 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) { return nil, err } - var candidate environment + var bestWork environment = work profit := big.NewInt(0) chooseMostProfitableBlock := func(e environment) { if e.profit.Gt(profit) { - candidate = e + bestWork.discard() + bestWork = e profit.Set(e.profit) } } w.multiCollator.Collect(chooseMostProfitableBlock) - return w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts) + return w.engine.FinalizeAndAssemble(w.chain, bestWork.header, bestWork.state, bestWork.txs, bestWork.unclelist(), bestWork.receipts) } // commitWork generates several new sealing tasks based on the parent block @@ -1101,11 +1103,13 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) { profit = new(big.Int) profit.Set(work.profit) + curBest := work cb := func(newWork environment) { if newWork.profit.Gt(profit) { // probably don't need to copy work again here but do it for now just to be safe w.commit(newWork.copy(), w.fullTaskHooke, true, start) profit.Set(newWork.profit) + curBest = newWork } } w.multiCollator.Collect(cb) @@ -1115,7 +1119,7 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) { if w.current != nil { w.current.discard() } - w.current = work + w.current = curBest } // commit runs any post-transaction state modifications, assembles the final block From bdc853b62613a86d2d12a5bbb198c0bf05989c21 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Tue, 10 Aug 2021 14:30:03 +0200 Subject: [PATCH 11/26] moar --- miner/multi_collator.go | 40 +++++++++++++++++++++------------------- miner/worker.go | 4 ++-- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/miner/multi_collator.go b/miner/multi_collator.go index a6600cb6d8926..b6b07219ed2bf 100644 --- a/miner/multi_collator.go +++ b/miner/multi_collator.go @@ -24,6 +24,12 @@ type collatorWork { interrupt *int32 } +// Pool is an interface to the transaction pool +type Pool interface { + Pending(bool) (map[common.Address]types.Transactions, error) + Locals() []common.Address +} + type collatorBlockState struct { work collatorWork c *collator @@ -41,7 +47,7 @@ func (w *collatorWork) Copy() collatorWork { func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb AddTransactionsResultFunc) { var ( - interrupt = bs.work.interrupt + interrupt = bs.work.env.interrupt header = bs.work.env.header gasPool = bs.work.env.gasPool signer = bs.work.env.signer @@ -50,13 +56,11 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad state = bs.work.env.state snap = state.Snapshot() curProfit = big.NewInt(0) - coinbaseBalanceBefore = bs.work.state.GetBalance(bs.work.header.Coinbase) -// --------------- - w = bs.worker + coinbaseBalanceBefore = state.GetBalance(bs.work.header.Coinbase) + tcount = bs.work.env.tcount err error logs []*types.Log - tcount = w.current.tcount - startTCount = w.current.tcount + startTCount = bs.work.env.tcount ) if bs.done { cb(ErrRecommit, nil) @@ -81,18 +85,18 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad from, _ := types.Sender(signer, tx) // Check whether the tx is replay protected. If we're not in the EIP155 hf // phase, start ignoring the sender until we do. - if tx.Protected() && !w.chainConfig.IsEIP155(header.Number) { - log.Trace("encountered replay-protected transaction when chain doesn't support replay protection", "hash", tx.Hash(), "eip155", w.chainConfig.EIP155Block) + if tx.Protected() && !chainConfig.IsEIP155(header.Number) { + log.Trace("encountered replay-protected transaction when chain doesn't support replay protection", "hash", tx.Hash(), "eip155", chainConfig.EIP155Block) err = ErrUnsupportedEIP155Tx break } // Start executing the transaction - state.Prepare(tx.Hash(), w.current.tcount) + state.Prepare(tx.Hash(), bs.work.env.tcount) var txLogs []*types.Log txLogs, err = commitTransaction(chain, chainConfig, tx, bs.Coinbase()) if err == nil { - //logs = append(logs, txLogs...) + logs = append(logs, txLogs...) gasUsed := new(big.Int).SetUint64(bs.work.env.receipts[-1].GasUsed) curProfit.Add(curProfit, gasUsed.Mul(gasUsed, gasPrice)) tcount++ @@ -120,10 +124,10 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad bs.work.env.txs = bs.work.env.txs[:startTCount] bs.work.env.receipts = bs.work.env.receipts[:startTCount] } else { - coinbaseBalanceAfter := bs.work.state.GetBalance(bs.work.header.Coinbase) + coinbaseBalanceAfter := bs.work.state.GetBalance(bs.work.env.header.Coinbase) coinbaseTransfer := big.NewInt(0).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore) curProfit.Add(curProfit, coinbaseTransfer) - //bs.logs = append(bs.logs, logs...) + bs.work.env.logs = append(bs.logs, logs...) bs.env.profit = curProfit bs.env.tcount = tcount } @@ -136,13 +140,15 @@ func (bs *collatorBlockState) Commit() { } } +type CollateBlockfunc func (bs BlockState, pool Pool) + type collator struct { newWorkCh chan<- collatorWork workResultCh chan<-collatorWork // channel signalling collator loop should exit exitCh chan<-interface{} newHeadCh chan<-types.Header - collateBlockImpl BlockCollator + collateBlockImpl CollateBlockFunc chainConfig *params.ChainConfig chain *core.BlockChain } @@ -151,11 +157,7 @@ func (c *collator) mainLoop() { for { select { case newWork := <-c.newWorkCh: - // pass a wrapped CollatorBlockState object to the collator - // implementation. collator calls Commit() to flush new work to the - // result channel. - c.collateBlockImpl(collatorBlockState{newWork, c}) - + c.collateBlockImpl(collatorBlockState{work: newWork, c: c, done: false}) // signal to the exitCh that the collator is done // computing this work. c.workResultCh <- collatorWork{nil, newWork.counter} @@ -195,7 +197,7 @@ func (m *MultiCollator) Stop() { } } -func (m *MultiCollator) CollateBlock(work *environment, interrupt *int32) { +func (m *MultiCollator) SuggestBlock(work *environment, interrupt *int32) { if m.counter == math.Uint64Max { m.counter = 0 } else { diff --git a/miner/worker.go b/miner/worker.go index f9a0d1d30d973..d474a1d350f31 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1060,7 +1060,7 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) { } defer work.discard() - w.multiCollator.CollateBlock(work, nil) + w.multiCollator.SuggestBlock(work, nil) if err := w.fillTransactions(nil, work); err != nil { return nil, err } @@ -1094,7 +1094,7 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) { w.commit(work.copy(), nil, false, start) } // start concurrent block collation for custom collators - w.multiCollator.CollateBlock(&work) + w.multiCollator.SuggestBlock(&work) // Fill pending transactions from the txpool if err := w.fillTransactions(interrupt, work); err != nil { return From 31cfd2592a32ba54bdda9963d2ac615ccd9b4469 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Tue, 10 Aug 2021 14:57:50 +0200 Subject: [PATCH 12/26] do profit calculations --- miner/multi_collator.go | 8 ++++---- miner/worker.go | 12 ++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/miner/multi_collator.go b/miner/multi_collator.go index b6b07219ed2bf..63fbd0135350b 100644 --- a/miner/multi_collator.go +++ b/miner/multi_collator.go @@ -67,10 +67,6 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad return } - gasPrice, err := tx.EffectiveGasTip(bs.work.env.header.BaseFee) - if err != nil { - return nil, err - } for _, tx := range sequence { if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone { @@ -90,6 +86,10 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad err = ErrUnsupportedEIP155Tx break } + gasPrice, err := tx.EffectiveGasTip(bs.work.env.header.BaseFee) + if err != nil { + return nil, err + } // Start executing the transaction state.Prepare(tx.Hash(), bs.work.env.tcount) diff --git a/miner/worker.go b/miner/worker.go index d474a1d350f31..df5c586129f80 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -819,6 +819,8 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP } var coalescedLogs []*types.Log + curProfit := big.NewInt(0).Add(env.profit) + for { // In the following three cases, we will interrupt the execution of the transaction. // (1) new head block event arrival, the interrupt signal is 1 @@ -863,6 +865,10 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP txs.Pop() continue } + gasPrice, err := tx.EffectiveGasTip(bs.work.env.header.BaseFee) + if err != nil { + return nil, err + } // Start executing the transaction env.state.Prepare(tx.Hash(), env.tcount) @@ -886,6 +892,8 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP case errors.Is(err, nil): // Everything ok, collect the logs and shift in the next transaction from the same account coalescedLogs = append(coalescedLogs, logs...) + gasUsed := new(big.Int).SetUint64(bs.work.env.receipts[-1].GasUsed) + curProfit.Add(curProfit, gasUsed.Mul(gasUsed, gasPrice)) env.tcount++ txs.Shift() @@ -902,6 +910,10 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP } } + if w.isRunning() { + env.profit = curProfit + } + if !w.isRunning() && len(coalescedLogs) > 0 { // We don't push the pendingLogsEvent while we are sealing. The reason is that // when we are sealing, the worker will regenerate a sealing block every 3 seconds. From 547e60fd2fe6d1756b93b017243769707cf189c3 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Tue, 10 Aug 2021 21:57:43 +0200 Subject: [PATCH 13/26] moar --- miner/multi_collator.go | 67 +++++++++++++++++++++++++++-------------- miner/worker.go | 24 ++++++++++++--- 2 files changed, 64 insertions(+), 27 deletions(-) diff --git a/miner/multi_collator.go b/miner/multi_collator.go index 63fbd0135350b..f6d5040bdb15d 100644 --- a/miner/multi_collator.go +++ b/miner/multi_collator.go @@ -1,7 +1,9 @@ type AddTransactionsResultFunc func(error, []*types.Receipt) bool // BlockState represents a block-to-be-mined, which is being assembled. -// A collator can add transactions by calling AddTransactions +// A collator can add transactions by calling AddTransactions. +// When the collator is done adding transactions to a block, it calls Commit. +// after-which, no more transactions can be added to the block type BlockState interface { // AddTransactions adds the sequence of transactions to the blockstate. Either all // transactions are added, or none of them. In the latter case, the error @@ -12,6 +14,8 @@ type BlockState interface { // value (true) from CollateBlock which indicates to the miner to discard the // block AddTransactions(sequence types.Transactions, cb AddTransactionsResultFunc) + Commit() + Copy() BlockState Gas() (remaining uint64) Coinbase() common.Address BaseFee() *big.Int @@ -67,7 +71,6 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad return } - for _, tx := range sequence { if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone { bs.done = true @@ -140,7 +143,28 @@ func (bs *collatorBlockState) Commit() { } } -type CollateBlockfunc func (bs BlockState, pool Pool) +func (bs *collatorBlockState) Gas() uint64 { + return *bs.work.env.gasPool +} + +func (bs *collatorBlockState) Coinbase() common.Address { + // TODO should clone this but I'm feeling lazy rn + return bs.work.env.header.Coinbase +} + +func (bs *collatorBlockState) BaseFee() *big.Int { + return new(big.Int).SetInt(bs.work.env.header.BaseFee) +} + +func (bs *collatorBlockState) Signer() types.Signer { + return bs.work.env.signer +} + +type BlockCollator interface { + func CollateBlock(bs BlockState, pool Pool) + func SideChainHook(header *types.Header) + func NewHeadHook(header *types.Header) +} type collator struct { newWorkCh chan<- collatorWork @@ -148,11 +172,14 @@ type collator struct { // channel signalling collator loop should exit exitCh chan<-interface{} newHeadCh chan<-types.Header - collateBlockImpl CollateBlockFunc + sideChainCh chan<-types.Header + blockCollatorImpl BlockCollator chainConfig *params.ChainConfig chain *core.BlockChain } +// mainLoop runs in a separate goroutine and handles the lifecycle of an active collator. +// TODO more explanation func (c *collator) mainLoop() { for { select { @@ -164,11 +191,11 @@ func (c *collator) mainLoop() { case <-c.exitCh: // TODO any cleanup needed? return - case newHead := <-newHeadCh: + case newHead := <-c.newHeadCh: + // TODO call hook here + case sideHeader := <-c.sideChainCh: // TODO call hook here - fallthrough default: - fallthrough } } } @@ -197,6 +224,10 @@ func (m *MultiCollator) Stop() { } } +// SuggestBlock sends a new empty block to each active collator. +// collators whose receiving channels are full are noted as "unresponsive" +// for the purpose of not expecting a response back (for this round) during +// polling performed by Collect func (m *MultiCollator) SuggestBlock(work *environment, interrupt *int32) { if m.counter == math.Uint64Max { m.counter = 0 @@ -215,20 +246,18 @@ func (m *MultiCollator) SuggestBlock(work *environment, interrupt *int32) { type WorkResult func(environment) -func (m *MultiCollator) Collect(work *environment, cb WorkResult) { +// Collect retrieves filled blocks returned by active collators based on the block suggested by the previous call to SuggestedBlock. +// It blocks until all responsive collators (ones which accepted the block from SuggestBlock) signal that they are done +// or the provided interrupt is set. +func (m *MultiCollator) Collect(cb WorkResult) { finishedCollators := []uint{} - shouldAdjustRecommitDown := true - for { if finishedCollators == m.responsiveCollatorcount { break } - - if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone { - // TODO: if the interrupt was recommit, signal to the worker to adjust up - shouldAdjustRecommitDown = false + if m.interrupt != nil && atomic.LoadInt32(m.interrupt) != commitInterruptNone { + break } - for i, c := range m.collators { select { case response := m.workResultCh: @@ -236,7 +265,6 @@ func (m *MultiCollator) Collect(work *environment, cb WorkResult) { if response.counter != m.counter { break } - // ignore responses from collators that have already signalled they are done shouldIgnore := false for _, finishedCollator := range finishedCollators { @@ -247,7 +275,6 @@ func (m *MultiCollator) Collect(work *environment, cb WorkResult) { if shouldIgnore { break } - // nil for work signals the collator won't send back any more blocks for this round if response.work == nil { finishedCollators = append(finishedCollators, i) @@ -255,14 +282,10 @@ func (m *MultiCollator) Collect(work *environment, cb WorkResult) { cb(response.work) } default: - fallthrough } } } - - if shouldAdjustRecommitDown { - // TODO signal to worker to adjust recommit interval down - } + return } /* diff --git a/miner/worker.go b/miner/worker.go index df5c586129f80..5cbf8b3441d3e 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -929,11 +929,7 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP } w.pendingLogsFeed.Send(cpy) } - // Notify resubmit loop to decrease resubmitting interval if current interval is larger - // than the user-specified one. - if interrupt != nil { - w.resubmitAdjustCh <- &intervalAdjust{inc: false} - } + return false } @@ -1126,6 +1122,24 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) { } w.multiCollator.Collect(cb) + // TODO how to determine how to adjust the resubmit interval here? i.e. how the ratio should be determined + /* + if interrupt != nil { + if interrupt == commitInterruptNone { + w.resubmitAdjustCh <- &intervalAdjust{inc: false} + } else if interrupt == commitInterruptNone { + ratio := float64(gasLimit-w.current.gasPool.Gas()) / float64(gasLimit) + if ratio < 0.1 { + ratio = 0.1 + } + w.resubmitAdjustCh <- &intervalAdjust{ + ratio: ratio, + inc: true, + } + } + } + */ + // Swap out the old work with the new one, terminating any leftover // prefetcher processes in the mean time and starting a new one. if w.current != nil { From ecbcc13bca0362f57bca57e6b61121e0d45bfa62 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Wed, 11 Aug 2021 01:05:02 +0200 Subject: [PATCH 14/26] add collator for default strategy --- miner/default_collator.go | 78 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 miner/default_collator.go diff --git a/miner/default_collator.go b/miner/default_collator.go new file mode 100644 index 0000000000000..d0a922fe84234 --- /dev/null +++ b/miner/default_collator.go @@ -0,0 +1,78 @@ +type DefaultCollator struct {} + +func submitTransactions(bs BlockState, txs *types.TransactionsByPriceAndNonce) bool { + cb := func(err error, receipts []*types.Receipt) bool { + switch { + case errors.Is(err, core.ErrGasLimitReached): + fallthrough + case errors.Is(err, core.ErrTxTypeNotSupported): + fallthrough + case errors.Is(err, core.ErrNonceTooHigh): + txs.Pop() + case errors.Is(err, core.ErrNonceTooLow): + fallthrough + case errors.Is(err, nil): + fallthrough + default: + txs.Shift() + } + return false + } + + for { + // If we don't have enough gas for any further transactions then we're done + available := bs.Gas() + if available < params.TxGas { + break + } + // Retrieve the next transaction and abort if all done + tx := txs.Peek() + if tx == nil { + break + } + // Enough space for this tx? + if available < tx.Gas() { + txs.Pop() + continue + } + if err := bs.AddTransactions(types.Transactions{tx}, cb); err != nil { + return true + } + } + return false +} + +// CollateBlock fills a block based on the highest paying transactions from the +// transaction pool, giving precedence over local transactions. +func (w *DefaultCollator) CollateBlock(bs BlockState, pool Pool) { + txs, err := pool.Pending(true) + if err != nil { + log.Error("could not get pending transactions from the pool", "err", err) + return + } + if len(txs) == 0 { + return + } + // Split the pending transactions into locals and remotes + localTxs, remoteTxs := make(map[common.Address]types.Transactions), txs + for _, account := range pool.Locals() { + if accountTxs := remoteTxs[account]; len(accountTxs) > 0 { + delete(remoteTxs, account) + localTxs[account] = accountTxs + } + } + if len(localTxs) > 0 { + if submitTransactions(bs, types.NewTransactionsByPriceAndNonce(bs.Signer(), localTxs, bs.BaseFee())) { + return + } + } + if len(remoteTxs) > 0 { + if submitTransactions(bs, types.NewTransactionsByPriceAndNonce(bs.Signer(), remoteTxs, bs.BaseFee()) { + return + } + } + + bs.Commit() + + return +} From fe187dde6f889fc4271c96f56644f5b4cef44452 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Wed, 11 Aug 2021 14:32:37 +0200 Subject: [PATCH 15/26] moar --- miner/multi_collator.go | 48 +++++++++++++++++++++++++++++++++++------ miner/worker.go | 24 +++++++++------------ 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/miner/multi_collator.go b/miner/multi_collator.go index f6d5040bdb15d..53fb8c1dc6ed8 100644 --- a/miner/multi_collator.go +++ b/miner/multi_collator.go @@ -49,7 +49,7 @@ func (w *collatorWork) Copy() collatorWork { } } -func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb AddTransactionsResultFunc) { +func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb AddTransactionsResultFunc) error { var ( interrupt = bs.work.env.interrupt header = bs.work.env.header @@ -67,8 +67,7 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad startTCount = bs.work.env.tcount ) if bs.done { - cb(ErrRecommit, nil) - return + return ErrAbort } for _, tx := range sequence { @@ -91,7 +90,7 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad } gasPrice, err := tx.EffectiveGasTip(bs.work.env.header.BaseFee) if err != nil { - return nil, err + return err } // Start executing the transaction state.Prepare(tx.Hash(), bs.work.env.tcount) @@ -134,12 +133,14 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad bs.env.profit = curProfit bs.env.tcount = tcount } + + return nil } func (bs *collatorBlockState) Commit() { if !bs.done { bs.done = true - bs.c.workResultch <- bs.work + bs.c.workResultCh <- bs.work } } @@ -200,20 +201,53 @@ func (c *collator) mainLoop() { } } +var ( + // MultiCollator + workResultChSize = 10 + + // collator + newWorkChSize = 10 + newHeadChSize = 10 + sideChainChSize = 10 +) + type MultiCollator struct { - workResultCh + workResultCh chan<- collatorWork counter uint64 responsiveCollatorCount uint collators []collator } +func NewMultiCollator(chainConfig *params.ChainConfig, chain *core.BlockChain, strategies []BlockCollator) MultiCollator { + workResultCh := make(chan collatorWork, workResultChSize), + collators := []collator{} + for _, s := range strategies { + collators = append(collators, collator{ + newWorkCh: make(chan collatorWork, newWorkChSize), + workResultCh: workResultCh, + exitCh: make(chan struct{}), + newHeadCh: make(chan types.Header, newHeadChSize), + blockCollatorImpl: s, + chainConfig: chainConfig, + chain: chain, + }) + } + + m := MultiCollator { + counter: 0, + responsiveCollatorCount: 0, + collators: collators, + workResultCh: workResultCh + } +} + func (m *MultiCollator) Start() { for c := range m.collators { go c.mainLoop() } } -func (m *MultiCollator) Stop() { +func (m *MultiCollator) Close() { for c := range m.collators { select { case c.exitCh<-true: diff --git a/miner/worker.go b/miner/worker.go index 5cbf8b3441d3e..be12c59d0167f 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -205,6 +205,7 @@ type worker struct { resubmitAdjustCh chan *intervalAdjust current *environment // An environment for current running cycle. + multiCollator MultiCollator // pool of active block collation strategies localUncles map[common.Hash]*types.Block // A set of side blocks generated locally as the possible uncle blocks. remoteUncles map[common.Hash]*types.Block // A set of side blocks as the possible uncle blocks. unconfirmed *unconfirmedBlocks // A set of locally mined blocks pending canonicalness confirmations. @@ -242,7 +243,7 @@ type worker struct { resubmitHook func(time.Duration, time.Duration) // Method to call upon updating resubmitting interval. } -func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(*types.Block) bool, init bool) *worker { +func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(*types.Block) bool, init bool, collators []BlockCollator) *worker { worker := &worker{ config: config, chainConfig: chainConfig, @@ -266,7 +267,10 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus startCh: make(chan struct{}, 1), resubmitIntervalCh: make(chan time.Duration), resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize), + multiCollator: NewMultiCollator(chainConfig, chain, collators), } + // start collator pool listening for new work + worker.multiCollator.Start() // Subscribe NewTxsEvent for tx pool worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh) // Subscribe events for blockchain @@ -379,6 +383,7 @@ func (w *worker) close() { if w.current != nil { w.current.discard() } + w.multiCollator.Close() atomic.StoreInt32(&w.running, 0) close(w.exitCh) } @@ -1068,11 +1073,7 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) { } defer work.discard() - w.multiCollator.SuggestBlock(work, nil) - if err := w.fillTransactions(nil, work); err != nil { - return nil, err - } - + w.multiCollator.SuggestBlock(&work, nil) var bestWork environment = work profit := big.NewInt(0) @@ -1101,21 +1102,16 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) { if !noempty && atomic.LoadUint32(&w.noempty) == 0 { w.commit(work.copy(), nil, false, start) } - // start concurrent block collation for custom collators - w.multiCollator.SuggestBlock(&work) - // Fill pending transactions from the txpool - if err := w.fillTransactions(interrupt, work); err != nil { - return - } - w.commit(work.copy(), w.fullTaskHook, true, start) + // suggest the new block to the pool of active collators and interrupt sealing as more profitable strategies are fed back + w.multiCollator.SuggestBlock(&work) profit = new(big.Int) profit.Set(work.profit) curBest := work cb := func(newWork environment) { if newWork.profit.Gt(profit) { // probably don't need to copy work again here but do it for now just to be safe - w.commit(newWork.copy(), w.fullTaskHooke, true, start) + w.commit(newWork.copy(), w.fullTaskHook, true, start) profit.Set(newWork.profit) curBest = newWork } From f2b95493164ed03896dcaa14307bb0e7eb4bb90b Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Wed, 11 Aug 2021 15:58:20 +0200 Subject: [PATCH 16/26] moar --- miner/default_collator.go | 36 ++++++++++++++++++-- miner/multi_collator.go | 72 +++++++++++++++++++++++++++------------ miner/worker.go | 2 +- 3 files changed, 86 insertions(+), 24 deletions(-) diff --git a/miner/default_collator.go b/miner/default_collator.go index d0a922fe84234..9dece537c6aaf 100644 --- a/miner/default_collator.go +++ b/miner/default_collator.go @@ -1,6 +1,34 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package miner + +import ( + "errors" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/log" +) + type DefaultCollator struct {} func submitTransactions(bs BlockState, txs *types.TransactionsByPriceAndNonce) bool { + var shouldAbort bool cb := func(err error, receipts []*types.Receipt) bool { switch { case errors.Is(err, core.ErrGasLimitReached): @@ -11,6 +39,9 @@ func submitTransactions(bs BlockState, txs *types.TransactionsByPriceAndNonce) b txs.Pop() case errors.Is(err, core.ErrNonceTooLow): fallthrough + case errors.Is(err, ErrAbort): + shouldAbort = true + return false // don't need to waste time rolling back these transactions when this work will be thrown away anyways. case errors.Is(err, nil): fallthrough default: @@ -35,7 +66,8 @@ func submitTransactions(bs BlockState, txs *types.TransactionsByPriceAndNonce) b txs.Pop() continue } - if err := bs.AddTransactions(types.Transactions{tx}, cb); err != nil { + bs.AddTransactions(types.Transactions{tx}, cb) + if shouldAbort { return true } } @@ -67,7 +99,7 @@ func (w *DefaultCollator) CollateBlock(bs BlockState, pool Pool) { } } if len(remoteTxs) > 0 { - if submitTransactions(bs, types.NewTransactionsByPriceAndNonce(bs.Signer(), remoteTxs, bs.BaseFee()) { + if submitTransactions(bs, types.NewTransactionsByPriceAndNonce(bs.Signer(), remoteTxs, bs.BaseFee())) { return } } diff --git a/miner/multi_collator.go b/miner/multi_collator.go index 53fb8c1dc6ed8..2763251eab2b3 100644 --- a/miner/multi_collator.go +++ b/miner/multi_collator.go @@ -1,3 +1,32 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package miner + +import ( + "errors" + "math/big" + "sync/atomic" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/log" +) type AddTransactionsResultFunc func(error, []*types.Receipt) bool // BlockState represents a block-to-be-mined, which is being assembled. @@ -13,7 +42,7 @@ type BlockState interface { // If ErrAbort is returned, the collator should immediately abort and return a // value (true) from CollateBlock which indicates to the miner to discard the // block - AddTransactions(sequence types.Transactions, cb AddTransactionsResultFunc) + AddTransactions(sequence types.Transactions, cb AddTransactionsResultFunc) error Commit() Copy() BlockState Gas() (remaining uint64) @@ -22,7 +51,7 @@ type BlockState interface { Signer() types.Signer } -type collatorWork { +type collatorWork struct { env *environment counter uint64 interrupt *int32 @@ -34,6 +63,11 @@ type Pool interface { Locals() []common.Address } +var ( + ErrAbort = errors.New("abort sealing current work (resubmit/newHead interrupt)") + ErrUnsupportedEIP155Tx = errors.New("replay-protected tx when EIP155 not enabled") +) + type collatorBlockState struct { work collatorWork c *collator @@ -41,7 +75,7 @@ type collatorBlockState struct { } func (w *collatorWork) Copy() collatorWork { - newEnv := w.copy() + newEnv := w.env.copy() return collatorWork{ env: newEnv, counter: w.counter, @@ -51,7 +85,7 @@ func (w *collatorWork) Copy() collatorWork { func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb AddTransactionsResultFunc) error { var ( - interrupt = bs.work.env.interrupt + interrupt = bs.work.interrupt header = bs.work.env.header gasPool = bs.work.env.gasPool signer = bs.work.env.signer @@ -60,7 +94,7 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad state = bs.work.env.state snap = state.Snapshot() curProfit = big.NewInt(0) - coinbaseBalanceBefore = state.GetBalance(bs.work.header.Coinbase) + coinbaseBalanceBefore = state.GetBalance(bs.work.env.header.Coinbase) tcount = bs.work.env.tcount err error logs []*types.Log @@ -90,16 +124,16 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad } gasPrice, err := tx.EffectiveGasTip(bs.work.env.header.BaseFee) if err != nil { - return err + break } // Start executing the transaction state.Prepare(tx.Hash(), bs.work.env.tcount) var txLogs []*types.Log - txLogs, err = commitTransaction(chain, chainConfig, tx, bs.Coinbase()) + txLogs, err = commitTransaction(chain, chainConfig, bs.work.env, tx, bs.Coinbase()) if err == nil { logs = append(logs, txLogs...) - gasUsed := new(big.Int).SetUint64(bs.work.env.receipts[-1].GasUsed) + gasUsed := new(big.Int).SetUint64(bs.work.env.receipts[len(bs.work.env.receipts) - 1].GasUsed) curProfit.Add(curProfit, gasUsed.Mul(gasUsed, gasPrice)) tcount++ } else { @@ -126,15 +160,13 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad bs.work.env.txs = bs.work.env.txs[:startTCount] bs.work.env.receipts = bs.work.env.receipts[:startTCount] } else { - coinbaseBalanceAfter := bs.work.state.GetBalance(bs.work.env.header.Coinbase) + coinbaseBalanceAfter := bs.work.env.state.GetBalance(bs.work.env.header.Coinbase) coinbaseTransfer := big.NewInt(0).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore) curProfit.Add(curProfit, coinbaseTransfer) - bs.work.env.logs = append(bs.logs, logs...) + bs.work.env.logs = append(bs.work.env.logs, logs...) bs.env.profit = curProfit bs.env.tcount = tcount } - - return nil } func (bs *collatorBlockState) Commit() { @@ -162,9 +194,9 @@ func (bs *collatorBlockState) Signer() types.Signer { } type BlockCollator interface { - func CollateBlock(bs BlockState, pool Pool) - func SideChainHook(header *types.Header) - func NewHeadHook(header *types.Header) + CollateBlock(bs BlockState, pool Pool) + SideChainHook(header *types.Header) + NewHeadHook(header *types.Header) } type collator struct { @@ -219,7 +251,7 @@ type MultiCollator struct { } func NewMultiCollator(chainConfig *params.ChainConfig, chain *core.BlockChain, strategies []BlockCollator) MultiCollator { - workResultCh := make(chan collatorWork, workResultChSize), + workResultCh := make(chan collatorWork, workResultChSize) collators := []collator{} for _, s := range strategies { collators = append(collators, collator{ @@ -237,7 +269,7 @@ func NewMultiCollator(chainConfig *params.ChainConfig, chain *core.BlockChain, s counter: 0, responsiveCollatorCount: 0, collators: collators, - workResultCh: workResultCh + workResultCh: workResultCh, } } @@ -251,9 +283,7 @@ func (m *MultiCollator) Close() { for c := range m.collators { select { case c.exitCh<-true: - fallthrough default: - continue } } } @@ -272,7 +302,7 @@ func (m *MultiCollator) SuggestBlock(work *environment, interrupt *int32) { m.interrupt = interrupt for c := range m.collators { select { - case c.newWorkCh <- collatorWork{env: work.copy(), counter: m.counter, interrupt: interrupt} + case c.newWorkCh <- collatorWork{env: work.copy(), counter: m.counter, interrupt: interrupt}: m.responsiveCollatorCount++ } } @@ -331,4 +361,4 @@ func (m *MultiCollator) NewHeadHook() { func (m *Multicollator) SideChainHook() { } -/* +*/ diff --git a/miner/worker.go b/miner/worker.go index be12c59d0167f..9a5d80b7aba09 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -803,7 +803,7 @@ func (w *worker) updateSnapshot(env *environment) { w.snapshotState = env.state.Copy() } -func commitTransaction(chain *core.BlockChain, chainConfig *core.ChainConfig, env *environment, tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) { +func commitTransaction(chain *core.BlockChain, chainConfig *params.ChainConfig, env *environment, tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) { snap := env.state.Snapshot() receipt, err := core.ApplyTransaction(chainConfig, chain, &coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *chain.GetVMConfig()) From a039210ca8e886283747f0199f1ac9569620745e Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Wed, 11 Aug 2021 18:14:09 +0200 Subject: [PATCH 17/26] moar --- miner/multi_collator.go | 9 ++++--- miner/worker.go | 58 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/miner/multi_collator.go b/miner/multi_collator.go index 2763251eab2b3..9784030a181f6 100644 --- a/miner/multi_collator.go +++ b/miner/multi_collator.go @@ -164,9 +164,10 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad coinbaseTransfer := big.NewInt(0).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore) curProfit.Add(curProfit, coinbaseTransfer) bs.work.env.logs = append(bs.work.env.logs, logs...) - bs.env.profit = curProfit - bs.env.tcount = tcount + bs.work.env.profit = curProfit + bs.work.env.tcount = tcount } + return } func (bs *collatorBlockState) Commit() { @@ -176,7 +177,7 @@ func (bs *collatorBlockState) Commit() { } } -func (bs *collatorBlockState) Gas() uint64 { +func (bs *collatorBlockState) Gas() core.GasPool { return *bs.work.env.gasPool } @@ -186,7 +187,7 @@ func (bs *collatorBlockState) Coinbase() common.Address { } func (bs *collatorBlockState) BaseFee() *big.Int { - return new(big.Int).SetInt(bs.work.env.header.BaseFee) + return new(big.Int).Set(bs.work.env.header.BaseFee) } func (bs *collatorBlockState) Signer() types.Signer { diff --git a/miner/worker.go b/miner/worker.go index 9a5d80b7aba09..213b4297a1008 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -93,6 +93,8 @@ type environment struct { receipts []*types.Receipt uncles map[common.Hash]*types.Header profit *big.Int + + logs []*types.Log } // copy creates a deep copy of environment. @@ -105,6 +107,7 @@ func (env *environment) copy() *environment { tcount: env.tcount, header: types.CopyHeader(env.header), receipts: copyReceipts(env.receipts), + logs: copyLogs(env.logs), } if env.gasPool != nil { cpy.gasPool = &(*env.gasPool) @@ -753,6 +756,7 @@ func (w *worker) makeEnv(parent *types.Block, header *types.Header) (*environmen family: mapset.NewSet(), header: header, uncles: make(map[common.Hash]*types.Header), + logs: make([]*types.Log), } // when 08 is processed ancestors contain 07 (quick block) for _, ancestor := range w.chain.GetBlocksFromHash(parent.Hash(), 7) { @@ -1118,6 +1122,29 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) { } w.multiCollator.Collect(cb) + // Swap out the old work with the new one, terminating any leftover + // prefetcher processes in the mean time and starting a new one. + if w.current != nil { + w.current.discard() + } + w.current = curBest + + if !w.isRunning() && len(coalescedLogs) > 0 { + // We don't push the pendingLogsEvent while we are sealing. The reason is that + // when we are sealing, the worker will regenerate a sealing block every 3 seconds. + // In order to avoid pushing the repeated pendingLog, we disable the pending log pushing. + + // make a copy, the state caches the logs and these logs get "upgraded" from pending to mined + // logs by filling in the block hash when the block was mined by the local miner. This can + // cause a race condition if a log was "upgraded" before the PendingLogsEvent is processed. + cpy := make([]*types.Log, len(coalescedLogs)) + for i, l := range coalescedLogs { + cpy[i] = new(types.Log) + *cpy[i] = *l + } + w.pendingLogsFeed.Send(cpy) + } + // TODO how to determine how to adjust the resubmit interval here? i.e. how the ratio should be determined /* if interrupt != nil { @@ -1136,12 +1163,6 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) { } */ - // Swap out the old work with the new one, terminating any leftover - // prefetcher processes in the mean time and starting a new one. - if w.current != nil { - w.current.discard() - } - w.current = curBest } // commit runs any post-transaction state modifications, assembles the final block @@ -1201,6 +1222,31 @@ func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64) (*types.B } } +func copyLogs(logs []*types.Log) []*types.Log { + result := make([]*types.Log, len(logs)) + for i, l := range logs { + logCopy := types.Log{} + copy(logCopy.Address, l.Address) + for _, t := range l.Topics { + topic := common.Hash{} + copy(topic, t) + logCopy.Topics = append(logCopy.Topics, topic) + } + logCopy.Data = make([]byte, len(l.Data)) + copy(logCopy.Data, l.Data, len(l.Data)) + logCopy.BlockNumber = l.BlockNumber + copy(logCopy.TxHash, l.TxHash) + logCopy.TxIndex = l.TxIndex + copy(logCopy.BlockHash, l.BlockHash) + logCopy.Index = l.Index + logCopy.Removed = l.Removed + + result = append(result, &logCopy) + } + + return result +} + // copyReceipts makes a deep copy of the given receipts. func copyReceipts(receipts []*types.Receipt) []*types.Receipt { result := make([]*types.Receipt, len(receipts)) From f11e9868a9296a7caf054934d3d5ea4ea9188b53 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Wed, 11 Aug 2021 23:50:32 +0200 Subject: [PATCH 18/26] it builds... and gofmt --- miner/default_collator.go | 26 +-- miner/miner.go | 4 +- miner/multi_collator.go | 378 ++++++++++++++++++++------------------ miner/worker.go | 218 ++++++++++------------ 4 files changed, 308 insertions(+), 318 deletions(-) diff --git a/miner/default_collator.go b/miner/default_collator.go index 9dece537c6aaf..f81652c96a686 100644 --- a/miner/default_collator.go +++ b/miner/default_collator.go @@ -17,18 +17,18 @@ package miner import ( - "errors" + "errors" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" ) -type DefaultCollator struct {} +type DefaultCollator struct{} func submitTransactions(bs BlockState, txs *types.TransactionsByPriceAndNonce) bool { - var shouldAbort bool + var shouldAbort bool cb := func(err error, receipts []*types.Receipt) bool { switch { case errors.Is(err, core.ErrGasLimitReached): @@ -39,9 +39,9 @@ func submitTransactions(bs BlockState, txs *types.TransactionsByPriceAndNonce) b txs.Pop() case errors.Is(err, core.ErrNonceTooLow): fallthrough - case errors.Is(err, ErrAbort): - shouldAbort = true - return false // don't need to waste time rolling back these transactions when this work will be thrown away anyways. + case errors.Is(err, ErrAbort): + shouldAbort = true + return false // don't need to waste time rolling back these transactions when this work will be thrown away anyways. case errors.Is(err, nil): fallthrough default: @@ -67,8 +67,8 @@ func submitTransactions(bs BlockState, txs *types.TransactionsByPriceAndNonce) b continue } bs.AddTransactions(types.Transactions{tx}, cb) - if shouldAbort { - return true + if shouldAbort { + return true } } return false @@ -83,7 +83,7 @@ func (w *DefaultCollator) CollateBlock(bs BlockState, pool Pool) { return } if len(txs) == 0 { - return + return } // Split the pending transactions into locals and remotes localTxs, remoteTxs := make(map[common.Address]types.Transactions), txs @@ -100,11 +100,11 @@ func (w *DefaultCollator) CollateBlock(bs BlockState, pool Pool) { } if len(remoteTxs) > 0 { if submitTransactions(bs, types.NewTransactionsByPriceAndNonce(bs.Signer(), remoteTxs, bs.BaseFee())) { - return - } + return + } } - bs.Commit() + bs.Commit() return } diff --git a/miner/miner.go b/miner/miner.go index 87e809f34f124..a4aba2401ad00 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -66,6 +66,8 @@ type Miner struct { } func New(eth Backend, config *Config, chainConfig *params.ChainConfig, mux *event.TypeMux, engine consensus.Engine, isLocalBlock func(block *types.Block) bool) *Miner { + activeCollators := []BlockCollator{} + activeCollators = append(activeCollators, &DefaultCollator{}) miner := &Miner{ eth: eth, mux: mux, @@ -73,7 +75,7 @@ func New(eth Backend, config *Config, chainConfig *params.ChainConfig, mux *even exitCh: make(chan struct{}), startCh: make(chan common.Address), stopCh: make(chan struct{}), - worker: newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, true), + worker: newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, true, activeCollators), } go miner.update() diff --git a/miner/multi_collator.go b/miner/multi_collator.go index 9784030a181f6..81ba634fce04b 100644 --- a/miner/multi_collator.go +++ b/miner/multi_collator.go @@ -17,16 +17,21 @@ package miner import ( - "errors" + "errors" "math/big" - "sync/atomic" + "sync/atomic" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" ) + +const ( + Uint64Max = 18446744073709551615 +) + type AddTransactionsResultFunc func(error, []*types.Receipt) bool // BlockState represents a block-to-be-mined, which is being assembled. @@ -42,9 +47,9 @@ type BlockState interface { // If ErrAbort is returned, the collator should immediately abort and return a // value (true) from CollateBlock which indicates to the miner to discard the // block - AddTransactions(sequence types.Transactions, cb AddTransactionsResultFunc) error - Commit() - Copy() BlockState + AddTransactions(sequence types.Transactions, cb AddTransactionsResultFunc) + Commit() + Copy() BlockState Gas() (remaining uint64) Coinbase() common.Address BaseFee() *big.Int @@ -52,61 +57,71 @@ type BlockState interface { } type collatorWork struct { - env *environment - counter uint64 - interrupt *int32 + env *environment + counter uint64 + interrupt *int32 } // Pool is an interface to the transaction pool type Pool interface { - Pending(bool) (map[common.Address]types.Transactions, error) - Locals() []common.Address + Pending(bool) (map[common.Address]types.Transactions, error) + Locals() []common.Address } var ( - ErrAbort = errors.New("abort sealing current work (resubmit/newHead interrupt)") - ErrUnsupportedEIP155Tx = errors.New("replay-protected tx when EIP155 not enabled") + ErrAbort = errors.New("abort sealing current work (resubmit/newHead interrupt)") + ErrUnsupportedEIP155Tx = errors.New("replay-protected tx when EIP155 not enabled") ) type collatorBlockState struct { - work collatorWork - c *collator - done bool + work collatorWork + c *collator + done bool +} + +func (c *collatorBlockState) Copy() BlockState { + return &collatorBlockState{ + work: c.work.Copy(), + c: c.c, + done: c.done, + } } func (w *collatorWork) Copy() collatorWork { - newEnv := w.env.copy() - return collatorWork{ - env: newEnv, - counter: w.counter, - interrupt: w.interrupt, - } + newEnv := w.env.copy() + return collatorWork{ + env: newEnv, + counter: w.counter, + interrupt: w.interrupt, + } } -func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb AddTransactionsResultFunc) error { +func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb AddTransactionsResultFunc) { var ( - interrupt = bs.work.interrupt - header = bs.work.env.header - gasPool = bs.work.env.gasPool - signer = bs.work.env.signer - chainConfig = bs.c.chainConfig - chain = bs.c.chain - state = bs.work.env.state - snap = state.Snapshot() - curProfit = big.NewInt(0) + interrupt = bs.work.interrupt + header = bs.work.env.header + gasPool = bs.work.env.gasPool + signer = bs.work.env.signer + chainConfig = bs.c.chainConfig + chain = bs.c.chain + state = bs.work.env.state + snap = state.Snapshot() + curProfit = big.NewInt(0) coinbaseBalanceBefore = state.GetBalance(bs.work.env.header.Coinbase) - tcount = bs.work.env.tcount - err error - logs []*types.Log - startTCount = bs.work.env.tcount + tcount = bs.work.env.tcount + err error + logs []*types.Log + startTCount = bs.work.env.tcount ) if bs.done { - return ErrAbort + err = ErrAbort + cb(err, nil) + return } for _, tx := range sequence { if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone { - bs.done = true + bs.done = true break } if gasPool.Gas() < params.TxGas { @@ -122,10 +137,10 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad err = ErrUnsupportedEIP155Tx break } - gasPrice, err := tx.EffectiveGasTip(bs.work.env.header.BaseFee) - if err != nil { - break - } + gasPrice, err := tx.EffectiveGasTip(bs.work.env.header.BaseFee) + if err != nil { + break + } // Start executing the transaction state.Prepare(tx.Hash(), bs.work.env.tcount) @@ -133,8 +148,8 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad txLogs, err = commitTransaction(chain, chainConfig, bs.work.env, tx, bs.Coinbase()) if err == nil { logs = append(logs, txLogs...) - gasUsed := new(big.Int).SetUint64(bs.work.env.receipts[len(bs.work.env.receipts) - 1].GasUsed) - curProfit.Add(curProfit, gasUsed.Mul(gasUsed, gasPrice)) + gasUsed := new(big.Int).SetUint64(bs.work.env.receipts[len(bs.work.env.receipts)-1].GasUsed) + curProfit.Add(curProfit, gasUsed.Mul(gasUsed, gasPrice)) tcount++ } else { log.Trace("Tx block inclusion failed", "sender", from, "nonce", tx.Nonce(), @@ -161,152 +176,157 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad bs.work.env.receipts = bs.work.env.receipts[:startTCount] } else { coinbaseBalanceAfter := bs.work.env.state.GetBalance(bs.work.env.header.Coinbase) - coinbaseTransfer := big.NewInt(0).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore) - curProfit.Add(curProfit, coinbaseTransfer) + coinbaseTransfer := big.NewInt(0).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore) + curProfit.Add(curProfit, coinbaseTransfer) bs.work.env.logs = append(bs.work.env.logs, logs...) - bs.work.env.profit = curProfit + bs.work.env.profit = curProfit bs.work.env.tcount = tcount } - return + return } func (bs *collatorBlockState) Commit() { - if !bs.done { - bs.done = true - bs.c.workResultCh <- bs.work - } + if !bs.done { + bs.done = true + bs.c.workResultCh <- bs.work + } } -func (bs *collatorBlockState) Gas() core.GasPool { - return *bs.work.env.gasPool +func (bs *collatorBlockState) Gas() uint64 { + return bs.work.env.gasPool.Gas() } func (bs *collatorBlockState) Coinbase() common.Address { - // TODO should clone this but I'm feeling lazy rn - return bs.work.env.header.Coinbase + // TODO should clone this but I'm feeling lazy rn + return bs.work.env.header.Coinbase } func (bs *collatorBlockState) BaseFee() *big.Int { - return new(big.Int).Set(bs.work.env.header.BaseFee) + return new(big.Int).Set(bs.work.env.header.BaseFee) } func (bs *collatorBlockState) Signer() types.Signer { - return bs.work.env.signer + return bs.work.env.signer } type BlockCollator interface { - CollateBlock(bs BlockState, pool Pool) - SideChainHook(header *types.Header) - NewHeadHook(header *types.Header) + CollateBlock(bs BlockState, pool Pool) + /* + // TODO implement these + SideChainHook(header *types.Header) + NewHeadHook(header *types.Header) + */ } type collator struct { - newWorkCh chan<- collatorWork - workResultCh chan<-collatorWork - // channel signalling collator loop should exit - exitCh chan<-interface{} - newHeadCh chan<-types.Header - sideChainCh chan<-types.Header - blockCollatorImpl BlockCollator - chainConfig *params.ChainConfig - chain *core.BlockChain + newWorkCh chan collatorWork + workResultCh chan collatorWork + // channel signalling collator loop should exit + exitCh chan struct{} + newHeadCh chan types.Header + sideChainCh chan types.Header + blockCollatorImpl BlockCollator + + chainConfig *params.ChainConfig + chain *core.BlockChain + pool Pool } // mainLoop runs in a separate goroutine and handles the lifecycle of an active collator. // TODO more explanation func (c *collator) mainLoop() { - for { - select { - case newWork := <-c.newWorkCh: - c.collateBlockImpl(collatorBlockState{work: newWork, c: c, done: false}) - // signal to the exitCh that the collator is done - // computing this work. - c.workResultCh <- collatorWork{nil, newWork.counter} - case <-c.exitCh: - // TODO any cleanup needed? - return - case newHead := <-c.newHeadCh: - // TODO call hook here - case sideHeader := <-c.sideChainCh: - // TODO call hook here - default: - } - } + for { + select { + case newWork := <-c.newWorkCh: + c.blockCollatorImpl.CollateBlock(&collatorBlockState{work: newWork, c: c, done: false}, c.pool) + // signal to the exitCh that the collator is done + // computing this work. + c.workResultCh <- collatorWork{env: nil, interrupt: nil, counter: newWork.counter} + case <-c.exitCh: + // TODO any cleanup needed? + return + case newHead := <-c.newHeadCh: + // TODO call hook here + _ = newHead + case sideHeader := <-c.sideChainCh: + _ = sideHeader + // TODO call hook here + default: + } + } } var ( - // MultiCollator - workResultChSize = 10 - - // collator - newWorkChSize = 10 - newHeadChSize = 10 - sideChainChSize = 10 + // collator + workResultChSize = 10 + newWorkChSize = 10 + newHeadChSize = 10 + sideChainChSize = 10 ) type MultiCollator struct { - workResultCh chan<- collatorWork - counter uint64 - responsiveCollatorCount uint - collators []collator + counter uint64 + responsiveCollatorCount int + collators []collator + pool Pool + interrupt *int32 } -func NewMultiCollator(chainConfig *params.ChainConfig, chain *core.BlockChain, strategies []BlockCollator) MultiCollator { - workResultCh := make(chan collatorWork, workResultChSize) - collators := []collator{} - for _, s := range strategies { - collators = append(collators, collator{ - newWorkCh: make(chan collatorWork, newWorkChSize), - workResultCh: workResultCh, - exitCh: make(chan struct{}), - newHeadCh: make(chan types.Header, newHeadChSize), - blockCollatorImpl: s, - chainConfig: chainConfig, - chain: chain, - }) - } - - m := MultiCollator { - counter: 0, - responsiveCollatorCount: 0, - collators: collators, - workResultCh: workResultCh, - } +func NewMultiCollator(chainConfig *params.ChainConfig, chain *core.BlockChain, pool Pool, strategies []BlockCollator) MultiCollator { + collators := []collator{} + for _, s := range strategies { + collators = append(collators, collator{ + newWorkCh: make(chan collatorWork, newWorkChSize), + workResultCh: make(chan collatorWork, workResultChSize), + exitCh: make(chan struct{}), + newHeadCh: make(chan types.Header, newHeadChSize), + blockCollatorImpl: s, + chainConfig: chainConfig, + chain: chain, + pool: pool, + }) + } + return MultiCollator{ + counter: 0, + responsiveCollatorCount: 0, + collators: collators, + interrupt: nil, + } } func (m *MultiCollator) Start() { - for c := range m.collators { - go c.mainLoop() - } + for _, c := range m.collators { + go c.mainLoop() + } } func (m *MultiCollator) Close() { - for c := range m.collators { - select { - case c.exitCh<-true: - default: - } - } + for _, c := range m.collators { + select { + case c.exitCh <- struct{}{}: + default: + } + } } // SuggestBlock sends a new empty block to each active collator. // collators whose receiving channels are full are noted as "unresponsive" -// for the purpose of not expecting a response back (for this round) during +// for the purpose of not expecting a response back (for this round) during // polling performed by Collect func (m *MultiCollator) SuggestBlock(work *environment, interrupt *int32) { - if m.counter == math.Uint64Max { - m.counter = 0 - } else { - m.counter++ - } - m.responsiveCollatorCount = 0 - m.interrupt = interrupt - for c := range m.collators { - select { - case c.newWorkCh <- collatorWork{env: work.copy(), counter: m.counter, interrupt: interrupt}: - m.responsiveCollatorCount++ - } - } + if m.counter == Uint64Max { + m.counter = 0 + } else { + m.counter++ + } + m.responsiveCollatorCount = 0 + m.interrupt = interrupt + for _, c := range m.collators { + select { + case c.newWorkCh <- collatorWork{env: work.copy(), counter: m.counter, interrupt: interrupt}: + m.responsiveCollatorCount++ + } + } } type WorkResult func(environment) @@ -315,42 +335,42 @@ type WorkResult func(environment) // It blocks until all responsive collators (ones which accepted the block from SuggestBlock) signal that they are done // or the provided interrupt is set. func (m *MultiCollator) Collect(cb WorkResult) { - finishedCollators := []uint{} - for { - if finishedCollators == m.responsiveCollatorcount { - break - } - if m.interrupt != nil && atomic.LoadInt32(m.interrupt) != commitInterruptNone { - break - } - for i, c := range m.collators { - select { - case response := m.workResultCh: - // ignore collators responding from old work rounds - if response.counter != m.counter { - break - } - // ignore responses from collators that have already signalled they are done - shouldIgnore := false - for _, finishedCollator := range finishedCollators { - if i == finishedCollator { - shouldIgnore = true - } - } - if shouldIgnore { - break - } - // nil for work signals the collator won't send back any more blocks for this round - if response.work == nil { - finishedCollators = append(finishedCollators, i) - } else { - cb(response.work) - } - default: - } - } - } - return + finishedCollators := []int{} + for { + if len(finishedCollators) == m.responsiveCollatorCount { + break + } + if m.interrupt != nil && atomic.LoadInt32(m.interrupt) != commitInterruptNone { + break + } + for i, c := range m.collators { + select { + case response := <-c.workResultCh: + // ignore collators responding from old work rounds + if response.counter != m.counter { + break + } + // ignore responses from collators that have already signalled they are done + shouldIgnore := false + for _, finishedCollator := range finishedCollators { + if i == finishedCollator { + shouldIgnore = true + } + } + if shouldIgnore { + break + } + // nil for work signals the collator won't send back any more blocks for this round + if response.env == nil { + finishedCollators = append(finishedCollators, i) + } else { + cb(*response.env) + } + default: + } + } + } + return } /* diff --git a/miner/worker.go b/miner/worker.go index 213b4297a1008..4c698316fb21c 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -92,7 +92,7 @@ type environment struct { txs []*types.Transaction receipts []*types.Receipt uncles map[common.Hash]*types.Header - profit *big.Int + profit *big.Int logs []*types.Log } @@ -107,7 +107,7 @@ func (env *environment) copy() *environment { tcount: env.tcount, header: types.CopyHeader(env.header), receipts: copyReceipts(env.receipts), - logs: copyLogs(env.logs), + logs: copyLogs(env.logs), } if env.gasPool != nil { cpy.gasPool = &(*env.gasPool) @@ -207,11 +207,11 @@ type worker struct { resubmitIntervalCh chan time.Duration resubmitAdjustCh chan *intervalAdjust - current *environment // An environment for current running cycle. - multiCollator MultiCollator // pool of active block collation strategies - localUncles map[common.Hash]*types.Block // A set of side blocks generated locally as the possible uncle blocks. - remoteUncles map[common.Hash]*types.Block // A set of side blocks as the possible uncle blocks. - unconfirmed *unconfirmedBlocks // A set of locally mined blocks pending canonicalness confirmations. + current *environment // An environment for current running cycle. + multiCollator MultiCollator // pool of active block collation strategies + localUncles map[common.Hash]*types.Block // A set of side blocks generated locally as the possible uncle blocks. + remoteUncles map[common.Hash]*types.Block // A set of side blocks as the possible uncle blocks. + unconfirmed *unconfirmedBlocks // A set of locally mined blocks pending canonicalness confirmations. mu sync.RWMutex // The lock used to protect the coinbase and extra fields coinbase common.Address @@ -270,10 +270,10 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus startCh: make(chan struct{}, 1), resubmitIntervalCh: make(chan time.Duration), resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize), - multiCollator: NewMultiCollator(chainConfig, chain, collators), + multiCollator: NewMultiCollator(chainConfig, eth.BlockChain(), eth.TxPool(), collators), } - // start collator pool listening for new work - worker.multiCollator.Start() + // start collator pool listening for new work + worker.multiCollator.Start() // Subscribe NewTxsEvent for tx pool worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh) // Subscribe events for blockchain @@ -386,7 +386,7 @@ func (w *worker) close() { if w.current != nil { w.current.discard() } - w.multiCollator.Close() + w.multiCollator.Close() atomic.StoreInt32(&w.running, 0) close(w.exitCh) } @@ -756,7 +756,7 @@ func (w *worker) makeEnv(parent *types.Block, header *types.Header) (*environmen family: mapset.NewSet(), header: header, uncles: make(map[common.Hash]*types.Header), - logs: make([]*types.Log), + logs: []*types.Log{}, } // when 08 is processed ancestors contain 07 (quick block) for _, ancestor := range w.chain.GetBlocksFromHash(parent.Hash(), 7) { @@ -808,17 +808,17 @@ func (w *worker) updateSnapshot(env *environment) { } func commitTransaction(chain *core.BlockChain, chainConfig *params.ChainConfig, env *environment, tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) { - snap := env.state.Snapshot() + snap := env.state.Snapshot() - receipt, err := core.ApplyTransaction(chainConfig, chain, &coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *chain.GetVMConfig()) - if err != nil { - env.state.RevertToSnapshot(snap) - return nil, err - } - env.txs = append(env.txs, tx) - env.receipts = append(env.receipts, receipt) + receipt, err := core.ApplyTransaction(chainConfig, chain, &coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *chain.GetVMConfig()) + if err != nil { + env.state.RevertToSnapshot(snap) + return nil, err + } + env.txs = append(env.txs, tx) + env.receipts = append(env.receipts, receipt) - return receipt.Logs, nil + return receipt.Logs, nil } func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByPriceAndNonce, coinbase common.Address, interrupt *int32) bool { @@ -828,7 +828,7 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP } var coalescedLogs []*types.Log - curProfit := big.NewInt(0).Add(env.profit) + curProfit := new(big.Int).Set(env.profit) for { // In the following three cases, we will interrupt the execution of the transaction. @@ -874,10 +874,11 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP txs.Pop() continue } - gasPrice, err := tx.EffectiveGasTip(bs.work.env.header.BaseFee) - if err != nil { - return nil, err - } + gasPrice, err := tx.EffectiveGasTip(env.header.BaseFee) + if err != nil { + txs.Shift() + continue + } // Start executing the transaction env.state.Prepare(tx.Hash(), env.tcount) @@ -901,8 +902,8 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP case errors.Is(err, nil): // Everything ok, collect the logs and shift in the next transaction from the same account coalescedLogs = append(coalescedLogs, logs...) - gasUsed := new(big.Int).SetUint64(bs.work.env.receipts[-1].GasUsed) - curProfit.Add(curProfit, gasUsed.Mul(gasUsed, gasPrice)) + gasUsed := new(big.Int).SetUint64(env.receipts[len(env.receipts)-1].GasUsed) + curProfit.Add(curProfit, gasUsed.Mul(gasUsed, gasPrice)) env.tcount++ txs.Shift() @@ -919,9 +920,9 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP } } - if w.isRunning() { - env.profit = curProfit - } + if w.isRunning() { + env.profit = curProfit + } if !w.isRunning() && len(coalescedLogs) > 0 { // We don't push the pendingLogsEvent while we are sealing. The reason is that @@ -1036,39 +1037,6 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { return env, nil } -// fillTransactions retrieves the pending transactions from the txpool and fills them -// into the given sealing block. The transaction selection and ordering strategy can -// be customized with the plugin in the future. -func (w *worker) fillTransactions(interrupt *int32, env *environment) error { - // Split the pending transactions into locals and remotes - // Fill the block with all available pending transactions. - pending, err := w.eth.TxPool().Pending(true) - if err != nil { - log.Error("Failed to fetch pending transactions", "err", err) - return err - } - localTxs, remoteTxs := make(map[common.Address]types.Transactions), pending - for _, account := range w.eth.TxPool().Locals() { - if txs := remoteTxs[account]; len(txs) > 0 { - delete(remoteTxs, account) - localTxs[account] = txs - } - } - if len(localTxs) > 0 { - txs := types.NewTransactionsByPriceAndNonce(env.signer, localTxs, env.header.BaseFee) - if w.commitTransactions(env, txs, w.coinbase, interrupt) { - return nil - } - } - if len(remoteTxs) > 0 { - txs := types.NewTransactionsByPriceAndNonce(env.signer, remoteTxs, env.header.BaseFee) - if w.commitTransactions(env, txs, w.coinbase, interrupt) { - return nil - } - } - return nil -} - // generateWork generates a sealing block based on the given parameters. func (w *worker) generateWork(params *generateParams) (*types.Block, error) { work, err := w.prepareWork(params) @@ -1077,18 +1045,18 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) { } defer work.discard() - w.multiCollator.SuggestBlock(&work, nil) - var bestWork environment = work - profit := big.NewInt(0) + w.multiCollator.SuggestBlock(work, nil) + var bestWork *environment = work + profit := big.NewInt(0) - chooseMostProfitableBlock := func(e environment) { - if e.profit.Gt(profit) { - bestWork.discard() - bestWork = e - profit.Set(e.profit) - } - } - w.multiCollator.Collect(chooseMostProfitableBlock) + chooseMostProfitableBlock := func(e environment) { + if e.profit.Cmp(profit) > 0 { + bestWork.discard() + bestWork = &e + profit.Set(e.profit) + } + } + w.multiCollator.Collect(chooseMostProfitableBlock) return w.engine.FinalizeAndAssemble(w.chain, bestWork.header, bestWork.state, bestWork.txs, bestWork.unclelist(), bestWork.receipts) } @@ -1107,20 +1075,20 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) { w.commit(work.copy(), nil, false, start) } - // suggest the new block to the pool of active collators and interrupt sealing as more profitable strategies are fed back - w.multiCollator.SuggestBlock(&work) - profit = new(big.Int) - profit.Set(work.profit) - curBest := work - cb := func(newWork environment) { - if newWork.profit.Gt(profit) { - // probably don't need to copy work again here but do it for now just to be safe - w.commit(newWork.copy(), w.fullTaskHook, true, start) - profit.Set(newWork.profit) - curBest = newWork - } - } - w.multiCollator.Collect(cb) + // suggest the new block to the pool of active collators and interrupt sealing as more profitable strategies are fed back + w.multiCollator.SuggestBlock(work, interrupt) + profit := new(big.Int) + profit.Set(work.profit) + curBest := work + cb := func(newWork environment) { + if newWork.profit.Cmp(profit) > 0 { + // probably don't need to copy work again here but do it for now just to be safe + w.commit(newWork.copy(), w.fullTaskHook, true, start) + profit.Set(newWork.profit) + curBest = &newWork + } + } + w.multiCollator.Collect(cb) // Swap out the old work with the new one, terminating any leftover // prefetcher processes in the mean time and starting a new one. @@ -1129,7 +1097,7 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) { } w.current = curBest - if !w.isRunning() && len(coalescedLogs) > 0 { + if !w.isRunning() && len(w.current.logs) > 0 { // We don't push the pendingLogsEvent while we are sealing. The reason is that // when we are sealing, the worker will regenerate a sealing block every 3 seconds. // In order to avoid pushing the repeated pendingLog, we disable the pending log pushing. @@ -1137,30 +1105,30 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) { // make a copy, the state caches the logs and these logs get "upgraded" from pending to mined // logs by filling in the block hash when the block was mined by the local miner. This can // cause a race condition if a log was "upgraded" before the PendingLogsEvent is processed. - cpy := make([]*types.Log, len(coalescedLogs)) - for i, l := range coalescedLogs { + cpy := make([]*types.Log, len(w.current.logs)) + for i, l := range w.current.logs { cpy[i] = new(types.Log) *cpy[i] = *l } w.pendingLogsFeed.Send(cpy) } - // TODO how to determine how to adjust the resubmit interval here? i.e. how the ratio should be determined + // TODO how to determine how to adjust the resubmit interval here? i.e. how the ratio should be determined /* - if interrupt != nil { - if interrupt == commitInterruptNone { - w.resubmitAdjustCh <- &intervalAdjust{inc: false} - } else if interrupt == commitInterruptNone { - ratio := float64(gasLimit-w.current.gasPool.Gas()) / float64(gasLimit) - if ratio < 0.1 { - ratio = 0.1 - } - w.resubmitAdjustCh <- &intervalAdjust{ - ratio: ratio, - inc: true, - } - } - } + if interrupt != nil { + if interrupt == commitInterruptNone { + w.resubmitAdjustCh <- &intervalAdjust{inc: false} + } else if interrupt == commitInterruptNone { + ratio := float64(gasLimit-w.current.gasPool.Gas()) / float64(gasLimit) + if ratio < 0.1 { + ratio = 0.1 + } + w.resubmitAdjustCh <- &intervalAdjust{ + ratio: ratio, + inc: true, + } + } + } */ } @@ -1224,27 +1192,27 @@ func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64) (*types.B func copyLogs(logs []*types.Log) []*types.Log { result := make([]*types.Log, len(logs)) - for i, l := range logs { + for _, l := range logs { logCopy := types.Log{} - copy(logCopy.Address, l.Address) - for _, t := range l.Topics { - topic := common.Hash{} - copy(topic, t) - logCopy.Topics = append(logCopy.Topics, topic) - } - logCopy.Data = make([]byte, len(l.Data)) - copy(logCopy.Data, l.Data, len(l.Data)) - logCopy.BlockNumber = l.BlockNumber - copy(logCopy.TxHash, l.TxHash) - logCopy.TxIndex = l.TxIndex - copy(logCopy.BlockHash, l.BlockHash) - logCopy.Index = l.Index - logCopy.Removed = l.Removed - - result = append(result, &logCopy) + copy(logCopy.Address[:], l.Address[:]) + for _, t := range l.Topics { + topic := common.Hash{} + copy(topic[:], t[:]) + logCopy.Topics = append(logCopy.Topics, topic) + } + logCopy.Data = make([]byte, len(l.Data)) + copy(logCopy.Data[:], l.Data[:]) + logCopy.BlockNumber = l.BlockNumber + copy(logCopy.TxHash[:], l.TxHash[:]) + logCopy.TxIndex = l.TxIndex + copy(logCopy.BlockHash[:], l.BlockHash[:]) + logCopy.Index = l.Index + logCopy.Removed = l.Removed + + result = append(result, &logCopy) } - return result + return result } // copyReceipts makes a deep copy of the given receipts. From 81f25c6ac95aa278c804ba532411c3ae2ac69eaa Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Thu, 12 Aug 2021 00:44:34 +0200 Subject: [PATCH 19/26] comments --- miner/multi_collator.go | 45 ++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/miner/multi_collator.go b/miner/multi_collator.go index 81ba634fce04b..feb587dd3e596 100644 --- a/miner/multi_collator.go +++ b/miner/multi_collator.go @@ -32,6 +32,8 @@ const ( Uint64Max = 18446744073709551615 ) +// called by AddTransactions. If the provided error is not nil, none of the trasactions were added. +// If the error is nil, the receipts of all executed transactions are provided type AddTransactionsResultFunc func(error, []*types.Receipt) bool // BlockState represents a block-to-be-mined, which is being assembled. @@ -42,13 +44,13 @@ type BlockState interface { // AddTransactions adds the sequence of transactions to the blockstate. Either all // transactions are added, or none of them. In the latter case, the error // describes the reason why the txs could not be included. - // if ErrRecommit, the collator should not attempt to add more transactions to the - // block and submit the block for sealing. - // If ErrAbort is returned, the collator should immediately abort and return a - // value (true) from CollateBlock which indicates to the miner to discard the - // block + // If all transactions were successfully executed, the return value of the callback + // determines if the transactions are kept (true) or all reverted (false) AddTransactions(sequence types.Transactions, cb AddTransactionsResultFunc) + // Commit is called when the collator is done adding transactions to a block + // and wants to suggest it for sealing Commit() + // deep copy of a blockState Copy() BlockState Gas() (remaining uint64) Coinbase() common.Address @@ -56,12 +58,23 @@ type BlockState interface { Signer() types.Signer } +// collatorWork is provided by the CollatorPool to each collator goroutine +// when new work is being generated type collatorWork struct { env *environment counter uint64 interrupt *int32 } +func (w *collatorWork) Copy() collatorWork { + newEnv := w.env.copy() + return collatorWork{ + env: newEnv, + counter: w.counter, + interrupt: w.interrupt, + } +} + // Pool is an interface to the transaction pool type Pool interface { Pending(bool) (map[common.Address]types.Transactions, error) @@ -73,6 +86,7 @@ var ( ErrUnsupportedEIP155Tx = errors.New("replay-protected tx when EIP155 not enabled") ) +// collatorBlockState is an implementation of BlockState type collatorBlockState struct { work collatorWork c *collator @@ -87,15 +101,6 @@ func (c *collatorBlockState) Copy() BlockState { } } -func (w *collatorWork) Copy() collatorWork { - newEnv := w.env.copy() - return collatorWork{ - env: newEnv, - counter: w.counter, - interrupt: w.interrupt, - } -} - func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb AddTransactionsResultFunc) { var ( interrupt = bs.work.interrupt @@ -209,6 +214,8 @@ func (bs *collatorBlockState) Signer() types.Signer { return bs.work.env.signer } +// BlockCollator is the publicly-exposed interface +// for implementing custom block collation strategies type BlockCollator interface { CollateBlock(bs BlockState, pool Pool) /* @@ -232,8 +239,9 @@ type collator struct { pool Pool } -// mainLoop runs in a separate goroutine and handles the lifecycle of an active collator. -// TODO more explanation +// each active collator runs mainLoop() in a goroutine. +// It receives new work from the miner and listens for new blocks built from CollateBlock +// calling Commit() on the provided collatorBlockState func (c *collator) mainLoop() { for { select { @@ -264,6 +272,7 @@ var ( sideChainChSize = 10 ) +// MultiCollator manages multiple active collators type MultiCollator struct { counter uint64 responsiveCollatorCount int @@ -331,9 +340,9 @@ func (m *MultiCollator) SuggestBlock(work *environment, interrupt *int32) { type WorkResult func(environment) -// Collect retrieves filled blocks returned by active collators based on the block suggested by the previous call to SuggestedBlock. +// Collect retrieves filled blocks returned by active collators in response to the block suggested by the previous call to SuggestBlock. // It blocks until all responsive collators (ones which accepted the block from SuggestBlock) signal that they are done -// or the provided interrupt is set. +// or the interrupt provided in SetBlock is set. func (m *MultiCollator) Collect(cb WorkResult) { finishedCollators := []int{} for { From 74b6d03288e65d1210bf0214335baac6e7ee49c1 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Thu, 12 Aug 2021 21:58:00 +0200 Subject: [PATCH 20/26] handle coinbase correctly --- miner/multi_collator.go | 12 +++++++----- miner/worker.go | 43 +++++++++++++++++++++++++---------------- miner/worker_test.go | 2 +- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/miner/multi_collator.go b/miner/multi_collator.go index feb587dd3e596..09146b6749bea 100644 --- a/miner/multi_collator.go +++ b/miner/multi_collator.go @@ -112,7 +112,7 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad state = bs.work.env.state snap = state.Snapshot() curProfit = big.NewInt(0) - coinbaseBalanceBefore = state.GetBalance(bs.work.env.header.Coinbase) + coinbaseBalanceBefore = state.GetBalance(bs.c.w.getEtherbase()) tcount = bs.work.env.tcount err error logs []*types.Log @@ -180,7 +180,7 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad bs.work.env.txs = bs.work.env.txs[:startTCount] bs.work.env.receipts = bs.work.env.receipts[:startTCount] } else { - coinbaseBalanceAfter := bs.work.env.state.GetBalance(bs.work.env.header.Coinbase) + coinbaseBalanceAfter := bs.work.env.state.GetBalance(bs.c.w.getEtherbase()) coinbaseTransfer := big.NewInt(0).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore) curProfit.Add(curProfit, coinbaseTransfer) bs.work.env.logs = append(bs.work.env.logs, logs...) @@ -202,8 +202,7 @@ func (bs *collatorBlockState) Gas() uint64 { } func (bs *collatorBlockState) Coinbase() common.Address { - // TODO should clone this but I'm feeling lazy rn - return bs.work.env.header.Coinbase + return bs.c.w.getEtherbase() } func (bs *collatorBlockState) BaseFee() *big.Int { @@ -237,6 +236,8 @@ type collator struct { chainConfig *params.ChainConfig chain *core.BlockChain pool Pool + + w *worker } // each active collator runs mainLoop() in a goroutine. @@ -281,7 +282,7 @@ type MultiCollator struct { interrupt *int32 } -func NewMultiCollator(chainConfig *params.ChainConfig, chain *core.BlockChain, pool Pool, strategies []BlockCollator) MultiCollator { +func NewMultiCollator(chainConfig *params.ChainConfig, chain *core.BlockChain, pool Pool, strategies []BlockCollator, w *worker) MultiCollator { collators := []collator{} for _, s := range strategies { collators = append(collators, collator{ @@ -293,6 +294,7 @@ func NewMultiCollator(chainConfig *params.ChainConfig, chain *core.BlockChain, p chainConfig: chainConfig, chain: chain, pool: pool, + w: w, }) } return MultiCollator{ diff --git a/miner/worker.go b/miner/worker.go index 4c698316fb21c..2015e8da53cf5 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -270,8 +270,8 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus startCh: make(chan struct{}, 1), resubmitIntervalCh: make(chan time.Duration), resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize), - multiCollator: NewMultiCollator(chainConfig, eth.BlockChain(), eth.TxPool(), collators), } + worker.multiCollator = NewMultiCollator(chainConfig, eth.BlockChain(), eth.TxPool(), collators, worker) // start collator pool listening for new work worker.multiCollator.Start() // Subscribe NewTxsEvent for tx pool @@ -306,6 +306,14 @@ func (w *worker) setEtherbase(addr common.Address) { w.coinbase = addr } +func (w *worker) getEtherbase() common.Address { + w.mu.Lock() + defer w.mu.Unlock() + coinbaseCopy := common.Address{} + copy(coinbaseCopy[:], w.coinbase[:]) + return coinbaseCopy +} + func (w *worker) setGasCeil(ceil uint64) { w.mu.Lock() defer w.mu.Unlock() @@ -757,6 +765,8 @@ func (w *worker) makeEnv(parent *types.Block, header *types.Header) (*environmen header: header, uncles: make(map[common.Hash]*types.Header), logs: []*types.Log{}, + profit: new(big.Int).SetUint64(0), + gasPool: new(core.GasPool).AddGas(header.GasLimit), } // when 08 is processed ancestors contain 07 (quick block) for _, ancestor := range w.chain.GetBlocksFromHash(parent.Hash(), 7) { @@ -1077,8 +1087,7 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) { // suggest the new block to the pool of active collators and interrupt sealing as more profitable strategies are fed back w.multiCollator.SuggestBlock(work, interrupt) - profit := new(big.Int) - profit.Set(work.profit) + profit := work.profit curBest := work cb := func(newWork environment) { if newWork.profit.Cmp(profit) > 0 { @@ -1115,20 +1124,20 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) { // TODO how to determine how to adjust the resubmit interval here? i.e. how the ratio should be determined /* - if interrupt != nil { - if interrupt == commitInterruptNone { - w.resubmitAdjustCh <- &intervalAdjust{inc: false} - } else if interrupt == commitInterruptNone { - ratio := float64(gasLimit-w.current.gasPool.Gas()) / float64(gasLimit) - if ratio < 0.1 { - ratio = 0.1 - } - w.resubmitAdjustCh <- &intervalAdjust{ - ratio: ratio, - inc: true, - } - } - } + if interrupt != nil { + if interrupt == commitInterruptNone { + w.resubmitAdjustCh <- &intervalAdjust{inc: false} + } else if interrupt == commitInterruptNone { + ratio := float64(gasLimit-w.current.gasPool.Gas()) / float64(gasLimit) + if ratio < 0.1 { + ratio = 0.1 + } + w.resubmitAdjustCh <- &intervalAdjust{ + ratio: ratio, + inc: true, + } + } + } */ } diff --git a/miner/worker_test.go b/miner/worker_test.go index c9b2564d1c3d9..6756c22c67c71 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -196,7 +196,7 @@ func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction { func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int) (*worker, *testWorkerBackend) { backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks) backend.txPool.AddLocals(pendingTxs) - w := newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false) + w := newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false, []BlockCollator{&DefaultCollator{}}) w.setEtherbase(testBankAddress) return w, backend } From 6fe229d7283b10ef9dfccdadb7091bea156448ec Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Fri, 13 Aug 2021 00:49:29 +0200 Subject: [PATCH 21/26] add effective coinbase (etherbase) for current work to environment --- miner/multi_collator.go | 15 ++++++------- miner/worker.go | 47 ++++++++++++++++++++--------------------- 2 files changed, 30 insertions(+), 32 deletions(-) diff --git a/miner/multi_collator.go b/miner/multi_collator.go index 09146b6749bea..cc5f7980d6f64 100644 --- a/miner/multi_collator.go +++ b/miner/multi_collator.go @@ -112,7 +112,7 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad state = bs.work.env.state snap = state.Snapshot() curProfit = big.NewInt(0) - coinbaseBalanceBefore = state.GetBalance(bs.c.w.getEtherbase()) + coinbaseBalanceBefore = state.GetBalance(bs.work.env.coinbase) tcount = bs.work.env.tcount err error logs []*types.Log @@ -180,8 +180,8 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad bs.work.env.txs = bs.work.env.txs[:startTCount] bs.work.env.receipts = bs.work.env.receipts[:startTCount] } else { - coinbaseBalanceAfter := bs.work.env.state.GetBalance(bs.c.w.getEtherbase()) - coinbaseTransfer := big.NewInt(0).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore) + coinbaseBalanceAfter := bs.work.env.state.GetBalance(bs.work.env.coinbase) + coinbaseTransfer := new(big.Int).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore) curProfit.Add(curProfit, coinbaseTransfer) bs.work.env.logs = append(bs.work.env.logs, logs...) bs.work.env.profit = curProfit @@ -202,7 +202,9 @@ func (bs *collatorBlockState) Gas() uint64 { } func (bs *collatorBlockState) Coinbase() common.Address { - return bs.c.w.getEtherbase() + resultCopy := common.Address{} + copy(resultCopy[:], bs.work.env.coinbase[:]) + return resultCopy } func (bs *collatorBlockState) BaseFee() *big.Int { @@ -236,8 +238,6 @@ type collator struct { chainConfig *params.ChainConfig chain *core.BlockChain pool Pool - - w *worker } // each active collator runs mainLoop() in a goroutine. @@ -282,7 +282,7 @@ type MultiCollator struct { interrupt *int32 } -func NewMultiCollator(chainConfig *params.ChainConfig, chain *core.BlockChain, pool Pool, strategies []BlockCollator, w *worker) MultiCollator { +func NewMultiCollator(chainConfig *params.ChainConfig, chain *core.BlockChain, pool Pool, strategies []BlockCollator) MultiCollator { collators := []collator{} for _, s := range strategies { collators = append(collators, collator{ @@ -294,7 +294,6 @@ func NewMultiCollator(chainConfig *params.ChainConfig, chain *core.BlockChain, p chainConfig: chainConfig, chain: chain, pool: pool, - w: w, }) } return MultiCollator{ diff --git a/miner/worker.go b/miner/worker.go index 2015e8da53cf5..e417f46cf9bf3 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -94,6 +94,8 @@ type environment struct { uncles map[common.Hash]*types.Header profit *big.Int + coinbase common.Address + logs []*types.Log } @@ -120,6 +122,8 @@ func (env *environment) copy() *environment { for hash, uncle := range env.uncles { cpy.uncles[hash] = uncle } + cpy.coinbase = common.Address{} + copy(cpy.coinbase[:], env.coinbase[:]) return cpy } @@ -270,8 +274,8 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus startCh: make(chan struct{}, 1), resubmitIntervalCh: make(chan time.Duration), resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize), + multiCollator: NewMultiCollator(chainConfig, eth.BlockChain(), eth.TxPool(), collators), } - worker.multiCollator = NewMultiCollator(chainConfig, eth.BlockChain(), eth.TxPool(), collators, worker) // start collator pool listening for new work worker.multiCollator.Start() // Subscribe NewTxsEvent for tx pool @@ -306,14 +310,6 @@ func (w *worker) setEtherbase(addr common.Address) { w.coinbase = addr } -func (w *worker) getEtherbase() common.Address { - w.mu.Lock() - defer w.mu.Unlock() - coinbaseCopy := common.Address{} - copy(coinbaseCopy[:], w.coinbase[:]) - return coinbaseCopy -} - func (w *worker) setGasCeil(ceil uint64) { w.mu.Lock() defer w.mu.Unlock() @@ -757,6 +753,9 @@ func (w *worker) makeEnv(parent *types.Block, header *types.Header) (*environmen } state.StartPrefetcher("miner") + coinbaseCopy := common.Address{} + copy(coinbaseCopy[:], w.coinbase[:]) + env := &environment{ signer: types.MakeSigner(w.chainConfig, header.Number), state: state, @@ -767,6 +766,7 @@ func (w *worker) makeEnv(parent *types.Block, header *types.Header) (*environmen logs: []*types.Log{}, profit: new(big.Int).SetUint64(0), gasPool: new(core.GasPool).AddGas(header.GasLimit), + coinbase: coinbaseCopy, } // when 08 is processed ancestors contain 07 (quick block) for _, ancestor := range w.chain.GetBlocksFromHash(parent.Hash(), 7) { @@ -1123,22 +1123,21 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) { } // TODO how to determine how to adjust the resubmit interval here? i.e. how the ratio should be determined - /* - if interrupt != nil { - if interrupt == commitInterruptNone { - w.resubmitAdjustCh <- &intervalAdjust{inc: false} - } else if interrupt == commitInterruptNone { - ratio := float64(gasLimit-w.current.gasPool.Gas()) / float64(gasLimit) - if ratio < 0.1 { - ratio = 0.1 - } - w.resubmitAdjustCh <- &intervalAdjust{ - ratio: ratio, - inc: true, - } - } + if interrupt != nil { + if atomic.LoadInt32(interrupt) == commitInterruptNone { + w.resubmitAdjustCh <- &intervalAdjust{inc: false} + } else if atomic.LoadInt32(interrupt) == commitInterruptNone { + gasLimit := w.current.header.GasLimit + ratio := float64(gasLimit-w.current.gasPool.Gas()) / float64(gasLimit) + if ratio < 0.1 { + ratio = 0.1 } - */ + w.resubmitAdjustCh <- &intervalAdjust{ + ratio: ratio, + inc: true, + } + } + } } From cba7763c3790b45f1d5dfac3043419e75063ff37 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Fri, 13 Aug 2021 17:25:45 +0200 Subject: [PATCH 22/26] address review --- miner/default_collator.go | 2 +- miner/multi_collator.go | 63 +++++++++++++++++++++++---------------- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/miner/default_collator.go b/miner/default_collator.go index f81652c96a686..236423560bb61 100644 --- a/miner/default_collator.go +++ b/miner/default_collator.go @@ -29,7 +29,7 @@ type DefaultCollator struct{} func submitTransactions(bs BlockState, txs *types.TransactionsByPriceAndNonce) bool { var shouldAbort bool - cb := func(err error, receipts []*types.Receipt) bool { + cb := func(err error, receipts *types.Receipt) bool { switch { case errors.Is(err, core.ErrGasLimitReached): fallthrough diff --git a/miner/multi_collator.go b/miner/multi_collator.go index cc5f7980d6f64..760cbc52d33eb 100644 --- a/miner/multi_collator.go +++ b/miner/multi_collator.go @@ -18,6 +18,7 @@ package miner import ( "errors" + "math" "math/big" "sync/atomic" @@ -28,13 +29,9 @@ import ( "github.com/ethereum/go-ethereum/params" ) -const ( - Uint64Max = 18446744073709551615 -) - // called by AddTransactions. If the provided error is not nil, none of the trasactions were added. // If the error is nil, the receipts of all executed transactions are provided -type AddTransactionsResultFunc func(error, []*types.Receipt) bool +type AddTransactionsResultFunc func(error, *types.Receipt) bool // BlockState represents a block-to-be-mined, which is being assembled. // A collator can add transactions by calling AddTransactions. @@ -56,6 +53,7 @@ type BlockState interface { Coinbase() common.Address BaseFee() *big.Int Signer() types.Signer + Profit() *big.Int } // collatorWork is provided by the CollatorPool to each collator goroutine @@ -111,12 +109,14 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad chain = bs.c.chain state = bs.work.env.state snap = state.Snapshot() - curProfit = big.NewInt(0) + curProfit = new(big.Int).Set(bs.work.env.profit) + startProfit = new(big.Int).Set(bs.work.env.profit) coinbaseBalanceBefore = state.GetBalance(bs.work.env.coinbase) tcount = bs.work.env.tcount err error logs []*types.Log startTCount = bs.work.env.tcount + shouldRevert bool ) if bs.done { err = ErrAbort @@ -127,11 +127,15 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad for _, tx := range sequence { if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone { bs.done = true + shouldRevert = true + cb(ErrAbort, nil) break } if gasPool.Gas() < params.TxGas { log.Trace("Not enough gas for further transactions", "have", gasPool, "want", params.TxGas) err = core.ErrGasLimitReached + cb(err, nil) + shouldRevert = true break } from, _ := types.Sender(signer, tx) @@ -140,10 +144,13 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad if tx.Protected() && !chainConfig.IsEIP155(header.Number) { log.Trace("encountered replay-protected transaction when chain doesn't support replay protection", "hash", tx.Hash(), "eip155", chainConfig.EIP155Block) err = ErrUnsupportedEIP155Tx + cb(err, nil) break } gasPrice, err := tx.EffectiveGasTip(bs.work.env.header.BaseFee) if err != nil { + shouldRevert = true + cb(err, nil) break } // Start executing the transaction @@ -154,37 +161,35 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad if err == nil { logs = append(logs, txLogs...) gasUsed := new(big.Int).SetUint64(bs.work.env.receipts[len(bs.work.env.receipts)-1].GasUsed) + // TODO remove this allocation once things are working + curProfit.Add(curProfit, gasUsed.Mul(gasUsed, gasPrice)) - tcount++ + coinbaseBalanceAfter := bs.work.env.state.GetBalance(bs.work.env.coinbase) + coinbaseTransfer := new(big.Int).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore) + curProfit.Add(curProfit, coinbaseTransfer) + bs.work.env.profit.Set(curProfit) + + if cb(nil, bs.work.env.receipts[len(bs.work.env.receipts) - 1]) { + shouldRevert = true + break + } else { + tcount++ + } } else { + cb(err, nil) + shouldRevert = true log.Trace("Tx block inclusion failed", "sender", from, "nonce", tx.Nonce(), "type", tx.Type(), "hash", tx.Hash(), "err", err) break } } - var txReceipts []*types.Receipt = nil - if err == nil { - txReceipts = bs.work.env.receipts[startTCount:tcount] - } - // TODO: deep copy the tx receipts here or add a disclaimer to implementors not to modify them? - shouldRevert := cb(err, txReceipts) - - if err != nil || shouldRevert { + if shouldRevert { state.RevertToSnapshot(snap) - - // remove the txs and receipts that were added - for i := startTCount; i < tcount; i++ { - bs.work.env.txs[i] = nil - bs.work.env.receipts[i] = nil - } bs.work.env.txs = bs.work.env.txs[:startTCount] bs.work.env.receipts = bs.work.env.receipts[:startTCount] + bs.work.env.profit.Set(startProfit) } else { - coinbaseBalanceAfter := bs.work.env.state.GetBalance(bs.work.env.coinbase) - coinbaseTransfer := new(big.Int).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore) - curProfit.Add(curProfit, coinbaseTransfer) bs.work.env.logs = append(bs.work.env.logs, logs...) - bs.work.env.profit = curProfit bs.work.env.tcount = tcount } return @@ -215,6 +220,10 @@ func (bs *collatorBlockState) Signer() types.Signer { return bs.work.env.signer } +func (bs *collatorBlockState) Profit() *big.Int { + return new(big.Int).Set(bs.work.env.profit) +} + // BlockCollator is the publicly-exposed interface // for implementing custom block collation strategies type BlockCollator interface { @@ -324,7 +333,7 @@ func (m *MultiCollator) Close() { // for the purpose of not expecting a response back (for this round) during // polling performed by Collect func (m *MultiCollator) SuggestBlock(work *environment, interrupt *int32) { - if m.counter == Uint64Max { + if m.counter == math.MaxUint64 { m.counter = 0 } else { m.counter++ @@ -335,6 +344,7 @@ func (m *MultiCollator) SuggestBlock(work *environment, interrupt *int32) { select { case c.newWorkCh <- collatorWork{env: work.copy(), counter: m.counter, interrupt: interrupt}: m.responsiveCollatorCount++ + default: } } } @@ -365,6 +375,7 @@ func (m *MultiCollator) Collect(cb WorkResult) { for _, finishedCollator := range finishedCollators { if i == finishedCollator { shouldIgnore = true + break } } if shouldIgnore { From dea8326238062febfff26316705604d1938124a8 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Fri, 13 Aug 2021 17:49:37 +0200 Subject: [PATCH 23/26] set profit --- miner/worker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/miner/worker.go b/miner/worker.go index e417f46cf9bf3..cf3e89f0987bc 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -110,6 +110,7 @@ func (env *environment) copy() *environment { header: types.CopyHeader(env.header), receipts: copyReceipts(env.receipts), logs: copyLogs(env.logs), + profit: new(big.Int).Set(env.profit), } if env.gasPool != nil { cpy.gasPool = &(*env.gasPool) From b467deebf173574ff65bd6436d01033657e8801b Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Fri, 13 Aug 2021 21:39:55 +0200 Subject: [PATCH 24/26] calculate profit correctly --- miner/multi_collator.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/miner/multi_collator.go b/miner/multi_collator.go index 760cbc52d33eb..0adb5a9334268 100644 --- a/miner/multi_collator.go +++ b/miner/multi_collator.go @@ -111,7 +111,6 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad snap = state.Snapshot() curProfit = new(big.Int).Set(bs.work.env.profit) startProfit = new(big.Int).Set(bs.work.env.profit) - coinbaseBalanceBefore = state.GetBalance(bs.work.env.coinbase) tcount = bs.work.env.tcount err error logs []*types.Log @@ -154,7 +153,7 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad break } // Start executing the transaction - state.Prepare(tx.Hash(), bs.work.env.tcount) + state.Prepare(tx.Hash(), tcount) var txLogs []*types.Log txLogs, err = commitTransaction(chain, chainConfig, bs.work.env, tx, bs.Coinbase()) @@ -162,13 +161,8 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad logs = append(logs, txLogs...) gasUsed := new(big.Int).SetUint64(bs.work.env.receipts[len(bs.work.env.receipts)-1].GasUsed) // TODO remove this allocation once things are working - curProfit.Add(curProfit, gasUsed.Mul(gasUsed, gasPrice)) - coinbaseBalanceAfter := bs.work.env.state.GetBalance(bs.work.env.coinbase) - coinbaseTransfer := new(big.Int).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore) - curProfit.Add(curProfit, coinbaseTransfer) bs.work.env.profit.Set(curProfit) - if cb(nil, bs.work.env.receipts[len(bs.work.env.receipts) - 1]) { shouldRevert = true break From 4cde3fb3d1909d0c7f626fe8f9b871fed1dbcf73 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Sun, 15 Aug 2021 02:02:04 +0200 Subject: [PATCH 25/26] remove unecessary default case on collaor mainLoop --- miner/multi_collator.go | 75 ++++++++++++++++++++--------------------- miner/worker.go | 2 +- 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/miner/multi_collator.go b/miner/multi_collator.go index 0adb5a9334268..e525a66191112 100644 --- a/miner/multi_collator.go +++ b/miner/multi_collator.go @@ -18,7 +18,7 @@ package miner import ( "errors" - "math" + "math" "math/big" "sync/atomic" @@ -53,7 +53,7 @@ type BlockState interface { Coinbase() common.Address BaseFee() *big.Int Signer() types.Signer - Profit() *big.Int + Profit() *big.Int } // collatorWork is provided by the CollatorPool to each collator goroutine @@ -101,21 +101,21 @@ func (c *collatorBlockState) Copy() BlockState { func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb AddTransactionsResultFunc) { var ( - interrupt = bs.work.interrupt - header = bs.work.env.header - gasPool = bs.work.env.gasPool - signer = bs.work.env.signer - chainConfig = bs.c.chainConfig - chain = bs.c.chain - state = bs.work.env.state - snap = state.Snapshot() - curProfit = new(big.Int).Set(bs.work.env.profit) - startProfit = new(big.Int).Set(bs.work.env.profit) - tcount = bs.work.env.tcount - err error - logs []*types.Log - startTCount = bs.work.env.tcount - shouldRevert bool + interrupt = bs.work.interrupt + header = bs.work.env.header + gasPool = bs.work.env.gasPool + signer = bs.work.env.signer + chainConfig = bs.c.chainConfig + chain = bs.c.chain + state = bs.work.env.state + snap = state.Snapshot() + curProfit = new(big.Int).Set(bs.work.env.profit) + startProfit = new(big.Int).Set(bs.work.env.profit) + tcount = bs.work.env.tcount + err error + logs []*types.Log + startTCount = bs.work.env.tcount + shouldRevert bool ) if bs.done { err = ErrAbort @@ -126,15 +126,15 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad for _, tx := range sequence { if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone { bs.done = true - shouldRevert = true - cb(ErrAbort, nil) + shouldRevert = true + cb(ErrAbort, nil) break } if gasPool.Gas() < params.TxGas { log.Trace("Not enough gas for further transactions", "have", gasPool, "want", params.TxGas) err = core.ErrGasLimitReached - cb(err, nil) - shouldRevert = true + cb(err, nil) + shouldRevert = true break } from, _ := types.Sender(signer, tx) @@ -143,13 +143,13 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad if tx.Protected() && !chainConfig.IsEIP155(header.Number) { log.Trace("encountered replay-protected transaction when chain doesn't support replay protection", "hash", tx.Hash(), "eip155", chainConfig.EIP155Block) err = ErrUnsupportedEIP155Tx - cb(err, nil) + cb(err, nil) break } gasPrice, err := tx.EffectiveGasTip(bs.work.env.header.BaseFee) if err != nil { - shouldRevert = true - cb(err, nil) + shouldRevert = true + cb(err, nil) break } // Start executing the transaction @@ -160,18 +160,18 @@ func (bs *collatorBlockState) AddTransactions(sequence types.Transactions, cb Ad if err == nil { logs = append(logs, txLogs...) gasUsed := new(big.Int).SetUint64(bs.work.env.receipts[len(bs.work.env.receipts)-1].GasUsed) - // TODO remove this allocation once things are working + // TODO remove this allocation once things are working curProfit.Add(curProfit, gasUsed.Mul(gasUsed, gasPrice)) - bs.work.env.profit.Set(curProfit) - if cb(nil, bs.work.env.receipts[len(bs.work.env.receipts) - 1]) { - shouldRevert = true - break - } else { - tcount++ - } + bs.work.env.profit.Set(curProfit) + if cb(nil, bs.work.env.receipts[len(bs.work.env.receipts)-1]) { + shouldRevert = true + break + } else { + tcount++ + } } else { - cb(err, nil) - shouldRevert = true + cb(err, nil) + shouldRevert = true log.Trace("Tx block inclusion failed", "sender", from, "nonce", tx.Nonce(), "type", tx.Type(), "hash", tx.Hash(), "err", err) break @@ -215,7 +215,7 @@ func (bs *collatorBlockState) Signer() types.Signer { } func (bs *collatorBlockState) Profit() *big.Int { - return new(big.Int).Set(bs.work.env.profit) + return new(big.Int).Set(bs.work.env.profit) } // BlockCollator is the publicly-exposed interface @@ -263,7 +263,6 @@ func (c *collator) mainLoop() { case sideHeader := <-c.sideChainCh: _ = sideHeader // TODO call hook here - default: } } } @@ -338,7 +337,7 @@ func (m *MultiCollator) SuggestBlock(work *environment, interrupt *int32) { select { case c.newWorkCh <- collatorWork{env: work.copy(), counter: m.counter, interrupt: interrupt}: m.responsiveCollatorCount++ - default: + default: } } } @@ -369,7 +368,7 @@ func (m *MultiCollator) Collect(cb WorkResult) { for _, finishedCollator := range finishedCollators { if i == finishedCollator { shouldIgnore = true - break + break } } if shouldIgnore { diff --git a/miner/worker.go b/miner/worker.go index cf3e89f0987bc..7c42033aff713 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -110,7 +110,7 @@ func (env *environment) copy() *environment { header: types.CopyHeader(env.header), receipts: copyReceipts(env.receipts), logs: copyLogs(env.logs), - profit: new(big.Int).Set(env.profit), + profit: new(big.Int).Set(env.profit), } if env.gasPool != nil { cpy.gasPool = &(*env.gasPool) From dd709c82e296c68a3dddfcee4ea811f3f792ff1f Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Sun, 15 Aug 2021 22:40:27 +0000 Subject: [PATCH 26/26] fix basefee --- miner/multi_collator.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/miner/multi_collator.go b/miner/multi_collator.go index e525a66191112..116f74c62bfad 100644 --- a/miner/multi_collator.go +++ b/miner/multi_collator.go @@ -207,7 +207,11 @@ func (bs *collatorBlockState) Coinbase() common.Address { } func (bs *collatorBlockState) BaseFee() *big.Int { - return new(big.Int).Set(bs.work.env.header.BaseFee) + if bs.work.env.header.BaseFee != nil { + return new(big.Int).Set(bs.work.env.header.BaseFee) + } + + return nil } func (bs *collatorBlockState) Signer() types.Signer {