nimbus-eth2/tests/consensus_spec/test_fixture_light_client_s...

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)