diff --git a/beacon_chain/beacon_chain_db.nim b/beacon_chain/beacon_chain_db.nim index d6902a978..c4a2643e8 100644 --- a/beacon_chain/beacon_chain_db.nim +++ b/beacon_chain/beacon_chain_db.nim @@ -72,3 +72,25 @@ proc getHead*(db: BeaconChainDB, T: type BeaconBlock): Option[T] = proc contains*( db: BeaconChainDB, key: Eth2Digest, T: type DbTypes): bool = db.backend.contains(subkey(T, key)) + +proc getAncestors*( + db: BeaconChainDB, blck: BeaconBlock, + predicate: proc(blck: BeaconBlock): bool = nil): seq[BeaconBlock] = + ## Load a chain of ancestors for blck - returns a list of blocks with the + ## oldest block last (blck will be at result[0]). + ## + ## The search will go on until the ancestor cannot be found (or slot 0) or + ## the predicate returns true (you found what you were looking for) - the list + ## will include the last block as well + ## TODO maybe turn into iterator? or add iterator also? + + result = @[blck] + + while result[^1].slot > 0.Slot: + let parent = db.get(result[^1].parent_root, BeaconBlock) + + if parent.isNone(): break + + result.add parent.get() + + if predicate != nil and predicate(parent.get()): break diff --git a/beacon_chain/beacon_node.nim b/beacon_chain/beacon_node.nim index d8df177a0..66b30d257 100644 --- a/beacon_chain/beacon_node.nim +++ b/beacon_chain/beacon_node.nim @@ -451,70 +451,66 @@ proc updateHeadBlock(node: BeaconNode, blck: BeaconBlock) = # chain of ancestors of the new block. We will do this by loading each # successive parent block and checking if we can find the corresponding state # in the database. - var parents = @[blck] - while true: - let top = parents[^1] + let + ancestors = node.db.getAncestors(blck) do (bb: BeaconBlock) -> bool: + node.db.contains(bb.state_root, BeaconState) + ancestor = ancestors[^1] - # We're looking for the most recent state that we have in the database - # that also exists on the ancestor chain. - if (let prevState = node.db.get(top.state_root, BeaconState); - prevState.isSome()): - # Got it! - notice "Replaying state transitions", - stateSlot = humaneSlotNum(node.beaconState.slot), - prevStateSlot = humaneSlotNum(prevState.get().slot) - node.beaconState = prevState.get() - break + # Several things can happen, but the most common one should be that we found + # a beacon state + if (let state = node.db.get(ancestor.state_root, BeaconState); state.isSome()): + # Got it! + notice "Replaying state transitions", + stateSlot = humaneSlotNum(node.beaconState.slot), + prevStateSlot = humaneSlotNum(state.get().slot) + node.beaconState = state.get() - if top.slot == 0: - # We've arrived at the genesis block and still haven't found what we're - # looking for. This is very bad - are we receiving blocks from a different - # chain? What's going on? - # TODO crashing like this is the wrong thing to do, obviously, but - # we'll do it anyway just to see if it ever happens - if it does, - # it's likely a bug :) - error "Couldn't find ancestor state", - blockSlot = humaneSlotNum(blck.slot), - blockRoot = shortHash(hash_tree_root_final(blck)) - doAssert false, "Oh noes, we passed big bang!" - - if (let parent = node.db.get(top.parent_root, BeaconBlock); parent.isSome): - # We're lucky this time - we found the parent block in the database, so - # we put it on the stack and keep looking - parents.add(parent.get()) - else: - # We don't have the parent block. This is a bit strange, but may happen - # if things are happening seriously out of order or if we're back after - # a net split or restart, for example. Once the missing block arrives, - # we should retry setting the head block.. - # TODO implement block sync here - # TODO instead of doing block sync here, make sure we are sync already - # elsewhere, so as to simplify the logic of finding the block - # here.. - error "Parent missing! Too bad, because sync is also missing :/", - parentRoot = shortHash(top.parent_root), - blockSlot = humaneSlotNum(top.slot) - quit("So long") + elif ancestor.slot == 0: + # We've arrived at the genesis block and still haven't found what we're + # looking for. This is very bad - are we receiving blocks from a different + # chain? What's going on? + # TODO crashing like this is the wrong thing to do, obviously, but + # we'll do it anyway just to see if it ever happens - if it does, + # it's likely a bug :) + error "Couldn't find ancestor state", + blockSlot = humaneSlotNum(blck.slot), + blockRoot = shortHash(hash_tree_root_final(blck)) + doAssert false, "Oh noes, we passed big bang!" + else: + # We don't have the parent block. This is a bit strange, but may happen + # if things are happening seriously out of order or if we're back after + # a net split or restart, for example. Once the missing block arrives, + # we should retry setting the head block.. + # TODO implement block sync here + # TODO instead of doing block sync here, make sure we are sync already + # elsewhere, so as to simplify the logic of finding the block + # here.. + error "Parent missing! Too bad, because sync is also missing :/", + parentRoot = shortHash(ancestor.parent_root), + blockSlot = humaneSlotNum(ancestor.slot) + doAssert false, "So long" # If we come this far, we found the state root. The last block on the stack # is the one that produced this particular state, so we can pop it # TODO it might be possible to use the latest block hashes from the state to # do this more efficiently.. whatever! - discard parents.pop() - # Time to replay all the blocks between then and now. - while parents.len > 0: - let last = parents.pop() + # Time to replay all the blocks between then and now. We skip the one because + # it's the one that we found the state with, and it has already been + # applied + for i in countdown(ancestors.len - 2, 0): + let last = ancestors[i] + skipSlots(node.beaconState, last.parent_root, last.slot) + # TODO technically, we should be storing states here, because we're now + # going down a different fork let ok = updateState( node.beaconState, last.parent_root, some(last), - if parents.len == 0: {} else: {skipValidation}) + if ancestors.len == 0: {} else: {skipValidation}) doAssert(ok) - doAssert hash_tree_root_final(node.beaconState) == blck.state_root - node.headBlock = blck node.headBlockRoot = hash_tree_root_final(blck) node.db.putHead(node.headBlockRoot) @@ -536,7 +532,6 @@ proc onBeaconBlock(node: BeaconNode, blck: BeaconBlock) = blockRoot = shortHash(blockRoot), stateSlot = humaneSlotNum(stateSlot) - updateHeadBlock(node, node.headBlock) return info "Block received", diff --git a/tests/all_tests.nim b/tests/all_tests.nim index 6ed09e8d6..5f1e83026 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -7,10 +7,11 @@ import ./test_attestation_pool, + ./test_beacon_chain_db, + ./test_beacon_node, ./test_beaconstate, - ./test_state_transition, ./test_helpers, ./test_ssz, - ./test_validator, - ./test_beacon_node, - ./test_sync_protocol + ./test_state_transition, + ./test_sync_protocol, + ./test_validator diff --git a/tests/simulation/start.sh b/tests/simulation/start.sh index 078c200bd..ee20eb9c5 100755 --- a/tests/simulation/start.sh +++ b/tests/simulation/start.sh @@ -29,7 +29,7 @@ VALIDATOR_KEYGEN_BIN=$BUILD_OUTPUTS_DIR/validator_keygen # Run with "SHARD_COUNT=4 ./start.sh" to change these DEFS="-d:SHARD_COUNT=${SHARD_COUNT:-4} " # Spec default: 1024 DEFS+="-d:EPOCH_LENGTH=${EPOCH_LENGTH:-8} " # Spec default: 64 -DEFS+="-d:SECONDS_PER_SLOT=${SECONDS_PER_SLOT:-4} " # Spec default: 6 +DEFS+="-d:SECONDS_PER_SLOT=${SECONDS_PER_SLOT:-6} " # Spec default: 6 if [[ -z "$SKIP_BUILDS" ]]; then nim c -o:"$VALIDATOR_KEYGEN_BIN" $DEFS -d:release beacon_chain/validator_keygen diff --git a/tests/test_beacon_chain_db.nim b/tests/test_beacon_chain_db.nim new file mode 100644 index 000000000..6cfa194eb --- /dev/null +++ b/tests/test_beacon_chain_db.nim @@ -0,0 +1,54 @@ +# Nimbus +# Copyright (c) 2018 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +import options, unittest, strutils, eth/trie/[db], + ../beacon_chain/[beacon_chain_db, ssz], + ../beacon_chain/spec/[datatypes, digest, crypto] + +suite "Beacon chain DB": + var + db = init(BeaconChainDB, newMemoryDB()) + + test "empty database": + check: + db.get(Eth2Digest(), BeaconState).isNone + db.get(Eth2Digest(), BeaconBlock).isNone + + + test "find ancestors": + var x: ValidatorSig + var y = init(ValidatorSig, x.getBytes()) + + check: x == y + + let + a0 = BeaconBlock(slot: 0) + a1 = BeaconBlock(slot: 1, parent_root: hash_tree_root_final(a0)) + a2 = BeaconBlock(slot: 2, parent_root: hash_tree_root_final(a1)) + + # TODO check completely kills compile times here + doAssert db.getAncestors(a0) == [a0] + doAssert db.getAncestors(a2) == [a2] + + db.put(a2) + + doAssert db.getAncestors(a0) == [a0] + doAssert db.getAncestors(a2) == [a2] + + db.put(a1) + + doAssert db.getAncestors(a0) == [a0] + doAssert db.getAncestors(a2) == [a2, a1] + + db.put(a0) + + doAssert db.getAncestors(a0) == [a0] + doAssert db.getAncestors(a2) == [a2, a1, a0] + + let tmp = db.getAncestors(a2) do (b: BeaconBlock) -> bool: + b.slot == 1 + doAssert tmp == [a2, a1]