From d4d17ecc4e06b92852b8b4592895bcc1d253d087 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 9 Dec 2019 16:47:43 -0700 Subject: [PATCH 01/21] ensure the best better-justification is stored in fork choice --- specs/core/0_fork-choice.md | 3 +- .../test/fork_choice/test_on_block.py | 40 ++++++++++++------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/specs/core/0_fork-choice.md b/specs/core/0_fork-choice.md index 6a5afdf5b..b957d65c1 100644 --- a/specs/core/0_fork-choice.md +++ b/specs/core/0_fork-choice.md @@ -281,7 +281,8 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: # Update justified checkpoint if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: - store.best_justified_checkpoint = state.current_justified_checkpoint + if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch: + store.best_justified_checkpoint = state.current_justified_checkpoint if should_update_justified_checkpoint(store, state.current_justified_checkpoint): store.justified_checkpoint = state.current_justified_checkpoint diff --git a/test_libs/pyspec/eth2spec/test/fork_choice/test_on_block.py b/test_libs/pyspec/eth2spec/test/fork_choice/test_on_block.py index 4fda49664..10d1c0011 100644 --- a/test_libs/pyspec/eth2spec/test/fork_choice/test_on_block.py +++ b/test_libs/pyspec/eth2spec/test/fork_choice/test_on_block.py @@ -168,7 +168,7 @@ def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): @with_all_phases @spec_state_test -def test_on_block_outside_safe_slots_and_old_block(spec, state): +def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): # Initialization store = spec.get_genesis_store(state) time = 100 @@ -187,20 +187,30 @@ def test_on_block_outside_safe_slots_and_old_block(spec, state): just_block.slot = spec.compute_start_slot_at_epoch(store.justified_checkpoint.epoch) store.blocks[just_block.hash_tree_root()] = just_block - # Mock the justified checkpoint - just_state = store.block_states[last_block_root] - new_justified = spec.Checkpoint( - epoch=just_state.current_justified_checkpoint.epoch + 1, - root=just_block.hash_tree_root(), - ) - just_state.current_justified_checkpoint = new_justified - - block = build_empty_block_for_next_slot(spec, just_state) - signed_block = state_transition_and_sign_block(spec, deepcopy(just_state), block) - + # Step time past safe slots spec.on_tick(store, store.time + spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED * spec.SECONDS_PER_SLOT) assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED - run_on_block(spec, store, signed_block) - assert store.justified_checkpoint != new_justified - assert store.best_justified_checkpoint == new_justified + previously_justified = store.justified_checkpoint + + # Add a series of new blocks with "better" justifications + best_justified_checkpoint = spec.Checkpoint(epoch=0) + for i in range(3, 0, -1): + just_state = store.block_states[last_block_root] + new_justified = spec.Checkpoint( + epoch=previously_justified.epoch + i, + root=just_block.hash_tree_root(), + ) + if new_justified.epoch > best_justified_checkpoint.epoch: + best_justified_checkpoint = new_justified + + just_state.current_justified_checkpoint = new_justified + + block = build_empty_block_for_next_slot(spec, just_state) + signed_block = state_transition_and_sign_block(spec, deepcopy(just_state), block) + + run_on_block(spec, store, signed_block) + + assert store.justified_checkpoint == previously_justified + # ensure the best from the series was stored + assert store.best_justified_checkpoint == best_justified_checkpoint From 432257659e46754dc93ca41f8cf29c44a7eb4cc8 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 9 Dec 2019 17:31:43 -0700 Subject: [PATCH 02/21] add asserts to ensure that attestation slot must match the target epoch --- specs/core/0_beacon-chain.md | 1 + specs/core/0_fork-choice.md | 1 + .../test/fork_choice/test_on_attestation.py | 23 +++++++++++++++++++ .../test_process_attestation.py | 14 +++++++++++ 4 files changed, 39 insertions(+) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index fca21994f..1655d57d9 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1482,6 +1482,7 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: data = attestation.data assert data.index < get_committee_count_at_slot(state, data.slot) assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state)) + assert data.target.epoch == compute_epoch_at_slot(data.slot) assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= data.slot + SLOTS_PER_EPOCH committee = get_beacon_committee(state, data.slot, data.index) diff --git a/specs/core/0_fork-choice.md b/specs/core/0_fork-choice.md index 6a5afdf5b..f7d863614 100644 --- a/specs/core/0_fork-choice.md +++ b/specs/core/0_fork-choice.md @@ -307,6 +307,7 @@ def on_attestation(store: Store, attestation: Attestation) -> None: # Use GENESIS_EPOCH for previous when genesis to avoid underflow previous_epoch = current_epoch - 1 if current_epoch > GENESIS_EPOCH else GENESIS_EPOCH assert target.epoch in [current_epoch, previous_epoch] + assert target.epoch == compute_epoch_at_slot(attestation.data.slot) # Attestations target be for a known block. If target block is unknown, delay consideration until the block is found assert target.root in store.blocks diff --git a/test_libs/pyspec/eth2spec/test/fork_choice/test_on_attestation.py b/test_libs/pyspec/eth2spec/test/fork_choice/test_on_attestation.py index 8f1d6f74f..d7fbc4777 100644 --- a/test_libs/pyspec/eth2spec/test/fork_choice/test_on_attestation.py +++ b/test_libs/pyspec/eth2spec/test/fork_choice/test_on_attestation.py @@ -84,6 +84,29 @@ def test_on_attestation_past_epoch(spec, state): run_on_attestation(spec, state, store, attestation, False) +@with_all_phases +@spec_state_test +def test_on_attestation_mismatched_target_and_slot(spec, state): + store = spec.get_genesis_store(state) + spec.on_tick(store, store.time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH) + + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + + # store block in store + spec.on_block(store, signed_block) + + attestation = get_valid_attestation(spec, state, slot=block.slot) + attestation.data.target.epoch += 1 + sign_attestation(spec, state, attestation) + + assert attestation.data.target.epoch == spec.GENESIS_EPOCH + 1 + assert spec.compute_epoch_at_slot(attestation.data.slot) == spec.GENESIS_EPOCH + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == spec.GENESIS_EPOCH + 1 + + run_on_attestation(spec, state, store, attestation, False) + + @with_all_phases @spec_state_test def test_on_attestation_target_not_in_store(spec, state): diff --git a/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py b/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py index f19bc66d3..d48386fd4 100644 --- a/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py +++ b/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py @@ -177,6 +177,20 @@ def test_invalid_index(spec, state): yield from run_attestation_processing(spec, state, attestation, False) +@with_all_phases +@spec_state_test +def test_mismatched_target_and_slot(spec, state): + next_epoch(spec, state) + next_epoch(spec, state) + + attestation = get_valid_attestation(spec, state) + attestation.data.slot = attestation.data.slot - spec.SLOTS_PER_EPOCH + + sign_attestation(spec, state, attestation) + + yield from run_attestation_processing(spec, state, attestation, False) + + @with_all_phases @spec_state_test def test_old_target_epoch(spec, state): From 9989f3ea9d3ad72507ceb409b051dcef312bc134 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 9 Dec 2019 19:02:16 -0700 Subject: [PATCH 03/21] ensure fork choice functions when GENESIS_SLOT != 0 --- specs/core/0_fork-choice.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/specs/core/0_fork-choice.md b/specs/core/0_fork-choice.md index 6a5afdf5b..56fe7fef6 100644 --- a/specs/core/0_fork-choice.md +++ b/specs/core/0_fork-choice.md @@ -13,9 +13,15 @@ - [`LatestMessage`](#latestmessage) - [`Store`](#store) - [`get_genesis_store`](#get_genesis_store) + - [`get_slots_since_genesis`](#get_slots_since_genesis) + - [`get_current_slot`](#get_current_slot) + - [`compute_slots_since_epoch_start`](#compute_slots_since_epoch_start) - [`get_ancestor`](#get_ancestor) - [`get_latest_attesting_balance`](#get_latest_attesting_balance) + - [`filter_block_tree`](#filter_block_tree) + - [`get_filtered_block_tree`](#get_filtered_block_tree) - [`get_head`](#get_head) + - [`should_update_justified_checkpoint`](#should_update_justified_checkpoint) - [Handlers](#handlers) - [`on_tick`](#on_tick) - [`on_block`](#on_block) @@ -96,11 +102,18 @@ def get_genesis_store(genesis_state: BeaconState) -> Store: ) ``` +#### `get_slots_since_genesis` + +```python +def get_slots_since_genesis(store: Store) -> Slot: + return Slot((store.time - store.genesis_time) // SECONDS_PER_SLOT) +``` + #### `get_current_slot` ```python def get_current_slot(store: Store) -> Slot: - return Slot((store.time - store.genesis_time) // SECONDS_PER_SLOT) + return GENESIS_SLOT + get_slots_since_genesis(store) ``` #### `compute_slots_since_epoch_start` @@ -327,7 +340,7 @@ def on_attestation(store: Store, attestation: Attestation) -> None: # Attestations can only affect the fork choice of subsequent slots. # Delay consideration in the fork choice until their slot is in the past. - assert store.time >= (attestation.data.slot + 1) * SECONDS_PER_SLOT + assert get_current_slot(store) >= attestation.data.slot + 1 # Get state at the `target` to validate attestation and calculate the committees indexed_attestation = get_indexed_attestation(target_state, attestation) From 2c5c9cb71a21c1907c9ad226efe9d25c0ea97c8c Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 10 Dec 2019 10:12:51 -0700 Subject: [PATCH 04/21] feedback from mikhail --- specs/core/0_fork-choice.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/specs/core/0_fork-choice.md b/specs/core/0_fork-choice.md index 56fe7fef6..0aaa7d953 100644 --- a/specs/core/0_fork-choice.md +++ b/specs/core/0_fork-choice.md @@ -105,15 +105,15 @@ def get_genesis_store(genesis_state: BeaconState) -> Store: #### `get_slots_since_genesis` ```python -def get_slots_since_genesis(store: Store) -> Slot: - return Slot((store.time - store.genesis_time) // SECONDS_PER_SLOT) +def get_slots_since_genesis(store: Store) -> int: + return (store.time - store.genesis_time) // SECONDS_PER_SLOT ``` #### `get_current_slot` ```python def get_current_slot(store: Store) -> Slot: - return GENESIS_SLOT + get_slots_since_genesis(store) + return Slot(GENESIS_SLOT + get_slots_since_genesis(store)) ``` #### `compute_slots_since_epoch_start` @@ -277,7 +277,7 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: assert block.parent_root in store.block_states pre_state = store.block_states[block.parent_root].copy() # Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past. - assert store.time >= pre_state.genesis_time + block.slot * SECONDS_PER_SLOT + assert get_current_slot(store) >= block.slot # Add new block to the store store.blocks[hash_tree_root(block)] = block # Check block is a descendant of the finalized block @@ -325,7 +325,7 @@ def on_attestation(store: Store, attestation: Attestation) -> None: assert target.root in store.blocks # Attestations cannot be from future epochs. If they are, delay consideration until the epoch arrives base_state = store.block_states[target.root].copy() - assert store.time >= base_state.genesis_time + compute_start_slot_at_epoch(target.epoch) * SECONDS_PER_SLOT + assert get_current_slot(store) >= compute_start_slot_at_epoch(target.epoch) # Attestations must be for a known block. If block is unknown, delay consideration until the block is found assert attestation.data.beacon_block_root in store.blocks From d126162ca8e687e0a687754b8e389f9cf68955b8 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 10 Dec 2019 11:25:55 -0700 Subject: [PATCH 05/21] fix activation queue efficiency --- specs/core/0_beacon-chain.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 9c7248c56..4caf8a862 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1311,17 +1311,16 @@ def process_registry_updates(state: BeaconState) -> None: if is_active_validator(validator, get_current_epoch(state)) and validator.effective_balance <= EJECTION_BALANCE: initiate_validator_exit(state, ValidatorIndex(index)) - # Queue validators eligible for activation and not dequeued for activation prior to finalized epoch + # Queue validators eligible for activation and not yet dequeued for activation prior activation_queue = sorted([ index for index, validator in enumerate(state.validators) if validator.activation_eligibility_epoch != FAR_FUTURE_EPOCH - and validator.activation_epoch >= compute_activation_exit_epoch(state.finalized_checkpoint.epoch) + and validator.activation_epoch == FAR_FUTURE_EPOCH ], key=lambda index: state.validators[index].activation_eligibility_epoch) - # Dequeued validators for activation up to churn limit (without resetting activation epoch) + # Dequeued validators for activation up to churn limit for index in activation_queue[:get_validator_churn_limit(state)]: validator = state.validators[index] - if validator.activation_epoch == FAR_FUTURE_EPOCH: - validator.activation_epoch = compute_activation_exit_epoch(get_current_epoch(state)) + validator.activation_epoch = compute_activation_exit_epoch(get_current_epoch(state)) ``` #### Slashings From e4d710590a1b9421b4701eb0626f892fc8679e21 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 10 Dec 2019 11:49:26 -0700 Subject: [PATCH 06/21] add test for queue efficiency --- specs/core/0_beacon-chain.md | 2 +- .../test_process_registry_updates.py | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 4caf8a862..35fd439c1 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1311,7 +1311,7 @@ def process_registry_updates(state: BeaconState) -> None: if is_active_validator(validator, get_current_epoch(state)) and validator.effective_balance <= EJECTION_BALANCE: initiate_validator_exit(state, ValidatorIndex(index)) - # Queue validators eligible for activation and not yet dequeued for activation prior + # Queue validators eligible for activation and not yet dequeued for activation activation_queue = sorted([ index for index, validator in enumerate(state.validators) if validator.activation_eligibility_epoch != FAR_FUTURE_EPOCH diff --git a/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py b/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py index bfd992ffa..26eb26104 100644 --- a/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py +++ b/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py @@ -63,6 +63,34 @@ def test_activation_queue_sorting(spec, state): assert state.validators[churn_limit - 2].activation_epoch != spec.FAR_FUTURE_EPOCH +@with_all_phases +@spec_state_test +def test_activation_queue_efficiency(spec, state): + churn_limit = spec.get_validator_churn_limit(state) + mock_activations = churn_limit * 2 + + epoch = spec.get_current_epoch(state) + for i in range(mock_activations): + mock_deposit(spec, state, i) + state.validators[i].activation_eligibility_epoch = epoch + 1 + + # Run first registry update. Do not yield test vectors + for _ in run_process_registry_updates(spec, state): + pass + + # Half should churn in first run of registry update + for i in range(mock_activations): + if i < mock_activations // 2: + assert state.validators[i].activation_epoch < spec.FAR_FUTURE_EPOCH + else: + assert state.validators[i].activation_epoch == spec.FAR_FUTURE_EPOCH + + # Second half should churn in second run of registry update + yield from run_process_registry_updates(spec, state) + for i in range(mock_activations): + assert state.validators[i].activation_epoch < spec.FAR_FUTURE_EPOCH + + @with_all_phases @spec_state_test def test_ejection(spec, state): From 6610aeea2fabea028180b2123e8b93ed800554d9 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 11 Dec 2019 16:00:46 -0700 Subject: [PATCH 07/21] fix activation queue to finality --- specs/core/0_beacon-chain.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 35fd439c1..5f8b84376 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -601,6 +601,21 @@ def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool: return (not validator.slashed) and (validator.activation_epoch <= epoch < validator.withdrawable_epoch) ``` +#### `is_eligible_for_activation` + +```python +def is_eligible_for_activation(state: BeaconState, validator: Validator) -> bool: + """ + Check if ``validator`` is eligible for activation. + """ + return ( + # Was placed in activation queue prior to most recent finalized epoch + validator.activation_eligibility_epoch < state.finalized_checkpoint.epoch + # Has not yet been activated + and validator.activation_epoch == FAR_FUTURE_EPOCH + ) +``` + #### `is_slashable_attestation_data` ```python @@ -1314,8 +1329,7 @@ def process_registry_updates(state: BeaconState) -> None: # Queue validators eligible for activation and not yet dequeued for activation activation_queue = sorted([ index for index, validator in enumerate(state.validators) - if validator.activation_eligibility_epoch != FAR_FUTURE_EPOCH - and validator.activation_epoch == FAR_FUTURE_EPOCH + if is_eligible_for_activation(state, validator) ], key=lambda index: state.validators[index].activation_eligibility_epoch) # Dequeued validators for activation up to churn limit for index in activation_queue[:get_validator_churn_limit(state)]: From e117b58ae2269afc56d2ce2dce397cfbe21f8954 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 11 Dec 2019 16:10:18 -0700 Subject: [PATCH 08/21] add queue eligibility helper --- specs/core/0_beacon-chain.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 5f8b84376..210e1797f 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -601,6 +601,20 @@ def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool: return (not validator.slashed) and (validator.activation_epoch <= epoch < validator.withdrawable_epoch) ``` +#### `is_eligible_for_activation_queue` + +```python +def is_eligible_for_activation_queue(validator: Validator) -> bool: + """ + Check if ``validator`` is eligible to be placed into the activation queue. + """ + return ( + validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH + and validator.effective_balance == MAX_EFFECTIVE_BALANCE + ) +``` + + #### `is_eligible_for_activation` ```python @@ -1317,10 +1331,7 @@ def process_rewards_and_penalties(state: BeaconState) -> None: def process_registry_updates(state: BeaconState) -> None: # Process activation eligibility and ejections for index, validator in enumerate(state.validators): - if ( - validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH - and validator.effective_balance == MAX_EFFECTIVE_BALANCE - ): + if is_eligible_for_activation_queue(validator): validator.activation_eligibility_epoch = get_current_epoch(state) if is_active_validator(validator, get_current_epoch(state)) and validator.effective_balance <= EJECTION_BALANCE: From e8d079b366ed4d1a81b9a5e5c9d503338d1d8721 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 11 Dec 2019 17:06:14 -0700 Subject: [PATCH 09/21] fix and add tests for activation queue --- .../test_process_registry_updates.py | 80 ++++++++++++++++--- 1 file changed, 71 insertions(+), 9 deletions(-) diff --git a/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py b/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py index 26eb26104..b6bd47204 100644 --- a/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py +++ b/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py @@ -1,4 +1,7 @@ -from eth2spec.test.helpers.state import next_epoch +from eth2spec.test.helpers.state import ( + next_epoch, + next_epoch_with_attestations, +) from eth2spec.test.context import spec_state_test, with_all_phases from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with @@ -17,18 +20,71 @@ def mock_deposit(spec, state, index): @with_all_phases @spec_state_test -def test_activation(spec, state): +def test_add_to_activation_queue(spec, state): + # move past first two irregular epochs wrt finality + next_epoch(spec, state) + next_epoch(spec, state) + index = 0 mock_deposit(spec, state, index) - for _ in range(spec.MAX_SEED_LOOKAHEAD + 1): - next_epoch(spec, state) + yield from run_process_registry_updates(spec, state) + + # validator moved into queue + assert state.validators[index].activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH + assert state.validators[index].activation_epoch == spec.FAR_FUTURE_EPOCH + assert not spec.is_active_validator(state.validators[index], spec.get_current_epoch(state)) + + +@with_all_phases +@spec_state_test +def test_activation_queue_to_activated_if_finalized(spec, state): + # move past first two irregular epochs wrt finality + next_epoch(spec, state) + next_epoch(spec, state) + + index = 0 + mock_deposit(spec, state, index) + + # mock validator as having been in queue since before latest finalized + state.finalized_checkpoint.epoch = spec.get_current_epoch(state) - 1 + state.validators[index].activation_eligibility_epoch = state.finalized_checkpoint.epoch - 1 + + assert not spec.is_active_validator(state.validators[index], spec.get_current_epoch(state)) yield from run_process_registry_updates(spec, state) + # validator activated for future epoch assert state.validators[index].activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH assert state.validators[index].activation_epoch != spec.FAR_FUTURE_EPOCH - assert spec.is_active_validator(state.validators[index], spec.get_current_epoch(state)) + assert not spec.is_active_validator(state.validators[index], spec.get_current_epoch(state)) + assert spec.is_active_validator( + state.validators[index], + spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) + ) + + +@with_all_phases +@spec_state_test +def test_activation_queue_no_activation_no_finality(spec, state): + # move past first two irregular epochs wrt finality + next_epoch(spec, state) + next_epoch(spec, state) + + index = 0 + mock_deposit(spec, state, index) + + # mock validator as having been in queue only since latest finalized (not before) + state.finalized_checkpoint.epoch = spec.get_current_epoch(state) - 1 + state.validators[index].activation_eligibility_epoch = state.finalized_checkpoint.epoch + + assert not spec.is_active_validator(state.validators[index], spec.get_current_epoch(state)) + + yield from run_process_registry_updates(spec, state) + + # validator not activated + assert state.validators[index].activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH + assert state.validators[index].activation_epoch == spec.FAR_FUTURE_EPOCH @with_all_phases @@ -44,6 +100,10 @@ def test_activation_queue_sorting(spec, state): # give the last priority over the others state.validators[mock_activations - 1].activation_eligibility_epoch = epoch + # move state forward and finalize to allow for activations + state.slot += spec.SLOTS_PER_EPOCH * 3 + state.finalized_checkpoint.epoch = epoch + 2 + # make sure we are hitting the churn churn_limit = spec.get_validator_churn_limit(state) assert mock_activations > churn_limit @@ -74,6 +134,10 @@ def test_activation_queue_efficiency(spec, state): mock_deposit(spec, state, i) state.validators[i].activation_eligibility_epoch = epoch + 1 + # move state forward and finalize to allow for activations + state.slot += spec.SLOTS_PER_EPOCH * 3 + state.finalized_checkpoint.epoch = epoch + 2 + # Run first registry update. Do not yield test vectors for _ in run_process_registry_updates(spec, state): pass @@ -101,13 +165,11 @@ def test_ejection(spec, state): # Mock an ejection state.validators[index].effective_balance = spec.EJECTION_BALANCE - for _ in range(spec.MAX_SEED_LOOKAHEAD + 1): - next_epoch(spec, state) - yield from run_process_registry_updates(spec, state) assert state.validators[index].exit_epoch != spec.FAR_FUTURE_EPOCH + assert spec.is_active_validator(state.validators[index], spec.get_current_epoch(state)) assert not spec.is_active_validator( state.validators[index], - spec.get_current_epoch(state), + spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) ) From b6d7cd93e9706702490d87cdf2d9dd202c392a45 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 11 Dec 2019 17:20:35 -0700 Subject: [PATCH 10/21] Add ejection/exit queue test --- .../test_process_registry_updates.py | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py b/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py index b6bd47204..26a1a5a94 100644 --- a/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py +++ b/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py @@ -1,7 +1,4 @@ -from eth2spec.test.helpers.state import ( - next_epoch, - next_epoch_with_attestations, -) +from eth2spec.test.helpers.state import next_epoch from eth2spec.test.context import spec_state_test, with_all_phases from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with @@ -90,7 +87,10 @@ def test_activation_queue_no_activation_no_finality(spec, state): @with_all_phases @spec_state_test def test_activation_queue_sorting(spec, state): - mock_activations = 10 + churn_limit = spec.get_validator_churn_limit(state) + + # try to activate more than the per-epoch churn linmit + mock_activations = churn_limit * 2 epoch = spec.get_current_epoch(state) for i in range(mock_activations): @@ -104,10 +104,6 @@ def test_activation_queue_sorting(spec, state): state.slot += spec.SLOTS_PER_EPOCH * 3 state.finalized_checkpoint.epoch = epoch + 2 - # make sure we are hitting the churn - churn_limit = spec.get_validator_churn_limit(state) - assert mock_activations > churn_limit - yield from run_process_registry_updates(spec, state) # the first got in as second @@ -173,3 +169,30 @@ def test_ejection(spec, state): state.validators[index], spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) ) + + +@with_all_phases +@spec_state_test +def test_ejection_past_churn_limit(spec, state): + churn_limit = spec.get_validator_churn_limit(state) + + # try to eject more than per-epoch churn limit + mock_ejections = churn_limit * 3 + + for i in range(mock_ejections): + state.validators[i].effective_balance = spec.EJECTION_BALANCE + + expected_ejection_epoch = spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) + + yield from run_process_registry_updates(spec, state) + + for i in range(mock_ejections): + # first third ejected in normal speed + if i < mock_ejections // 3: + assert state.validators[i].exit_epoch == expected_ejection_epoch + # second thirdgets delayed by 1 epoch + elif mock_ejections // 3 <= i < mock_ejections * 2 // 3: + assert state.validators[i].exit_epoch == expected_ejection_epoch + 1 + # second thirdgets delayed by 2 epochs + else: + assert state.validators[i].exit_epoch == expected_ejection_epoch + 2 From 45620e345d1daf6146efa39d212869e9acb9db1e Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 11 Dec 2019 17:31:05 -0700 Subject: [PATCH 11/21] add test for activation_queue, activation, and ejection all in one --- .../test_process_registry_updates.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py b/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py index 26a1a5a94..a4655b33e 100644 --- a/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py +++ b/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py @@ -196,3 +196,52 @@ def test_ejection_past_churn_limit(spec, state): # second thirdgets delayed by 2 epochs else: assert state.validators[i].exit_epoch == expected_ejection_epoch + 2 + + +@with_all_phases +@spec_state_test +def test_activation_queue_activation_and_ejection(spec, state): + # move past first two irregular epochs wrt finality + next_epoch(spec, state) + next_epoch(spec, state) + + # ready for entrance into activation queue + activation_queue_index = 0 + mock_deposit(spec, state, activation_queue_index) + + # ready for activation + activation_index = 1 + mock_deposit(spec, state, activation_index) + state.finalized_checkpoint.epoch = spec.get_current_epoch(state) - 1 + state.validators[activation_index].activation_eligibility_epoch = state.finalized_checkpoint.epoch - 1 + + # ready for ejection + ejection_index = 2 + state.validators[ejection_index].effective_balance = spec.EJECTION_BALANCE + + yield from run_process_registry_updates(spec, state) + + # validator moved into activation queue + validator = state.validators[activation_queue_index] + assert validator.activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH + assert validator.activation_epoch == spec.FAR_FUTURE_EPOCH + assert not spec.is_active_validator(validator, spec.get_current_epoch(state)) + + # validator activated for future epoch + validator = state.validators[activation_index] + assert validator.activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH + assert validator.activation_epoch != spec.FAR_FUTURE_EPOCH + assert not spec.is_active_validator(validator, spec.get_current_epoch(state)) + assert spec.is_active_validator( + validator, + spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) + ) + + # validator ejected for future epoch + validator = state.validators[ejection_index] + assert validator.exit_epoch != spec.FAR_FUTURE_EPOCH + assert spec.is_active_validator(validator, spec.get_current_epoch(state)) + assert not spec.is_active_validator( + validator, + spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) + ) From 2405060a7e07c7a7141ba6f45da54eb9d9c21fa4 Mon Sep 17 00:00:00 2001 From: protolambda Date: Thu, 12 Dec 2019 01:45:59 +0100 Subject: [PATCH 12/21] Fixes #1486: disallow duplicate indices in indexed attestation --- specs/core/0_beacon-chain.md | 4 +- .../test_process_attester_slashing.py | 60 +++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 7ce93eb6f..8234c9324 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -628,8 +628,8 @@ def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: Indexe # Verify max number of indices if not len(indices) <= MAX_VALIDATORS_PER_COMMITTEE: return False - # Verify indices are sorted - if not indices == sorted(indices): + # Verify indices are sorted and unique + if not indices == sorted(set(indices)): return False # Verify aggregate signature if not bls_verify( diff --git a/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py b/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py index 85e807ec0..3fc2f8a38 100644 --- a/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py +++ b/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py @@ -252,6 +252,66 @@ def test_att2_bad_replaced_index(spec, state): yield from run_attester_slashing_processing(spec, state, attester_slashing, False) +@with_all_phases +@spec_state_test +@always_bls +def test_att1_duplicate_index_normal_signed(spec, state): + attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True) + + indices = attester_slashing.attestation_1.attesting_indices + indices.pop(1) # remove an index, make room for the additional duplicate index. + indices.append(indices[0]) # add one of the indices a second time + attester_slashing.attestation_1.attesting_indices = sorted(indices) + sign_indexed_attestation(spec, state, attester_slashing.attestation_1) + # it will just appear normal, unless the double index is spotted + yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + + +@with_all_phases +@spec_state_test +@always_bls +def test_att2_duplicate_index_normal_signed(spec, state): + attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=False) + + indices = attester_slashing.attestation_2.attesting_indices + indices.pop(2) # remove an index, make room for the additional duplicate index. + indices.append(indices[1]) # add one of the indices a second time + attester_slashing.attestation_2.attesting_indices = sorted(indices) + sign_indexed_attestation(spec, state, attester_slashing.attestation_2) + # it will just appear normal, unless the double index is spotted + yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + + +@with_all_phases +@spec_state_test +@always_bls +def test_att1_duplicate_index_double_signed(spec, state): + attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True) + + indices = attester_slashing.attestation_1.attesting_indices + indices.pop(1) # remove an index, make room for the additional duplicate index. + indices.append(indices[2]) # add one of the indices a second time + attester_slashing.attestation_1.attesting_indices = sorted(indices) + sign_indexed_attestation(spec, state, attester_slashing.attestation_1) # will have one attester signing it double + + yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + + +@with_all_phases +@spec_state_test +@always_bls +def test_att2_duplicate_index_double_signed(spec, state): + attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=False) + + indices = attester_slashing.attestation_2.attesting_indices + indices.pop(1) # remove an index, make room for the additional duplicate index. + indices.append(indices[2]) # add one of the indices a second time + attester_slashing.attestation_2.attesting_indices = sorted(indices) + sign_indexed_attestation(spec, state, attester_slashing.attestation_2) # will have one attester signing it double + + yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + + @with_all_phases @spec_state_test def test_unsorted_att_1(spec, state): From 7691e312c7d9a34dcf213faaf7ec2404b01c90b8 Mon Sep 17 00:00:00 2001 From: protolambda Date: Thu, 12 Dec 2019 01:48:03 +0100 Subject: [PATCH 13/21] update mypy to compile pyspec well with py 3.8.0, and minor mypy fix --- scripts/build_spec.py | 2 +- test_libs/pyspec/requirements-testing.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build_spec.py b/scripts/build_spec.py index c38d0bd65..cca5a1bf9 100644 --- a/scripts/build_spec.py +++ b/scripts/build_spec.py @@ -82,7 +82,7 @@ def get_eth1_data(distance: uint64) -> Bytes32: return hash(distance) -def hash(x: bytes) -> Bytes32: +def hash(x: bytes) -> Bytes32: # type: ignore if x not in hash_cache: hash_cache[x] = Bytes32(_hash(x)) return hash_cache[x] diff --git a/test_libs/pyspec/requirements-testing.txt b/test_libs/pyspec/requirements-testing.txt index b5229ae20..e8ecd12a6 100644 --- a/test_libs/pyspec/requirements-testing.txt +++ b/test_libs/pyspec/requirements-testing.txt @@ -2,6 +2,6 @@ pytest>=4.4 ../config_helpers flake8==3.7.7 -mypy==0.701 +mypy==0.750 pytest-cov pytest-xdist From 8d5d7387f959e6393da5b29684d52ae04e64722f Mon Sep 17 00:00:00 2001 From: protolambda Date: Thu, 12 Dec 2019 02:36:20 +0100 Subject: [PATCH 14/21] Two tests for proposer indices being off because of active validators status, fixes #1515 --- .../eth2spec/test/sanity/test_blocks.py | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/test_libs/pyspec/eth2spec/test/sanity/test_blocks.py b/test_libs/pyspec/eth2spec/test/sanity/test_blocks.py index 41316e92d..3c42446cb 100644 --- a/test_libs/pyspec/eth2spec/test/sanity/test_blocks.py +++ b/test_libs/pyspec/eth2spec/test/sanity/test_blocks.py @@ -3,7 +3,7 @@ from copy import deepcopy from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.utils.bls import bls_sign -from eth2spec.test.helpers.state import get_balance, state_transition_and_sign_block +from eth2spec.test.helpers.state import get_balance, state_transition_and_sign_block, next_slot from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_empty_block, sign_block, \ transition_unsigned_block from eth2spec.test.helpers.keys import privkeys, pubkeys @@ -253,6 +253,56 @@ def test_attester_slashing(spec, state): ) +@with_all_phases +@spec_state_test +def test_proposer_after_inactive_index(spec, state): + # disable some low validator index to check after for + inactive_index = 10 + state.validators[inactive_index].exit_epoch = spec.get_current_epoch(state) + + # skip forward, get brand new proposers + state.slot = spec.SLOTS_PER_EPOCH * 2 + + while True: + proposer_index = spec.get_beacon_proposer_index(state) + if proposer_index > inactive_index: + # found a proposer that has a higher index than a disabled validator + yield 'pre', state + # test if the proposer can be recognized correctly after the inactive validator + signed_block = sign_block(spec, state, build_empty_block(spec, state), proposer_index=proposer_index) + yield 'blocks', [signed_block] + yield 'post', state + break + else: + next_slot(spec, state) + + +@with_all_phases +@spec_state_test +def test_high_proposer_index(spec, state): + # disable a good amount of validators to make the active count lower, for a faster test + current_epoch = spec.get_current_epoch(state) + for i in range(len(state.validators) // 3): + state.validators[i].exit_epoch = current_epoch + + # skip forward, get brand new proposers + state.slot = spec.SLOTS_PER_EPOCH * 2 + + active_count = len(spec.get_active_validator_indices(state, current_epoch)) + while True: + proposer_index = spec.get_beacon_proposer_index(state) + if proposer_index >= active_count: + # found a proposer that has a higher index than the active validator count + yield 'pre', state + # test if the proposer can be recognized correctly, even while it has a high index. + signed_block = sign_block(spec, state, build_empty_block(spec, state), proposer_index=proposer_index) + yield 'blocks', [signed_block] + yield 'post', state + break + else: + next_slot(spec, state) + + @with_all_phases @spec_state_test def test_expected_deposit_in_block(spec, state): From 19ec01e4e919252108876168b116f3e511784854 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 12 Dec 2019 06:43:37 -0700 Subject: [PATCH 15/21] add comment about activation queue sort order Co-Authored-By: Hsiao-Wei Wang --- specs/core/0_beacon-chain.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 210e1797f..ff0d42634 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1341,7 +1341,8 @@ def process_registry_updates(state: BeaconState) -> None: activation_queue = sorted([ index for index, validator in enumerate(state.validators) if is_eligible_for_activation(state, validator) - ], key=lambda index: state.validators[index].activation_eligibility_epoch) + # Order by the sequence of activation_eligibility_epoch setting and then index. + ], key=lambda index: (state.validators[index].activation_eligibility_epoch, index)) # Dequeued validators for activation up to churn limit for index in activation_queue[:get_validator_churn_limit(state)]: validator = state.validators[index] From 86fb3acd59a51be40c04e7a56e50d3ea51c43d71 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 12 Dec 2019 06:53:56 -0700 Subject: [PATCH 16/21] minor changes to finality in activation queue --- specs/core/0_beacon-chain.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index ff0d42634..f9a726ff4 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -623,8 +623,8 @@ def is_eligible_for_activation(state: BeaconState, validator: Validator) -> bool Check if ``validator`` is eligible for activation. """ return ( - # Was placed in activation queue prior to most recent finalized epoch - validator.activation_eligibility_epoch < state.finalized_checkpoint.epoch + # Placement in queue is finalized + validator.activation_eligibility_epoch <= state.finalized_checkpoint.epoch # Has not yet been activated and validator.activation_epoch == FAR_FUTURE_EPOCH ) @@ -1332,7 +1332,7 @@ def process_registry_updates(state: BeaconState) -> None: # Process activation eligibility and ejections for index, validator in enumerate(state.validators): if is_eligible_for_activation_queue(validator): - validator.activation_eligibility_epoch = get_current_epoch(state) + validator.activation_eligibility_epoch = get_current_epoch(state) + 1 if is_active_validator(validator, get_current_epoch(state)) and validator.effective_balance <= EJECTION_BALANCE: initiate_validator_exit(state, ValidatorIndex(index)) @@ -1341,7 +1341,7 @@ def process_registry_updates(state: BeaconState) -> None: activation_queue = sorted([ index for index, validator in enumerate(state.validators) if is_eligible_for_activation(state, validator) - # Order by the sequence of activation_eligibility_epoch setting and then index. + # Order by the sequence of activation_eligibility_epoch setting and then index ], key=lambda index: (state.validators[index].activation_eligibility_epoch, index)) # Dequeued validators for activation up to churn limit for index in activation_queue[:get_validator_churn_limit(state)]: From 199933cb2664061e0ebff273ef9bf0c5f4b024a8 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 12 Dec 2019 06:57:11 -0700 Subject: [PATCH 17/21] fix tocs --- specs/core/0_beacon-chain.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index f9a726ff4..b74a29e9d 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -61,6 +61,8 @@ - [`bls_aggregate_pubkeys`](#bls_aggregate_pubkeys) - [Predicates](#predicates) - [`is_active_validator`](#is_active_validator) + - [`is_eligible_for_activation_queue`](#is_eligible_for_activation_queue) + - [`is_eligible_for_activation`](#is_eligible_for_activation) - [`is_slashable_validator`](#is_slashable_validator) - [`is_slashable_attestation_data`](#is_slashable_attestation_data) - [`is_valid_indexed_attestation`](#is_valid_indexed_attestation) @@ -591,16 +593,6 @@ def is_active_validator(validator: Validator, epoch: Epoch) -> bool: return validator.activation_epoch <= epoch < validator.exit_epoch ``` -#### `is_slashable_validator` - -```python -def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool: - """ - Check if ``validator`` is slashable. - """ - return (not validator.slashed) and (validator.activation_epoch <= epoch < validator.withdrawable_epoch) -``` - #### `is_eligible_for_activation_queue` ```python @@ -614,7 +606,6 @@ def is_eligible_for_activation_queue(validator: Validator) -> bool: ) ``` - #### `is_eligible_for_activation` ```python @@ -630,6 +621,16 @@ def is_eligible_for_activation(state: BeaconState, validator: Validator) -> bool ) ``` +#### `is_slashable_validator` + +```python +def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool: + """ + Check if ``validator`` is slashable. + """ + return (not validator.slashed) and (validator.activation_epoch <= epoch < validator.withdrawable_epoch) +``` + #### `is_slashable_attestation_data` ```python From ba8a67ccd89fa068dd4ba4d88ac788d095deba21 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 12 Dec 2019 07:31:51 -0700 Subject: [PATCH 18/21] update registry tests to modified finality condition --- .../test_process_registry_updates.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py b/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py index a4655b33e..526aba277 100644 --- a/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py +++ b/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py @@ -43,9 +43,9 @@ def test_activation_queue_to_activated_if_finalized(spec, state): index = 0 mock_deposit(spec, state, index) - # mock validator as having been in queue since before latest finalized + # mock validator as having been in queue since latest finalized state.finalized_checkpoint.epoch = spec.get_current_epoch(state) - 1 - state.validators[index].activation_eligibility_epoch = state.finalized_checkpoint.epoch - 1 + state.validators[index].activation_eligibility_epoch = state.finalized_checkpoint.epoch assert not spec.is_active_validator(state.validators[index], spec.get_current_epoch(state)) @@ -71,9 +71,9 @@ def test_activation_queue_no_activation_no_finality(spec, state): index = 0 mock_deposit(spec, state, index) - # mock validator as having been in queue only since latest finalized (not before) + # mock validator as having been in queue only after latest finalized state.finalized_checkpoint.epoch = spec.get_current_epoch(state) - 1 - state.validators[index].activation_eligibility_epoch = state.finalized_checkpoint.epoch + state.validators[index].activation_eligibility_epoch = state.finalized_checkpoint.epoch + 1 assert not spec.is_active_validator(state.validators[index], spec.get_current_epoch(state)) @@ -102,7 +102,7 @@ def test_activation_queue_sorting(spec, state): # move state forward and finalize to allow for activations state.slot += spec.SLOTS_PER_EPOCH * 3 - state.finalized_checkpoint.epoch = epoch + 2 + state.finalized_checkpoint.epoch = epoch + 1 yield from run_process_registry_updates(spec, state) @@ -132,7 +132,7 @@ def test_activation_queue_efficiency(spec, state): # move state forward and finalize to allow for activations state.slot += spec.SLOTS_PER_EPOCH * 3 - state.finalized_checkpoint.epoch = epoch + 2 + state.finalized_checkpoint.epoch = epoch + 1 # Run first registry update. Do not yield test vectors for _ in run_process_registry_updates(spec, state): @@ -213,7 +213,7 @@ def test_activation_queue_activation_and_ejection(spec, state): activation_index = 1 mock_deposit(spec, state, activation_index) state.finalized_checkpoint.epoch = spec.get_current_epoch(state) - 1 - state.validators[activation_index].activation_eligibility_epoch = state.finalized_checkpoint.epoch - 1 + state.validators[activation_index].activation_eligibility_epoch = state.finalized_checkpoint.epoch # ready for ejection ejection_index = 2 From 020dbb1ecd3ac29f7e8e0a0beae304f5769c4304 Mon Sep 17 00:00:00 2001 From: protolambda Date: Thu, 12 Dec 2019 16:16:52 +0100 Subject: [PATCH 19/21] fix missing transition --- test_libs/pyspec/eth2spec/test/sanity/test_blocks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_libs/pyspec/eth2spec/test/sanity/test_blocks.py b/test_libs/pyspec/eth2spec/test/sanity/test_blocks.py index 3c42446cb..2c08a88fe 100644 --- a/test_libs/pyspec/eth2spec/test/sanity/test_blocks.py +++ b/test_libs/pyspec/eth2spec/test/sanity/test_blocks.py @@ -269,7 +269,7 @@ def test_proposer_after_inactive_index(spec, state): # found a proposer that has a higher index than a disabled validator yield 'pre', state # test if the proposer can be recognized correctly after the inactive validator - signed_block = sign_block(spec, state, build_empty_block(spec, state), proposer_index=proposer_index) + signed_block = state_transition_and_sign_block(spec, state, build_empty_block(spec, state)) yield 'blocks', [signed_block] yield 'post', state break @@ -295,7 +295,7 @@ def test_high_proposer_index(spec, state): # found a proposer that has a higher index than the active validator count yield 'pre', state # test if the proposer can be recognized correctly, even while it has a high index. - signed_block = sign_block(spec, state, build_empty_block(spec, state), proposer_index=proposer_index) + signed_block = state_transition_and_sign_block(spec, state, build_empty_block(spec, state)) yield 'blocks', [signed_block] yield 'post', state break From 5c26d8e52fd48833ed22e4773915cc9b2a3b248b Mon Sep 17 00:00:00 2001 From: protolambda Date: Thu, 12 Dec 2019 16:29:30 +0100 Subject: [PATCH 20/21] fix normal signed case; only sign for 1 of the duplicate indices --- .../test_process_attester_slashing.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py b/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py index 3fc2f8a38..98a6e25e5 100644 --- a/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py +++ b/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py @@ -260,9 +260,14 @@ def test_att1_duplicate_index_normal_signed(spec, state): indices = attester_slashing.attestation_1.attesting_indices indices.pop(1) # remove an index, make room for the additional duplicate index. + attester_slashing.attestation_1.attesting_indices = sorted(indices) + + # sign it, the signature will be valid for a single occurence. If the transition accidentally ignores the duplicate. + sign_indexed_attestation(spec, state, attester_slashing.attestation_1) + indices.append(indices[0]) # add one of the indices a second time attester_slashing.attestation_1.attesting_indices = sorted(indices) - sign_indexed_attestation(spec, state, attester_slashing.attestation_1) + # it will just appear normal, unless the double index is spotted yield from run_attester_slashing_processing(spec, state, attester_slashing, False) @@ -275,9 +280,14 @@ def test_att2_duplicate_index_normal_signed(spec, state): indices = attester_slashing.attestation_2.attesting_indices indices.pop(2) # remove an index, make room for the additional duplicate index. + attester_slashing.attestation_2.attesting_indices = sorted(indices) + + # sign it, the signature will be valid for a single occurence. If the transition accidentally ignores the duplicate. + sign_indexed_attestation(spec, state, attester_slashing.attestation_2) + indices.append(indices[1]) # add one of the indices a second time attester_slashing.attestation_2.attesting_indices = sorted(indices) - sign_indexed_attestation(spec, state, attester_slashing.attestation_2) + # it will just appear normal, unless the double index is spotted yield from run_attester_slashing_processing(spec, state, attester_slashing, False) From 020af2707ac5c26e2753cc69f8a02af5086543eb Mon Sep 17 00:00:00 2001 From: protolambda Date: Thu, 12 Dec 2019 16:40:57 +0100 Subject: [PATCH 21/21] fix: don't get stuck in same slot doing a transition, and clean up latest-block-header with starting block for pre-state --- test_libs/pyspec/eth2spec/test/sanity/test_blocks.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test_libs/pyspec/eth2spec/test/sanity/test_blocks.py b/test_libs/pyspec/eth2spec/test/sanity/test_blocks.py index 2c08a88fe..c2f980ba0 100644 --- a/test_libs/pyspec/eth2spec/test/sanity/test_blocks.py +++ b/test_libs/pyspec/eth2spec/test/sanity/test_blocks.py @@ -262,8 +262,11 @@ def test_proposer_after_inactive_index(spec, state): # skip forward, get brand new proposers state.slot = spec.SLOTS_PER_EPOCH * 2 + block = build_empty_block_for_next_slot(spec, state) + state_transition_and_sign_block(spec, state, block) while True: + next_slot(spec, state) proposer_index = spec.get_beacon_proposer_index(state) if proposer_index > inactive_index: # found a proposer that has a higher index than a disabled validator @@ -273,8 +276,6 @@ def test_proposer_after_inactive_index(spec, state): yield 'blocks', [signed_block] yield 'post', state break - else: - next_slot(spec, state) @with_all_phases @@ -287,9 +288,12 @@ def test_high_proposer_index(spec, state): # skip forward, get brand new proposers state.slot = spec.SLOTS_PER_EPOCH * 2 + block = build_empty_block_for_next_slot(spec, state) + state_transition_and_sign_block(spec, state, block) active_count = len(spec.get_active_validator_indices(state, current_epoch)) while True: + next_slot(spec, state) proposer_index = spec.get_beacon_proposer_index(state) if proposer_index >= active_count: # found a proposer that has a higher index than the active validator count @@ -299,8 +303,6 @@ def test_high_proposer_index(spec, state): yield 'blocks', [signed_block] yield 'post', state break - else: - next_slot(spec, state) @with_all_phases