Skip to content

fCU early notification of finalized hash to FC. #3204

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions execution_chain/beacon/api_handler/api_forkchoice.nim
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ proc forkchoiceUpdated*(ben: BeaconEngineRef,
# Check whether we have the block yet in our database or not. If not, we'll
# need to either trigger a sync, or to reject this forkchoice update for a
# reason.
let header = ben.chain.headerByHash(headHash).valueOr:
let header = chain.headerByHash(headHash).valueOr:
# If this block was previously invalidated, keep rejecting it here too
let res = ben.checkInvalidAncestor(headHash, headHash)
if res.isSome:
Expand All @@ -106,6 +106,7 @@ proc forkchoiceUpdated*(ben: BeaconEngineRef,
number = header.number,
hash = headHash.short

chain.notifyFinalizedHash(update.finalizedBlockHash)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should also update latestFinalizedBlockNumber with max(latestFinalizedBlockNumber, newNumber) ..

also, should set latestFCU even if we can't find the number

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not quite done here: need to look up the block number of update.finalizedBlockHash in the quarantine - the syncer will not do it if it's already syncing something else.

# Inform the header chain cache (used by the syncer)
com.headerChainUpdate(header, update.finalizedBlockHash)

Expand Down Expand Up @@ -172,12 +173,9 @@ proc forkchoiceUpdated*(ben: BeaconEngineRef,
warn "Safe block not in canonical tree",
hash=safeBlockHash.short
raise invalidForkChoiceState("safe block not in canonical tree")
# Current version of FC module is not interested in safeBlockHash
# so we save it here
let txFrame = chain.txFrame(safeBlockHash)
txFrame.safeHeaderHash(safeBlockHash)
# similar to headHash, safeBlockHash is saved by FC module

chain.forkChoice(headHash, update.finalizedBlockHash).isOkOr:
chain.forkChoice(headHash, finalizedBlockHash, safeBlockHash).isOkOr:
return invalidFCU(error, chain, header)

# If payload generation was requested, create a new block to be potentially
Expand Down
28 changes: 15 additions & 13 deletions execution_chain/common/common.nim
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import
chronicles,
logging,
../db/[core_db, ledger, storage_types],
../db/[core_db, ledger, storage_types, fcu_db],
../utils/[utils],
".."/[constants, errors, version],
"."/[chain_config, evmforks, genesis, hardforks],
Expand Down Expand Up @@ -110,8 +110,9 @@ proc initializeDb(com: CommonRef) =
proc contains(txFrame: CoreDbTxRef; key: openArray[byte]): bool =
txFrame.hasKeyRc(key).expect "valid bool"
if canonicalHeadHashKey().toOpenArray notin txFrame:
let genesisHash = com.genesisHeader.computeBlockHash
info "Writing genesis to DB",
blockHash = com.genesisHeader.computeBlockHash,
blockHash = genesisHash ,
stateRoot = com.genesisHeader.stateRoot,
difficulty = com.genesisHeader.difficulty,
gasLimit = com.genesisHeader.gasLimit,
Expand All @@ -122,7 +123,8 @@ proc initializeDb(com: CommonRef) =
txFrame.persistHeaderAndSetHead(com.genesisHeader,
startOfHistory=com.genesisHeader.parentHash).
expect("can persist genesis header")

txFrame.fcuHead(genesisHash, com.genesisHeader.number).
expect("fcuHead OK")
doAssert(canonicalHeadHashKey().toOpenArray in txFrame)

txFrame.checkpoint(com.genesisHeader.number)
Expand All @@ -136,18 +138,18 @@ proc initializeDb(com: CommonRef) =
fatal "Cannot load base block header",
baseNum, err = error
quit 1
finalized = txFrame.finalizedHeader().valueOr:
debug "No finalized block stored in database, reverting to base"
base
head = txFrame.getCanonicalHead().valueOr:
fatal "Cannot load canonical block header",
err = error
quit 1
baseHash = base.computeBlockHash
finalized = txFrame.fcuFinalized().valueOr:
debug "Reverting to base", err = error
FcuHashAndNumber(hash: baseHash, number: base.number)
head = txFrame.fcuHead().valueOr:
fatal "Reverting to base", err = error
FcuHashAndNumber(hash: baseHash, number: base.number)

info "Database initialized",
base = (base.computeBlockHash, base.number),
finalized = (finalized.computeBlockHash, finalized.number),
head = (head.computeBlockHash, head.number)
base = (baseHash, base.number),
finalized = (finalized.hash, finalized.number),
head = (head.hash, head.number)

proc init(com : CommonRef,
db : CoreDbRef,
Expand Down
77 changes: 67 additions & 10 deletions execution_chain/core/chain/forked_chain.nim
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import
std/[tables, algorithm],
../../common,
../../db/core_db,
../../db/fcu_db,
../../evm/types,
../../evm/state,
../validate,
Expand Down Expand Up @@ -111,6 +112,16 @@ proc writeBaggage(c: ForkedChainRef,
header.withdrawalsRoot.expect("WithdrawalsRoot should be verified before"),
blk.withdrawals.get)

proc fcuSetHead(c: ForkedChainRef,
txFrame: CoreDbTxRef,
header: Header,
hash: Hash32,
number: uint64) =
txFrame.setHead(header, hash).expect("setHead OK")
txFrame.fcuHead(hash, number).expect("fcuHead OK")
c.fcuHead.number = number
c.fcuHead.hash = hash

proc validateBlock(c: ForkedChainRef,
parent: BlockPos,
blk: Block): Result[Hash32, string] =
Expand All @@ -122,7 +133,8 @@ proc validateBlock(c: ForkedChainRef,

if blkHash == c.pendingFCU:
# Resolve the hash into latestFinalizedBlockNumber
c.latestFinalizedBlockNumber = blk.header.number
c.latestFinalizedBlockNumber = max(blk.header.number,
c.latestFinalizedBlockNumber)

let
parentFrame = parent.txFrame
Expand Down Expand Up @@ -192,11 +204,12 @@ proc validateBlock(c: ForkedChainRef,
# If on disk head behind base, move it to base too.
let newBaseNumber = c.baseBranch.tailNumber
if newBaseNumber > prevBaseNumber:
let canonicalHead = ?c.baseTxFrame.getCanonicalHead()
if canonicalHead.number < newBaseNumber:
if c.fcuHead.number < newBaseNumber:
let head = c.baseBranch.firstBlockPos
head.txFrame.setHead(head.branch.tailHeader,
head.branch.tailHash).expect("OK")
c.fcuSetHead(head.txFrame,
head.branch.tailHeader,
head.branch.tailHash,
head.branch.tailNumber)

ok(blkHash)

Expand Down Expand Up @@ -371,8 +384,10 @@ proc updateHead(c: ForkedChainRef, head: BlockPos) =
c.removeBlockFromCache(head.branch.blocks[i])

head.branch.blocks.setLen(head.index+1)
head.txFrame.setHead(head.branch.headHeader,
head.branch.headHash).expect("OK")
c.fcuSetHead(head.txFrame,
head.branch.headHeader,
head.branch.headHash,
head.branch.headNumber)

proc updateFinalized(c: ForkedChainRef, finalized: BlockPos) =
# Pruning
Expand Down Expand Up @@ -411,7 +426,7 @@ proc updateFinalized(c: ForkedChainRef, finalized: BlockPos) =
inc i

let txFrame = finalized.txFrame
txFrame.finalizedHeaderHash(finalized.hash)
txFrame.fcuFinalized(finalized.hash, finalized.number).expect("fcuFinalized OK")

proc updateBase(c: ForkedChainRef, newBase: BlockPos) =
##
Expand Down Expand Up @@ -541,6 +556,10 @@ proc init*(
baseHash = baseTxFrame.getBlockHash(base).expect("baseHash exists")
baseHeader = baseTxFrame.getBlockHeader(baseHash).expect("base header exists")
baseBranch = branch(baseHeader, baseHash, baseTxFrame)
fcuHead = baseTxFrame.fcuHead().valueOr:
FcuHashAndNumber(hash: baseHash, number: baseHeader.number)
fcuSafe = baseTxFrame.fcuSafe().valueOr:
FcuHashAndNumber(hash: baseHash, number: baseHeader.number)

T(com: com,
baseBranch: baseBranch,
Expand All @@ -550,7 +569,9 @@ proc init*(
baseTxFrame: baseTxFrame,
baseDistance: baseDistance,
persistBatchSize:persistBatchSize,
quarantine: Quarantine.init())
quarantine: Quarantine.init(),
fcuHead: fcuHead,
fcuSafe: fcuSafe)

proc importBlock*(c: ForkedChainRef, blk: Block): Result[void, string] =
## Try to import block to canonical or side chain.
Expand Down Expand Up @@ -596,11 +617,19 @@ proc importBlock*(c: ForkedChainRef, blk: Block): Result[void, string] =

proc forkChoice*(c: ForkedChainRef,
headHash: Hash32,
finalizedHash: Hash32): Result[void, string] =
finalizedHash: Hash32,
safeHash: Hash32 = zeroHash32): Result[void, string] =

if finalizedHash != zeroHash32:
c.pendingFCU = finalizedHash

if safeHash != zeroHash32:
c.hashToBlock.withValue(safeHash, loc):
let number = loc[].number
c.fcuSafe.number = number
c.fcuSafe.hash = safeHash
?loc[].txFrame.fcuSafe(c.fcuSafe)

if headHash == c.activeBranch.headHash:
if finalizedHash == zeroHash32:
# Do nothing if the new head already our current head
Expand Down Expand Up @@ -644,6 +673,10 @@ proc forkChoice*(c: ForkedChainRef,

ok()

func notifyFinalizedHash*(c: ForkedChainRef, finHash: Hash32) =
if finHash != zeroHash32:
c.pendingFCU = finHash

func haveBlockAndState*(c: ForkedChainRef, blockHash: Hash32): bool =
## Blocks still in memory with it's txFrame
c.hashToBlock.hasKey(blockHash)
Expand Down Expand Up @@ -739,6 +772,30 @@ proc headerByNumber*(c: ForkedChainRef, number: BlockNumber): Result[Header, str

err("Header not found, number = " & $number)

func finalizedHeader*(c: ForkedChainRef): Header =
c.hashToBlock.withValue(c.pendingFCU, loc):
return loc[].header

c.baseBranch.tailHeader

func safeHeader*(c: ForkedChainRef): Header =
c.hashToBlock.withValue(c.fcuSafe.hash, loc):
return loc[].header

c.baseBranch.tailHeader

func finalizedBlock*(c: ForkedChainRef): Block =
c.hashToBlock.withValue(c.pendingFCU, loc):
return loc[].blk

c.baseBranch.tailBlock

func safeBlock*(c: ForkedChainRef): Block =
c.hashToBlock.withValue(c.fcuSafe.hash, loc):
return loc[].blk

c.baseBranch.tailBlock

proc headerByHash*(c: ForkedChainRef, blockHash: Hash32): Result[Header, string] =
c.hashToBlock.withValue(blockHash, loc):
return ok(loc[].header)
Expand Down
3 changes: 3 additions & 0 deletions execution_chain/core/chain/forked_chain/chain_branch.nim
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ type
parent*: BranchRef
# If parent.isNil: it is a base branch

func tailBlock*(brc: BranchRef): Block =
brc.blocks[0].blk

func tailNumber*(brc: BranchRef): BlockNumber =
brc.blocks[0].blk.header.number

Expand Down
6 changes: 5 additions & 1 deletion execution_chain/core/chain/forked_chain/chain_desc.nim
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import
./chain_branch,
./block_quarantine,
../../../common,
../../../db/core_db
../../../db/core_db,
../../../db/fcu_db

export deques, tables

Expand Down Expand Up @@ -66,6 +67,9 @@ type
# to move the base. And the bulk writing can works
# efficiently.

fcuHead*: FcuHashAndNumber
fcuSafe*: FcuHashAndNumber

# ----------------

func txRecords*(c: ForkedChainRef): var Table[Hash32, (Hash32, uint64)] =
Expand Down
31 changes: 0 additions & 31 deletions execution_chain/db/core_db/core_apps.nim
Original file line number Diff line number Diff line change
Expand Up @@ -601,37 +601,6 @@ proc persistUncles*(db: CoreDbTxRef, uncles: openArray[Header]): Hash32 =
warn "persistUncles()", unclesHash=result, error=($$error)
return EMPTY_ROOT_HASH


proc safeHeaderHash*(db: CoreDbTxRef): Hash32 =
db.getHash(safeHashKey()).valueOr(default(Hash32))

proc safeHeaderHash*(db: CoreDbTxRef, headerHash: Hash32) =
let safeHashKey = safeHashKey()
db.put(safeHashKey.toOpenArray, rlp.encode(headerHash)).isOkOr:
warn "safeHeaderHash()", safeHashKey, error=($$error)
return

proc finalizedHeaderHash*(
db: CoreDbTxRef;
): Hash32 =
db.getHash(finalizedHashKey()).valueOr(default(Hash32))

proc finalizedHeaderHash*(db: CoreDbTxRef, headerHash: Hash32) =
let finalizedHashKey = finalizedHashKey()
db.put(finalizedHashKey.toOpenArray, rlp.encode(headerHash)).isOkOr:
warn "finalizedHeaderHash()", finalizedHashKey, error=($$error)
return

proc safeHeader*(
db: CoreDbTxRef;
): Result[Header, string] =
db.getBlockHeader(db.safeHeaderHash)

proc finalizedHeader*(
db: CoreDbTxRef;
): Result[Header, string] =
db.getBlockHeader(db.finalizedHeaderHash)

# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------
74 changes: 74 additions & 0 deletions execution_chain/db/fcu_db.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# nimbus-execution-client
# Copyright (c) 2025 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.

{.push raises: [].}

import
eth/common/hashes,
stew/endians2,
stew/assign2,
results,
./core_db/base,
./storage_types

type
FcuHashAndNumber* = object
hash*: Hash32
number*: uint64

const
headKey = fcuKey 0
finKey = fcuKey 1
safeKey = fcuKey 2
DataLen = sizeof(Hash32) + sizeof(uint64)

template fcuReadImpl(key: DbKey, name: string): auto =
let data = db.getOrEmpty(key.toOpenArray).valueOr:
return err($error)
if data.len != DataLen:
return err("no " & name & " block hash and number")
ok(FcuHashAndNumber(
hash: Hash32.copyFrom(data.toOpenArray(sizeof(uint64), data.len-1)),
number: uint64.fromBytesBE(data),
))

template fcuWriteImpl(key: DbKey, hash: Hash32, number: uint64): auto =
var data: array[DataLen, byte]
assign(data.toOpenArray(0, sizeof(uint64)-1), number.toBytesBE)
assign(data.toOpenArray(sizeof(uint64), data.len-1), hash.data)
db.put(key.toOpenArray, data).isOkOr:
return err($error)
ok()

proc fcuHead*(db: CoreDbTxRef): Result[FcuHashAndNumber, string] =
fcuReadImpl(headKey, "head")

proc fcuHead*(db: CoreDbTxRef, hash: Hash32, number: uint64): Result[void, string] =
fcuWriteImpl(headKey, hash, number)

template fcuHead*(db: CoreDbTxRef, head: FcuHashAndNumber): auto =
fcuHead(db, head.hash, head.number)

proc fcuFinalized*(db: CoreDbTxRef): Result[FcuHashAndNumber, string] =
fcuReadImpl(finKey, "finalized")

proc fcuFinalized*(db: CoreDbTxRef, hash: Hash32, number: uint64): Result[void, string] =
fcuWriteImpl(finKey, hash, number)

template fcuFinalized*(db: CoreDbTxRef, finalized: FcuHashAndNumber): auto =
fcuFinalized(db, finalized.hash, finalized.number)

proc fcuSafe*(db: CoreDbTxRef): Result[FcuHashAndNumber, string] =
fcuReadImpl(safeKey, "safe")

proc fcuSafe*(db: CoreDbTxRef, hash: Hash32, number: uint64): Result[void, string] =
fcuWriteImpl(safeKey, hash, number)

template fcuSafe*(db: CoreDbTxRef, safe: FcuHashAndNumber): auto =
fcuSafe(db, safe.hash, safe.number)
Loading
Loading