276 lines
11 KiB
Nim
276 lines
11 KiB
Nim
# beacon_chain
|
|
# Copyright (c) 2022-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, streams],
|
|
# Status libraries
|
|
stew/byteutils,
|
|
# Third-party
|
|
yaml, yaml/tojson,
|
|
# Beacon chain internals
|
|
../../beacon_chain/spec/[forks, light_client_sync],
|
|
# Test utilities
|
|
../testutil,
|
|
./fixtures_utils, ./os_ops
|
|
|
|
type
|
|
TestMeta = object
|
|
genesis_validators_root: Eth2Digest
|
|
trusted_block_root: Eth2Digest
|
|
fork_digests: ForkDigests
|
|
bootstrap_fork_digest: ForkDigest
|
|
store_fork_digest: ForkDigest
|
|
|
|
TestChecks = object
|
|
finalized_slot: Slot
|
|
finalized_beacon_root: Eth2Digest
|
|
finalized_execution_root: Eth2Digest
|
|
optimistic_slot: Slot
|
|
optimistic_beacon_root: Eth2Digest
|
|
optimistic_execution_root: Eth2Digest
|
|
|
|
TestStepKind {.pure.} = enum
|
|
ForceUpdate
|
|
ProcessUpdate
|
|
UpgradeStore
|
|
|
|
TestStep = object
|
|
case kind: TestStepKind
|
|
of TestStepKind.ForceUpdate:
|
|
discard
|
|
of TestStepKind.ProcessUpdate:
|
|
update: ForkedLightClientUpdate
|
|
of TestStepKind.UpgradeStore:
|
|
store_data_fork: LightClientDataFork
|
|
current_slot: Slot
|
|
checks: TestChecks
|
|
|
|
proc loadSteps(
|
|
path: string,
|
|
fork_digests: ForkDigests
|
|
): seq[TestStep] {.raises: [
|
|
KeyError, ValueError, YamlConstructionError, YamlParserError].} =
|
|
let stepsYAML = os_ops.readFile(path/"steps.yaml")
|
|
let steps = loadToJson(stepsYAML)
|
|
|
|
result = @[]
|
|
for step in steps[0]:
|
|
func getChecks(c: JsonNode): TestChecks {.raises: [KeyError].} =
|
|
TestChecks(
|
|
finalized_slot:
|
|
c["finalized_header"]["slot"].getInt().Slot,
|
|
finalized_beacon_root:
|
|
Eth2Digest.fromHex(c["finalized_header"]["beacon_root"].getStr()),
|
|
finalized_execution_root:
|
|
Eth2Digest.fromHex(c["finalized_header"]{"execution_root"}.getStr()),
|
|
optimistic_slot:
|
|
c["optimistic_header"]["slot"].getInt().Slot,
|
|
optimistic_beacon_root:
|
|
Eth2Digest.fromHex(c["optimistic_header"]["beacon_root"].getStr()),
|
|
optimistic_execution_root:
|
|
Eth2Digest.fromHex(c["optimistic_header"]{"execution_root"}.getStr()))
|
|
|
|
if step.hasKey"force_update":
|
|
let s = step["force_update"]
|
|
|
|
result.add TestStep(
|
|
kind: TestStepKind.ForceUpdate,
|
|
current_slot: s["current_slot"].getInt().Slot,
|
|
checks: s["checks"].getChecks())
|
|
elif step.hasKey"process_update":
|
|
let
|
|
s = step["process_update"]
|
|
update_fork_digest =
|
|
distinctBase(ForkDigest).fromHex(s{"update_fork_digest"}.getStr(
|
|
distinctBase(fork_digests.altair).toHex())).ForkDigest
|
|
update_consensus_fork =
|
|
fork_digests.consensusForkForDigest(update_fork_digest)
|
|
.expect("Unknown update fork " & $update_fork_digest)
|
|
update_filename = s["update"].getStr()
|
|
|
|
var update {.noinit.}: ForkedLightClientUpdate
|
|
withLcDataFork(lcDataForkAtConsensusFork(update_consensus_fork)):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
update = ForkedLightClientUpdate.init(parseTest(
|
|
path/update_filename & ".ssz_snappy", SSZ,
|
|
lcDataFork.LightClientUpdate))
|
|
else: raiseAssert "Unreachable update fork " & $update_fork_digest
|
|
|
|
result.add TestStep(
|
|
kind: TestStepKind.ProcessUpdate,
|
|
update: update,
|
|
current_slot: s["current_slot"].getInt().Slot,
|
|
checks: s["checks"].getChecks())
|
|
elif step.hasKey"upgrade_store":
|
|
let
|
|
s = step["upgrade_store"]
|
|
store_fork_digest =
|
|
distinctBase(ForkDigest).fromHex(
|
|
s["store_fork_digest"].getStr()).ForkDigest
|
|
store_consensus_fork =
|
|
fork_digests.consensusForkForDigest(store_fork_digest)
|
|
.expect("Unknown store fork " & $store_fork_digest)
|
|
|
|
result.add TestStep(
|
|
kind: TestStepKind.UpgradeStore,
|
|
store_data_fork: lcDataForkAtConsensusFork(store_consensus_fork),
|
|
checks: s["checks"].getChecks())
|
|
else:
|
|
doAssert false, "Unknown test step: " & $step
|
|
|
|
proc runTest(suiteName, path: string) =
|
|
let relativePathComponent = path.relativeTestPathComponent()
|
|
test "Light client - Sync - " & relativePathComponent:
|
|
# Reduce stack size by making this a `proc`
|
|
proc loadTestMeta(): (RuntimeConfig, TestMeta) {.raises: [
|
|
Exception, IOError, PresetFileError, PresetIncompatibleError].} =
|
|
let (cfg, _) = readRuntimeConfig(path/"config.yaml")
|
|
|
|
when false:
|
|
# TODO evaluate whether this is useful and if so, fix it
|
|
# Unhandled defect: nimbus-eth2/tests/consensus_spec/test_fixture_light_client_sync.nim(131, 16) `unknowns.len == 0` Unknown config constants: @["MAXIMUM_GOSSIP_CLOCK_DISPARITY", "ATTESTATION_PROPAGATION_SLOT_RANGE", "MAX_REQUEST_BLOCKS", "SUBNETS_PER_NODE", "TTFB_TIMEOUT", "MIN_EPOCHS_FOR_BLOCK_REQUESTS", "MESSAGE_DOMAIN_VALID_SNAPPY", "ATTESTATION_SUBNET_EXTRA_BITS", "MAX_CHUNK_SIZE", "EPOCHS_PER_SUBNET_SUBSCRIPTION", "GOSSIP_MAX_SIZE", "ATTESTATION_SUBNET_PREFIX_BITS", "MESSAGE_DOMAIN_INVALID_SNAPPY", "RESP_TIMEOUT"] [AssertionDefect]
|
|
doAssert unknowns.len == 0, "Unknown config constants: " & $unknowns
|
|
|
|
type TestMetaYaml {.sparse.} = object
|
|
genesis_validators_root: string
|
|
trusted_block_root: string
|
|
bootstrap_fork_digest: Option[string]
|
|
store_fork_digest: Option[string]
|
|
let
|
|
meta = block:
|
|
var s = openFileStream(path/"meta.yaml")
|
|
defer: close(s)
|
|
var res: TestMetaYaml
|
|
yaml.load(s, res)
|
|
res
|
|
genesis_validators_root =
|
|
Eth2Digest.fromHex(meta.genesis_validators_root)
|
|
trusted_block_root =
|
|
Eth2Digest.fromHex(meta.trusted_block_root)
|
|
fork_digests =
|
|
ForkDigests.init(cfg, genesis_validators_root)
|
|
bootstrap_fork_digest =
|
|
distinctBase(ForkDigest).fromHex(meta.bootstrap_fork_digest.get(
|
|
distinctBase(fork_digests.altair).toHex())).ForkDigest
|
|
store_fork_digest =
|
|
distinctBase(ForkDigest).fromHex(meta.store_fork_digest.get(
|
|
distinctBase(fork_digests.altair).toHex())).ForkDigest
|
|
|
|
(cfg, TestMeta(
|
|
genesis_validators_root: genesis_validators_root,
|
|
trusted_block_root: trusted_block_root,
|
|
fork_digests: fork_digests,
|
|
bootstrap_fork_digest: bootstrap_fork_digest,
|
|
store_fork_digest: store_fork_digest))
|
|
|
|
let
|
|
(cfg, meta) = loadTestMeta()
|
|
steps = loadSteps(path, meta.fork_digests)
|
|
|
|
# Reduce stack size by making this a `proc`
|
|
proc loadBootstrap(): ForkedLightClientBootstrap =
|
|
let bootstrap_consensus_fork =
|
|
meta.fork_digests.consensusForkForDigest(meta.bootstrap_fork_digest)
|
|
.expect("Unknown bootstrap fork " & $meta.bootstrap_fork_digest)
|
|
var bootstrap {.noinit.}: ForkedLightClientBootstrap
|
|
withLcDataFork(lcDataForkAtConsensusFork(bootstrap_consensus_fork)):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
bootstrap = ForkedLightClientBootstrap.init(parseTest(
|
|
path/"bootstrap.ssz_snappy", SSZ,
|
|
lcDataFork.LightClientBootstrap))
|
|
else:
|
|
raiseAssert "Unknown bootstrap fork " & $meta.bootstrap_fork_digest
|
|
bootstrap
|
|
|
|
# Reduce stack size by making this a `proc`
|
|
proc initializeStore(
|
|
bootstrap: ref ForkedLightClientBootstrap): ForkedLightClientStore =
|
|
let store_consensus_fork =
|
|
meta.fork_digests.consensusForkForDigest(meta.store_fork_digest)
|
|
.expect("Unknown store fork " & $meta.store_fork_digest)
|
|
var store {.noinit.}: ForkedLightClientStore
|
|
withLcDataFork(lcDataForkAtConsensusFork(store_consensus_fork)):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
bootstrap[].migrateToDataFork(lcDataFork)
|
|
store = ForkedLightClientStore.init(initialize_light_client_store(
|
|
meta.trusted_block_root, bootstrap[].forky(lcDataFork), cfg).get)
|
|
else: raiseAssert "Unreachable store fork " & $meta.store_fork_digest
|
|
store
|
|
|
|
let bootstrap = newClone(loadBootstrap())
|
|
var store = initializeStore(bootstrap)
|
|
|
|
# Reduce stack size by making this a `proc`
|
|
proc processStep(step: TestStep) =
|
|
withForkyStore(store):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
case step.kind
|
|
of TestStepKind.ForceUpdate:
|
|
process_light_client_store_force_update(
|
|
forkyStore, step.current_slot)
|
|
of TestStepKind.ProcessUpdate:
|
|
check step.update.kind <= lcDataFork
|
|
let
|
|
upgradedUpdate = step.update.migratingToDataFork(lcDataFork)
|
|
res = process_light_client_update(
|
|
forkyStore, upgradedUpdate.forky(lcDataFork), step.current_slot,
|
|
cfg, meta.genesis_validators_root)
|
|
check res.isOk
|
|
of TestStepKind.UpgradeStore:
|
|
check step.store_data_fork >= lcDataFork
|
|
withLcDataFork(step.store_data_fork):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
store.migrateToDataFork(lcDataFork)
|
|
else: raiseAssert "Unreachable"
|
|
|
|
withForkyStore(store):
|
|
when lcDataFork > LightClientDataFork.None:
|
|
let
|
|
finalized_slot =
|
|
forkyStore.finalized_header.beacon.slot
|
|
finalized_beacon_root =
|
|
hash_tree_root(forkyStore.finalized_header.beacon)
|
|
finalized_execution_root =
|
|
when lcDataFork >= LightClientDataFork.Capella:
|
|
get_lc_execution_root(forkyStore.finalized_header, cfg)
|
|
else:
|
|
ZERO_HASH
|
|
optimistic_slot =
|
|
forkyStore.optimistic_header.beacon.slot
|
|
optimistic_beacon_root =
|
|
hash_tree_root(forkyStore.optimistic_header.beacon)
|
|
optimistic_execution_root =
|
|
when lcDataFork >= LightClientDataFork.Capella:
|
|
get_lc_execution_root(forkyStore.optimistic_header, cfg)
|
|
else:
|
|
ZERO_HASH
|
|
check:
|
|
finalized_slot == step.checks.finalized_slot
|
|
finalized_beacon_root == step.checks.finalized_beacon_root
|
|
finalized_execution_root == step.checks.finalized_execution_root
|
|
optimistic_slot == step.checks.optimistic_slot
|
|
optimistic_beacon_root == step.checks.optimistic_beacon_root
|
|
optimistic_execution_root == step.checks.optimistic_execution_root
|
|
else: raiseAssert "Unreachable"
|
|
|
|
for step in steps:
|
|
processStep(step)
|
|
|
|
suite "EF - Light client - Sync" & preset():
|
|
const presetPath = SszTestsDir/const_preset
|
|
for kind, path in walkDir(presetPath, relative = true, checkDir = true):
|
|
let basePath =
|
|
presetPath/path/"light_client"/"sync"/"pyspec_tests"
|
|
if kind != pcDir or not dirExists(basePath):
|
|
continue
|
|
for kind, path in walkDir(basePath, relative = true, checkDir = true):
|
|
runTest(suiteName, basePath/path)
|