# beacon_chain # Copyright (c) 2018-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. {.used.} import ../beacon_chain/spec/datatypes, ../beacon_chain/ssz import unittest, chronicles, stew/byteutils, ./testutil, ./testblockutil, ../beacon_chain/spec/[digest, validator, state_transition, helpers, beaconstate, presets], ../beacon_chain/[beacon_node_types, attestation_pool, block_pool, extras], ../beacon_chain/fork_choice/[fork_choice_types, fork_choice] template wrappedTimedTest(name: string, body: untyped) = # `check` macro takes a copy of whatever it's checking, on the stack! block: # Symbol namespacing proc wrappedTest() = timedTest name: body wrappedTest() suiteReport "Attestation pool processing" & preset(): ## For now just test that we can compile and execute block processing with ## mock data. setup: # Genesis state that results in 3 members per committee var blockPool = newClone(BlockPool.init(defaultRuntimePreset, makeTestDB(SLOTS_PER_EPOCH * 3))) pool = newClone(AttestationPool.init(blockPool[])) state = newClone(loadTailState(blockPool[])) # Slot 0 is a finalized slot - won't be making attestations for it.. check: process_slots(state.data, state.data.data.slot + 1) timedTest "Can add and retrieve simple attestation" & preset(): var cache = get_empty_per_epoch_cache() let # Create an attestation for slot 1! beacon_committee = get_beacon_committee( state.data.data, state.data.data.slot, 0.CommitteeIndex, cache) attestation = makeAttestation( state.data.data, state.blck.root, beacon_committee[0], cache) pool[].addAttestation(attestation) check: process_slots(state.data, MIN_ATTESTATION_INCLUSION_DELAY.Slot + 1) let attestations = pool[].getAttestationsForBlock(state.data.data) check: attestations.len == 1 timedTest "Attestations may arrive in any order" & preset(): var cache = get_empty_per_epoch_cache() let # Create an attestation for slot 1! bc0 = get_beacon_committee( state.data.data, state.data.data.slot, 0.CommitteeIndex, cache) attestation0 = makeAttestation( state.data.data, state.blck.root, bc0[0], cache) check: process_slots(state.data, state.data.data.slot + 1) let bc1 = get_beacon_committee(state.data.data, state.data.data.slot, 0.CommitteeIndex, cache) attestation1 = makeAttestation( state.data.data, state.blck.root, bc1[0], cache) # test reverse order pool[].addAttestation(attestation1) pool[].addAttestation(attestation0) discard process_slots(state.data, MIN_ATTESTATION_INCLUSION_DELAY.Slot + 1) let attestations = pool[].getAttestationsForBlock(state.data.data) check: attestations.len == 1 timedTest "Attestations should be combined" & preset(): var cache = get_empty_per_epoch_cache() let # Create an attestation for slot 1! bc0 = get_beacon_committee( state.data.data, state.data.data.slot, 0.CommitteeIndex, cache) attestation0 = makeAttestation( state.data.data, state.blck.root, bc0[0], cache) attestation1 = makeAttestation( state.data.data, state.blck.root, bc0[1], cache) pool[].addAttestation(attestation0) pool[].addAttestation(attestation1) check: process_slots(state.data, MIN_ATTESTATION_INCLUSION_DELAY.Slot + 1) let attestations = pool[].getAttestationsForBlock(state.data.data) check: attestations.len == 1 timedTest "Attestations may overlap, bigger first" & preset(): var cache = get_empty_per_epoch_cache() var # Create an attestation for slot 1! bc0 = get_beacon_committee( state.data.data, state.data.data.slot, 0.CommitteeIndex, cache) attestation0 = makeAttestation( state.data.data, state.blck.root, bc0[0], cache) attestation1 = makeAttestation( state.data.data, state.blck.root, bc0[1], cache) attestation0.combine(attestation1, {}) pool[].addAttestation(attestation0) pool[].addAttestation(attestation1) check: process_slots(state.data, MIN_ATTESTATION_INCLUSION_DELAY.Slot + 1) let attestations = pool[].getAttestationsForBlock(state.data.data) check: attestations.len == 1 timedTest "Attestations may overlap, smaller first" & preset(): var cache = get_empty_per_epoch_cache() var # Create an attestation for slot 1! bc0 = get_beacon_committee(state.data.data, state.data.data.slot, 0.CommitteeIndex, cache) attestation0 = makeAttestation( state.data.data, state.blck.root, bc0[0], cache) attestation1 = makeAttestation( state.data.data, state.blck.root, bc0[1], cache) attestation0.combine(attestation1, {}) pool[].addAttestation(attestation1) pool[].addAttestation(attestation0) check: process_slots(state.data, MIN_ATTESTATION_INCLUSION_DELAY.Slot + 1) let attestations = pool[].getAttestationsForBlock(state.data.data) check: attestations.len == 1 timedTest "Fork choice returns latest block with no attestations": var cache = get_empty_per_epoch_cache() let b1 = addTestBlock(state.data, blockPool[].tail.root, cache) b1Root = hash_tree_root(b1.message) b1Add = blockpool[].addRawBlock(b1Root, b1) do (validBlock: BlockRef): # Callback Add to fork choice pool[].addForkChoice_v2(validBlock) let head = pool[].selectHead() check: head == b1Add[] let b2 = addTestBlock(state.data, b1Root, cache) b2Root = hash_tree_root(b2.message) b2Add = blockpool[].addRawBlock(b2Root, b2) do (validBlock: BlockRef): # Callback Add to fork choice pool[].addForkChoice_v2(validBlock) let head2 = pool[].selectHead() check: head2 == b2Add[] timedTest "Fork choice returns block with attestation": var cache = get_empty_per_epoch_cache() let b10 = makeTestBlock(state.data, blockPool[].tail.root, cache) b10Root = hash_tree_root(b10.message) b10Add = blockpool[].addRawBlock(b10Root, b10) do (validBlock: BlockRef): # Callback Add to fork choice pool[].addForkChoice_v2(validBlock) let head = pool[].selectHead() check: head == b10Add[] let b11 = makeTestBlock(state.data, blockPool[].tail.root, cache, graffiti = GraffitiBytes [1'u8, 0, 0, 0 ,0 ,0 ,0 ,0 ,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ) b11Root = hash_tree_root(b11.message) b11Add = blockpool[].addRawBlock(b11Root, b11) do (validBlock: BlockRef): # Callback Add to fork choice pool[].addForkChoice_v2(validBlock) bc1 = get_beacon_committee( state.data.data, state.data.data.slot, 1.CommitteeIndex, cache) attestation0 = makeAttestation(state.data.data, b10Root, bc1[0], cache) pool[].addAttestation(attestation0) let head2 = pool[].selectHead() check: # Single vote for b10 and no votes for b11 head2 == b10Add[] let attestation1 = makeAttestation(state.data.data, b11Root, bc1[1], cache) attestation2 = makeAttestation(state.data.data, b11Root, bc1[2], cache) pool[].addAttestation(attestation1) let head3 = pool[].selectHead() # Warning - the tiebreak are incorrect and guaranteed consensus fork, it should be bigger let smaller = if b10Root.data < b11Root.data: b10Add else: b11Add check: # Ties broken lexicographically in spec -> ? # all implementations favor the biggest root # TODO # currently using smaller as we have used for over a year head3 == smaller[] pool[].addAttestation(attestation2) let head4 = pool[].selectHead() check: # Two votes for b11 head4 == b11Add[] timedTest "Trying to add a block twice tags the second as an error": var cache = get_empty_per_epoch_cache() let b10 = makeTestBlock(state.data, blockPool[].tail.root, cache) b10Root = hash_tree_root(b10.message) b10Add = blockpool[].addRawBlock(b10Root, b10) do (validBlock: BlockRef): # Callback Add to fork choice pool[].addForkChoice_v2(validBlock) let head = pool[].selectHead() check: head == b10Add[] # ------------------------------------------------------------- # Add back the old block to ensure we have a duplicate error let b10_clone = b10 # Assumes deep copy let b10Add_clone = blockpool[].addRawBlock(b10Root, b10_clone) do (validBlock: BlockRef): # Callback Add to fork choice pool[].addForkChoice_v2(validBlock) doAssert: b10Add_clone.error == Duplicate wrappedTimedTest "Trying to add a duplicate block from an old pruned epoch is tagged as an error": var cache = get_empty_per_epoch_cache() blockpool[].addFlags {skipBLSValidation} pool.forkChoice_v2.proto_array.prune_threshold = 1 let b10 = makeTestBlock(state.data, blockPool[].tail.root, cache) b10Root = hash_tree_root(b10.message) b10Add = blockpool[].addRawBlock(b10Root, b10) do (validBlock: BlockRef): # Callback Add to fork choice pool[].addForkChoice_v2(validBlock) let head = pool[].selectHead() doAssert: head == b10Add[] let block_ok = state_transition(defaultRuntimePreset, state.data, b10, {}, noRollback) doAssert: block_ok # ------------------------------------------------------------- let b10_clone = b10 # Assumes deep copy # ------------------------------------------------------------- # Pass an epoch var block_root = b10Root var attestations: seq[Attestation] for epoch in 0 ..< 5: let start_slot = compute_start_slot_at_epoch(Epoch epoch) for slot in start_slot ..< start_slot + SLOTS_PER_EPOCH: let new_block = makeTestBlock(state.data, block_root, cache, attestations = attestations) let block_ok = state_transition(defaultRuntimePreset, state.data, new_block, {skipBLSValidation}, noRollback) doAssert: block_ok block_root = hash_tree_root(new_block.message) let blockRef = blockpool[].addRawBlock(block_root, new_block) do (validBlock: BlockRef): # Callback Add to fork choice pool[].addForkChoice_v2(validBlock) let head = pool[].selectHead() doassert: head == blockRef[] blockPool[].updateHead(head) attestations.setlen(0) for index in 0 ..< get_committee_count_at_slot(state.data.data, slot.Slot): let committee = get_beacon_committee( state.data.data, state.data.data.slot, index.CommitteeIndex, cache) # Create a bitfield filled with the given count per attestation, # exactly on the right-most part of the committee field. var aggregation_bits = init(CommitteeValidatorsBits, committee.len) for v in 0 ..< committee.len * 2 div 3 + 1: aggregation_bits[v] = true attestations.add Attestation( aggregation_bits: aggregation_bits, data: makeAttestationData( state.data.data, state.data.data.slot, index, blockroot ) # signature: ValidatorSig() ) cache = get_empty_per_epoch_cache() # ------------------------------------------------------------- # Prune echo "\nPruning all blocks before: ", shortlog(blockPool[].finalizedHead), '\n' doAssert: blockPool[].finalizedHead.slot != 0 pool[].pruneBefore(blockPool[].finalizedHead) doAssert: b10Root notin pool.forkChoice_v2 # Add back the old block to ensure we have a duplicate error let b10Add_clone = blockpool[].addRawBlock(b10Root, b10_clone) do (validBlock: BlockRef): # Callback Add to fork choice pool[].addForkChoice_v2(validBlock) doAssert: b10Add_clone.error == Duplicate