Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add test runner for LC data collection tests #6021

Merged
merged 2 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions beacon_chain/spec/forks.nim
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,11 @@ template SignedBlindedBeaconBlock*(kind: static ConsensusFork): auto =
else:
static: raiseAssert "Unreachable"

template Forky*(
x: typedesc[ForkedSignedBeaconBlock],
kind: static ConsensusFork): auto =
kind.SignedBeaconBlock

template withAll*(
x: typedesc[ConsensusFork], body: untyped): untyped =
static: doAssert ConsensusFork.high == ConsensusFork.Deneb
Expand Down
201 changes: 201 additions & 0 deletions tests/consensus_spec/test_fixture_light_client_data_collection.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# 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,
# 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 = yaml.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, unknowns) = readRuntimeConfig(path/"config.yaml")
doAssert unknowns.len == 0

let
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)
Loading