From 9f4e59b0bc5c9552e79313ab3dfdeee870b6b4a0 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 18 Apr 2019 18:33:06 -0600 Subject: [PATCH] enhance finality testing -- case 1, 2, 4 --- specs/core/0_beacon-chain.md | 4 + test_libs/pyspec/tests/helpers.py | 1 - test_libs/pyspec/tests/test_finality.py | 156 ++++++++++++++++++++++++ test_libs/pyspec/tests/test_sanity.py | 27 ---- 4 files changed, 160 insertions(+), 28 deletions(-) create mode 100644 test_libs/pyspec/tests/test_finality.py diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index eaceec6ac..4cdfa9c4f 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1697,18 +1697,22 @@ def process_justification_and_finalization(state: BeaconState) -> None: current_epoch = get_current_epoch(state) # The 2nd/3rd/4th most recent epochs are justified, the 2nd using the 4th as source if (bitfield >> 1) % 8 == 0b111 and old_previous_justified_epoch == current_epoch - 3: + print("rule 1") state.finalized_epoch = old_previous_justified_epoch state.finalized_root = get_block_root(state, get_epoch_start_slot(state.finalized_epoch)) # The 2nd/3rd most recent epochs are justified, the 2nd using the 3rd as source if (bitfield >> 1) % 4 == 0b11 and old_previous_justified_epoch == current_epoch - 2: + print("rule 2") state.finalized_epoch = old_previous_justified_epoch state.finalized_root = get_block_root(state, get_epoch_start_slot(state.finalized_epoch)) # The 1st/2nd/3rd most recent epochs are justified, the 1st using the 3rd as source if (bitfield >> 0) % 8 == 0b111 and old_current_justified_epoch == current_epoch - 2: + print("rule 3") state.finalized_epoch = old_current_justified_epoch state.finalized_root = get_block_root(state, get_epoch_start_slot(state.finalized_epoch)) # The 1st/2nd most recent epochs are justified, the 1st using the 2nd as source if (bitfield >> 0) % 4 == 0b11 and old_current_justified_epoch == current_epoch - 1: + print("rule 4") state.finalized_epoch = old_current_justified_epoch state.finalized_root = get_block_root(state, get_epoch_start_slot(state.finalized_epoch)) ``` diff --git a/test_libs/pyspec/tests/helpers.py b/test_libs/pyspec/tests/helpers.py index 44d2dcb4d..387c434a0 100644 --- a/test_libs/pyspec/tests/helpers.py +++ b/test_libs/pyspec/tests/helpers.py @@ -155,7 +155,6 @@ def build_attestation_data(state, slot, shard): current_epoch_start_slot = get_epoch_start_slot(get_current_epoch(state)) if slot < current_epoch_start_slot: - print(slot) epoch_boundary_root = get_block_root(state, get_epoch_start_slot(get_previous_epoch(state))) elif slot == current_epoch_start_slot: epoch_boundary_root = block_root diff --git a/test_libs/pyspec/tests/test_finality.py b/test_libs/pyspec/tests/test_finality.py new file mode 100644 index 000000000..8a429cb6e --- /dev/null +++ b/test_libs/pyspec/tests/test_finality.py @@ -0,0 +1,156 @@ +from copy import deepcopy + +import pytest + +import eth2spec.phase0.spec as spec + +from eth2spec.phase0.state_transition import ( + state_transition, +) +from .helpers import ( + build_empty_block_for_next_slot, + fill_aggregate_attestation, + get_current_epoch, + get_epoch_start_slot, + get_valid_attestation, + next_epoch, +) + +# mark entire file as 'state' +pytestmark = pytest.mark.state + + +def check_finality(state, + prev_state, + current_justified_changed, + previous_justified_changed, + finalized_changed): + if current_justified_changed: + assert state.current_justified_epoch > prev_state.current_justified_epoch + assert state.current_justified_root != prev_state.current_justified_root + else: + assert state.current_justified_epoch == prev_state.current_justified_epoch + assert state.current_justified_root == prev_state.current_justified_root + + if previous_justified_changed: + assert state.previous_justified_epoch > prev_state.previous_justified_epoch + assert state.previous_justified_root != prev_state.previous_justified_root + else: + assert state.previous_justified_epoch == prev_state.previous_justified_epoch + assert state.previous_justified_root == prev_state.previous_justified_root + + if finalized_changed: + assert state.finalized_epoch > prev_state.finalized_epoch + assert state.finalized_root != prev_state.finalized_root + else: + assert state.finalized_epoch == prev_state.finalized_epoch + assert state.finalized_root == prev_state.finalized_root + + +def test_finality_from_genesis_rule_4(state): + test_state = deepcopy(state) + + blocks = [] + for epoch in range(6): + old_current_justified_epoch = test_state.current_justified_epoch + old_current_justified_root = test_state.current_justified_root + for slot in range(spec.SLOTS_PER_EPOCH): + attestation = None + slot_to_attest = test_state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1 + if slot_to_attest >= spec.GENESIS_SLOT: + attestation = get_valid_attestation(test_state, slot_to_attest) + fill_aggregate_attestation(test_state, attestation) + block = build_empty_block_for_next_slot(test_state) + if attestation: + block.body.attestations.append(attestation) + state_transition(test_state, block) + blocks.append(block) + + if epoch == 0: + check_finality(test_state, state, False, False, False) + elif epoch == 1: + check_finality(test_state, state, False, False, False) + elif epoch == 2: + check_finality(test_state, state, True, False, False) + elif epoch >= 3: + # rule 4 of finaliy + check_finality(test_state, state, True, True, True) + assert test_state.finalized_epoch == old_current_justified_epoch + assert test_state.finalized_root == old_current_justified_root + + return state, blocks, test_state + + +def test_finality_rule_1(state): + # get past first two epochs that finality does not run on + next_epoch(state) + next_epoch(state) + + test_state = deepcopy(state) + + blocks = [] + for epoch in range(3): + old_previous_justified_epoch = test_state.previous_justified_epoch + old_previous_justified_root = test_state.previous_justified_root + for slot in range(spec.SLOTS_PER_EPOCH): + slot_to_attest = test_state.slot - spec.SLOTS_PER_EPOCH + 1 + attestation = get_valid_attestation(test_state, slot_to_attest) + fill_aggregate_attestation(test_state, attestation) + block = build_empty_block_for_next_slot(test_state) + block.body.attestations.append(attestation) + state_transition(test_state, block) + + assert len(test_state.previous_epoch_attestations) >= 0 + assert len(test_state.current_epoch_attestations) == 0 + + blocks.append(block) + + if epoch == 0: + check_finality(test_state, state, True, False, False) + elif epoch == 1: + check_finality(test_state, state, True, True, False) + elif epoch == 2: + # finalized by rule 1 + check_finality(test_state, state, True, True, True) + assert test_state.finalized_epoch == old_previous_justified_epoch + assert test_state.finalized_root == old_previous_justified_root + + +def test_finality_rule_2(state): + # get past first two epochs that finality does not run on + next_epoch(state) + next_epoch(state) + + test_state = deepcopy(state) + + blocks = [] + for epoch in range(3): + old_previous_justified_epoch = test_state.previous_justified_epoch + old_previous_justified_root = test_state.previous_justified_root + for slot in range(spec.SLOTS_PER_EPOCH): + attestation = None + if epoch == 0: + slot_to_attest = test_state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1 + if slot_to_attest >= get_epoch_start_slot(get_current_epoch(state)): + attestation = get_valid_attestation(test_state, slot_to_attest) + fill_aggregate_attestation(test_state, attestation) + if epoch == 2: + slot_to_attest = test_state.slot - spec.SLOTS_PER_EPOCH + 1 + attestation = get_valid_attestation(test_state, slot_to_attest) + fill_aggregate_attestation(test_state, attestation) + + block = build_empty_block_for_next_slot(test_state) + if attestation: + block.body.attestations.append(attestation) + state_transition(test_state, block) + blocks.append(block) + + if epoch == 0: + check_finality(test_state, state, True, False, False) + elif epoch == 1: + check_finality(test_state, state, True, True, False) + elif epoch == 2: + # finalized by rule 2 + check_finality(test_state, state, True, True, True) + assert test_state.finalized_epoch == old_previous_justified_epoch + assert test_state.finalized_root == old_previous_justified_root diff --git a/test_libs/pyspec/tests/test_sanity.py b/test_libs/pyspec/tests/test_sanity.py index 29333a7ad..e48a6b774 100644 --- a/test_libs/pyspec/tests/test_sanity.py +++ b/test_libs/pyspec/tests/test_sanity.py @@ -145,33 +145,6 @@ def test_empty_epoch_transition_not_finalizing(state): return state, [block], test_state -def test_full_attestations_finalizing(state): - test_state = deepcopy(state) - - for slot in range(spec.MIN_ATTESTATION_INCLUSION_DELAY): - next_slot(test_state) - - for epoch in range(5): - for slot in range(spec.SLOTS_PER_EPOCH): - print(test_state.slot) - attestation = get_valid_attestation(test_state, test_state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY) - fill_aggregate_attestation(test_state, attestation) - block = build_empty_block_for_next_slot(test_state) - block.body.attestations.append(attestation) - state_transition(test_state, block) - - if epoch == 0: - check_finality(test_state, state, False, False, False) - elif epoch == 1: - check_finality(test_state, state, False, False, False) - elif epoch == 2: - check_finality(test_state, state, True, False, False) - elif epoch == 3: - check_finality(test_state, state, True, True, False) - elif epoch == 4: - check_finality(test_state, state, True, True, True) - - def test_proposer_slashing(state): test_state = deepcopy(state) proposer_slashing = get_valid_proposer_slashing(state)