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:
Jacek Sieka 2018-12-21 16:37:46 -06:00 committed by GitHub
parent abb199d6dc
commit 04314589ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 224 additions and 172 deletions

View File

@ -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") =

View File

@ -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,

View File

@ -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

View File

@ -42,9 +42,10 @@ 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
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..
@ -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:
func hash_tree_root*[T: seq|array](x: T): array[32, byte] =
## 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: object|tuple](x: T): array[32, byte] =
## Containers have their fields recursively hashed, concatenated and hashed
withHash:
for name, value in fields.sortedByIt(it.name):
h.update value.value
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)

View File

@ -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,9 +794,8 @@ 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
@ -782,35 +803,26 @@ proc updateState*(state: BeaconState, latest_block: BeaconBlock,
# 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)
if processBlock(new_state, new_block.get(), flags):
# Block ok so far, proceed with state update
processEpoch(new_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)
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)
false
else:
false
(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)
true
# State update never fails, but block validation might...
(new_state, block_ok)
(new_state, true)

1
research/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.json

46
research/state_sim.nim Normal file
View 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)

View File

@ -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

View File

@ -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)