mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-01-23 13:00:34 +00:00
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:
parent
dc428e00db
commit
aed291128a
7
Makefile
7
Makefile
@ -200,12 +200,14 @@ define CONNECT_TO_NETWORK
|
||||
--base-metrics-port $$(($(BASE_METRICS_PORT) + $(NODE_ID))) \
|
||||
--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 \
|
||||
--network=$(1) \
|
||||
--log-level="$(LOG_LEVEL)" \
|
||||
--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) \
|
||||
$(GOERLI_TESTNETS_PARAMS) $(NODE_PARAMS)
|
||||
$$CHECKPOINT_PARAMS $(GOERLI_TESTNETS_PARAMS) $(NODE_PARAMS)
|
||||
endef
|
||||
|
||||
define CONNECT_TO_NETWORK_IN_DEV_MODE
|
||||
@ -293,6 +295,9 @@ medalla: | beacon_node signing_process
|
||||
medalla-vc: | beacon_node signing_process validator_client
|
||||
$(call CONNECT_TO_NETWORK_WITH_VALIDATOR_CLIENT,medalla)
|
||||
|
||||
medalla-fast-sync: | beacon_node
|
||||
$(call connect_to_network,medalla,FastSync)
|
||||
|
||||
ifneq ($(LOG_LEVEL), TRACE)
|
||||
medalla-dev:
|
||||
+ "$(MAKE)" LOG_LEVEL=TRACE $@
|
||||
|
@ -26,14 +26,19 @@ type
|
||||
DbKeyKind = enum
|
||||
kHashToState
|
||||
kHashToBlock
|
||||
kHeadBlock # Pointer to the most recent block selected by the fork choice
|
||||
kTailBlock ##\
|
||||
## Pointer to the earliest finalized block - this is the genesis block when
|
||||
## the chain starts, but might advance as the database gets pruned
|
||||
## TODO: determine how aggressively the database should be pruned. For a
|
||||
## healthy network sync, we probably need to store blocks at least
|
||||
## past the weak subjectivity period.
|
||||
kBlockSlotStateRoot ## BlockSlot -> state_root mapping
|
||||
kHeadBlock
|
||||
## Pointer to the most recent block selected by the fork choice
|
||||
kTailBlock
|
||||
## Pointer to the earliest finalized block - this is the genesis block when
|
||||
## the chain starts, but might advance as the database gets pruned
|
||||
## TODO: determine how aggressively the database should be pruned. For a
|
||||
## healthy network sync, we probably need to store blocks at least
|
||||
## past the weak subjectivity period.
|
||||
kBlockSlotStateRoot
|
||||
## BlockSlot -> state_root mapping
|
||||
kGenesisBlockRoot
|
||||
## Immutable reference to the network genesis state
|
||||
## (needed for satisfying requests to the beacon node API).
|
||||
|
||||
const
|
||||
maxDecompressedDbRecordSize = 16*1024*1024
|
||||
@ -165,6 +170,9 @@ proc putHeadBlock*(db: BeaconChainDB, key: Eth2Digest) =
|
||||
proc putTailBlock*(db: BeaconChainDB, key: Eth2Digest) =
|
||||
db.put(subkey(kTailBlock), key)
|
||||
|
||||
proc putGenesisBlockRoot*(db: BeaconChainDB, key: Eth2Digest) =
|
||||
db.put(subkey(kGenesisBlockRoot), key)
|
||||
|
||||
proc getBlock*(db: BeaconChainDB, key: Eth2Digest): Opt[TrustedSignedBeaconBlock] =
|
||||
# We only store blocks that we trust in the database
|
||||
result.ok(TrustedSignedBeaconBlock())
|
||||
@ -207,6 +215,9 @@ proc getHeadBlock*(db: BeaconChainDB): Opt[Eth2Digest] =
|
||||
proc getTailBlock*(db: BeaconChainDB): Opt[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 =
|
||||
db.backend.contains(subkey(SignedBeaconBlock, key)).expect("working database")
|
||||
|
||||
|
@ -23,7 +23,7 @@ import
|
||||
|
||||
# Local modules
|
||||
spec/[datatypes, digest, crypto, beaconstate, helpers, network, presets],
|
||||
spec/state_transition,
|
||||
spec/[state_transition, weak_subjectivity],
|
||||
conf, time, beacon_chain_db, validator_pool, extras,
|
||||
attestation_pool, exit_pool, eth2_network, eth2_discovery,
|
||||
beacon_node_common, beacon_node_types, beacon_node_status,
|
||||
@ -36,7 +36,6 @@ import
|
||||
./eth2_processor
|
||||
|
||||
const
|
||||
genesisFile* = "genesis.ssz"
|
||||
hasPrompt = not defined(withoutPrompt)
|
||||
|
||||
type
|
||||
@ -60,62 +59,6 @@ declareGauge ticks_delay,
|
||||
|
||||
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 =
|
||||
let
|
||||
forkVer = state.fork.current_version
|
||||
@ -129,21 +72,59 @@ func enrForkIdFromState(state: BeaconState): ENRForkID =
|
||||
proc init*(T: type BeaconNode,
|
||||
rng: ref BrHmacDrbgContext,
|
||||
conf: BeaconNodeConf,
|
||||
stateSnapshotContents: ref string): Future[BeaconNode] {.async.} =
|
||||
genesisStateContents: ref string): Future[BeaconNode] {.async.} =
|
||||
let
|
||||
netKeys = getPersistentNetKeys(rng[], conf)
|
||||
nickname = if conf.nodeName == "auto": shortForm(netKeys)
|
||||
else: conf.nodeName
|
||||
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):
|
||||
# Fresh start - need to load a genesis state from somewhere
|
||||
var genesisState = conf.getStateFromSnapshot(stateSnapshotContents)
|
||||
var
|
||||
tailState: ref BeaconState
|
||||
tailBlock: SignedBeaconBlock
|
||||
|
||||
# Try file from command line first
|
||||
if genesisState.isNil:
|
||||
if genesisStateContents == nil and checkpointState == nil:
|
||||
# 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:
|
||||
fatal "Web3 URL not specified"
|
||||
quit 1
|
||||
@ -186,42 +167,68 @@ proc init*(T: type BeaconNode,
|
||||
if bnStatus == BeaconNodeStatus.Stopping:
|
||||
return nil
|
||||
|
||||
tailState = genesisState
|
||||
tailBlock = get_initial_beacon_block(genesisState[])
|
||||
|
||||
notice "Eth2 genesis state detected",
|
||||
genesisTime = genesisState.genesisTime,
|
||||
eth1Block = genesisState.eth1_data.block_hash,
|
||||
totalDeposits = genesisState.eth1_data.deposit_count
|
||||
|
||||
# This is needed to prove the not nil property from here on
|
||||
if genesisState == nil:
|
||||
doAssert false
|
||||
elif genesisStateContents == nil:
|
||||
if checkpointState.slot == GENESIS_SLOT:
|
||||
genesisState = checkpointState
|
||||
tailState = checkpointState
|
||||
tailBlock = get_initial_beacon_block(genesisState[])
|
||||
else:
|
||||
fatal "State checkpoints cannot be provided for a network without a known genesis state"
|
||||
quit 1
|
||||
else:
|
||||
if genesisState.slot != GENESIS_SLOT:
|
||||
# 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
|
||||
|
||||
let tailBlock = get_initial_beacon_block(genesisState[])
|
||||
|
||||
try:
|
||||
ChainDAGRef.preInit(db, genesisState[], tailBlock)
|
||||
doAssert ChainDAGRef.isInitialized(db), "preInit should have initialized db"
|
||||
except CatchableError as e:
|
||||
error "Failed to initialize database", err = e.msg
|
||||
quit 1
|
||||
genesisState = newClone(SSZ.decode(genesisStateContents[], BeaconState))
|
||||
except CatchableError as err:
|
||||
raiseAssert "The baked-in state must be valid"
|
||||
|
||||
if stateSnapshotContents != nil:
|
||||
# The memory for the initial snapshot won't be needed anymore
|
||||
stateSnapshotContents[] = ""
|
||||
if checkpointState != nil:
|
||||
tailState = checkpointState
|
||||
tailBlock = checkpointBlock
|
||||
else:
|
||||
tailState = genesisState
|
||||
tailBlock = get_initial_beacon_block(genesisState[])
|
||||
|
||||
try:
|
||||
ChainDAGRef.preInit(db, genesisState[], tailState[], tailBlock)
|
||||
doAssert ChainDAGRef.isInitialized(db), "preInit should have initialized db"
|
||||
except CatchableError as e:
|
||||
error "Failed to initialize database", err = e.msg
|
||||
quit 1
|
||||
|
||||
# TODO check that genesis given on command line (if any) matches database
|
||||
let
|
||||
chainDagFlags = if conf.verifyFinalization: {verifyFinalization}
|
||||
else: {}
|
||||
chainDag = init(ChainDAGRef, conf.runtimePreset, db, chainDagFlags)
|
||||
beaconClock = BeaconClock.init(chainDag.headState.data.data)
|
||||
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
|
||||
conf.web3Url.len > 0 and
|
||||
conf.depositContractAddress.isSome:
|
||||
@ -259,7 +266,7 @@ proc init*(T: type BeaconNode,
|
||||
attestationPool: attestationPool,
|
||||
exitPool: exitPool,
|
||||
mainchainMonitor: mainchainMonitor,
|
||||
beaconClock: BeaconClock.init(chainDag.headState.data.data),
|
||||
beaconClock: beaconClock,
|
||||
rpcServer: rpcServer,
|
||||
forkDigest: enrForkId.forkDigest,
|
||||
topicBeaconBlocks: topicBeaconBlocks,
|
||||
@ -649,8 +656,7 @@ proc startSyncManager(node: BeaconNode) =
|
||||
epoch.compute_start_slot_at_epoch()
|
||||
|
||||
func getFirstSlotAtFinalizedEpoch(): Slot =
|
||||
let fepoch = node.chainDag.headState.data.data.finalized_checkpoint.epoch
|
||||
compute_start_slot_at_epoch(fepoch)
|
||||
node.chainDag.finalizedHead.slot
|
||||
|
||||
proc scoreCheck(peer: Peer): bool =
|
||||
if peer.score < PeerScoreLowLimit:
|
||||
@ -1091,7 +1097,7 @@ programMain:
|
||||
var
|
||||
config = makeBannerAndConfig(clientId, BeaconNodeConf)
|
||||
# This is ref so we can mutate it (to erase it) after the initial loading.
|
||||
stateSnapshotContents: ref string
|
||||
genesisStateContents: ref string
|
||||
|
||||
setupStdoutLogging(config.logLevel)
|
||||
|
||||
@ -1110,8 +1116,8 @@ programMain:
|
||||
for node in metadata.bootstrapNodes:
|
||||
config.bootstrapNodes.add node
|
||||
|
||||
if config.stateSnapshot.isNone and metadata.genesisData.len > 0:
|
||||
stateSnapshotContents = newClone metadata.genesisData
|
||||
if metadata.genesisData.len > 0:
|
||||
genesisStateContents = newClone metadata.genesisData
|
||||
|
||||
template checkForIncompatibleOption(flagName, fieldName) =
|
||||
# TODO: This will have to be reworked slightly when we introduce config files.
|
||||
@ -1224,9 +1230,11 @@ programMain:
|
||||
address = metricsAddress, port = 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:
|
||||
return
|
||||
# The memory for the initial snapshot won't be needed anymore
|
||||
if genesisStateContents != nil: genesisStateContents[] = ""
|
||||
|
||||
when hasPrompt:
|
||||
initPrompt(node)
|
||||
|
@ -207,6 +207,12 @@ func shortLog*(v: BlockRef): string =
|
||||
else:
|
||||
&"{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 BlockRef: shortLog(it)
|
||||
|
||||
|
@ -383,8 +383,12 @@ proc init*(T: type ChainDAGRef,
|
||||
# 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
|
||||
# the same - these only change on epoch boundaries.
|
||||
res.finalizedHead = headRef.atEpochStart(
|
||||
res.headState.data.data.finalized_checkpoint.epoch)
|
||||
# When we start from a snapshot state, the `finalized_checkpoint` in the
|
||||
# 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
|
||||
|
||||
@ -398,6 +402,7 @@ proc init*(T: type ChainDAGRef,
|
||||
|
||||
proc findEpochRef*(blck: BlockRef, epoch: Epoch): EpochRef = # may return nil!
|
||||
let ancestor = blck.epochAncestor(epoch)
|
||||
doAssert ancestor.blck != nil
|
||||
for epochRef in ancestor.blck.epochRefs:
|
||||
if epochRef.epoch == epoch:
|
||||
return epochRef
|
||||
@ -405,8 +410,8 @@ proc findEpochRef*(blck: BlockRef, epoch: Epoch): EpochRef = # may return nil!
|
||||
proc getEpochRef*(dag: ChainDAGRef, blck: BlockRef, epoch: Epoch): EpochRef =
|
||||
let epochRef = blck.findEpochRef(epoch)
|
||||
if epochRef != nil:
|
||||
beacon_state_data_cache_hits.inc
|
||||
return epochRef
|
||||
beacon_state_data_cache_hits.inc
|
||||
return epochRef
|
||||
|
||||
beacon_state_data_cache_misses.inc
|
||||
|
||||
@ -415,7 +420,8 @@ proc getEpochRef*(dag: ChainDAGRef, blck: BlockRef, epoch: Epoch): EpochRef =
|
||||
|
||||
dag.withState(dag.tmpState, ancestor):
|
||||
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)
|
||||
|
||||
# TODO consider constraining the number of epochrefs per state
|
||||
@ -512,14 +518,18 @@ func getBlockRange*(
|
||||
## at this index.
|
||||
##
|
||||
## 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",
|
||||
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
|
||||
headSlot = dag.head.slot
|
||||
runway = if headSlot > startSlot: uint64(headSlot - startSlot)
|
||||
else: return output.len # Identical to returning an empty set of block as indicated above
|
||||
runway = uint64(headSlot - startSlot)
|
||||
skipStep = max(skipStep, 1) # Treat 0 step as 1
|
||||
count = min(1'u64 + (runway div skipStep), requestedCount)
|
||||
endSlot = startSlot + count * skipStep
|
||||
@ -702,7 +712,7 @@ proc updateHead*(
|
||||
## blocks that were once considered potential candidates for a tree will
|
||||
## now fall from grace, or no longer be considered resolved.
|
||||
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:
|
||||
newHead = shortLog(newHead)
|
||||
|
||||
@ -843,25 +853,56 @@ proc isInitialized*(T: type ChainDAGRef, db: BeaconChainDB): bool =
|
||||
true
|
||||
|
||||
proc preInit*(
|
||||
T: type ChainDAGRef, db: BeaconChainDB, state: BeaconState,
|
||||
signedBlock: SignedBeaconBlock) =
|
||||
T: type ChainDAGRef, db: BeaconChainDB,
|
||||
genesisState, tailState: BeaconState, tailBlock: SignedBeaconBlock) =
|
||||
# write a genesis state, the way the ChainDAGRef expects it to be stored in
|
||||
# database
|
||||
# TODO probably should just init a block pool with the freshly written
|
||||
# state - but there's more refactoring needed to make it nice - doing
|
||||
# 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",
|
||||
blockRoot = shortLog(signedBlock.root),
|
||||
stateRoot = shortLog(signedBlock.message.state_root),
|
||||
fork = state.fork,
|
||||
validators = state.validators.len()
|
||||
blockRoot = shortLog(tailBlock.root),
|
||||
stateRoot = shortLog(tailBlock.message.state_root),
|
||||
fork = tailState.fork,
|
||||
validators = tailState.validators.len()
|
||||
|
||||
db.putState(state)
|
||||
db.putBlock(signedBlock)
|
||||
db.putTailBlock(signedBlock.root)
|
||||
db.putHeadBlock(signedBlock.root)
|
||||
db.putStateRoot(signedBlock.root, state.slot, signedBlock.message.state_root)
|
||||
db.putState(tailState)
|
||||
db.putBlock(tailBlock)
|
||||
db.putTailBlock(tailBlock.root)
|
||||
db.putHeadBlock(tailBlock.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*(
|
||||
dag: ChainDAGRef, head: BlockRef, slot: Slot):
|
||||
|
@ -4,10 +4,10 @@ import
|
||||
os, options, unicode,
|
||||
chronicles, chronicles/options as chroniclesOptions,
|
||||
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],
|
||||
spec/[crypto, keystore, digest, datatypes, network], network_metadata,
|
||||
stew/io2
|
||||
spec/[crypto, keystore, digest, datatypes, network, weak_subjectivity],
|
||||
network_metadata
|
||||
|
||||
export
|
||||
defaultEth2TcpPort, enabledLogLevel, ValidIpAddress,
|
||||
@ -142,13 +142,17 @@ type
|
||||
abbr: "v"
|
||||
name: "validator" }: seq[ValidatorKeyPath]
|
||||
|
||||
stateSnapshot* {.
|
||||
desc: "SSZ file specifying a recent state snapshot"
|
||||
abbr: "s"
|
||||
name: "state-snapshot" }: Option[InputFile]
|
||||
weakSubjectivityCheckpoint* {.
|
||||
desc: "Weak subjectivity checkpoint in the format block_root:epoch_number"
|
||||
name: "weak-subjectivity-checkpoint" }: Option[WeakSubjectivityCheckpoint]
|
||||
|
||||
stateSnapshotContents* {.hidden.}: ref string
|
||||
# This is ref so we can mutate it (to erase it) after the initial loading.
|
||||
finalizedCheckpointState* {.
|
||||
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
|
||||
|
||||
@ -459,6 +463,13 @@ func parseCmdArg*(T: type GraffitiBytes, input: TaintedString): T
|
||||
func completeCmdArg*(T: type GraffitiBytes, input: TaintedString): seq[string] =
|
||||
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 =
|
||||
# This can be eventually replaced by the `unicodeplus` package, but a single
|
||||
# proc does not justify the extra dependencies at the moment:
|
||||
|
@ -304,17 +304,19 @@ func is_valid_genesis_state*(preset: RuntimePreset,
|
||||
return false
|
||||
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
|
||||
# so only usable/used in research/ and tests/
|
||||
func get_initial_beacon_block*(state: BeaconState): SignedBeaconBlock =
|
||||
let message = BeaconBlock(
|
||||
slot: GENESIS_SLOT,
|
||||
state_root: hash_tree_root(state),
|
||||
body: BeaconBlockBody(
|
||||
# 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
|
||||
# initialized to default values.
|
||||
slot: state.slot,
|
||||
state_root: hash_tree_root(state),
|
||||
body: emptyBeaconBlockBody())
|
||||
# parent_root, randao_reveal, eth1_data, signature, and body automatically
|
||||
# initialized to default values.
|
||||
SignedBeaconBlock(message: message, root: hash_tree_root(message))
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.3/specs/phase0/beacon-chain.md#get_block_root_at_slot
|
||||
|
@ -64,6 +64,11 @@ func get_active_validator_indices*(state: BeaconState, epoch: Epoch):
|
||||
if is_active_validator(val, epoch):
|
||||
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
|
||||
func get_current_epoch*(state: BeaconState): Epoch =
|
||||
## Return the current epoch.
|
||||
|
48
beacon_chain/spec/weak_subjectivity.nim
Normal file
48
beacon_chain/spec/weak_subjectivity.nim
Normal 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
|
||||
|
@ -142,7 +142,7 @@ proc getBlockDataFromBlockId(node: BeaconNode, blockId: string): BlockData =
|
||||
of "head":
|
||||
node.chainDag.get(node.chainDag.head)
|
||||
of "genesis":
|
||||
node.chainDag.get(node.chainDag.tail)
|
||||
node.chainDag.getGenesisBlockData()
|
||||
of "finalized":
|
||||
node.chainDag.get(node.chainDag.finalizedHead.blck)
|
||||
else:
|
||||
@ -163,7 +163,7 @@ proc stateIdToBlockSlot(node: BeaconNode, stateId: string): BlockSlot =
|
||||
of "head":
|
||||
node.chainDag.head.toBlockSlot()
|
||||
of "genesis":
|
||||
node.chainDag.tail.toBlockSlot()
|
||||
node.chainDag.getGenesisBlockSlot()
|
||||
of "finalized":
|
||||
node.chainDag.finalizedHead
|
||||
of "justified":
|
||||
@ -188,7 +188,7 @@ proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) =
|
||||
template withStateForStateId(stateId: string, body: untyped): untyped =
|
||||
# TODO this can be optimized for the "head" case since that should be most common
|
||||
node.chainDag.withState(node.chainDag.tmpState,
|
||||
node.stateIdToBlockSlot(stateId)):
|
||||
node.stateIdToBlockSlot(stateId)):
|
||||
body
|
||||
|
||||
rpcServer.rpc("get_v1_beacon_genesis") do () -> BeaconGenesisTuple:
|
||||
|
@ -54,7 +54,7 @@ cli do(slots = SLOTS_PER_EPOCH * 6,
|
||||
db = BeaconChainDB.init(kvStore SqStoreRef.init(".", "block_sim").tryGet())
|
||||
defer: db.close()
|
||||
|
||||
ChainDAGRef.preInit(db, state[].data, genesisBlock)
|
||||
ChainDAGRef.preInit(db, state[].data, state[].data, genesisBlock)
|
||||
|
||||
var
|
||||
chainDag = init(ChainDAGRef, defaultRuntimePreset, db)
|
||||
|
@ -225,7 +225,7 @@ if [[ $USE_GANACHE == "0" ]]; then
|
||||
--insecure-netkey-password=true \
|
||||
--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
|
||||
echo "Launching ganache"
|
||||
ganache-cli --blockTime 17 --gasLimit 100000000 -e 100000 --verbose > "${DATA_DIR}/log_ganache.txt" 2>&1 &
|
||||
|
@ -78,7 +78,7 @@ mkdir -p "$NODE_DATA_DIR/dump"
|
||||
|
||||
SNAPSHOT_ARG=""
|
||||
if [ -f "${SNAPSHOT_FILE}" ]; then
|
||||
SNAPSHOT_ARG="--state-snapshot=${SNAPSHOT_FILE}"
|
||||
SNAPSHOT_ARG="--finalized-checkpoint-state=${SNAPSHOT_FILE}"
|
||||
fi
|
||||
|
||||
cd "$NODE_DATA_DIR"
|
||||
|
@ -99,7 +99,7 @@ template timedTest*(name, body) =
|
||||
|
||||
proc makeTestDB*(tailState: BeaconState, tailBlock: SignedBeaconBlock): BeaconChainDB =
|
||||
result = init(BeaconChainDB, kvStore MemStoreRef.init())
|
||||
ChainDAGRef.preInit(result, tailState, tailBlock)
|
||||
ChainDAGRef.preInit(result, tailState, tailState, tailBlock)
|
||||
|
||||
proc makeTestDB*(validators: Natural): BeaconChainDB =
|
||||
let
|
||||
|
2
vendor/eth2-testnets
vendored
2
vendor/eth2-testnets
vendored
@ -1 +1 @@
|
||||
Subproject commit f04512f15adccfc01b68f15c6216d681d08a1f83
|
||||
Subproject commit d4e434148b851ef5baecbd289ac9df6c4fb62d47
|
2
vendor/nim-eth
vendored
2
vendor/nim-eth
vendored
@ -1 +1 @@
|
||||
Subproject commit 3ddb498f2a41e1e470d780757faeedc0b8cb3a21
|
||||
Subproject commit de2d43a7e7afb7b094ca251bdbbd58fbf47df031
|
Loading…
x
Reference in New Issue
Block a user