Skip to content

Commit

Permalink
Revert "miner: suspend miner if node is syncing (ethereum#27218)"
Browse files Browse the repository at this point in the history
This reverts commit f768ce5.
  • Loading branch information
devopsbo3 committed Nov 10, 2023
1 parent b679e3b commit 185aa5e
Show file tree
Hide file tree
Showing 8 changed files with 650 additions and 86 deletions.
9 changes: 0 additions & 9 deletions eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,6 @@ 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
Expand Down Expand Up @@ -125,9 +122,6 @@ 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
Expand Down Expand Up @@ -202,9 +196,6 @@ 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
Expand Down
6 changes: 0 additions & 6 deletions eth/api_debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,6 @@ 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
Expand Down Expand Up @@ -144,9 +141,6 @@ 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 {
Expand Down
2 changes: 1 addition & 1 deletion eth/filters/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 || receipts == nil {
if block == nil {
return nil
}
if bloomFilter(block.Bloom(), f.addresses, f.topics) {
Expand Down
30 changes: 19 additions & 11 deletions miner/miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,22 +131,16 @@ 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()
}
Expand Down Expand Up @@ -202,14 +196,12 @@ func (miner *Miner) SetRecommitInterval(interval time.Duration) {
miner.worker.setRecommitInterval(interval)
}

// Pending returns the currently pending block and associated state. The returned
// values can be nil in case the pending block is not initialized
// Pending returns the currently pending block and associated state.
func (miner *Miner) Pending() (*types.Block, *state.StateDB) {
return miner.worker.pending()
}

// PendingBlock returns the currently pending block. The returned block can be
// nil in case the pending block is not initialized.
// PendingBlock returns the currently pending block.
//
// Note, to access both the pending block and the pending state
// simultaneously, please use Pending(), as the pending state can
Expand All @@ -219,7 +211,6 @@ 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()
}
Expand All @@ -234,6 +225,23 @@ 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 {
Expand Down
136 changes: 136 additions & 0 deletions miner/unconfirmed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// 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)
}
}
}
87 changes: 87 additions & 0 deletions miner/unconfirmed_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// 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)
}
}

0 comments on commit 185aa5e

Please sign in to comment.