Skip to content

Commit dd7d46f

Browse files
MariusVanDerWijdenjagdeep sidhu
authored andcommitted
core, eth, internal, rpc: implement final block (ethereum#24282)
* eth: core: implement finalized block * eth/catalyst: fix final block * eth/catalyst: update finalized head gauge * internal/jsre/deps: updated web3.js to allow for finalized block * eth/catalyst: make sure only one thread can call fcu * eth/catalyst: nitpicks * eth/catalyst: use plain mutex * eth: nitpicks
1 parent 0935be0 commit dd7d46f

File tree

12 files changed

+96
-15
lines changed

12 files changed

+96
-15
lines changed

core/blockchain.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,10 @@ import (
4747
)
4848

4949
var (
50-
headBlockGauge = metrics.NewRegisteredGauge("chain/head/block", nil)
51-
headHeaderGauge = metrics.NewRegisteredGauge("chain/head/header", nil)
52-
headFastBlockGauge = metrics.NewRegisteredGauge("chain/head/receipt", nil)
50+
headBlockGauge = metrics.NewRegisteredGauge("chain/head/block", nil)
51+
headHeaderGauge = metrics.NewRegisteredGauge("chain/head/header", nil)
52+
headFastBlockGauge = metrics.NewRegisteredGauge("chain/head/receipt", nil)
53+
headFinalizedBlockGauge = metrics.NewRegisteredGauge("chain/head/finalized", nil)
5354

5455
accountReadTimer = metrics.NewRegisteredTimer("chain/account/reads", nil)
5556
accountHashTimer = metrics.NewRegisteredTimer("chain/account/hashes", nil)
@@ -188,8 +189,9 @@ type BlockChain struct {
188189
// Readers don't need to take it, they can just read the database.
189190
chainmu *syncx.ClosableMutex
190191

191-
currentBlock atomic.Value // Current head of the block chain
192-
currentFastBlock atomic.Value // Current head of the fast-sync chain (may be above the block chain!)
192+
currentBlock atomic.Value // Current head of the block chain
193+
currentFastBlock atomic.Value // Current head of the fast-sync chain (may be above the block chain!)
194+
currentFinalizedBlock atomic.Value // Current finalized head
193195

194196
stateCache state.Database // State database to reuse between imports (contains state cache)
195197
bodyCache *lru.Cache // Cache for the most recent block bodies
@@ -265,6 +267,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
265267
var nilBlock *types.Block
266268
bc.currentBlock.Store(nilBlock)
267269
bc.currentFastBlock.Store(nilBlock)
270+
bc.currentFinalizedBlock.Store(nilBlock)
268271

269272
// Initialize the chain with ancient data if it isn't empty.
270273
var txIndexBlock uint64
@@ -461,8 +464,17 @@ func (bc *BlockChain) loadLastState() error {
461464
headFastBlockGauge.Update(int64(block.NumberU64()))
462465
}
463466
}
467+
468+
// Restore the last known finalized block
469+
if head := rawdb.ReadFinalizedBlockHash(bc.db); head != (common.Hash{}) {
470+
if block := bc.GetBlockByHash(head); block != nil {
471+
bc.currentFinalizedBlock.Store(block)
472+
headFinalizedBlockGauge.Update(int64(block.NumberU64()))
473+
}
474+
}
464475
// Issue a status log for the user
465476
currentFastBlock := bc.CurrentFastBlock()
477+
currentFinalizedBlock := bc.CurrentFinalizedBlock()
466478

467479
headerTd := bc.GetTd(currentHeader.Hash(), currentHeader.Number.Uint64())
468480
blockTd := bc.GetTd(currentBlock.Hash(), currentBlock.NumberU64())
@@ -471,6 +483,11 @@ func (bc *BlockChain) loadLastState() error {
471483
log.Info("Loaded most recent local header", "number", currentHeader.Number, "hash", currentHeader.Hash(), "td", headerTd, "age", common.PrettyAge(time.Unix(int64(currentHeader.Time), 0)))
472484
log.Info("Loaded most recent local full block", "number", currentBlock.Number(), "hash", currentBlock.Hash(), "td", blockTd, "age", common.PrettyAge(time.Unix(int64(currentBlock.Time()), 0)))
473485
log.Info("Loaded most recent local fast block", "number", currentFastBlock.Number(), "hash", currentFastBlock.Hash(), "td", fastTd, "age", common.PrettyAge(time.Unix(int64(currentFastBlock.Time()), 0)))
486+
487+
if currentFinalizedBlock != nil {
488+
finalTd := bc.GetTd(currentFinalizedBlock.Hash(), currentFinalizedBlock.NumberU64())
489+
log.Info("Loaded most recent local finalized block", "number", currentFinalizedBlock.Number(), "hash", currentFinalizedBlock.Hash(), "td", finalTd, "age", common.PrettyAge(time.Unix(int64(currentFinalizedBlock.Time()), 0)))
490+
}
474491
if pivot := rawdb.ReadLastPivotNumber(bc.db); pivot != nil {
475492
log.Info("Loaded last fast-sync pivot marker", "number", *pivot)
476493
}
@@ -485,6 +502,13 @@ func (bc *BlockChain) SetHead(head uint64) error {
485502
return err
486503
}
487504

505+
// SetFinalized sets the finalized block.
506+
func (bc *BlockChain) SetFinalized(block *types.Block) {
507+
bc.currentFinalizedBlock.Store(block)
508+
rawdb.WriteFinalizedBlockHash(bc.db, block.Hash())
509+
headFinalizedBlockGauge.Update(int64(block.NumberU64()))
510+
}
511+
488512
// setHeadBeyondRoot rewinds the local chain to a new head with the extra condition
489513
// that the rewind must pass the specified state root. This method is meant to be
490514
// used when rewinding with snapshots enabled to ensure that we go back further than

core/blockchain_reader.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ func (bc *BlockChain) CurrentFastBlock() *types.Block {
4949
return bc.currentFastBlock.Load().(*types.Block)
5050
}
5151

52+
// CurrentFinalizedBlock retrieves the current finalized block of the canonical
53+
// chain. The block is retrieved from the blockchain's internal cache.
54+
func (bc *BlockChain) CurrentFinalizedBlock() *types.Block {
55+
return bc.currentFinalizedBlock.Load().(*types.Block)
56+
}
57+
5258
// HasHeader checks if a block header is present in the database or not, caching
5359
// it if present.
5460
func (bc *BlockChain) HasHeader(hash common.Hash, number uint64) bool {

core/rawdb/accessors_chain.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,22 @@ func WriteHeadFastBlockHash(db ethdb.KeyValueWriter, hash common.Hash) {
219219
}
220220
}
221221

222+
// ReadFinalizedBlockHash retrieves the hash of the finalized block.
223+
func ReadFinalizedBlockHash(db ethdb.KeyValueReader) common.Hash {
224+
data, _ := db.Get(headFinalizedBlockKey)
225+
if len(data) == 0 {
226+
return common.Hash{}
227+
}
228+
return common.BytesToHash(data)
229+
}
230+
231+
// WriteFinalizedBlockHash stores the hash of the finalized block.
232+
func WriteFinalizedBlockHash(db ethdb.KeyValueWriter, hash common.Hash) {
233+
if err := db.Put(headFinalizedBlockKey, hash.Bytes()); err != nil {
234+
log.Crit("Failed to store last finalized block's hash", "err", err)
235+
}
236+
}
237+
222238
// ReadLastPivotNumber retrieves the number of the last pivot block. If the node
223239
// full synced, the last pivot will always be nil.
224240
func ReadLastPivotNumber(db ethdb.KeyValueReader) *uint64 {

core/rawdb/database.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -418,8 +418,8 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
418418
default:
419419
var accounted bool
420420
for _, meta := range [][]byte{
421-
databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, lastPivotKey,
422-
fastTrieProgressKey, snapshotDisabledKey, SnapshotRootKey, snapshotJournalKey,
421+
databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, headFinalizedBlockKey,
422+
lastPivotKey, fastTrieProgressKey, snapshotDisabledKey, SnapshotRootKey, snapshotJournalKey,
423423
snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey,
424424
uncleanShutdownKey, badBlockKey, transitionStatusKey, skeletonSyncStatusKey,
425425
} {

core/rawdb/schema.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ var (
4141
// headFastBlockKey tracks the latest known incomplete block's hash during fast sync.
4242
headFastBlockKey = []byte("LastFast")
4343

44+
// headFinalizedBlockKey tracks the latest known finalized block hash.
45+
headFinalizedBlockKey = []byte("LastFinalized")
46+
4447
// lastPivotKey tracks the last pivot block used by fast sync (to reenable on sethead).
4548
lastPivotKey = []byte("LastPivot")
4649

eth/api.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,8 @@ func (api *PublicDebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error
285285
var block *types.Block
286286
if blockNr == rpc.LatestBlockNumber {
287287
block = api.eth.blockchain.CurrentBlock()
288+
} else if blockNr == rpc.FinalizedBlockNumber {
289+
block = api.eth.blockchain.CurrentFinalizedBlock()
288290
} else {
289291
block = api.eth.blockchain.GetBlockByNumber(uint64(blockNr))
290292
}
@@ -373,6 +375,8 @@ func (api *PublicDebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, sta
373375
var block *types.Block
374376
if number == rpc.LatestBlockNumber {
375377
block = api.eth.blockchain.CurrentBlock()
378+
} else if number == rpc.FinalizedBlockNumber {
379+
block = api.eth.blockchain.CurrentFinalizedBlock()
376380
} else {
377381
block = api.eth.blockchain.GetBlockByNumber(uint64(number))
378382
}

eth/api_backend.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb
7373
if number == rpc.LatestBlockNumber {
7474
return b.eth.blockchain.CurrentBlock().Header(), nil
7575
}
76+
if number == rpc.FinalizedBlockNumber {
77+
return b.eth.blockchain.CurrentFinalizedBlock().Header(), nil
78+
}
7679
return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil
7780
}
7881

@@ -114,6 +117,9 @@ func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumbe
114117
if number == rpc.LatestBlockNumber {
115118
return b.eth.blockchain.CurrentBlock(), nil
116119
}
120+
if number == rpc.FinalizedBlockNumber {
121+
return b.eth.blockchain.CurrentFinalizedBlock(), nil
122+
}
117123
return b.eth.blockchain.GetBlockByNumber(uint64(number)), nil
118124
}
119125

eth/catalyst/api.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"encoding/binary"
2323
"errors"
2424
"fmt"
25+
"sync"
2526
"time"
2627

2728
"github.com/ethereum/go-ethereum/common"
@@ -60,6 +61,8 @@ type ConsensusAPI struct {
6061
eth *eth.Ethereum
6162
remoteBlocks *headerQueue // Cache of remote payloads received
6263
localBlocks *payloadQueue // Cache of local payloads generated
64+
// Lock for the forkChoiceUpdated method
65+
forkChoiceLock sync.Mutex
6366
}
6467

6568
// NewConsensusAPI creates a new consensus api for the given backend.
@@ -86,11 +89,15 @@ func NewConsensusAPI(eth *eth.Ethereum) *ConsensusAPI {
8689
// If there are payloadAttributes:
8790
// we try to assemble a block with the payloadAttributes and return its payloadID
8891
func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributesV1) (beacon.ForkChoiceResponse, error) {
92+
api.forkChoiceLock.Lock()
93+
defer api.forkChoiceLock.Unlock()
94+
8995
log.Trace("Engine API request received", "method", "ForkchoiceUpdated", "head", update.HeadBlockHash, "finalized", update.FinalizedBlockHash, "safe", update.SafeBlockHash)
9096
if update.HeadBlockHash == (common.Hash{}) {
9197
log.Warn("Forkchoice requested update to zero hash")
9298
return beacon.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this?
9399
}
100+
94101
// Check whether we have the block yet in our database or not. If not, we'll
95102
// need to either trigger a sync, or to reject this forkchoice update for a
96103
// reason.
@@ -154,7 +161,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
154161
if merger := api.eth.Merger(); !merger.PoSFinalized() {
155162
merger.FinalizePoS()
156163
}
157-
// TODO (MariusVanDerWijden): If the finalized block is not in our canonical tree, somethings wrong
164+
// If the finalized block is not in our canonical tree, somethings wrong
158165
finalBlock := api.eth.BlockChain().GetBlockByHash(update.FinalizedBlockHash)
159166
if finalBlock == nil {
160167
log.Warn("Final block not available in database", "hash", update.FinalizedBlockHash)
@@ -163,8 +170,10 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
163170
log.Warn("Final block not in canonical chain", "number", block.NumberU64(), "hash", update.HeadBlockHash)
164171
return beacon.STATUS_INVALID, errors.New("final block not canonical")
165172
}
173+
// Set the finalized block
174+
api.eth.BlockChain().SetFinalized(finalBlock)
166175
}
167-
// TODO (MariusVanDerWijden): Check if the safe block hash is in our canonical tree, if not somethings wrong
176+
// Check if the safe block hash is in our canonical tree, if not somethings wrong
168177
if update.SafeBlockHash != (common.Hash{}) {
169178
safeBlock := api.eth.BlockChain().GetBlockByHash(update.SafeBlockHash)
170179
if safeBlock == nil {

eth/catalyst/api_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,10 @@ func TestFullAPI(t *testing.T) {
476476
t.Fatalf("Failed to insert block: %v", err)
477477
}
478478
if ethservice.BlockChain().CurrentBlock().NumberU64() != payload.Number {
479-
t.Fatalf("Chain head should be updated")
479+
t.Fatal("Chain head should be updated")
480+
}
481+
if ethservice.BlockChain().CurrentFinalizedBlock().NumberU64() != payload.Number-1 {
482+
t.Fatal("Finalized block should be updated")
480483
}
481484
parent = ethservice.BlockChain().CurrentBlock()
482485
}

internal/jsre/deps/web3.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3696,7 +3696,7 @@ var outputBigNumberFormatter = function (number) {
36963696
};
36973697

36983698
var isPredefinedBlockNumber = function (blockNumber) {
3699-
return blockNumber === 'latest' || blockNumber === 'pending' || blockNumber === 'earliest';
3699+
return blockNumber === 'latest' || blockNumber === 'pending' || blockNumber === 'earliest' || blockNumber === 'finalized';
37003700
};
37013701

37023702
var inputDefaultBlockNumberFormatter = function (blockNumber) {

0 commit comments

Comments
 (0)