nimbus-eth2/research/state_sim.nim

210 lines
6.9 KiB
Nim
Raw Normal View History

# beacon_chain
# Copyright (c) 2019-2020 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.
import
confutils, stats, times, std/monotimes,
strformat,
options, sequtils, random, tables,
../tests/[testblockutil],
../beacon_chain/spec/[beaconstate, crypto, datatypes, digest, helpers, validator],
../beacon_chain/[attestation_pool, extras, ssz]
2019-03-04 13:04:26 +00:00
type Timers = enum
tBlock = "Process non-epoch slot with block"
2019-03-23 04:55:41 +00:00
tEpoch = "Process epoch slot with block"
2019-03-04 13:04:26 +00:00
tHashBlock = "Tree-hash block"
tShuffle = "Retrieve committee once using get_beacon_committee"
2019-03-04 13:04:26 +00:00
tAttest = "Combine committee attestations"
template withTimer(stats: var RunningStat, body: untyped) =
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
proc jsonName(prefix, slot: auto): string =
fmt"{prefix:04}-{shortLog(slot):08}.json"
proc writeJson*(fn, v: auto) =
var f: File
defer: close(f)
Json.saveFile(fn, v, pretty = true)
func verifyConsensus(state: BeaconState, attesterRatio: auto) =
if attesterRatio < 0.63:
doAssert state.current_justified_checkpoint.epoch == 0
doAssert 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 state.current_justified_checkpoint.epoch + 1 >= current_epoch
if current_epoch >= 4:
doAssert state.finalized_checkpoint.epoch + 2 >= current_epoch
cli do(slots = SLOTS_PER_EPOCH * 6,
validators = SLOTS_PER_EPOCH * 30, # One per shard is minimum
json_interval = SLOTS_PER_EPOCH,
write_last_json = false,
prefix = 0,
attesterRatio {.desc: "ratio of validators that attest in each round"} = 0.73,
validate = true):
echo "Preparing validators..."
let
# TODO should we include other skipXXXValidation flags here (e.g. StateRoot)?
flags = if validate: {} else: {skipBlsValidation}
deposits = makeInitialDeposits(validators, flags)
echo "Generating Genesis..."
let
state = initialize_beacon_state_from_eth1(Eth2Digest(), 0, deposits, flags)
let genesisBlock = get_initial_beacon_block(state[])
echo "Starting simulation..."
var
attestations = initTable[Slot, seq[Attestation]]()
2019-12-16 18:08:50 +00:00
latest_block_root = hash_tree_root(genesisBlock.message)
2019-03-04 13:04:26 +00:00
timers: array[Timers, RunningStat]
attesters: RunningStat
r: Rand
signedBlock: SignedBeaconBlock
cache = get_empty_per_epoch_cache()
proc maybeWrite(last: bool) =
if write_last_json:
if state.slot mod json_interval.uint64 == 0:
write(stdout, ":")
else:
write(stdout, ".")
if last:
writeJson("state.json", state[])
else:
if state[].slot mod json_interval.uint64 == 0:
writeJson(jsonName(prefix, state.slot), state[])
write(stdout, ":")
else:
write(stdout, ".")
# TODO doAssert against this up-front
# indexed attestation: validator index beyond max validators per committee
# len(indices) <= MAX_VALIDATORS_PER_COMMITTEE
2019-03-04 13:04:26 +00:00
for i in 0..<slots:
maybeWrite(false)
verifyConsensus(state[], attesterRatio)
2019-03-04 13:04:26 +00:00
let
attestations_idx = state.slot
blockAttestations = attestations.getOrDefault(attestations_idx)
attestations.del attestations_idx
doAssert len(attestations) <=
(SLOTS_PER_EPOCH.int + MIN_ATTESTATION_INCLUSION_DELAY.int)
2019-03-04 13:04:26 +00:00
let t =
2019-03-23 04:52:45 +00:00
if (state.slot > GENESIS_SLOT and
(state.slot + 1) mod SLOTS_PER_EPOCH == 0): tEpoch
2019-03-04 13:04:26 +00:00
else: tBlock
withTimer(timers[t]):
signedBlock = addTestBlock(
state[], latest_block_root, attestations = blockAttestations, flags = flags)
2019-03-04 13:04:26 +00:00
latest_block_root = withTimerRet(timers[tHashBlock]):
hash_tree_root(signedBlock.message)
if attesterRatio > 0.0:
# attesterRatio is the fraction of attesters that actually do their
# work for every slot - we'll randomize it deterministically to give
# some variation
let
target_slot = state.slot + MIN_ATTESTATION_INCLUSION_DELAY - 1
scass = withTimerRet(timers[tShuffle]):
mapIt(
0'u64 ..< get_committee_count_at_slot(state[], target_slot),
get_beacon_committee(state[], target_slot, it.CommitteeIndex, cache))
for i, scas in scass:
var
attestation: Attestation
first = true
attesters.push scas.len()
2019-03-04 13:04:26 +00:00
withTimer(timers[tAttest]):
for v in scas:
2019-03-04 13:04:26 +00:00
if (rand(r, high(int)).float * attesterRatio).int <= high(int):
if first:
attestation =
makeAttestation(state[], latest_block_root, scas, target_slot,
i.uint64, v, cache, flags)
2019-03-04 13:04:26 +00:00
first = false
else:
attestation.combine(
makeAttestation(state[], latest_block_root, scas, target_slot,
i.uint64, v, cache, flags),
flags)
if not first:
# add the attestation if any of the validators attested, as given
# by the randomness. We have to delay when the attestation is
# actually added to the block per the attestation delay rule!
let target_slot =
attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY - 1
doAssert target_slot > attestations_idx
var target_slot_attestations =
getOrDefault(attestations, target_slot)
target_slot_attestations.add attestation
attestations[target_slot] = target_slot_attestations
flushFile(stdout)
2019-03-04 13:04:26 +00:00
if (state.slot) mod SLOTS_PER_EPOCH == 0:
2019-08-15 16:01:55 +00:00
echo &" slot: {shortLog(state.slot)} ",
initial 0.9.0 spec sync (#509) * rename compute_epoch_of_slot(...) to compute_epoch_at_slot(...) * remove some unnecessary imports; remove some crosslink-related code and tests; complete renaming of compute_epoch_of_slot(...) to compute_epoch_at_slot(...) * rm more transfer-related code and tests; rm more unnecessary strutils imports * rm remaining unused imports * remove useless get_empty_per_epoch_cache(...)/compute_start_slot_of_epoch(...) calls * rename compute_start_slot_of_epoch(...) to compute_start_slot_at_epoch(...) * rename ACTIVATION_EXIT_DELAY to MAX_SEED_LOOKAHEAD * update domain types to 0.9.0 * mark AttesterSlashing, IndexedAttestation, AttestationDataAndCustodyBit, DepositData, BeaconBlockHeader, Fork, integer_squareroot(...), and process_voluntary_exit(...) as 0.9.0 * mark increase_balance(...), decrease_balance(...), get_block_root(...), CheckPoint, Deposit, PendingAttestation, HistoricalBatch, is_active_validator(...), and is_slashable_attestation_data(...) as 0.9.0 * mark compute_activation_exit_epoch(...), bls_verify(...), Validator, get_active_validator_indices(...), get_current_epoch(...), get_total_active_balance(...), and get_previous_epoch(...) as 0.9.0 * mark get_block_root_at_slot(...), ProposerSlashing, get_domain(...), VoluntaryExit, mainnet preset Gwei values, minimal preset max operations, process_block_header(...), and is_slashable_validator(...) as 0.9.0 * mark makeWithdrawalCredentials(...), get_validator_churn_limit(...), get_total_balance(...), is_valid_indexed_attestation(...), bls_aggregate_pubkeys(...), initial genesis value/constants, Attestation, get_randao_mix(...), mainnet preset max operations per block constants, minimal preset Gwei values and time parameters, process_eth1_data(...), get_shuffled_seq(...), compute_committee(...), and process_slots(...) as 0.9.0; partially update get_indexed_attestation(...) to 0.9.0 by removing crosslink refs and associated tests * mark initiate_validator_exit(...), process_registry_updates(...), BeaconBlock, Eth1Data, compute_domain(...), process_randao(...), process_attester_slashing(...), get_base_reward(...), and process_slot(...) as 0.9.0
2019-10-30 19:41:19 +00:00
&"epoch: {shortLog(state.slot.compute_epoch_at_slot)}"
2019-03-04 13:04:26 +00:00
maybeWrite(true) # catch that last state as well..
echo "Done!"
2019-03-04 13:04:26 +00:00
echo "Validators: ", validators, ", epoch length: ", SLOTS_PER_EPOCH
echo "Validators per attestation (mean): ", attesters.mean
proc fmtTime(t: float): string = &"{t * 1000 :>12.3f}, "
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 meaning that no BLS operations are performed"
2019-03-04 13:04:26 +00:00
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