Add support for starting from weak subjectivity checkpoints

Also removes the `genesis.ssz` file stored in the data folder.
The `medalla-fast-sync` target has been adapted to use the new features.
This commit is contained in:
Zahary Karadjov 2020-09-22 23:42:42 +03:00 committed by zah
parent dc428e00db
commit aed291128a
16 changed files with 285 additions and 148 deletions

View File

@ -200,12 +200,14 @@ define CONNECT_TO_NETWORK
--base-metrics-port $$(($(BASE_METRICS_PORT) + $(NODE_ID))) \ --base-metrics-port $$(($(BASE_METRICS_PORT) + $(NODE_ID))) \
--config-file "build/data/shared_$(1)_$(NODE_ID)/prometheus.yml" --config-file "build/data/shared_$(1)_$(NODE_ID)/prometheus.yml"
[ "$(2)" == "FastSync" ] && { export CHECKPOINT_PARAMS="--finalized-checkpoint-state=vendor/eth2-testnets/shared/$(1)/recent-finalized-state.ssz \
--finalized-checkpoint-block=vendor/eth2-testnets/shared/$(1)/recent-finalized-block.ssz" ; }; \
$(CPU_LIMIT_CMD) build/beacon_node \ $(CPU_LIMIT_CMD) build/beacon_node \
--network=$(1) \ --network=$(1) \
--log-level="$(LOG_LEVEL)" \ --log-level="$(LOG_LEVEL)" \
--log-file=build/data/shared_$(1)_$(NODE_ID)/nbc_bn_$$(date +"%Y%m%d%H%M%S").log \ --log-file=build/data/shared_$(1)_$(NODE_ID)/nbc_bn_$$(date +"%Y%m%d%H%M%S").log \
--data-dir=build/data/shared_$(1)_$(NODE_ID) \ --data-dir=build/data/shared_$(1)_$(NODE_ID) \
$(GOERLI_TESTNETS_PARAMS) $(NODE_PARAMS) $$CHECKPOINT_PARAMS $(GOERLI_TESTNETS_PARAMS) $(NODE_PARAMS)
endef endef
define CONNECT_TO_NETWORK_IN_DEV_MODE define CONNECT_TO_NETWORK_IN_DEV_MODE
@ -293,6 +295,9 @@ medalla: | beacon_node signing_process
medalla-vc: | beacon_node signing_process validator_client medalla-vc: | beacon_node signing_process validator_client
$(call CONNECT_TO_NETWORK_WITH_VALIDATOR_CLIENT,medalla) $(call CONNECT_TO_NETWORK_WITH_VALIDATOR_CLIENT,medalla)
medalla-fast-sync: | beacon_node
$(call connect_to_network,medalla,FastSync)
ifneq ($(LOG_LEVEL), TRACE) ifneq ($(LOG_LEVEL), TRACE)
medalla-dev: medalla-dev:
+ "$(MAKE)" LOG_LEVEL=TRACE $@ + "$(MAKE)" LOG_LEVEL=TRACE $@

View File

@ -26,14 +26,19 @@ type
DbKeyKind = enum DbKeyKind = enum
kHashToState kHashToState
kHashToBlock kHashToBlock
kHeadBlock # Pointer to the most recent block selected by the fork choice kHeadBlock
kTailBlock ##\ ## Pointer to the most recent block selected by the fork choice
kTailBlock
## Pointer to the earliest finalized block - this is the genesis block when ## Pointer to the earliest finalized block - this is the genesis block when
## the chain starts, but might advance as the database gets pruned ## the chain starts, but might advance as the database gets pruned
## TODO: determine how aggressively the database should be pruned. For a ## TODO: determine how aggressively the database should be pruned. For a
## healthy network sync, we probably need to store blocks at least ## healthy network sync, we probably need to store blocks at least
## past the weak subjectivity period. ## past the weak subjectivity period.
kBlockSlotStateRoot ## BlockSlot -> state_root mapping kBlockSlotStateRoot
## BlockSlot -> state_root mapping
kGenesisBlockRoot
## Immutable reference to the network genesis state
## (needed for satisfying requests to the beacon node API).
const const
maxDecompressedDbRecordSize = 16*1024*1024 maxDecompressedDbRecordSize = 16*1024*1024
@ -165,6 +170,9 @@ proc putHeadBlock*(db: BeaconChainDB, key: Eth2Digest) =
proc putTailBlock*(db: BeaconChainDB, key: Eth2Digest) = proc putTailBlock*(db: BeaconChainDB, key: Eth2Digest) =
db.put(subkey(kTailBlock), key) db.put(subkey(kTailBlock), key)
proc putGenesisBlockRoot*(db: BeaconChainDB, key: Eth2Digest) =
db.put(subkey(kGenesisBlockRoot), key)
proc getBlock*(db: BeaconChainDB, key: Eth2Digest): Opt[TrustedSignedBeaconBlock] = proc getBlock*(db: BeaconChainDB, key: Eth2Digest): Opt[TrustedSignedBeaconBlock] =
# We only store blocks that we trust in the database # We only store blocks that we trust in the database
result.ok(TrustedSignedBeaconBlock()) result.ok(TrustedSignedBeaconBlock())
@ -207,6 +215,9 @@ proc getHeadBlock*(db: BeaconChainDB): Opt[Eth2Digest] =
proc getTailBlock*(db: BeaconChainDB): Opt[Eth2Digest] = proc getTailBlock*(db: BeaconChainDB): Opt[Eth2Digest] =
db.get(subkey(kTailBlock), Eth2Digest) db.get(subkey(kTailBlock), Eth2Digest)
proc getGenesisBlockRoot*(db: BeaconChainDB): Eth2Digest =
db.get(subkey(kGenesisBlockRoot), Eth2Digest).expect("The database must be seeded with the genesis state")
proc containsBlock*(db: BeaconChainDB, key: Eth2Digest): bool = proc containsBlock*(db: BeaconChainDB, key: Eth2Digest): bool =
db.backend.contains(subkey(SignedBeaconBlock, key)).expect("working database") db.backend.contains(subkey(SignedBeaconBlock, key)).expect("working database")

View File

@ -23,7 +23,7 @@ import
# Local modules # Local modules
spec/[datatypes, digest, crypto, beaconstate, helpers, network, presets], spec/[datatypes, digest, crypto, beaconstate, helpers, network, presets],
spec/state_transition, spec/[state_transition, weak_subjectivity],
conf, time, beacon_chain_db, validator_pool, extras, conf, time, beacon_chain_db, validator_pool, extras,
attestation_pool, exit_pool, eth2_network, eth2_discovery, attestation_pool, exit_pool, eth2_network, eth2_discovery,
beacon_node_common, beacon_node_types, beacon_node_status, beacon_node_common, beacon_node_types, beacon_node_status,
@ -36,7 +36,6 @@ import
./eth2_processor ./eth2_processor
const const
genesisFile* = "genesis.ssz"
hasPrompt = not defined(withoutPrompt) hasPrompt = not defined(withoutPrompt)
type type
@ -60,62 +59,6 @@ declareGauge ticks_delay,
logScope: topics = "beacnde" logScope: topics = "beacnde"
proc getStateFromSnapshot(conf: BeaconNodeConf, stateSnapshotContents: ref string): NilableBeaconStateRef =
var
genesisPath = conf.dataDir/genesisFile
snapshotContents: TaintedString
writeGenesisFile = false
if conf.stateSnapshot.isSome:
let
snapshotPath = conf.stateSnapshot.get.string
snapshotExt = splitFile(snapshotPath).ext
if cmpIgnoreCase(snapshotExt, ".ssz") != 0:
error "The supplied state snapshot must be a SSZ file",
suppliedPath = snapshotPath
quit 1
snapshotContents = readFile(snapshotPath)
if fileExists(genesisPath):
let genesisContents = readFile(genesisPath)
if snapshotContents != genesisContents:
error "Data directory not empty. Existing genesis state differs from supplied snapshot",
dataDir = conf.dataDir.string, snapshot = snapshotPath
quit 1
else:
debug "No previous genesis state. Importing snapshot",
genesisPath, dataDir = conf.dataDir.string
writeGenesisFile = true
genesisPath = snapshotPath
elif fileExists(genesisPath):
try: snapshotContents = readFile(genesisPath)
except CatchableError as err:
error "Failed to read genesis file", err = err.msg
quit 1
elif stateSnapshotContents != nil:
swap(snapshotContents, TaintedString stateSnapshotContents[])
else:
# No snapshot was provided. We should wait for genesis.
return nil
result = try:
newClone(SSZ.decode(snapshotContents, BeaconState))
except SerializationError:
error "Failed to import genesis file", path = genesisPath
quit 1
info "Loaded genesis state", path = genesisPath
if writeGenesisFile:
try:
notice "Writing genesis to data directory", path = conf.dataDir/genesisFile
writeFile(conf.dataDir/genesisFile, snapshotContents.string)
except CatchableError as err:
error "Failed to persist genesis file to data dir",
err = err.msg, genesisFile = conf.dataDir/genesisFile
quit 1
func enrForkIdFromState(state: BeaconState): ENRForkID = func enrForkIdFromState(state: BeaconState): ENRForkID =
let let
forkVer = state.fork.current_version forkVer = state.fork.current_version
@ -129,21 +72,59 @@ func enrForkIdFromState(state: BeaconState): ENRForkID =
proc init*(T: type BeaconNode, proc init*(T: type BeaconNode,
rng: ref BrHmacDrbgContext, rng: ref BrHmacDrbgContext,
conf: BeaconNodeConf, conf: BeaconNodeConf,
stateSnapshotContents: ref string): Future[BeaconNode] {.async.} = genesisStateContents: ref string): Future[BeaconNode] {.async.} =
let let
netKeys = getPersistentNetKeys(rng[], conf) netKeys = getPersistentNetKeys(rng[], conf)
nickname = if conf.nodeName == "auto": shortForm(netKeys) nickname = if conf.nodeName == "auto": shortForm(netKeys)
else: conf.nodeName else: conf.nodeName
db = BeaconChainDB.init(kvStore SqStoreRef.init(conf.databaseDir, "nbc").tryGet()) db = BeaconChainDB.init(kvStore SqStoreRef.init(conf.databaseDir, "nbc").tryGet())
var mainchainMonitor: MainchainMonitor var
mainchainMonitor: MainchainMonitor
genesisState, checkpointState: ref BeaconState
checkpointBlock: SignedBeaconBlock
if conf.finalizedCheckpointState.isSome:
let checkpointStatePath = conf.finalizedCheckpointState.get.string
checkpointState = try:
newClone(SSZ.loadFile(checkpointStatePath, BeaconState))
except SerializationError as err:
fatal "Checkpoint state deserialization failed",
err = formatMsg(err, checkpointStatePath)
quit 1
except CatchableError as err:
fatal "Failed to read checkpoint state file", err = err.msg
quit 1
if conf.finalizedCheckpointBlock.isNone:
if checkpointState.slot > 0:
fatal "Specifying a non-genesis --finalized-checkpoint-state requires specifying --finalized-checkpoint-block as well"
quit 1
else:
let checkpointBlockPath = conf.finalizedCheckpointBlock.get.string
try:
checkpointBlock = SSZ.loadFile(checkpointBlockPath, SignedBeaconBlock)
except SerializationError as err:
fatal "Invalid checkpoint block", err = err.formatMsg(checkpointBlockPath)
quit 1
except IOError as err:
fatal "Failed to load the checkpoint block", err = err.msg
quit 1
elif conf.finalizedCheckpointBlock.isSome:
# TODO We can download the state from somewhere in the future relying
# on the trusted `state_root` appearing in the checkpoint block.
fatal "--finalized-checkpoint-block cannot be specified without --finalized-checkpoint-state"
quit 1
if not ChainDAGRef.isInitialized(db): if not ChainDAGRef.isInitialized(db):
# Fresh start - need to load a genesis state from somewhere var
var genesisState = conf.getStateFromSnapshot(stateSnapshotContents) tailState: ref BeaconState
tailBlock: SignedBeaconBlock
# Try file from command line first if genesisStateContents == nil and checkpointState == nil:
if genesisState.isNil: # This is a fresh start without a known genesis state
# (most likely, it hasn't arrived yet). We'll try to
# obtain a genesis through the Eth1 deposits monitor:
if conf.web3Url.len == 0: if conf.web3Url.len == 0:
fatal "Web3 URL not specified" fatal "Web3 URL not specified"
quit 1 quit 1
@ -186,42 +167,68 @@ proc init*(T: type BeaconNode,
if bnStatus == BeaconNodeStatus.Stopping: if bnStatus == BeaconNodeStatus.Stopping:
return nil return nil
tailState = genesisState
tailBlock = get_initial_beacon_block(genesisState[])
notice "Eth2 genesis state detected", notice "Eth2 genesis state detected",
genesisTime = genesisState.genesisTime, genesisTime = genesisState.genesisTime,
eth1Block = genesisState.eth1_data.block_hash, eth1Block = genesisState.eth1_data.block_hash,
totalDeposits = genesisState.eth1_data.deposit_count totalDeposits = genesisState.eth1_data.deposit_count
# This is needed to prove the not nil property from here on elif genesisStateContents == nil:
if genesisState == nil: if checkpointState.slot == GENESIS_SLOT:
doAssert false genesisState = checkpointState
tailState = checkpointState
tailBlock = get_initial_beacon_block(genesisState[])
else: else:
if genesisState.slot != GENESIS_SLOT: fatal "State checkpoints cannot be provided for a network without a known genesis state"
# TODO how to get a block from a non-genesis state?
error "Starting from non-genesis state not supported",
stateSlot = genesisState.slot,
stateRoot = hash_tree_root(genesisState[])
quit 1 quit 1
else:
try:
genesisState = newClone(SSZ.decode(genesisStateContents[], BeaconState))
except CatchableError as err:
raiseAssert "The baked-in state must be valid"
let tailBlock = get_initial_beacon_block(genesisState[]) if checkpointState != nil:
tailState = checkpointState
tailBlock = checkpointBlock
else:
tailState = genesisState
tailBlock = get_initial_beacon_block(genesisState[])
try: try:
ChainDAGRef.preInit(db, genesisState[], tailBlock) ChainDAGRef.preInit(db, genesisState[], tailState[], tailBlock)
doAssert ChainDAGRef.isInitialized(db), "preInit should have initialized db" doAssert ChainDAGRef.isInitialized(db), "preInit should have initialized db"
except CatchableError as e: except CatchableError as e:
error "Failed to initialize database", err = e.msg error "Failed to initialize database", err = e.msg
quit 1 quit 1
if stateSnapshotContents != nil:
# The memory for the initial snapshot won't be needed anymore
stateSnapshotContents[] = ""
# TODO check that genesis given on command line (if any) matches database # TODO check that genesis given on command line (if any) matches database
let let
chainDagFlags = if conf.verifyFinalization: {verifyFinalization} chainDagFlags = if conf.verifyFinalization: {verifyFinalization}
else: {} else: {}
chainDag = init(ChainDAGRef, conf.runtimePreset, db, chainDagFlags) chainDag = init(ChainDAGRef, conf.runtimePreset, db, chainDagFlags)
beaconClock = BeaconClock.init(chainDag.headState.data.data)
quarantine = QuarantineRef() quarantine = QuarantineRef()
if conf.weakSubjectivityCheckpoint.isSome:
let
currentSlot = beaconClock.now.slotOrZero
isCheckpointStale = not is_within_weak_subjectivity_period(
currentSlot,
chainDag.headState.data.data,
conf.weakSubjectivityCheckpoint.get)
if isCheckpointStale:
error "Weak subjectivity checkpoint is stale",
currentSlot,
checkpoint = conf.weakSubjectivityCheckpoint.get,
headStateSlot = chainDag.headState.data.data.slot
quit 1
if checkpointState != nil:
chainDag.setTailState(checkpointState[], checkpointBlock)
if mainchainMonitor.isNil and if mainchainMonitor.isNil and
conf.web3Url.len > 0 and conf.web3Url.len > 0 and
conf.depositContractAddress.isSome: conf.depositContractAddress.isSome:
@ -259,7 +266,7 @@ proc init*(T: type BeaconNode,
attestationPool: attestationPool, attestationPool: attestationPool,
exitPool: exitPool, exitPool: exitPool,
mainchainMonitor: mainchainMonitor, mainchainMonitor: mainchainMonitor,
beaconClock: BeaconClock.init(chainDag.headState.data.data), beaconClock: beaconClock,
rpcServer: rpcServer, rpcServer: rpcServer,
forkDigest: enrForkId.forkDigest, forkDigest: enrForkId.forkDigest,
topicBeaconBlocks: topicBeaconBlocks, topicBeaconBlocks: topicBeaconBlocks,
@ -649,8 +656,7 @@ proc startSyncManager(node: BeaconNode) =
epoch.compute_start_slot_at_epoch() epoch.compute_start_slot_at_epoch()
func getFirstSlotAtFinalizedEpoch(): Slot = func getFirstSlotAtFinalizedEpoch(): Slot =
let fepoch = node.chainDag.headState.data.data.finalized_checkpoint.epoch node.chainDag.finalizedHead.slot
compute_start_slot_at_epoch(fepoch)
proc scoreCheck(peer: Peer): bool = proc scoreCheck(peer: Peer): bool =
if peer.score < PeerScoreLowLimit: if peer.score < PeerScoreLowLimit:
@ -1091,7 +1097,7 @@ programMain:
var var
config = makeBannerAndConfig(clientId, BeaconNodeConf) config = makeBannerAndConfig(clientId, BeaconNodeConf)
# This is ref so we can mutate it (to erase it) after the initial loading. # This is ref so we can mutate it (to erase it) after the initial loading.
stateSnapshotContents: ref string genesisStateContents: ref string
setupStdoutLogging(config.logLevel) setupStdoutLogging(config.logLevel)
@ -1110,8 +1116,8 @@ programMain:
for node in metadata.bootstrapNodes: for node in metadata.bootstrapNodes:
config.bootstrapNodes.add node config.bootstrapNodes.add node
if config.stateSnapshot.isNone and metadata.genesisData.len > 0: if metadata.genesisData.len > 0:
stateSnapshotContents = newClone metadata.genesisData genesisStateContents = newClone metadata.genesisData
template checkForIncompatibleOption(flagName, fieldName) = template checkForIncompatibleOption(flagName, fieldName) =
# TODO: This will have to be reworked slightly when we introduce config files. # TODO: This will have to be reworked slightly when we introduce config files.
@ -1224,9 +1230,11 @@ programMain:
address = metricsAddress, port = config.metricsPort address = metricsAddress, port = config.metricsPort
metrics.startHttpServer($metricsAddress, config.metricsPort) metrics.startHttpServer($metricsAddress, config.metricsPort)
var node = waitFor BeaconNode.init(rng, config, stateSnapshotContents) var node = waitFor BeaconNode.init(rng, config, genesisStateContents)
if bnStatus == BeaconNodeStatus.Stopping: if bnStatus == BeaconNodeStatus.Stopping:
return return
# The memory for the initial snapshot won't be needed anymore
if genesisStateContents != nil: genesisStateContents[] = ""
when hasPrompt: when hasPrompt:
initPrompt(node) initPrompt(node)

View File

@ -207,6 +207,12 @@ func shortLog*(v: BlockRef): string =
else: else:
&"{v.root.data.toOpenArray(0, 3).toHex()}:{v.slot}" &"{v.root.data.toOpenArray(0, 3).toHex()}:{v.slot}"
func shortLog*(v: EpochRef): string =
if v == nil:
"EpochRef(nil)"
else:
&"(epoch ref: {v.epoch})"
chronicles.formatIt BlockSlot: shortLog(it) chronicles.formatIt BlockSlot: shortLog(it)
chronicles.formatIt BlockRef: shortLog(it) chronicles.formatIt BlockRef: shortLog(it)

View File

@ -383,8 +383,12 @@ proc init*(T: type ChainDAGRef,
# state we loaded might be older than head block - nonetheless, it will be # state we loaded might be older than head block - nonetheless, it will be
# from the same epoch as the head, thus the finalized and justified slots are # from the same epoch as the head, thus the finalized and justified slots are
# the same - these only change on epoch boundaries. # the same - these only change on epoch boundaries.
res.finalizedHead = headRef.atEpochStart( # When we start from a snapshot state, the `finalized_checkpoint` in the
res.headState.data.data.finalized_checkpoint.epoch) # snapshot will point to an even older state, but we trust the tail state
# (the snapshot) to be finalized, hence the `max` expression below.
let finalizedEpoch = max(res.headState.data.data.finalized_checkpoint.epoch,
tailRef.slot.epoch)
res.finalizedHead = headRef.atEpochStart(finalizedEpoch)
res.clearanceState = res.headState res.clearanceState = res.headState
@ -398,6 +402,7 @@ proc init*(T: type ChainDAGRef,
proc findEpochRef*(blck: BlockRef, epoch: Epoch): EpochRef = # may return nil! proc findEpochRef*(blck: BlockRef, epoch: Epoch): EpochRef = # may return nil!
let ancestor = blck.epochAncestor(epoch) let ancestor = blck.epochAncestor(epoch)
doAssert ancestor.blck != nil
for epochRef in ancestor.blck.epochRefs: for epochRef in ancestor.blck.epochRefs:
if epochRef.epoch == epoch: if epochRef.epoch == epoch:
return epochRef return epochRef
@ -415,7 +420,8 @@ proc getEpochRef*(dag: ChainDAGRef, blck: BlockRef, epoch: Epoch): EpochRef =
dag.withState(dag.tmpState, ancestor): dag.withState(dag.tmpState, ancestor):
let let
prevEpochRef = blck.findEpochRef(epoch - 1) prevEpochRef = if dag.tail.slot.epoch >= epoch: nil
else: blck.findEpochRef(epoch - 1)
newEpochRef = EpochRef.init(state, cache, prevEpochRef) newEpochRef = EpochRef.init(state, cache, prevEpochRef)
# TODO consider constraining the number of epochrefs per state # TODO consider constraining the number of epochrefs per state
@ -512,14 +518,18 @@ func getBlockRange*(
## at this index. ## at this index.
## ##
## If there were no blocks in the range, `output.len` will be returned. ## If there were no blocks in the range, `output.len` will be returned.
let requestedCount = output.lenu64 let
requestedCount = output.lenu64
headSlot = dag.head.slot
trace "getBlockRange entered", trace "getBlockRange entered",
head = shortLog(dag.head.root), requestedCount, startSlot, skipStep head = shortLog(dag.head.root), requestedCount, startSlot, skipStep, headSlot
if startSlot < dag.tail.slot or headSlot <= startSlot:
return output.len # Identical to returning an empty set of block as indicated above
let let
headSlot = dag.head.slot runway = uint64(headSlot - startSlot)
runway = if headSlot > startSlot: uint64(headSlot - startSlot)
else: return output.len # Identical to returning an empty set of block as indicated above
skipStep = max(skipStep, 1) # Treat 0 step as 1 skipStep = max(skipStep, 1) # Treat 0 step as 1
count = min(1'u64 + (runway div skipStep), requestedCount) count = min(1'u64 + (runway div skipStep), requestedCount)
endSlot = startSlot + count * skipStep endSlot = startSlot + count * skipStep
@ -702,7 +712,7 @@ proc updateHead*(
## blocks that were once considered potential candidates for a tree will ## blocks that were once considered potential candidates for a tree will
## now fall from grace, or no longer be considered resolved. ## now fall from grace, or no longer be considered resolved.
doAssert not newHead.isNil() doAssert not newHead.isNil()
doAssert not newHead.parent.isNil() or newHead.slot == 0 doAssert not newHead.parent.isNil() or newHead.slot <= dag.tail.slot
logScope: logScope:
newHead = shortLog(newHead) newHead = shortLog(newHead)
@ -843,25 +853,56 @@ proc isInitialized*(T: type ChainDAGRef, db: BeaconChainDB): bool =
true true
proc preInit*( proc preInit*(
T: type ChainDAGRef, db: BeaconChainDB, state: BeaconState, T: type ChainDAGRef, db: BeaconChainDB,
signedBlock: SignedBeaconBlock) = genesisState, tailState: BeaconState, tailBlock: SignedBeaconBlock) =
# write a genesis state, the way the ChainDAGRef expects it to be stored in # write a genesis state, the way the ChainDAGRef expects it to be stored in
# database # database
# TODO probably should just init a block pool with the freshly written # TODO probably should just init a block pool with the freshly written
# state - but there's more refactoring needed to make it nice - doing # state - but there's more refactoring needed to make it nice - doing
# a minimal patch for now.. # a minimal patch for now..
doAssert signedBlock.message.state_root == hash_tree_root(state) doAssert tailBlock.message.state_root == hash_tree_root(tailState)
notice "New database from snapshot", notice "New database from snapshot",
blockRoot = shortLog(signedBlock.root), blockRoot = shortLog(tailBlock.root),
stateRoot = shortLog(signedBlock.message.state_root), stateRoot = shortLog(tailBlock.message.state_root),
fork = state.fork, fork = tailState.fork,
validators = state.validators.len() validators = tailState.validators.len()
db.putState(state) db.putState(tailState)
db.putBlock(signedBlock) db.putBlock(tailBlock)
db.putTailBlock(signedBlock.root) db.putTailBlock(tailBlock.root)
db.putHeadBlock(signedBlock.root) db.putHeadBlock(tailBlock.root)
db.putStateRoot(signedBlock.root, state.slot, signedBlock.message.state_root) db.putStateRoot(tailBlock.root, tailState.slot, tailBlock.message.state_root)
if tailState.slot == GENESIS_SLOT:
db.putGenesisBlockRoot(tailBlock.root)
else:
doAssert genesisState.slot == GENESIS_SLOT
db.putState(genesisState)
let genesisBlock = get_initial_beacon_block(genesisState)
db.putBlock(genesisBlock)
db.putStateRoot(genesisBlock.root, GENESIS_SLOT, genesisBlock.message.state_root)
db.putGenesisBlockRoot(genesisBlock.root)
proc setTailState*(dag: ChainDAGRef,
checkpointState: BeaconState,
checkpointBlock: SignedBeaconBlock) =
# TODO
# Delete all records up to the tail node. If the tail node is not
# in the database, init the dabase in a way similar to `preInit`.
discard
proc getGenesisBlockData*(dag: ChainDAGRef): BlockData =
let
genesisBlockRoot = dag.db.getGenesisBlockRoot()
# The database should be seeded with the genesis block root by `preInit`:
genesisBlock = dag.db.getBlock(genesisBlockRoot).get()
BlockData(data: genesisBlock,
refs: BlockRef(root: genesisBlockRoot, slot: GENESIS_SLOT))
proc getGenesisBlockSlot*(dag: ChainDAGRef): BlockSlot =
let blockData = dag.getGenesisBlockData()
BlockSlot(blck: blockData.refs, slot: GENESIS_SLOT)
proc getProposer*( proc getProposer*(
dag: ChainDAGRef, head: BlockRef, slot: Slot): dag: ChainDAGRef, head: BlockRef, slot: Slot):

View File

@ -4,10 +4,10 @@ import
os, options, unicode, os, options, unicode,
chronicles, chronicles/options as chroniclesOptions, chronicles, chronicles/options as chroniclesOptions,
confutils, confutils/defs, confutils/std/net, stew/shims/net as stewNet, confutils, confutils/defs, confutils/std/net, stew/shims/net as stewNet,
unicodedb/properties, normalize, stew/io2, unicodedb/properties, normalize,
json_serialization, web3/[ethtypes, confutils_defs], json_serialization, web3/[ethtypes, confutils_defs],
spec/[crypto, keystore, digest, datatypes, network], network_metadata, spec/[crypto, keystore, digest, datatypes, network, weak_subjectivity],
stew/io2 network_metadata
export export
defaultEth2TcpPort, enabledLogLevel, ValidIpAddress, defaultEth2TcpPort, enabledLogLevel, ValidIpAddress,
@ -142,13 +142,17 @@ type
abbr: "v" abbr: "v"
name: "validator" }: seq[ValidatorKeyPath] name: "validator" }: seq[ValidatorKeyPath]
stateSnapshot* {. weakSubjectivityCheckpoint* {.
desc: "SSZ file specifying a recent state snapshot" desc: "Weak subjectivity checkpoint in the format block_root:epoch_number"
abbr: "s" name: "weak-subjectivity-checkpoint" }: Option[WeakSubjectivityCheckpoint]
name: "state-snapshot" }: Option[InputFile]
stateSnapshotContents* {.hidden.}: ref string finalizedCheckpointState* {.
# This is ref so we can mutate it (to erase it) after the initial loading. desc: "SSZ file specifying a recent finalized state"
name: "finalized-checkpoint-state" }: Option[InputFile]
finalizedCheckpointBlock* {.
desc: "SSZ file specifying a recent finalized block"
name: "finalized-checkpoint-block" }: Option[InputFile]
runtimePreset* {.hidden.}: RuntimePreset runtimePreset* {.hidden.}: RuntimePreset
@ -459,6 +463,13 @@ func parseCmdArg*(T: type GraffitiBytes, input: TaintedString): T
func completeCmdArg*(T: type GraffitiBytes, input: TaintedString): seq[string] = func completeCmdArg*(T: type GraffitiBytes, input: TaintedString): seq[string] =
return @[] return @[]
func parseCmdArg*(T: type WeakSubjectivityCheckpoint, input: TaintedString): T
{.raises: [ValueError, Defect].} =
init(T, input)
func completeCmdArg*(T: type WeakSubjectivityCheckpoint, input: TaintedString): seq[string] =
return @[]
proc isPrintable(rune: Rune): bool = proc isPrintable(rune: Rune): bool =
# This can be eventually replaced by the `unicodeplus` package, but a single # This can be eventually replaced by the `unicodeplus` package, but a single
# proc does not justify the extra dependencies at the moment: # proc does not justify the extra dependencies at the moment:

View File

@ -304,15 +304,17 @@ func is_valid_genesis_state*(preset: RuntimePreset,
return false return false
true true
func emptyBeaconBlockBody*(): BeaconBlockBody =
# TODO: This shouldn't be necessary if OpaqueBlob is the default
BeaconBlockBody(randao_reveal: ValidatorSig(kind: OpaqueBlob))
# TODO this is now a non-spec helper function, and it's not really accurate # TODO this is now a non-spec helper function, and it's not really accurate
# so only usable/used in research/ and tests/ # so only usable/used in research/ and tests/
func get_initial_beacon_block*(state: BeaconState): SignedBeaconBlock = func get_initial_beacon_block*(state: BeaconState): SignedBeaconBlock =
let message = BeaconBlock( let message = BeaconBlock(
slot: GENESIS_SLOT, slot: state.slot,
state_root: hash_tree_root(state), state_root: hash_tree_root(state),
body: BeaconBlockBody( body: emptyBeaconBlockBody())
# TODO: This shouldn't be necessary if OpaqueBlob is the default
randao_reveal: ValidatorSig(kind: OpaqueBlob)))
# parent_root, randao_reveal, eth1_data, signature, and body automatically # parent_root, randao_reveal, eth1_data, signature, and body automatically
# initialized to default values. # initialized to default values.
SignedBeaconBlock(message: message, root: hash_tree_root(message)) SignedBeaconBlock(message: message, root: hash_tree_root(message))

View File

@ -64,6 +64,11 @@ func get_active_validator_indices*(state: BeaconState, epoch: Epoch):
if is_active_validator(val, epoch): if is_active_validator(val, epoch):
result.add idx.ValidatorIndex result.add idx.ValidatorIndex
func get_active_validator_indices_len*(state: BeaconState, epoch: Epoch): uint64 =
for idx, val in state.validators:
if is_active_validator(val, epoch):
inc result
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.3/specs/phase0/beacon-chain.md#get_current_epoch # https://github.com/ethereum/eth2.0-specs/blob/v0.12.3/specs/phase0/beacon-chain.md#get_current_epoch
func get_current_epoch*(state: BeaconState): Epoch = func get_current_epoch*(state: BeaconState): Epoch =
## Return the current epoch. ## Return the current epoch.

View File

@ -0,0 +1,48 @@
import
strutils,
datatypes, digest, helpers
{.push raises: [Defect].}
const
SAFETY_DECAY* = 10'u64
type
WeakSubjectivityCheckpoint* = object
root*: Eth2Digest
epoch*: Epoch
func init*(T: type WeakSubjectivityCheckpoint, str: TaintedString): T
{.raises: [ValueError, Defect].} =
let sepIdx = find(str.string, ':')
if sepIdx == -1:
raise newException(ValueError,
"The weak subjectivity checkpoint must be provided in the `block_root:epoch_number` format")
T(root: Eth2Digest.fromHex(str[0 ..< sepIdx]),
epoch: parseBiggestUInt(str[sepIdx .. ^1]).Epoch)
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.3/specs/phase0/weak-subjectivity.md#calculating-the-weak-subjectivity-period
func compute_weak_subjectivity_period*(state: BeaconState): uint64 =
var weak_subjectivity_period = MIN_VALIDATOR_WITHDRAWABILITY_DELAY
let validator_count = get_active_validator_indices_len(state, get_current_epoch(state))
if validator_count >= MIN_PER_EPOCH_CHURN_LIMIT * CHURN_LIMIT_QUOTIENT:
weak_subjectivity_period += SAFETY_DECAY * CHURN_LIMIT_QUOTIENT div (2 * 100)
else:
weak_subjectivity_period += SAFETY_DECAY * validator_count div (2 * 100 * MIN_PER_EPOCH_CHURN_LIMIT)
return weak_subjectivity_period
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.3/specs/phase0/weak-subjectivity.md#checking-for-stale-weak-subjectivity-checkpoint
func is_within_weak_subjectivity_period*(current_slot: Slot,
ws_state: BeaconState,
ws_checkpoint: WeakSubjectivityCheckpoint): bool =
# Clients may choose to validate the input state against the input Weak Subjectivity Checkpoint
doAssert ws_state.latest_block_header.state_root == ws_checkpoint.root
doAssert compute_epoch_at_slot(ws_state.slot) == ws_checkpoint.epoch
let
ws_period = compute_weak_subjectivity_period(ws_state)
ws_state_epoch = compute_epoch_at_slot(ws_state.slot)
current_epoch = compute_epoch_at_slot(current_slot)
current_epoch <= ws_state_epoch + ws_period

View File

@ -142,7 +142,7 @@ proc getBlockDataFromBlockId(node: BeaconNode, blockId: string): BlockData =
of "head": of "head":
node.chainDag.get(node.chainDag.head) node.chainDag.get(node.chainDag.head)
of "genesis": of "genesis":
node.chainDag.get(node.chainDag.tail) node.chainDag.getGenesisBlockData()
of "finalized": of "finalized":
node.chainDag.get(node.chainDag.finalizedHead.blck) node.chainDag.get(node.chainDag.finalizedHead.blck)
else: else:
@ -163,7 +163,7 @@ proc stateIdToBlockSlot(node: BeaconNode, stateId: string): BlockSlot =
of "head": of "head":
node.chainDag.head.toBlockSlot() node.chainDag.head.toBlockSlot()
of "genesis": of "genesis":
node.chainDag.tail.toBlockSlot() node.chainDag.getGenesisBlockSlot()
of "finalized": of "finalized":
node.chainDag.finalizedHead node.chainDag.finalizedHead
of "justified": of "justified":

View File

@ -54,7 +54,7 @@ cli do(slots = SLOTS_PER_EPOCH * 6,
db = BeaconChainDB.init(kvStore SqStoreRef.init(".", "block_sim").tryGet()) db = BeaconChainDB.init(kvStore SqStoreRef.init(".", "block_sim").tryGet())
defer: db.close() defer: db.close()
ChainDAGRef.preInit(db, state[].data, genesisBlock) ChainDAGRef.preInit(db, state[].data, state[].data, genesisBlock)
var var
chainDag = init(ChainDAGRef, defaultRuntimePreset, db) chainDag = init(ChainDAGRef, defaultRuntimePreset, db)

View File

@ -225,7 +225,7 @@ if [[ $USE_GANACHE == "0" ]]; then
--insecure-netkey-password=true \ --insecure-netkey-password=true \
--genesis-offset=${GENESIS_OFFSET} # Delay in seconds --genesis-offset=${GENESIS_OFFSET} # Delay in seconds
STATE_SNAPSHOT_ARG="--state-snapshot=${NETWORK_DIR}/genesis.ssz" STATE_SNAPSHOT_ARG="--finalized-checkpoint-state=${NETWORK_DIR}/genesis.ssz"
else else
echo "Launching ganache" echo "Launching ganache"
ganache-cli --blockTime 17 --gasLimit 100000000 -e 100000 --verbose > "${DATA_DIR}/log_ganache.txt" 2>&1 & ganache-cli --blockTime 17 --gasLimit 100000000 -e 100000 --verbose > "${DATA_DIR}/log_ganache.txt" 2>&1 &

View File

@ -78,7 +78,7 @@ mkdir -p "$NODE_DATA_DIR/dump"
SNAPSHOT_ARG="" SNAPSHOT_ARG=""
if [ -f "${SNAPSHOT_FILE}" ]; then if [ -f "${SNAPSHOT_FILE}" ]; then
SNAPSHOT_ARG="--state-snapshot=${SNAPSHOT_FILE}" SNAPSHOT_ARG="--finalized-checkpoint-state=${SNAPSHOT_FILE}"
fi fi
cd "$NODE_DATA_DIR" cd "$NODE_DATA_DIR"

View File

@ -99,7 +99,7 @@ template timedTest*(name, body) =
proc makeTestDB*(tailState: BeaconState, tailBlock: SignedBeaconBlock): BeaconChainDB = proc makeTestDB*(tailState: BeaconState, tailBlock: SignedBeaconBlock): BeaconChainDB =
result = init(BeaconChainDB, kvStore MemStoreRef.init()) result = init(BeaconChainDB, kvStore MemStoreRef.init())
ChainDAGRef.preInit(result, tailState, tailBlock) ChainDAGRef.preInit(result, tailState, tailState, tailBlock)
proc makeTestDB*(validators: Natural): BeaconChainDB = proc makeTestDB*(validators: Natural): BeaconChainDB =
let let

@ -1 +1 @@
Subproject commit f04512f15adccfc01b68f15c6216d681d08a1f83 Subproject commit d4e434148b851ef5baecbd289ac9df6c4fb62d47

2
vendor/nim-eth vendored

@ -1 +1 @@
Subproject commit 3ddb498f2a41e1e470d780757faeedc0b8cb3a21 Subproject commit de2d43a7e7afb7b094ca251bdbbd58fbf47df031