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)
This commit is contained in:
rjl493456442 2023-05-31 15:09:49 +08:00 committed by GitHub
parent 61dcf76230
commit d4961881d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 86 additions and 650 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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) {

View File

@ -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 {

View File

@ -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 <http://www.gnu.org/licenses/>.
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)
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
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)
}
}

View File

@ -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)

View File

@ -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 = &params.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 = &params.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 {