def test_ex_ante_sandwich_without_attestations(spec, state): """ Simple Sandwich test with boost and no attestations. Obejcts: Block A - slot N Block B (parent A) - slot N+1 Block C (parent A) - slot N+2 Block D (parent B) - slot N+3 Steps: Block A received at N — A is head Block C received at N+2 — C is head Block B received at N+2 — C is head (with boost) Block D received at N+3 — D is head (with boost) """ test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) yield 'anchor_state', state yield 'anchor_block', anchor_block current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time # On receiving block A at slot `N` yield from _apply_base_block_a(spec, state, store, test_steps) state_a = state.copy() # Block B at slot `N + 1`, parent is A state_b = state_a.copy() block = build_empty_block(spec, state_a, slot=state_a.slot + 1) signed_block_b = state_transition_and_sign_block(spec, state_b, block) # Block C at slot `N + 2`, parent is A state_c = state_a.copy() block = build_empty_block(spec, state_c, slot=state_a.slot + 2) signed_block_c = state_transition_and_sign_block(spec, state_c, block) # Block D at slot `N + 3`, parent is B state_d = state_b.copy() block = build_empty_block(spec, state_d, slot=state_a.slot + 3) signed_block_d = state_transition_and_sign_block(spec, state_d, block) # Block C received at N+2 — C is head time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, time, test_steps) yield from add_block(spec, store, signed_block_c, test_steps) assert spec.get_head(store) == signed_block_c.message.hash_tree_root() # Block B received at N+2 — C is head, it has proposer score boost yield from add_block(spec, store, signed_block_b, test_steps) assert spec.get_head(store) == signed_block_c.message.hash_tree_root() # Block D received at N+3 - D is head, it has proposer score boost time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, time, test_steps) yield from add_block(spec, store, signed_block_d, test_steps) assert spec.get_head(store) == signed_block_d.message.hash_tree_root() yield 'steps', test_steps
def test_proposer_boost(spec, state): test_steps = [] genesis_state = state.copy() # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) yield 'anchor_state', state yield 'anchor_block', anchor_block # Build block that serves as head ONLY on timely arrival, and ONLY in that slot state = genesis_state.copy() next_slots(spec, state, 3) block = build_empty_block_for_next_slot(spec, state) signed_block = state_transition_and_sign_block(spec, state, block) # Process block on timely arrival just before end of boost interval time = (store.genesis_time + block.slot * spec.config.SECONDS_PER_SLOT + spec.config.SECONDS_PER_SLOT // spec.INTERVALS_PER_SLOT - 1) on_tick_and_append_step(spec, store, time, test_steps) yield from add_block(spec, store, signed_block, test_steps) assert store.proposer_boost_root == spec.hash_tree_root(block) assert spec.get_latest_attesting_balance(store, spec.hash_tree_root(block)) > 0 # Ensure that boost is removed after slot is over time = (store.genesis_time + block.slot * spec.config.SECONDS_PER_SLOT + spec.config.SECONDS_PER_SLOT) on_tick_and_append_step(spec, store, time, test_steps) assert store.proposer_boost_root == spec.Root() assert spec.get_latest_attesting_balance(store, spec.hash_tree_root(block)) == 0 next_slots(spec, state, 3) block = build_empty_block_for_next_slot(spec, state) signed_block = state_transition_and_sign_block(spec, state, block) # Process block on timely arrival at start of boost interval time = (store.genesis_time + block.slot * spec.config.SECONDS_PER_SLOT) on_tick_and_append_step(spec, store, time, test_steps) yield from add_block(spec, store, signed_block, test_steps) assert store.proposer_boost_root == spec.hash_tree_root(block) assert spec.get_latest_attesting_balance(store, spec.hash_tree_root(block)) > 0 # Ensure that boost is removed after slot is over time = (store.genesis_time + block.slot * spec.config.SECONDS_PER_SLOT + spec.config.SECONDS_PER_SLOT) on_tick_and_append_step(spec, store, time, test_steps) assert store.proposer_boost_root == spec.Root() assert spec.get_latest_attesting_balance(store, spec.hash_tree_root(block)) == 0 test_steps.append({ 'checks': { 'proposer_boost_root': encode_hex(store.proposer_boost_root), } }) yield 'steps', test_steps
def test_proposer_boost_root_same_slot_untimely_block(spec, state): test_steps = [] genesis_state = state.copy() # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) yield 'anchor_state', state yield 'anchor_block', anchor_block # Build block that serves as head ONLY on timely arrival, and ONLY in that slot state = genesis_state.copy() next_slots(spec, state, 3) block = build_empty_block_for_next_slot(spec, state) signed_block = state_transition_and_sign_block(spec, state, block) # Process block on untimely arrival in the same slot time = (store.genesis_time + block.slot * spec.config.SECONDS_PER_SLOT + spec.config.SECONDS_PER_SLOT // spec.INTERVALS_PER_SLOT) on_tick_and_append_step(spec, store, time, test_steps) yield from add_block(spec, store, signed_block, test_steps) assert store.proposer_boost_root == spec.Root() test_steps.append({ 'checks': { 'proposer_boost_root': encode_hex(store.proposer_boost_root), } }) yield 'steps', test_steps
def test_split_tie_breaker_no_attestations(spec, state): test_steps = [] genesis_state = state.copy() # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) yield 'anchor_state', state yield 'anchor_block', anchor_block anchor_root = get_anchor_root(spec, state) assert spec.get_head(store) == anchor_root test_steps.append( {'checks': { 'head': get_formatted_head_output(spec, store), }}) # Create block at slot 1 block_1_state = genesis_state.copy() block_1 = build_empty_block_for_next_slot(spec, block_1_state) signed_block_1 = state_transition_and_sign_block(spec, block_1_state, block_1) # Create additional block at slot 1 block_2_state = genesis_state.copy() block_2 = build_empty_block_for_next_slot(spec, block_2_state) block_2.body.graffiti = b'\x42' * 32 signed_block_2 = state_transition_and_sign_block(spec, block_2_state, block_2) # Tick time past slot 1 so proposer score boost does not apply time = store.genesis_time + (block_2.slot + 1) * spec.config.SECONDS_PER_SLOT on_tick_and_append_step(spec, store, time, test_steps) yield from add_block(spec, store, signed_block_1, test_steps) yield from add_block(spec, store, signed_block_2, test_steps) highest_root = max(spec.hash_tree_root(block_1), spec.hash_tree_root(block_2)) assert spec.get_head(store) == highest_root test_steps.append( {'checks': { 'head': get_formatted_head_output(spec, store), }}) yield 'steps', test_steps
def test_on_block_future_block(spec, state): test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) yield 'anchor_state', state yield 'anchor_block', anchor_block current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time # Do NOT tick time to `GENESIS_SLOT + 1` slot # Fail receiving block of `GENESIS_SLOT + 1` slot block = build_empty_block_for_next_slot(spec, state) signed_block = state_transition_and_sign_block(spec, state, block) yield from add_block(spec, store, signed_block, test_steps, valid=False) yield 'steps', test_steps
def test_on_block_bad_parent_root(spec, state): test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) yield 'anchor_state', state yield 'anchor_block', anchor_block current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time # Fail receiving block of `GENESIS_SLOT + 1` slot block = build_empty_block_for_next_slot(spec, state) transition_unsigned_block(spec, state, block) block.state_root = state.hash_tree_root() block.parent_root = b'\x45' * 32 signed_block = sign_block(spec, state, block) yield from add_block(spec, store, signed_block, test_steps, valid=False) yield 'steps', test_steps
def test_discard_equivocations(spec, state): test_steps = [] genesis_state = state.copy() # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) yield 'anchor_state', state yield 'anchor_block', anchor_block anchor_root = get_anchor_root(spec, state) assert spec.get_head(store) == anchor_root test_steps.append( {'checks': { 'head': get_formatted_head_output(spec, store), }}) # Build block that serves as head before discarding equivocations state_1 = genesis_state.copy() next_slots(spec, state_1, 3) block_1 = build_empty_block_for_next_slot(spec, state_1) signed_block_1 = state_transition_and_sign_block(spec, state_1, block_1) # Build equivocating attestations to feed to store state_eqv = state_1.copy() block_eqv = apply_empty_block(spec, state_eqv, state_eqv.slot + 1) attestation_eqv = get_valid_attestation(spec, state_eqv, slot=block_eqv.slot, signed=True) next_slots(spec, state_1, 1) attestation = get_valid_attestation(spec, state_1, slot=block_eqv.slot, signed=True) assert spec.is_slashable_attestation_data(attestation.data, attestation_eqv.data) indexed_attestation = spec.get_indexed_attestation(state_1, attestation) indexed_attestation_eqv = spec.get_indexed_attestation( state_eqv, attestation_eqv) attester_slashing = spec.AttesterSlashing( attestation_1=indexed_attestation, attestation_2=indexed_attestation_eqv) # Build block that serves as head after discarding equivocations state_2 = genesis_state.copy() next_slots(spec, state_2, 2) block_2 = build_empty_block_for_next_slot(spec, state_2) signed_block_2 = state_transition_and_sign_block(spec, state_2.copy(), block_2) while spec.hash_tree_root(block_1) >= spec.hash_tree_root(block_2): block_2.body.graffiti = spec.Bytes32( hex(rng.getrandbits(8 * 32))[2:].zfill(64)) signed_block_2 = state_transition_and_sign_block( spec, state_2.copy(), block_2) assert spec.hash_tree_root(block_1) < spec.hash_tree_root(block_2) # Tick to (block_eqv.slot + 2) slot time time = store.genesis_time + (block_eqv.slot + 2) * spec.config.SECONDS_PER_SLOT on_tick_and_append_step(spec, store, time, test_steps) # Process block_2 yield from add_block(spec, store, signed_block_2, test_steps) assert store.proposer_boost_root == spec.Root() assert spec.get_head(store) == spec.hash_tree_root(block_2) # Process block_1 # The head should remain block_2 yield from add_block(spec, store, signed_block_1, test_steps) assert store.proposer_boost_root == spec.Root() assert spec.get_head(store) == spec.hash_tree_root(block_2) # Process attestation # The head should change to block_1 yield from add_attestation(spec, store, attestation, test_steps) assert spec.get_head(store) == spec.hash_tree_root(block_1) # Process attester_slashing # The head should revert to block_2 yield from add_attester_slashing(spec, store, attester_slashing, test_steps) assert spec.get_head(store) == spec.hash_tree_root(block_2) test_steps.append( {'checks': { 'head': get_formatted_head_output(spec, store), }}) yield 'steps', test_steps
def test_proposer_boost_correct_head(spec, state): test_steps = [] genesis_state = state.copy() # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) yield 'anchor_state', state yield 'anchor_block', anchor_block anchor_root = get_anchor_root(spec, state) assert spec.get_head(store) == anchor_root test_steps.append( {'checks': { 'head': get_formatted_head_output(spec, store), }}) # Build block that serves as head ONLY on timely arrival, and ONLY in that slot state_1 = genesis_state.copy() next_slots(spec, state_1, 3) block_1 = build_empty_block_for_next_slot(spec, state_1) signed_block_1 = state_transition_and_sign_block(spec, state_1, block_1) # Build block that serves as current head, and remains the head after block_1.slot state_2 = genesis_state.copy() next_slots(spec, state_2, 2) block_2 = build_empty_block_for_next_slot(spec, state_2) signed_block_2 = state_transition_and_sign_block(spec, state_2.copy(), block_2) while spec.hash_tree_root(block_1) >= spec.hash_tree_root(block_2): block_2.body.graffiti = spec.Bytes32( hex(rng.getrandbits(8 * 32))[2:].zfill(64)) signed_block_2 = state_transition_and_sign_block( spec, state_2.copy(), block_2) assert spec.hash_tree_root(block_1) < spec.hash_tree_root(block_2) # Tick to block_1 slot time time = store.genesis_time + block_1.slot * spec.config.SECONDS_PER_SLOT on_tick_and_append_step(spec, store, time, test_steps) # Process block_2 yield from add_block(spec, store, signed_block_2, test_steps) assert store.proposer_boost_root == spec.Root() assert spec.get_head(store) == spec.hash_tree_root(block_2) # Process block_1 on timely arrival # The head should temporarily change to block_1 yield from add_block(spec, store, signed_block_1, test_steps) assert store.proposer_boost_root == spec.hash_tree_root(block_1) assert spec.get_head(store) == spec.hash_tree_root(block_1) # After block_1.slot, the head should revert to block_2 time = store.genesis_time + (block_1.slot + 1) * spec.config.SECONDS_PER_SLOT on_tick_and_append_step(spec, store, time, test_steps) assert store.proposer_boost_root == spec.Root() assert spec.get_head(store) == spec.hash_tree_root(block_2) test_steps.append( {'checks': { 'head': get_formatted_head_output(spec, store), }}) yield 'steps', test_steps
def test_filtered_block_tree(spec, state): test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) yield 'anchor_state', state yield 'anchor_block', anchor_block anchor_root = get_anchor_root(spec, state) assert spec.get_head(store) == anchor_root test_steps.append( {'checks': { 'head': get_formatted_head_output(spec, store), }}) # transition state past initial couple of epochs next_epoch(spec, state) next_epoch(spec, state) # fill in attestations for entire epoch, justifying the recent epoch prev_state, signed_blocks, state = next_epoch_with_attestations( spec, state, True, False) assert state.current_justified_checkpoint.epoch > prev_state.current_justified_checkpoint.epoch # tick time forward and add blocks and attestations to store current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) for signed_block in signed_blocks: yield from add_block(spec, store, signed_block, test_steps) assert store.justified_checkpoint == state.current_justified_checkpoint # the last block in the branch should be the head expected_head_root = spec.hash_tree_root(signed_blocks[-1].message) assert spec.get_head(store) == expected_head_root test_steps.append({ 'checks': { 'head': get_formatted_head_output(spec, store), 'justified_checkpoint_root': encode_hex(store.justified_checkpoint.root), } }) # # create branch containing the justified block but not containing enough on # chain votes to justify that block # # build a chain without attestations off of previous justified block non_viable_state = store.block_states[ store.justified_checkpoint.root].copy() # ensure that next wave of votes are for future epoch next_epoch(spec, non_viable_state) next_epoch(spec, non_viable_state) next_epoch(spec, non_viable_state) assert spec.get_current_epoch( non_viable_state) > store.justified_checkpoint.epoch # create rogue block that will be attested to in this non-viable branch rogue_block = build_empty_block_for_next_slot(spec, non_viable_state) signed_rogue_block = state_transition_and_sign_block( spec, non_viable_state, rogue_block) # create an epoch's worth of attestations for the rogue block next_epoch(spec, non_viable_state) attestations = [] for i in range(spec.SLOTS_PER_EPOCH): slot = rogue_block.slot + i for index in range( spec.get_committee_count_per_slot( non_viable_state, spec.compute_epoch_at_slot(slot))): attestation = get_valid_attestation(spec, non_viable_state, slot, index, signed=True) attestations.append(attestation) # tick time forward to be able to include up to the latest attestation current_time = (attestations[-1].data.slot + 1) * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) # include rogue block and associated attestations in the store yield from add_block(spec, store, signed_rogue_block, test_steps) for attestation in attestations: yield from tick_and_run_on_attestation(spec, store, attestation, test_steps) # ensure that get_head still returns the head from the previous branch assert spec.get_head(store) == expected_head_root test_steps.append( {'checks': { 'head': get_formatted_head_output(spec, store) }}) yield 'steps', test_steps
def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): """ J: Justified F: Finalized state (forked from genesis): epoch [0] <- [1] <- [2] <- [3] <- [4] <- [5] F J another_state (forked from epoch 0): └──── [1] <- [2] <- [3] <- [4] <- [5] F J """ test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) yield 'anchor_state', state yield 'anchor_block', anchor_block current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time # ----- Process state # Goal: make `store.finalized_checkpoint.epoch == 0` and `store.justified_checkpoint.epoch == 3` # Skip epoch 0 next_epoch(spec, state) # Forking another_state another_state = state.copy() # Fill epoch 1 with previous epoch attestations state, store, _ = yield from apply_next_epoch_with_attestations( spec, state, store, False, True, test_steps=test_steps) # Skip epoch 2 next_epoch(spec, state) # Fill epoch 3 & 4 with previous epoch attestations for _ in range(2): state, store, _ = yield from apply_next_epoch_with_attestations( spec, state, store, False, True, test_steps=test_steps) assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 assert store.justified_checkpoint == state.current_justified_checkpoint # Create another chain # Goal: make `another_state.finalized_checkpoint.epoch == 2` and `another_state.justified_checkpoint.epoch == 3` all_blocks = [] # Fill epoch 1 & 2 with previous + current epoch attestations for _ in range(3): _, signed_blocks, another_state = next_epoch_with_attestations( spec, another_state, True, True) all_blocks += signed_blocks assert another_state.finalized_checkpoint.epoch == 2 assert another_state.current_justified_checkpoint.epoch == 3 assert state.finalized_checkpoint != another_state.finalized_checkpoint assert state.current_justified_checkpoint != another_state.current_justified_checkpoint pre_store_justified_checkpoint_root = store.justified_checkpoint.root # Apply blocks of `another_state` to `store` for block in all_blocks: # NOTE: Do not call `on_tick` here yield from add_block(spec, store, block, test_steps) finalized_slot = spec.compute_start_slot_at_epoch( store.finalized_checkpoint.epoch) ancestor_at_finalized_slot = spec.get_ancestor( store, pre_store_justified_checkpoint_root, finalized_slot) assert ancestor_at_finalized_slot != store.finalized_checkpoint.root assert store.finalized_checkpoint == another_state.finalized_checkpoint assert store.justified_checkpoint == another_state.current_justified_checkpoint yield 'steps', test_steps
def test_new_justified_is_later_than_store_justified(spec, state): """ J: Justified F: Finalized fork_1_state (forked from genesis): epoch [0] <- [1] <- [2] <- [3] <- [4] F J fork_2_state (forked from fork_1_state's epoch 2): epoch └──── [3] <- [4] <- [5] <- [6] F J fork_3_state (forked from genesis): [0] <- [1] <- [2] <- [3] <- [4] <- [5] F J """ # The 1st fork, from genesis fork_1_state = state.copy() # The 3rd fork, from genesis fork_3_state = state.copy() test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) yield 'anchor_state', state yield 'anchor_block', anchor_block current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time # ----- Process fork_1_state # Skip epoch 0 next_epoch(spec, fork_1_state) # Fill epoch 1 with previous epoch attestations fork_1_state, store, _ = yield from apply_next_epoch_with_attestations( spec, fork_1_state, store, False, True, test_steps=test_steps) # Fork `fork_2_state` at the start of epoch 2 fork_2_state = fork_1_state.copy() assert spec.get_current_epoch(fork_2_state) == 2 # Skip epoch 2 next_epoch(spec, fork_1_state) # # Fill epoch 3 & 4 with previous epoch attestations for _ in range(2): fork_1_state, store, _ = yield from apply_next_epoch_with_attestations( spec, fork_1_state, store, False, True, test_steps=test_steps) assert fork_1_state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 assert fork_1_state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 assert store.justified_checkpoint == fork_1_state.current_justified_checkpoint # ------ fork_2_state: Create a chain to set store.best_justified_checkpoint # NOTE: The goal is to make `store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch` all_blocks = [] # Proposed an empty block at epoch 2, 1st slot block = build_empty_block_for_next_slot(spec, fork_2_state) signed_block = state_transition_and_sign_block(spec, fork_2_state, block) yield from tick_and_add_block(spec, store, signed_block, test_steps) assert fork_2_state.current_justified_checkpoint.epoch == 0 # Skip to epoch 4 for _ in range(2): next_epoch(spec, fork_2_state) assert fork_2_state.current_justified_checkpoint.epoch == 0 # Propose a block at epoch 4, 5th slot # Propose a block at epoch 5, 5th slot for _ in range(2): next_epoch(spec, fork_2_state) next_slots(spec, fork_2_state, 4) signed_block = state_transition_with_full_attestations_block( spec, fork_2_state, True, True) yield from tick_and_add_block(spec, store, signed_block, test_steps) assert fork_2_state.current_justified_checkpoint.epoch == 0 # Propose a block at epoch 6, SAFE_SLOTS_TO_UPDATE_JUSTIFIED + 2 slot next_epoch(spec, fork_2_state) next_slots(spec, fork_2_state, spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + 2) signed_block = state_transition_with_full_attestations_block( spec, fork_2_state, True, True) assert fork_2_state.finalized_checkpoint.epoch == 0 assert fork_2_state.current_justified_checkpoint.epoch == 5 # Check SAFE_SLOTS_TO_UPDATE_JUSTIFIED time = store.genesis_time + fork_2_state.slot * spec.config.SECONDS_PER_SLOT on_tick_and_append_step(spec, store, time, test_steps) assert spec.compute_slots_since_epoch_start( spec.get_current_slot(store)) >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED # Run on_block yield from add_block(spec, store, signed_block, test_steps) assert store.finalized_checkpoint.epoch == 0 assert store.justified_checkpoint.epoch == 3 assert store.best_justified_checkpoint.epoch == 5 # ------ fork_3_state: Create another chain to test the # "Update justified if new justified is later than store justified" case all_blocks = [] for _ in range(3): next_epoch(spec, fork_3_state) # epoch 3 _, signed_blocks, fork_3_state = next_epoch_with_attestations( spec, fork_3_state, True, True) all_blocks += signed_blocks assert fork_3_state.finalized_checkpoint.epoch == 0 # epoch 4, attest the first 5 blocks _, blocks, fork_3_state = next_slots_with_attestations( spec, fork_3_state, 5, True, True) all_blocks += blocks.copy() assert fork_3_state.finalized_checkpoint.epoch == 0 # Propose a block at epoch 5, 5th slot next_epoch(spec, fork_3_state) next_slots(spec, fork_3_state, 4) signed_block = state_transition_with_full_block(spec, fork_3_state, True, True) all_blocks.append(signed_block.copy()) assert fork_3_state.finalized_checkpoint.epoch == 0 # Propose a block at epoch 6, 5th slot next_epoch(spec, fork_3_state) next_slots(spec, fork_3_state, 4) signed_block = state_transition_with_full_block(spec, fork_3_state, True, True) all_blocks.append(signed_block.copy()) assert fork_3_state.finalized_checkpoint.epoch == 3 assert fork_3_state.current_justified_checkpoint.epoch == 4 # Apply blocks of `fork_3_state` to `store` for block in all_blocks: if store.time < spec.compute_time_at_slot(fork_2_state, block.message.slot): time = store.genesis_time + block.message.slot * spec.config.SECONDS_PER_SLOT on_tick_and_append_step(spec, store, time, test_steps) yield from add_block(spec, store, block, test_steps) assert store.finalized_checkpoint == fork_3_state.finalized_checkpoint assert store.justified_checkpoint == fork_3_state.current_justified_checkpoint assert store.justified_checkpoint != store.best_justified_checkpoint assert store.best_justified_checkpoint == fork_2_state.current_justified_checkpoint yield 'steps', test_steps
def test_on_block_outside_safe_slots_but_finality(spec, state): """ Test `should_update_justified_checkpoint` case - compute_slots_since_epoch_start(get_current_slot(store)) > SAFE_SLOTS_TO_UPDATE_JUSTIFIED - new_justified_checkpoint and store.justified_checkpoint.root are NOT conflicting Thus should_update_justified_checkpoint returns True. Part of this script is similar to `test_new_justified_is_later_than_store_justified`. """ test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) yield 'anchor_state', state yield 'anchor_block', anchor_block current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time # Skip epoch 0 next_epoch(spec, state) # Fill epoch 1 to 3, attest current epoch for _ in range(3): state, store, _ = yield from apply_next_epoch_with_attestations( spec, state, store, True, False, test_steps=test_steps) assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 # Skip epoch 4-6 for _ in range(3): next_epoch(spec, state) # epoch 7 state, store, _ = yield from apply_next_epoch_with_attestations( spec, state, store, True, True, test_steps=test_steps) assert state.finalized_checkpoint.epoch == 2 assert state.current_justified_checkpoint.epoch == 7 # epoch 8, attest the first 5 blocks state, store, _ = yield from apply_next_slots_with_attestations( spec, state, store, 5, True, True, test_steps) assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 7 # Propose a block at epoch 9, 5th slot next_epoch(spec, state) next_slots(spec, state, 4) signed_block = state_transition_with_full_attestations_block( spec, state, True, True) yield from tick_and_add_block(spec, store, signed_block, test_steps) assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 7 # Propose an empty block at epoch 10, SAFE_SLOTS_TO_UPDATE_JUSTIFIED + 2 slot # This block would trigger justification and finality updates on store next_epoch(spec, state) next_slots(spec, state, 4) block = build_empty_block_for_next_slot(spec, state) signed_block = state_transition_and_sign_block(spec, state, block) assert state.finalized_checkpoint.epoch == 7 assert state.current_justified_checkpoint.epoch == 8 # Step time past safe slots and run on_block if store.time < spec.compute_time_at_slot(state, signed_block.message.slot): time = store.genesis_time + signed_block.message.slot * spec.config.SECONDS_PER_SLOT on_tick_and_append_step(spec, store, time, test_steps) assert spec.get_current_slot( store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED yield from add_block(spec, store, signed_block, test_steps) # Ensure justified_checkpoint finality has been changed assert store.finalized_checkpoint.epoch == 7 assert store.finalized_checkpoint == state.finalized_checkpoint assert store.justified_checkpoint.epoch == 8 assert store.justified_checkpoint == state.current_justified_checkpoint yield 'steps', test_steps
def test_ex_ante_sandwich_with_boost_not_sufficient(spec, state): """ Boost not sufficient to sandwich attack. Objects: Block A - slot N Block B (parent A) - slot N+1 Block C (parent A) - slot N+2 Block D (parent B) - slot N+3 Attestation_set_1 (Block C); size proposer_boost + 1 - slot N+2 Steps: Block A received at N — A is head Block C received at N+2 — C is head Block B received at N+2 — C is head Attestation_set_1 received — C is head Block D received at N+3 — C is head """ test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) yield 'anchor_state', state yield 'anchor_block', anchor_block current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time # On receiving block A at slot `N` yield from _apply_base_block_a(spec, state, store, test_steps) state_a = state.copy() # Block B at slot `N + 1`, parent is A state_b = state_a.copy() block = build_empty_block(spec, state_a, slot=state_a.slot + 1) signed_block_b = state_transition_and_sign_block(spec, state_b, block) # Block C at slot `N + 2`, parent is A state_c = state_a.copy() block = build_empty_block(spec, state_c, slot=state_a.slot + 2) signed_block_c = state_transition_and_sign_block(spec, state_c, block) # Block D at slot `N + 3`, parent is B state_d = state_b.copy() block = build_empty_block(spec, state_d, slot=state_a.slot + 3) signed_block_d = state_transition_and_sign_block(spec, state_d, block) # Block C received at N+2 — C is head time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, time, test_steps) yield from add_block(spec, store, signed_block_c, test_steps) assert spec.get_head(store) == signed_block_c.message.hash_tree_root() # Block B received at N+2 — C is head, it has proposer score boost yield from add_block(spec, store, signed_block_b, test_steps) assert spec.get_head(store) == signed_block_c.message.hash_tree_root() # Attestation_set_1 at N+2 voting for block C proposer_boost_root = signed_block_c.message.hash_tree_root() root = signed_block_c.message.hash_tree_root() participant_num = _get_greater_than_proposer_boost_score( spec, store, state, proposer_boost_root, root) def _filter_participant_set(participants): return [ index for i, index in enumerate(participants) if i < participant_num ] attestation = get_valid_attestation( spec, state_c, slot=state_c.slot, signed=False, filter_participant_set=_filter_participant_set) attestation.data.beacon_block_root = signed_block_c.message.hash_tree_root( ) assert len([i for i in attestation.aggregation_bits if i == 1]) == participant_num sign_attestation(spec, state_c, attestation) # Attestation_1 received at N+3 — B is head because B's attestation_score > C's proposer_score. # (B's proposer_score = C's attestation_score = 0) time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, time, test_steps) yield from add_attestation(spec, store, attestation, test_steps) assert spec.get_head(store) == signed_block_c.message.hash_tree_root() # Block D received at N+3 - C is head, D's boost not sufficient! yield from add_block(spec, store, signed_block_d, test_steps) assert spec.get_head(store) == signed_block_c.message.hash_tree_root() yield 'steps', test_steps
def test_ex_ante_vanilla(spec, state): """ With a single adversarial attestation Objects: Block A - slot N Block B (parent A) - slot N+1 Block C (parent A) - slot N+2 Attestation_1 (Block B); size `1` - slot N+1 Steps: Block A received at N — A is head Block C received at N+2 — C is head Block B received at N+2 — C is head Attestation_1 received at N+2 — C is head """ test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) yield 'anchor_state', state yield 'anchor_block', anchor_block current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time # On receiving block A at slot `N` yield from _apply_base_block_a(spec, state, store, test_steps) state_a = state.copy() # Block B at slot `N + 1`, parent is A state_b = state_a.copy() block = build_empty_block(spec, state_a, slot=state_a.slot + 1) signed_block_b = state_transition_and_sign_block(spec, state_b, block) # Block C at slot `N + 2`, parent is A state_c = state_a.copy() block = build_empty_block(spec, state_c, slot=state_a.slot + 2) signed_block_c = state_transition_and_sign_block(spec, state_c, block) # Attestation_1 at slot `N + 1` voting for block B def _filter_participant_set(participants): return [next(iter(participants))] attestation = get_valid_attestation( spec, state_b, slot=state_b.slot, signed=False, filter_participant_set=_filter_participant_set) attestation.data.beacon_block_root = signed_block_b.message.hash_tree_root( ) assert len([i for i in attestation.aggregation_bits if i == 1]) == 1 sign_attestation(spec, state_b, attestation) # Block C received at N+2 — C is head time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, time, test_steps) yield from add_block(spec, store, signed_block_c, test_steps) assert spec.get_head(store) == signed_block_c.message.hash_tree_root() # Block B received at N+2 — C is head due to proposer score boost yield from add_block(spec, store, signed_block_b, test_steps) assert spec.get_head(store) == signed_block_c.message.hash_tree_root() # Attestation_1 received at N+2 — C is head yield from add_attestation(spec, store, attestation, test_steps) assert spec.get_head(store) == signed_block_c.message.hash_tree_root() yield 'steps', test_steps