2022-03-11 20:28:10 +00:00
|
|
|
# beacon_chain
|
|
|
|
# Copyright (c) 2021-2022 Status Research & Development GmbH
|
|
|
|
# Licensed and distributed under either of
|
|
|
|
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
|
|
|
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
|
|
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
|
|
|
|
|
|
|
{.used.}
|
|
|
|
|
|
|
|
import
|
|
|
|
# Status libraries
|
2022-06-07 17:05:06 +00:00
|
|
|
eth/keys, stew/objects, taskpools,
|
2022-03-11 20:28:10 +00:00
|
|
|
# Beacon chain internals
|
|
|
|
../beacon_chain/consensus_object_pools/
|
|
|
|
[block_clearance, block_quarantine, blockchain_dag],
|
|
|
|
../beacon_chain/spec/[forks, helpers, light_client_sync, state_transition],
|
|
|
|
# Test utilities
|
|
|
|
./testutil, ./testdbutil
|
|
|
|
|
|
|
|
suite "Light client" & preset():
|
|
|
|
let
|
|
|
|
cfg = block:
|
|
|
|
var res = defaultRuntimeConfig
|
|
|
|
res.ALTAIR_FORK_EPOCH = GENESIS_EPOCH + 1
|
|
|
|
res
|
|
|
|
altairStartSlot = cfg.ALTAIR_FORK_EPOCH.start_slot
|
|
|
|
|
|
|
|
proc advanceToSlot(
|
|
|
|
dag: ChainDAGRef,
|
|
|
|
targetSlot: Slot,
|
|
|
|
verifier: var BatchVerifier,
|
|
|
|
quarantine: var Quarantine,
|
|
|
|
attested = true,
|
2022-05-23 12:02:54 +00:00
|
|
|
syncCommitteeRatio = 0.82) =
|
2022-03-11 20:28:10 +00:00
|
|
|
var cache: StateCache
|
|
|
|
const maxAttestedSlotsPerPeriod = 3 * SLOTS_PER_EPOCH
|
|
|
|
while true:
|
2022-03-16 07:20:40 +00:00
|
|
|
var slot = getStateField(dag.headState, slot)
|
2022-03-11 20:28:10 +00:00
|
|
|
doAssert targetSlot >= slot
|
|
|
|
if targetSlot == slot: break
|
|
|
|
|
|
|
|
# When there is a large jump, skip to the end of the current period,
|
|
|
|
# create blocks for a few epochs to finalize it, then proceed
|
|
|
|
let
|
|
|
|
nextPeriod = slot.sync_committee_period + 1
|
|
|
|
periodEpoch = nextPeriod.start_epoch
|
|
|
|
periodSlot = periodEpoch.start_slot
|
|
|
|
checkpointSlot = periodSlot - maxAttestedSlotsPerPeriod
|
|
|
|
if targetSlot > checkpointSlot and checkpointSlot > dag.head.slot:
|
|
|
|
var info: ForkedEpochInfo
|
2022-03-16 07:20:40 +00:00
|
|
|
doAssert process_slots(cfg, dag.headState, checkpointSlot,
|
2022-03-11 20:28:10 +00:00
|
|
|
cache, info, flags = {}).isOk()
|
|
|
|
slot = checkpointSlot
|
|
|
|
|
|
|
|
# Create blocks for final few epochs
|
|
|
|
let blocks = min(targetSlot - slot, maxAttestedSlotsPerPeriod)
|
2022-03-16 07:20:40 +00:00
|
|
|
for blck in makeTestBlocks(dag.headState, cache, blocks.int,
|
2022-03-11 20:28:10 +00:00
|
|
|
attested, syncCommitteeRatio, cfg):
|
|
|
|
let added =
|
|
|
|
case blck.kind
|
|
|
|
of BeaconBlockFork.Phase0:
|
|
|
|
const nilCallback = OnPhase0BlockAdded(nil)
|
|
|
|
dag.addHeadBlock(verifier, blck.phase0Data, nilCallback)
|
|
|
|
of BeaconBlockFork.Altair:
|
|
|
|
const nilCallback = OnAltairBlockAdded(nil)
|
|
|
|
dag.addHeadBlock(verifier, blck.altairData, nilCallback)
|
|
|
|
of BeaconBlockFork.Bellatrix:
|
|
|
|
const nilCallback = OnBellatrixBlockAdded(nil)
|
|
|
|
dag.addHeadBlock(verifier, blck.bellatrixData, nilCallback)
|
2022-11-02 16:23:30 +00:00
|
|
|
of BeaconBlockFork.Capella:
|
|
|
|
raiseAssert $capellaImplementationMissing
|
2022-03-11 20:28:10 +00:00
|
|
|
check: added.isOk()
|
|
|
|
dag.updateHead(added[], quarantine)
|
|
|
|
|
|
|
|
setup:
|
|
|
|
const num_validators = SLOTS_PER_EPOCH
|
|
|
|
let
|
|
|
|
validatorMonitor = newClone(ValidatorMonitor.init())
|
|
|
|
dag = ChainDAGRef.init(
|
|
|
|
cfg, makeTestDB(num_validators), validatorMonitor, {},
|
2022-06-28 20:52:29 +00:00
|
|
|
lcDataConfig = LightClientDataConfig(
|
|
|
|
serve: true,
|
|
|
|
importMode: LightClientDataImportMode.OnlyNew))
|
2022-03-11 20:28:10 +00:00
|
|
|
quarantine = newClone(Quarantine.init())
|
2022-04-08 16:22:49 +00:00
|
|
|
taskpool = Taskpool.new()
|
2022-03-11 20:28:10 +00:00
|
|
|
var verifier = BatchVerifier(rng: keys.newRng(), taskpool: taskpool)
|
|
|
|
|
|
|
|
test "Pre-Altair":
|
|
|
|
# Genesis
|
|
|
|
check:
|
2022-03-16 07:20:40 +00:00
|
|
|
dag.headState.kind == BeaconStateFork.Phase0
|
2022-05-23 12:02:54 +00:00
|
|
|
dag.getLightClientUpdateForPeriod(0.SyncCommitteePeriod).isNone
|
|
|
|
dag.getLightClientFinalityUpdate.isNone
|
|
|
|
dag.getLightClientOptimisticUpdate.isNone
|
2022-03-11 20:28:10 +00:00
|
|
|
|
|
|
|
# Advance to last slot before Altair
|
|
|
|
dag.advanceToSlot(altairStartSlot - 1, verifier, quarantine[])
|
|
|
|
check:
|
2022-03-16 07:20:40 +00:00
|
|
|
dag.headState.kind == BeaconStateFork.Phase0
|
2022-05-23 12:02:54 +00:00
|
|
|
dag.getLightClientUpdateForPeriod(0.SyncCommitteePeriod).isNone
|
|
|
|
dag.getLightClientFinalityUpdate.isNone
|
|
|
|
dag.getLightClientOptimisticUpdate.isNone
|
2022-03-11 20:28:10 +00:00
|
|
|
|
|
|
|
# Advance to Altair
|
|
|
|
dag.advanceToSlot(altairStartSlot, verifier, quarantine[])
|
|
|
|
check:
|
2022-03-16 07:20:40 +00:00
|
|
|
dag.headState.kind == BeaconStateFork.Altair
|
2022-05-23 12:02:54 +00:00
|
|
|
dag.getLightClientUpdateForPeriod(0.SyncCommitteePeriod).isNone
|
|
|
|
dag.getLightClientFinalityUpdate.isNone
|
|
|
|
dag.getLightClientOptimisticUpdate.isNone
|
2022-03-11 20:28:10 +00:00
|
|
|
|
|
|
|
test "Light client sync":
|
|
|
|
# Advance to Altair
|
|
|
|
dag.advanceToSlot(altairStartSlot, verifier, quarantine[])
|
|
|
|
|
|
|
|
# Track trusted checkpoint for light client
|
|
|
|
let
|
2022-04-08 16:22:49 +00:00
|
|
|
genesis_validators_root = dag.genesis_validators_root
|
2022-03-16 07:20:40 +00:00
|
|
|
trusted_block_root = dag.head.root
|
2022-03-11 20:28:10 +00:00
|
|
|
|
|
|
|
# Advance to target slot
|
|
|
|
const
|
|
|
|
headPeriod = 2.SyncCommitteePeriod
|
|
|
|
periodEpoch = headPeriod.start_epoch
|
|
|
|
headSlot = (periodEpoch + 2).start_slot + 5
|
|
|
|
dag.advanceToSlot(headSlot, verifier, quarantine[])
|
2022-03-16 07:20:40 +00:00
|
|
|
let currentSlot = getStateField(dag.headState, slot)
|
2022-03-11 20:28:10 +00:00
|
|
|
|
|
|
|
# Initialize light client store
|
|
|
|
let bootstrap = dag.getLightClientBootstrap(trusted_block_root)
|
2022-03-20 10:58:59 +00:00
|
|
|
check bootstrap.isOk
|
2022-03-11 20:28:10 +00:00
|
|
|
var storeRes = initialize_light_client_store(
|
|
|
|
trusted_block_root, bootstrap.get)
|
2022-03-14 09:25:54 +00:00
|
|
|
check storeRes.isOk
|
2022-03-11 20:28:10 +00:00
|
|
|
template store(): auto = storeRes.get
|
|
|
|
|
|
|
|
# Sync to latest sync committee period
|
|
|
|
var numIterations = 0
|
|
|
|
while store.finalized_header.slot.sync_committee_period + 1 < headPeriod:
|
|
|
|
let
|
|
|
|
period =
|
2022-05-23 12:02:54 +00:00
|
|
|
if store.is_next_sync_committee_known:
|
2022-03-11 20:28:10 +00:00
|
|
|
store.finalized_header.slot.sync_committee_period + 1
|
2022-05-23 12:02:54 +00:00
|
|
|
else:
|
|
|
|
store.finalized_header.slot.sync_committee_period
|
|
|
|
update = dag.getLightClientUpdateForPeriod(period)
|
2022-03-11 20:28:10 +00:00
|
|
|
res = process_light_client_update(
|
2022-05-23 12:02:54 +00:00
|
|
|
store, update.get, currentSlot, cfg, genesis_validators_root)
|
2022-03-11 20:28:10 +00:00
|
|
|
check:
|
2022-05-23 12:02:54 +00:00
|
|
|
update.isSome
|
|
|
|
update.get.finalized_header.slot.sync_committee_period == period
|
2022-03-14 09:25:54 +00:00
|
|
|
res.isOk
|
2022-05-23 12:02:54 +00:00
|
|
|
if update.get.finalized_header.slot > bootstrap.get.header.slot:
|
|
|
|
store.finalized_header == update.get.finalized_header
|
|
|
|
else:
|
|
|
|
store.finalized_header == bootstrap.get.header
|
2022-03-11 20:28:10 +00:00
|
|
|
inc numIterations
|
|
|
|
if numIterations > 20: doAssert false # Avoid endless loop on test failure
|
|
|
|
|
|
|
|
# Sync to latest update
|
|
|
|
let
|
2022-05-23 12:02:54 +00:00
|
|
|
finalityUpdate = dag.getLightClientFinalityUpdate
|
2022-03-11 20:28:10 +00:00
|
|
|
res = process_light_client_update(
|
2022-05-23 12:02:54 +00:00
|
|
|
store, finalityUpdate.get, currentSlot, cfg, genesis_validators_root)
|
2022-03-11 20:28:10 +00:00
|
|
|
check:
|
2022-05-23 12:02:54 +00:00
|
|
|
finalityUpdate.isSome
|
|
|
|
finalityUpdate.get.attested_header.slot == dag.head.parent.slot
|
2022-03-14 09:25:54 +00:00
|
|
|
res.isOk
|
2022-05-23 12:02:54 +00:00
|
|
|
store.finalized_header == finalityUpdate.get.finalized_header
|
|
|
|
store.optimistic_header == finalityUpdate.get.attested_header
|
2022-03-11 20:28:10 +00:00
|
|
|
|
|
|
|
test "Init from checkpoint":
|
|
|
|
# Fetch genesis state
|
2022-03-16 07:20:40 +00:00
|
|
|
let genesisState = assignClone dag.headState
|
2022-03-11 20:28:10 +00:00
|
|
|
|
|
|
|
# Advance to target slot for checkpoint
|
|
|
|
let finalizedSlot =
|
|
|
|
((altairStartSlot.sync_committee_period + 1).start_epoch + 2).start_slot
|
|
|
|
dag.advanceToSlot(finalizedSlot, verifier, quarantine[])
|
|
|
|
|
|
|
|
# Initialize new DAG from checkpoint
|
|
|
|
let cpDb = BeaconChainDB.new("", inMemory = true)
|
State-only checkpoint state startup (#4251)
Currently, we require genesis and a checkpoint block and state to start
from an arbitrary slot - this PR relaxes this requirement so that we can
start with a state alone.
The current trusted-node-sync algorithm works by first downloading
blocks until we find an epoch aligned non-empty slot, then downloads the
state via slot.
However, current
[proposals](https://github.com/ethereum/beacon-APIs/pull/226) for
checkpointing prefer finalized state as
the main reference - this allows more simple access control and caching
on the server side - in particular, this should help checkpoint-syncing
from sources that have a fast `finalized` state download (like infura
and teku) but are slow when accessing state via slot.
Earlier versions of Nimbus will not be able to read databases created
without a checkpoint block and genesis. In most cases, backfilling makes
the database compatible except where genesis is also missing (custom
networks).
* backfill checkpoint block from libp2p instead of checkpoint source,
when doing trusted node sync
* allow starting the client without genesis / checkpoint block
* perform epoch start slot lookahead when loading tail state, so as to
deal with the case where the epoch start slot does not have a block
* replace `--blockId` with `--state-id` in TNS command line
* when replaying, also look at the parent of the last-known-block (even
if we don't have the parent block data, we can still replay from a
"parent" state) - in particular, this clears the way for implementing
state pruning
* deprecate `--finalized-checkpoint-block` option (no longer needed)
2022-11-02 10:02:38 +00:00
|
|
|
ChainDAGRef.preInit(cpDb, genesisState[])
|
|
|
|
ChainDAGRef.preInit(cpDb, dag.headState) # dag.getForkedBlock(dag.head.bid).get)
|
2022-03-11 20:28:10 +00:00
|
|
|
let cpDag = ChainDAGRef.init(
|
|
|
|
cfg, cpDb, validatorMonitor, {},
|
2022-06-28 20:52:29 +00:00
|
|
|
lcDataConfig = LightClientDataConfig(
|
|
|
|
serve: true,
|
|
|
|
importMode: LightClientDataImportMode.Full))
|
2022-03-11 20:28:10 +00:00
|
|
|
|
|
|
|
# Advance by a couple epochs
|
|
|
|
for i in 1'u64 .. 10:
|
|
|
|
let headSlot = (finalizedSlot.epoch + i).start_slot
|
|
|
|
cpDag.advanceToSlot(headSlot, verifier, quarantine[])
|
|
|
|
|
|
|
|
check true
|