allow `--external-beacon-api-url` to fallback to genesis if viable (#5998)

When using `--external-beacon-api-url`, one has to accompany it with
either `--trusted-block-root` or `--trusted-state-root`. If neither is
specified, we can fallback to a deeply finalized noncontroversial block
root. For networks that started post Altair, e.g., Holesky, the genesis
block root fulfills that requirement, as in, it is implicitly trusted.
Therefore, if only `--external-beacon-api-url` is provided without any
`--trusted-block-root` or `--trusted-state-root`, use genesis block root
if it is a viable starting point (post-Altair).

```
build/nimbus_beacon_node \
    --network=holesky \
    --data-dir="$HOME/Downloads/nimbus/data/holesky" \
    "--external-beacon-api-url=http://unstable.holesky.beacon-api.nimbus.team" \
    --tcp-port=9010 --udp-port=9010 \
    --rest --log-level=DEBUG \
    --no-el
```
This commit is contained in:
Etan Kissling 2024-03-05 15:41:22 +01:00 committed by GitHub
parent 2c924bcc8c
commit 89d9dc24bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 93 additions and 69 deletions

View File

@ -54,6 +54,43 @@ declareGauge next_action_wait,
declareCounter db_checkpoint_seconds, declareCounter db_checkpoint_seconds,
"Time spent checkpointing the database to clear the WAL file" "Time spent checkpointing the database to clear the WAL file"
proc fetchGenesisState(
config: BeaconNodeConf,
metadata: Eth2NetworkMetadata
): Future[ref ForkedHashedBeaconState] {.async: (raises: []).} =
let genesisBytes =
if metadata.genesis.kind != BakedIn and config.genesisState.isSome:
let res = io2.readAllBytes(config.genesisState.get.string)
res.valueOr:
error "Failed to read genesis state file", err = res.error.ioErrorMsg
quit 1
elif metadata.hasGenesis:
try:
if metadata.genesis.kind == BakedInUrl:
info "Obtaining genesis state",
sourceUrl = $config.genesisStateUrl
.get(parseUri metadata.genesis.url)
await metadata.fetchGenesisBytes(config.genesisStateUrl)
except CatchableError as err:
error "Failed to obtain genesis state",
source = metadata.genesis.sourceDesc,
err = err.msg
quit 1
else:
@[]
if genesisBytes.len > 0:
try:
newClone readSszForkedHashedBeaconState(metadata.cfg, genesisBytes)
except CatchableError as err:
error "Invalid genesis state",
size = genesisBytes.len,
digest = eth2digest(genesisBytes),
err = err.msg
quit 1
else:
nil
proc doRunTrustedNodeSync( proc doRunTrustedNodeSync(
db: BeaconChainDB, db: BeaconChainDB,
metadata: Eth2NetworkMetadata, metadata: Eth2NetworkMetadata,
@ -64,10 +101,9 @@ proc doRunTrustedNodeSync(
trustedBlockRoot: Option[Eth2Digest], trustedBlockRoot: Option[Eth2Digest],
backfill: bool, backfill: bool,
reindex: bool, reindex: bool,
downloadDepositSnapshot: bool) {.async.} = downloadDepositSnapshot: bool,
let genesisState: ref ForkedHashedBeaconState) {.async.} =
cfg = metadata.cfg let syncTarget =
syncTarget =
if stateId.isSome: if stateId.isSome:
if trustedBlockRoot.isSome: if trustedBlockRoot.isSome:
warn "Ignoring `trustedBlockRoot`, `stateId` is set", warn "Ignoring `trustedBlockRoot`, `stateId` is set",
@ -83,19 +119,9 @@ proc doRunTrustedNodeSync(
TrustedNodeSyncTarget( TrustedNodeSyncTarget(
kind: TrustedNodeSyncKind.StateId, kind: TrustedNodeSyncKind.StateId,
stateId: "finalized") stateId: "finalized")
genesis =
if metadata.hasGenesis:
let genesisBytes = try: await metadata.fetchGenesisBytes()
except CatchableError as err:
error "Failed to obtain genesis state",
source = metadata.genesis.sourceDesc,
err = err.msg
quit 1
newClone(readSszForkedHashedBeaconState(cfg, genesisBytes))
else: nil
await db.doTrustedNodeSync( await db.doTrustedNodeSync(
cfg, metadata.cfg,
databaseDir, databaseDir,
eraDir, eraDir,
restUrl, restUrl,
@ -103,7 +129,7 @@ proc doRunTrustedNodeSync(
backfill, backfill,
reindex, reindex,
downloadDepositSnapshot, downloadDepositSnapshot,
genesis) genesisState)
func getVanityLogs(stdoutKind: StdoutLogKind): VanityLogs = func getVanityLogs(stdoutKind: StdoutLogKind): VanityLogs =
case stdoutKind case stdoutKind
@ -544,13 +570,36 @@ proc init*(T: type BeaconNode,
db = BeaconChainDB.new(config.databaseDir, cfg, inMemory = false) db = BeaconChainDB.new(config.databaseDir, cfg, inMemory = false)
if config.externalBeaconApiUrl.isSome and ChainDAGRef.isInitialized(db).isErr: if config.externalBeaconApiUrl.isSome and ChainDAGRef.isInitialized(db).isErr:
if config.trustedStateRoot.isNone and config.trustedBlockRoot.isNone: var genesisState: ref ForkedHashedBeaconState
let trustedBlockRoot =
if config.trustedStateRoot.isSome or config.trustedBlockRoot.isSome:
config.trustedBlockRoot
elif cfg.ALTAIR_FORK_EPOCH == GENESIS_EPOCH:
# Sync can be bootstrapped from the genesis block root
genesisState = await fetchGenesisState(config, metadata)
if genesisState != nil:
let genesisBlockRoot = get_initial_beacon_block(genesisState[]).root
notice "Neither `--trusted-block-root` nor `--trusted-state-root` " &
"provided with `--external-beacon-api-url`, " &
"falling back to genesis block root",
externalBeaconApiUrl = config.externalBeaconApiUrl.get,
trustedBlockRoot = config.trustedBlockRoot,
trustedStateRoot = config.trustedStateRoot,
genesisBlockRoot = $genesisBlockRoot
some genesisBlockRoot
else:
none[Eth2Digest]()
else:
none[Eth2Digest]()
if config.trustedStateRoot.isNone and trustedBlockRoot.isNone:
warn "Ignoring `--external-beacon-api-url`, neither " & warn "Ignoring `--external-beacon-api-url`, neither " &
"`--trusted-block-root` nor `--trusted-state-root` are provided", "`--trusted-block-root` nor `--trusted-state-root` provided",
externalBeaconApiUrl = config.externalBeaconApiUrl.get, externalBeaconApiUrl = config.externalBeaconApiUrl.get,
trustedBlockRoot = config.trustedBlockRoot, trustedBlockRoot = config.trustedBlockRoot,
trustedStateRoot = config.trustedStateRoot trustedStateRoot = config.trustedStateRoot
else: else:
if genesisState == nil:
genesisState = await fetchGenesisState(config, metadata)
await db.doRunTrustedNodeSync( await db.doRunTrustedNodeSync(
metadata, metadata,
config.databaseDir, config.databaseDir,
@ -558,10 +607,11 @@ proc init*(T: type BeaconNode,
config.externalBeaconApiUrl.get, config.externalBeaconApiUrl.get,
config.trustedStateRoot.map do (x: Eth2Digest) -> string: config.trustedStateRoot.map do (x: Eth2Digest) -> string:
"0x" & x.data.toHex, "0x" & x.data.toHex,
config.trustedBlockRoot, trustedBlockRoot,
backfill = false, backfill = false,
reindex = false, reindex = false,
downloadDepositSnapshot = false) downloadDepositSnapshot = false,
genesisState)
if config.finalizedCheckpointBlock.isSome: if config.finalizedCheckpointBlock.isSome:
warn "--finalized-checkpoint-block has been deprecated, ignoring" warn "--finalized-checkpoint-block has been deprecated, ignoring"
@ -609,40 +659,12 @@ proc init*(T: type BeaconNode,
var networkGenesisValidatorsRoot = metadata.bakedGenesisValidatorsRoot var networkGenesisValidatorsRoot = metadata.bakedGenesisValidatorsRoot
if not ChainDAGRef.isInitialized(db).isOk(): if not ChainDAGRef.isInitialized(db).isOk():
let genesisState = if checkpointState != nil and getStateField(checkpointState[], slot) == 0: let genesisState =
if checkpointState != nil and
getStateField(checkpointState[], slot) == 0:
checkpointState checkpointState
else: else:
let genesisBytes = block: await fetchGenesisState(config, metadata)
if metadata.genesis.kind != BakedIn and config.genesisState.isSome:
let res = io2.readAllBytes(config.genesisState.get.string)
res.valueOr:
error "Failed to read genesis state file", err = res.error.ioErrorMsg
quit 1
elif metadata.hasGenesis:
try:
if metadata.genesis.kind == BakedInUrl:
info "Obtaining genesis state",
sourceUrl = $config.genesisStateUrl.get(parseUri metadata.genesis.url)
await metadata.fetchGenesisBytes(config.genesisStateUrl)
except CatchableError as err:
error "Failed to obtain genesis state",
source = metadata.genesis.sourceDesc,
err = err.msg
quit 1
else:
@[]
if genesisBytes.len > 0:
try:
newClone readSszForkedHashedBeaconState(cfg, genesisBytes)
except CatchableError as err:
error "Invalid genesis state",
size = genesisBytes.len,
digest = eth2digest(genesisBytes),
err = err.msg
quit 1
else:
nil
if genesisState == nil and checkpointState == nil: if genesisState == nil and checkpointState == nil:
fatal "No database and no genesis snapshot found. Please supply a genesis.ssz " & fatal "No database and no genesis snapshot found. Please supply a genesis.ssz " &
@ -2263,6 +2285,7 @@ proc handleStartUpCmd(config: var BeaconNodeConf) {.raises: [CatchableError].} =
let let
metadata = loadEth2Network(config) metadata = loadEth2Network(config)
db = BeaconChainDB.new(config.databaseDir, metadata.cfg, inMemory = false) db = BeaconChainDB.new(config.databaseDir, metadata.cfg, inMemory = false)
genesisState = waitFor fetchGenesisState(config, metadata)
waitFor db.doRunTrustedNodeSync( waitFor db.doRunTrustedNodeSync(
metadata, metadata,
config.databaseDir, config.databaseDir,
@ -2272,7 +2295,8 @@ proc handleStartUpCmd(config: var BeaconNodeConf) {.raises: [CatchableError].} =
config.lcTrustedBlockRoot, config.lcTrustedBlockRoot,
config.backfillBlocks, config.backfillBlocks,
config.reindex, config.reindex,
config.downloadDepositSnapshot) config.downloadDepositSnapshot,
genesisState)
db.close() db.close()
{.pop.} # TODO moduletests exceptions {.pop.} # TODO moduletests exceptions