From 6bcefc0e42350fd1ef2b1c6dc04d52b80ef98d1f Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Fri, 8 Mar 2019 10:40:17 -0600 Subject: [PATCH] verify blocks before storing in block pool / database (#158) * fix state db lookup typo * fix randao reveal slot when proposing blocks * only store blocks that can be applied to a state * store state at every epoch boundary (yes, needs pruning!) * split out state advancement function when there's no block * default state sim to 0.9 attestation ratio --- beacon_chain/beacon_chain_db.nim | 2 +- beacon_chain/beacon_node.nim | 74 ++++++++++---------- beacon_chain/block_pool.nim | 110 ++++++++++++++++++++++-------- beacon_chain/state_transition.nim | 86 ++++++++++++----------- research/state_sim.nim | 2 +- tests/test_attestation_pool.nim | 10 +-- tests/test_beacon_chain_db.nim | 42 ++++++++++-- tests/test_block_pool.nim | 13 ++-- tests/test_state_transition.nim | 29 +++----- tests/testutil.nim | 4 +- 10 files changed, 226 insertions(+), 146 deletions(-) diff --git a/beacon_chain/beacon_chain_db.nim b/beacon_chain/beacon_chain_db.nim index ebb2b3bbc..224639400 100644 --- a/beacon_chain/beacon_chain_db.nim +++ b/beacon_chain/beacon_chain_db.nim @@ -112,7 +112,7 @@ proc containsBlock*( proc containsState*( db: BeaconChainDB, key: Eth2Digest): bool = - db.backend.contains(subkey(BeaconBlock, key)) + db.backend.contains(subkey(BeaconState, key)) iterator getAncestors*(db: BeaconChainDB, root: Eth2Digest): tuple[root: Eth2Digest, blck: BeaconBlock] = diff --git a/beacon_chain/beacon_node.nim b/beacon_chain/beacon_node.nim index 4268b95c2..82d78ae40 100644 --- a/beacon_chain/beacon_node.nim +++ b/beacon_chain/beacon_node.nim @@ -330,13 +330,13 @@ proc proposeBlock(node: BeaconNode, var newBlock = BeaconBlock( slot: slot, parent_root: node.state.blck.root, - randao_reveal: validator.genRandaoReveal(state, state.slot), + randao_reveal: validator.genRandaoReveal(state, slot), eth1_data: node.mainchainMonitor.getBeaconBlockRef(), signature: ValidatorSig(), # we need the rest of the block first! body: blockBody) let ok = - updateState(state, node.state.blck.root, some(newBlock), {skipValidation}) + updateState(state, node.state.blck.root, newBlock, {skipValidation}) doAssert ok # TODO: err, could this fail somehow? newBlock.state_root = Eth2Digest(data: hash_tree_root(state)) @@ -428,43 +428,40 @@ proc scheduleEpochActions(node: BeaconNode, epoch: Epoch) = epoch = humaneEpochNum(epoch), stateEpoch = humaneEpochNum(node.state.data.slot.slot_to_epoch()) + # In case some late blocks dropped in + node.updateHead() + # Sanity check - verify that the current head block is not too far behind if node.state.data.slot.slot_to_epoch() + 1 < epoch: - # Normally, we update the head state lazily, just before making an - # attestation. However, if we skip scheduling attestations, we'll never - # run the head update - thus we make an attempt now: - node.updateHead() + # We're hopelessly behind! + # + # There's a few ways this can happen: + # + # * we receive no attestations or blocks for an extended period of time + # * all the attestations we receive are bogus - maybe we're connected to + # the wrong network? + # * we just started and still haven't synced + # + # TODO make an effort to find other nodes and sync? A worst case scenario + # here is that the network stalls because nobody is sending out + # attestations because nobody is scheduling them, in a vicious + # circle + # TODO diagnose the various scenarios and do something smart... - if node.state.data.slot.slot_to_epoch() + 1 < epoch: - # We're still behind! - # - # There's a few ways this can happen: - # - # * we receive no attestations or blocks for an extended period of time - # * all the attestations we receive are bogus - maybe we're connected to - # the wrong network? - # * we just started and still haven't synced - # - # TODO make an effort to find other nodes and sync? A worst case scenario - # here is that the network stalls because nobody is sending out - # attestations because nobody is scheduling them, in a vicious - # circle - # TODO diagnose the various scenarios and do something smart... + let + expectedSlot = node.state.data.getSlotFromTime() + nextSlot = expectedSlot + 1 + at = node.slotStart(nextSlot) - let - expectedSlot = node.state.data.getSlotFromTime() - nextSlot = expectedSlot + 1 - at = node.slotStart(nextSlot) + notice "Delaying epoch scheduling, head too old - scheduling new attempt", + stateSlot = humaneSlotNum(node.state.data.slot), + expectedEpoch = humaneEpochNum(epoch), + expectedSlot = humaneSlotNum(expectedSlot), + fromNow = (at - fastEpochTime()) div 1000 - notice "Delaying epoch scheduling, head too old - scheduling new attempt", - stateSlot = humaneSlotNum(node.state.data.slot), - expectedEpoch = humaneEpochNum(epoch), - expectedSlot = humaneSlotNum(expectedSlot), - fromNow = (at - fastEpochTime()) div 1000 - - addTimer(at) do (p: pointer): - node.scheduleEpochActions(nextSlot.slot_to_epoch()) - return + addTimer(at) do (p: pointer): + node.scheduleEpochActions(nextSlot.slot_to_epoch()) + return # TODO: is this necessary with the new shuffling? # see get_beacon_proposer_index @@ -580,7 +577,14 @@ proc onBeaconBlock(node: BeaconNode, blck: BeaconBlock) = voluntary_exits = blck.body.voluntary_exits.len, transfers = blck.body.transfers.len - if not node.blockPool.add(blockRoot, blck): + var + # TODO We could avoid this copy by having node.state as a general cache + # that just holds a random recent state - that would however require + # rethinking scheduling etc, which relies on there being a fairly + # accurate representation of the state available. Notably, when there's + # a reorg, the scheduling might change! + stateTmp = node.state + if not node.blockPool.add(stateTmp, blockRoot, blck): # TODO the fact that add returns a bool that causes the parent block to be # pre-emptively fetched is quite ugly - fix. node.fetchBlocks(@[blck.parent_root]) diff --git a/beacon_chain/block_pool.nim b/beacon_chain/block_pool.nim index d03a72ff5..afb169a3f 100644 --- a/beacon_chain/block_pool.nim +++ b/beacon_chain/block_pool.nim @@ -1,7 +1,7 @@ import bitops, chronicles, options, sequtils, sets, tables, ssz, beacon_chain_db, state_transition, extras, - spec/[crypto, datatypes, digest] + spec/[crypto, datatypes, digest, helpers] type BlockPool* = ref object @@ -140,10 +140,18 @@ proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool = db: db ) -proc add*(pool: var BlockPool, blockRoot: Eth2Digest, blck: BeaconBlock): bool = +proc updateState*( + pool: BlockPool, state: var StateData, blck: BlockRef) {.gcsafe.} + +proc add*( + pool: var BlockPool, state: var StateData, blockRoot: Eth2Digest, + blck: BeaconBlock): bool {.gcsafe.} = ## return false indicates that the block parent was missing and should be ## fetched - ## TODO reevaluate this API - it's pretty ugly with the bool return + ## the state parameter may be updated to include the given block, if + ## everything checks out + # TODO reevaluate passing the state in like this + # TODO reevaluate this API - it's pretty ugly with the bool return doAssert blockRoot == hash_tree_root_final(blck) # Already seen this block?? @@ -169,23 +177,40 @@ proc add*(pool: var BlockPool, blockRoot: Eth2Digest, blck: BeaconBlock): bool = return true - # TODO we should now validate the block to ensure that it's sane - but the - # only way to do that is to apply it to the state... for now, we assume - # all blocks are good! let parent = pool.blocks.getOrDefault(blck.parent_root) if parent != nil: - # The block is resolved, nothing more to do! + # The block might have been in either of these - we don't want any more + # work done on its behalf + pool.unresolved.del(blockRoot) + pool.pending.del(blockRoot) + + # The block is resolved, now it's time to validate it to ensure that the + # blocks we add to the database are clean for the given state + updateState(pool, state, parent) + skipSlots(state.data, parent.root, blck.slot - 1) + + if not updateState(state.data, parent.root, blck, {}): + # TODO find a better way to log all this block data + notice "Invalid block", + blockRoot = shortLog(blockRoot), + slot = humaneSlotNum(blck.slot), + stateRoot = shortLog(blck.state_root), + parentRoot = shortLog(blck.parent_root), + signature = shortLog(blck.signature), + proposer_slashings = blck.body.proposer_slashings.len, + attester_slashings = blck.body.attester_slashings.len, + attestations = blck.body.attestations.len, + deposits = blck.body.deposits.len, + voluntary_exits = blck.body.voluntary_exits.len, + transfers = blck.body.transfers.len + let blockRef = BlockRef( root: blockRoot ) link(parent, blockRef) pool.blocks[blockRoot] = blockRef - # The block might have been in either of these - we don't want any more - # work done on its behalf - pool.unresolved.del(blockRoot) - pool.pending.del(blockRoot) # Resolved blocks should be stored in database pool.db.putBlock(blockRoot, blck) @@ -209,7 +234,7 @@ proc add*(pool: var BlockPool, blockRoot: Eth2Digest, blck: BeaconBlock): bool = # running out of stack etc let retries = pool.pending for k, v in retries: - discard pool.add(k, v) + discard pool.add(state, k, v) return true @@ -271,6 +296,8 @@ proc checkUnresolved*(pool: var BlockPool): seq[Eth2Digest] = inc v.tries for k in done: + # TODO Need to potentially remove from pool.pending - this is currently a + # memory leak here! pool.unresolved.del(k) # simple (simplistic?) exponential backoff for retries.. @@ -279,24 +306,43 @@ proc checkUnresolved*(pool: var BlockPool): seq[Eth2Digest] = result.add(k) proc skipAndUpdateState( - state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags): bool = - skipSlots(state, blck.parent_root, blck.slot - 1) - updateState(state, blck.parent_root, some(blck), flags) + state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags, + afterUpdate: proc (state: BeaconState)): bool = + skipSlots(state, blck.parent_root, blck.slot - 1, afterUpdate) + let ok = updateState(state, blck.parent_root, blck, flags) + + afterUpdate(state) + + ok + +proc maybePutState(pool: BlockPool, state: BeaconState) = + # TODO we save state at every epoch start but never remove them - we also + # potentially save multiple states per slot if reorgs happen, meaning + # we could easily see a state explosion + if state.slot mod SLOTS_PER_EPOCH == 0: + info "Storing state", + stateSlot = humaneSlotNum(state.slot), + stateRoot = hash_tree_root_final(state) # TODO cache? + pool.db.putState(state) proc updateState*( pool: BlockPool, state: var StateData, blck: BlockRef) = - if state.blck.root == blck.root: - return # State already at the right spot - - # TODO this blockref should never be created, since we trace every blockref - # back to the tail block - doAssert (not blck.parent.isNil), "trying to apply genesis block!" - + # Rewind or advance state such that it matches the given block - this may + # include replaying from an earlier snapshot if blck is on a different branch + # or has advanced to a higher slot number than blck var ancestors = @[pool.get(blck)] + # We need to check the slot because the state might have moved forwards + # without blocks + if state.blck.root == blck.root and state.data.slot == ancestors[0].data.slot: + return # State already at the right spot + # Common case: blck points to a block that is one step ahead of state - if state.blck.root == blck.parent.root: - let ok = skipAndUpdateState(state.data, ancestors[0].data, {skipValidation}) + if state.blck.root == ancestors[0].data.parent_root and + state.data.slot + 1 == ancestors[0].data.slot: + let ok = skipAndUpdateState( + state.data, ancestors[0].data, {skipValidation}) do (state: BeaconState): + pool.maybePutState(state) doAssert ok, "Blocks in database should never fail to apply.." state.blck = blck state.root = ancestors[0].data.state_root @@ -329,6 +375,7 @@ proc updateState*( notice "Replaying state transitions", stateSlot = humaneSlotNum(state.data.slot), + stateRoot = shortLog(ancestor.data.state_root), prevStateSlot = humaneSlotNum(ancestorState.get().slot), ancestors = ancestors.len @@ -345,18 +392,21 @@ proc updateState*( for i in countdown(ancestors.len - 2, 0): let last = ancestors[i] - skipSlots(state.data, last.data.parent_root, last.data.slot - 1) + skipSlots( + state.data, last.data.parent_root, + last.data.slot - 1) do(state: BeaconState): + pool.maybePutState(state) - # TODO technically, we should be adding states to the database here because - # we're going down a different fork.. let ok = updateState( - state.data, last.data.parent_root, some(last.data), {skipValidation}) - - doAssert(ok) + state.data, last.data.parent_root, last.data, {skipValidation}) + doAssert ok, + "We only keep validated blocks in the database, should never fail" state.blck = blck state.root = ancestors[0].data.state_root + pool.maybePutState(state.data) + proc loadTailState*(pool: BlockPool): StateData = ## Load the state associated with the current tail in the pool StateData( diff --git a/beacon_chain/state_transition.nim b/beacon_chain/state_transition.nim index d297fbac9..d608ff7dc 100644 --- a/beacon_chain/state_transition.nim +++ b/beacon_chain/state_transition.nim @@ -1006,14 +1006,16 @@ proc verifyStateRoot(state: BeaconState, blck: BeaconBlock): bool = else: true -proc updateState*(state: var BeaconState, previous_block_root: Eth2Digest, - new_block: Option[BeaconBlock], flags: UpdateFlags): bool = +proc updateState*( + state: var BeaconState, previous_block_root: Eth2Digest, + new_block: BeaconBlock, flags: UpdateFlags): 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 ## by the contents of a new block, but it may happen that the block goes ## missing - the state updates happen regardless. + ## ## Each call to this function will advance the state by one slot - new_block, - ## if present, must match that slot. + ## must match that slot. If the update fails, the state will remain unchanged. ## ## 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 @@ -1025,59 +1027,67 @@ proc updateState*(state: var BeaconState, previous_block_root: Eth2Digest, # 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 There's a discussion about what this function should do, and when: + # https://github.com/ethereum/eth2.0-specs/issues/284 + # 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.. - # TODO many functions will mutate `state` partially without rolling back + # many functions will mutate `state` partially without rolling back # the changes in case of failure (look out for `var BeaconState` and # bool return values...) - # TODO There's a discussion about what this function should do, and when: - # https://github.com/ethereum/eth2.0-specs/issues/284 var old_state = state # Per-slot updates - these happen regardless if there is a block or not processSlot(state, previous_block_root) - 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(state, new_block.get(), flags): - # Block ok so far, proceed with state update - processEpoch(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 - if skipValidation in flags or verifyStateRoot(state, new_block.get()): - # State root is what it should be - we're done! - return true - - # Block processing failed, have to start over - state = old_state - processSlot(state, previous_block_root) + # 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(state, new_block, flags): + # Block ok so far, proceed with state update processEpoch(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(state) - true + # 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(state, new_block): + # State root is what it should be - we're done! + return true -proc skipSlots*(state: var BeaconState, parentRoot: Eth2Digest, slot: Slot) = + # Block processing failed, roll back changes + state = old_state + false + +proc advanceState*( + state: var BeaconState, previous_block_root: Eth2Digest) = + ## Sometimes we need to update the state even though we don't have a block at + ## hand - this happens for example when a block proposer fails to produce a + ## a block. + # TODO In the current spec, this can fail only when the state is inconsistent + # or buggy - how do we handle that? crash? + + # Per-slot updates - these happen regardless if there is a block or not + processSlot(state, previous_block_root) + + # Heavy updates that happen for every epoch - these never fail (or so we hope) + processEpoch(state) + +proc skipSlots*(state: var BeaconState, parentRoot: Eth2Digest, slot: Slot, + afterSlot: proc (state: BeaconState) = nil) = if state.slot < slot: - info "Advancing state past slot gap", + debug "Advancing state past slot gap", targetSlot = humaneSlotNum(slot), stateSlot = humaneSlotNum(state.slot) while state.slot < slot: - let ok = updateState(state, parentRoot, none[BeaconBlock](), {}) - doAssert ok, "Empty block state update should never fail!" + advanceState(state, parentRoot) + + if not afterSlot.isNil: + afterSlot(state) # TODO document this: diff --git a/research/state_sim.nim b/research/state_sim.nim index 3e0dcd6cc..eec5cab97 100644 --- a/research/state_sim.nim +++ b/research/state_sim.nim @@ -46,7 +46,7 @@ cli do(slots = 1945, validators = SLOTS_PER_EPOCH, # One per shard is minimum json_interval = SLOTS_PER_EPOCH, prefix = 0, - attesterRatio {.desc: "ratio of validators that attest in each round"} = 0.0, + attesterRatio {.desc: "ratio of validators that attest in each round"} = 0.9, validate = false): let flags = if validate: {} else: {skipValidation} diff --git a/tests/test_attestation_pool.nim b/tests/test_attestation_pool.nim index a2e0256ce..b49785622 100644 --- a/tests/test_attestation_pool.nim +++ b/tests/test_attestation_pool.nim @@ -29,8 +29,7 @@ suite "Attestation pool processing": pool = AttestationPool.init(blockPool) state = blockPool.loadTailState() # Slot 0 is a finalized slot - won't be making attestations for it.. - discard updateState( - state.data, state.blck.root, none(BeaconBlock), {skipValidation}) + advanceState(state.data, state.blck.root) let # Create an attestation for slot 1 signed by the only attester we have! @@ -47,14 +46,12 @@ suite "Attestation pool processing": check: attestations.len == 1 - test "Attestations may arrive in any order": var pool = AttestationPool.init(blockPool) state = blockPool.loadTailState() # Slot 0 is a finalized slot - won't be making attestations for it.. - discard updateState( - state.data, state.blck.root, none(BeaconBlock), {skipValidation}) + advanceState(state.data, state.blck.root) let # Create an attestation for slot 1 signed by the only attester we have! @@ -63,8 +60,7 @@ suite "Attestation pool processing": attestation1 = makeAttestation( state.data, state.blck.root, crosslink_committees1[0].committee[0]) - discard updateState( - state.data, state.blck.root, none(BeaconBlock), {skipValidation}) + advanceState(state.data, state.blck.root) let crosslink_committees2 = diff --git a/tests/test_beacon_chain_db.nim b/tests/test_beacon_chain_db.nim index e6cd93e26..30baf404c 100644 --- a/tests/test_beacon_chain_db.nim +++ b/tests/test_beacon_chain_db.nim @@ -10,18 +10,50 @@ import options, unittest, sequtils, strutils, eth/trie/[db], ../beacon_chain/spec/[datatypes, digest, crypto] suite "Beacon chain DB": - var - db = init(BeaconChainDB, newMemoryDB()) test "empty database": + var + db = init(BeaconChainDB, newMemoryDB()) + check: db.getState(Eth2Digest()).isNone db.getBlock(Eth2Digest()).isNone - test "find ancestors": - var x: ValidatorSig - var y = init(ValidatorSig, x.getBytes()) + test "sanity check blocks": + var + db = init(BeaconChainDB, newMemoryDB()) + let + blck = BeaconBlock() + root = hash_tree_root_final(blck) + + db.putBlock(blck) + + check: + db.containsBlock(root) + db.getBlock(root).get() == blck + + test "sanity check states": + var + db = init(BeaconChainDB, newMemoryDB()) + + let + state = BeaconState() + root = hash_tree_root_final(state) + + db.putState(state) + + check: + db.containsState(root) + db.getState(root).get() == state + + test "find ancestors": + var + db = init(BeaconChainDB, newMemoryDB()) + x: ValidatorSig + y = init(ValidatorSig, x.getBytes()) + + # Silly serialization check that fails without the right import check: x == y let diff --git a/tests/test_block_pool.nim b/tests/test_block_pool.nim index 02c0d5d46..d823b24ed 100644 --- a/tests/test_block_pool.nim +++ b/tests/test_block_pool.nim @@ -12,7 +12,7 @@ import ../beacon_chain/[block_pool, beacon_chain_db, extras, state_transition, ssz] suite "Block pool processing": - var + let genState = get_genesis_beacon_state( makeInitialDeposits(flags = {skipValidation}), 0, Eth1Data(), {skipValidation}) @@ -38,7 +38,7 @@ suite "Block pool processing": b1Root = hash_tree_root_final(b1) # TODO the return value is ugly here, need to fix and test.. - discard pool.add(b1Root, b1) + discard pool.add(state, b1Root, b1) let b1Ref = pool.get(b1Root) @@ -53,19 +53,18 @@ suite "Block pool processing": state = pool.loadTailState() let - b1 = addBlock( - state.data, state.blck.root, BeaconBlockBody(), {skipValidation}) + b1 = addBlock(state.data, state.blck.root, BeaconBlockBody(), {}) b1Root = hash_tree_root_final(b1) - b2 = addBlock(state.data, b1Root, BeaconBlockBody(), {skipValidation}) + b2 = addBlock(state.data, b1Root, BeaconBlockBody(), {}) b2Root = hash_tree_root_final(b2) - discard pool.add(b2Root, b2) + discard pool.add(state, b2Root, b2) check: pool.get(b2Root).isNone() # Unresolved, shouldn't show up b1Root in pool.checkUnresolved() - discard pool.add(b1Root, b1) + discard pool.add(state, b1Root, b1) let b1r = pool.get(b1Root) diff --git a/tests/test_state_transition.nim b/tests/test_state_transition.nim index cc780526e..c530f9aa4 100644 --- a/tests/test_state_transition.nim +++ b/tests/test_state_transition.nim @@ -25,24 +25,19 @@ suite "Block processing": test "Passes from genesis state, no block": var state = genesisState - proposer_index = getNextBeaconProposerIndex(state) previous_block_root = hash_tree_root_final(genesisBlock) - let block_ok = - updateState(state, previous_block_root, none(BeaconBlock), {}) - check: - block_ok + advanceState(state, previous_block_root) + check: state.slot == genesisState.slot + 1 test "Passes from genesis state, empty block": var state = genesisState - proposer_index = getNextBeaconProposerIndex(state) previous_block_root = hash_tree_root_final(genesisBlock) new_block = makeBlock(state, previous_block_root, BeaconBlockBody()) - let block_ok = updateState( - state, previous_block_root, some(new_block), {}) + let block_ok = updateState(state, previous_block_root, new_block, {}) check: block_ok @@ -55,10 +50,7 @@ suite "Block processing": previous_block_root = hash_tree_root_final(genesisBlock) for i in 1..SLOTS_PER_EPOCH.int: - let block_ok = updateState( - state, previous_block_root, none(BeaconBlock), {}) - check: - block_ok + advanceState(state, previous_block_root) check: state.slot == genesisState.slot + SLOTS_PER_EPOCH @@ -72,7 +64,7 @@ suite "Block processing": var new_block = makeBlock(state, previous_block_root, BeaconBlockBody()) let block_ok = updateState( - state, previous_block_root, some(new_block), {}) + state, previous_block_root, new_block, {}) check: block_ok @@ -88,8 +80,7 @@ suite "Block processing": previous_block_root = hash_tree_root_final(genesisBlock) # Slot 0 is a finalized slot - won't be making attestations for it.. - discard updateState( - state, previous_block_root, none(BeaconBlock), {}) + advanceState(state, previous_block_root) let # Create an attestation for slot 1 signed by the only attester we have! @@ -101,21 +92,19 @@ suite "Block processing": # Some time needs to pass before attestations are included - this is # to let the attestation propagate properly to interested participants while state.slot < GENESIS_SLOT + MIN_ATTESTATION_INCLUSION_DELAY + 1: - discard updateState( - state, previous_block_root, none(BeaconBlock), {}) + advanceState(state, previous_block_root) let new_block = makeBlock(state, previous_block_root, BeaconBlockBody( attestations: @[attestation] )) - discard updateState(state, previous_block_root, some(new_block), {}) + discard updateState(state, previous_block_root, new_block, {}) check: state.latest_attestations.len == 1 while state.slot < 191: - discard updateState( - state, previous_block_root, none(BeaconBlock), {}) + advanceState(state, previous_block_root) # Would need to process more epochs for the attestation to be removed from # the state! (per above bug) diff --git a/tests/testutil.nim b/tests/testutil.nim index 78cd63330..00aefe2ba 100644 --- a/tests/testutil.nim +++ b/tests/testutil.nim @@ -107,7 +107,7 @@ proc addBlock*( ) let block_ok = updateState( - state, previous_block_root, some(new_block), {skipValidation}) + state, previous_block_root, new_block, {skipValidation}) assert block_ok # Ok, we have the new state as it would look with the block applied - now we @@ -174,7 +174,7 @@ proc makeAttestation*( shard: sac.shard, beacon_block_root: beacon_block_root, epoch_boundary_root: Eth2Digest(), # TODO - latest_crosslink: Crosslink(epoch: state.latest_crosslinks[sac.shard].epoch), # TODO + latest_crosslink: state.latest_crosslinks[sac.shard], shard_block_root: Eth2Digest(), # TODO justified_epoch: state.justified_epoch, justified_block_root: get_block_root(state, get_epoch_start_slot(state.justified_epoch)),