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))) \
--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 $@

View File

@ -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")

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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:

View File

@ -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

View File

@ -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.

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":
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:

View File

@ -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)

View File

@ -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 &

View File

@ -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"

View File

@ -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

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

2
vendor/nim-eth vendored

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