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 // Pending block is only known by the miner
if number == rpc.PendingBlockNumber { if number == rpc.PendingBlockNumber {
block := b.eth.miner.PendingBlock() block := b.eth.miner.PendingBlock()
if block == nil {
return nil, errors.New("pending block is not available")
}
return block.Header(), nil return block.Header(), nil
} }
// Otherwise resolve and return the block // 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 // Pending block is only known by the miner
if number == rpc.PendingBlockNumber { if number == rpc.PendingBlockNumber {
block := b.eth.miner.PendingBlock() block := b.eth.miner.PendingBlock()
if block == nil {
return nil, errors.New("pending block is not available")
}
return block, nil return block, nil
} }
// Otherwise resolve and return the block // 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 // Pending state is only known by the miner
if number == rpc.PendingBlockNumber { if number == rpc.PendingBlockNumber {
block, state := b.eth.miner.Pending() 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 return state, block.Header(), nil
} }
// Otherwise resolve the block number and return its state // 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 // both the pending block as well as the pending state from
// the miner and operate on those // the miner and operate on those
_, stateDb := api.eth.miner.Pending() _, stateDb := api.eth.miner.Pending()
if stateDb == nil {
return state.Dump{}, errors.New("pending state is not available")
}
return stateDb.RawDump(opts), nil return stateDb.RawDump(opts), nil
} }
var header *types.Header 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 // both the pending block as well as the pending state from
// the miner and operate on those // the miner and operate on those
_, stateDb = api.eth.miner.Pending() _, stateDb = api.eth.miner.Pending()
if stateDb == nil {
return state.IteratorDump{}, errors.New("pending state is not available")
}
} else { } else {
var header *types.Header var header *types.Header
if number == rpc.LatestBlockNumber { 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. // pendingLogs returns the logs matching the filter criteria within the pending block.
func (f *Filter) pendingLogs() []*types.Log { func (f *Filter) pendingLogs() []*types.Log {
block, receipts := f.sys.backend.PendingBlockAndReceipts() block, receipts := f.sys.backend.PendingBlockAndReceipts()
if block == nil { if block == nil || receipts == nil {
return nil return nil
} }
if bloomFilter(block.Bloom(), f.addresses, f.topics) { if bloomFilter(block.Bloom(), f.addresses, f.topics) {

View File

@ -131,16 +131,22 @@ func (miner *Miner) update() {
shouldStart = true shouldStart = true
log.Info("Mining aborted due to sync") log.Info("Mining aborted due to sync")
} }
miner.worker.syncing.Store(true)
case downloader.FailedEvent: case downloader.FailedEvent:
canStart = true canStart = true
if shouldStart { if shouldStart {
miner.worker.start() miner.worker.start()
} }
miner.worker.syncing.Store(false)
case downloader.DoneEvent: case downloader.DoneEvent:
canStart = true canStart = true
if shouldStart { if shouldStart {
miner.worker.start() miner.worker.start()
} }
miner.worker.syncing.Store(false)
// Stop reacting to downloader events // Stop reacting to downloader events
events.Unsubscribe() events.Unsubscribe()
} }
@ -196,12 +202,14 @@ func (miner *Miner) SetRecommitInterval(interval time.Duration) {
miner.worker.setRecommitInterval(interval) 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) { func (miner *Miner) Pending() (*types.Block, *state.StateDB) {
return miner.worker.pending() 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 // Note, to access both the pending block and the pending state
// simultaneously, please use Pending(), as the pending state can // 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. // 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) { func (miner *Miner) PendingBlockAndReceipts() (*types.Block, types.Receipts) {
return miner.worker.pendingBlockAndReceipts() return miner.worker.pendingBlockAndReceipts()
} }
@ -225,23 +234,6 @@ func (miner *Miner) SetGasCeil(ceil uint64) {
miner.worker.setGasCeil(ceil) 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 // SubscribePendingLogs starts delivering logs from pending transactions
// to the given channel. // to the given channel.
func (miner *Miner) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscription { 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" "sync/atomic"
"time" "time"
mapset "github.com/deckarep/golang-set/v2"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/consensus/misc"
@ -48,15 +47,9 @@ const (
// chainHeadChanSize is the size of channel listening to ChainHeadEvent. // chainHeadChanSize is the size of channel listening to ChainHeadEvent.
chainHeadChanSize = 10 chainHeadChanSize = 10
// chainSideChanSize is the size of channel listening to ChainSideEvent.
chainSideChanSize = 10
// resubmitAdjustChanSize is the size of resubmitting interval adjustment channel. // resubmitAdjustChanSize is the size of resubmitting interval adjustment channel.
resubmitAdjustChanSize = 10 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 // minRecommitInterval is the minimal time interval to recreate the sealing block with
// any newly arrived transactions. // any newly arrived transactions.
minRecommitInterval = 1 * time.Second minRecommitInterval = 1 * time.Second
@ -86,57 +79,36 @@ var (
// environment is the worker's current environment and holds all // environment is the worker's current environment and holds all
// information of the sealing block generation. // information of the sealing block generation.
type environment struct { type environment struct {
signer types.Signer signer types.Signer
state *state.StateDB // apply state changes here
state *state.StateDB // apply state changes here tcount int // tx count in cycle
ancestors mapset.Set[common.Hash] // ancestor set (used for checking uncle parent validity) gasPool *core.GasPool // available gas used to pack transactions
family mapset.Set[common.Hash] // family set (used for checking uncle invalidity) coinbase common.Address
tcount int // tx count in cycle
gasPool *core.GasPool // available gas used to pack transactions
coinbase common.Address
header *types.Header header *types.Header
txs []*types.Transaction txs []*types.Transaction
receipts []*types.Receipt receipts []*types.Receipt
uncles map[common.Hash]*types.Header
} }
// copy creates a deep copy of environment. // copy creates a deep copy of environment.
func (env *environment) copy() *environment { func (env *environment) copy() *environment {
cpy := &environment{ cpy := &environment{
signer: env.signer, signer: env.signer,
state: env.state.Copy(), state: env.state.Copy(),
ancestors: env.ancestors.Clone(), tcount: env.tcount,
family: env.family.Clone(), coinbase: env.coinbase,
tcount: env.tcount, header: types.CopyHeader(env.header),
coinbase: env.coinbase, receipts: copyReceipts(env.receipts),
header: types.CopyHeader(env.header),
receipts: copyReceipts(env.receipts),
} }
if env.gasPool != nil { if env.gasPool != nil {
gasPool := *env.gasPool gasPool := *env.gasPool
cpy.gasPool = &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)) cpy.txs = make([]*types.Transaction, len(env.txs))
copy(cpy.txs, 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 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 // discard terminates the background prefetcher go-routine. It should
// always be called for all created environment instances otherwise // always be called for all created environment instances otherwise
// the go-routine leak can happen. // the go-routine leak can happen.
@ -165,7 +137,6 @@ const (
// newWorkReq represents a request for new sealing work submitting with relative interrupt notifier. // newWorkReq represents a request for new sealing work submitting with relative interrupt notifier.
type newWorkReq struct { type newWorkReq struct {
interrupt *atomic.Int32 interrupt *atomic.Int32
noempty bool
timestamp int64 timestamp int64
} }
@ -206,8 +177,6 @@ type worker struct {
txsSub event.Subscription txsSub event.Subscription
chainHeadCh chan core.ChainHeadEvent chainHeadCh chan core.ChainHeadEvent
chainHeadSub event.Subscription chainHeadSub event.Subscription
chainSideCh chan core.ChainSideEvent
chainSideSub event.Subscription
// Channels // Channels
newWorkCh chan *newWorkReq newWorkCh chan *newWorkReq
@ -221,10 +190,7 @@ type worker struct {
wg sync.WaitGroup wg sync.WaitGroup
current *environment // An environment for current running cycle. 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.
mu sync.RWMutex // The lock used to protect the coinbase and extra fields mu sync.RWMutex // The lock used to protect the coinbase and extra fields
coinbase common.Address coinbase common.Address
@ -241,13 +207,7 @@ type worker struct {
// atomic status counters // atomic status counters
running atomic.Bool // The indicator whether the consensus engine is running or not. 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. newTxs atomic.Int32 // New arrival transaction count since last sealing work submitting.
syncing atomic.Bool // The indicator whether the node is still syncing.
// 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
// newpayloadTimeout is the maximum timeout allowance for creating payload. // newpayloadTimeout is the maximum timeout allowance for creating payload.
// The default value is 2 seconds but node operator can set it to arbitrary // 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(), chain: eth.BlockChain(),
mux: mux, mux: mux,
isLocalBlock: isLocalBlock, isLocalBlock: isLocalBlock,
localUncles: make(map[common.Hash]*types.Block),
remoteUncles: make(map[common.Hash]*types.Block),
unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), sealingLogAtDepth),
coinbase: config.Etherbase, coinbase: config.Etherbase,
extra: config.ExtraData, extra: config.ExtraData,
pendingTasks: make(map[common.Hash]*task), pendingTasks: make(map[common.Hash]*task),
txsCh: make(chan core.NewTxsEvent, txChanSize), txsCh: make(chan core.NewTxsEvent, txChanSize),
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize), chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize),
newWorkCh: make(chan *newWorkReq), newWorkCh: make(chan *newWorkReq),
getWorkCh: make(chan *getWorkReq), getWorkCh: make(chan *getWorkReq),
taskCh: make(chan *task), taskCh: make(chan *task),
@ -301,7 +257,6 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh) worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh)
// Subscribe events for blockchain // Subscribe events for blockchain
worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh) 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. // Sanitize recommit interval if the user-specified one is too short.
recommit := worker.config.Recommit recommit := worker.config.Recommit
@ -370,19 +325,9 @@ func (w *worker) setRecommitInterval(interval time.Duration) {
} }
} }
// disablePreseal disables pre-sealing feature // pending returns the pending state and corresponding block. The returned
func (w *worker) disablePreseal() { // values can be nil in case the pending block is not initialized.
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.
func (w *worker) pending() (*types.Block, *state.StateDB) { func (w *worker) pending() (*types.Block, *state.StateDB) {
// return a snapshot to avoid contention on currentMu mutex
w.snapshotMu.RLock() w.snapshotMu.RLock()
defer w.snapshotMu.RUnlock() defer w.snapshotMu.RUnlock()
if w.snapshotState == nil { if w.snapshotState == nil {
@ -391,17 +336,17 @@ func (w *worker) pending() (*types.Block, *state.StateDB) {
return w.snapshotBlock, w.snapshotState.Copy() 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 { func (w *worker) pendingBlock() *types.Block {
// return a snapshot to avoid contention on currentMu mutex
w.snapshotMu.RLock() w.snapshotMu.RLock()
defer w.snapshotMu.RUnlock() defer w.snapshotMu.RUnlock()
return w.snapshotBlock return w.snapshotBlock
} }
// pendingBlockAndReceipts returns pending block and corresponding receipts. // 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) { func (w *worker) pendingBlockAndReceipts() (*types.Block, types.Receipts) {
// return a snapshot to avoid contention on currentMu mutex
w.snapshotMu.RLock() w.snapshotMu.RLock()
defer w.snapshotMu.RUnlock() defer w.snapshotMu.RUnlock()
return w.snapshotBlock, w.snapshotReceipts return w.snapshotBlock, w.snapshotReceipts
@ -467,13 +412,13 @@ func (w *worker) newWorkLoop(recommit time.Duration) {
<-timer.C // discard the initial tick <-timer.C // discard the initial tick
// commit aborts in-flight transaction execution with given signal and resubmits a new one. // 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 { if interrupt != nil {
interrupt.Store(s) interrupt.Store(s)
} }
interrupt = new(atomic.Int32) interrupt = new(atomic.Int32)
select { select {
case w.newWorkCh <- &newWorkReq{interrupt: interrupt, noempty: noempty, timestamp: timestamp}: case w.newWorkCh <- &newWorkReq{interrupt: interrupt, timestamp: timestamp}:
case <-w.exitCh: case <-w.exitCh:
return return
} }
@ -496,12 +441,12 @@ func (w *worker) newWorkLoop(recommit time.Duration) {
case <-w.startCh: case <-w.startCh:
clearPending(w.chain.CurrentBlock().Number.Uint64()) clearPending(w.chain.CurrentBlock().Number.Uint64())
timestamp = time.Now().Unix() timestamp = time.Now().Unix()
commit(false, commitInterruptNewHead) commit(commitInterruptNewHead)
case head := <-w.chainHeadCh: case head := <-w.chainHeadCh:
clearPending(head.Block.NumberU64()) clearPending(head.Block.NumberU64())
timestamp = time.Now().Unix() timestamp = time.Now().Unix()
commit(false, commitInterruptNewHead) commit(commitInterruptNewHead)
case <-timer.C: case <-timer.C:
// If sealing is running resubmit a new work cycle periodically to pull in // 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) timer.Reset(recommit)
continue continue
} }
commit(true, commitInterruptResubmit) commit(commitInterruptResubmit)
} }
case interval := <-w.resubmitIntervalCh: case interval := <-w.resubmitIntervalCh:
@ -558,20 +503,16 @@ func (w *worker) mainLoop() {
defer w.wg.Done() defer w.wg.Done()
defer w.txsSub.Unsubscribe() defer w.txsSub.Unsubscribe()
defer w.chainHeadSub.Unsubscribe() defer w.chainHeadSub.Unsubscribe()
defer w.chainSideSub.Unsubscribe()
defer func() { defer func() {
if w.current != nil { if w.current != nil {
w.current.discard() w.current.discard()
} }
}() }()
cleanTicker := time.NewTicker(time.Second * 10)
defer cleanTicker.Stop()
for { for {
select { select {
case req := <-w.newWorkCh: case req := <-w.newWorkCh:
w.commitWork(req.interrupt, req.noempty, req.timestamp) w.commitWork(req.interrupt, req.timestamp)
case req := <-w.getWorkCh: case req := <-w.getWorkCh:
block, fees, err := w.generateWork(req.params) block, fees, err := w.generateWork(req.params)
@ -580,42 +521,6 @@ func (w *worker) mainLoop() {
block: block, block: block,
fees: fees, 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: case ev := <-w.txsCh:
// Apply transactions to the pending state if we're not sealing // 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 // submit sealing work here since all empty submission will be rejected
// by clique. Of course the advance sealing(empty submission) is disabled. // by clique. Of course the advance sealing(empty submission) is disabled.
if w.chainConfig.Clique != nil && w.chainConfig.Clique.Period == 0 { 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))) w.newTxs.Add(int32(len(ev.Txs)))
@ -659,8 +564,6 @@ func (w *worker) mainLoop() {
return return
case <-w.chainHeadSub.Err(): case <-w.chainHeadSub.Err():
return return
case <-w.chainSideSub.Err():
return
} }
} }
} }
@ -780,9 +683,6 @@ func (w *worker) resultLoop() {
// Broadcast the block and announce chain insertion event // Broadcast the block and announce chain insertion event
w.mux.Post(core.NewMinedBlockEvent{Block: block}) 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: case <-w.exitCh:
return 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. // Note the passed coinbase may be different with header.Coinbase.
env := &environment{ env := &environment{
signer: types.MakeSigner(w.chainConfig, header.Number, header.Time), signer: types.MakeSigner(w.chainConfig, header.Number, header.Time),
state: state, state: state,
coinbase: coinbase, coinbase: coinbase,
ancestors: mapset.NewSet[common.Hash](), header: header,
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())
} }
// Keep track of transactions which return errors so they can be removed // Keep track of transactions which return errors so they can be removed
env.tcount = 0 env.tcount = 0
return env, 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 {
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. // updateSnapshot updates pending snapshot block, receipts and state.
func (w *worker) updateSnapshot(env *environment) { func (w *worker) updateSnapshot(env *environment) {
w.snapshotMu.Lock() w.snapshotMu.Lock()
@ -852,7 +719,7 @@ func (w *worker) updateSnapshot(env *environment) {
w.snapshotBlock = types.NewBlock( w.snapshotBlock = types.NewBlock(
env.header, env.header,
env.txs, env.txs,
env.unclelist(), nil,
env.receipts, env.receipts,
trie.NewStackTrie(nil), trie.NewStackTrie(nil),
) )
@ -962,7 +829,6 @@ type generateParams struct {
coinbase common.Address // The fee recipient address for including transaction coinbase common.Address // The fee recipient address for including transaction
random common.Hash // The randomness generated by beacon chain, empty before the merge random common.Hash // The randomness generated by beacon chain, empty before the merge
withdrawals types.Withdrawals // List of withdrawals to include in block. 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 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) log.Error("Failed to create sealing context", "err", err)
return nil, 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 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)) 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 { if err != nil {
return nil, nil, err 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 // commitWork generates several new sealing tasks based on the parent block
// and submit them to the sealer. // 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() start := time.Now()
// Set the coinbase if the worker is running or it's required // 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 { if err != nil {
return 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. // Fill pending transactions from the txpool into the block.
err = w.fillTransactions(interrupt, work) err = w.fillTransactions(interrupt, work)
switch { 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 // https://github.com/ethereum/go-ethereum/issues/24299
env := env.copy() env := env.copy()
// Withdrawals are set to nil here, because this is only called in PoW. // 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 { if err != nil {
return err return err
} }
@ -1192,13 +1039,10 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti
if !w.isTTDReached(block.Header()) { if !w.isTTDReached(block.Header()) {
select { select {
case w.taskCh <- &task{receipts: env.receipts, state: env.state, 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)
fees := totalFees(block, env.receipts) fees := totalFees(block, env.receipts)
feesInEther := new(big.Float).Quo(new(big.Float).SetInt(fees), big.NewFloat(params.Ether)) 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()), log.Info("Commit new sealing work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()),
"uncles", len(env.uncles), "txs", env.tcount, "txs", env.tcount, "gas", block.GasUsed(), "fees", feesInEther,
"gas", block.GasUsed(), "fees", feesInEther,
"elapsed", common.PrettyDuration(time.Since(start))) "elapsed", common.PrettyDuration(time.Since(start)))
case <-w.exitCh: case <-w.exitCh:
@ -1224,7 +1068,6 @@ func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64, coinbase
coinbase: coinbase, coinbase: coinbase,
random: random, random: random,
withdrawals: withdrawals, withdrawals: withdrawals,
noUncle: true,
noTxs: noTxs, noTxs: noTxs,
}, },
result: make(chan *newPayloadResult, 1), result: make(chan *newPayloadResult, 1),
@ -1258,14 +1101,6 @@ func copyReceipts(receipts []*types.Receipt) []*types.Receipt {
return result 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. // 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 { func totalFees(block *types.Block, receipts []*types.Receipt) *big.Int {
feesWei := new(big.Int) feesWei := new(big.Int)

View File

@ -17,8 +17,6 @@
package miner package miner
import ( import (
"crypto/rand"
"errors"
"math/big" "math/big"
"sync/atomic" "sync/atomic"
"testing" "testing"
@ -31,7 +29,6 @@ import (
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "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/txpool"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "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. // testWorkerBackend implements worker.Backend interfaces and wraps all information needed during the testing.
type testWorkerBackend struct { type testWorkerBackend struct {
db ethdb.Database db ethdb.Database
txPool *txpool.TxPool txPool *txpool.TxPool
chain *core.BlockChain chain *core.BlockChain
genesis *core.Genesis genesis *core.Genesis
uncleBlock *types.Block
} }
func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, n int) *testWorkerBackend { 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 { if err != nil {
t.Fatalf("core.NewBlockChain failed: %v", err) 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{ return &testWorkerBackend{
db: db, db: db,
chain: chain, chain: chain,
txPool: txpool, txPool: txpool.NewTxPool(testTxPoolConfig, chainConfig, chain),
genesis: gspec, genesis: gspec,
uncleBlock: uncle,
} }
} }
func (b *testWorkerBackend) BlockChain() *core.BlockChain { return b.chain } func (b *testWorkerBackend) BlockChain() *core.BlockChain { return b.chain }
func (b *testWorkerBackend) TxPool() *txpool.TxPool { return b.txPool } 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 { func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction {
var tx *types.Transaction var tx *types.Transaction
@ -208,25 +162,15 @@ func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consens
return w, backend return w, backend
} }
func TestGenerateBlockAndImportClique(t *testing.T) { func TestGenerateAndImportBlock(t *testing.T) {
testGenerateBlockAndImport(t, true)
}
func testGenerateBlockAndImport(t *testing.T, isClique bool) {
var ( var (
engine consensus.Engine db = rawdb.NewMemoryDatabase()
chainConfig params.ChainConfig config = *params.AllCliqueProtocolChanges
db = rawdb.NewMemoryDatabase()
) )
if isClique { config.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000}
chainConfig = *params.AllCliqueProtocolChanges engine := clique.New(config.Clique, db)
chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000}
engine = clique.New(chainConfig.Clique, db) w, b := newTestWorker(t, &config, engine, db, 0)
} else {
chainConfig = *params.AllEthashProtocolChanges
engine = ethash.NewFaker()
}
w, b := newTestWorker(t, &chainConfig, engine, db, 0)
defer w.close() defer w.close()
// This test chain imports the mined blocks. // This test chain imports the mined blocks.
@ -248,8 +192,6 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool) {
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
b.txPool.AddLocal(b.newRandomTx(true)) b.txPool.AddLocal(b.newRandomTx(true))
b.txPool.AddLocal(b.newRandomTx(false)) b.txPool.AddLocal(b.newRandomTx(false))
w.postSideBlock(core.ChainSideEvent{Block: b.newRandomUncle()})
w.postSideBlock(core.ChainSideEvent{Block: b.newRandomUncle()})
select { select {
case ev := <-sub.Chan(): 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) w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0)
defer w.close() defer w.close()
var ( taskCh := make(chan struct{}, 2)
taskIndex int checkEqual := func(t *testing.T, task *task) {
taskCh = make(chan struct{}, 2) // The work should contain 1 tx
) receiptLen, balance := 1, big.NewInt(1000)
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)
}
if len(task.receipts) != receiptLen { if len(task.receipts) != receiptLen {
t.Fatalf("receipt number mismatch: have %d, want %d", 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) { w.newTaskHook = func(task *task) {
if task.block.NumberU64() == 1 { if task.block.NumberU64() == 1 {
checkEqual(t, task, taskIndex) checkEqual(t, task)
taskIndex += 1
taskCh <- struct{}{} taskCh <- struct{}{}
} }
} }
@ -306,122 +240,9 @@ func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consens
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
} }
w.start() // Start mining! 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 { select {
case <-taskCh: case <-taskCh:
case <-time.NewTimer(time.Second).C: case <-time.NewTimer(3 * 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:
t.Error("new task timeout") t.Error("new task timeout")
} }
} }
@ -542,7 +363,6 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co
defer w.close() defer w.close()
w.setExtra([]byte{0x01, 0x02}) w.setExtra([]byte{0x01, 0x02})
w.postSideBlock(core.ChainSideEvent{Block: b.uncleBlock})
w.skipSealHook = func(task *task) bool { w.skipSealHook = func(task *task) bool {
return true 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. // is even smaller than parent block's. It's OK.
t.Logf("Invalid timestamp, want %d, get %d", timestamp, block.Time()) 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) _, isClique := engine.(*clique.Clique)
if !isClique { if !isClique {
if len(block.Extra()) != 2 { if len(block.Extra()) != 2 {