Skip to content

Commit e6fa102

Browse files
core, eth, internal, rpc: implement final block (#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 57192bd commit e6fa102

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)
@@ -187,8 +188,9 @@ type BlockChain struct {
187188
// Readers don't need to take it, they can just read the database.
188189
chainmu *syncx.ClosableMutex
189190

190-
currentBlock atomic.Value // Current head of the block chain
191-
currentFastBlock atomic.Value // Current head of the fast-sync chain (may be above the block chain!)
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!)
193+
currentFinalizedBlock atomic.Value // Current finalized head
192194

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

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

466478
headerTd := bc.GetTd(currentHeader.Hash(), currentHeader.Number.Uint64())
467479
blockTd := bc.GetTd(currentBlock.Hash(), currentBlock.NumberU64())
@@ -470,6 +482,11 @@ func (bc *BlockChain) loadLastState() error {
470482
log.Info("Loaded most recent local header", "number", currentHeader.Number, "hash", currentHeader.Hash(), "td", headerTd, "age", common.PrettyAge(time.Unix(int64(currentHeader.Time), 0)))
471483
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)))
472484
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)))
485+
486+
if currentFinalizedBlock != nil {
487+
finalTd := bc.GetTd(currentFinalizedBlock.Hash(), currentFinalizedBlock.NumberU64())
488+
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)))
489+
}
473490
if pivot := rawdb.ReadLastPivotNumber(bc.db); pivot != nil {
474491
log.Info("Loaded last fast-sync pivot marker", "number", *pivot)
475492
}
@@ -484,6 +501,13 @@ func (bc *BlockChain) SetHead(head uint64) error {
484501
return err
485502
}
486503

504+
// SetFinalized sets the finalized block.
505+
func (bc *BlockChain) SetFinalized(block *types.Block) {
506+
bc.currentFinalizedBlock.Store(block)
507+
rawdb.WriteFinalizedBlockHash(bc.db, block.Hash())
508+
headFinalizedBlockGauge.Update(int64(block.NumberU64()))
509+
}
510+
487511
// setHeadBeyondRoot rewinds the local chain to a new head with the extra condition
488512
// that the rewind must pass the specified state root. This method is meant to be
489513
// 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
@@ -216,6 +216,22 @@ func WriteHeadFastBlockHash(db ethdb.KeyValueWriter, hash common.Hash) {
216216
}
217217
}
218218

219+
// ReadFinalizedBlockHash retrieves the hash of the finalized block.
220+
func ReadFinalizedBlockHash(db ethdb.KeyValueReader) common.Hash {
221+
data, _ := db.Get(headFinalizedBlockKey)
222+
if len(data) == 0 {
223+
return common.Hash{}
224+
}
225+
return common.BytesToHash(data)
226+
}
227+
228+
// WriteFinalizedBlockHash stores the hash of the finalized block.
229+
func WriteFinalizedBlockHash(db ethdb.KeyValueWriter, hash common.Hash) {
230+
if err := db.Put(headFinalizedBlockKey, hash.Bytes()); err != nil {
231+
log.Crit("Failed to store last finalized block's hash", "err", err)
232+
}
233+
}
234+
219235
// ReadLastPivotNumber retrieves the number of the last pivot block. If the node
220236
// full synced, the last pivot will always be nil.
221237
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
@@ -39,6 +39,9 @@ var (
3939
// headFastBlockKey tracks the latest known incomplete block's hash during fast sync.
4040
headFastBlockKey = []byte("LastFast")
4141

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

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

@@ -107,6 +110,9 @@ func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumbe
107110
if number == rpc.LatestBlockNumber {
108111
return b.eth.blockchain.CurrentBlock(), nil
109112
}
113+
if number == rpc.FinalizedBlockNumber {
114+
return b.eth.blockchain.CurrentFinalizedBlock(), nil
115+
}
110116
return b.eth.blockchain.GetBlockByNumber(uint64(number)), nil
111117
}
112118

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)