2022-06-18 04:57:37 +00:00
|
|
|
# beacon_chain
|
2024-01-06 14:26:56 +00:00
|
|
|
# Copyright (c) 2020-2024 Status Research & Development GmbH
|
2022-06-18 04:57:37 +00:00
|
|
|
# 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.
|
|
|
|
|
2024-02-17 19:23:26 +00:00
|
|
|
{.push raises: [].}
|
|
|
|
|
2020-05-01 15:51:24 +00:00
|
|
|
import
|
2023-08-10 12:52:49 +00:00
|
|
|
stew/io2,
|
2023-02-10 20:59:38 +00:00
|
|
|
../tests/testblockutil, ../tests/consensus_spec/os_ops,
|
2023-08-21 09:10:15 +00:00
|
|
|
../beacon_chain/spec/[beaconstate, forks]
|
|
|
|
|
|
|
|
from std/stats import RunningStat, mean, push, standardDeviationS
|
|
|
|
from std/strformat import `&`
|
|
|
|
from std/times import cpuTime
|
|
|
|
from ../beacon_chain/filepath import secureCreatePath
|
|
|
|
from ../beacon_chain/spec/deposit_snapshots import DepositTreeSnapshot
|
2020-05-01 15:51:24 +00:00
|
|
|
|
|
|
|
template withTimer*(stats: var RunningStat, body: untyped) =
|
|
|
|
# TODO unify timing somehow
|
|
|
|
let start = cpuTime()
|
|
|
|
|
|
|
|
block:
|
|
|
|
body
|
|
|
|
|
|
|
|
let stop = cpuTime()
|
|
|
|
stats.push stop - start
|
|
|
|
|
|
|
|
template withTimerRet*(stats: var RunningStat, body: untyped): untyped =
|
|
|
|
let start = cpuTime()
|
|
|
|
let tmp = block:
|
|
|
|
body
|
|
|
|
let stop = cpuTime()
|
|
|
|
stats.push stop - start
|
|
|
|
|
|
|
|
tmp
|
|
|
|
|
2023-08-21 09:10:15 +00:00
|
|
|
func verifyConsensus*(state: ForkedHashedBeaconState, attesterRatio: float) =
|
2021-04-16 08:49:37 +00:00
|
|
|
if attesterRatio < 0.63:
|
|
|
|
doAssert getStateField(state, current_justified_checkpoint).epoch == 0
|
|
|
|
doAssert getStateField(state, finalized_checkpoint).epoch == 0
|
|
|
|
|
|
|
|
# Quorum is 2/3 of validators, and at low numbers, quantization effects
|
|
|
|
# can dominate, so allow for play above/below attesterRatio of 2/3.
|
|
|
|
if attesterRatio < 0.72:
|
|
|
|
return
|
|
|
|
|
|
|
|
let current_epoch = get_current_epoch(state)
|
|
|
|
if current_epoch >= 3:
|
|
|
|
doAssert getStateField(
|
|
|
|
state, current_justified_checkpoint).epoch + 1 >= current_epoch
|
|
|
|
if current_epoch >= 4:
|
|
|
|
doAssert getStateField(
|
|
|
|
state, finalized_checkpoint).epoch + 2 >= current_epoch
|
|
|
|
|
2023-08-21 09:10:15 +00:00
|
|
|
func getSimulationConfig*(): RuntimeConfig {.compileTime.} =
|
|
|
|
var cfg = defaultRuntimeConfig
|
|
|
|
cfg.ALTAIR_FORK_EPOCH = 0.Epoch
|
|
|
|
cfg.BELLATRIX_FORK_EPOCH = 0.Epoch
|
|
|
|
cfg.CAPELLA_FORK_EPOCH = 0.Epoch
|
|
|
|
cfg.DENEB_FORK_EPOCH = 2.Epoch
|
|
|
|
cfg
|
|
|
|
|
2020-12-03 04:30:35 +00:00
|
|
|
proc loadGenesis*(validators: Natural, validate: bool):
|
2022-12-07 10:24:51 +00:00
|
|
|
(ref ForkedHashedBeaconState, DepositTreeSnapshot) =
|
2023-04-27 12:17:19 +00:00
|
|
|
const genesisDir = "test_sim"
|
|
|
|
if (let res = secureCreatePath(genesisDir); res.isErr):
|
|
|
|
fatal "Could not create directory",
|
|
|
|
path = genesisDir, err = ioErrorMsg(res.error)
|
|
|
|
quit 1
|
|
|
|
|
2020-12-03 04:30:35 +00:00
|
|
|
let
|
2024-02-17 19:23:26 +00:00
|
|
|
suffix = const_preset & "_" & $validators & "_" & SPEC_VERSION
|
2023-04-27 12:17:19 +00:00
|
|
|
genesisFn = genesisDir /
|
2024-02-17 19:23:26 +00:00
|
|
|
"genesis_" & suffix & ".ssz"
|
2023-04-27 12:17:19 +00:00
|
|
|
contractSnapshotFn = genesisDir /
|
2024-02-17 19:23:26 +00:00
|
|
|
"deposit_contract_snapshot_" & suffix & ".ssz"
|
2023-08-21 09:10:15 +00:00
|
|
|
const cfg = getSimulationConfig()
|
2021-11-10 11:39:08 +00:00
|
|
|
|
2020-12-03 04:30:35 +00:00
|
|
|
if fileExists(genesisFn) and fileExists(contractSnapshotFn):
|
2024-02-17 19:23:26 +00:00
|
|
|
let res =
|
|
|
|
try:
|
|
|
|
newClone(readSszForkedHashedBeaconState(
|
|
|
|
cfg, readAllBytes(genesisFn).tryGet()))
|
|
|
|
except ResultError[IoErrorCode] as exc:
|
|
|
|
fatal "Genesis file failed to load",
|
|
|
|
fileName = genesisFn, exc = exc.msg
|
|
|
|
quit 1
|
|
|
|
except SerializationError as exc:
|
|
|
|
fatal "Genesis file malformed",
|
|
|
|
fileName = genesisFn, exc = exc.msg
|
|
|
|
quit 1
|
2020-05-01 15:51:24 +00:00
|
|
|
|
2021-11-10 11:39:08 +00:00
|
|
|
withState(res[]):
|
2022-08-26 22:47:40 +00:00
|
|
|
if forkyState.data.slot != GENESIS_SLOT:
|
2024-02-17 19:23:26 +00:00
|
|
|
fatal "Can only start from genesis state"
|
2021-11-10 11:39:08 +00:00
|
|
|
quit 1
|
2020-05-01 15:51:24 +00:00
|
|
|
|
2022-08-26 22:47:40 +00:00
|
|
|
if forkyState.data.validators.len != validators:
|
2024-02-17 19:23:26 +00:00
|
|
|
fatal "Supplied genesis file has unexpected number of validators",
|
|
|
|
numExpectedValidators = validators,
|
|
|
|
numActualValidators = forkyState.data.validators.len
|
|
|
|
quit 1
|
2020-12-03 04:30:35 +00:00
|
|
|
|
2024-02-17 19:23:26 +00:00
|
|
|
info "Loaded genesis file", fileName = genesisFn
|
2020-12-03 04:30:35 +00:00
|
|
|
|
2023-04-27 12:17:19 +00:00
|
|
|
# TODO check that the private keys are EF test keys
|
2021-11-10 11:39:08 +00:00
|
|
|
|
2024-02-17 19:23:26 +00:00
|
|
|
let contractSnapshot =
|
|
|
|
try:
|
|
|
|
SSZ.loadFile(contractSnapshotFn, DepositTreeSnapshot)
|
|
|
|
except IOError as exc:
|
|
|
|
fatal "Deposit contract snapshot failed to load",
|
|
|
|
fileName = contractSnapshotFn, exc = exc.msg
|
|
|
|
quit 1
|
|
|
|
except SerializationError as exc:
|
|
|
|
fatal "Deposit contract snapshot malformed",
|
|
|
|
fileName = contractSnapshotFn, exc = exc.msg
|
|
|
|
quit 1
|
2021-11-10 11:39:08 +00:00
|
|
|
(res, contractSnapshot)
|
2020-05-01 15:51:24 +00:00
|
|
|
else:
|
2024-02-17 19:23:26 +00:00
|
|
|
warn "Genesis file not found, making one up",
|
|
|
|
hint = "use nimbus_beacon_node createTestnet to make one"
|
2020-05-01 15:51:24 +00:00
|
|
|
|
2024-02-17 19:23:26 +00:00
|
|
|
info "Preparing validators..."
|
2020-05-01 15:51:24 +00:00
|
|
|
let
|
|
|
|
flags = if validate: {} else: {skipBlsValidation}
|
2020-07-13 14:44:58 +00:00
|
|
|
deposits = makeInitialDeposits(validators.uint64, flags)
|
2020-05-01 15:51:24 +00:00
|
|
|
|
2024-02-17 19:23:26 +00:00
|
|
|
info "Generating Genesis..."
|
2020-12-03 04:30:35 +00:00
|
|
|
var merkleizer = init DepositsMerkleizer
|
|
|
|
for d in deposits:
|
|
|
|
merkleizer.addChunk hash_tree_root(d).data
|
2022-12-07 10:24:51 +00:00
|
|
|
let contractSnapshot = DepositTreeSnapshot(
|
2020-12-03 04:30:35 +00:00
|
|
|
depositContractState: merkleizer.toDepositContractState)
|
2020-05-01 15:51:24 +00:00
|
|
|
|
2021-11-18 12:02:43 +00:00
|
|
|
let res = (ref ForkedHashedBeaconState)(
|
2023-08-21 09:10:15 +00:00
|
|
|
kind: ConsensusFork.Capella,
|
|
|
|
capellaData: capella.HashedBeaconState(
|
|
|
|
data: initialize_beacon_state_from_eth1(
|
|
|
|
cfg, ZERO_HASH, 0, deposits,
|
|
|
|
default(capella.ExecutionPayloadHeader), {skipBlsValidation})))
|
2020-05-01 15:51:24 +00:00
|
|
|
|
2024-02-17 19:23:26 +00:00
|
|
|
info "Saving genesis file", fileName = genesisFn
|
|
|
|
try:
|
|
|
|
SSZ.saveFile(genesisFn, res.capellaData.data)
|
|
|
|
except IOError as exc:
|
|
|
|
fatal "Genesis file failed to save",
|
|
|
|
fileName = genesisFn, exc = exc.msg
|
|
|
|
quit 1
|
|
|
|
info "Saving deposit contract snapshot", fileName = contractSnapshotFn
|
|
|
|
try:
|
|
|
|
SSZ.saveFile(contractSnapshotFn, contractSnapshot)
|
|
|
|
except IOError as exc:
|
|
|
|
fatal "Deposit contract snapshot failed to save",
|
|
|
|
fileName = contractSnapshotFn, exc = exc.msg
|
|
|
|
quit 1
|
2020-06-16 20:34:34 +00:00
|
|
|
|
2020-12-03 04:30:35 +00:00
|
|
|
(res, contractSnapshot)
|
2020-05-01 15:51:24 +00:00
|
|
|
|
|
|
|
proc printTimers*[Timers: enum](
|
2024-02-17 19:23:26 +00:00
|
|
|
validate: bool,
|
|
|
|
timers: array[Timers, RunningStat]) =
|
|
|
|
func fmtTime(t: float): string =
|
|
|
|
try:
|
|
|
|
&"{t * 1000 :>12.3f}, "
|
|
|
|
except ValueError as exc:
|
|
|
|
raiseAssert "formatValue failed unexpectedly: " & $exc.msg
|
|
|
|
|
|
|
|
try:
|
|
|
|
echo "All time are ms"
|
|
|
|
echo &"{\"Average\" :>12}, {\"StdDev\" :>12}, {\"Min\" :>12}, " &
|
|
|
|
&"{\"Max\" :>12}, {\"Samples\" :>12}, {\"Test\" :>12}"
|
|
|
|
|
|
|
|
if not validate:
|
|
|
|
echo "Validation is turned off; no BLS operations are performed"
|
|
|
|
|
|
|
|
for t in Timers:
|
|
|
|
echo fmtTime(timers[t].mean), fmtTime(timers[t].standardDeviationS),
|
|
|
|
fmtTime(timers[t].min), fmtTime(timers[t].max), &"{timers[t].n :>12}, ",
|
|
|
|
$t
|
|
|
|
except ValueError as exc:
|
|
|
|
raiseAssert "formatValue failed unexpectedly: " & $exc.msg
|
2020-05-28 14:19:25 +00:00
|
|
|
|
2021-04-16 08:49:37 +00:00
|
|
|
proc printTimers*[Timers: enum](
|
2021-06-11 17:51:46 +00:00
|
|
|
state: ForkedHashedBeaconState, attesters: RunningStat, validate: bool,
|
2021-04-16 08:49:37 +00:00
|
|
|
timers: array[Timers, RunningStat]) =
|
2024-02-17 19:23:26 +00:00
|
|
|
echo "Validators: ", getStateField(state, validators).len,
|
|
|
|
", epoch length: ", SLOTS_PER_EPOCH
|
2021-04-16 08:49:37 +00:00
|
|
|
echo "Validators per attestation (mean): ", attesters.mean
|
|
|
|
printTimers(validate, timers)
|