From f768ce56b9efc4e3bc05bdbb18bdb811e60b81ec Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Wed, 31 May 2023 15:09:49 +0800 Subject: [PATCH] miner: suspend miner if node is syncing (#27218) Drop the notions of uncles, and disables activities while syncing - Disable activities (e.g. generate pending state) while node is syncing, - Disable empty block submission (but empty block is still kept for payload building), - Drop uncle notion since (ethash is already deprecated) --- eth/api_backend.go | 9 ++ eth/api_debug.go | 6 + eth/filters/filter.go | 2 +- miner/miner.go | 30 ++--- miner/unconfirmed.go | 136 --------------------- miner/unconfirmed_test.go | 87 -------------- miner/worker.go | 241 ++++++-------------------------------- miner/worker_test.go | 225 ++++------------------------------- 8 files changed, 86 insertions(+), 650 deletions(-) delete mode 100644 miner/unconfirmed.go delete mode 100644 miner/unconfirmed_test.go diff --git a/eth/api_backend.go b/eth/api_backend.go index c9ad4311e74c..927dcdef1042 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -68,6 +68,9 @@ func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb // Pending block is only known by the miner if number == rpc.PendingBlockNumber { block := b.eth.miner.PendingBlock() + if block == nil { + return nil, errors.New("pending block is not available") + } return block.Header(), nil } // Otherwise resolve and return the block @@ -122,6 +125,9 @@ func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumbe // Pending block is only known by the miner if number == rpc.PendingBlockNumber { block := b.eth.miner.PendingBlock() + if block == nil { + return nil, errors.New("pending block is not available") + } return block, nil } // Otherwise resolve and return the block @@ -196,6 +202,9 @@ func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.B // Pending state is only known by the miner if number == rpc.PendingBlockNumber { block, state := b.eth.miner.Pending() + if block == nil || state == nil { + return nil, nil, errors.New("pending state is not available") + } return state, block.Header(), nil } // Otherwise resolve the block number and return its state diff --git a/eth/api_debug.go b/eth/api_debug.go index 929e0460f7c5..7cf0239174fc 100644 --- a/eth/api_debug.go +++ b/eth/api_debug.go @@ -56,6 +56,9 @@ func (api *DebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error) { // both the pending block as well as the pending state from // the miner and operate on those _, stateDb := api.eth.miner.Pending() + if stateDb == nil { + return state.Dump{}, errors.New("pending state is not available") + } return stateDb.RawDump(opts), nil } var header *types.Header @@ -141,6 +144,9 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex // both the pending block as well as the pending state from // the miner and operate on those _, stateDb = api.eth.miner.Pending() + if stateDb == nil { + return state.IteratorDump{}, errors.New("pending state is not available") + } } else { var header *types.Header if number == rpc.LatestBlockNumber { diff --git a/eth/filters/filter.go b/eth/filters/filter.go index 7543c06033e4..d65bbfacfc85 100644 --- a/eth/filters/filter.go +++ b/eth/filters/filter.go @@ -334,7 +334,7 @@ func (f *Filter) checkMatches(ctx context.Context, header *types.Header) ([]*typ // pendingLogs returns the logs matching the filter criteria within the pending block. func (f *Filter) pendingLogs() []*types.Log { block, receipts := f.sys.backend.PendingBlockAndReceipts() - if block == nil { + if block == nil || receipts == nil { return nil } if bloomFilter(block.Bloom(), f.addresses, f.topics) { diff --git a/miner/miner.go b/miner/miner.go index b1d1f7c4cbfe..b7273948f5e7 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -131,16 +131,22 @@ func (miner *Miner) update() { shouldStart = true log.Info("Mining aborted due to sync") } + miner.worker.syncing.Store(true) + case downloader.FailedEvent: canStart = true if shouldStart { miner.worker.start() } + miner.worker.syncing.Store(false) + case downloader.DoneEvent: canStart = true if shouldStart { miner.worker.start() } + miner.worker.syncing.Store(false) + // Stop reacting to downloader events events.Unsubscribe() } @@ -196,12 +202,14 @@ func (miner *Miner) SetRecommitInterval(interval time.Duration) { miner.worker.setRecommitInterval(interval) } -// Pending returns the currently pending block and associated state. +// Pending returns the currently pending block and associated state. The returned +// values can be nil in case the pending block is not initialized func (miner *Miner) Pending() (*types.Block, *state.StateDB) { return miner.worker.pending() } -// PendingBlock returns the currently pending block. +// PendingBlock returns the currently pending block. The returned block can be +// nil in case the pending block is not initialized. // // Note, to access both the pending block and the pending state // simultaneously, please use Pending(), as the pending state can @@ -211,6 +219,7 @@ func (miner *Miner) PendingBlock() *types.Block { } // PendingBlockAndReceipts returns the currently pending block and corresponding receipts. +// The returned values can be nil in case the pending block is not initialized. func (miner *Miner) PendingBlockAndReceipts() (*types.Block, types.Receipts) { return miner.worker.pendingBlockAndReceipts() } @@ -225,23 +234,6 @@ func (miner *Miner) SetGasCeil(ceil uint64) { miner.worker.setGasCeil(ceil) } -// EnablePreseal turns on the preseal mining feature. It's enabled by default. -// Note this function shouldn't be exposed to API, it's unnecessary for users -// (miners) to actually know the underlying detail. It's only for outside project -// which uses this library. -func (miner *Miner) EnablePreseal() { - miner.worker.enablePreseal() -} - -// DisablePreseal turns off the preseal mining feature. It's necessary for some -// fake consensus engine which can seal blocks instantaneously. -// Note this function shouldn't be exposed to API, it's unnecessary for users -// (miners) to actually know the underlying detail. It's only for outside project -// which uses this library. -func (miner *Miner) DisablePreseal() { - miner.worker.disablePreseal() -} - // 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/unconfirmed.go b/miner/unconfirmed.go deleted file mode 100644 index 0489f1ea4adb..000000000000 --- a/miner/unconfirmed.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2016 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 ( - "container/ring" - "sync" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" -) - -// chainRetriever is used by the unconfirmed block set to verify whether a previously -// mined block is part of the canonical chain or not. -type chainRetriever interface { - // GetHeaderByNumber retrieves the canonical header associated with a block number. - GetHeaderByNumber(number uint64) *types.Header - - // GetBlockByNumber retrieves the canonical block associated with a block number. - GetBlockByNumber(number uint64) *types.Block -} - -// unconfirmedBlock is a small collection of metadata about a locally mined block -// that is placed into a unconfirmed set for canonical chain inclusion tracking. -type unconfirmedBlock struct { - index uint64 - hash common.Hash -} - -// unconfirmedBlocks implements a data structure to maintain locally mined blocks -// have not yet reached enough maturity to guarantee chain inclusion. It is -// used by the miner to provide logs to the user when a previously mined block -// has a high enough guarantee to not be reorged out of the canonical chain. -type unconfirmedBlocks struct { - chain chainRetriever // Blockchain to verify canonical status through - depth uint // Depth after which to discard previous blocks - blocks *ring.Ring // Block infos to allow canonical chain cross checks - lock sync.Mutex // Protects the fields from concurrent access -} - -// newUnconfirmedBlocks returns new data structure to track currently unconfirmed blocks. -func newUnconfirmedBlocks(chain chainRetriever, depth uint) *unconfirmedBlocks { - return &unconfirmedBlocks{ - chain: chain, - depth: depth, - } -} - -// Insert adds a new block to the set of unconfirmed ones. -func (set *unconfirmedBlocks) Insert(index uint64, hash common.Hash) { - // If a new block was mined locally, shift out any old enough blocks - set.Shift(index) - - // Create the new item as its own ring - item := ring.New(1) - item.Value = &unconfirmedBlock{ - index: index, - hash: hash, - } - // Set as the initial ring or append to the end - set.lock.Lock() - defer set.lock.Unlock() - - if set.blocks == nil { - set.blocks = item - } else { - set.blocks.Move(-1).Link(item) - } - // Display a log for the user to notify of a new mined block unconfirmed - log.Info("🔨 mined potential block", "number", index, "hash", hash) -} - -// Shift drops all unconfirmed blocks from the set which exceed the unconfirmed sets depth -// allowance, checking them against the canonical chain for inclusion or staleness -// report. -func (set *unconfirmedBlocks) Shift(height uint64) { - set.lock.Lock() - defer set.lock.Unlock() - - for set.blocks != nil { - // Retrieve the next unconfirmed block and abort if too fresh - next := set.blocks.Value.(*unconfirmedBlock) - if next.index+uint64(set.depth) > height { - break - } - // Block seems to exceed depth allowance, check for canonical status - header := set.chain.GetHeaderByNumber(next.index) - switch { - case header == nil: - log.Warn("Failed to retrieve header of mined block", "number", next.index, "hash", next.hash) - case header.Hash() == next.hash: - log.Info("🔗 block reached canonical chain", "number", next.index, "hash", next.hash) - default: - // Block is not canonical, check whether we have an uncle or a lost block - included := false - for number := next.index; !included && number < next.index+uint64(set.depth) && number <= height; number++ { - if block := set.chain.GetBlockByNumber(number); block != nil { - for _, uncle := range block.Uncles() { - if uncle.Hash() == next.hash { - included = true - break - } - } - } - } - if included { - log.Info("â‘‚ block became an uncle", "number", next.index, "hash", next.hash) - } else { - log.Info("😱 block lost", "number", next.index, "hash", next.hash) - } - } - // Drop the block out of the ring - if set.blocks.Value == set.blocks.Next().Value { - set.blocks = nil - } else { - set.blocks = set.blocks.Move(-1) - set.blocks.Unlink(1) - set.blocks = set.blocks.Move(1) - } - } -} diff --git a/miner/unconfirmed_test.go b/miner/unconfirmed_test.go deleted file mode 100644 index 60958f658abc..000000000000 --- a/miner/unconfirmed_test.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2016 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 ( - "testing" - - "github.com/ethereum/go-ethereum/core/types" -) - -// noopChainRetriever is an implementation of headerRetriever that always -// returns nil for any requested headers. -type noopChainRetriever struct{} - -func (r *noopChainRetriever) GetHeaderByNumber(number uint64) *types.Header { - return nil -} -func (r *noopChainRetriever) GetBlockByNumber(number uint64) *types.Block { - return nil -} - -// Tests that inserting blocks into the unconfirmed set accumulates them until -// the desired depth is reached, after which they begin to be dropped. -func TestUnconfirmedInsertBounds(t *testing.T) { - limit := uint(10) - - pool := newUnconfirmedBlocks(new(noopChainRetriever), limit) - for depth := uint64(0); depth < 2*uint64(limit); depth++ { - // Insert multiple blocks for the same level just to stress it - for i := 0; i < int(depth); i++ { - pool.Insert(depth, [32]byte{byte(depth), byte(i)}) - } - // Validate that no blocks below the depth allowance are left in - pool.blocks.Do(func(block interface{}) { - if block := block.(*unconfirmedBlock); block.index+uint64(limit) <= depth { - t.Errorf("depth %d: block %x not dropped", depth, block.hash) - } - }) - } -} - -// Tests that shifting blocks out of the unconfirmed set works both for normal -// cases as well as for corner cases such as empty sets, empty shifts or full -// shifts. -func TestUnconfirmedShifts(t *testing.T) { - // Create a pool with a few blocks on various depths - limit, start := uint(10), uint64(25) - - pool := newUnconfirmedBlocks(new(noopChainRetriever), limit) - for depth := start; depth < start+uint64(limit); depth++ { - pool.Insert(depth, [32]byte{byte(depth)}) - } - // Try to shift below the limit and ensure no blocks are dropped - pool.Shift(start + uint64(limit) - 1) - if n := pool.blocks.Len(); n != int(limit) { - t.Errorf("unconfirmed count mismatch: have %d, want %d", n, limit) - } - // Try to shift half the blocks out and verify remainder - pool.Shift(start + uint64(limit) - 1 + uint64(limit/2)) - if n := pool.blocks.Len(); n != int(limit)/2 { - t.Errorf("unconfirmed count mismatch: have %d, want %d", n, limit/2) - } - // Try to shift all the remaining blocks out and verify emptiness - pool.Shift(start + 2*uint64(limit)) - if n := pool.blocks.Len(); n != 0 { - t.Errorf("unconfirmed count mismatch: have %d, want %d", n, 0) - } - // Try to shift out from the empty set and make sure it doesn't break - pool.Shift(start + 3*uint64(limit)) - if n := pool.blocks.Len(); n != 0 { - t.Errorf("unconfirmed count mismatch: have %d, want %d", n, 0) - } -} diff --git a/miner/worker.go b/miner/worker.go index 936a9e74a5b6..d61f8dcada51 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -24,7 +24,6 @@ import ( "sync/atomic" "time" - mapset "github.com/deckarep/golang-set/v2" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" @@ -48,15 +47,9 @@ const ( // chainHeadChanSize is the size of channel listening to ChainHeadEvent. chainHeadChanSize = 10 - // chainSideChanSize is the size of channel listening to ChainSideEvent. - chainSideChanSize = 10 - // resubmitAdjustChanSize is the size of resubmitting interval adjustment channel. resubmitAdjustChanSize = 10 - // sealingLogAtDepth is the number of confirmations before logging successful sealing. - sealingLogAtDepth = 7 - // minRecommitInterval is the minimal time interval to recreate the sealing block with // any newly arrived transactions. minRecommitInterval = 1 * time.Second @@ -86,57 +79,36 @@ var ( // 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[common.Hash] // ancestor set (used for checking uncle parent validity) - family mapset.Set[common.Hash] // family set (used for checking uncle invalidity) - tcount int // tx count in cycle - gasPool *core.GasPool // available gas used to pack transactions - coinbase common.Address + signer types.Signer + state *state.StateDB // apply state changes here + tcount int // tx count in cycle + gasPool *core.GasPool // available gas used to pack transactions + coinbase common.Address header *types.Header txs []*types.Transaction receipts []*types.Receipt - 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(), - tcount: env.tcount, - coinbase: env.coinbase, - header: types.CopyHeader(env.header), - receipts: copyReceipts(env.receipts), + signer: env.signer, + state: env.state.Copy(), + tcount: env.tcount, + coinbase: env.coinbase, + header: types.CopyHeader(env.header), + receipts: copyReceipts(env.receipts), } if env.gasPool != nil { gasPool := *env.gasPool cpy.gasPool = &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(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. @@ -165,7 +137,6 @@ const ( // newWorkReq represents a request for new sealing work submitting with relative interrupt notifier. type newWorkReq struct { interrupt *atomic.Int32 - noempty bool timestamp int64 } @@ -206,8 +177,6 @@ type worker struct { txsSub event.Subscription chainHeadCh chan core.ChainHeadEvent chainHeadSub event.Subscription - chainSideCh chan core.ChainSideEvent - chainSideSub event.Subscription // Channels newWorkCh chan *newWorkReq @@ -221,10 +190,7 @@ type worker struct { wg sync.WaitGroup - current *environment // An environment for current running cycle. - 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. mu sync.RWMutex // The lock used to protect the coinbase and extra fields coinbase common.Address @@ -241,13 +207,7 @@ type worker struct { // atomic status counters running atomic.Bool // The indicator whether the consensus engine is running or not. newTxs atomic.Int32 // New arrival transaction count since last sealing work submitting. - - // noempty is the flag used to control whether the feature of pre-seal empty - // block is enabled. The default value is false(pre-seal is enabled by default). - // But in some special scenario the consensus engine will seal blocks instantaneously, - // in this case this feature will add all empty blocks into canonical chain - // non-stop and no real transaction will be included. - noempty atomic.Bool + syncing atomic.Bool // The indicator whether the node is still syncing. // newpayloadTimeout is the maximum timeout allowance for creating payload. // The default value is 2 seconds but node operator can set it to arbitrary @@ -279,15 +239,11 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus chain: eth.BlockChain(), mux: mux, isLocalBlock: isLocalBlock, - localUncles: make(map[common.Hash]*types.Block), - remoteUncles: make(map[common.Hash]*types.Block), - unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), sealingLogAtDepth), coinbase: config.Etherbase, extra: config.ExtraData, pendingTasks: make(map[common.Hash]*task), txsCh: make(chan core.NewTxsEvent, txChanSize), chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize), - chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize), newWorkCh: make(chan *newWorkReq), getWorkCh: make(chan *getWorkReq), taskCh: make(chan *task), @@ -301,7 +257,6 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh) // Subscribe events for blockchain worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh) - worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh) // Sanitize recommit interval if the user-specified one is too short. recommit := worker.config.Recommit @@ -370,19 +325,9 @@ func (w *worker) setRecommitInterval(interval time.Duration) { } } -// disablePreseal disables pre-sealing feature -func (w *worker) disablePreseal() { - w.noempty.Store(true) -} - -// enablePreseal enables pre-sealing feature -func (w *worker) enablePreseal() { - w.noempty.Store(false) -} - -// pending returns the pending state and corresponding block. +// pending returns the pending state and corresponding block. The returned +// values can be nil in case the pending block is not initialized. func (w *worker) pending() (*types.Block, *state.StateDB) { - // return a snapshot to avoid contention on currentMu mutex w.snapshotMu.RLock() defer w.snapshotMu.RUnlock() if w.snapshotState == nil { @@ -391,17 +336,17 @@ func (w *worker) pending() (*types.Block, *state.StateDB) { return w.snapshotBlock, w.snapshotState.Copy() } -// pendingBlock returns pending block. +// pendingBlock returns pending block. The returned block can be nil in case the +// pending block is not initialized. func (w *worker) pendingBlock() *types.Block { - // return a snapshot to avoid contention on currentMu mutex w.snapshotMu.RLock() defer w.snapshotMu.RUnlock() return w.snapshotBlock } // pendingBlockAndReceipts returns pending block and corresponding receipts. +// The returned values can be nil in case the pending block is not initialized. func (w *worker) pendingBlockAndReceipts() (*types.Block, types.Receipts) { - // return a snapshot to avoid contention on currentMu mutex w.snapshotMu.RLock() defer w.snapshotMu.RUnlock() return w.snapshotBlock, w.snapshotReceipts @@ -467,13 +412,13 @@ func (w *worker) newWorkLoop(recommit time.Duration) { <-timer.C // discard the initial tick // commit aborts in-flight transaction execution with given signal and resubmits a new one. - commit := func(noempty bool, s int32) { + commit := func(s int32) { if interrupt != nil { interrupt.Store(s) } interrupt = new(atomic.Int32) select { - case w.newWorkCh <- &newWorkReq{interrupt: interrupt, noempty: noempty, timestamp: timestamp}: + case w.newWorkCh <- &newWorkReq{interrupt: interrupt, timestamp: timestamp}: case <-w.exitCh: return } @@ -496,12 +441,12 @@ func (w *worker) newWorkLoop(recommit time.Duration) { case <-w.startCh: clearPending(w.chain.CurrentBlock().Number.Uint64()) timestamp = time.Now().Unix() - commit(false, commitInterruptNewHead) + commit(commitInterruptNewHead) case head := <-w.chainHeadCh: clearPending(head.Block.NumberU64()) timestamp = time.Now().Unix() - commit(false, commitInterruptNewHead) + commit(commitInterruptNewHead) case <-timer.C: // If sealing is running resubmit a new work cycle periodically to pull in @@ -512,7 +457,7 @@ func (w *worker) newWorkLoop(recommit time.Duration) { timer.Reset(recommit) continue } - commit(true, commitInterruptResubmit) + commit(commitInterruptResubmit) } case interval := <-w.resubmitIntervalCh: @@ -558,20 +503,16 @@ func (w *worker) mainLoop() { defer w.wg.Done() defer w.txsSub.Unsubscribe() defer w.chainHeadSub.Unsubscribe() - defer w.chainSideSub.Unsubscribe() defer func() { if w.current != nil { w.current.discard() } }() - cleanTicker := time.NewTicker(time.Second * 10) - defer cleanTicker.Stop() - for { select { case req := <-w.newWorkCh: - w.commitWork(req.interrupt, req.noempty, req.timestamp) + w.commitWork(req.interrupt, req.timestamp) case req := <-w.getWorkCh: block, fees, err := w.generateWork(req.params) @@ -580,42 +521,6 @@ func (w *worker) mainLoop() { block: block, fees: fees, } - case ev := <-w.chainSideCh: - // Short circuit for duplicate side blocks - if _, exist := w.localUncles[ev.Block.Hash()]; exist { - continue - } - if _, exist := w.remoteUncles[ev.Block.Hash()]; exist { - continue - } - // Add side block to possible uncle block set depending on the author. - if w.isLocalBlock != nil && w.isLocalBlock(ev.Block.Header()) { - w.localUncles[ev.Block.Hash()] = ev.Block - } else { - w.remoteUncles[ev.Block.Hash()] = ev.Block - } - // 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 && 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) - } - } - - case <-cleanTicker.C: - chainHead := w.chain.CurrentBlock() - for hash, uncle := range w.localUncles { - if uncle.NumberU64()+staleThreshold <= chainHead.Number.Uint64() { - delete(w.localUncles, hash) - } - } - for hash, uncle := range w.remoteUncles { - if uncle.NumberU64()+staleThreshold <= chainHead.Number.Uint64() { - delete(w.remoteUncles, hash) - } - } case ev := <-w.txsCh: // Apply transactions to the pending state if we're not sealing @@ -647,7 +552,7 @@ func (w *worker) mainLoop() { // 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()) + w.commitWork(nil, time.Now().Unix()) } } w.newTxs.Add(int32(len(ev.Txs))) @@ -659,8 +564,6 @@ func (w *worker) mainLoop() { return case <-w.chainHeadSub.Err(): return - case <-w.chainSideSub.Err(): - return } } } @@ -780,9 +683,6 @@ func (w *worker) resultLoop() { // Broadcast the block and announce chain insertion event w.mux.Post(core.NewMinedBlockEvent{Block: block}) - // Insert the block into the set of pending ones to resultLoop for confirmations - w.unconfirmed.Insert(block.NumberU64(), block.Hash()) - case <-w.exitCh: return } @@ -801,49 +701,16 @@ func (w *worker) makeEnv(parent *types.Header, header *types.Header, coinbase co // Note the passed coinbase may be different with header.Coinbase. env := &environment{ - signer: types.MakeSigner(w.chainConfig, header.Number, header.Time), - state: state, - coinbase: coinbase, - ancestors: mapset.NewSet[common.Hash](), - family: mapset.NewSet[common.Hash](), - 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) { - for _, uncle := range ancestor.Uncles() { - env.family.Add(uncle.Hash()) - } - env.family.Add(ancestor.Hash()) - env.ancestors.Add(ancestor.Hash()) + signer: types.MakeSigner(w.chainConfig, header.Number, header.Time), + state: state, + coinbase: coinbase, + header: header, } // Keep track of transactions which return errors so they can be removed env.tcount = 0 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 { - if w.isTTDReached(env.header) { - return errors.New("ignore uncle for beacon block") - } - hash := uncle.Hash() - if _, exist := env.uncles[hash]; exist { - return errors.New("uncle not unique") - } - if env.header.ParentHash == uncle.ParentHash { - return errors.New("uncle is sibling") - } - if !env.ancestors.Contains(uncle.ParentHash) { - return errors.New("uncle's parent unknown") - } - if env.family.Contains(hash) { - return errors.New("uncle already included") - } - env.uncles[hash] = uncle - return nil -} - // updateSnapshot updates pending snapshot block, receipts and state. func (w *worker) updateSnapshot(env *environment) { w.snapshotMu.Lock() @@ -852,7 +719,7 @@ func (w *worker) updateSnapshot(env *environment) { w.snapshotBlock = types.NewBlock( env.header, env.txs, - env.unclelist(), + nil, env.receipts, trie.NewStackTrie(nil), ) @@ -962,7 +829,6 @@ type generateParams struct { coinbase common.Address // The fee recipient address for including transaction random common.Hash // The randomness generated by beacon chain, empty before the merge withdrawals types.Withdrawals // List of withdrawals to include in block. - noUncle bool // Flag whether the uncle block inclusion is allowed noTxs bool // Flag whether an empty block without any transaction is expected } @@ -1028,24 +894,6 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { log.Error("Failed to create sealing context", "err", err) return nil, err } - // 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 len(env.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) - } - } - } - // Prefer to locally generated uncle - commitUncles(w.localUncles) - commitUncles(w.remoteUncles) - } return env, nil } @@ -1098,7 +946,7 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, *big.Int, e log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(w.newpayloadTimeout)) } } - block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts, params.withdrawals) + block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, nil, work.receipts, params.withdrawals) if err != nil { return nil, nil, err } @@ -1107,7 +955,11 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, *big.Int, e // commitWork generates several new sealing tasks based on the parent block // and submit them to the sealer. -func (w *worker) commitWork(interrupt *atomic.Int32, noempty bool, timestamp int64) { +func (w *worker) commitWork(interrupt *atomic.Int32, timestamp int64) { + // Abort committing if node is still syncing + if w.syncing.Load() { + return + } start := time.Now() // Set the coinbase if the worker is running or it's required @@ -1126,11 +978,6 @@ func (w *worker) commitWork(interrupt *atomic.Int32, noempty bool, timestamp int if err != nil { return } - // Create an empty block based on temporary copied state for - // sealing in advance without waiting block execution finished. - if !noempty && !w.noempty.Load() { - w.commit(work.copy(), nil, false, start) - } // Fill pending transactions from the txpool into the block. err = w.fillTransactions(interrupt, work) switch { @@ -1184,7 +1031,7 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti // https://github.com/ethereum/go-ethereum/issues/24299 env := env.copy() // Withdrawals are set to nil here, because this is only called in PoW. - block, err := w.engine.FinalizeAndAssemble(w.chain, env.header, env.state, env.txs, env.unclelist(), env.receipts, nil) + block, err := w.engine.FinalizeAndAssemble(w.chain, env.header, env.state, env.txs, nil, env.receipts, nil) if err != nil { return err } @@ -1192,13 +1039,10 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti if !w.isTTDReached(block.Header()) { select { case w.taskCh <- &task{receipts: env.receipts, state: env.state, block: block, createdAt: time.Now()}: - w.unconfirmed.Shift(block.NumberU64() - 1) - fees := totalFees(block, env.receipts) feesInEther := new(big.Float).Quo(new(big.Float).SetInt(fees), big.NewFloat(params.Ether)) 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", feesInEther, + "txs", env.tcount, "gas", block.GasUsed(), "fees", feesInEther, "elapsed", common.PrettyDuration(time.Since(start))) case <-w.exitCh: @@ -1224,7 +1068,6 @@ func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64, coinbase coinbase: coinbase, random: random, withdrawals: withdrawals, - noUncle: true, noTxs: noTxs, }, result: make(chan *newPayloadResult, 1), @@ -1258,14 +1101,6 @@ func copyReceipts(receipts []*types.Receipt) []*types.Receipt { return result } -// postSideBlock fires a side chain event, only use it for testing. -func (w *worker) postSideBlock(event core.ChainSideEvent) { - select { - case w.chainSideCh <- event: - case <-w.exitCh: - } -} - // totalFees computes total consumed miner fees in Wei. Block transactions and receipts have to have the same order. func totalFees(block *types.Block, receipts []*types.Receipt) *big.Int { feesWei := new(big.Int) diff --git a/miner/worker_test.go b/miner/worker_test.go index 683d019d2d55..fb15d365a76e 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -17,8 +17,6 @@ package miner import ( - "crypto/rand" - "errors" "math/big" "sync/atomic" "testing" @@ -31,7 +29,6 @@ import ( "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -109,11 +106,10 @@ func init() { // testWorkerBackend implements worker.Backend interfaces and wraps all information needed during the testing. type testWorkerBackend struct { - db ethdb.Database - txPool *txpool.TxPool - chain *core.BlockChain - genesis *core.Genesis - uncleBlock *types.Block + db ethdb.Database + txPool *txpool.TxPool + chain *core.BlockChain + genesis *core.Genesis } func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, n int) *testWorkerBackend { @@ -136,58 +132,16 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine if err != nil { t.Fatalf("core.NewBlockChain failed: %v", err) } - txpool := txpool.NewTxPool(testTxPoolConfig, chainConfig, chain) - - // Generate a small n-block chain and an uncle block for it - var uncle *types.Block - if n > 0 { - genDb, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, n, func(i int, gen *core.BlockGen) { - gen.SetCoinbase(testBankAddress) - }) - if _, err := chain.InsertChain(blocks); err != nil { - t.Fatalf("failed to insert origin chain: %v", err) - } - parent := chain.GetBlockByHash(chain.CurrentBlock().ParentHash) - blocks, _ = core.GenerateChain(chainConfig, parent, engine, genDb, 1, func(i int, gen *core.BlockGen) { - gen.SetCoinbase(testUserAddress) - }) - uncle = blocks[0] - } else { - _, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, 1, func(i int, gen *core.BlockGen) { - gen.SetCoinbase(testUserAddress) - }) - uncle = blocks[0] - } return &testWorkerBackend{ - db: db, - chain: chain, - txPool: txpool, - genesis: gspec, - uncleBlock: uncle, + db: db, + chain: chain, + txPool: txpool.NewTxPool(testTxPoolConfig, chainConfig, chain), + genesis: gspec, } } func (b *testWorkerBackend) BlockChain() *core.BlockChain { return b.chain } func (b *testWorkerBackend) TxPool() *txpool.TxPool { return b.txPool } -func (b *testWorkerBackend) StateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (statedb *state.StateDB, err error) { - return nil, errors.New("not supported") -} - -func (b *testWorkerBackend) newRandomUncle() *types.Block { - var parent *types.Block - cur := b.chain.CurrentBlock() - if cur.Number.Uint64() == 0 { - parent = b.chain.Genesis() - } else { - parent = b.chain.GetBlockByHash(b.chain.CurrentBlock().ParentHash) - } - blocks, _ := core.GenerateChain(b.chain.Config(), parent, b.chain.Engine(), b.db, 1, func(i int, gen *core.BlockGen) { - var addr = make([]byte, common.AddressLength) - rand.Read(addr) - gen.SetCoinbase(common.BytesToAddress(addr)) - }) - return blocks[0] -} func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction { var tx *types.Transaction @@ -208,25 +162,15 @@ func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consens return w, backend } -func TestGenerateBlockAndImportClique(t *testing.T) { - testGenerateBlockAndImport(t, true) -} - -func testGenerateBlockAndImport(t *testing.T, isClique bool) { +func TestGenerateAndImportBlock(t *testing.T) { var ( - engine consensus.Engine - chainConfig params.ChainConfig - db = rawdb.NewMemoryDatabase() + db = rawdb.NewMemoryDatabase() + config = *params.AllCliqueProtocolChanges ) - if isClique { - chainConfig = *params.AllCliqueProtocolChanges - chainConfig.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} - engine = clique.New(chainConfig.Clique, db) - } else { - chainConfig = *params.AllEthashProtocolChanges - engine = ethash.NewFaker() - } - w, b := newTestWorker(t, &chainConfig, engine, db, 0) + config.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} + engine := clique.New(config.Clique, db) + + w, b := newTestWorker(t, &config, engine, db, 0) defer w.close() // This test chain imports the mined blocks. @@ -248,8 +192,6 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool) { for i := 0; i < 5; i++ { b.txPool.AddLocal(b.newRandomTx(true)) b.txPool.AddLocal(b.newRandomTx(false)) - w.postSideBlock(core.ChainSideEvent{Block: b.newRandomUncle()}) - w.postSideBlock(core.ChainSideEvent{Block: b.newRandomUncle()}) select { case ev := <-sub.Chan(): @@ -276,17 +218,10 @@ func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consens w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0) defer w.close() - var ( - taskIndex int - taskCh = make(chan struct{}, 2) - ) - checkEqual := func(t *testing.T, task *task, index int) { - // The first empty work without any txs included - receiptLen, balance := 0, big.NewInt(0) - if index == 1 { - // The second full work with 1 tx included - receiptLen, balance = 1, big.NewInt(1000) - } + taskCh := make(chan struct{}, 2) + checkEqual := func(t *testing.T, task *task) { + // The work should contain 1 tx + receiptLen, balance := 1, big.NewInt(1000) if len(task.receipts) != receiptLen { t.Fatalf("receipt number mismatch: have %d, want %d", len(task.receipts), receiptLen) } @@ -296,8 +231,7 @@ func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consens } w.newTaskHook = func(task *task) { if task.block.NumberU64() == 1 { - checkEqual(t, task, taskIndex) - taskIndex += 1 + checkEqual(t, task) taskCh <- struct{}{} } } @@ -306,122 +240,9 @@ func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consens time.Sleep(100 * time.Millisecond) } w.start() // Start mining! - for i := 0; i < 2; i += 1 { - select { - case <-taskCh: - case <-time.NewTimer(3 * time.Second).C: - t.Error("new task timeout") - } - } -} - -func TestStreamUncleBlock(t *testing.T) { - ethash := ethash.NewFaker() - defer ethash.Close() - - w, b := newTestWorker(t, ethashChainConfig, ethash, rawdb.NewMemoryDatabase(), 1) - defer w.close() - - var taskCh = make(chan struct{}, 3) - - taskIndex := 0 - w.newTaskHook = func(task *task) { - if task.block.NumberU64() == 2 { - // The first task is an empty task, the second - // one has 1 pending tx, the third one has 1 tx - // and 1 uncle. - if taskIndex == 2 { - have := task.block.Header().UncleHash - want := types.CalcUncleHash([]*types.Header{b.uncleBlock.Header()}) - if have != want { - t.Errorf("uncle hash mismatch: have %s, want %s", have.Hex(), want.Hex()) - } - } - taskCh <- struct{}{} - taskIndex += 1 - } - } - w.skipSealHook = func(task *task) bool { - return true - } - w.fullTaskHook = func() { - time.Sleep(100 * time.Millisecond) - } - w.start() - - for i := 0; i < 2; i += 1 { - select { - case <-taskCh: - case <-time.NewTimer(time.Second).C: - t.Error("new task timeout") - } - } - - w.postSideBlock(core.ChainSideEvent{Block: b.uncleBlock}) - - select { - case <-taskCh: - case <-time.NewTimer(time.Second).C: - t.Error("new task timeout") - } -} - -func TestRegenerateMiningBlockEthash(t *testing.T) { - testRegenerateMiningBlock(t, ethashChainConfig, ethash.NewFaker()) -} - -func TestRegenerateMiningBlockClique(t *testing.T) { - testRegenerateMiningBlock(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase())) -} - -func testRegenerateMiningBlock(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) { - defer engine.Close() - - w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0) - defer w.close() - - var taskCh = make(chan struct{}, 3) - - taskIndex := 0 - w.newTaskHook = func(task *task) { - if task.block.NumberU64() == 1 { - // The first task is an empty task, the second - // one has 1 pending tx, the third one has 2 txs - if taskIndex == 2 { - receiptLen, balance := 2, big.NewInt(2000) - if len(task.receipts) != receiptLen { - t.Errorf("receipt number mismatch: have %d, want %d", len(task.receipts), receiptLen) - } - if task.state.GetBalance(testUserAddress).Cmp(balance) != 0 { - t.Errorf("account balance mismatch: have %d, want %d", task.state.GetBalance(testUserAddress), balance) - } - } - taskCh <- struct{}{} - taskIndex += 1 - } - } - w.skipSealHook = func(task *task) bool { - return true - } - w.fullTaskHook = func() { - time.Sleep(100 * time.Millisecond) - } - - w.start() - // Ignore the first two works - for i := 0; i < 2; i += 1 { - select { - case <-taskCh: - case <-time.NewTimer(time.Second).C: - t.Error("new task timeout") - } - } - b.txPool.AddLocals(newTxs) - time.Sleep(time.Second) - select { case <-taskCh: - case <-time.NewTimer(time.Second).C: + case <-time.NewTimer(3 * time.Second).C: t.Error("new task timeout") } } @@ -542,7 +363,6 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co defer w.close() w.setExtra([]byte{0x01, 0x02}) - w.postSideBlock(core.ChainSideEvent{Block: b.uncleBlock}) w.skipSealHook = func(task *task) bool { return true @@ -557,9 +377,6 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co // is even smaller than parent block's. It's OK. t.Logf("Invalid timestamp, want %d, get %d", timestamp, block.Time()) } - if len(block.Uncles()) != 0 { - t.Error("Unexpected uncle block") - } _, isClique := engine.(*clique.Clique) if !isClique { if len(block.Extra()) != 2 {