mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-02-23 11:48:33 +00:00
spec updates (#45)
* spec updates * random small updates * ssz no longer sorts by field, fix enum serialization * rewire block processing a little to avoid a few state copies * add a state simulation tool that writes out jsons
This commit is contained in:
parent
abb199d6dc
commit
04314589ff
@ -3,8 +3,11 @@ version = "0.0.1"
|
||||
author = "Status Research & Development GmbH"
|
||||
description = "Eth2.0 research implementation of the beacon chain"
|
||||
license = "MIT or Apache License 2.0"
|
||||
installDirs = @["beacon_chain"]
|
||||
bin = @["beacon_chain/beacon_node", "beacon_chain/validator_keygen"]
|
||||
installDirs = @["beacon_chain", "research"]
|
||||
bin = @[
|
||||
"beacon_chain/beacon_node",
|
||||
"beacon_chain/validator_keygen",
|
||||
"research/state_sim"]
|
||||
|
||||
### Dependencies
|
||||
requires "nim >= 0.18.0",
|
||||
@ -18,7 +21,8 @@ requires "nim >= 0.18.0",
|
||||
"confutils",
|
||||
"serialization",
|
||||
"json_serialization",
|
||||
"json_rpc"
|
||||
"json_rpc",
|
||||
"cligen 0.9.18"
|
||||
|
||||
### Helper functions
|
||||
proc test(name: string, defaultLang = "c") =
|
||||
|
@ -178,10 +178,6 @@ func get_initial_beacon_state*(
|
||||
validator_registry_exit_count: 0,
|
||||
validator_registry_delta_chain_tip: ZERO_HASH,
|
||||
|
||||
# Randomness and committees
|
||||
randao_mix: ZERO_HASH,
|
||||
next_seed: ZERO_HASH,
|
||||
|
||||
# Finality
|
||||
previous_justified_slot: INITIAL_SLOT_NUMBER,
|
||||
justified_slot: INITIAL_SLOT_NUMBER,
|
||||
@ -228,10 +224,9 @@ func get_initial_beacon_state*(
|
||||
|
||||
func get_block_root*(state: BeaconState,
|
||||
slot: uint64): Eth2Digest =
|
||||
doAssert slot + len(state.latest_block_roots).uint64 > state.slot
|
||||
doAssert state.slot <= slot + LATEST_BLOCK_ROOTS_LENGTH
|
||||
doAssert slot < state.slot
|
||||
state.latest_block_roots[
|
||||
(slot + len(state.latest_block_roots).uint64 - state.slot).int]
|
||||
state.latest_block_roots[slot mod LATEST_BLOCK_ROOTS_LENGTH]
|
||||
|
||||
func get_attestation_participants*(state: BeaconState,
|
||||
attestation_data: AttestationData,
|
||||
|
@ -67,6 +67,7 @@ const
|
||||
|
||||
MAX_CASPER_VOTES* = 2^10
|
||||
LATEST_BLOCK_ROOTS_LENGTH* = 2'u64^13
|
||||
LATEST_RANDAO_MIXES_LENGTH* = 2'u64^13
|
||||
|
||||
MIN_BALANCE* = 2'u64^4 ##\
|
||||
## Minimum balance in ETH before a validator is removed from the validator
|
||||
@ -268,9 +269,7 @@ type
|
||||
## For light clients to easily track delta
|
||||
|
||||
# Randomness and committees
|
||||
randao_mix*: Eth2Digest
|
||||
next_seed*: Eth2Digest ##\
|
||||
## Randao seed used for next shuffling
|
||||
latest_randao_mixes*: array[LATEST_BLOCK_ROOTS_LENGTH.int, Eth2Digest]
|
||||
|
||||
shard_committees_at_slots*: array[2 * EPOCH_LENGTH, seq[ShardCommittee]] ## \
|
||||
## Committee members and their assigned shard, per slot, covers 2 cycles
|
||||
|
@ -42,15 +42,16 @@ func toBytesSSZ(x: Eth2Digest): array[32, byte] = x.data
|
||||
# https://github.com/ethereum/eth2.0-specs/issues/308#issuecomment-447026815
|
||||
func toBytesSSZ(x: ValidatorPubKey|ValidatorSig): auto = x.getRaw()
|
||||
|
||||
type TrivialTypes =
|
||||
# Types that serialize down to a fixed-length array - most importantly, these
|
||||
# values don't carry a length prefix in the final encoding. toBytesSSZ
|
||||
# provides the actual nim-type-to-bytes conversion.
|
||||
# TODO think about this for a bit - depends where the serialization of
|
||||
# validator keys ends up going..
|
||||
# TODO can't put ranges like Uint24 in here:
|
||||
# https://github.com/nim-lang/Nim/issues/10027
|
||||
SomeInteger | EthAddress | Eth2Digest | ValidatorPubKey | ValidatorSig
|
||||
type
|
||||
TrivialTypes =
|
||||
# Types that serialize down to a fixed-length array - most importantly,
|
||||
# these values don't carry a length prefix in the final encoding. toBytesSSZ
|
||||
# provides the actual nim-type-to-bytes conversion.
|
||||
# TODO think about this for a bit - depends where the serialization of
|
||||
# validator keys ends up going..
|
||||
# TODO can't put ranges like Uint24 in here:
|
||||
# https://github.com/nim-lang/Nim/issues/10027
|
||||
SomeInteger | EthAddress | Eth2Digest | ValidatorPubKey | ValidatorSig
|
||||
|
||||
func sszLen(v: TrivialTypes): int = toBytesSSZ(v).len
|
||||
func sszLen(v: Uint24): int = toBytesSSZ(v).len
|
||||
@ -189,8 +190,6 @@ func serialize[T: not enum](dest: var seq[byte], src: T) =
|
||||
for val in src:
|
||||
serialize dest, val
|
||||
else:
|
||||
# TODO to sort, or not to sort, that is the question:
|
||||
# TODO or.. https://github.com/ethereum/eth2.0-specs/issues/275
|
||||
when defined(debugFieldSizes) and T is (BeaconState | BeaconBlock):
|
||||
# for research/serialized_sizes, remove when appropriate
|
||||
for name, field in src.fieldPairs:
|
||||
@ -216,17 +215,14 @@ proc deserialize*(data: openArray[byte],
|
||||
if not deserialize(ret, offset, data): none(typ)
|
||||
else: some(ret)
|
||||
|
||||
func serialize*[T](value: T): seq[byte] =
|
||||
# TODO Fields should be sorted, but...
|
||||
func serialize*(value: auto): seq[byte] =
|
||||
serialize(result, value)
|
||||
|
||||
# ################### Hashing ###################################
|
||||
|
||||
# Sample hash_tree_root implementation based on:
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/98312f40b5742de6aa73f24e6225ee68277c4614/specs/simple-serialize.md
|
||||
# and
|
||||
# https://github.com/ethereum/beacon_chain/pull/134
|
||||
# Probably wrong - the spec is pretty bare-bones and no test vectors yet
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/a9328157a87451ee4f372df272ece158b386ec41/specs/simple-serialize.md
|
||||
# TODO Probably wrong - the spec is pretty bare-bones and no test vectors yet
|
||||
|
||||
const CHUNK_SIZE = 128
|
||||
|
||||
@ -277,31 +273,26 @@ func hash_tree_root*(x: openArray[byte]): array[32, byte] =
|
||||
## Blobs are hashed
|
||||
hash(x)
|
||||
|
||||
func hash_tree_root*[T: not enum](x: T): array[32, byte] =
|
||||
when T is seq or T is array:
|
||||
## Sequences are tree-hashed
|
||||
merkleHash(x)
|
||||
else:
|
||||
## Containers have their fields recursively hashed, concatenated and hashed
|
||||
# TODO could probaby compile-time-macro-sort fields...
|
||||
# TODO or.. https://github.com/ethereum/eth2.0-specs/issues/275
|
||||
var fields: seq[tuple[name: string, value: seq[byte]]]
|
||||
for name, field in x.fieldPairs:
|
||||
fields.add (name, @(hash_tree_root(field)))
|
||||
func hash_tree_root*[T: seq|array](x: T): array[32, byte] =
|
||||
## Sequences are tree-hashed
|
||||
merkleHash(x)
|
||||
|
||||
withHash:
|
||||
for name, value in fields.sortedByIt(it.name):
|
||||
h.update value.value
|
||||
func hash_tree_root*[T: object|tuple](x: T): array[32, byte] =
|
||||
## Containers have their fields recursively hashed, concatenated and hashed
|
||||
withHash:
|
||||
for field in x.fields:
|
||||
h.update hash_tree_root(field)
|
||||
|
||||
# #################################
|
||||
# hash_tree_root not part of official spec
|
||||
func hash_tree_root*(x: enum): array[32, byte] =
|
||||
func hash_tree_root*(x: enum): array[8, byte] =
|
||||
## TODO - Warning ⚠️: not part of the spec
|
||||
## as of https://github.com/ethereum/beacon_chain/pull/133/files
|
||||
## This is a "stub" needed for BeaconBlock hashing
|
||||
static: assert x.sizeof == 1 # Check that the enum fits in 1 byte
|
||||
withHash:
|
||||
h.update [uint8 x]
|
||||
# TODO We've put enums where the spec uses `uint64` - maybe we should not be
|
||||
# using enums?
|
||||
hash_tree_root(uint64(x))
|
||||
|
||||
func hash_tree_root*(x: ValidatorPubKey): array[32, byte] =
|
||||
## TODO - Warning ⚠️: not part of the spec
|
||||
@ -363,4 +354,4 @@ func merkleHash[T](lst: openArray[T]): array[32, byte] =
|
||||
|
||||
chunkz.setLen(chunkz.len div 2)
|
||||
|
||||
result = hash(chunkz[0], dataLen)
|
||||
hash(chunkz[0], dataLen)
|
||||
|
@ -38,6 +38,12 @@ import
|
||||
./spec/[beaconstate, crypto, datatypes, digest, helpers, validator],
|
||||
milagro_crypto
|
||||
|
||||
type
|
||||
UpdateFlag* = enum
|
||||
skipValidation
|
||||
|
||||
UpdateFlags* = set[UpdateFlag]
|
||||
|
||||
func flatten[T](v: openArray[seq[T]]): seq[T] =
|
||||
# TODO not in nim - doh.
|
||||
for x in v: result.add x
|
||||
@ -66,7 +72,8 @@ func verifyProposerSignature(state: BeaconState, blck: BeaconBlock): bool =
|
||||
proposal_hash, blck.signature,
|
||||
get_domain(state.fork_data, state.slot, DOMAIN_PROPOSAL))
|
||||
|
||||
func processRandao(state: var BeaconState, blck: BeaconBlock, force: bool): bool =
|
||||
func processRandao(
|
||||
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags): bool =
|
||||
## When a validator signs up, they will commit an hash to the block,
|
||||
## the randao_commitment - this hash is the result of a secret value
|
||||
## hashed n times.
|
||||
@ -84,15 +91,16 @@ func processRandao(state: var BeaconState, blck: BeaconBlock, force: bool): bool
|
||||
proposer_index = get_beacon_proposer_index(state, state.slot)
|
||||
proposer = addr state.validator_registry[proposer_index]
|
||||
|
||||
if not force:
|
||||
if skipValidation notin flags:
|
||||
# Check that proposer commit and reveal match
|
||||
if repeat_hash(blck.randao_reveal, proposer.randao_layers) !=
|
||||
proposer.randao_commitment:
|
||||
return false
|
||||
|
||||
# Update state and proposer now that we're alright
|
||||
for i, b in state.randao_mix.data:
|
||||
state.randao_mix.data[i] = b xor blck.randao_reveal.data[i]
|
||||
let mix = state.slot mod LATEST_RANDAO_MIXES_LENGTH
|
||||
for i, b in state.latest_randao_mixes[mix].data:
|
||||
state.latest_randao_mixes[mix].data[i] = b xor blck.randao_reveal.data[i]
|
||||
|
||||
proposer.randao_commitment = blck.randao_reveal
|
||||
proposer.randao_layers = 0
|
||||
@ -301,7 +309,7 @@ proc process_ejections(state: var BeaconState) =
|
||||
if is_active_validator(validator) and validator.balance < EJECTION_BALANCE:
|
||||
update_validator_status(state, index.Uint24, EXITED_WITHOUT_PENALTY)
|
||||
|
||||
func processSlot(state: var BeaconState, latest_block: BeaconBlock) =
|
||||
func processSlot(state: var BeaconState, previous_block_root: Eth2Digest) =
|
||||
## Time on the beacon chain moves in slots. Every time we make it to a new
|
||||
## slot, a proposer cleates a block to represent the state of the beacon
|
||||
## chain at that time. In case the proposer is missing, it may happen that
|
||||
@ -312,20 +320,16 @@ func processSlot(state: var BeaconState, latest_block: BeaconBlock) =
|
||||
state.slot += 1
|
||||
state.validator_registry[
|
||||
get_beacon_proposer_index(state, state.slot)].randao_layers += 1
|
||||
|
||||
let
|
||||
previous_block_root = Eth2Digest(data: hash_tree_root(latest_block))
|
||||
for i in 0 ..< state.latest_block_roots.len - 1:
|
||||
state.latest_block_roots[i] = state.latest_block_roots[i + 1]
|
||||
state.latest_block_roots[state.latest_block_roots.len - 1] =
|
||||
state.latest_randao_mixes[state.slot mod LATEST_RANDAO_MIXES_LENGTH] =
|
||||
state.latest_randao_mixes[(state.slot - 1) mod LATEST_RANDAO_MIXES_LENGTH]
|
||||
state.latest_block_roots[(state.slot - 1) mod LATEST_BLOCK_ROOTS_LENGTH] =
|
||||
previous_block_root
|
||||
|
||||
if state.slot mod LATEST_BLOCK_ROOTS_LENGTH == 0:
|
||||
state.batched_block_roots.add(merkle_root(state.latest_block_roots))
|
||||
|
||||
|
||||
proc processBlock(
|
||||
state: var BeaconState, blck: BeaconBlock, force: bool): bool =
|
||||
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags): bool =
|
||||
## When there's a new block, we need to verify that the block is sane and
|
||||
## update the state accordingly
|
||||
|
||||
@ -336,12 +340,16 @@ proc processBlock(
|
||||
warn("Unexpected block slot number")
|
||||
return false
|
||||
|
||||
# TODO does this need checking? not in the spec!
|
||||
if not (blck.parent_root == state.latest_block_roots[^1]):
|
||||
# Spec does not have this check explicitly, but requires that this condition
|
||||
# holds - so we give verify it as well - this would happen naturally if
|
||||
# `blck.parent_root` was used in `processSlot` - but that doesn't cut it for
|
||||
# blockless slot processing.
|
||||
if not (blck.parent_root ==
|
||||
state.latest_block_roots[(state.slot - 1) mod LATEST_BLOCK_ROOTS_LENGTH]):
|
||||
warn("Unexpected parent root")
|
||||
return false
|
||||
|
||||
if not force:
|
||||
if skipValidation notin flags:
|
||||
# TODO Technically, we could make processBlock take a generic type instead
|
||||
# of BeaconBlock - we would then have an intermediate `ProposedBlock`
|
||||
# type that omits some fields - this way, the compiler would guarantee
|
||||
@ -350,7 +358,7 @@ proc processBlock(
|
||||
warn("Proposer signature not valid")
|
||||
return false
|
||||
|
||||
if not processRandao(state, blck, force):
|
||||
if not processRandao(state, blck, flags):
|
||||
warn("Randao reveal failed")
|
||||
return false
|
||||
|
||||
@ -689,11 +697,11 @@ func processEpoch(state: var BeaconState) =
|
||||
let next_start_shard =
|
||||
(state.shard_committees_at_slots[^1][^1].shard + 1) mod SHARD_COUNT
|
||||
for i, v in get_new_shuffling(
|
||||
state.next_seed, state.validator_registry, next_start_shard):
|
||||
state.latest_randao_mixes[
|
||||
(state.slot - EPOCH_LENGTH) mod LATEST_RANDAO_MIXES_LENGTH],
|
||||
state.validator_registry, next_start_shard):
|
||||
state.shard_committees_at_slots[i + EPOCH_LENGTH] = v
|
||||
|
||||
state.next_seed = state.randao_mix
|
||||
|
||||
else:
|
||||
# If a validator registry change does NOT happen
|
||||
for i in 0..<EPOCH_LENGTH:
|
||||
@ -707,9 +715,10 @@ func processEpoch(state: var BeaconState) =
|
||||
MIN_VALIDATOR_REGISTRY_CHANGE_INTERVAL or
|
||||
is_power_of_2(slots_since_finality):
|
||||
for i, v in get_new_shuffling(
|
||||
state.next_seed, state.validator_registry, start_shard):
|
||||
state.latest_randao_mixes[
|
||||
(state.slot - EPOCH_LENGTH) mod LATEST_RANDAO_MIXES_LENGTH],
|
||||
state.validator_registry, start_shard):
|
||||
state.shard_committees_at_slots[i + EPOCH_LENGTH] = v
|
||||
state.next_seed = state.randao_mix
|
||||
# Note that `start_shard` is not changed from the last epoch.
|
||||
|
||||
block: # Proposer reshuffling
|
||||
@ -719,9 +728,10 @@ func processEpoch(state: var BeaconState) =
|
||||
for i in 0..<num_validators_to_reshuffle:
|
||||
# Multiplying i to 2 to ensure we have different input to all the required hashes in the shuffling
|
||||
# and none of the hashes used for entropy in this loop will be the same
|
||||
# TODO Modulo of hash value.. hm...
|
||||
let
|
||||
validator_index = 0.Uint24 # active_validator_indices[hash(state.randao_mix + bytes8(i * 2)) mod len(active_validator_indices)]
|
||||
new_shard = 0'u64 # hash(state.randao_mix + bytes8(i * 2 + 1)) mod SHARD_COUNT
|
||||
validator_index = 0.Uint24 # TODO active_validator_indices[hash(state.latest_randao_mixes[state.slot % LATEST_RANDAO_MIXES_LENGTH] + bytes8(i * 2)) % len(active_validator_indices)]
|
||||
new_shard = 0'u64 # TODO hash(state.randao_mix + bytes8(i * 2 + 1)) mod SHARD_COUNT
|
||||
shard_reassignment_record = ShardReassignmentRecord(
|
||||
validator_index: validator_index,
|
||||
shard: new_shard,
|
||||
@ -744,8 +754,17 @@ func processEpoch(state: var BeaconState) =
|
||||
not (it.data.slot + EPOCH_LENGTH < state.slot)
|
||||
)
|
||||
|
||||
proc updateState*(state: BeaconState, latest_block: BeaconBlock,
|
||||
new_block: Option[BeaconBlock], force: bool):
|
||||
proc verifyStateRoot(state: BeaconState, blck: BeaconBlock): bool =
|
||||
let state_root = Eth2Digest(data: hash_tree_root(state))
|
||||
if state_root != blck.state_root:
|
||||
warn("Block: root verification failed",
|
||||
block_state_root = blck.state_root, state_root)
|
||||
false
|
||||
else:
|
||||
true
|
||||
|
||||
proc updateState*(state: BeaconState, previous_block_root: Eth2Digest,
|
||||
new_block: Option[BeaconBlock], flags: UpdateFlags):
|
||||
tuple[state: BeaconState, block_ok: bool] =
|
||||
## Time in the beacon chain moves by slots. Every time (haha.) that happens,
|
||||
## we will update the beacon state. Normally, the state updates will be driven
|
||||
@ -754,13 +773,16 @@ proc updateState*(state: BeaconState, latest_block: BeaconBlock,
|
||||
## Each call to this function will advance the state by one slot - new_block,
|
||||
## if present, must match that slot.
|
||||
##
|
||||
## The `force` flag is used to skip a few checks, and apply `new_block`
|
||||
## regardless. We do this because some fields in the block depend on the state
|
||||
## being updated with the information in the new block.
|
||||
## The flags are used to specify that certain validations should be skipped
|
||||
## for the new block. This is done during block proposal, to create a state
|
||||
## whose hash can be included in the new block.
|
||||
#
|
||||
# TODO this function can be written with a loop inside to handle all empty
|
||||
# slots up to the slot of the new_block - but then again, why not eagerly
|
||||
# update the state as time passes? Something to ponder...
|
||||
# One reason to keep it this way is that you need to look ahead if you're
|
||||
# the block proposer, though in reality we only need a partial update for
|
||||
# that
|
||||
# TODO check to which extent this copy can be avoided (considering forks etc),
|
||||
# for now, it serves as a reminder that we need to handle invalid blocks
|
||||
# somewhere..
|
||||
@ -772,45 +794,35 @@ proc updateState*(state: BeaconState, latest_block: BeaconBlock,
|
||||
var new_state = state
|
||||
|
||||
# Per-slot updates - these happen regardless if there is a block or not
|
||||
processSlot(new_state, latest_block)
|
||||
processSlot(new_state, previous_block_root)
|
||||
|
||||
let block_ok =
|
||||
if new_block.isSome():
|
||||
# Block updates - these happen when there's a new block being suggested
|
||||
# by the block proposer. Every actor in the network will update its state
|
||||
# according to the contents of this block - but first they will validate
|
||||
# that the block is sane.
|
||||
# TODO what should happen if block processing fails?
|
||||
# https://github.com/ethereum/eth2.0-specs/issues/293
|
||||
var block_state = new_state
|
||||
if processBlock(block_state, new_block.get(), force):
|
||||
# processBlock will mutate the state! only apply if it worked..
|
||||
# TODO yeah, this too is inefficient
|
||||
new_state = block_state
|
||||
processEpoch(block_state)
|
||||
|
||||
# This is a bit awkward - at the end of processing we verify that the
|
||||
# state we arrive at is what the block producer thought it would be -
|
||||
# meaning that potentially, it could fail verification
|
||||
let expected_state_root = Eth2Digest(data: hash_tree_root(block_state))
|
||||
if force or
|
||||
(new_block.get().state_root == expected_state_root):
|
||||
new_state = block_state
|
||||
true
|
||||
else:
|
||||
warn("State: root verification failed",
|
||||
state_root = new_block.get().state_root, expected_state_root)
|
||||
processEpoch(new_state)
|
||||
false
|
||||
else:
|
||||
false
|
||||
else:
|
||||
# Skip all per-block processing. Move directly to epoch processing
|
||||
# prison. Do not do any block updates when passing go.
|
||||
|
||||
# Heavy updates that happen for every epoch - these never fail (or so we hope)
|
||||
if new_block.isSome():
|
||||
# Block updates - these happen when there's a new block being suggested
|
||||
# by the block proposer. Every actor in the network will update its state
|
||||
# according to the contents of this block - but first they will validate
|
||||
# that the block is sane.
|
||||
# TODO what should happen if block processing fails?
|
||||
# https://github.com/ethereum/eth2.0-specs/issues/293
|
||||
if processBlock(new_state, new_block.get(), flags):
|
||||
# Block ok so far, proceed with state update
|
||||
processEpoch(new_state)
|
||||
true
|
||||
|
||||
# State update never fails, but block validation might...
|
||||
(new_state, block_ok)
|
||||
# This is a bit awkward - at the end of processing we verify that the
|
||||
# state we arrive at is what the block producer thought it would be -
|
||||
# meaning that potentially, it could fail verification
|
||||
if skipValidation in flags or verifyStateRoot(new_state, new_block.get()):
|
||||
# State root is what it should be - we're done!
|
||||
return (new_state, true)
|
||||
|
||||
# Block processing failed, have to start over
|
||||
new_state = state
|
||||
processSlot(new_state, previous_block_root)
|
||||
processEpoch(new_state)
|
||||
(new_state, false)
|
||||
else:
|
||||
# Skip all per-block processing. Move directly to epoch processing
|
||||
# prison. Do not do any block updates when passing go.
|
||||
|
||||
# Heavy updates that happen for every epoch - these never fail (or so we hope)
|
||||
processEpoch(new_state)
|
||||
(new_state, true)
|
||||
|
1
research/.gitignore
vendored
Normal file
1
research/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.json
|
46
research/state_sim.nim
Normal file
46
research/state_sim.nim
Normal file
@ -0,0 +1,46 @@
|
||||
import
|
||||
cligen,
|
||||
json, strformat,
|
||||
options, sequtils,
|
||||
../tests/[testutil],
|
||||
../beacon_chain/spec/[beaconstate, crypto, datatypes, digest, helpers],
|
||||
../beacon_chain/[extras, ssz, state_transition]
|
||||
|
||||
proc `%`(v: uint64): JsonNode = newJInt(v.BiggestInt)
|
||||
proc `%`(v: Eth2Digest): JsonNode = newJString($v)
|
||||
|
||||
proc writeJson*(prefix, slot, v: auto) =
|
||||
var f: File
|
||||
defer: close(f)
|
||||
discard open(f, fmt"{prefix:04}-{slot:08}.json", fmWrite)
|
||||
write(f, pretty(%*(v)))
|
||||
|
||||
proc transition(
|
||||
slots = 1945,
|
||||
validators = EPOCH_LENGTH, # One per shard is minimum
|
||||
json_interval = EPOCH_LENGTH,
|
||||
prefix = 0) =
|
||||
let
|
||||
genesisState = get_initial_beacon_state(
|
||||
makeInitialDeposits(validators), 0, Eth2Digest())
|
||||
genesisBlock = makeGenesisBlock(genesisState)
|
||||
|
||||
var
|
||||
state = genesisState
|
||||
latest_block_root = Eth2Digest(data: hash_tree_root(genesisBlock))
|
||||
|
||||
for i in 0..<slots:
|
||||
if state.slot mod json_interval.uint64 == 0:
|
||||
writeJson(prefix, state.slot, state)
|
||||
write(stdout, ":")
|
||||
else:
|
||||
write(stdout, ".")
|
||||
|
||||
latest_block_root = Eth2Digest(data: hash_tree_root(
|
||||
addBlock(state, latest_block_root, BeaconBlockBody())))
|
||||
|
||||
flushFile(stdout)
|
||||
|
||||
echo "done!"
|
||||
|
||||
dispatch(transition)
|
@ -9,7 +9,7 @@ import
|
||||
options, sequtils, unittest,
|
||||
./testutil,
|
||||
../beacon_chain/spec/[beaconstate, crypto, datatypes, digest, helpers],
|
||||
../beacon_chain/[extras, state_transition]
|
||||
../beacon_chain/[extras, state_transition, ssz]
|
||||
|
||||
suite "Block processing":
|
||||
## For now just test that we can compile and execute block processing with
|
||||
@ -25,78 +25,68 @@ suite "Block processing":
|
||||
test "Passes from genesis state, no block":
|
||||
let
|
||||
state = genesisState
|
||||
latest_block = genesisBlock
|
||||
new_state = updateState(state, latest_block, none(BeaconBlock), false)
|
||||
proposer_index = getNextBeaconProposerIndex(state)
|
||||
previous_block_root = Eth2Digest(data: hash_tree_root(genesisBlock))
|
||||
new_state = updateState(
|
||||
state, previous_block_root, none(BeaconBlock), {})
|
||||
check:
|
||||
new_state.state.slot == latest_block.slot + 1
|
||||
new_state.block_ok
|
||||
|
||||
new_state.state.slot == state.slot + 1
|
||||
|
||||
# When proposer skips their proposal, randao layer is still peeled!
|
||||
new_state.state.validator_registry[proposer_index].randao_layers ==
|
||||
state.validator_registry[proposer_index].randao_layers + 1
|
||||
|
||||
test "Passes from genesis state, empty block":
|
||||
let
|
||||
state = genesisState
|
||||
latest_block = genesisBlock
|
||||
new_block = makeBlock(state, latest_block)
|
||||
new_state = updateState(state, latest_block, some(new_block), false)
|
||||
proposer_index = getNextBeaconProposerIndex(state)
|
||||
previous_block_root = Eth2Digest(data: hash_tree_root(genesisBlock))
|
||||
new_block = makeBlock(state, previous_block_root, BeaconBlockBody())
|
||||
new_state = updateState(
|
||||
state, previous_block_root, some(new_block), {})
|
||||
|
||||
check:
|
||||
new_state.state.slot == latest_block.slot + 1
|
||||
new_state.block_ok
|
||||
|
||||
new_state.state.slot == state.slot + 1
|
||||
|
||||
# Proposer proposed, no need for additional peeling
|
||||
new_state.state.validator_registry[proposer_index].randao_layers ==
|
||||
state.validator_registry[proposer_index].randao_layers
|
||||
|
||||
test "Passes through epoch update, no block":
|
||||
var
|
||||
state = genesisState
|
||||
latest_block = genesisBlock
|
||||
previous_block_root = Eth2Digest(data: hash_tree_root(genesisBlock))
|
||||
|
||||
for i in 1..EPOCH_LENGTH.int:
|
||||
let new_state = updateState(state, latest_block, none(BeaconBlock), false)
|
||||
let new_state = updateState(
|
||||
state, previous_block_root, none(BeaconBlock), {})
|
||||
check:
|
||||
new_state.block_ok
|
||||
state = new_state.state
|
||||
|
||||
check:
|
||||
state.slot == latest_block.slot + EPOCH_LENGTH
|
||||
state.slot == genesisState.slot + EPOCH_LENGTH
|
||||
|
||||
test "Passes through epoch update, empty block":
|
||||
var
|
||||
state = genesisState
|
||||
latest_block = genesisBlock
|
||||
previous_block_root = Eth2Digest(data: hash_tree_root(genesisBlock))
|
||||
|
||||
for i in 1..EPOCH_LENGTH.int:
|
||||
var new_block = makeBlock(state, latest_block)
|
||||
var new_block = makeBlock(state, previous_block_root, BeaconBlockBody())
|
||||
|
||||
let new_state = updateState(state, latest_block, some(new_block), false)
|
||||
let new_state = updateState(
|
||||
state, previous_block_root, some(new_block), {})
|
||||
|
||||
check:
|
||||
new_state.block_ok
|
||||
state = new_state.state
|
||||
latest_block = new_block
|
||||
if new_state.block_ok:
|
||||
previous_block_root = Eth2Digest(data: hash_tree_root(new_block))
|
||||
|
||||
check:
|
||||
state.slot == latest_block.slot
|
||||
|
||||
test "Increments proposer randao_layers, no block":
|
||||
let
|
||||
state = genesisState
|
||||
latest_block = genesisBlock
|
||||
proposer_index = getNextBeaconProposerIndex(state)
|
||||
previous_randao_layers =
|
||||
state.validator_registry[proposer_index].randao_layers
|
||||
new_state = updateState(state, latest_block, none(BeaconBlock), false)
|
||||
updated_proposer = new_state.state.validator_registry[proposer_index]
|
||||
|
||||
check:
|
||||
updated_proposer.randao_layers == previous_randao_layers + 1
|
||||
|
||||
test "Proposer randao layers unchanged, empty block":
|
||||
let
|
||||
state = genesisState
|
||||
latest_block = genesisBlock
|
||||
proposer_index = getNextBeaconProposerIndex(state)
|
||||
previous_randao_layers =
|
||||
state.validator_registry[proposer_index].randao_layers
|
||||
new_block = makeBlock(state, latest_block)
|
||||
new_state = updateState(state, latest_block, some(new_block), false)
|
||||
updated_proposer = new_state.state.validator_registry[proposer_index]
|
||||
|
||||
check:
|
||||
updated_proposer.randao_layers == previous_randao_layers
|
||||
state.slot == genesisState.slot + EPOCH_LENGTH
|
||||
|
@ -80,11 +80,17 @@ func getNextBeaconProposerIndex*(state: BeaconState): Uint24 =
|
||||
next_state.slot += 1
|
||||
get_beacon_proposer_index(next_state, next_state.slot)
|
||||
|
||||
proc makeBlock*(state: BeaconState, previous_block: BeaconBlock): BeaconBlock =
|
||||
# Create a block for `state.slot + 1` - like a block proposer would do!
|
||||
# It's a bit awkward - in order to produce a block for N+1, we need to
|
||||
# calculate what the state will look like after that block has been applied,
|
||||
# because the block includes the state root.
|
||||
proc addBlock*(
|
||||
state: var BeaconState, previous_block_root: Eth2Digest,
|
||||
body: BeaconBlockBody): BeaconBlock =
|
||||
# Create and add a block to state - state will advance by one slot!
|
||||
# This is the equivalent of running
|
||||
# updateState(state, prev_block, makeBlock(...), {skipValidation})
|
||||
# but avoids some slow block copies
|
||||
|
||||
state.slot += 1
|
||||
let proposer_index = get_beacon_proposer_index(state, state.slot)
|
||||
state.slot -= 1
|
||||
|
||||
let
|
||||
# Index from the new state, but registry from the old state.. hmm...
|
||||
@ -96,10 +102,7 @@ proc makeBlock*(state: BeaconState, previous_block: BeaconBlock): BeaconBlock =
|
||||
# would look with the new block applied.
|
||||
new_block = BeaconBlock(
|
||||
slot: state.slot + 1,
|
||||
|
||||
# TODO is this checked anywhere?
|
||||
# https://github.com/ethereum/eth2.0-specs/issues/336
|
||||
parent_root: Eth2Digest(data: hash_tree_root(previous_block)),
|
||||
parent_root: previous_block_root,
|
||||
state_root: Eth2Digest(), # we need the new state first
|
||||
randao_reveal: hackReveal(proposer),
|
||||
candidate_pow_receipt_root: Eth2Digest(), # TODO
|
||||
@ -107,13 +110,14 @@ proc makeBlock*(state: BeaconState, previous_block: BeaconBlock): BeaconBlock =
|
||||
body: BeaconBlockBody() # TODO throw in stuff here...
|
||||
)
|
||||
|
||||
let
|
||||
next_state = updateState(state, previous_block, some(new_block), true)
|
||||
assert next_state.block_ok
|
||||
var block_ok: bool
|
||||
(state, block_ok) = updateState(
|
||||
state, previous_block_root, some(new_block), {skipValidation})
|
||||
assert block_ok
|
||||
|
||||
# Ok, we have the new state as it would look with the block applied - now we
|
||||
# can set the state root in order to be able to create a valid signature
|
||||
new_block.state_root = Eth2Digest(data: hash_tree_root(next_state.state))
|
||||
new_block.state_root = Eth2Digest(data: hash_tree_root(state))
|
||||
|
||||
let
|
||||
proposerPrivkey = hackPrivKey(proposer)
|
||||
@ -142,3 +146,13 @@ proc makeBlock*(state: BeaconState, previous_block: BeaconBlock): BeaconBlock =
|
||||
"we just signed this message - it should pass verification!"
|
||||
|
||||
new_block
|
||||
|
||||
proc makeBlock*(
|
||||
state: BeaconState, previous_block_root: Eth2Digest,
|
||||
body: BeaconBlockBody): BeaconBlock =
|
||||
# Create a block for `state.slot + 1` - like a block proposer would do!
|
||||
# It's a bit awkward - in order to produce a block for N+1, we need to
|
||||
# calculate what the state will look like after that block has been applied,
|
||||
# because the block includes the state root.
|
||||
var next_state = state
|
||||
addBlock(next_state, previous_block_root, body)
|
||||
|
Loading…
x
Reference in New Issue
Block a user