From 387113b2f4011c27d0cfc2beb57afb7666ce0c69 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 30 Aug 2021 13:36:01 -0700 Subject: [PATCH 01/53] add "collect only" mode to spec test generator --- .../gen_helpers/gen_base/gen_runner.py | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_runner.py b/tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_runner.py index 765fc501b..6850af188 100644 --- a/tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_runner.py +++ b/tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_runner.py @@ -77,6 +77,13 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]): required=False, help="specify presets to run with. Allows all if no preset names are specified.", ) + parser.add_argument( + "-c", + "--collect-only", + action="store_true", + default=False, + help="if set only print tests to generate, do not actually run the test and dump the target data", + ) args = parser.parse_args() output_dir = args.output_dir @@ -100,12 +107,15 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]): if len(presets) != 0: print(f"Filtering test-generator runs to only include presets: {', '.join(presets)}") + collect_only = args.collect_only + collected_test_count = 0 generated_test_count = 0 skipped_test_count = 0 provider_start = time.time() for tprov in test_providers: - # runs anything that we don't want to repeat for every test case. - tprov.prepare() + if not collect_only: + # runs anything that we don't want to repeat for every test case. + tprov.prepare() for test_case in tprov.make_cases(): case_dir = ( @@ -115,6 +125,11 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]): ) incomplete_tag_file = case_dir / "INCOMPLETE" + collected_test_count += 1 + if collect_only: + print(f"Collected test at: {case_dir}") + continue + if case_dir.exists(): if not args.force and not incomplete_tag_file.exists(): skipped_test_count += 1 @@ -193,11 +208,14 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]): provider_end = time.time() span = round(provider_end - provider_start, 2) - summary_message = f"completed generation of {generator_name} with {generated_test_count} tests" - summary_message += f" ({skipped_test_count} skipped tests)" - if span > TIME_THRESHOLD_TO_PRINT: - summary_message += f" in {span} seconds" - print(summary_message) + if collect_only: + print(f"Collected {collected_test_count} tests in total") + else: + summary_message = f"completed generation of {generator_name} with {generated_test_count} tests" + summary_message += f" ({skipped_test_count} skipped tests)" + if span > TIME_THRESHOLD_TO_PRINT: + summary_message += f" in {span} seconds" + print(summary_message) def dump_yaml_fn(data: Any, name: str, file_mode: str, yaml_encoder: YAML): From 2477deaf1312539cc0e429452ffe3e2d608e6b28 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 30 Aug 2021 13:53:05 -0700 Subject: [PATCH 02/53] Allow test driver to batch test cases under one handler name with a list --- .../gen_helpers/gen_from_tests/gen.py | 25 +++++++++++-------- tests/generators/operations/main.py | 6 ++--- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/tests/core/pyspec/eth2spec/gen_helpers/gen_from_tests/gen.py b/tests/core/pyspec/eth2spec/gen_helpers/gen_from_tests/gen.py index db3f9e949..88bc6d601 100644 --- a/tests/core/pyspec/eth2spec/gen_helpers/gen_from_tests/gen.py +++ b/tests/core/pyspec/eth2spec/gen_helpers/gen_from_tests/gen.py @@ -1,6 +1,6 @@ from importlib import import_module from inspect import getmembers, isfunction -from typing import Any, Callable, Dict, Iterable, Optional +from typing import Any, Callable, Dict, Iterable, Optional, List, Union from eth2spec.utils import bls from eth2spec.test.helpers.constants import ALL_PRESETS, TESTGEN_FORKS @@ -59,8 +59,10 @@ def generate_from_tests(runner_name: str, handler_name: str, src: Any, def get_provider(create_provider_fn: Callable[[SpecForkName, PresetBaseName, str, str], TestProvider], fork_name: SpecForkName, preset_name: PresetBaseName, - all_mods: Dict[str, Dict[str, str]]) -> Iterable[TestProvider]: + all_mods: Dict[str, Dict[str, Union[List[str], str]]]) -> Iterable[TestProvider]: for key, mod_name in all_mods[fork_name].items(): + if not isinstance(mod_name, List): + mod_name = [mod_name] yield create_provider_fn( fork_name=fork_name, preset_name=preset_name, @@ -75,16 +77,17 @@ def get_create_provider_fn(runner_name: str) -> Callable[[SpecForkName, str, str return def create_provider(fork_name: SpecForkName, preset_name: PresetBaseName, - handler_name: str, tests_src_mod_name: str) -> TestProvider: + handler_name: str, tests_src_mod_name: List[str]) -> TestProvider: def cases_fn() -> Iterable[TestCase]: - tests_src = import_module(tests_src_mod_name) - return generate_from_tests( - runner_name=runner_name, - handler_name=handler_name, - src=tests_src, - fork_name=fork_name, - preset_name=preset_name, - ) + for mod_name in tests_src_mod_name: + tests_src = import_module(mod_name) + yield from generate_from_tests( + runner_name=runner_name, + handler_name=handler_name, + src=tests_src, + fork_name=fork_name, + preset_name=preset_name, + ) return TestProvider(prepare=prepare_fn, make_cases=cases_fn) return create_provider diff --git a/tests/generators/operations/main.py b/tests/generators/operations/main.py index d2653d87d..0f4da69a5 100644 --- a/tests/generators/operations/main.py +++ b/tests/generators/operations/main.py @@ -12,9 +12,9 @@ if __name__ == "__main__": 'voluntary_exit', ]} altair_mods = { - **{key: 'eth2spec.test.altair.block_processing.sync_aggregate.test_process_' + key for key in [ - 'sync_aggregate', - 'sync_aggregate_random', + **{'sync_aggregate': [ + 'eth2spec.test.altair.block_processing.sync_aggregate.test_process_' + key + for key in ['sync_aggregate', 'sync_aggregate_random'] ]}, **phase_0_mods, } # also run the previous phase 0 tests From 189a9d4ae993ce29ec1088ba2f50610819e14ca8 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 31 Aug 2021 12:10:37 +0800 Subject: [PATCH 03/53] Add the missed on_tick output and remove the useless on_tick call --- .../eth2spec/test/phase0/fork_choice/test_on_block.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py index 634610fe7..457eef7ff 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py @@ -483,7 +483,8 @@ def test_new_justified_is_later_than_store_justified(spec, state): assert fork_2_state.finalized_checkpoint.epoch == 0 assert fork_2_state.current_justified_checkpoint.epoch == 5 # Check SAFE_SLOTS_TO_UPDATE_JUSTIFIED - spec.on_tick(store, store.genesis_time + fork_2_state.slot * spec.config.SECONDS_PER_SLOT) + 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) @@ -526,7 +527,8 @@ def test_new_justified_is_later_than_store_justified(spec, state): # # 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): - # spec.on_tick(store, store.genesis_time + block.message.slot * spec.config.SECONDS_PER_SLOT) + # time = store.genesis_time + block.message.slot * spec.config.SECONDS_PER_SLOT + # on_tick_and_append_step(spec, store, time, test_steps) # # valid_attestations=False because the attestations are outdated (older than previous epoch) # yield from add_block(spec, store, block, test_steps, allow_invalid_attestations=False) @@ -643,7 +645,6 @@ def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): # Process state next_epoch(spec, state) - spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) state, store, _ = yield from apply_next_epoch_with_attestations( spec, state, store, False, True, test_steps=test_steps) From da8d22c7541d78774b2a1179dca225d63b8e24e2 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 31 Aug 2021 13:16:19 +0800 Subject: [PATCH 04/53] Update `checks` Checkpoint fields --- .../eth2spec/test/helpers/fork_choice.py | 15 ++++++-- tests/formats/fork_choice/README.md | 36 +++++++++++-------- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index ec5793af5..65d6975f2 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -156,9 +156,18 @@ def add_block(spec, store, signed_block, test_steps, valid=True, allow_invalid_a 'checks': { 'time': int(store.time), 'head': get_formatted_head_output(spec, store), - 'justified_checkpoint_root': encode_hex(store.justified_checkpoint.root), - 'finalized_checkpoint_root': encode_hex(store.finalized_checkpoint.root), - 'best_justified_checkpoint': encode_hex(store.best_justified_checkpoint.root), + 'justified_checkpoint': { + 'epoch': int(store.justified_checkpoint.epoch), + 'root': encode_hex(store.justified_checkpoint.root), + }, + 'finalized_checkpoint': { + 'epoch': int(store.finalized_checkpoint.epoch), + 'root': encode_hex(store.finalized_checkpoint.root), + }, + 'best_justified_checkpoint': { + 'epoch': int(store.best_justified_checkpoint.epoch), + 'root': encode_hex(store.best_justified_checkpoint.root), + }, } }) diff --git a/tests/formats/fork_choice/README.md b/tests/formats/fork_choice/README.md index 90c0aafc7..cfc86776d 100644 --- a/tests/formats/fork_choice/README.md +++ b/tests/formats/fork_choice/README.md @@ -80,26 +80,34 @@ checks: {: value} -- the assertions. `` is the field member or property of [`Store`](../../../specs/phase0/fork-choice.md#store) object that maintained by client implementation. Currently, the possible fields included: ```yaml -head: { -- Encoded 32-byte value from get_head(store) - slot: slot, - root: string, +head: { + slot: int, + root: string, -- Encoded 32-byte value from get_head(store) +} +time: int -- store.time +genesis_time: int -- store.genesis_time +justified_checkpoint: { + epoch: int, -- Integer value from store.justified_checkpoint.epoch + root: string, -- Encoded 32-byte value from store.justified_checkpoint.root +} +finalized_checkpoint: { + epoch: int, -- Integer value from store.finalized_checkpoint.epoch + root: string, -- Encoded 32-byte value from store.finalized_checkpoint.root +} +best_justified_checkpoint: { + epoch: int, -- Integer value from store.best_justified_checkpoint.epoch + root: string, -- Encoded 32-byte value from store.best_justified_checkpoint.root } -time: int -- store.time -genesis_time: int -- store.genesis_time -justified_checkpoint_root: string -- Encoded 32-byte value from store.justified_checkpoint.root -finalized_checkpoint_root: string -- Encoded 32-byte value from store.finalized_checkpoint.root -best_justified_checkpoint_root: string -- Encoded 32-byte value from store.best_justified_checkpoint.root ``` For example: ```yaml - checks: - time: 144 - genesis_time: 0 - head: {slot: 17, root: '0xd2724c86002f7e1f8656ab44a341a409ad80e6e70a5225fd94835566deebb66f'} - justified_checkpoint_root: '0xcea6ecd3d3188e32ebf611f960eebd45b6c6f477a7cff242fa567a42653bfc7c' - finalized_checkpoint_root: '0xcea6ecd3d3188e32ebf611f960eebd45b6c6f477a7cff242fa567a42653bfc7c' - best_justified_checkpoint: '0xcea6ecd3d3188e32ebf611f960eebd45b6c6f477a7cff242fa567a42653bfc7c' + time: 192 + head: {slot: 32, root: '0xdaa1d49d57594ced0c35688a6da133abb086d191a2ebdfd736fad95299325aeb'} + justified_checkpoint: {epoch: 3, root: '0xc25faab4acab38d3560864ca01e4d5cc4dc2cd473da053fbc03c2669143a2de4'} + finalized_checkpoint: {epoch: 2, root: '0x40d32d6283ec11c53317a46808bc88f55657d93b95a1af920403187accf48f4f'} + best_justified_checkpoint: {epoch: 3, root: '0xc25faab4acab38d3560864ca01e4d5cc4dc2cd473da053fbc03c2669143a2de4'} ``` *Note*: Each `checks` step may include one or multiple items. Each item has to be checked against the current store. From 9b065c78167d1288e58913b56868e742207cd85e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 31 Aug 2021 17:42:10 +0800 Subject: [PATCH 05/53] To avoid using non-genesis anchor state, rewrite `test_on_block_finalized_skip_slots_not_in_skip_chain` --- .../test/phase0/fork_choice/test_on_block.py | 83 +++++++++++-------- 1 file changed, 50 insertions(+), 33 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py index 457eef7ff..0a3802338 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py @@ -26,7 +26,6 @@ from eth2spec.test.helpers.state import ( next_epoch, next_slots, state_transition_and_sign_block, - transition_to, ) @@ -191,6 +190,10 @@ def test_on_block_before_finalized(spec, state): @spec_state_test @with_presets([MINIMAL], reason="too slow") def test_on_block_finalized_skip_slots(spec, state): + """ + Test case was originally from https://github.com/ethereum/consensus-specs/pull/1579 + And then rewrote largely. + """ test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) @@ -200,21 +203,28 @@ def test_on_block_finalized_skip_slots(spec, state): on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time - # Create a finalized chain - for _ in range(4): + # Fill epoch 0 and the first slot of epoch 1 + state, store, _ = yield from apply_next_slots_with_attestations( + spec, state, store, spec.SLOTS_PER_EPOCH, True, False, test_steps) + + # Skip the rest slots of epoch 1 and the first slot of epoch 2 + next_slots(spec, state, spec.SLOTS_PER_EPOCH) + + # Fill epoch 3 and 4 + for _ in range(2): state, store, _ = yield from apply_next_epoch_with_attestations( - spec, state, store, True, False, test_steps=test_steps) - assert store.finalized_checkpoint.epoch == 2 + spec, state, store, True, True, test_steps=test_steps) - # Another chain - another_state = store.block_states[store.finalized_checkpoint.root].copy() - # Build block that includes the skipped slots up to finality in chain - block = build_empty_block(spec, - another_state, - spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + 2) - block.body.graffiti = b'\x12' * 32 - signed_block = state_transition_and_sign_block(spec, another_state, block) + # Now we get finalized epoch 1, where compute_start_slot_at_epoch(1) is a skipped slot + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + assert store.finalized_checkpoint.root == spec.get_block_root(state, 1) == spec.get_block_root(state, 2) + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert store.justified_checkpoint == state.current_justified_checkpoint + # Now build a block at later slot than finalized *epoch* + # Includes finalized block in chain and the skipped slots + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) yield from tick_and_add_block(spec, store, signed_block, test_steps) yield 'steps', test_steps @@ -224,36 +234,43 @@ def test_on_block_finalized_skip_slots(spec, state): @spec_state_test @with_presets([MINIMAL], reason="too slow") def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): + """ + Test case was originally from https://github.com/ethereum/consensus-specs/pull/1579 + And then rewrote largely. + """ test_steps = [] # Initialization - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH - 1) - block = build_empty_block_for_next_slot(spec, state) - transition_unsigned_block(spec, state, block) - block.state_root = state.hash_tree_root() - store = spec.get_forkchoice_store(state, block) + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) yield 'anchor_state', state - yield 'anchor_block', block - + 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 - pre_finalized_checkpoint_epoch = store.finalized_checkpoint.epoch + # Fill epoch 0 and the first slot of epoch 1 + state, store, _ = yield from apply_next_slots_with_attestations( + spec, state, store, spec.SLOTS_PER_EPOCH, True, False, test_steps) - # Finalized - for _ in range(3): + # Skip the rest slots of epoch 1 and the first slot of epoch 2 + next_slots(spec, state, spec.SLOTS_PER_EPOCH) + + # Fill epoch 3 and 4 + for _ in range(2): state, store, _ = yield from apply_next_epoch_with_attestations( - spec, state, store, True, False, test_steps=test_steps) - assert store.finalized_checkpoint.epoch == pre_finalized_checkpoint_epoch + 1 + spec, state, store, True, True, test_steps=test_steps) - # Now build a block at later slot than finalized epoch - # Includes finalized block in chain, but not at appropriate skip slot - pre_state = store.block_states[block.hash_tree_root()].copy() - block = build_empty_block(spec, - state=pre_state, - slot=spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + 2) - block.body.graffiti = b'\x12' * 32 - signed_block = sign_block(spec, pre_state, block) + # Now we get finalized epoch 1, where compute_start_slot_at_epoch(1) is a skipped slot + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + assert store.finalized_checkpoint.root == spec.get_block_root(state, 1) == spec.get_block_root(state, 2) + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert store.justified_checkpoint == state.current_justified_checkpoint + + # Now build a block after the block of the finalized **root** + # Includes finalized block in chain, but does not include finalized skipped slots + another_state = store.block_states[store.finalized_checkpoint.root].copy() + assert another_state.slot == spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch - 1) + block = build_empty_block_for_next_slot(spec, another_state) + signed_block = state_transition_and_sign_block(spec, another_state, block) yield from tick_and_add_block(spec, store, signed_block, test_steps, valid=False) yield 'steps', test_steps From b23ed05eee5f499d3ff41f8356c19bd07b01e0ca Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 31 Aug 2021 18:40:26 +0800 Subject: [PATCH 06/53] [`test_on_block_finalized_skip_slots`] Make target state right after skipped slots --- .../eth2spec/test/phase0/fork_choice/test_on_block.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py index 0a3802338..8227c4cfb 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py @@ -210,6 +210,9 @@ def test_on_block_finalized_skip_slots(spec, state): # Skip the rest slots of epoch 1 and the first slot of epoch 2 next_slots(spec, state, spec.SLOTS_PER_EPOCH) + # The state after the skipped slots + target_state = state.copy() + # Fill epoch 3 and 4 for _ in range(2): state, store, _ = yield from apply_next_epoch_with_attestations( @@ -223,8 +226,8 @@ def test_on_block_finalized_skip_slots(spec, state): # Now build a block at later slot than finalized *epoch* # Includes finalized block in chain and the skipped slots - block = build_empty_block_for_next_slot(spec, state) - signed_block = state_transition_and_sign_block(spec, state, block) + block = build_empty_block_for_next_slot(spec, target_state) + signed_block = state_transition_and_sign_block(spec, target_state, block) yield from tick_and_add_block(spec, store, signed_block, test_steps) yield 'steps', test_steps From 4c34518edf54f041824f114ac2ddba4383cf92b7 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 1 Sep 2021 00:23:42 +0800 Subject: [PATCH 07/53] Call Altair and Merge operations in `process_and_sign_block_without_header_validations` --- .../pyspec/eth2spec/test/phase0/sanity/test_blocks.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py index a79ed94d2..78b91ea91 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py @@ -19,6 +19,7 @@ from eth2spec.test.helpers.attester_slashings import ( from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing, check_proposer_slashing_effect from eth2spec.test.helpers.attestations import get_valid_attestation from eth2spec.test.helpers.deposits import prepare_state_and_deposit +from eth2spec.test.helpers.execution_payload import build_empty_execution_payload from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits from eth2spec.test.helpers.multi_operations import ( run_slash_and_exit, @@ -38,6 +39,7 @@ from eth2spec.test.context import ( with_custom_state, large_validator_set, is_post_altair, + is_post_merge, ) @@ -146,6 +148,11 @@ def process_and_sign_block_without_header_validations(spec, state, block): spec.process_randao(state, block.body) spec.process_eth1_data(state, block.body) spec.process_operations(state, block.body) + if is_post_altair(spec): + spec.process_sync_aggregate(state, block.body.sync_aggregate) + if is_post_merge(spec): + if spec.is_execution_enabled(state, block.body): + spec.process_execution_payload(state, block.body.execution_payload, spec.EXECUTION_ENGINE) # Insert post-state rot block.state_root = state.hash_tree_root() @@ -188,6 +195,10 @@ def test_parent_from_same_slot(spec, state): child_block = parent_block.copy() child_block.parent_root = state.latest_block_header.hash_tree_root() + if is_post_merge(spec): + randao_mix = spec.compute_randao_mix(state, child_block.body.randao_reveal) + child_block.body.execution_payload = build_empty_execution_payload(spec, state, randao_mix) + # Show that normal path through transition fails failed_state = state.copy() expect_assertion_error( From 9bf8ad91306a055e251d83017252f9d751a0e2f3 Mon Sep 17 00:00:00 2001 From: Antonio Sanso Date: Thu, 2 Sep 2021 11:00:51 +0200 Subject: [PATCH 08/53] Update test_process_sync_aggregate.py --- .../sync_aggregate/test_process_sync_aggregate.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py index 4be6737e2..58acdbdde 100644 --- a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py +++ b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py @@ -23,7 +23,6 @@ from eth2spec.test.context import ( always_bls, ) - @with_altair_and_later @spec_state_test @always_bls @@ -33,9 +32,8 @@ def test_invalid_signature_bad_domain(spec, state): random_participant = rng.choice(committee_indices) block = build_empty_block_for_next_slot(spec, state) - # Exclude one participant whose signature was included. block.body.sync_aggregate = spec.SyncAggregate( - sync_committee_bits=[index != random_participant for index in committee_indices], + sync_committee_bits= committee_indices, sync_committee_signature=compute_aggregate_sync_committee_signature( spec, state, From 2d736139d51cb8204cc10cb6522784f9b8177346 Mon Sep 17 00:00:00 2001 From: Antonio Sanso Date: Thu, 2 Sep 2021 11:19:58 +0200 Subject: [PATCH 09/53] Update test_process_sync_aggregate.py --- .../sync_aggregate/test_process_sync_aggregate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py index 58acdbdde..fdb872d6e 100644 --- a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py +++ b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py @@ -33,7 +33,7 @@ def test_invalid_signature_bad_domain(spec, state): block = build_empty_block_for_next_slot(spec, state) block.body.sync_aggregate = spec.SyncAggregate( - sync_committee_bits= committee_indices, + sync_committee_bits=committee_indices, sync_committee_signature=compute_aggregate_sync_committee_signature( spec, state, From 2206a583351a4e1bd8b27e230241b912e91f321f Mon Sep 17 00:00:00 2001 From: Antonio Sanso Date: Thu, 2 Sep 2021 11:33:12 +0200 Subject: [PATCH 10/53] Update test_process_sync_aggregate.py --- .../sync_aggregate/test_process_sync_aggregate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py index fdb872d6e..840d1d63d 100644 --- a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py +++ b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py @@ -23,6 +23,7 @@ from eth2spec.test.context import ( always_bls, ) + @with_altair_and_later @spec_state_test @always_bls From 5f1a444e70ef6c597bff1781ec1ab39d319fef36 Mon Sep 17 00:00:00 2001 From: Antonio Sanso Date: Thu, 2 Sep 2021 11:34:42 +0200 Subject: [PATCH 11/53] Update test_process_sync_aggregate.py --- .../sync_aggregate/test_process_sync_aggregate.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py index 840d1d63d..95e0284d2 100644 --- a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py +++ b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py @@ -29,8 +29,6 @@ from eth2spec.test.context import ( @always_bls def test_invalid_signature_bad_domain(spec, state): committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) - rng = random.Random(2020) - random_participant = rng.choice(committee_indices) block = build_empty_block_for_next_slot(spec, state) block.body.sync_aggregate = spec.SyncAggregate( From ae8c0447efbd89004107d4e15ca07b103f648737 Mon Sep 17 00:00:00 2001 From: Antonio Sanso Date: Thu, 2 Sep 2021 14:57:47 +0200 Subject: [PATCH 12/53] Update tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py Co-authored-by: Alex Stokes --- .../sync_aggregate/test_process_sync_aggregate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py index 95e0284d2..bd52ce727 100644 --- a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py +++ b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py @@ -32,7 +32,7 @@ def test_invalid_signature_bad_domain(spec, state): block = build_empty_block_for_next_slot(spec, state) block.body.sync_aggregate = spec.SyncAggregate( - sync_committee_bits=committee_indices, + sync_committee_bits=[True] * len(committee_indices), sync_committee_signature=compute_aggregate_sync_committee_signature( spec, state, From e341f4e1f858ac678ab9f9c052b01588d67612cc Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 31 Aug 2021 12:45:47 -0700 Subject: [PATCH 13/53] refactor "leaking patch" helper --- .../pyspec/eth2spec/test/helpers/random.py | 30 +++++++++++++++++++ .../test/utils/randomized_block_tests.py | 19 ++---------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index 8f095aebb..0bb2a6672 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -128,3 +128,33 @@ def randomize_state(spec, state, rng=Random(8020), exit_fraction=None, slash_fra exit_random_validators(spec, state, rng, fraction=exit_fraction) slash_random_validators(spec, state, rng, fraction=slash_fraction) randomize_attestation_participation(spec, state, rng) + + +def patch_state_to_non_leaking(spec, state): + """ + This function performs an irregular state transition so that: + 1. the current justified checkpoint references the previous epoch + 2. the previous justified checkpoint references the epoch before previous + 3. the finalized checkpoint matches the previous justified checkpoint + + The effects of this function are intended to offset randomization side effects + performed by other functionality in this module so that if the ``state`` was leaking, + then the ``state`` is not leaking after. + """ + state.justification_bits = (True, True, True, True) + previous_epoch = spec.get_previous_epoch(state) + previous_root = spec.get_block_root(state, previous_epoch) + previous_previous_epoch = max(spec.GENESIS_EPOCH, spec.Epoch(previous_epoch - 1)) + previous_previous_root = spec.get_block_root(state, previous_previous_epoch) + state.previous_justified_checkpoint = spec.Checkpoint( + epoch=previous_previous_epoch, + root=previous_previous_root, + ) + state.current_justified_checkpoint = spec.Checkpoint( + epoch=previous_epoch, + root=previous_root, + ) + state.finalized_checkpoint = spec.Checkpoint( + epoch=previous_previous_epoch, + root=previous_previous_root, + ) diff --git a/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py b/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py index ce99c1053..02a5464f7 100644 --- a/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py +++ b/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py @@ -17,6 +17,7 @@ from eth2spec.test.helpers.inactivity_scores import ( ) from eth2spec.test.helpers.random import ( randomize_state as randomize_state_helper, + patch_state_to_non_leaking, ) from eth2spec.test.helpers.state import ( next_slot, @@ -274,23 +275,7 @@ def _randomized_scenario_setup(state_randomizer): may not reflect this condition with prior (arbitrary) mutations, so this mutator addresses that fact. """ - state.justification_bits = (True, True, True, True) - previous_epoch = spec.get_previous_epoch(state) - previous_root = spec.get_block_root(state, previous_epoch) - previous_previous_epoch = max(spec.GENESIS_EPOCH, spec.Epoch(previous_epoch - 1)) - previous_previous_root = spec.get_block_root(state, previous_previous_epoch) - state.previous_justified_checkpoint = spec.Checkpoint( - epoch=previous_previous_epoch, - root=previous_previous_root, - ) - state.current_justified_checkpoint = spec.Checkpoint( - epoch=previous_epoch, - root=previous_root, - ) - state.finalized_checkpoint = spec.Checkpoint( - epoch=previous_previous_epoch, - root=previous_previous_root, - ) + patch_state_to_non_leaking(spec, state) return ( # NOTE: the block randomization function assumes at least 1 shard committee period From 7cb5901ee62d2ec99ff05537c079db2d3f055402 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 31 Aug 2021 12:46:00 -0700 Subject: [PATCH 14/53] add spec test case for rewards with exited validators and _no_ leak --- tests/core/pyspec/eth2spec/test/helpers/rewards.py | 14 +++++++++++++- .../eth2spec/test/phase0/rewards/test_random.py | 12 ++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index 1867db08f..ec617bda9 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -255,7 +255,19 @@ def run_get_inactivity_penalty_deltas(spec, state): else: assert penalties[index] > base_penalty else: - assert penalties[index] == 0 + if not is_post_altair(spec): + assert penalties[index] == 0 + continue + else: + # post altair, this penalty is derived from the inactivity score + # regardless if the state is leaking or not... + if index in matching_attesting_indices: + assert penalties[index] == 0 + else: + # copied from spec: + penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index] + penalty_denominator = spec.config.INACTIVITY_SCORE_BIAS * spec.INACTIVITY_PENALTY_QUOTIENT_ALTAIR + assert penalties[index] == penalty_numerator // penalty_denominator def transition_state_to_leak(spec, state, epochs=None): diff --git a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py index 1184a6617..78c6846ae 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py @@ -9,6 +9,7 @@ from eth2spec.test.context import ( low_balances, misc_balances, ) import eth2spec.test.helpers.rewards as rewards_helpers +from eth2spec.test.helpers.random import randomize_state, patch_state_to_non_leaking @with_all_phases @@ -57,3 +58,14 @@ def test_full_random_low_balances_1(spec, state): @single_phase def test_full_random_misc_balances(spec, state): yield from rewards_helpers.run_test_full_random(spec, state, rng=Random(7070)) + + +@with_all_phases +@spec_state_test +def test_full_random_without_leak_0(spec, state): + rng = Random(1010) + randomize_state(spec, state, rng) + assert spec.is_in_inactivity_leak(state) + patch_state_to_non_leaking(spec, state) + assert not spec.is_in_inactivity_leak(state) + yield from rewards_helpers.run_deltas(spec, state) From cf23cd00ab2c5bf4fe5addfed5df1be06402bf2a Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 1 Sep 2021 14:38:02 -0700 Subject: [PATCH 15/53] ensure the test covers exited, unslashed validators --- tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py | 7 +++++++ .../pyspec/eth2spec/test/phase0/rewards/test_random.py | 3 +++ 2 files changed, 10 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py b/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py index 73d4598b3..55ea0b5b0 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py +++ b/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py @@ -34,6 +34,13 @@ def get_exited_validators(spec, state): return [index for (index, validator) in enumerate(state.validators) if validator.exit_epoch <= current_epoch] +def get_unslashed_exited_validators(spec, state): + return [ + index for index in get_exited_validators(spec, state) + if not state.validators[index].slashed + ] + + def exit_validators(spec, state, validator_count, rng=None): if rng is None: rng = Random(1337) diff --git a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py index 78c6846ae..f158f3cf8 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py @@ -10,6 +10,7 @@ from eth2spec.test.context import ( ) import eth2spec.test.helpers.rewards as rewards_helpers from eth2spec.test.helpers.random import randomize_state, patch_state_to_non_leaking +from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators @with_all_phases @@ -68,4 +69,6 @@ def test_full_random_without_leak_0(spec, state): assert spec.is_in_inactivity_leak(state) patch_state_to_non_leaking(spec, state) assert not spec.is_in_inactivity_leak(state) + target_validators = get_unslashed_exited_validators(spec, state) + assert len(target_validators) != 0 yield from rewards_helpers.run_deltas(spec, state) From 0cc5f9cd59369ead4088b07580817fb66a5fc6e0 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 1 Sep 2021 14:43:11 -0700 Subject: [PATCH 16/53] modify helper for more precision on exited validators --- tests/core/pyspec/eth2spec/test/helpers/state.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/state.py b/tests/core/pyspec/eth2spec/test/helpers/state.py index b4c9e1d67..d8fda3754 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/state.py +++ b/tests/core/pyspec/eth2spec/test/helpers/state.py @@ -1,6 +1,6 @@ from eth2spec.test.context import expect_assertion_error, is_post_altair from eth2spec.test.helpers.block import apply_empty_block, sign_block, transition_unsigned_block -from eth2spec.test.helpers.voluntary_exits import get_exited_validators +from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators def get_balance(state, index): @@ -142,7 +142,7 @@ def ensure_state_has_validators_across_lifecycle(spec, state): for each of the following lifecycle states: 1. Pending / deposited 2. Active - 3. Exited + 3. Exited (but not slashed) 4. Slashed """ has_pending = any(filter(spec.is_eligible_for_activation_queue, state.validators)) @@ -150,7 +150,7 @@ def ensure_state_has_validators_across_lifecycle(spec, state): current_epoch = spec.get_current_epoch(state) has_active = any(filter(lambda v: spec.is_active_validator(v, current_epoch), state.validators)) - has_exited = any(get_exited_validators(spec, state)) + has_exited = any(get_unslashed_exited_validators(spec, state)) has_slashed = any(filter(lambda v: v.slashed, state.validators)) From 58c0da9059844e879593f2aff5ee081d01587c01 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 1 Sep 2021 14:44:06 -0700 Subject: [PATCH 17/53] ensure rewards spec test with exited validators --- .../eth2spec/test/phase0/rewards/test_random.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py index f158f3cf8..68a6a3279 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py @@ -37,6 +37,20 @@ def test_full_random_3(spec, state): yield from rewards_helpers.run_test_full_random(spec, state, rng=Random(4040)) +@with_all_phases +@spec_state_test +def test_full_random_4(spec, state): + """ + Ensure a rewards test with some exited (but not slashed) validators. + """ + rng = Random(5050) + randomize_state(spec, state, rng) + assert spec.is_in_inactivity_leak(state) + target_validators = get_unslashed_exited_validators(spec, state) + assert len(target_validators) != 0 + yield from rewards_helpers.run_deltas(spec, state) + + @with_all_phases @with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) @spec_test From df8976377736c2d9121cb63454345cdb6011eaa0 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 1 Sep 2021 15:46:40 -0700 Subject: [PATCH 18/53] ensure balance differential as a sanity check --- tests/core/pyspec/eth2spec/test/helpers/state.py | 12 ++++++++++++ .../eth2spec/test/phase0/rewards/test_random.py | 3 +++ 2 files changed, 15 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/helpers/state.py b/tests/core/pyspec/eth2spec/test/helpers/state.py index d8fda3754..327bebaf8 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/state.py +++ b/tests/core/pyspec/eth2spec/test/helpers/state.py @@ -155,3 +155,15 @@ def ensure_state_has_validators_across_lifecycle(spec, state): has_slashed = any(filter(lambda v: v.slashed, state.validators)) return has_pending and has_active and has_exited and has_slashed + + +def has_active_balance_differential(spec, state): + """ + Ensure there is a difference between the total balance of + all _active_ validators and _all_ validators. + """ + epoch = spec.get_current_epoch(state) + active_indices = spec.get_active_validator_indices(state, epoch) + active_balance = spec.get_total_balance(state, set(active_indices)) + total_balance = spec.get_total_balance(state, set(range(len(state.validators)))) + return active_balance // spec.EFFECTIVE_BALANCE_INCREMENT != total_balance // spec.EFFECTIVE_BALANCE_INCREMENT diff --git a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py index 68a6a3279..44f22270b 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py @@ -10,6 +10,7 @@ from eth2spec.test.context import ( ) import eth2spec.test.helpers.rewards as rewards_helpers from eth2spec.test.helpers.random import randomize_state, patch_state_to_non_leaking +from eth2spec.test.helpers.state import has_active_balance_differential from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators @@ -48,6 +49,7 @@ def test_full_random_4(spec, state): assert spec.is_in_inactivity_leak(state) target_validators = get_unslashed_exited_validators(spec, state) assert len(target_validators) != 0 + assert has_active_balance_differential(spec, state) yield from rewards_helpers.run_deltas(spec, state) @@ -85,4 +87,5 @@ def test_full_random_without_leak_0(spec, state): assert not spec.is_in_inactivity_leak(state) target_validators = get_unslashed_exited_validators(spec, state) assert len(target_validators) != 0 + assert has_active_balance_differential(spec, state) yield from rewards_helpers.run_deltas(spec, state) From ad076697f424a84c0e9b07e1d9316c3cd5906ee6 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 1 Sep 2021 15:47:05 -0700 Subject: [PATCH 19/53] add test case for active/exited difference for sync rewards processing --- .../test_process_sync_aggregate.py | 21 +++---- .../test_process_sync_aggregate_random.py | 62 ++++++++++++++++--- .../eth2spec/test/helpers/sync_committee.py | 26 ++------ 3 files changed, 67 insertions(+), 42 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py index bd52ce727..0eaddbb88 100644 --- a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py +++ b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py @@ -12,7 +12,6 @@ from eth2spec.test.helpers.constants import ( from eth2spec.test.helpers.sync_committee import ( compute_aggregate_sync_committee_signature, compute_committee_indices, - get_committee_indices, run_sync_committee_processing, run_successful_sync_committee_test, ) @@ -28,7 +27,7 @@ from eth2spec.test.context import ( @spec_state_test @always_bls def test_invalid_signature_bad_domain(spec, state): - committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) + committee_indices = compute_committee_indices(spec, state) block = build_empty_block_for_next_slot(spec, state) block.body.sync_aggregate = spec.SyncAggregate( @@ -48,7 +47,7 @@ def test_invalid_signature_bad_domain(spec, state): @spec_state_test @always_bls def test_invalid_signature_missing_participant(spec, state): - committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) + committee_indices = compute_committee_indices(spec, state) rng = random.Random(2020) random_participant = rng.choice(committee_indices) @@ -111,7 +110,7 @@ def test_invalid_signature_infinite_signature_with_single_participant(spec, stat @spec_state_test @always_bls def test_invalid_signature_extra_participant(spec, state): - committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) + committee_indices = compute_committee_indices(spec, state) rng = random.Random(3030) random_participant = rng.choice(committee_indices) @@ -134,7 +133,7 @@ def test_invalid_signature_extra_participant(spec, state): @with_presets([MINIMAL], reason="to create nonduplicate committee") @spec_state_test def test_sync_committee_rewards_nonduplicate_committee(spec, state): - committee_indices = get_committee_indices(spec, state, duplicates=False) + committee_indices = compute_committee_indices(spec, state) committee_size = len(committee_indices) committee_bits = [True] * committee_size active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state))) @@ -150,7 +149,7 @@ def test_sync_committee_rewards_nonduplicate_committee(spec, state): @with_presets([MAINNET], reason="to create duplicate committee") @spec_state_test def test_sync_committee_rewards_duplicate_committee_no_participation(spec, state): - committee_indices = get_committee_indices(spec, state, duplicates=True) + committee_indices = compute_committee_indices(spec, state) committee_size = len(committee_indices) committee_bits = [False] * committee_size active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state))) @@ -166,7 +165,7 @@ def test_sync_committee_rewards_duplicate_committee_no_participation(spec, state @with_presets([MAINNET], reason="to create duplicate committee") @spec_state_test def test_sync_committee_rewards_duplicate_committee_half_participation(spec, state): - committee_indices = get_committee_indices(spec, state, duplicates=True) + committee_indices = compute_committee_indices(spec, state) committee_size = len(committee_indices) committee_bits = [True] * (committee_size // 2) + [False] * (committee_size // 2) assert len(committee_bits) == committee_size @@ -183,7 +182,7 @@ def test_sync_committee_rewards_duplicate_committee_half_participation(spec, sta @with_presets([MAINNET], reason="to create duplicate committee") @spec_state_test def test_sync_committee_rewards_duplicate_committee_full_participation(spec, state): - committee_indices = get_committee_indices(spec, state, duplicates=True) + committee_indices = compute_committee_indices(spec, state) committee_size = len(committee_indices) committee_bits = [True] * committee_size active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state))) @@ -199,7 +198,7 @@ def test_sync_committee_rewards_duplicate_committee_full_participation(spec, sta @spec_state_test @always_bls def test_sync_committee_rewards_not_full_participants(spec, state): - committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) + committee_indices = compute_committee_indices(spec, state) rng = random.Random(1010) committee_bits = [rng.choice([True, False]) for _ in committee_indices] @@ -210,7 +209,7 @@ def test_sync_committee_rewards_not_full_participants(spec, state): @spec_state_test @always_bls def test_sync_committee_rewards_empty_participants(spec, state): - committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) + committee_indices = compute_committee_indices(spec, state) committee_bits = [False for _ in committee_indices] yield from run_successful_sync_committee_test(spec, state, committee_indices, committee_bits) @@ -220,7 +219,7 @@ def test_sync_committee_rewards_empty_participants(spec, state): @spec_state_test @always_bls def test_invalid_signature_past_block(spec, state): - committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) + committee_indices = compute_committee_indices(spec, state) for _ in range(2): # NOTE: need to transition twice to move beyond the degenerate case at genesis diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py index 75845e060..436e4d04b 100644 --- a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py +++ b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py @@ -2,10 +2,19 @@ import random from eth2spec.test.helpers.constants import ( MAINNET, MINIMAL, ) +from eth2spec.test.helpers.random import ( + randomize_state +) +from eth2spec.test.helpers.state import ( + has_active_balance_differential, +) from eth2spec.test.helpers.sync_committee import ( - get_committee_indices, + compute_committee_indices, run_successful_sync_committee_test, ) +from eth2spec.test.helpers.voluntary_exits import ( + get_unslashed_exited_validators, +) from eth2spec.test.context import ( with_altair_and_later, spec_state_test, @@ -18,8 +27,8 @@ from eth2spec.test.context import ( ) -def _test_harness_for_randomized_test_case(spec, state, duplicates=False, participation_fn=None): - committee_indices = get_committee_indices(spec, state, duplicates=duplicates) +def _test_harness_for_randomized_test_case(spec, state, expect_duplicates=False, participation_fn=None): + committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) if participation_fn: participating_indices = participation_fn(committee_indices) @@ -28,7 +37,7 @@ def _test_harness_for_randomized_test_case(spec, state, duplicates=False, partic committee_bits = [index in participating_indices for index in committee_indices] committee_size = len(committee_indices) - if duplicates: + if expect_duplicates: assert committee_size > len(set(committee_indices)) else: assert committee_size == len(set(committee_indices)) @@ -44,7 +53,7 @@ def test_random_only_one_participant_with_duplicates(spec, state): yield from _test_harness_for_randomized_test_case( spec, state, - duplicates=True, + expect_duplicates=True, participation_fn=lambda comm: [rng.choice(comm)], ) @@ -57,7 +66,7 @@ def test_random_low_participation_with_duplicates(spec, state): yield from _test_harness_for_randomized_test_case( spec, state, - duplicates=True, + expect_duplicates=True, participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.25)), ) @@ -70,7 +79,7 @@ def test_random_high_participation_with_duplicates(spec, state): yield from _test_harness_for_randomized_test_case( spec, state, - duplicates=True, + expect_duplicates=True, participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.75)), ) @@ -83,7 +92,7 @@ def test_random_all_but_one_participating_with_duplicates(spec, state): yield from _test_harness_for_randomized_test_case( spec, state, - duplicates=True, + expect_duplicates=True, participation_fn=lambda comm: rng.sample(comm, len(comm) - 1), ) @@ -98,7 +107,25 @@ def test_random_misc_balances_and_half_participation_with_duplicates(spec, state yield from _test_harness_for_randomized_test_case( spec, state, - duplicates=True, + expect_duplicates=True, + participation_fn=lambda comm: rng.sample(comm, len(comm) // 2), + ) + + +@with_altair_and_later +@with_presets([MAINNET], reason="to create duplicate committee") +@spec_state_test +@single_phase +def test_random_with_exits_with_duplicates(spec, state): + rng = random.Random(1402) + randomize_state(spec, state, rng=rng, exit_fraction=0.1, slash_fraction=0.0) + target_validators = get_unslashed_exited_validators(spec, state) + assert len(target_validators) != 0 + assert has_active_balance_differential(spec, state) + yield from _test_harness_for_randomized_test_case( + spec, + state, + expect_duplicates=True, participation_fn=lambda comm: rng.sample(comm, len(comm) // 2), ) @@ -163,3 +190,20 @@ def test_random_misc_balances_and_half_participation_without_duplicates(spec, st state, participation_fn=lambda comm: rng.sample(comm, len(comm) // 2), ) + + +@with_altair_and_later +@with_presets([MINIMAL], reason="to create nonduplicate committee") +@spec_state_test +@single_phase +def test_random_with_exits_without_duplicates(spec, state): + rng = random.Random(1502) + randomize_state(spec, state, rng=rng, exit_fraction=0.1, slash_fraction=0.0) + target_validators = get_unslashed_exited_validators(spec, state) + assert len(target_validators) != 0 + assert has_active_balance_differential(spec, state) + yield from _test_harness_for_randomized_test_case( + spec, + state, + participation_fn=lambda comm: rng.sample(comm, len(comm) // 2), + ) diff --git a/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py b/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py index e59f679e1..417802ece 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py +++ b/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py @@ -9,7 +9,6 @@ from eth2spec.test.helpers.block import ( ) from eth2spec.test.helpers.block_processing import run_block_processing_to from eth2spec.utils import bls -from eth2spec.utils.hash_function import hash def compute_sync_committee_signature(spec, state, slot, privkey, block_root=None, domain_type=None): @@ -75,10 +74,12 @@ def compute_sync_committee_proposer_reward(spec, state, committee_indices, commi return spec.Gwei(participant_reward * participant_number) -def compute_committee_indices(spec, state, committee): +def compute_committee_indices(spec, state, committee=None): """ Given a ``committee``, calculate and return the related indices """ + if committee is None: + committee = state.current_sync_committee all_pubkeys = [v.pubkey for v in state.validators] return [all_pubkeys.index(pubkey) for pubkey in committee.pubkeys] @@ -153,6 +154,7 @@ def _build_block_for_next_slot_with_sync_participation(spec, state, committee_in state, block.slot - 1, [index for index, bit in zip(committee_indices, committee_bits) if bit], + block_root=block.parent_root, ) ) return block @@ -161,23 +163,3 @@ def _build_block_for_next_slot_with_sync_participation(spec, state, committee_in def run_successful_sync_committee_test(spec, state, committee_indices, committee_bits): block = _build_block_for_next_slot_with_sync_participation(spec, state, committee_indices, committee_bits) yield from run_sync_committee_processing(spec, state, block) - - -def get_committee_indices(spec, state, duplicates=False): - """ - This utility function allows the caller to ensure there are or are not - duplicate validator indices in the returned committee based on - the boolean ``duplicates``. - """ - state = state.copy() - current_epoch = spec.get_current_epoch(state) - randao_index = (current_epoch + 1) % spec.EPOCHS_PER_HISTORICAL_VECTOR - while True: - committee = spec.get_next_sync_committee_indices(state) - if duplicates: - if len(committee) != len(set(committee)): - return committee - else: - if len(committee) == len(set(committee)): - return committee - state.randao_mixes[randao_index] = hash(state.randao_mixes[randao_index]) From bd38587a1e4491f66174a7bc7314e17f34c53e8e Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 1 Sep 2021 15:53:08 -0700 Subject: [PATCH 20/53] add active/exited balances test for `process_slashings` --- .../test_process_slashings.py | 49 +++++++++++++++++-- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py index e336ebef7..1b977640d 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py @@ -1,7 +1,11 @@ +from random import Random from eth2spec.test.context import spec_state_test, with_all_phases, is_post_altair from eth2spec.test.helpers.epoch_processing import ( run_epoch_processing_with, run_epoch_processing_to ) +from eth2spec.test.helpers.random import randomize_state +from eth2spec.test.helpers.state import has_active_balance_differential +from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators from eth2spec.test.helpers.state import next_epoch @@ -22,6 +26,9 @@ def slash_validators(spec, state, indices, out_epochs): spec.get_current_epoch(state) % spec.EPOCHS_PER_SLASHINGS_VECTOR ] = total_slashed_balance + # verify some slashings happened... + assert total_slashed_balance != 0 + def get_slashing_multiplier(spec): if is_post_altair(spec): @@ -30,9 +37,7 @@ def get_slashing_multiplier(spec): return spec.PROPORTIONAL_SLASHING_MULTIPLIER -@with_all_phases -@spec_state_test -def test_max_penalties(spec, state): +def _setup_process_slashings_test(spec, state, not_slashable_set=set()): # Slashed count to ensure that enough validators are slashed to induce maximum penalties slashed_count = min( (len(state.validators) // get_slashing_multiplier(spec)) + 1, @@ -41,14 +46,23 @@ def test_max_penalties(spec, state): ) out_epoch = spec.get_current_epoch(state) + (spec.EPOCHS_PER_SLASHINGS_VECTOR // 2) - slashed_indices = list(range(slashed_count)) - slash_validators(spec, state, slashed_indices, [out_epoch] * slashed_count) + eligible_indices = set(range(slashed_count)) + slashed_indices = eligible_indices.difference(not_slashable_set) + slash_validators(spec, state, sorted(slashed_indices), [out_epoch] * slashed_count) total_balance = spec.get_total_active_balance(state) total_penalties = sum(state.slashings) assert total_balance // get_slashing_multiplier(spec) <= total_penalties + return slashed_indices + + +@with_all_phases +@spec_state_test +def test_max_penalties(spec, state): + slashed_indices = _setup_process_slashings_test(spec, state) + yield from run_process_slashings(spec, state) for i in slashed_indices: @@ -171,3 +185,28 @@ def test_scaled_penalties(spec, state): * spec.EFFECTIVE_BALANCE_INCREMENT ) assert state.balances[i] == pre_slash_balances[i] - expected_penalty + + +@with_all_phases +@spec_state_test +def test_slashings_with_random_state(spec, state): + rng = Random(9998) + randomize_state(spec, state, rng) + + pre_balances = state.balances.copy() + + target_validators = get_unslashed_exited_validators(spec, state) + assert len(target_validators) != 0 + assert has_active_balance_differential(spec, state) + + slashed_indices = _setup_process_slashings_test(spec, state, not_slashable_set=target_validators) + + # ensure no accidental slashings of protected set... + current_target_validators = get_unslashed_exited_validators(spec, state) + assert len(current_target_validators) != 0 + assert current_target_validators == target_validators + + yield from run_process_slashings(spec, state) + + for i in slashed_indices: + assert state.balances[i] < pre_balances[i] From d834b6e800eacea1961682e5abad701c5fb17c34 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 2 Sep 2021 12:37:11 -0700 Subject: [PATCH 21/53] add active/exited balances test for justification --- ..._process_justification_and_finalization.py | 76 ++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py index 9db6076f8..1dfc07188 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py @@ -1,8 +1,10 @@ +from random import Random from eth2spec.test.context import is_post_altair, spec_state_test, with_all_phases from eth2spec.test.helpers.epoch_processing import ( run_epoch_processing_with, ) -from eth2spec.test.helpers.state import transition_to +from eth2spec.test.helpers.state import transition_to, next_epoch_via_block, next_slot +from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators def run_process_just_and_fin(spec, state): @@ -300,3 +302,75 @@ def test_12_ok_support_messed_target(spec, state): @spec_state_test def test_12_poor_support(spec, state): yield from finalize_on_12(spec, state, 3, False, False) + + +@with_all_phases +@spec_state_test +def test_balance_threshold_with_exited_validators(spec, state): + """ + This test exercises a very specific failure mode where + exited validators are incorrectly included in the total active balance + when weighing justification. + """ + rng = Random(133333) + # move past genesis conditions + for _ in range(3): + next_epoch_via_block(spec, state) + + # mock attestation helper requires last slot of epoch + for _ in range(spec.SLOTS_PER_EPOCH - 1): + next_slot(spec, state) + + # Step 1: Exit ~1/2 vals in current epoch + epoch = spec.get_current_epoch(state) + for index in spec.get_active_validator_indices(state, epoch): + if rng.choice([True, False]): + continue + + validator = state.validators[index] + validator.exit_epoch = epoch + validator.withdrawable_epoch = epoch + 1 + + exited_validators = get_unslashed_exited_validators(spec, state) + assert len(exited_validators) != 0 + + source = state.current_justified_checkpoint + target = spec.Checkpoint( + epoch=epoch, + root=spec.get_block_root(state, epoch) + ) + add_mock_attestations( + spec, + state, + epoch, + source, + target, + sufficient_support=False, + ) + + if not is_post_altair(spec): + current_attestations = spec.get_matching_target_attestations(state, epoch) + total_active_balance = spec.get_total_active_balance(state) + current_target_balance = spec.get_attesting_balance(state, current_attestations) + # Check we will not justify the current checkpoint + does_justify = current_target_balance * 3 >= total_active_balance * 2 + assert not does_justify + # Ensure we would have justified the current checkpoint w/ the exited validators + current_exited_balance = spec.get_total_balance(state, exited_validators) + does_justify = (current_target_balance + current_exited_balance) * 3 >= total_active_balance * 2 + assert does_justify + else: + current_indices = spec.get_unslashed_participating_indices(state, spec.TIMELY_TARGET_FLAG_INDEX, epoch) + total_active_balance = spec.get_total_active_balance(state) + current_target_balance = spec.get_total_balance(state, current_indices) + # Check we will not justify the current checkpoint + does_justify = current_target_balance * 3 >= total_active_balance * 2 + assert not does_justify + # Ensure we would have justified the current checkpoint w/ the exited validators + current_exited_balance = spec.get_total_balance(state, exited_validators) + does_justify = (current_target_balance + current_exited_balance) * 3 >= total_active_balance * 2 + assert does_justify + + yield from run_process_just_and_fin(spec, state) + + assert state.current_justified_checkpoint.epoch != epoch From 30596fb8a1c47b8d14c8619ae3e1c4ccb068b313 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 2 Sep 2021 15:35:15 -0600 Subject: [PATCH 22/53] Update tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py --- .../sync_aggregate/test_process_sync_aggregate_random.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py index 436e4d04b..d38c3fadf 100644 --- a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py +++ b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py @@ -28,7 +28,7 @@ from eth2spec.test.context import ( def _test_harness_for_randomized_test_case(spec, state, expect_duplicates=False, participation_fn=None): - committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) + committee_indices = compute_committee_indices(spec, state) if participation_fn: participating_indices = participation_fn(committee_indices) From 4168943ecf0f3465fdea74f3d9bdc2d21f567153 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 2 Sep 2021 15:38:52 -0600 Subject: [PATCH 23/53] Update tests/core/pyspec/eth2spec/test/helpers/random.py --- tests/core/pyspec/eth2spec/test/helpers/random.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index 0bb2a6672..b24d3c8c7 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -141,7 +141,8 @@ def patch_state_to_non_leaking(spec, state): performed by other functionality in this module so that if the ``state`` was leaking, then the ``state`` is not leaking after. """ - state.justification_bits = (True, True, True, True) + state.justification_bits[0] = True + state.justification_bits[1] = True previous_epoch = spec.get_previous_epoch(state) previous_root = spec.get_block_root(state, previous_epoch) previous_previous_epoch = max(spec.GENESIS_EPOCH, spec.Epoch(previous_epoch - 1)) From 25c290474f3cd0ab5c6b59366ff320c8a4a0c02e Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 2 Sep 2021 15:50:26 -0700 Subject: [PATCH 24/53] fix test filtering on eth1 voting spec test --- .../pyspec/eth2spec/test/phase0/sanity/test_blocks.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py index a79ed94d2..81b0cca7b 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py @@ -974,12 +974,9 @@ def test_historical_batch(spec, state): @with_all_phases +@with_presets([MINIMAL], reason="suffices to test eth1 data voting without long voting period") @spec_state_test def test_eth1_data_votes_consensus(spec, state): - if spec.EPOCHS_PER_ETH1_VOTING_PERIOD > 2: - return dump_skipping_message("Skip test if config with longer `EPOCHS_PER_ETH1_VOTING_PERIOD` for saving time." - " Minimal config suffice to cover the target-of-test.") - voting_period_slots = spec.EPOCHS_PER_ETH1_VOTING_PERIOD * spec.SLOTS_PER_EPOCH offset_block = build_empty_block(spec, state, slot=voting_period_slots - 1) @@ -1018,12 +1015,9 @@ def test_eth1_data_votes_consensus(spec, state): @with_all_phases +@with_presets([MINIMAL], reason="suffices to test eth1 data voting without long voting period") @spec_state_test def test_eth1_data_votes_no_consensus(spec, state): - if spec.EPOCHS_PER_ETH1_VOTING_PERIOD > 2: - return dump_skipping_message("Skip test if config with longer `EPOCHS_PER_ETH1_VOTING_PERIOD` for saving time." - " Minimal config suffice to cover the target-of-test.") - voting_period_slots = spec.EPOCHS_PER_ETH1_VOTING_PERIOD * spec.SLOTS_PER_EPOCH pre_eth1_hash = state.eth1_data.block_hash From fb4a4f669460fba812715501dfdd0be40fac4f81 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 7 Sep 2021 13:09:23 -0600 Subject: [PATCH 25/53] Update tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py Co-authored-by: Danny Ryan --- .../sync_aggregate/test_process_sync_aggregate_random.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py index d38c3fadf..903df4081 100644 --- a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py +++ b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py @@ -3,7 +3,7 @@ from eth2spec.test.helpers.constants import ( MAINNET, MINIMAL, ) from eth2spec.test.helpers.random import ( - randomize_state + randomize_state, ) from eth2spec.test.helpers.state import ( has_active_balance_differential, From 064b489d18504e796adc4661cd4d641456dfd0a8 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 7 Sep 2021 12:23:57 -0700 Subject: [PATCH 26/53] Use spec function for total active balance --- tests/core/pyspec/eth2spec/test/helpers/state.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/state.py b/tests/core/pyspec/eth2spec/test/helpers/state.py index 327bebaf8..6f1923e54 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/state.py +++ b/tests/core/pyspec/eth2spec/test/helpers/state.py @@ -162,8 +162,6 @@ def has_active_balance_differential(spec, state): Ensure there is a difference between the total balance of all _active_ validators and _all_ validators. """ - epoch = spec.get_current_epoch(state) - active_indices = spec.get_active_validator_indices(state, epoch) - active_balance = spec.get_total_balance(state, set(active_indices)) + active_balance = spec.get_total_active_balance(state) total_balance = spec.get_total_balance(state, set(range(len(state.validators)))) return active_balance // spec.EFFECTIVE_BALANCE_INCREMENT != total_balance // spec.EFFECTIVE_BALANCE_INCREMENT From 14f71ffb4bb49f436dff5262cebed2720e0bdef7 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 7 Sep 2021 12:25:09 -0700 Subject: [PATCH 27/53] Use realistic `withdrawable_epoch` in spec test --- .../test_process_justification_and_finalization.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py index 1dfc07188..1d3197ba6 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py @@ -330,6 +330,7 @@ def test_balance_threshold_with_exited_validators(spec, state): validator = state.validators[index] validator.exit_epoch = epoch validator.withdrawable_epoch = epoch + 1 + validator.withdrawable_epoch = validator.exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY exited_validators = get_unslashed_exited_validators(spec, state) assert len(exited_validators) != 0 From 43e79a7ee03d89568371c25e3c7e5d2b9cc049c3 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 7 Sep 2021 20:34:28 -0600 Subject: [PATCH 28/53] add process_registry_updates tests for scaled churn limit --- configs/minimal.yaml | 4 +- tests/core/pyspec/eth2spec/test/context.py | 11 ++ .../test_process_registry_updates.py | 175 ++++++++++++++---- 3 files changed, 147 insertions(+), 43 deletions(-) diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 37a428b50..b067f222f 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -58,8 +58,8 @@ INACTIVITY_SCORE_RECOVERY_RATE: 16 EJECTION_BALANCE: 16000000000 # 2**2 (= 4) MIN_PER_EPOCH_CHURN_LIMIT: 4 -# 2**16 (= 65,536) -CHURN_LIMIT_QUOTIENT: 65536 +# [customized] scale queue churn at much lower validator counts for testing +CHURN_LIMIT_QUOTIENT: 32 # Deposit contract diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 346cdc8f1..ef92efade 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -126,6 +126,17 @@ def default_balances(spec): return [spec.MAX_EFFECTIVE_BALANCE] * num_validators +def scaled_churn_balances(spec): + """ + Helper method to create enough validators to scale the churn limit. + (This is *firmly* over the churn limit -- thus the +2 instead of just +1) + See the second argument of ``max`` in ``get_validator_churn_limit``. + Usage: `@with_custom_state(balances_fn=scaled_churn_balances, ...)` + """ + num_validators = spec.config.CHURN_LIMIT_QUOTIENT * (2 + spec.config.MIN_PER_EPOCH_CHURN_LIMIT) + return [spec.MAX_EFFECTIVE_BALANCE] * num_validators + + with_state = with_custom_state(default_balances, default_activation_threshold) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py index 6e7784aa9..e3f1f2093 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py @@ -1,6 +1,11 @@ from eth2spec.test.helpers.deposits import mock_deposit from eth2spec.test.helpers.state import next_epoch, next_slots -from eth2spec.test.context import spec_state_test, with_all_phases +from eth2spec.test.context import ( + spec_test, spec_state_test, + with_all_phases, single_phase, + with_custom_state, + scaled_churn_balances, +) from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with @@ -112,9 +117,7 @@ def test_activation_queue_sorting(spec, state): assert state.validators[churn_limit - 1].activation_epoch != spec.FAR_FUTURE_EPOCH -@with_all_phases -@spec_state_test -def test_activation_queue_efficiency(spec, state): +def run_test_activation_queue_efficiency(spec, state): churn_limit = spec.get_validator_churn_limit(state) mock_activations = churn_limit * 2 @@ -128,23 +131,43 @@ def test_activation_queue_efficiency(spec, state): state.finalized_checkpoint.epoch = epoch + 1 + # Churn limit could have changed given the active vals removed via `mock_deposit` + churn_limit_0 = spec.get_validator_churn_limit(state) + # 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: + if i < churn_limit_0: 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 + churn_limit_1 = spec.get_validator_churn_limit(state) yield from run_process_registry_updates(spec, state) - for i in range(mock_activations): + for i in range(churn_limit_0 + churn_limit_1): assert state.validators[i].activation_epoch < spec.FAR_FUTURE_EPOCH +@with_all_phases +@spec_state_test +def test_activation_queue_efficiency_min(spec, state): + assert spec.get_validator_churn_limit(state) == spec.config.MIN_PER_EPOCH_CHURN_LIMIT + yield from run_test_activation_queue_efficiency(spec, state) + + +@with_all_phases +@spec_test +@with_custom_state(balances_fn=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@single_phase +def test_activation_queue_efficiency_scaled(spec, state): + assert spec.get_validator_churn_limit(state) > spec.config.MIN_PER_EPOCH_CHURN_LIMIT + yield from run_test_activation_queue_efficiency(spec, state) + + @with_all_phases @spec_state_test def test_ejection(spec, state): @@ -165,9 +188,7 @@ def test_ejection(spec, state): ) -@with_all_phases -@spec_state_test -def test_ejection_past_churn_limit(spec, state): +def run_test_ejection_past_churn_limit(spec, state): churn_limit = spec.get_validator_churn_limit(state) # try to eject more than per-epoch churn limit @@ -184,58 +205,130 @@ def test_ejection_past_churn_limit(spec, state): # 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 + # second third gets 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 + # final third gets 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): +def test_ejection_past_churn_limit_min(spec, state): + assert spec.get_validator_churn_limit(state) == spec.config.MIN_PER_EPOCH_CHURN_LIMIT + yield from run_test_ejection_past_churn_limit(spec, state) + + +@with_all_phases +@spec_test +@with_custom_state(balances_fn=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@single_phase +def test_ejection_past_churn_limit_scaled(spec, state): + assert spec.get_validator_churn_limit(state) > spec.config.MIN_PER_EPOCH_CHURN_LIMIT + yield from run_test_ejection_past_churn_limit(spec, state) + + +def run_test_activation_queue_activation_and_ejection(spec, state, num_per_status): # 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) + activation_queue_start_index = 0 + activation_queue_indices = list(range(activation_queue_start_index, activation_queue_start_index + num_per_status)) + for validator_index in activation_queue_indices: + mock_deposit(spec, state, validator_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 + activation_start_index = num_per_status + activation_indices = list(range(activation_start_index, activation_start_index + num_per_status)) + for validator_index in activation_indices: + mock_deposit(spec, state, validator_index) + state.validators[validator_index].activation_eligibility_epoch = state.finalized_checkpoint.epoch # ready for ejection - ejection_index = 2 - state.validators[ejection_index].effective_balance = spec.config.EJECTION_BALANCE + ejection_start_index = num_per_status * 2 + ejection_indices = list(range(ejection_start_index, ejection_start_index + num_per_status)) + for validator_index in ejection_indices: + state.validators[validator_index].effective_balance = spec.config.EJECTION_BALANCE + churn_limit = spec.get_validator_churn_limit(state) 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)) + # all eligible validators moved into activation queue + for validator_index in activation_queue_indices: + validator = state.validators[validator_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)) - ) + # up to churn limit validators get activated for future epoch from the queue + for validator_index in activation_indices[:churn_limit]: + validator = state.validators[validator_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)) - ) + # any remaining validators do not exit the activation queue + for validator_index in activation_indices[churn_limit:]: + validator = state.validators[validator_index] + assert validator.activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH + assert validator.activation_epoch == spec.FAR_FUTURE_EPOCH + + # all ejection balance validators ejected for a future epoch + for i, validator_index in enumerate(ejection_indices): + validator = state.validators[validator_index] + assert validator.exit_epoch != spec.FAR_FUTURE_EPOCH + assert spec.is_active_validator(validator, spec.get_current_epoch(state)) + queue_offset = i // churn_limit + assert not spec.is_active_validator( + validator, + spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) + queue_offset + ) + + +@with_all_phases +@spec_state_test +def test_activation_queue_activation_and_ejection__1(spec, state): + yield from run_test_activation_queue_activation_and_ejection(spec, state, 1) + + +@with_all_phases +@spec_state_test +def test_activation_queue_activation_and_ejection__churn_limit(spec, state): + num_validators_per_status= spec.get_validator_churn_limit(state) + yield from run_test_activation_queue_activation_and_ejection(spec, state, num_validators_per_status) + + +@with_all_phases +@spec_state_test +def test_activation_queue_activation_and_ejection__exceed_churn_limit(spec, state): + num_validators_per_status = spec.get_validator_churn_limit(state) + 1 + yield from run_test_activation_queue_activation_and_ejection(spec, state, num_validators_per_status) + + +@with_all_phases +@spec_test +@with_custom_state(balances_fn=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@single_phase +def test_activation_queue_activation_and_ejection__scaled_churn_limit(spec, state): + churn_limit = spec.get_validator_churn_limit(state) + assert churn_limit > spec.config.MIN_PER_EPOCH_CHURN_LIMIT + yield from run_test_activation_queue_activation_and_ejection(spec, state, churn_limit) + + +@with_all_phases +@spec_test +@with_custom_state(balances_fn=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@single_phase +def test_activation_queue_activation_and_ejection__exceed_scaled_churn_limit(spec, state): + churn_limit = spec.get_validator_churn_limit(state) + assert churn_limit > spec.config.MIN_PER_EPOCH_CHURN_LIMIT + num_validators_per_status = churn_limit * 2 + yield from run_test_activation_queue_activation_and_ejection(spec, state, num_validators_per_status) From 6784025d645f71fd16e54cb77983e608ac918776 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 7 Sep 2021 20:49:54 -0600 Subject: [PATCH 29/53] add scaled churn limit tests for voluntary exits --- .../test_process_voluntary_exit.py | 36 ++++++++++++++----- .../test_process_registry_updates.py | 2 +- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py index f713d1792..1b6b40580 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py @@ -1,4 +1,9 @@ -from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls, with_all_phases +from eth2spec.test.context import ( + spec_state_test, expect_assertion_error, + always_bls, with_all_phases, + spec_test, single_phase, + with_custom_state, scaled_churn_balances, +) from eth2spec.test.helpers.keys import pubkey_to_privkey from eth2spec.test.helpers.voluntary_exits import sign_voluntary_exit @@ -68,9 +73,7 @@ def test_invalid_signature(spec, state): yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, False) -@with_all_phases -@spec_state_test -def test_success_exit_queue(spec, state): +def run_test_success_exit_queue(spec, state): # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH @@ -106,10 +109,27 @@ def test_success_exit_queue(spec, state): # when processing an additional exit, it results in an exit in a later epoch yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit) - assert ( - state.validators[validator_index].exit_epoch == - state.validators[initial_indices[0]].exit_epoch + 1 - ) + for index in initial_indices: + assert ( + state.validators[validator_index].exit_epoch == + state.validators[index].exit_epoch + 1 + ) + + +@with_all_phases +@spec_state_test +def test_success_exit_queue__min_churn(spec, state): + yield from run_test_success_exit_queue(spec, state) + + +@with_all_phases +@spec_test +@with_custom_state(balances_fn=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@single_phase +def test_success_exit_queue__scaled_churn(spec, state): + churn_limit = spec.get_validator_churn_limit(state) + assert churn_limit > spec.config.MIN_PER_EPOCH_CHURN_LIMIT + yield from run_test_success_exit_queue(spec, state) @with_all_phases diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py index e3f1f2093..6b1d7bce9 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py @@ -302,7 +302,7 @@ def test_activation_queue_activation_and_ejection__1(spec, state): @with_all_phases @spec_state_test def test_activation_queue_activation_and_ejection__churn_limit(spec, state): - num_validators_per_status= spec.get_validator_churn_limit(state) + num_validators_per_status = spec.get_validator_churn_limit(state) yield from run_test_activation_queue_activation_and_ejection(spec, state, num_validators_per_status) From 5bc59d8aabcee8930900466dee9fa9af2257622f Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 8 Sep 2021 21:22:48 +0800 Subject: [PATCH 30/53] Fix the comments --- .../pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py index 8227c4cfb..c00a91b28 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py @@ -218,7 +218,7 @@ def test_on_block_finalized_skip_slots(spec, state): state, store, _ = yield from apply_next_epoch_with_attestations( spec, state, store, True, True, test_steps=test_steps) - # Now we get finalized epoch 1, where compute_start_slot_at_epoch(1) is a skipped slot + # Now we get finalized epoch 2, where `compute_start_slot_at_epoch(2)` is a skipped slot assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 assert store.finalized_checkpoint.root == spec.get_block_root(state, 1) == spec.get_block_root(state, 2) assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 @@ -262,7 +262,7 @@ def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): state, store, _ = yield from apply_next_epoch_with_attestations( spec, state, store, True, True, test_steps=test_steps) - # Now we get finalized epoch 1, where compute_start_slot_at_epoch(1) is a skipped slot + # Now we get finalized epoch 2, where `compute_start_slot_at_epoch(2)` is a skipped slot assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 assert store.finalized_checkpoint.root == spec.get_block_root(state, 1) == spec.get_block_root(state, 2) assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 From 8220f7dd44fa983d3d40d8a72d758c8eb3bbc506 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 7 Sep 2021 20:55:47 -0600 Subject: [PATCH 31/53] ensure new dynamic queue tests don't run for mainnet cofig --- .../block_processing/test_process_voluntary_exit.py | 5 ++++- .../epoch_processing/test_process_registry_updates.py | 11 ++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py index 1b6b40580..9e209f23e 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py @@ -1,6 +1,7 @@ +from eth2spec.test.helpers.constants import MINIMAL from eth2spec.test.context import ( spec_state_test, expect_assertion_error, - always_bls, with_all_phases, + always_bls, with_all_phases, with_presets, spec_test, single_phase, with_custom_state, scaled_churn_balances, ) @@ -123,6 +124,8 @@ def test_success_exit_queue__min_churn(spec, state): @with_all_phases +@with_presets([MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") @spec_test @with_custom_state(balances_fn=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) @single_phase diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py index 6b1d7bce9..5bcf3a82b 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py @@ -1,9 +1,10 @@ from eth2spec.test.helpers.deposits import mock_deposit from eth2spec.test.helpers.state import next_epoch, next_slots +from eth2spec.test.helpers.constants import MINIMAL from eth2spec.test.context import ( spec_test, spec_state_test, with_all_phases, single_phase, - with_custom_state, + with_custom_state, with_presets, scaled_churn_balances, ) from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with @@ -160,6 +161,8 @@ def test_activation_queue_efficiency_min(spec, state): @with_all_phases +@with_presets([MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") @spec_test @with_custom_state(balances_fn=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) @single_phase @@ -221,6 +224,8 @@ def test_ejection_past_churn_limit_min(spec, state): @with_all_phases +@with_presets([MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") @spec_test @with_custom_state(balances_fn=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) @single_phase @@ -314,6 +319,8 @@ def test_activation_queue_activation_and_ejection__exceed_churn_limit(spec, stat @with_all_phases +@with_presets([MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") @spec_test @with_custom_state(balances_fn=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) @single_phase @@ -324,6 +331,8 @@ def test_activation_queue_activation_and_ejection__scaled_churn_limit(spec, stat @with_all_phases +@with_presets([MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") @spec_test @with_custom_state(balances_fn=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) @single_phase From 49d225bb785bba6eb233e306119b7caf6ca3057c Mon Sep 17 00:00:00 2001 From: lsankar4033 Date: Wed, 8 Sep 2021 12:34:33 -0700 Subject: [PATCH 32/53] Add new --terminal-total-difficulty-override client_setting --- README.md | 3 ++- specs/merge/client_settings.md | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 specs/merge/client_settings.md diff --git a/README.md b/README.md index bc4ba100b..b5a898d37 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ The merge is still actively in development. The exact specification has not been * [Merge fork](specs/merge/fork.md) * [Fork Choice changes](specs/merge/fork-choice.md) * [Validator additions](specs/merge/validator.md) + * [Client settings](specs/merge/client_settings.md) ### Sharding @@ -53,7 +54,7 @@ Sharding follows the merge, and is divided into three parts: * Sharding base functionality - In early engineering phase * [Beacon Chain changes](specs/sharding/beacon-chain.md) * [P2P Network changes](specs/sharding/p2p-interface.md) -* Custody Game - Ready, dependent on sharding +* Custody Game - Ready, dependent on sharding * [Beacon Chain changes](specs/custody_game/beacon-chain.md) * [Validator custody work](specs/custody_game/validator.md) * Data Availability Sampling - In active R&D diff --git a/specs/merge/client_settings.md b/specs/merge/client_settings.md new file mode 100644 index 000000000..172617a74 --- /dev/null +++ b/specs/merge/client_settings.md @@ -0,0 +1,14 @@ +# The Merge -- Client Settings + +**Notice**: This document is a work-in-progress for researchers and implementers. + +This document specifies configurable settings that merge clients are expected to ship with. + +### Override terminal total difficulty + +To coordinate changes to [`terminal_total_difficulty`](specs/merge/fork-choice.md#transitionstore), clients +should have a setting `--terminal-total-difficulty-override`. + +If `TransitionStore` has already been initialized, this just changes the value of +`TransitionStore.terminal_total_difficulty`, otherwise it initializes `TransitionStore` with the specified +`terminal_total_difficulty`. From 01fe3cdb083dbf561d08ad2f11db777fe2724811 Mon Sep 17 00:00:00 2001 From: lsankar4033 Date: Wed, 8 Sep 2021 12:57:49 -0700 Subject: [PATCH 33/53] Add note about default behavior --- specs/merge/client_settings.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/specs/merge/client_settings.md b/specs/merge/client_settings.md index 172617a74..f1ff8b639 100644 --- a/specs/merge/client_settings.md +++ b/specs/merge/client_settings.md @@ -12,3 +12,6 @@ should have a setting `--terminal-total-difficulty-override`. If `TransitionStore` has already been initialized, this just changes the value of `TransitionStore.terminal_total_difficulty`, otherwise it initializes `TransitionStore` with the specified `terminal_total_difficulty`. + +By default, this setting is expected to not be used and `terminal_total_difficulty` will be set as defined +[here](specs/merge/fork.md#initializing-transition-store). From 0f7d8e5552c0ed7c4e992e3183a7e19d365c63c4 Mon Sep 17 00:00:00 2001 From: lsankar4033 Date: Wed, 8 Sep 2021 13:03:05 -0700 Subject: [PATCH 34/53] Add notes in merge/fork.md --- specs/merge/fork.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/specs/merge/fork.md b/specs/merge/fork.md index f16be0574..0785f577c 100644 --- a/specs/merge/fork.md +++ b/specs/merge/fork.md @@ -97,13 +97,13 @@ def upgrade_to_merge(pre: altair.BeaconState) -> BeaconState: # Execution-layer latest_execution_payload_header=ExecutionPayloadHeader(), ) - + return post ``` ### Initializing transition store -If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == MERGE_FORK_EPOCH`, a transition store is initialized to be further utilized by the transition process of the Merge. +If `state.slot % SLOTS_PER_EPOCH == 0`, `compute_epoch_at_slot(state.slot) == MERGE_FORK_EPOCH`, and the transition store has not already been initialized, a transition store is initialized to be further utilized by the transition process of the Merge. Transition store initialization occurs after the state has been modified by corresponding `upgrade_to_merge` function. @@ -127,3 +127,6 @@ def initialize_transition_store(state: BeaconState) -> TransitionStore: pow_block = get_pow_block(state.eth1_data.block_hash) return get_transition_store(pow_block) ``` + +Note that transition store can also be initialized at client startup by [overriding terminal total +difficulty](specs/merge/client_settings.md#override-terminal-total-difficulty). From a542a07578d5cc3b5c1d41e11a14a071cc0fe7da Mon Sep 17 00:00:00 2001 From: lsankar4033 Date: Wed, 8 Sep 2021 13:06:22 -0700 Subject: [PATCH 35/53] Fix links --- specs/merge/client_settings.md | 4 ++-- specs/merge/fork.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/merge/client_settings.md b/specs/merge/client_settings.md index f1ff8b639..520ca1fdf 100644 --- a/specs/merge/client_settings.md +++ b/specs/merge/client_settings.md @@ -6,7 +6,7 @@ This document specifies configurable settings that merge clients are expected to ### Override terminal total difficulty -To coordinate changes to [`terminal_total_difficulty`](specs/merge/fork-choice.md#transitionstore), clients +To coordinate changes to [`terminal_total_difficulty`](fork-choice.md#transitionstore), clients should have a setting `--terminal-total-difficulty-override`. If `TransitionStore` has already been initialized, this just changes the value of @@ -14,4 +14,4 @@ If `TransitionStore` has already been initialized, this just changes the value o `terminal_total_difficulty`. By default, this setting is expected to not be used and `terminal_total_difficulty` will be set as defined -[here](specs/merge/fork.md#initializing-transition-store). +[here](fork.md#initializing-transition-store). diff --git a/specs/merge/fork.md b/specs/merge/fork.md index 0785f577c..64216a401 100644 --- a/specs/merge/fork.md +++ b/specs/merge/fork.md @@ -129,4 +129,4 @@ def initialize_transition_store(state: BeaconState) -> TransitionStore: ``` Note that transition store can also be initialized at client startup by [overriding terminal total -difficulty](specs/merge/client_settings.md#override-terminal-total-difficulty). +difficulty](client_settings.md#override-terminal-total-difficulty). From 989cd380417930ffe33033a9476c264dbe6ba0a3 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 8 Sep 2021 19:55:41 -0700 Subject: [PATCH 36/53] add rewards spec test with exit in current epoch --- .../test/phase0/rewards/test_random.py | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py index 44f22270b..e6112557a 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py @@ -9,8 +9,12 @@ from eth2spec.test.context import ( low_balances, misc_balances, ) import eth2spec.test.helpers.rewards as rewards_helpers -from eth2spec.test.helpers.random import randomize_state, patch_state_to_non_leaking -from eth2spec.test.helpers.state import has_active_balance_differential +from eth2spec.test.helpers.random import ( + randomize_state, + patch_state_to_non_leaking, + randomize_attestation_participation, +) +from eth2spec.test.helpers.state import has_active_balance_differential, next_epoch from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators @@ -89,3 +93,38 @@ def test_full_random_without_leak_0(spec, state): assert len(target_validators) != 0 assert has_active_balance_differential(spec, state) yield from rewards_helpers.run_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_full_random_without_leak_and_current_exit_0(spec, state): + """ + This test specifically ensures a validator exits in the current epoch + to ensure rewards are handled properly in this case. + """ + rng = Random(1011) + randomize_state(spec, state, rng) + assert spec.is_in_inactivity_leak(state) + patch_state_to_non_leaking(spec, state) + assert not spec.is_in_inactivity_leak(state) + target_validators = get_unslashed_exited_validators(spec, state) + assert len(target_validators) != 0 + + # move forward some epochs to process attestations added + # by ``randomize_state`` before we exit validators in + # what will be the current epoch + for _ in range(2): + next_epoch(spec, state) + + current_epoch = spec.get_current_epoch(state) + for index in target_validators: + # patch exited validators to exit in the current epoch + validator = state.validators[index] + validator.exit_epoch = current_epoch + validator.withdrawable_epoch = current_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + + # re-randomize attestation participation for the current epoch + randomize_attestation_participation(spec, state, rng) + + assert has_active_balance_differential(spec, state) + yield from rewards_helpers.run_deltas(spec, state) From 10f4ea4b5122903c28ef9dbb6ec66bba93b9791a Mon Sep 17 00:00:00 2001 From: Lakshman Sankar Date: Thu, 9 Sep 2021 08:36:25 -0700 Subject: [PATCH 37/53] Wording change Co-authored-by: Danny Ryan --- specs/merge/client_settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/merge/client_settings.md b/specs/merge/client_settings.md index 520ca1fdf..8f4ca6367 100644 --- a/specs/merge/client_settings.md +++ b/specs/merge/client_settings.md @@ -9,7 +9,7 @@ This document specifies configurable settings that merge clients are expected to To coordinate changes to [`terminal_total_difficulty`](fork-choice.md#transitionstore), clients should have a setting `--terminal-total-difficulty-override`. -If `TransitionStore` has already been initialized, this just changes the value of +If `TransitionStore` has already [been initialized](./fork.md#initializing-transition-store), this alters the previously initialized value of `TransitionStore.terminal_total_difficulty`, otherwise it initializes `TransitionStore` with the specified `terminal_total_difficulty`. From de1487564ad02c5201427179d42507971808cccf Mon Sep 17 00:00:00 2001 From: Lakshman Sankar Date: Thu, 9 Sep 2021 08:36:42 -0700 Subject: [PATCH 38/53] Wording change Co-authored-by: Mikhail Kalinin --- specs/merge/fork.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/merge/fork.md b/specs/merge/fork.md index 64216a401..f2547758d 100644 --- a/specs/merge/fork.md +++ b/specs/merge/fork.md @@ -128,5 +128,5 @@ def initialize_transition_store(state: BeaconState) -> TransitionStore: return get_transition_store(pow_block) ``` -Note that transition store can also be initialized at client startup by [overriding terminal total +*Note*: Transition store can also be initialized at client startup by [overriding terminal total difficulty](client_settings.md#override-terminal-total-difficulty). From 385ee12ef0a9d2db1fb57af9549ac9c4a5b42d4c Mon Sep 17 00:00:00 2001 From: Lakshman Sankar Date: Thu, 9 Sep 2021 08:36:54 -0700 Subject: [PATCH 39/53] 'should' -> 'must' Co-authored-by: Danny Ryan --- specs/merge/client_settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/merge/client_settings.md b/specs/merge/client_settings.md index 8f4ca6367..e2a69c538 100644 --- a/specs/merge/client_settings.md +++ b/specs/merge/client_settings.md @@ -7,7 +7,7 @@ This document specifies configurable settings that merge clients are expected to ### Override terminal total difficulty To coordinate changes to [`terminal_total_difficulty`](fork-choice.md#transitionstore), clients -should have a setting `--terminal-total-difficulty-override`. +must provide `--terminal-total-difficulty-override` as a configurable setting. If `TransitionStore` has already [been initialized](./fork.md#initializing-transition-store), this alters the previously initialized value of `TransitionStore.terminal_total_difficulty`, otherwise it initializes `TransitionStore` with the specified From 771933d1a70ef62c7cd645d3050127c2f5d1fb52 Mon Sep 17 00:00:00 2001 From: Lakshman Sankar Date: Thu, 9 Sep 2021 08:37:27 -0700 Subject: [PATCH 40/53] Stronger language around usage of the setting Co-authored-by: Danny Ryan --- specs/merge/client_settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/merge/client_settings.md b/specs/merge/client_settings.md index e2a69c538..05a13094a 100644 --- a/specs/merge/client_settings.md +++ b/specs/merge/client_settings.md @@ -13,5 +13,5 @@ If `TransitionStore` has already [been initialized](./fork.md#initializing-trans `TransitionStore.terminal_total_difficulty`, otherwise it initializes `TransitionStore` with the specified `terminal_total_difficulty`. -By default, this setting is expected to not be used and `terminal_total_difficulty` will be set as defined +Except under exceptional scenarios, this setting is expected to not be used, and `terminal_total_difficulty` will operate with [default functionality](./fork.md#initializing-transition-store). Sufficient warning to the user about this exceptional configurable setting should be provided. [here](fork.md#initializing-transition-store). From 252f4ea14ad5a48d7100a38968f636d380e893b2 Mon Sep 17 00:00:00 2001 From: Lakshman Sankar Date: Thu, 9 Sep 2021 08:37:39 -0700 Subject: [PATCH 41/53] Wording change Co-authored-by: Danny Ryan --- specs/merge/client_settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/merge/client_settings.md b/specs/merge/client_settings.md index 05a13094a..6356e8a5b 100644 --- a/specs/merge/client_settings.md +++ b/specs/merge/client_settings.md @@ -2,7 +2,7 @@ **Notice**: This document is a work-in-progress for researchers and implementers. -This document specifies configurable settings that merge clients are expected to ship with. +This document specifies configurable settings that clients must implement for the Merge. ### Override terminal total difficulty From 83471fe46121760d6fc8b2e150e6590c7f6ad3f7 Mon Sep 17 00:00:00 2001 From: Lakshman Sankar Date: Thu, 9 Sep 2021 08:38:33 -0700 Subject: [PATCH 42/53] Apply suggestions from code review Co-authored-by: Danny Ryan --- specs/merge/client_settings.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/merge/client_settings.md b/specs/merge/client_settings.md index 6356e8a5b..3ff897560 100644 --- a/specs/merge/client_settings.md +++ b/specs/merge/client_settings.md @@ -6,11 +6,11 @@ This document specifies configurable settings that clients must implement for th ### Override terminal total difficulty -To coordinate changes to [`terminal_total_difficulty`](fork-choice.md#transitionstore), clients +To coordinate manual overrides to [`terminal_total_difficulty`](fork-choice.md#transitionstore), clients must provide `--terminal-total-difficulty-override` as a configurable setting. If `TransitionStore` has already [been initialized](./fork.md#initializing-transition-store), this alters the previously initialized value of -`TransitionStore.terminal_total_difficulty`, otherwise it initializes `TransitionStore` with the specified +`TransitionStore.terminal_total_difficulty`, otherwise this setting initializes `TransitionStore` with the specified, bypassing `compute_terminal_total_difficulty` and the use of an `anchor_pow_block`. `terminal_total_difficulty`. Except under exceptional scenarios, this setting is expected to not be used, and `terminal_total_difficulty` will operate with [default functionality](./fork.md#initializing-transition-store). Sufficient warning to the user about this exceptional configurable setting should be provided. From 879a60a1697b72497e5dfb0cd0cc733d51b4ac85 Mon Sep 17 00:00:00 2001 From: lsankar4033 Date: Thu, 9 Sep 2021 08:45:21 -0700 Subject: [PATCH 43/53] Run doctoc --- specs/merge/client_settings.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/specs/merge/client_settings.md b/specs/merge/client_settings.md index 3ff897560..a8ca633ff 100644 --- a/specs/merge/client_settings.md +++ b/specs/merge/client_settings.md @@ -1,3 +1,12 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [The Merge -- Client Settings](#the-merge----client-settings) + - [Override terminal total difficulty](#override-terminal-total-difficulty) + + + # The Merge -- Client Settings **Notice**: This document is a work-in-progress for researchers and implementers. From c0994e6736bac1400cb24fb9456c3e6936f134cb Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 9 Sep 2021 13:24:55 -0700 Subject: [PATCH 44/53] Add sync committee tests with exited and withdrawable members --- .../test_process_sync_aggregate.py | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py index 0eaddbb88..933bed1b2 100644 --- a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py +++ b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py @@ -5,6 +5,7 @@ from eth2spec.test.helpers.block import ( from eth2spec.test.helpers.state import ( state_transition_and_sign_block, transition_to, + next_epoch_via_block, ) from eth2spec.test.helpers.constants import ( MAINNET, MINIMAL, @@ -15,6 +16,10 @@ from eth2spec.test.helpers.sync_committee import ( run_sync_committee_processing, run_successful_sync_committee_test, ) +from eth2spec.test.helpers.voluntary_exits import ( + prepare_signed_exits, + get_unslashed_exited_validators, +) from eth2spec.test.context import ( with_altair_and_later, with_presets, @@ -402,3 +407,189 @@ def test_proposer_in_committee_with_participation(spec, state): else: state_transition_and_sign_block(spec, state, block) raise AssertionError("failed to find a proposer in the sync committee set; check test setup") + + +@with_altair_and_later +@spec_state_test +@always_bls +def test_sync_committee_with_participating_exited_member(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + # move forward via some blocks + for _ in range(3): + next_epoch_via_block(spec, state) + + committee_indices = compute_committee_indices(spec, state) + rng = random.Random(1010) + + exited_validator_index = rng.sample(committee_indices, 1)[0] + exits = prepare_signed_exits(spec, state, [exited_validator_index]) + assert len(exits) == 1 + voluntary_exit = exits.pop() + spec.process_voluntary_exit(state, voluntary_exit) + + exit_epoch = state.validators[exited_validator_index].exit_epoch + exit_slot = exit_epoch * spec.SLOTS_PER_EPOCH + transition_to(spec, state, exit_slot) + + exited_validator_indices = get_unslashed_exited_validators(spec, state) + assert exited_validator_index in exited_validator_indices + exited_pubkey = state.validators[exited_validator_index].pubkey + assert exited_pubkey in state.current_sync_committee.pubkeys + current_epoch = spec.get_current_epoch(state) + assert current_epoch < state.validators[exited_validator_index].withdrawable_epoch + + block = build_empty_block_for_next_slot(spec, state) + block.body.sync_aggregate = spec.SyncAggregate( + sync_committee_bits=[True] * len(committee_indices), + sync_committee_signature=compute_aggregate_sync_committee_signature( + spec, + state, + block.slot - 1, + committee_indices, # full committee signs + ) + ) + yield from run_sync_committee_processing(spec, state, block) + + +@with_altair_and_later +@spec_state_test +@always_bls +def test_sync_committee_with_nonparticipating_exited_member(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + # move forward via some blocks + for _ in range(3): + next_epoch_via_block(spec, state) + + committee_indices = compute_committee_indices(spec, state) + rng = random.Random(1010) + + exited_validator_index = rng.sample(committee_indices, 1)[0] + exits = prepare_signed_exits(spec, state, [exited_validator_index]) + assert len(exits) == 1 + voluntary_exit = exits.pop() + spec.process_voluntary_exit(state, voluntary_exit) + + exit_epoch = state.validators[exited_validator_index].exit_epoch + exit_slot = exit_epoch * spec.SLOTS_PER_EPOCH + transition_to(spec, state, exit_slot) + + exited_validator_indices = get_unslashed_exited_validators(spec, state) + assert exited_validator_index in exited_validator_indices + exited_pubkey = state.validators[exited_validator_index].pubkey + assert exited_pubkey in state.current_sync_committee.pubkeys + current_epoch = spec.get_current_epoch(state) + assert current_epoch < state.validators[exited_validator_index].withdrawable_epoch + + exited_committee_index = state.current_sync_committee.pubkeys.index(exited_pubkey) + block = build_empty_block_for_next_slot(spec, state) + committee_bits = [i != exited_committee_index for i in committee_indices] + committee_indices.remove(exited_committee_index) + block.body.sync_aggregate = spec.SyncAggregate( + sync_committee_bits=committee_bits, + sync_committee_signature=compute_aggregate_sync_committee_signature( + spec, + state, + block.slot - 1, + committee_indices, # with exited validator removed + ) + ) + yield from run_sync_committee_processing(spec, state, block) + + +@with_altair_and_later +@spec_state_test +@always_bls +def test_sync_committee_with_participating_withdrawable_member(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + # move forward via some blocks + for _ in range(3): + next_epoch_via_block(spec, state) + + committee_indices = compute_committee_indices(spec, state) + rng = random.Random(1010) + + exited_validator_index = rng.sample(committee_indices, 1)[0] + exits = prepare_signed_exits(spec, state, [exited_validator_index]) + assert len(exits) == 1 + voluntary_exit = exits.pop() + spec.process_voluntary_exit(state, voluntary_exit) + + target_validator = state.validators[exited_validator_index] + target_validator.withdrawable_epoch = target_validator.exit_epoch + 1 + + target_slot = (target_validator.withdrawable_epoch + 1) * spec.SLOTS_PER_EPOCH + transition_to(spec, state, target_slot) + + exited_validator_indices = get_unslashed_exited_validators(spec, state) + assert exited_validator_index in exited_validator_indices + exited_pubkey = state.validators[exited_validator_index].pubkey + assert exited_pubkey in state.current_sync_committee.pubkeys + current_epoch = spec.get_current_epoch(state) + assert current_epoch > state.validators[exited_validator_index].withdrawable_epoch + + block = build_empty_block_for_next_slot(spec, state) + block.body.sync_aggregate = spec.SyncAggregate( + sync_committee_bits=[True] * len(committee_indices), + sync_committee_signature=compute_aggregate_sync_committee_signature( + spec, + state, + block.slot - 1, + committee_indices, # full committee signs + ) + ) + yield from run_sync_committee_processing(spec, state, block) + + +@with_altair_and_later +@spec_state_test +@always_bls +def test_sync_committee_with_nonparticipating_withdrawable_member(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + # move forward via some blocks + for _ in range(3): + next_epoch_via_block(spec, state) + + committee_indices = compute_committee_indices(spec, state) + rng = random.Random(1010) + + exited_validator_index = rng.sample(committee_indices, 1)[0] + exits = prepare_signed_exits(spec, state, [exited_validator_index]) + assert len(exits) == 1 + voluntary_exit = exits.pop() + spec.process_voluntary_exit(state, voluntary_exit) + + target_validator = state.validators[exited_validator_index] + target_validator.withdrawable_epoch = target_validator.exit_epoch + 1 + + target_slot = (target_validator.withdrawable_epoch + 1) * spec.SLOTS_PER_EPOCH + transition_to(spec, state, target_slot) + + exited_validator_indices = get_unslashed_exited_validators(spec, state) + assert exited_validator_index in exited_validator_indices + exited_pubkey = state.validators[exited_validator_index].pubkey + assert exited_pubkey in state.current_sync_committee.pubkeys + current_epoch = spec.get_current_epoch(state) + assert current_epoch > state.validators[exited_validator_index].withdrawable_epoch + + target_committee_index = state.current_sync_committee.pubkeys.index(exited_pubkey) + block = build_empty_block_for_next_slot(spec, state) + committee_bits = [i != target_committee_index for i in committee_indices] + committee_indices.remove(target_committee_index) + block.body.sync_aggregate = spec.SyncAggregate( + sync_committee_bits=committee_bits, + sync_committee_signature=compute_aggregate_sync_committee_signature( + spec, + state, + block.slot - 1, + committee_indices, # with withdrawable validator removed + ) + ) + yield from run_sync_committee_processing(spec, state, block) From a47ade3ba6eb96e6b51957cfeb2ecc34910f3740 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 9 Sep 2021 15:05:52 -0600 Subject: [PATCH 45/53] pr feedback --- .../test_process_registry_updates.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py index 5bcf3a82b..6539dc92d 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py @@ -307,15 +307,17 @@ def test_activation_queue_activation_and_ejection__1(spec, state): @with_all_phases @spec_state_test def test_activation_queue_activation_and_ejection__churn_limit(spec, state): - num_validators_per_status = spec.get_validator_churn_limit(state) - yield from run_test_activation_queue_activation_and_ejection(spec, state, num_validators_per_status) + churn_limit = spec.get_validator_churn_limit(state) + assert churn_limit == spec.config.MIN_PER_EPOCH_CHURN_LIMIT + yield from run_test_activation_queue_activation_and_ejection(spec, state, churn_limit) @with_all_phases @spec_state_test def test_activation_queue_activation_and_ejection__exceed_churn_limit(spec, state): - num_validators_per_status = spec.get_validator_churn_limit(state) + 1 - yield from run_test_activation_queue_activation_and_ejection(spec, state, num_validators_per_status) + churn_limit = spec.get_validator_churn_limit(state) + assert churn_limit == spec.config.MIN_PER_EPOCH_CHURN_LIMIT + yield from run_test_activation_queue_activation_and_ejection(spec, state, churn_limit + 1) @with_all_phases @@ -339,5 +341,4 @@ def test_activation_queue_activation_and_ejection__scaled_churn_limit(spec, stat def test_activation_queue_activation_and_ejection__exceed_scaled_churn_limit(spec, state): churn_limit = spec.get_validator_churn_limit(state) assert churn_limit > spec.config.MIN_PER_EPOCH_CHURN_LIMIT - num_validators_per_status = churn_limit * 2 - yield from run_test_activation_queue_activation_and_ejection(spec, state, num_validators_per_status) + yield from run_test_activation_queue_activation_and_ejection(spec, state, churn_limit * 2) From 5348b9a3b905dc3477c8bd9d3892c928b7d117ab Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 9 Sep 2021 15:56:08 -0600 Subject: [PATCH 46/53] randomize state can result in some exited vals for current epoch --- tests/core/pyspec/eth2spec/test/helpers/random.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index b24d3c8c7..8448b2424 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -37,8 +37,8 @@ def exit_random_validators(spec, state, rng, fraction=None): continue validator = state.validators[index] - validator.exit_epoch = rng.choice([current_epoch - 1, current_epoch - 2, current_epoch - 3]) - # ~1/2 are withdrawable + validator.exit_epoch = rng.choice([current_epoch, current_epoch - 1, current_epoch - 2, current_epoch - 3]) + # ~1/2 are withdrawable (note, unnatural span between exit epoch and withdrawable epoch) if rng.choice([True, False]): validator.withdrawable_epoch = current_epoch else: From 0cee5660dba55f6e0ed1745b597b786bf2209c33 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 9 Sep 2021 15:43:42 -0700 Subject: [PATCH 47/53] pr feedback --- .../test_process_sync_aggregate.py | 119 +++++++++--------- 1 file changed, 58 insertions(+), 61 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py index 933bed1b2..474380acd 100644 --- a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py +++ b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py @@ -17,7 +17,6 @@ from eth2spec.test.helpers.sync_committee import ( run_successful_sync_committee_test, ) from eth2spec.test.helpers.voluntary_exits import ( - prepare_signed_exits, get_unslashed_exited_validators, ) from eth2spec.test.context import ( @@ -409,6 +408,30 @@ def test_proposer_in_committee_with_participation(spec, state): raise AssertionError("failed to find a proposer in the sync committee set; check test setup") +def _exit_validator_from_committee_and_transition_state(spec, + state, + committee_indices, + rng, + target_epoch_provider, + withdrawable_offset=1): + exited_validator_index = rng.sample(committee_indices, 1)[0] + validator = state.validators[exited_validator_index] + current_epoch = spec.get_current_epoch(state) + validator.exit_epoch = current_epoch + validator.withdrawable_epoch = validator.exit_epoch + withdrawable_offset + + target_epoch = target_epoch_provider(state.validators[exited_validator_index]) + target_slot = target_epoch * spec.SLOTS_PER_EPOCH + transition_to(spec, state, target_slot) + + exited_validator_indices = get_unslashed_exited_validators(spec, state) + assert exited_validator_index in exited_validator_indices + exited_pubkey = state.validators[exited_validator_index].pubkey + assert exited_pubkey in state.current_sync_committee.pubkeys + + return exited_validator_index + + @with_altair_and_later @spec_state_test @always_bls @@ -423,22 +446,16 @@ def test_sync_committee_with_participating_exited_member(spec, state): committee_indices = compute_committee_indices(spec, state) rng = random.Random(1010) - exited_validator_index = rng.sample(committee_indices, 1)[0] - exits = prepare_signed_exits(spec, state, [exited_validator_index]) - assert len(exits) == 1 - voluntary_exit = exits.pop() - spec.process_voluntary_exit(state, voluntary_exit) + exited_index = _exit_validator_from_committee_and_transition_state( + spec, + state, + committee_indices, + rng, + lambda v: v.exit_epoch, + ) - exit_epoch = state.validators[exited_validator_index].exit_epoch - exit_slot = exit_epoch * spec.SLOTS_PER_EPOCH - transition_to(spec, state, exit_slot) - - exited_validator_indices = get_unslashed_exited_validators(spec, state) - assert exited_validator_index in exited_validator_indices - exited_pubkey = state.validators[exited_validator_index].pubkey - assert exited_pubkey in state.current_sync_committee.pubkeys current_epoch = spec.get_current_epoch(state) - assert current_epoch < state.validators[exited_validator_index].withdrawable_epoch + assert current_epoch < state.validators[exited_index].withdrawable_epoch block = build_empty_block_for_next_slot(spec, state) block.body.sync_aggregate = spec.SyncAggregate( @@ -467,22 +484,17 @@ def test_sync_committee_with_nonparticipating_exited_member(spec, state): committee_indices = compute_committee_indices(spec, state) rng = random.Random(1010) - exited_validator_index = rng.sample(committee_indices, 1)[0] - exits = prepare_signed_exits(spec, state, [exited_validator_index]) - assert len(exits) == 1 - voluntary_exit = exits.pop() - spec.process_voluntary_exit(state, voluntary_exit) + exited_index = _exit_validator_from_committee_and_transition_state( + spec, + state, + committee_indices, + rng, + lambda v: v.exit_epoch, + ) + exited_pubkey = state.validators[exited_index].pubkey - exit_epoch = state.validators[exited_validator_index].exit_epoch - exit_slot = exit_epoch * spec.SLOTS_PER_EPOCH - transition_to(spec, state, exit_slot) - - exited_validator_indices = get_unslashed_exited_validators(spec, state) - assert exited_validator_index in exited_validator_indices - exited_pubkey = state.validators[exited_validator_index].pubkey - assert exited_pubkey in state.current_sync_committee.pubkeys current_epoch = spec.get_current_epoch(state) - assert current_epoch < state.validators[exited_validator_index].withdrawable_epoch + assert current_epoch < state.validators[exited_index].withdrawable_epoch exited_committee_index = state.current_sync_committee.pubkeys.index(exited_pubkey) block = build_empty_block_for_next_slot(spec, state) @@ -514,24 +526,16 @@ def test_sync_committee_with_participating_withdrawable_member(spec, state): committee_indices = compute_committee_indices(spec, state) rng = random.Random(1010) - exited_validator_index = rng.sample(committee_indices, 1)[0] - exits = prepare_signed_exits(spec, state, [exited_validator_index]) - assert len(exits) == 1 - voluntary_exit = exits.pop() - spec.process_voluntary_exit(state, voluntary_exit) + exited_index = _exit_validator_from_committee_and_transition_state( + spec, + state, + committee_indices, + rng, + lambda v: v.withdrawable_epoch + 1, + ) - target_validator = state.validators[exited_validator_index] - target_validator.withdrawable_epoch = target_validator.exit_epoch + 1 - - target_slot = (target_validator.withdrawable_epoch + 1) * spec.SLOTS_PER_EPOCH - transition_to(spec, state, target_slot) - - exited_validator_indices = get_unslashed_exited_validators(spec, state) - assert exited_validator_index in exited_validator_indices - exited_pubkey = state.validators[exited_validator_index].pubkey - assert exited_pubkey in state.current_sync_committee.pubkeys current_epoch = spec.get_current_epoch(state) - assert current_epoch > state.validators[exited_validator_index].withdrawable_epoch + assert current_epoch > state.validators[exited_index].withdrawable_epoch block = build_empty_block_for_next_slot(spec, state) block.body.sync_aggregate = spec.SyncAggregate( @@ -560,24 +564,17 @@ def test_sync_committee_with_nonparticipating_withdrawable_member(spec, state): committee_indices = compute_committee_indices(spec, state) rng = random.Random(1010) - exited_validator_index = rng.sample(committee_indices, 1)[0] - exits = prepare_signed_exits(spec, state, [exited_validator_index]) - assert len(exits) == 1 - voluntary_exit = exits.pop() - spec.process_voluntary_exit(state, voluntary_exit) + exited_index = _exit_validator_from_committee_and_transition_state( + spec, + state, + committee_indices, + rng, + lambda v: v.withdrawable_epoch + 1, + ) + exited_pubkey = state.validators[exited_index].pubkey - target_validator = state.validators[exited_validator_index] - target_validator.withdrawable_epoch = target_validator.exit_epoch + 1 - - target_slot = (target_validator.withdrawable_epoch + 1) * spec.SLOTS_PER_EPOCH - transition_to(spec, state, target_slot) - - exited_validator_indices = get_unslashed_exited_validators(spec, state) - assert exited_validator_index in exited_validator_indices - exited_pubkey = state.validators[exited_validator_index].pubkey - assert exited_pubkey in state.current_sync_committee.pubkeys current_epoch = spec.get_current_epoch(state) - assert current_epoch > state.validators[exited_validator_index].withdrawable_epoch + assert current_epoch > state.validators[exited_index].withdrawable_epoch target_committee_index = state.current_sync_committee.pubkeys.index(exited_pubkey) block = build_empty_block_for_next_slot(spec, state) From 8f064d104f656139d1a423d9e1e6c96e0c1a30f6 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 9 Sep 2021 17:11:27 -0600 Subject: [PATCH 48/53] bmp version.txt to 1.1.0-beta-4 --- tests/core/pyspec/eth2spec/VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/VERSION.txt b/tests/core/pyspec/eth2spec/VERSION.txt index a0e0952ff..3bec004b5 100644 --- a/tests/core/pyspec/eth2spec/VERSION.txt +++ b/tests/core/pyspec/eth2spec/VERSION.txt @@ -1 +1 @@ -1.1.0-beta.3 \ No newline at end of file +1.1.0-beta.4 \ No newline at end of file From d6b5cbd94c39aafa6199e776a6f672b5c94b113f Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 9 Sep 2021 19:53:30 -0600 Subject: [PATCH 49/53] fix sync agg test for mainnet --- .../test_process_sync_aggregate.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py index 474380acd..8a2deabb1 100644 --- a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py +++ b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py @@ -41,6 +41,7 @@ def test_invalid_signature_bad_domain(spec, state): state, block.slot - 1, committee_indices, # full committee signs + block_root=block.parent_root, domain_type=spec.DOMAIN_BEACON_ATTESTER, # Incorrect domain ) ) @@ -64,6 +65,7 @@ def test_invalid_signature_missing_participant(spec, state): state, block.slot - 1, committee_indices, # full committee signs + block_root=block.parent_root, ) ) yield from run_sync_committee_processing(spec, state, block, expect_exception=True) @@ -127,6 +129,7 @@ def test_invalid_signature_extra_participant(spec, state): state, block.slot - 1, [index for index in committee_indices if index != random_participant], + block_root=block.parent_root, ) ) @@ -236,6 +239,7 @@ def test_invalid_signature_past_block(spec, state): state, block.slot - 1, committee_indices, + block_root=block.parent_root, ) ) @@ -286,6 +290,7 @@ def test_invalid_signature_previous_committee(spec, state): state, block.slot - 1, committee_indices, + block_root=block.parent_root, ) ) @@ -327,6 +332,7 @@ def test_valid_signature_future_committee(spec, state): state, block.slot - 1, committee_indices, + block_root=block.parent_root, ) ) @@ -360,6 +366,7 @@ def test_proposer_in_committee_without_participation(spec, state): state, block.slot - 1, participants, + block_root=block.parent_root, ) ) @@ -396,6 +403,7 @@ def test_proposer_in_committee_with_participation(spec, state): state, block.slot - 1, committee_indices, + block_root=block.parent_root, ) ) @@ -465,6 +473,7 @@ def test_sync_committee_with_participating_exited_member(spec, state): state, block.slot - 1, committee_indices, # full committee signs + block_root=block.parent_root, ) ) yield from run_sync_committee_processing(spec, state, block) @@ -499,7 +508,7 @@ def test_sync_committee_with_nonparticipating_exited_member(spec, state): exited_committee_index = state.current_sync_committee.pubkeys.index(exited_pubkey) block = build_empty_block_for_next_slot(spec, state) committee_bits = [i != exited_committee_index for i in committee_indices] - committee_indices.remove(exited_committee_index) + committee_indices = [index for index in committee_indices if index != exited_committee_index] block.body.sync_aggregate = spec.SyncAggregate( sync_committee_bits=committee_bits, sync_committee_signature=compute_aggregate_sync_committee_signature( @@ -507,6 +516,7 @@ def test_sync_committee_with_nonparticipating_exited_member(spec, state): state, block.slot - 1, committee_indices, # with exited validator removed + block_root=block.parent_root, ) ) yield from run_sync_committee_processing(spec, state, block) @@ -545,6 +555,7 @@ def test_sync_committee_with_participating_withdrawable_member(spec, state): state, block.slot - 1, committee_indices, # full committee signs + block_root=block.parent_root, ) ) yield from run_sync_committee_processing(spec, state, block) @@ -579,7 +590,7 @@ def test_sync_committee_with_nonparticipating_withdrawable_member(spec, state): target_committee_index = state.current_sync_committee.pubkeys.index(exited_pubkey) block = build_empty_block_for_next_slot(spec, state) committee_bits = [i != target_committee_index for i in committee_indices] - committee_indices.remove(target_committee_index) + committee_indices = [index for index in committee_indices if index != target_committee_index] block.body.sync_aggregate = spec.SyncAggregate( sync_committee_bits=committee_bits, sync_committee_signature=compute_aggregate_sync_committee_signature( @@ -587,6 +598,7 @@ def test_sync_committee_with_nonparticipating_withdrawable_member(spec, state): state, block.slot - 1, committee_indices, # with withdrawable validator removed + block_root=block.parent_root, ) ) yield from run_sync_committee_processing(spec, state, block) From 960a49afc9629d96f853242f5fe4a268e0ddeeb3 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Fri, 10 Sep 2021 16:07:26 +0600 Subject: [PATCH 50/53] Verify terminal PoW block after call to state_transition --- setup.py | 3 +-- specs/merge/fork-choice.md | 21 +++++++++------------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/setup.py b/setup.py index 281e292dc..6e55cfdde 100644 --- a/setup.py +++ b/setup.py @@ -509,8 +509,7 @@ ExecutionState = Any def get_pow_block(hash: Bytes32) -> PowBlock: - return PowBlock(block_hash=hash, parent_hash=Bytes32(), is_valid=True, is_processed=True, - total_difficulty=uint256(0), difficulty=uint256(0)) + return PowBlock(block_hash=hash, parent_hash=Bytes32(), total_difficulty=uint256(0), difficulty=uint256(0)) def get_execution_state(execution_state_root: Bytes32) -> ExecutionState: diff --git a/specs/merge/fork-choice.md b/specs/merge/fork-choice.md index de82b17fa..84be0f1ce 100644 --- a/specs/merge/fork-choice.md +++ b/specs/merge/fork-choice.md @@ -83,8 +83,6 @@ class TransitionStore(object): class PowBlock(object): block_hash: Hash32 parent_hash: Hash32 - is_processed: boolean - is_valid: boolean total_difficulty: uint256 difficulty: uint256 ``` @@ -93,7 +91,7 @@ class PowBlock(object): Let `get_pow_block(block_hash: Hash32) -> PowBlock` be the function that given the hash of the PoW block returns its data. -*Note*: The `eth_getBlockByHash` JSON-RPC method does not distinguish invalid blocks from blocks that haven't been processed yet. Either extending this existing method or implementing a new one is required. +*Note*: The `eth_getBlockByHash` JSON-RPC method may be used to pull this information from an execution client. ### `is_valid_terminal_pow_block` @@ -103,7 +101,7 @@ Used by fork-choice handler, `on_block`. def is_valid_terminal_pow_block(transition_store: TransitionStore, block: PowBlock, parent: PowBlock) -> bool: is_total_difficulty_reached = block.total_difficulty >= transition_store.terminal_total_difficulty is_parent_total_difficulty_valid = parent.total_difficulty < transition_store.terminal_total_difficulty - return block.is_valid and is_total_difficulty_reached and is_parent_total_difficulty_valid + return is_total_difficulty_reached and is_parent_total_difficulty_valid ``` ## Updated fork-choice handlers @@ -128,17 +126,16 @@ def on_block(store: Store, signed_block: SignedBeaconBlock, transition_store: Tr # Check block is a descendant of the finalized block at the checkpoint finalized slot assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root - # [New in Merge] - if (transition_store is not None) and is_merge_block(pre_state, block.body): - # Delay consideration of block until PoW block is processed by the PoW node - pow_block = get_pow_block(block.body.execution_payload.parent_hash) - pow_parent = get_pow_block(pow_block.parent_hash) - assert pow_block.is_processed - assert is_valid_terminal_pow_block(transition_store, pow_block, pow_parent) - # Check the block is valid and compute the post-state state = pre_state.copy() state_transition(state, signed_block, True) + + # [New in Merge] + if (transition_store is not None) and is_merge_block(pre_state, block.body): + pow_block = get_pow_block(block.body.execution_payload.parent_hash) + pow_parent = get_pow_block(pow_block.parent_hash) + assert is_valid_terminal_pow_block(transition_store, pow_block, pow_parent) + # Add new block to the store store.blocks[hash_tree_root(block)] = block # Add new state for this block to the store From 559ca86e2a6eefa1dc4aa4e48b2deede55f5d268 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Fri, 10 Sep 2021 16:56:27 +0200 Subject: [PATCH 51/53] "is build" -> "is built" typo Corrects a typo in a Makefile comment. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9f32b4568..977141078 100644 --- a/Makefile +++ b/Makefile @@ -173,7 +173,7 @@ define run_generator echo "generator $(1) finished" endef -# The tests dir itself is simply build by creating the directory (recursively creating deeper directories if necessary) +# The tests dir itself is simply built by creating the directory (recursively creating deeper directories if necessary) $(TEST_VECTOR_DIR): $(info creating test output directory, for generators: ${GENERATOR_TARGETS}) mkdir -p $@ From 899bde08734c3ebe72340226cab808fb563c4f84 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 13 Sep 2021 06:31:11 -0700 Subject: [PATCH 52/53] Rename client_settings.md to client-settings.md --- specs/merge/{client_settings.md => client-settings.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename specs/merge/{client_settings.md => client-settings.md} (100%) diff --git a/specs/merge/client_settings.md b/specs/merge/client-settings.md similarity index 100% rename from specs/merge/client_settings.md rename to specs/merge/client-settings.md From 370b9e86e31e966a7f105e1b32018d3602d0dec4 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Wed, 15 Sep 2021 21:30:16 +0200 Subject: [PATCH 53/53] pass sync committee sig consistently in tests There are three defined unit tests for the light client sync protocol. They all follow a similar structure. However, there is an inconcistency how they pass the slot to compute_aggregate_sync_committee_signature. In one instance it is passed as `block.slot`. In the other two cases it is passed as `block_header.slot`. As the `block_header` is created from the `block`, they share the same value. This patch makes the way how the slot is passed consistent across all of the test cases. --- .../pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py b/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py index a3cf8b7ca..c69957de5 100644 --- a/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py +++ b/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py @@ -52,7 +52,7 @@ def test_process_light_client_update_not_updated(spec, state): sync_committee_signature = compute_aggregate_sync_committee_signature( spec, state, - block.slot, + block_header.slot, committee, ) next_sync_committee_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_INDEX))]