200 lines
7.6 KiB
Nim
200 lines
7.6 KiB
Nim
# beacon_chain
|
|
# Copyright (c) 2024 Status Research & Development GmbH
|
|
# Licensed and distributed under either of
|
|
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
|
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
|
|
|
{.push raises: [].}
|
|
{.used.}
|
|
|
|
import
|
|
# Standard library
|
|
std/[json, sequtils],
|
|
# Status libraries
|
|
stew/byteutils,
|
|
chronicles,
|
|
taskpools,
|
|
# Third-party
|
|
yaml/tojson,
|
|
# Beacon chain internals
|
|
../../beacon_chain/beacon_chain_db,
|
|
../../beacon_chain/consensus_object_pools/[block_clearance, block_quarantine],
|
|
../../beacon_chain/spec/forks,
|
|
# Test utilities
|
|
../testutil, ../testbcutil,
|
|
./fixtures_utils, ./os_ops
|
|
|
|
type
|
|
TestStepKind {.pure.} = enum
|
|
NewBlock
|
|
NewHead
|
|
|
|
NewHeadChecks = object
|
|
latestFinalizedCheckpoint: Checkpoint
|
|
bootstraps: Table[Eth2Digest, ForkedLightClientBootstrap]
|
|
bestUpdates: Table[SyncCommitteePeriod, ForkedLightClientUpdate]
|
|
latestFinalityUpdate: ForkedLightClientFinalityUpdate
|
|
latestOptimisticUpdate: ForkedLightClientOptimisticUpdate
|
|
|
|
TestStep = object
|
|
case kind: TestStepKind
|
|
of TestStepKind.NewBlock:
|
|
blck: ForkedSignedBeaconBlock
|
|
of TestStepKind.NewHead:
|
|
headBlockRoot: Eth2Digest
|
|
checks: NewHeadChecks
|
|
|
|
func `==`(a, b: SomeForkedLightClientObject): bool =
|
|
a.kind == b.kind and withForkyObject(a, (
|
|
when lcDataFork > LightClientDataFork.None:
|
|
forkyObject == b.forky(lcDataFork)
|
|
else:
|
|
true))
|
|
|
|
proc loadForked[T: not Opt](
|
|
t: typedesc[T],
|
|
s: JsonNode,
|
|
path: string,
|
|
fork_digests: ForkDigests): T {.raises: [ValueError].} =
|
|
if s == nil:
|
|
when T is SomeForkedLightClientObject:
|
|
return default(T)
|
|
else:
|
|
raiseAssert "Unexpected nil JSON node"
|
|
let
|
|
fork_digest = ForkDigest distinctBase(ForkDigest)
|
|
.fromHex(s["fork_digest"].getStr())
|
|
consensusFork = fork_digests.consensusForkForDigest(fork_digest)
|
|
.expect("Unknown fork " & $fork_digest)
|
|
filename = s["data"].getStr()
|
|
when T is SomeForkedLightClientObject:
|
|
withLcDataFork(lcDataForkAtConsensusFork(consensusFork)):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
T.init(parseTest(
|
|
path/filename & ".ssz_snappy", SSZ, T.Forky(lcDataFork)))
|
|
else: raiseAssert $consensusFork & " does not support LC data"
|
|
else:
|
|
withConsensusFork(consensusFork):
|
|
T.init(parseTest(
|
|
path/filename & ".ssz_snappy", SSZ, T.Forky(consensusFork)))
|
|
|
|
proc loadSteps(
|
|
path: string,
|
|
fork_digests: ForkDigests
|
|
): seq[TestStep] {.raises: [
|
|
IOError, KeyError, ValueError, YamlConstructionError, YamlParserError].} =
|
|
template loadForked[T](t: typedesc[T], s: JsonNode): T =
|
|
loadForked(t, s, path, fork_digests)
|
|
|
|
let stepsYAML = os_ops.readFile(path/"steps.yaml")
|
|
let steps = loadToJson(stepsYAML)
|
|
|
|
result = @[]
|
|
for step in steps[0]:
|
|
if step.hasKey"new_block":
|
|
let s = step["new_block"]
|
|
result.add TestStep(
|
|
kind: TestStepKind.NewBlock,
|
|
blck: ForkedSignedBeaconBlock.loadForked(s))
|
|
elif step.hasKey"new_head":
|
|
let
|
|
s = step["new_head"]
|
|
checks = s["checks"]
|
|
result.add TestStep(
|
|
kind: TestStepKind.NewHead,
|
|
headBlockRoot: Eth2Digest.fromHex(s["head_block_root"].getStr()),
|
|
checks: NewHeadChecks(
|
|
latestFinalizedCheckpoint: Checkpoint(
|
|
epoch: Epoch(
|
|
checks["latest_finalized_checkpoint"]["epoch"].getInt()),
|
|
root: Eth2Digest.fromHex(
|
|
checks["latest_finalized_checkpoint"]["root"].getStr())),
|
|
bootstraps: checks["bootstraps"].foldl((block:
|
|
check: not a.hasKeyOrPut(
|
|
Eth2Digest.fromHex(b["block_root"].getStr()),
|
|
ForkedLightClientBootstrap.loadForked(b{"bootstrap"}))
|
|
a), newTable[Eth2Digest, ForkedLightClientBootstrap]())[],
|
|
bestUpdates: checks["best_updates"].foldl((block:
|
|
check: not a.hasKeyOrPut(
|
|
SyncCommitteePeriod(b["period"].getInt()),
|
|
ForkedLightClientUpdate.loadForked(b{"update"}))
|
|
a), newTable[SyncCommitteePeriod, ForkedLightClientUpdate]())[],
|
|
latestFinalityUpdate: ForkedLightClientFinalityUpdate
|
|
.loadForked(checks{"latest_finality_update"}),
|
|
latestOptimisticUpdate: ForkedLightClientOptimisticUpdate
|
|
.loadForked(checks{"latest_optimistic_update"})))
|
|
else:
|
|
raiseAssert "Unknown test step: " & $step
|
|
|
|
proc runTest(suiteName, path: string, consensusFork: static ConsensusFork) =
|
|
let relativePathComponent = path.relativeTestPathComponent()
|
|
test "Light client - Data collection - " & relativePathComponent:
|
|
let
|
|
(cfg, _) = readRuntimeConfig(path/"config.yaml")
|
|
initial_state = loadForkedState(
|
|
path/"initial_state.ssz_snappy", consensusFork)
|
|
db = BeaconChainDB.new("", cfg = cfg, inMemory = true)
|
|
defer: db.close()
|
|
ChainDAGRef.preInit(db, initial_state[])
|
|
|
|
let
|
|
validatorMonitor = newClone(ValidatorMonitor.init(false, false))
|
|
dag = ChainDAGRef.init(cfg, db, validatorMonitor, {},
|
|
lcDataConfig = LightClientDataConfig(
|
|
serve: true, importMode: LightClientDataImportMode.Full))
|
|
rng = HmacDrbgContext.new()
|
|
taskpool = TaskPool.new()
|
|
var
|
|
verifier = BatchVerifier.init(rng, taskpool)
|
|
quarantine = newClone(Quarantine.init())
|
|
|
|
let steps = loadSteps(path, dag.forkDigests[])
|
|
for i, step in steps:
|
|
case step.kind
|
|
of TestStepKind.NewBlock:
|
|
checkpoint $i & " new_block: " & $shortLog(step.blck.toBlockId())
|
|
let added = withBlck(step.blck):
|
|
const nilCallback = (consensusFork.OnBlockAddedCallback)(nil)
|
|
dag.addHeadBlock(verifier, forkyBlck, nilCallback)
|
|
check: added.isOk()
|
|
of TestStepKind.NewHead:
|
|
let blck = dag.getBlockRef(step.headBlockRoot)
|
|
check blck.isSome
|
|
checkpoint $i & " new_head: " & $shortLog(blck.get.bid)
|
|
dag.updateHead(blck.get, quarantine[], knownValidators = [])
|
|
if dag.needStateCachesAndForkChoicePruning():
|
|
dag.pruneStateCachesDAG()
|
|
check:
|
|
step.checks.latestFinalizedCheckpoint.epoch ==
|
|
dag.finalizedHead.slot.epoch
|
|
step.checks.latestFinalizedCheckpoint.root == (
|
|
if dag.finalizedHead.blck.slot != GENESIS_SLOT:
|
|
dag.finalizedHead.blck.root
|
|
else:
|
|
ZERO_HASH)
|
|
step.checks.bootstraps.pairs().toSeq().allIt:
|
|
dag.getLightClientBootstrap(it[0]) == it[1]
|
|
step.checks.bestUpdates.pairs().toSeq().allIt:
|
|
dag.getLightClientUpdateForPeriod(it[0]) == it[1]
|
|
step.checks.latestFinalityUpdate ==
|
|
dag.getLightClientFinalityUpdate()
|
|
step.checks.latestOptimisticUpdate ==
|
|
dag.getLightClientOptimisticUpdate()
|
|
|
|
suite "EF - Light client - Data collection" & preset():
|
|
const presetPath = SszTestsDir/const_preset
|
|
for kind, path in walkDir(presetPath, relative = true, checkDir = true):
|
|
let testsPath =
|
|
presetPath/path/"light_client"/"data_collection"/"pyspec_tests"
|
|
if kind != pcDir or not dirExists(testsPath):
|
|
continue
|
|
let consensusFork = forkForPathComponent(path).valueOr:
|
|
let relativePathComponent = path.relativeTestPathComponent()
|
|
test "Light client - Data collection - " & relativePathComponent:
|
|
skip()
|
|
continue
|
|
for kind, path in walkDir(testsPath, relative = true, checkDir = true):
|
|
withConsensusFork(consensusFork):
|
|
runTest(suiteName, testsPath/path, consensusFork)
|