Skip to content
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

Trusted node sync #3209

Merged
merged 3 commits into from
Jan 17, 2022
Merged
Changes from 1 commit
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
Prev Previous commit
Next Next commit
fixes
* load genesis from network metadata
* check checkpoint block root against state
* fix invalid block root in rest json decoding
* odds and ends
arnetheduck committed Jan 12, 2022
commit 63116d75147466026e20c72d9252395dc954647a
16 changes: 13 additions & 3 deletions beacon_chain/nimbus_beacon_node.nim
Original file line number Diff line number Diff line change
@@ -1927,10 +1927,20 @@ programMain:
of BNStartUpCmd.web3: doWeb3Cmd(config)
of BNStartUpCmd.slashingdb: doSlashingInterchange(config)
of BNStartupCmd.trustedNodeSync:
# TODO use genesis state from metadata
let
network = loadEth2Network(config)
cfg = network.cfg
genesis =
if network.genesisData.len > 0:
newClone(readSszForkedHashedBeaconState(
cfg,
network.genesisData.toOpenArrayByte(0, network.genesisData.high())))
else: nil

waitFor doTrustedNodeSync(
getRuntimeConfig(config.eth2Network),
cfg,
config.databaseDir,
config.trustedNodeUrl,
config.blockId,
config.backfillBlocks)
config.backfillBlocks,
genesis)
2 changes: 2 additions & 0 deletions beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim
Original file line number Diff line number Diff line change
@@ -795,6 +795,8 @@ proc readValue*(reader: var JsonReader[RestJson],
if res.isNone():
reader.raiseUnexpectedValue("Incorrect merge block format")
value = ForkedSignedBeaconBlock.init(res.get())
withBlck(value):
blck.root = hash_tree_root(blck.message)

proc writeValue*(writer: var JsonWriter[RestJson],
value: ForkedSignedBeaconBlock) {.
68 changes: 44 additions & 24 deletions beacon_chain/trusted_node_sync.nim
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Copyright (c) 2018-2022 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.

{.push raises: [Defect].}

import
std/[os],

stew/[assign2, base10],
chronicles, chronos,
./sync/sync_manager,
@@ -9,8 +16,6 @@ import
./spec/[beaconstate, eth2_merkleization, forks, presets, state_transition],
"."/[beacon_clock, beacon_chain_db]

{.push raises: [Defect].}

type
DbCache = object
summaries: Table[Eth2Digest, BeaconBlockSummary]
@@ -22,7 +27,8 @@ const
proc updateSlots(cache: var DbCache, root: Eth2Digest, slot: Slot) =
# The slots mapping stores one linear block history - we construct it by
# starting from a given root/slot and walking the known parents as far back
# as possible - this ensures that
# as possible which ensures that all blocks belong to the same history

if cache.slots.len() < slot.int + 1:
Copy link
Contributor

Choose a reason for hiding this comment

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

lenu64 would allow this to be somewhat type-safer

Copy link
Member Author

Choose a reason for hiding this comment

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

...and crash on the next line where setLen(slot.int + 1) happens :/

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I don't see a great approach to this. Maybe better to at least be consistent within these two lines, leave it as is.

cache.slots.setLen(slot.int + 1)

@@ -57,14 +63,14 @@ proc update(cache: var DbCache, blck: ForkySignedBeaconBlock) =
cache.updateSlots(blck.root, blck.message.slot)

proc isKnown(cache: DbCache, slot: Slot): bool =
slot.int < cache.slots.len and cache.slots[slot.int].isSome()
slot < cache.slots.lenu64 and cache.slots[slot.int].isSome()

proc doTrustedNodeSync*(
cfg: RuntimeConfig, databaseDir: string, restUrl: string,
blockId: string, backfill: bool,
genesisState: ref ForkedHashedBeaconState = nil) {.async.} =
notice "Starting trusted node sync",
databaseDir, restUrl, blockId
databaseDir, restUrl, blockId, backfill

let
db = BeaconChainDB.new(databaseDir, inMemory = false)
@@ -80,6 +86,7 @@ proc doTrustedNodeSync*(
# over in this case
error "Database missing head block summary - database too old or corrupt"
quit 1

let slot = dbCache.summaries[dbHead.get()].slot
dbCache.updateSlots(dbHead.get(), slot)
slot
@@ -101,7 +108,7 @@ proc doTrustedNodeSync*(
warn "Retrying download of block", slot, err = exc.msg
client = RestClientRef.new(restUrl).get()

error "Unable to download block",
error "Unable to download block - backfill incomplete, but will resume when you start the beacon node",
slot, error = lastError.msg, url = client.address

quit 1
@@ -131,7 +138,8 @@ proc doTrustedNodeSync*(

withState(genesisState[]):
info "Writing genesis state",
stateRoot = shortLog(state.root)
stateRoot = shortLog(state.root),
genesis_validators_root = shortLog(state.data.genesis_validators_root)

db.putStateRoot(state.latest_block_root(), state.data.slot, state.root)
db.putState(state.root, state.data)
@@ -154,10 +162,11 @@ proc doTrustedNodeSync*(
error "Unable to download genesis header",
error = exc.msg, restUrl
quit 1

if genesisHeader.root != genesisRoot:
error "Server genesis block root does not match local genesis",
localGenesis = shortLog(genesisRoot),
remoteGenesis = shortLog(genesisHeader.root)
localGenesisRoot = shortLog(genesisRoot),
remoteGenesisRoot = shortLog(genesisHeader.root)
quit 1

notice "Downloading checkpoint block", restUrl, blockId
@@ -191,7 +200,7 @@ proc doTrustedNodeSync*(
checkpointSlot, checkpointRoot = shortLog(checkpointBlock.root), headSlot
quit 1

if checkpointSlot.uint64 mod SLOTS_PER_EPOCH != 0:
if not checkpointSlot.is_epoch():
# Else the ChainDAG logic might get messed up - this constraint could
# potentially be avoided with appropriate refactoring
error "Checkpoint block must fall on an epoch boundary",
@@ -205,7 +214,7 @@ proc doTrustedNodeSync*(
dbCache.updateSlots(blck.root, blck.message.slot)

else:
notice "Downloading checkpoint state", restUrl, blockId, checkpointSlot
notice "Downloading checkpoint state", restUrl, checkpointSlot

let
state = try:
@@ -220,6 +229,15 @@ proc doTrustedNodeSync*(
quit 1

withState(state[]):
let latest_block_root = state.latest_block_root

if latest_block_root != checkpointBlock.root:
error "Checkpoint state does not match checkpoint block, server error?",
blockRoot = shortLog(checkpointBlock.root),
blck = shortLog(checkpointBlock),
stateBlockRoot = shortLog(latest_block_root)
quit 1

info "Writing checkpoint state",
stateRoot = shortLog(state.root)
db.putStateRoot(state.latest_block_root(), state.data.slot, state.root)
@@ -253,7 +271,7 @@ proc doTrustedNodeSync*(
if missingSlots == 0:
info "Database fully backfilled"
elif backfill:
notice "Backfilling historical blocks",
notice "Downloading historical blocks - you can interrupt this process at any time and it automatically be completed when you start the beacon node",
checkpointSlot, missingSlots
Copy link
Contributor

Choose a reason for hiding this comment

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

Will this part be mutualized with deferred backfill in #3263

Copy link
Member Author

Choose a reason for hiding this comment

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

The backfill of 3263 picks up wherever this back fill is interrupted


var # Same averaging as SyncManager
@@ -262,6 +280,7 @@ proc doTrustedNodeSync*(
avgSyncSpeed = 0.0
stamp = SyncMoment.now(0)

# Download several blocks in parallel but process them serially
var gets: array[8, Future[Option[ForkedSignedBeaconBlock]]]
proc processBlock(fut: Future[Option[ForkedSignedBeaconBlock]], slot: Slot) {.async.} =
processed += 1
@@ -278,7 +297,7 @@ proc doTrustedNodeSync*(

var childSlot = blck.message.slot + 1
while true:
if childSlot.int >= dbCache.slots.len():
if childSlot >= dbCache.slots.lenu64():
error "Downloaded block does not match checkpoint history"
quit 1

@@ -332,23 +351,24 @@ proc doTrustedNodeSync*(
# Download blocks backwards from the checkpoint slot, skipping the ones we
# already have in the database. We'll do a few downloads in parallel which
# risks having some redundant downloads going on, but speeds things up
for i in 0..checkpointSlot.int64 + gets.len():
if not isNil(gets[i mod gets.len]):
await processBlock(gets[i mod gets.len], Slot(
gets.len().int64 + checkpointSlot.int64 - i))
gets[i mod gets.len] = nil

if i < checkpointSlot.int64:
let slot = Slot(checkpointSlot.int64 - i)
for i in 0'u64..<(checkpointSlot.uint64 + gets.lenu64()):
if not isNil(gets[int(i mod gets.lenu64)]):
await processBlock(
gets[int(i mod gets.lenu64)],
checkpointSlot + gets.lenu64() - uint64(i))
gets[int(i mod gets.lenu64)] = nil

if i < checkpointSlot:
let slot = checkpointSlot - i
if dbCache.isKnown(slot):
continue

gets[i mod gets.len] = downloadBlock(slot)
gets[int(i mod gets.lenu64)] = downloadBlock(slot)
else:
notice "Database initialized, historical blocks will be backfilled when starting the node",
missingSlots

notice "Done, your beacon node is ready to serve you! Don't forget to check that you're on the canoncial chain by comparing the checkpoint root with other online sources. See https://nimbus.guide/trusted-node-sync.html for more infromation.",
notice "Done, your beacon node is ready to serve you! Don't forget to check that you're on the canoncial chain by comparing the checkpoint root with other online sources. See https://nimbus.guide/trusted-node-sync.html for more information.",
checkpointRoot = checkpointBlock.root

when isMainModule: