From 42eae81013074ee191eaf0429e5575a61c714f60 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 15 Jun 2021 21:52:25 +0800 Subject: [PATCH 001/135] WIP. Rework on_block tests --- .../eth2spec/test/helpers/fork_choice.py | 7 +- .../test/phase0/fork_choice/test_on_block.py | 339 ++++++++++++++++++ tests/formats/fork_choice/README.md | 20 +- tests/generators/fork_choice/main.py | 3 +- 4 files changed, 363 insertions(+), 6 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index f6b007894..48248089b 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -74,17 +74,20 @@ def on_tick_and_append_step(spec, store, time, test_steps): def run_on_block(spec, store, signed_block, test_steps, valid=True): + yield get_block_file_name(signed_block), signed_block if not valid: try: spec.on_block(store, signed_block) - except AssertionError: + test_steps.append({ + 'block': get_block_file_name(signed_block), + 'valid': True, + }) return else: assert False spec.on_block(store, signed_block) - yield get_block_file_name(signed_block), signed_block test_steps.append({'block': get_block_file_name(signed_block)}) # An on_block step implies receiving block's attestations 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 new file mode 100644 index 000000000..e33c32e58 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py @@ -0,0 +1,339 @@ +from eth2spec.utils.ssz.ssz_impl import hash_tree_root + +from eth2spec.test.context import MINIMAL, spec_state_test, with_all_phases, with_presets +from eth2spec.test.helpers.attestations import next_epoch_with_attestations +from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_empty_block +from eth2spec.test.helpers.fork_choice import ( + get_genesis_forkchoice_store_and_block, + on_tick_and_append_step, + run_on_block, + tick_and_run_on_block, +) +from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_block + + +def apply_next_epoch_with_attestations(spec, state, store, test_steps=None): + if test_steps is None: + test_steps = [] + + _, new_signed_blocks, post_state = next_epoch_with_attestations(spec, state, True, False) + for signed_block in new_signed_blocks: + block = signed_block.message + block_root = hash_tree_root(block) + store.blocks[block_root] = block + store.block_states[block_root] = post_state + yield from tick_and_run_on_block(spec, store, signed_block, test_steps) + last_signed_block = signed_block + + return post_state, store, last_signed_block + + +@with_all_phases +@spec_state_test +def test_basic(spec, state): + # Initialization + test_steps = [] + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving a block of `GENESIS_SLOT + 1` slot + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_run_on_block(spec, store, signed_block, test_steps) + assert spec.get_head(store) == signed_block.message.hash_tree_root() + + # On receiving a block of next epoch + store.time = current_time + spec.config.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH + block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH) + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_run_on_block(spec, store, signed_block, test_steps) + assert spec.get_head(store) == signed_block.message.hash_tree_root() + + yield 'steps', test_steps + + # TODO: add tests for justified_root and finalized_root + + +@with_all_phases +@with_presets([MINIMAL], reason="too slow") +@spec_state_test +def test_on_block_checkpoints(spec, state): + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # Run for 1 epoch with full attestations + next_epoch(spec, state) + on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) + state, store, last_signed_block = yield from apply_next_epoch_with_attestations(spec, state, store, test_steps) + last_block_root = hash_tree_root(last_signed_block.message) + assert spec.get_head(store) == last_block_root + + # Forward 1 epoch + next_epoch(spec, state) + on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) + + # Mock the finalized_checkpoint and build a block on it + fin_state = store.block_states[last_block_root] + fin_state.finalized_checkpoint = ( + store.block_states[last_block_root].current_justified_checkpoint + ) + + block = build_empty_block_for_next_slot(spec, fin_state) + signed_block = state_transition_and_sign_block(spec, fin_state.copy(), block) + yield from tick_and_run_on_block(spec, store, signed_block, test_steps) + assert spec.get_head(store) == signed_block.message.hash_tree_root() + yield 'steps', test_steps + + +@with_all_phases +@spec_state_test +def test_on_block_future_block(spec, state): + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # do not tick time + + # Fail receiving block of `GENESIS_SLOT + 1` slot + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + run_on_block(spec, store, signed_block, test_steps, valid=False) + + yield 'steps', test_steps + + +# @with_all_phases +# @spec_state_test +# def test_on_block_bad_parent_root(spec, state): +# test_steps = [] +# # Initialization +# store = get_genesis_forkchoice_store(spec, state) +# time = 100 +# on_tick_and_append_step(spec, store, time, test_steps) + +# # Fail receiving block of `GENESIS_SLOT + 1` slot +# block = build_empty_block_for_next_slot(spec, state) +# transition_unsigned_block(spec, state, block) +# block.state_root = state.hash_tree_root() + +# block.parent_root = b'\x45' * 32 + +# signed_block = sign_block(spec, state, block) + +# run_on_block(spec, store, signed_block, test_steps, valid=False) + + +# @with_all_phases +# @spec_state_test +# def test_on_block_before_finalized(spec, state): +# test_steps = [] +# # Initialization +# store = get_genesis_forkchoice_store(spec, state) +# time = 100 +# on_tick_and_append_step(spec, store, time, test_steps) + +# store.finalized_checkpoint = spec.Checkpoint( +# epoch=store.finalized_checkpoint.epoch + 2, +# root=store.finalized_checkpoint.root +# ) + +# # Fail receiving block of `GENESIS_SLOT + 1` slot +# block = build_empty_block_for_next_slot(spec, state) +# signed_block = state_transition_and_sign_block(spec, state, block) +# run_on_block(spec, store, signed_block, test_steps, valid=False) + + +# @with_all_phases +# @spec_state_test +# def test_on_block_finalized_skip_slots(spec, state): +# test_steps = [] +# # Initialization +# store = get_genesis_forkchoice_store(spec, state) +# time = 100 +# on_tick_and_append_step(spec, store, time, test_steps) + +# store.finalized_checkpoint = spec.Checkpoint( +# epoch=store.finalized_checkpoint.epoch + 2, +# root=store.finalized_checkpoint.root +# ) + +# # Build block that includes the skipped slots up to finality in chain +# block = build_empty_block(spec, state, spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + 2) +# signed_block = state_transition_and_sign_block(spec, state, block) +# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) +# run_on_block(spec, store, signed_block, test_steps) + + +# @with_all_phases +# @spec_state_test +# def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): +# 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.finalized_checkpoint = spec.Checkpoint( +# epoch=store.finalized_checkpoint.epoch + 2, +# root=store.finalized_checkpoint.root +# ) + +# # First transition through the epoch to ensure no skipped slots +# state, store, _ = apply_next_epoch_with_attestations(spec, state, store) + +# # Now build a block at later slot than finalized epoch +# # Includes finalized block in chain, but not at appropriate skip slot +# block = build_empty_block(spec, state, spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + 2) +# signed_block = state_transition_and_sign_block(spec, state, block) +# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) +# run_on_block(spec, store, signed_block, test_steps, valid=False) + + +# @with_all_phases +# @spec_state_test +# def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): +# test_steps = [] +# # Initialization +# store = get_genesis_forkchoice_store(spec, state) +# time = 0 +# on_tick_and_append_step(spec, store, time, test_steps) + +# next_epoch(spec, state) +# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) +# state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store) +# next_epoch(spec, state) +# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) +# last_block_root = hash_tree_root(last_signed_block.message) + +# # Mock the justified checkpoint +# just_state = store.block_states[last_block_root] +# new_justified = spec.Checkpoint( +# epoch=just_state.current_justified_checkpoint.epoch + 1, +# root=b'\x77' * 32, +# ) +# just_state.current_justified_checkpoint = new_justified + +# block = build_empty_block_for_next_slot(spec, just_state) +# signed_block = state_transition_and_sign_block(spec, deepcopy(just_state), block) +# assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH < spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED +# run_on_block(spec, store, signed_block, test_steps) + +# assert store.justified_checkpoint == new_justified + + +# @with_all_phases +# @spec_state_test +# def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): +# test_steps = [] +# # Initialization +# store = get_genesis_forkchoice_store(spec, state) +# time = 0 +# on_tick_and_append_step(spec, store, time, test_steps) + +# next_epoch(spec, state) +# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) +# state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store) +# next_epoch(spec, state) +# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) +# last_block_root = hash_tree_root(last_signed_block.message) + +# # Mock justified block in store +# just_block = build_empty_block_for_next_slot(spec, state) +# # Slot is same as justified checkpoint so does not trigger an override in the store +# just_block.slot = spec.compute_start_slot_at_epoch(store.justified_checkpoint.epoch) +# store.blocks[just_block.hash_tree_root()] = just_block + +# # Step time past safe slots +# time = store.time + spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED * spec.config.SECONDS_PER_SLOT +# on_tick_and_append_step(spec, store, time, test_steps) +# assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + +# previously_justified = store.justified_checkpoint + +# # Add a series of new blocks with "better" justifications +# best_justified_checkpoint = spec.Checkpoint(epoch=0) +# for i in range(3, 0, -1): +# just_state = store.block_states[last_block_root] +# new_justified = spec.Checkpoint( +# epoch=previously_justified.epoch + i, +# root=just_block.hash_tree_root(), +# ) +# if new_justified.epoch > best_justified_checkpoint.epoch: +# best_justified_checkpoint = new_justified + +# just_state.current_justified_checkpoint = new_justified + +# block = build_empty_block_for_next_slot(spec, just_state) +# signed_block = state_transition_and_sign_block(spec, deepcopy(just_state), block) + +# run_on_block(spec, store, signed_block, test_steps) + +# assert store.justified_checkpoint == previously_justified +# # ensure the best from the series was stored +# assert store.best_justified_checkpoint == best_justified_checkpoint + + +# @with_all_phases +# @spec_state_test +# def test_on_block_outside_safe_slots_but_finality(spec, state): +# test_steps = [] +# # Initialization +# store = get_genesis_forkchoice_store(spec, state) +# time = 100 +# on_tick_and_append_step(spec, store, time, test_steps) + +# next_epoch(spec, state) +# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) +# state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store) +# next_epoch(spec, state) +# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) +# last_block_root = hash_tree_root(last_signed_block.message) + +# # Mock justified block in store +# just_block = build_empty_block_for_next_slot(spec, state) +# # Slot is same as justified checkpoint so does not trigger an override in the store +# just_block.slot = spec.compute_start_slot_at_epoch(store.justified_checkpoint.epoch) +# store.blocks[just_block.hash_tree_root()] = just_block + +# # Step time past safe slots +# time = store.time + spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED * spec.config.SECONDS_PER_SLOT +# on_tick_and_append_step(spec, store, time, test_steps) +# assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + +# # Mock justified and finalized update in state +# just_fin_state = store.block_states[last_block_root] +# new_justified = spec.Checkpoint( +# epoch=store.justified_checkpoint.epoch + 1, +# root=just_block.hash_tree_root(), +# ) +# new_finalized = spec.Checkpoint( +# epoch=store.finalized_checkpoint.epoch + 1, +# root=just_block.parent_root, +# ) +# just_fin_state.current_justified_checkpoint = new_justified +# just_fin_state.finalized_checkpoint = new_finalized + +# # Build and add block that includes the new justified/finalized info +# block = build_empty_block_for_next_slot(spec, just_fin_state) +# signed_block = state_transition_and_sign_block(spec, deepcopy(just_fin_state), block) + +# run_on_block(spec, store, signed_block, test_steps) + +# assert store.finalized_checkpoint == new_finalized +# assert store.justified_checkpoint == new_justified diff --git a/tests/formats/fork_choice/README.md b/tests/formats/fork_choice/README.md index 832ce9dd1..199b93784 100644 --- a/tests/formats/fork_choice/README.md +++ b/tests/formats/fork_choice/README.md @@ -28,7 +28,11 @@ The steps to execute in sequence. There may be multiple items of the following t The parameter that is required for executing `on_tick(store, time)`. ```yaml -{ tick: int } -- to execute `on_tick(store, time)` +{ + tick: int -- to execute `on_tick(store, time)`. + valid: bool -- optional, default to `True`. + If it's `False`, this execution step is expected to be invalid. +} ``` After this step, the `store` object may have been updated. @@ -38,7 +42,12 @@ After this step, the `store` object may have been updated. The parameter that is required for executing `on_attestation(store, attestation)`. ```yaml -{ attestation: string } -- the name of the `attestation_<32-byte-root>.ssz_snappy` file. To execute `on_attestation(store, attestation)` with the given attestation. +{ + attestation: string -- the name of the `attestation_<32-byte-root>.ssz_snappy` file. + To execute `on_attestation(store, attestation)` with the given attestation. + valid: bool -- optional, default to `True`. + If it's `False`, this execution step is expected to be invalid. +} ``` The file is located in the same folder (see below). @@ -49,7 +58,12 @@ After this step, the `store` object may have been updated. The parameter that is required for executing `on_block(store, block)`. ```yaml -{ block: string } -- the name of the `block_<32-byte-root>.ssz_snappy` file. To execute `on_block(store, block)` with the given attestation. +{ + block: string -- the name of the `block_<32-byte-root>.ssz_snappy` file. + To execute `on_block(store, block)` with the given attestation. + valid: bool -- optional, default to `True`. + If it's `False`, this execution step is expected to be invalid. +} ``` The file is located in the same folder (see below). diff --git a/tests/generators/fork_choice/main.py b/tests/generators/fork_choice/main.py index f162d9564..9b6325ccf 100644 --- a/tests/generators/fork_choice/main.py +++ b/tests/generators/fork_choice/main.py @@ -4,7 +4,8 @@ from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE if __name__ == "__main__": phase_0_mods = {key: 'eth2spec.test.phase0.fork_choice.test_' + key for key in [ - 'get_head', + # 'get_head', + 'on_block', ]} # No additional Altair specific finality tests, yet. altair_mods = phase_0_mods From fb2465db45161d468aa5ffd6d8cbee5f664fc36b Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 18 Jun 2021 17:39:46 +0800 Subject: [PATCH 002/135] Rework `on_block` unit tests --- .../eth2spec/test/helpers/fork_choice.py | 55 +++- .../test/phase0/fork_choice/test_get_head.py | 20 +- .../test/phase0/fork_choice/test_on_block.py | 282 +++--------------- .../unittests/fork_choice/test_on_block.py | 205 +++++-------- tests/generators/fork_choice/main.py | 2 +- 5 files changed, 164 insertions(+), 400 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index 48248089b..bac3d1ff5 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -1,4 +1,5 @@ from eth_utils import encode_hex +from eth2spec.test.helpers.attestations import next_epoch_with_attestations def get_anchor_root(spec, state): @@ -18,7 +19,7 @@ def add_block_to_store(spec, store, signed_block): spec.on_block(store, signed_block) -def tick_and_run_on_block(spec, store, signed_block, test_steps=None): +def tick_and_add_block(spec, store, signed_block, test_steps=None, valid=True): if test_steps is None: test_steps = [] @@ -28,7 +29,7 @@ def tick_and_run_on_block(spec, store, signed_block, test_steps=None): if store.time < block_time: on_tick_and_append_step(spec, store, block_time, test_steps) - yield from run_on_block(spec, store, signed_block, test_steps) + yield from add_block(spec, store, signed_block, test_steps, valid=valid) def tick_and_run_on_attestation(spec, store, attestation, test_steps=None): @@ -73,28 +74,47 @@ def on_tick_and_append_step(spec, store, time, test_steps): test_steps.append({'tick': int(time)}) -def run_on_block(spec, store, signed_block, test_steps, valid=True): - yield get_block_file_name(signed_block), signed_block +def run_on_block(spec, store, signed_block, valid=True): if not valid: try: spec.on_block(store, signed_block) except AssertionError: - test_steps.append({ - 'block': get_block_file_name(signed_block), - 'valid': True, - }) return else: assert False spec.on_block(store, signed_block) + assert store.blocks[signed_block.message.hash_tree_root()] == signed_block.message + + +def add_block(spec, store, signed_block, test_steps=None, valid=True): + if test_steps is None: + test_steps = [] + + yield get_block_file_name(signed_block), signed_block + + if not valid: + try: + run_on_block(spec, store, signed_block, valid=True) + except AssertionError: + test_steps.append({ + 'block': get_block_file_name(signed_block), + 'valid': False, + }) + return + else: + assert False + + run_on_block(spec, store, signed_block, valid=True) test_steps.append({'block': get_block_file_name(signed_block)}) # An on_block step implies receiving block's attestations for attestation in signed_block.message.body.attestations: spec.on_attestation(store, attestation) - assert store.blocks[signed_block.message.hash_tree_root()] == signed_block.message + block_root = signed_block.message.hash_tree_root() + assert store.blocks[block_root] == signed_block.message + assert store.block_states[block_root].hash_tree_root() == signed_block.message.state_root test_steps.append({ 'checks': { 'time': int(store.time), @@ -113,3 +133,20 @@ def get_formatted_head_output(spec, store): 'slot': int(slot), 'root': encode_hex(head), } + + +def apply_next_epoch_with_attestations(spec, state, store, test_steps=None): + if test_steps is None: + test_steps = [] + + _, new_signed_blocks, post_state = next_epoch_with_attestations(spec, state, True, False) + for signed_block in new_signed_blocks: + block = signed_block.message + yield from tick_and_add_block(spec, store, signed_block, test_steps) + block_root = block.hash_tree_root() + assert store.blocks[block_root] == block + last_signed_block = signed_block + + assert store.block_states[block_root].hash_tree_root() == post_state.hash_tree_root() + + return post_state, store, last_signed_block diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py index bce886931..12b261e4e 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py @@ -11,12 +11,12 @@ from eth2spec.test.helpers.block import build_empty_block_for_next_slot from eth2spec.test.helpers.constants import MINIMAL from eth2spec.test.helpers.fork_choice import ( tick_and_run_on_attestation, - tick_and_run_on_block, + tick_and_add_block, get_anchor_root, get_genesis_forkchoice_store_and_block, get_formatted_head_output, on_tick_and_append_step, - run_on_block, + add_block, ) from eth2spec.test.helpers.state import ( next_epoch, @@ -68,12 +68,12 @@ def test_chain_no_attestations(spec, state): # On receiving a block of `GENESIS_SLOT + 1` slot block_1 = build_empty_block_for_next_slot(spec, state) signed_block_1 = state_transition_and_sign_block(spec, state, block_1) - yield from tick_and_run_on_block(spec, store, signed_block_1, test_steps) + yield from tick_and_add_block(spec, store, signed_block_1, test_steps) # On receiving a block of next epoch block_2 = build_empty_block_for_next_slot(spec, state) signed_block_2 = state_transition_and_sign_block(spec, state, block_2) - yield from tick_and_run_on_block(spec, store, signed_block_2, test_steps) + yield from tick_and_add_block(spec, store, signed_block_2, test_steps) assert spec.get_head(store) == spec.hash_tree_root(block_2) test_steps.append({ @@ -107,14 +107,14 @@ def test_split_tie_breaker_no_attestations(spec, state): block_1_state = genesis_state.copy() block_1 = build_empty_block_for_next_slot(spec, block_1_state) signed_block_1 = state_transition_and_sign_block(spec, block_1_state, block_1) - yield from tick_and_run_on_block(spec, store, signed_block_1, test_steps) + yield from tick_and_add_block(spec, store, signed_block_1, test_steps) # additional block at slot 1 block_2_state = genesis_state.copy() block_2 = build_empty_block_for_next_slot(spec, block_2_state) block_2.body.graffiti = b'\x42' * 32 signed_block_2 = state_transition_and_sign_block(spec, block_2_state, block_2) - yield from tick_and_run_on_block(spec, store, signed_block_2, test_steps) + yield from tick_and_add_block(spec, store, signed_block_2, test_steps) highest_root = max(spec.hash_tree_root(block_1), spec.hash_tree_root(block_2)) assert spec.get_head(store) == highest_root @@ -150,14 +150,14 @@ def test_shorter_chain_but_heavier_weight(spec, state): for _ in range(3): long_block = build_empty_block_for_next_slot(spec, long_state) signed_long_block = state_transition_and_sign_block(spec, long_state, long_block) - yield from tick_and_run_on_block(spec, store, signed_long_block, test_steps) + yield from tick_and_add_block(spec, store, signed_long_block, test_steps) # build short tree short_state = genesis_state.copy() short_block = build_empty_block_for_next_slot(spec, short_state) short_block.body.graffiti = b'\x42' * 32 signed_short_block = state_transition_and_sign_block(spec, short_state, short_block) - yield from tick_and_run_on_block(spec, store, signed_short_block, test_steps) + yield from tick_and_add_block(spec, store, signed_short_block, test_steps) short_attestation = get_valid_attestation(spec, short_state, short_block.slot, signed=True) yield from tick_and_run_on_attestation(spec, store, short_attestation, test_steps) @@ -200,7 +200,7 @@ def test_filtered_block_tree(spec, state): current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) for signed_block in signed_blocks: - yield from run_on_block(spec, store, signed_block, test_steps) + yield from add_block(spec, store, signed_block, test_steps) assert store.justified_checkpoint == state.current_justified_checkpoint @@ -247,7 +247,7 @@ def test_filtered_block_tree(spec, state): on_tick_and_append_step(spec, store, current_time, test_steps) # include rogue block and associated attestations in the store - yield from run_on_block(spec, store, signed_rogue_block, test_steps) + yield from add_block(spec, store, signed_rogue_block, test_steps) for attestation in attestations: yield from tick_and_run_on_attestation(spec, store, attestation, test_steps) 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 e33c32e58..e9f2fae63 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 @@ -1,38 +1,30 @@ from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.test.context import MINIMAL, spec_state_test, with_all_phases, with_presets -from eth2spec.test.helpers.attestations import next_epoch_with_attestations -from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_empty_block +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, + build_empty_block, + transition_unsigned_block, + sign_block, +) from eth2spec.test.helpers.fork_choice import ( get_genesis_forkchoice_store_and_block, on_tick_and_append_step, - run_on_block, - tick_and_run_on_block, + add_block, + tick_and_add_block, + apply_next_epoch_with_attestations, +) +from eth2spec.test.helpers.state import ( + next_epoch, + state_transition_and_sign_block, ) -from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_block - - -def apply_next_epoch_with_attestations(spec, state, store, test_steps=None): - if test_steps is None: - test_steps = [] - - _, new_signed_blocks, post_state = next_epoch_with_attestations(spec, state, True, False) - for signed_block in new_signed_blocks: - block = signed_block.message - block_root = hash_tree_root(block) - store.blocks[block_root] = block - store.block_states[block_root] = post_state - yield from tick_and_run_on_block(spec, store, signed_block, test_steps) - last_signed_block = signed_block - - return post_state, store, last_signed_block @with_all_phases @spec_state_test def test_basic(spec, state): - # Initialization test_steps = [] + # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) yield 'anchor_state', state yield 'anchor_block', anchor_block @@ -43,14 +35,14 @@ def test_basic(spec, state): # On receiving a block of `GENESIS_SLOT + 1` slot block = build_empty_block_for_next_slot(spec, state) signed_block = state_transition_and_sign_block(spec, state, block) - yield from tick_and_run_on_block(spec, store, signed_block, test_steps) + yield from tick_and_add_block(spec, store, signed_block, test_steps) assert spec.get_head(store) == signed_block.message.hash_tree_root() # On receiving a block of next epoch store.time = current_time + spec.config.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH) signed_block = state_transition_and_sign_block(spec, state, block) - yield from tick_and_run_on_block(spec, store, signed_block, test_steps) + yield from tick_and_add_block(spec, store, signed_block, test_steps) assert spec.get_head(store) == signed_block.message.hash_tree_root() yield 'steps', test_steps @@ -74,6 +66,7 @@ def test_on_block_checkpoints(spec, state): # Run for 1 epoch with full attestations next_epoch(spec, state) on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) + state, store, last_signed_block = yield from apply_next_epoch_with_attestations(spec, state, store, test_steps) last_block_root = hash_tree_root(last_signed_block.message) assert spec.get_head(store) == last_block_root @@ -90,7 +83,7 @@ def test_on_block_checkpoints(spec, state): block = build_empty_block_for_next_slot(spec, fin_state) signed_block = state_transition_and_sign_block(spec, fin_state.copy(), block) - yield from tick_and_run_on_block(spec, store, signed_block, test_steps) + yield from tick_and_add_block(spec, store, signed_block, test_steps) assert spec.get_head(store) == signed_block.message.hash_tree_root() yield 'steps', test_steps @@ -107,233 +100,36 @@ def test_on_block_future_block(spec, state): on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time - # do not tick time - + # Do NOT tick time to `GENESIS_SLOT + 1` slot # Fail receiving block of `GENESIS_SLOT + 1` slot block = build_empty_block_for_next_slot(spec, state) signed_block = state_transition_and_sign_block(spec, state, block) - run_on_block(spec, store, signed_block, test_steps, valid=False) + yield from add_block(spec, store, signed_block, test_steps, valid=False) yield 'steps', test_steps -# @with_all_phases -# @spec_state_test -# def test_on_block_bad_parent_root(spec, state): -# test_steps = [] -# # Initialization -# store = get_genesis_forkchoice_store(spec, state) -# time = 100 -# on_tick_and_append_step(spec, store, time, test_steps) +@with_all_phases +@spec_state_test +def test_on_block_bad_parent_root(spec, state): + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time -# # Fail receiving block of `GENESIS_SLOT + 1` slot -# block = build_empty_block_for_next_slot(spec, state) -# transition_unsigned_block(spec, state, block) -# block.state_root = state.hash_tree_root() + # Fail receiving block of `GENESIS_SLOT + 1` slot + block = build_empty_block_for_next_slot(spec, state) + transition_unsigned_block(spec, state, block) + block.state_root = state.hash_tree_root() -# block.parent_root = b'\x45' * 32 + block.parent_root = b'\x45' * 32 -# signed_block = sign_block(spec, state, block) + signed_block = sign_block(spec, state, block) -# run_on_block(spec, store, signed_block, test_steps, valid=False) + yield from add_block(spec, store, signed_block, test_steps, valid=False) - -# @with_all_phases -# @spec_state_test -# def test_on_block_before_finalized(spec, state): -# test_steps = [] -# # Initialization -# store = get_genesis_forkchoice_store(spec, state) -# time = 100 -# on_tick_and_append_step(spec, store, time, test_steps) - -# store.finalized_checkpoint = spec.Checkpoint( -# epoch=store.finalized_checkpoint.epoch + 2, -# root=store.finalized_checkpoint.root -# ) - -# # Fail receiving block of `GENESIS_SLOT + 1` slot -# block = build_empty_block_for_next_slot(spec, state) -# signed_block = state_transition_and_sign_block(spec, state, block) -# run_on_block(spec, store, signed_block, test_steps, valid=False) - - -# @with_all_phases -# @spec_state_test -# def test_on_block_finalized_skip_slots(spec, state): -# test_steps = [] -# # Initialization -# store = get_genesis_forkchoice_store(spec, state) -# time = 100 -# on_tick_and_append_step(spec, store, time, test_steps) - -# store.finalized_checkpoint = spec.Checkpoint( -# epoch=store.finalized_checkpoint.epoch + 2, -# root=store.finalized_checkpoint.root -# ) - -# # Build block that includes the skipped slots up to finality in chain -# block = build_empty_block(spec, state, spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + 2) -# signed_block = state_transition_and_sign_block(spec, state, block) -# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) -# run_on_block(spec, store, signed_block, test_steps) - - -# @with_all_phases -# @spec_state_test -# def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): -# 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.finalized_checkpoint = spec.Checkpoint( -# epoch=store.finalized_checkpoint.epoch + 2, -# root=store.finalized_checkpoint.root -# ) - -# # First transition through the epoch to ensure no skipped slots -# state, store, _ = apply_next_epoch_with_attestations(spec, state, store) - -# # Now build a block at later slot than finalized epoch -# # Includes finalized block in chain, but not at appropriate skip slot -# block = build_empty_block(spec, state, spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + 2) -# signed_block = state_transition_and_sign_block(spec, state, block) -# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) -# run_on_block(spec, store, signed_block, test_steps, valid=False) - - -# @with_all_phases -# @spec_state_test -# def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): -# test_steps = [] -# # Initialization -# store = get_genesis_forkchoice_store(spec, state) -# time = 0 -# on_tick_and_append_step(spec, store, time, test_steps) - -# next_epoch(spec, state) -# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) -# state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store) -# next_epoch(spec, state) -# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) -# last_block_root = hash_tree_root(last_signed_block.message) - -# # Mock the justified checkpoint -# just_state = store.block_states[last_block_root] -# new_justified = spec.Checkpoint( -# epoch=just_state.current_justified_checkpoint.epoch + 1, -# root=b'\x77' * 32, -# ) -# just_state.current_justified_checkpoint = new_justified - -# block = build_empty_block_for_next_slot(spec, just_state) -# signed_block = state_transition_and_sign_block(spec, deepcopy(just_state), block) -# assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH < spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED -# run_on_block(spec, store, signed_block, test_steps) - -# assert store.justified_checkpoint == new_justified - - -# @with_all_phases -# @spec_state_test -# def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): -# test_steps = [] -# # Initialization -# store = get_genesis_forkchoice_store(spec, state) -# time = 0 -# on_tick_and_append_step(spec, store, time, test_steps) - -# next_epoch(spec, state) -# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) -# state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store) -# next_epoch(spec, state) -# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) -# last_block_root = hash_tree_root(last_signed_block.message) - -# # Mock justified block in store -# just_block = build_empty_block_for_next_slot(spec, state) -# # Slot is same as justified checkpoint so does not trigger an override in the store -# just_block.slot = spec.compute_start_slot_at_epoch(store.justified_checkpoint.epoch) -# store.blocks[just_block.hash_tree_root()] = just_block - -# # Step time past safe slots -# time = store.time + spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED * spec.config.SECONDS_PER_SLOT -# on_tick_and_append_step(spec, store, time, test_steps) -# assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED - -# previously_justified = store.justified_checkpoint - -# # Add a series of new blocks with "better" justifications -# best_justified_checkpoint = spec.Checkpoint(epoch=0) -# for i in range(3, 0, -1): -# just_state = store.block_states[last_block_root] -# new_justified = spec.Checkpoint( -# epoch=previously_justified.epoch + i, -# root=just_block.hash_tree_root(), -# ) -# if new_justified.epoch > best_justified_checkpoint.epoch: -# best_justified_checkpoint = new_justified - -# just_state.current_justified_checkpoint = new_justified - -# block = build_empty_block_for_next_slot(spec, just_state) -# signed_block = state_transition_and_sign_block(spec, deepcopy(just_state), block) - -# run_on_block(spec, store, signed_block, test_steps) - -# assert store.justified_checkpoint == previously_justified -# # ensure the best from the series was stored -# assert store.best_justified_checkpoint == best_justified_checkpoint - - -# @with_all_phases -# @spec_state_test -# def test_on_block_outside_safe_slots_but_finality(spec, state): -# test_steps = [] -# # Initialization -# store = get_genesis_forkchoice_store(spec, state) -# time = 100 -# on_tick_and_append_step(spec, store, time, test_steps) - -# next_epoch(spec, state) -# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) -# state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store) -# next_epoch(spec, state) -# on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) -# last_block_root = hash_tree_root(last_signed_block.message) - -# # Mock justified block in store -# just_block = build_empty_block_for_next_slot(spec, state) -# # Slot is same as justified checkpoint so does not trigger an override in the store -# just_block.slot = spec.compute_start_slot_at_epoch(store.justified_checkpoint.epoch) -# store.blocks[just_block.hash_tree_root()] = just_block - -# # Step time past safe slots -# time = store.time + spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED * spec.config.SECONDS_PER_SLOT -# on_tick_and_append_step(spec, store, time, test_steps) -# assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED - -# # Mock justified and finalized update in state -# just_fin_state = store.block_states[last_block_root] -# new_justified = spec.Checkpoint( -# epoch=store.justified_checkpoint.epoch + 1, -# root=just_block.hash_tree_root(), -# ) -# new_finalized = spec.Checkpoint( -# epoch=store.finalized_checkpoint.epoch + 1, -# root=just_block.parent_root, -# ) -# just_fin_state.current_justified_checkpoint = new_justified -# just_fin_state.finalized_checkpoint = new_finalized - -# # Build and add block that includes the new justified/finalized info -# block = build_empty_block_for_next_slot(spec, just_fin_state) -# signed_block = state_transition_and_sign_block(spec, deepcopy(just_fin_state), block) - -# run_on_block(spec, store, signed_block, test_steps) - -# assert store.finalized_checkpoint == new_finalized -# assert store.justified_checkpoint == new_justified + yield 'steps', test_steps diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py index b1862d093..ce2ab95f4 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py @@ -1,123 +1,17 @@ from copy import deepcopy from eth2spec.utils.ssz.ssz_impl import hash_tree_root -from eth2spec.test.context import with_all_phases, spec_state_test -from eth2spec.test.helpers.attestations import next_epoch_with_attestations -from eth2spec.test.helpers.block import build_empty_block_for_next_slot, sign_block, transition_unsigned_block, \ - build_empty_block -from eth2spec.test.helpers.fork_choice import get_genesis_forkchoice_store +from eth2spec.test.context import with_all_phases, spec_state_test, with_phases, PHASE0 +from eth2spec.test.helpers.block import build_empty_block_for_next_slot, transition_unsigned_block, \ + build_empty_block, sign_block +from eth2spec.test.helpers.fork_choice import ( + get_genesis_forkchoice_store, + run_on_block, + apply_next_epoch_with_attestations, +) from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_block, transition_to -def run_on_block(spec, store, signed_block, valid=True): - if not valid: - try: - spec.on_block(store, signed_block) - except AssertionError: - return - else: - assert False - - spec.on_block(store, signed_block) - assert store.blocks[hash_tree_root(signed_block.message)] == signed_block.message - - -def apply_next_epoch_with_attestations(spec, state, store): - _, new_signed_blocks, post_state = next_epoch_with_attestations(spec, state, True, False) - for signed_block in new_signed_blocks: - block = signed_block.message - block_root = hash_tree_root(block) - store.blocks[block_root] = block - store.block_states[block_root] = post_state - last_signed_block = signed_block - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) - return post_state, store, last_signed_block - - -@with_all_phases -@spec_state_test -def test_basic(spec, state): - # Initialization - store = get_genesis_forkchoice_store(spec, state) - time = 100 - spec.on_tick(store, time) - assert store.time == time - - # On receiving a block of `GENESIS_SLOT + 1` slot - block = build_empty_block_for_next_slot(spec, state) - signed_block = state_transition_and_sign_block(spec, state, block) - run_on_block(spec, store, signed_block) - - # On receiving a block of next epoch - store.time = time + spec.config.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH - block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH) - signed_block = state_transition_and_sign_block(spec, state, block) - - run_on_block(spec, store, signed_block) - - # TODO: add tests for justified_root and finalized_root - - -@with_all_phases -@spec_state_test -def test_on_block_checkpoints(spec, state): - # Initialization - store = get_genesis_forkchoice_store(spec, state) - time = 100 - spec.on_tick(store, time) - - next_epoch(spec, state) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) - state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store) - next_epoch(spec, state) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) - last_block_root = hash_tree_root(last_signed_block.message) - - # Mock the finalized_checkpoint - fin_state = store.block_states[last_block_root] - fin_state.finalized_checkpoint = ( - store.block_states[last_block_root].current_justified_checkpoint - ) - - block = build_empty_block_for_next_slot(spec, fin_state) - signed_block = state_transition_and_sign_block(spec, deepcopy(fin_state), block) - run_on_block(spec, store, signed_block) - - -@with_all_phases -@spec_state_test -def test_on_block_future_block(spec, state): - # Initialization - store = get_genesis_forkchoice_store(spec, state) - - # do not tick time - - # Fail receiving block of `GENESIS_SLOT + 1` slot - block = build_empty_block_for_next_slot(spec, state) - signed_block = state_transition_and_sign_block(spec, state, block) - run_on_block(spec, store, signed_block, False) - - -@with_all_phases -@spec_state_test -def test_on_block_bad_parent_root(spec, state): - # Initialization - store = get_genesis_forkchoice_store(spec, state) - time = 100 - spec.on_tick(store, time) - - # Fail receiving block of `GENESIS_SLOT + 1` slot - block = build_empty_block_for_next_slot(spec, state) - transition_unsigned_block(spec, state, block) - block.state_root = state.hash_tree_root() - - block.parent_root = b'\x45' * 32 - - signed_block = sign_block(spec, state, block) - - run_on_block(spec, store, signed_block, False) - - @with_all_phases @spec_state_test def test_on_block_before_finalized(spec, state): @@ -134,7 +28,7 @@ def test_on_block_before_finalized(spec, state): # Fail receiving block of `GENESIS_SLOT + 1` slot block = build_empty_block_for_next_slot(spec, state) signed_block = state_transition_and_sign_block(spec, state, block) - run_on_block(spec, store, signed_block, False) + run_on_block(spec, store, signed_block, valid=False) @with_all_phases @@ -166,23 +60,31 @@ def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): transition_unsigned_block(spec, state, block) block.state_root = state.hash_tree_root() store = spec.get_forkchoice_store(state, block) - store.finalized_checkpoint = spec.Checkpoint( - epoch=store.finalized_checkpoint.epoch + 2, - root=store.finalized_checkpoint.root - ) - # First transition through the epoch to ensure no skipped slots - state, store, _ = apply_next_epoch_with_attestations(spec, state, store) + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + spec.on_tick(store, current_time) + assert store.time == current_time + + pre_finalized_checkpoint_epoch = store.finalized_checkpoint.epoch + + # Finalized + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations(spec, state, store) + assert store.finalized_checkpoint.epoch == pre_finalized_checkpoint_epoch + 1 # Now build a block at later slot than finalized epoch # Includes finalized block in chain, but not at appropriate skip slot - block = build_empty_block(spec, state, spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + 2) - signed_block = state_transition_and_sign_block(spec, state, block) + pre_state = store.block_states[block.hash_tree_root()] + block = build_empty_block(spec, + state=pre_state, + slot=spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + 2) + signed_block = sign_block(spec, pre_state, block) + spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) - run_on_block(spec, store, signed_block, False) + run_on_block(spec, store, signed_block, valid=False) -@with_all_phases +@with_phases([PHASE0]) @spec_state_test def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): # Initialization @@ -192,21 +94,33 @@ def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): next_epoch(spec, state) spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) - state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store) + state, store, last_signed_block = yield from apply_next_epoch_with_attestations(spec, state, store) next_epoch(spec, state) spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) - last_block_root = hash_tree_root(last_signed_block.message) + last_block = last_signed_block.message + last_block_root = last_block.hash_tree_root() - # Mock the justified checkpoint + # NOTE: Mock the justified checkpoint just_state = store.block_states[last_block_root] new_justified = spec.Checkpoint( epoch=just_state.current_justified_checkpoint.epoch + 1, root=b'\x77' * 32, ) - just_state.current_justified_checkpoint = new_justified + just_state.current_justified_checkpoint = new_justified # Mutate `store` + + assert store.block_states[last_block_root].hash_tree_root() == just_state.hash_tree_root() block = build_empty_block_for_next_slot(spec, just_state) - signed_block = state_transition_and_sign_block(spec, deepcopy(just_state), block) + + # NOTE: Mock store so that the modified state could be accessed + parent_block = last_signed_block.message.copy() + parent_block.state_root = just_state.hash_tree_root() + store.blocks[block.parent_root] = parent_block + store.block_states[block.parent_root] = just_state.copy() + assert block.parent_root in store.blocks.keys() + assert block.parent_root in store.block_states.keys() + + signed_block = state_transition_and_sign_block(spec, just_state.copy(), block) assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH < spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED run_on_block(spec, store, signed_block) @@ -223,10 +137,10 @@ def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): next_epoch(spec, state) spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) - state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store) + state, store, last_signed_block = yield from apply_next_epoch_with_attestations(spec, state, store) last_block_root = hash_tree_root(last_signed_block.message) - # Mock fictitious justified checkpoint in store + # NOTE: Mock fictitious justified checkpoint in store store.justified_checkpoint = spec.Checkpoint( epoch=spec.compute_epoch_at_slot(last_signed_block.message.slot), root=spec.Root("0x4a55535449464945440000000000000000000000000000000000000000000000") @@ -248,6 +162,7 @@ def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): # Add a series of new blocks with "better" justifications best_justified_checkpoint = spec.Checkpoint(epoch=0) for i in range(3, 0, -1): + # Mutate store just_state = store.block_states[last_block_root] new_justified = spec.Checkpoint( epoch=previously_justified.epoch + i, @@ -261,6 +176,14 @@ def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): block = build_empty_block_for_next_slot(spec, just_state) signed_block = state_transition_and_sign_block(spec, deepcopy(just_state), block) + # NOTE: Mock store so that the modified state could be accessed + parent_block = store.blocks[last_block_root].copy() + parent_block.state_root = just_state.hash_tree_root() + store.blocks[block.parent_root] = parent_block + store.block_states[block.parent_root] = just_state.copy() + assert block.parent_root in store.blocks.keys() + assert block.parent_root in store.block_states.keys() + run_on_block(spec, store, signed_block) assert store.justified_checkpoint == previously_justified @@ -273,15 +196,15 @@ def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): def test_on_block_outside_safe_slots_but_finality(spec, state): # Initialization store = get_genesis_forkchoice_store(spec, state) - time = 100 + time = 0 spec.on_tick(store, time) next_epoch(spec, state) spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) - state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store) + state, store, last_signed_block = yield from apply_next_epoch_with_attestations(spec, state, store) last_block_root = hash_tree_root(last_signed_block.message) - # Mock fictitious justified checkpoint in store + # NOTE: Mock fictitious justified checkpoint in store store.justified_checkpoint = spec.Checkpoint( epoch=spec.compute_epoch_at_slot(last_signed_block.message.slot), root=spec.Root("0x4a55535449464945440000000000000000000000000000000000000000000000") @@ -290,7 +213,7 @@ def test_on_block_outside_safe_slots_but_finality(spec, state): next_epoch(spec, state) spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) - # Create new higher justified checkpoint not in branch of store's justified checkpoint + # NOTE: Mock a new higher justified checkpoint not in branch of store's justified checkpoint just_block = build_empty_block_for_next_slot(spec, state) store.blocks[just_block.hash_tree_root()] = just_block @@ -298,7 +221,7 @@ def test_on_block_outside_safe_slots_but_finality(spec, state): spec.on_tick(store, store.time + spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED * spec.config.SECONDS_PER_SLOT) assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED - # Mock justified and finalized update in state + # NOTE: Mock justified and finalized update in state just_fin_state = store.block_states[last_block_root] new_justified = spec.Checkpoint( epoch=spec.compute_epoch_at_slot(just_block.slot) + 1, @@ -317,6 +240,14 @@ def test_on_block_outside_safe_slots_but_finality(spec, state): block = build_empty_block_for_next_slot(spec, just_fin_state) signed_block = state_transition_and_sign_block(spec, deepcopy(just_fin_state), block) + # NOTE: Mock store so that the modified state could be accessed + parent_block = last_signed_block.message.copy() + parent_block.state_root = just_fin_state.hash_tree_root() + store.blocks[block.parent_root] = parent_block + store.block_states[block.parent_root] = just_fin_state.copy() + assert block.parent_root in store.blocks.keys() + assert block.parent_root in store.block_states.keys() + run_on_block(spec, store, signed_block) assert store.finalized_checkpoint == new_finalized diff --git a/tests/generators/fork_choice/main.py b/tests/generators/fork_choice/main.py index 9b6325ccf..0ebdbf2c0 100644 --- a/tests/generators/fork_choice/main.py +++ b/tests/generators/fork_choice/main.py @@ -4,7 +4,7 @@ from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE if __name__ == "__main__": phase_0_mods = {key: 'eth2spec.test.phase0.fork_choice.test_' + key for key in [ - # 'get_head', + 'get_head', 'on_block', ]} # No additional Altair specific finality tests, yet. From 2445fe5a7684fd2607129f3feea390b6d95cc264 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 18 Jun 2021 19:14:03 +0800 Subject: [PATCH 003/135] Add new test cases - `test_new_finalized_slot_is_not_justified_checkpoint_ancestor` - `test_new_finalized_slot_is_justified_checkpoint_ancestor` --- .../unittests/fork_choice/test_on_block.py | 141 +++++++++++++++++- 1 file changed, 139 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py index ce2ab95f4..195c53839 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py @@ -1,7 +1,8 @@ from copy import deepcopy from eth2spec.utils.ssz.ssz_impl import hash_tree_root -from eth2spec.test.context import with_all_phases, spec_state_test, with_phases, PHASE0 +from eth2spec.test.context import with_all_phases, spec_state_test +from eth2spec.test.helpers.attestations import next_epoch_with_attestations from eth2spec.test.helpers.block import build_empty_block_for_next_slot, transition_unsigned_block, \ build_empty_block, sign_block from eth2spec.test.helpers.fork_choice import ( @@ -84,7 +85,7 @@ def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): run_on_block(spec, store, signed_block, valid=False) -@with_phases([PHASE0]) +@with_all_phases @spec_state_test def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): # Initialization @@ -252,3 +253,139 @@ def test_on_block_outside_safe_slots_but_finality(spec, state): assert store.finalized_checkpoint == new_finalized assert store.justified_checkpoint == new_justified + + +@with_all_phases +@spec_state_test +def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): + """ + J: Justified + F: Finalized + pre-store: + epoch + [0] <- [1] <- [2] <- [3] <- [4] <- [5] + F J + + another_state: + [0] <- [1] <- [2] <- [3] <- [4] <- [5] + F J + """ + another_state = state.copy() + # Initialization + store = get_genesis_forkchoice_store(spec, state) + time = 0 + spec.on_tick(store, time) + + # Process state + next_epoch(spec, state) + _, signed_blocks, state = next_epoch_with_attestations(spec, state, False, True) + for block in signed_blocks: + spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + run_on_block(spec, store, block) + next_epoch(spec, state) + for _ in range(2): + _, signed_blocks, state = next_epoch_with_attestations(spec, state, False, True) + for block in signed_blocks: + spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + run_on_block(spec, store, block) + + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert store.justified_checkpoint.hash_tree_root() == state.current_justified_checkpoint.hash_tree_root() + + # Create another chain + # another_state = store.block_states[store.justified_checkpoint.root].copy() + next_epoch(spec, another_state) + spec.on_tick(store, store.time + another_state.slot * spec.config.SECONDS_PER_SLOT) + + all_blocks = [] + for _ in range(3): + _, signed_blocks, another_state = next_epoch_with_attestations(spec, another_state, True, True) + all_blocks += signed_blocks + + assert another_state.finalized_checkpoint.epoch == 2 + assert another_state.current_justified_checkpoint.epoch == 3 + assert state.finalized_checkpoint.hash_tree_root() != another_state.finalized_checkpoint.hash_tree_root() + assert ( + state.current_justified_checkpoint.hash_tree_root() + != another_state.current_justified_checkpoint.hash_tree_root() + ) + + pre_store_justified_checkpoint_root = store.justified_checkpoint.root + for block in all_blocks: + run_on_block(spec, store, block) + + finalized_slot = spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + ancestor_at_finalized_slot = spec.get_ancestor(store, pre_store_justified_checkpoint_root, finalized_slot) + assert ancestor_at_finalized_slot != store.finalized_checkpoint.root + + assert store.finalized_checkpoint.hash_tree_root() == another_state.finalized_checkpoint.hash_tree_root() + assert store.justified_checkpoint.hash_tree_root() == another_state.current_justified_checkpoint.hash_tree_root() + + +@with_all_phases +@spec_state_test +def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): + """ + J: Justified + F: Finalized + pre-store: + epoch + [0] <- [1] <- [2] <- [3] <- [4] <- [5] + F J + + another_state: + <- [4] <- [5] + F+J + """ + # Initialization + store = get_genesis_forkchoice_store(spec, state) + time = 0 + spec.on_tick(store, time) + + # Process state + next_epoch(spec, state) + spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + _, signed_blocks, state = next_epoch_with_attestations(spec, state, False, True) + for block in signed_blocks: + spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + run_on_block(spec, store, block) + _, signed_blocks, state = next_epoch_with_attestations(spec, state, True, False) + for block in signed_blocks: + spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + run_on_block(spec, store, block) + next_epoch(spec, state) + spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + for _ in range(2): + _, signed_blocks, state = next_epoch_with_attestations(spec, state, False, True) + for block in signed_blocks: + spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + run_on_block(spec, store, block) + + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 4 + assert store.justified_checkpoint.hash_tree_root() == state.current_justified_checkpoint.hash_tree_root() + + # Create another chain + # Forking from epoch 3 + all_blocks = [] + slot = spec.compute_start_slot_at_epoch(3) + block_root = spec.get_block_root_at_slot(state, slot) + another_state = store.block_states[block_root].copy() + for _ in range(2): + _, signed_blocks, another_state = next_epoch_with_attestations(spec, another_state, True, True) + all_blocks += signed_blocks + + assert another_state.finalized_checkpoint.epoch == 3 + assert another_state.current_justified_checkpoint.epoch == 4 + + pre_store_justified_checkpoint_root = store.justified_checkpoint.root + for block in all_blocks: + run_on_block(spec, store, block) + + finalized_slot = spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + ancestor_at_finalized_slot = spec.get_ancestor(store, pre_store_justified_checkpoint_root, finalized_slot) + assert ancestor_at_finalized_slot == store.finalized_checkpoint.root + + assert store.finalized_checkpoint.hash_tree_root() == another_state.finalized_checkpoint.hash_tree_root() + assert store.justified_checkpoint.hash_tree_root() != another_state.current_justified_checkpoint.hash_tree_root() From 7a9ae5733584e3fde5b304d7c606a2264c26f5d8 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sat, 19 Jun 2021 02:13:02 +0800 Subject: [PATCH 004/135] Minor formatting. `True` -> `true`, `False` -> `false` --- tests/formats/fork_choice/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/formats/fork_choice/README.md b/tests/formats/fork_choice/README.md index 199b93784..03a1a6e9c 100644 --- a/tests/formats/fork_choice/README.md +++ b/tests/formats/fork_choice/README.md @@ -30,8 +30,8 @@ The parameter that is required for executing `on_tick(store, time)`. ```yaml { tick: int -- to execute `on_tick(store, time)`. - valid: bool -- optional, default to `True`. - If it's `False`, this execution step is expected to be invalid. + valid: bool -- optional, default to `true`. + If it's `false`, this execution step is expected to be invalid. } ``` @@ -45,8 +45,8 @@ The parameter that is required for executing `on_attestation(store, attestation) { attestation: string -- the name of the `attestation_<32-byte-root>.ssz_snappy` file. To execute `on_attestation(store, attestation)` with the given attestation. - valid: bool -- optional, default to `True`. - If it's `False`, this execution step is expected to be invalid. + valid: bool -- optional, default to `true`. + If it's `false`, this execution step is expected to be invalid. } ``` The file is located in the same folder (see below). @@ -61,8 +61,8 @@ The parameter that is required for executing `on_block(store, block)`. { block: string -- the name of the `block_<32-byte-root>.ssz_snappy` file. To execute `on_block(store, block)` with the given attestation. - valid: bool -- optional, default to `True`. - If it's `False`, this execution step is expected to be invalid. + valid: bool -- optional, default to `true`. + If it's `false`, this execution step is expected to be invalid. } ``` The file is located in the same folder (see below). From 83598af188cd75ec0ee30ef60a21e3d419229771 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sat, 19 Jun 2021 06:29:01 +0800 Subject: [PATCH 005/135] Add `test_new_justified_is_later_than_store_justified` and fix test cases - Fix `on_tick` calls - Refactor test cases --- .../eth2spec/test/helpers/attestations.py | 32 +++ .../unittests/fork_choice/test_on_block.py | 221 +++++++++++++++--- 2 files changed, 223 insertions(+), 30 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index c92860ffa..fd0e0d880 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -261,6 +261,38 @@ def next_epoch_with_attestations(spec, ) +def state_transition_with_signed_full_block(spec, state, fill_cur_epoch, fill_prev_epoch): + # Build a block with previous attestations + block = build_empty_block_for_next_slot(spec, state) + attestations = [] + + if fill_prev_epoch: + # current epoch + slots = state.slot % spec.SLOTS_PER_EPOCH + for slot_offset in range(slots): + target_slot = state.slot - slot_offset + attestations += _get_valid_attestation_at_slot( + state, + spec, + target_slot, + ) + + if fill_prev_epoch: + # attest previous epoch + slots = spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH + for slot_offset in range(1, slots): + target_slot = state.slot - (state.slot % spec.SLOTS_PER_EPOCH) - slot_offset + attestations += _get_valid_attestation_at_slot( + state, + spec, + target_slot, + ) + + block.body.attestations = attestations + signed_block = state_transition_and_sign_block(spec, state, block) + return signed_block + + def prepare_state_with_attestations(spec, state, participation_fn=None): """ Prepare state with attestations according to the ``participation_fn``. diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py index 195c53839..70b5a9706 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py @@ -1,8 +1,12 @@ from copy import deepcopy from eth2spec.utils.ssz.ssz_impl import hash_tree_root -from eth2spec.test.context import with_all_phases, spec_state_test -from eth2spec.test.helpers.attestations import next_epoch_with_attestations +from eth2spec.test.context import MINIMAL, with_all_phases, spec_state_test, with_presets +from eth2spec.test.helpers.attestations import ( + next_epoch_with_attestations, + next_slots_with_attestations, + state_transition_with_signed_full_block, +) from eth2spec.test.helpers.block import build_empty_block_for_next_slot, transition_unsigned_block, \ build_empty_block, sign_block from eth2spec.test.helpers.fork_choice import ( @@ -10,7 +14,7 @@ from eth2spec.test.helpers.fork_choice import ( run_on_block, apply_next_epoch_with_attestations, ) -from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_block, transition_to +from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_block, transition_to, next_slots @with_all_phases @@ -48,7 +52,7 @@ def test_on_block_finalized_skip_slots(spec, state): # Build block that includes the skipped slots up to finality in chain block = build_empty_block(spec, state, spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + 2) signed_block = state_transition_and_sign_block(spec, state, block) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) run_on_block(spec, store, signed_block) @@ -81,7 +85,7 @@ def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): slot=spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + 2) signed_block = sign_block(spec, pre_state, block) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) run_on_block(spec, store, signed_block, valid=False) @@ -94,10 +98,10 @@ def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): spec.on_tick(store, time) next_epoch(spec, state) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) state, store, last_signed_block = yield from apply_next_epoch_with_attestations(spec, state, store) next_epoch(spec, state) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) last_block = last_signed_block.message last_block_root = last_block.hash_tree_root() @@ -137,7 +141,7 @@ def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): spec.on_tick(store, time) next_epoch(spec, state) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) state, store, last_signed_block = yield from apply_next_epoch_with_attestations(spec, state, store) last_block_root = hash_tree_root(last_signed_block.message) @@ -148,7 +152,7 @@ def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): ) next_epoch(spec, state) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) # Create new higher justified checkpoint not in branch of store's justified checkpoint just_block = build_empty_block_for_next_slot(spec, state) @@ -201,7 +205,7 @@ def test_on_block_outside_safe_slots_but_finality(spec, state): spec.on_tick(store, time) next_epoch(spec, state) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) state, store, last_signed_block = yield from apply_next_epoch_with_attestations(spec, state, store) last_block_root = hash_tree_root(last_signed_block.message) @@ -212,7 +216,7 @@ def test_on_block_outside_safe_slots_but_finality(spec, state): ) next_epoch(spec, state) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) # NOTE: Mock a new higher justified checkpoint not in branch of store's justified checkpoint just_block = build_empty_block_for_next_slot(spec, state) @@ -255,18 +259,166 @@ def test_on_block_outside_safe_slots_but_finality(spec, state): assert store.justified_checkpoint == new_justified +@with_all_phases +@with_presets([MINIMAL], reason="It assumes that `MAX_ATTESTATIONS` >= 2/3 attestations of an epoch") +@spec_state_test +def test_new_justified_is_later_than_store_justified(spec, state): + """ + J: Justified + F: Finalized + fork_1_state (forked from genesis): + epoch + [0] <- [1] <- [2] <- [3] <- [4] + F J + + fork_2_state (forked from fork_1_state's epoch 2): + epoch + └──── [3] <- [4] <- [5] <- [6] + F J + + fork_3_state (forked from genesis): + [0] <- [1] <- [2] <- [3] <- [4] <- [5] + F J + """ + # The 1st fork, from genesis + fork_1_state = state.copy() + # The 3rd fork, from genesis + fork_3_state = state.copy() + + # Initialization + store = get_genesis_forkchoice_store(spec, fork_1_state) + time = 0 + spec.on_tick(store, time) + + # ----- Process fork_1_state + # Skip epoch 0 + next_epoch(spec, fork_1_state) + # Fill epoch 1 with previous epoch attestations + _, signed_blocks, fork_1_state = next_epoch_with_attestations(spec, fork_1_state, False, True) + for block in signed_blocks: + spec.on_tick(store, store.genesis_time + fork_1_state.slot * spec.config.SECONDS_PER_SLOT) + run_on_block(spec, store, block) + + # Fork `fork_2_state` at the start of epoch 2 + fork_2_state = fork_1_state.copy() + assert spec.get_current_epoch(fork_2_state) == 2 + + # Skip epoch 2 + next_epoch(spec, fork_1_state) + # # Fill epoch 3 & 4 with previous epoch attestations + for _ in range(2): + _, signed_blocks, fork_1_state = next_epoch_with_attestations(spec, fork_1_state, False, True) + for block in signed_blocks: + spec.on_tick(store, store.genesis_time + fork_1_state.slot * spec.config.SECONDS_PER_SLOT) + run_on_block(spec, store, block) + + assert fork_1_state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 + assert fork_1_state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert store.justified_checkpoint.hash_tree_root() == fork_1_state.current_justified_checkpoint.hash_tree_root() + + # ------ fork_2_state: Create a chain to set store.best_justified_checkpoint + # NOTE: The goal is to make `store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch` + all_blocks = [] + + # Proposed an empty block at epoch 2, 1st slot + block = build_empty_block_for_next_slot(spec, fork_2_state) + signed_block = state_transition_and_sign_block(spec, fork_2_state, block) + all_blocks.append(signed_block.copy()) + assert fork_2_state.current_justified_checkpoint.epoch == 0 + + # Skip to epoch 4 + for _ in range(2): + next_epoch(spec, fork_2_state) + assert fork_2_state.current_justified_checkpoint.epoch == 0 + + # Propose a block at epoch 4, 5th slot + # Propose a block at epoch 5, 5th slot + for _ in range(2): + next_epoch(spec, fork_2_state) + next_slots(spec, fork_2_state, 4) + signed_block = state_transition_with_signed_full_block(spec, fork_2_state, True, True) + all_blocks.append(signed_block.copy()) + assert fork_2_state.current_justified_checkpoint.epoch == 0 + + # Propose a block at epoch 6, SAFE_SLOTS_TO_UPDATE_JUSTIFIED + 2 slot + next_epoch(spec, fork_2_state) + next_slots(spec, fork_2_state, spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + 2) + signed_block = state_transition_with_signed_full_block(spec, fork_2_state, True, True) + all_blocks.append(signed_block.copy()) + 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) + assert spec.compute_slots_since_epoch_start(spec.get_current_slot(store)) >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + + # 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) + run_on_block(spec, store, block) + + assert store.finalized_checkpoint.epoch == 0 + assert store.justified_checkpoint.epoch == 3 + assert store.best_justified_checkpoint.epoch == 5 + + # ------ fork_3_state: Create another chain to test the + # "Update justified if new justified is later than store justified" case + all_blocks = [] + for _ in range(3): + next_epoch(spec, fork_3_state) + + # epoch 3 + _, signed_blocks, fork_3_state = next_epoch_with_attestations(spec, fork_3_state, True, True) + all_blocks += signed_blocks + assert fork_3_state.finalized_checkpoint.epoch == 0 + + # epoch 4, attest the first 5 blocks + _, blocks, fork_3_state = next_slots_with_attestations(spec, fork_3_state, 5, True, True) + all_blocks += blocks.copy() + assert fork_3_state.finalized_checkpoint.epoch == 0 + + # Propose a block at epoch 5, 5th slot + next_epoch(spec, fork_3_state) + next_slots(spec, fork_3_state, 4) + signed_block = state_transition_with_signed_full_block(spec, fork_3_state, True, True) + all_blocks.append(signed_block.copy()) + assert fork_3_state.finalized_checkpoint.epoch == 0 + + # Propose a block at epoch 6, 5th slot + next_epoch(spec, fork_3_state) + next_slots(spec, fork_3_state, 4) + signed_block = state_transition_with_signed_full_block(spec, fork_3_state, True, True) + all_blocks.append(signed_block.copy()) + assert fork_3_state.finalized_checkpoint.epoch == 3 + assert fork_3_state.current_justified_checkpoint.epoch == 4 + + # Apply blocks of `fork_3_state` to `store` + for block in all_blocks: + if store.time < spec.compute_time_at_slot(fork_2_state, block.message.slot): + spec.on_tick(store, store.genesis_time + block.message.slot * spec.config.SECONDS_PER_SLOT) + run_on_block(spec, store, block) + + assert store.finalized_checkpoint.hash_tree_root() == fork_3_state.finalized_checkpoint.hash_tree_root() + assert (store.justified_checkpoint.hash_tree_root() + == fork_3_state.current_justified_checkpoint.hash_tree_root() + != store.best_justified_checkpoint.hash_tree_root()) + assert (store.best_justified_checkpoint.hash_tree_root() + == fork_2_state.current_justified_checkpoint.hash_tree_root()) + + @with_all_phases @spec_state_test def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): """ J: Justified F: Finalized - pre-store: + state (forked from genesis): epoch [0] <- [1] <- [2] <- [3] <- [4] <- [5] F J - another_state: + another_state (forked from genesis): [0] <- [1] <- [2] <- [3] <- [4] <- [5] F J """ @@ -276,17 +428,22 @@ def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): time = 0 spec.on_tick(store, time) - # Process state + # ----- Process state + # Goal: make `store.finalized_checkpoint.epoch == 0` and `store.justified_checkpoint.epoch == 3` + # Skip epoch 0 next_epoch(spec, state) + # Fill epoch 1 with previous epoch attestations _, signed_blocks, state = next_epoch_with_attestations(spec, state, False, True) for block in signed_blocks: - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) run_on_block(spec, store, block) + # Skip epoch 2 next_epoch(spec, state) + # Fill epoch 3 & 4 with previous epoch attestations for _ in range(2): _, signed_blocks, state = next_epoch_with_attestations(spec, state, False, True) for block in signed_blocks: - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) run_on_block(spec, store, block) assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 @@ -294,11 +451,11 @@ def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): assert store.justified_checkpoint.hash_tree_root() == state.current_justified_checkpoint.hash_tree_root() # Create another chain - # another_state = store.block_states[store.justified_checkpoint.root].copy() - next_epoch(spec, another_state) - spec.on_tick(store, store.time + another_state.slot * spec.config.SECONDS_PER_SLOT) - + # Goal: make `another_state.finalized_checkpoint.epoch == 2` and `another_state.justified_checkpoint.epoch == 3` all_blocks = [] + # Skip epoch 0 + next_epoch(spec, another_state) + # Fill epoch 1 & 2 with previous + current epoch attestations for _ in range(3): _, signed_blocks, another_state = next_epoch_with_attestations(spec, another_state, True, True) all_blocks += signed_blocks @@ -310,9 +467,11 @@ def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): state.current_justified_checkpoint.hash_tree_root() != another_state.current_justified_checkpoint.hash_tree_root() ) - pre_store_justified_checkpoint_root = store.justified_checkpoint.root + + # Apply blocks of `another_state` to `store` for block in all_blocks: + # NOTE: Do not call `on_tick` here run_on_block(spec, store, block) finalized_slot = spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) @@ -329,14 +488,14 @@ def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): """ J: Justified F: Finalized - pre-store: + state: epoch [0] <- [1] <- [2] <- [3] <- [4] <- [5] F J - another_state: - <- [4] <- [5] - F+J + another_state (forked from state at epoch 3): + └──── [4] <- [5] + F J """ # Initialization store = get_genesis_forkchoice_store(spec, state) @@ -345,21 +504,21 @@ def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): # Process state next_epoch(spec, state) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) _, signed_blocks, state = next_epoch_with_attestations(spec, state, False, True) for block in signed_blocks: - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) run_on_block(spec, store, block) _, signed_blocks, state = next_epoch_with_attestations(spec, state, True, False) for block in signed_blocks: - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) run_on_block(spec, store, block) next_epoch(spec, state) - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) for _ in range(2): _, signed_blocks, state = next_epoch_with_attestations(spec, state, False, True) for block in signed_blocks: - spec.on_tick(store, store.time + state.slot * spec.config.SECONDS_PER_SLOT) + spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) run_on_block(spec, store, block) assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 @@ -381,6 +540,8 @@ def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): pre_store_justified_checkpoint_root = store.justified_checkpoint.root for block in all_blocks: + if store.time < spec.compute_time_at_slot(another_state, block.message.slot): + spec.on_tick(store, store.genesis_time + block.message.slot * spec.config.SECONDS_PER_SLOT) run_on_block(spec, store, block) finalized_slot = spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) From 88be6cdf603b334d2d1f11daee3063c6ddbc4b5c Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 22 Jun 2021 20:55:52 +0800 Subject: [PATCH 006/135] Apply Danny's suggestions from code review Co-authored-by: Danny Ryan --- .../test/phase0/unittests/fork_choice/test_on_block.py | 8 ++++++-- tests/formats/fork_choice/README.md | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py index 70b5a9706..5ca362930 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py @@ -7,8 +7,12 @@ from eth2spec.test.helpers.attestations import ( next_slots_with_attestations, state_transition_with_signed_full_block, ) -from eth2spec.test.helpers.block import build_empty_block_for_next_slot, transition_unsigned_block, \ - build_empty_block, sign_block +from eth2spec.test.helpers.block import ( + build_empty_block, + build_empty_block_for_next_slot, + sign_block, + transition_unsigned_block, +) from eth2spec.test.helpers.fork_choice import ( get_genesis_forkchoice_store, run_on_block, diff --git a/tests/formats/fork_choice/README.md b/tests/formats/fork_choice/README.md index 03a1a6e9c..90c0aafc7 100644 --- a/tests/formats/fork_choice/README.md +++ b/tests/formats/fork_choice/README.md @@ -29,7 +29,7 @@ The parameter that is required for executing `on_tick(store, time)`. ```yaml { - tick: int -- to execute `on_tick(store, time)`. + tick: int -- to execute `on_tick(store, time)`. valid: bool -- optional, default to `true`. If it's `false`, this execution step is expected to be invalid. } From 69a645aa8b428dcd47836e4efaa1d9b408853995 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 23 Jun 2021 03:38:17 +0800 Subject: [PATCH 007/135] Apply PR feedback --- .../unittests/fork_choice/test_on_block.py | 31 +++++-------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py index 5ca362930..2f945f06c 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py @@ -26,8 +26,6 @@ from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_bl def test_on_block_before_finalized(spec, state): # Initialization store = get_genesis_forkchoice_store(spec, state) - time = 100 - spec.on_tick(store, time) store.finalized_checkpoint = spec.Checkpoint( epoch=store.finalized_checkpoint.epoch + 2, @@ -45,9 +43,8 @@ def test_on_block_before_finalized(spec, state): def test_on_block_finalized_skip_slots(spec, state): # Initialization store = get_genesis_forkchoice_store(spec, state) - time = 100 - spec.on_tick(store, time) + # Create a finalized chain store.finalized_checkpoint = spec.Checkpoint( epoch=store.finalized_checkpoint.epoch + 2, root=store.finalized_checkpoint.root @@ -98,16 +95,13 @@ def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): # Initialization store = get_genesis_forkchoice_store(spec, state) - time = 0 - spec.on_tick(store, time) next_epoch(spec, state) spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) state, store, last_signed_block = yield from apply_next_epoch_with_attestations(spec, state, store) next_epoch(spec, state) spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - last_block = last_signed_block.message - last_block_root = last_block.hash_tree_root() + last_block_root = last_signed_block.message.hash_tree_root() # NOTE: Mock the justified checkpoint just_state = store.block_states[last_block_root] @@ -141,8 +135,6 @@ def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): # Initialization store = get_genesis_forkchoice_store(spec, state) - time = 0 - spec.on_tick(store, time) next_epoch(spec, state) spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) @@ -205,8 +197,6 @@ def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): def test_on_block_outside_safe_slots_but_finality(spec, state): # Initialization store = get_genesis_forkchoice_store(spec, state) - time = 0 - spec.on_tick(store, time) next_epoch(spec, state) spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) @@ -291,8 +281,6 @@ def test_new_justified_is_later_than_store_justified(spec, state): # Initialization store = get_genesis_forkchoice_store(spec, fork_1_state) - time = 0 - spec.on_tick(store, time) # ----- Process fork_1_state # Skip epoch 0 @@ -422,20 +410,21 @@ def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): [0] <- [1] <- [2] <- [3] <- [4] <- [5] F J - another_state (forked from genesis): - [0] <- [1] <- [2] <- [3] <- [4] <- [5] + another_state (forked from epoch 0): + └──── [1] <- [2] <- [3] <- [4] <- [5] F J """ - another_state = state.copy() # Initialization store = get_genesis_forkchoice_store(spec, state) - time = 0 - spec.on_tick(store, time) # ----- Process state # Goal: make `store.finalized_checkpoint.epoch == 0` and `store.justified_checkpoint.epoch == 3` # Skip epoch 0 next_epoch(spec, state) + + # Forking another_state + another_state = state.copy() + # Fill epoch 1 with previous epoch attestations _, signed_blocks, state = next_epoch_with_attestations(spec, state, False, True) for block in signed_blocks: @@ -457,8 +446,6 @@ def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): # Create another chain # Goal: make `another_state.finalized_checkpoint.epoch == 2` and `another_state.justified_checkpoint.epoch == 3` all_blocks = [] - # Skip epoch 0 - next_epoch(spec, another_state) # Fill epoch 1 & 2 with previous + current epoch attestations for _ in range(3): _, signed_blocks, another_state = next_epoch_with_attestations(spec, another_state, True, True) @@ -503,8 +490,6 @@ def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): """ # Initialization store = get_genesis_forkchoice_store(spec, state) - time = 0 - spec.on_tick(store, time) # Process state next_epoch(spec, state) From f55afefe9025c103e1fc9f6a193891514b8f8c0e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 23 Jun 2021 04:58:27 +0800 Subject: [PATCH 008/135] Move more tests from unittests to testgen tests - `test_on_block_before_finalized` - `test_on_block_finalized_skip_slots` - `test_on_block_finalized_skip_slots_not_in_skip_chain` --- .../test/phase0/fork_choice/test_on_block.py | 110 +++++++++++++++++- .../unittests/fork_choice/test_on_block.py | 74 +----------- 2 files changed, 106 insertions(+), 78 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 e9f2fae63..c54a7a7be 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 @@ -17,6 +17,7 @@ from eth2spec.test.helpers.fork_choice import ( from eth2spec.test.helpers.state import ( next_epoch, state_transition_and_sign_block, + transition_to, ) @@ -51,8 +52,8 @@ def test_basic(spec, state): @with_all_phases -@with_presets([MINIMAL], reason="too slow") @spec_state_test +@with_presets([MINIMAL], reason="too slow") def test_on_block_checkpoints(spec, state): test_steps = [] # Initialization @@ -76,10 +77,8 @@ def test_on_block_checkpoints(spec, state): on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) # Mock the finalized_checkpoint and build a block on it - fin_state = store.block_states[last_block_root] - fin_state.finalized_checkpoint = ( - store.block_states[last_block_root].current_justified_checkpoint - ) + fin_state = store.block_states[last_block_root].copy() + fin_state.finalized_checkpoint = store.block_states[last_block_root].current_justified_checkpoint.copy() block = build_empty_block_for_next_slot(spec, fin_state) signed_block = state_transition_and_sign_block(spec, fin_state.copy(), block) @@ -133,3 +132,104 @@ def test_on_block_bad_parent_root(spec, state): yield from add_block(spec, store, signed_block, test_steps, valid=False) yield 'steps', test_steps + + +@with_all_phases +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_on_block_before_finalized(spec, state): + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # Fork + another_state = state.copy() + + # Create a finalized chain + for _ in range(4): + state, store, _ = yield from apply_next_epoch_with_attestations(spec, state, store, test_steps) + assert store.finalized_checkpoint.epoch == 2 + + # Fail receiving block of `GENESIS_SLOT + 1` slot + block = build_empty_block_for_next_slot(spec, another_state) + block.body.graffiti = b'\x12' * 32 + signed_block = state_transition_and_sign_block(spec, another_state, block) + assert signed_block.message.hash_tree_root() not in store.blocks + yield from tick_and_add_block(spec, store, signed_block, test_steps, valid=False) + + yield 'steps', test_steps + + +@with_all_phases +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_on_block_finalized_skip_slots(spec, state): + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # Create a finalized chain + for _ in range(4): + state, store, _ = yield from apply_next_epoch_with_attestations(spec, state, store, test_steps) + assert store.finalized_checkpoint.epoch == 2 + + # 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) + + yield from tick_and_add_block(spec, store, signed_block, test_steps) + + yield 'steps', test_steps + + +@with_all_phases +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): + 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) + yield 'anchor_state', state + yield 'anchor_block', 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 + + # Finalized + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations(spec, state, store, test_steps) + assert store.finalized_checkpoint.epoch == pre_finalized_checkpoint_epoch + 1 + + # 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) + yield from tick_and_add_block(spec, store, signed_block, test_steps, valid=False) + + yield 'steps', test_steps diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py index 2f945f06c..18e23ade2 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py @@ -8,86 +8,14 @@ from eth2spec.test.helpers.attestations import ( state_transition_with_signed_full_block, ) from eth2spec.test.helpers.block import ( - build_empty_block, build_empty_block_for_next_slot, - sign_block, - transition_unsigned_block, ) from eth2spec.test.helpers.fork_choice import ( get_genesis_forkchoice_store, run_on_block, apply_next_epoch_with_attestations, ) -from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_block, transition_to, next_slots - - -@with_all_phases -@spec_state_test -def test_on_block_before_finalized(spec, state): - # Initialization - store = get_genesis_forkchoice_store(spec, state) - - store.finalized_checkpoint = spec.Checkpoint( - epoch=store.finalized_checkpoint.epoch + 2, - root=store.finalized_checkpoint.root - ) - - # Fail receiving block of `GENESIS_SLOT + 1` slot - block = build_empty_block_for_next_slot(spec, state) - signed_block = state_transition_and_sign_block(spec, state, block) - run_on_block(spec, store, signed_block, valid=False) - - -@with_all_phases -@spec_state_test -def test_on_block_finalized_skip_slots(spec, state): - # Initialization - store = get_genesis_forkchoice_store(spec, state) - - # Create a finalized chain - store.finalized_checkpoint = spec.Checkpoint( - epoch=store.finalized_checkpoint.epoch + 2, - root=store.finalized_checkpoint.root - ) - - # Build block that includes the skipped slots up to finality in chain - block = build_empty_block(spec, state, spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + 2) - signed_block = state_transition_and_sign_block(spec, state, block) - spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - run_on_block(spec, store, signed_block) - - -@with_all_phases -@spec_state_test -def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): - # 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) - - current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time - spec.on_tick(store, current_time) - assert store.time == current_time - - pre_finalized_checkpoint_epoch = store.finalized_checkpoint.epoch - - # Finalized - for _ in range(3): - state, store, _ = yield from apply_next_epoch_with_attestations(spec, state, store) - assert store.finalized_checkpoint.epoch == pre_finalized_checkpoint_epoch + 1 - - # 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()] - block = build_empty_block(spec, - state=pre_state, - slot=spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + 2) - signed_block = sign_block(spec, pre_state, block) - - spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - run_on_block(spec, store, signed_block, valid=False) +from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_block, next_slots @with_all_phases From 29a93f62853bb7f09e6448acd2e74fe2167dc667 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 25 Jun 2021 18:48:30 +0800 Subject: [PATCH 009/135] Move more unit tests to test vectors --- .../eth2spec/test/helpers/attestations.py | 73 ++- .../eth2spec/test/helpers/fork_choice.py | 104 +++- .../test/phase0/fork_choice/test_on_block.py | 466 +++++++++++++++++- .../unittests/fork_choice/test_on_block.py | 412 +--------------- 4 files changed, 608 insertions(+), 447 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index fd0e0d880..ffd484ecd 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -217,30 +217,13 @@ def next_slots_with_attestations(spec, post_state = state.copy() signed_blocks = [] for _ in range(slot_count): - block = build_empty_block_for_next_slot(spec, post_state) - if fill_cur_epoch and post_state.slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY: - slot_to_attest = post_state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1 - if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(post_state)): - attestations = _get_valid_attestation_at_slot( - post_state, - spec, - slot_to_attest, - participation_fn=participation_fn - ) - for attestation in attestations: - block.body.attestations.append(attestation) - if fill_prev_epoch: - slot_to_attest = post_state.slot - spec.SLOTS_PER_EPOCH + 1 - attestations = _get_valid_attestation_at_slot( - post_state, - spec, - slot_to_attest, - participation_fn=participation_fn - ) - for attestation in attestations: - block.body.attestations.append(attestation) - - signed_block = state_transition_and_sign_block(spec, post_state, block) + signed_block = state_transition_with_full_block( + spec, + post_state, + fill_cur_epoch, + fill_prev_epoch, + participation_fn, + ) signed_blocks.append(signed_block) return state, signed_blocks, post_state @@ -249,7 +232,8 @@ def next_slots_with_attestations(spec, def next_epoch_with_attestations(spec, state, fill_cur_epoch, - fill_prev_epoch): + fill_prev_epoch, + participation_fn=None): assert state.slot % spec.SLOTS_PER_EPOCH == 0 return next_slots_with_attestations( @@ -258,15 +242,50 @@ def next_epoch_with_attestations(spec, spec.SLOTS_PER_EPOCH, fill_cur_epoch, fill_prev_epoch, + participation_fn, ) -def state_transition_with_signed_full_block(spec, state, fill_cur_epoch, fill_prev_epoch): +def state_transition_with_full_block(spec, state, fill_cur_epoch, fill_prev_epoch, participation_fn=None): + """ + Build and apply a block with attestions at the calculated `slot_to_attest` of current epoch and/or previous epoch. + """ + block = build_empty_block_for_next_slot(spec, state) + if fill_cur_epoch and state.slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY: + slot_to_attest = state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1 + if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)): + attestations = _get_valid_attestation_at_slot( + state, + spec, + slot_to_attest, + participation_fn=participation_fn + ) + for attestation in attestations: + block.body.attestations.append(attestation) + if fill_prev_epoch: + slot_to_attest = state.slot - spec.SLOTS_PER_EPOCH + 1 + attestations = _get_valid_attestation_at_slot( + state, + spec, + slot_to_attest, + participation_fn=participation_fn + ) + for attestation in attestations: + block.body.attestations.append(attestation) + + signed_block = state_transition_and_sign_block(spec, state, block) + return signed_block + + +def state_transition_with_full_attestations_block(spec, state, fill_cur_epoch, fill_prev_epoch): + """ + Build and apply a block with attestions at all valid slots of current epoch and/or previous epoch. + """ # Build a block with previous attestations block = build_empty_block_for_next_slot(spec, state) attestations = [] - if fill_prev_epoch: + if fill_cur_epoch: # current epoch slots = state.slot % spec.SLOTS_PER_EPOCH for slot_offset in range(slots): diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index bac3d1ff5..ec5793af5 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -1,5 +1,8 @@ from eth_utils import encode_hex -from eth2spec.test.helpers.attestations import next_epoch_with_attestations +from eth2spec.test.helpers.attestations import ( + next_epoch_with_attestations, + next_slots_with_attestations, +) def get_anchor_root(spec, state): @@ -19,23 +22,20 @@ def add_block_to_store(spec, store, signed_block): spec.on_block(store, signed_block) -def tick_and_add_block(spec, store, signed_block, test_steps=None, valid=True): - if test_steps is None: - test_steps = [] - +def tick_and_add_block(spec, store, signed_block, test_steps, valid=True, allow_invalid_attestations=False): pre_state = store.block_states[signed_block.message.parent_root] block_time = pre_state.genesis_time + signed_block.message.slot * spec.config.SECONDS_PER_SLOT if store.time < block_time: on_tick_and_append_step(spec, store, block_time, test_steps) - yield from add_block(spec, store, signed_block, test_steps, valid=valid) + post_state = yield from add_block( + spec, store, signed_block, test_steps, valid=valid, allow_invalid_attestations=allow_invalid_attestations) + + return post_state -def tick_and_run_on_attestation(spec, store, attestation, test_steps=None): - if test_steps is None: - test_steps = [] - +def tick_and_run_on_attestation(spec, store, attestation, test_steps): parent_block = store.blocks[attestation.data.beacon_block_root] pre_state = store.block_states[spec.hash_tree_root(parent_block)] block_time = pre_state.genesis_time + parent_block.slot * spec.config.SECONDS_PER_SLOT @@ -50,6 +50,37 @@ def tick_and_run_on_attestation(spec, store, attestation, test_steps=None): test_steps.append({'attestation': get_attestation_file_name(attestation)}) +def add_attestation(spec, store, attestation, test_steps, valid=True): + yield get_attestation_file_name(attestation), attestation + + if not valid: + try: + run_on_attestation(spec, store, attestation, valid=True) + except AssertionError: + test_steps.append({ + 'attestation': get_attestation_file_name(attestation), + 'valid': False, + }) + return + else: + assert False + + run_on_attestation(spec, store, attestation, valid=True) + test_steps.append({'attestation': get_attestation_file_name(attestation)}) + + +def run_on_attestation(spec, store, attestation, valid=True): + if not valid: + try: + spec.on_attestation(store, attestation) + except AssertionError: + return + else: + assert False + + spec.on_attestation(store, attestation) + + def get_genesis_forkchoice_store(spec, genesis_state): store, _ = get_genesis_forkchoice_store_and_block(spec, genesis_state) return store @@ -87,10 +118,10 @@ def run_on_block(spec, store, signed_block, valid=True): assert store.blocks[signed_block.message.hash_tree_root()] == signed_block.message -def add_block(spec, store, signed_block, test_steps=None, valid=True): - if test_steps is None: - test_steps = [] - +def add_block(spec, store, signed_block, test_steps, valid=True, allow_invalid_attestations=False): + """ + Run on_block and on_attestation + """ yield get_block_file_name(signed_block), signed_block if not valid: @@ -109,8 +140,14 @@ def add_block(spec, store, signed_block, test_steps=None, valid=True): test_steps.append({'block': get_block_file_name(signed_block)}) # An on_block step implies receiving block's attestations - for attestation in signed_block.message.body.attestations: - spec.on_attestation(store, attestation) + try: + for attestation in signed_block.message.body.attestations: + run_on_attestation(spec, store, attestation, valid=True) + except AssertionError: + if allow_invalid_attestations: + pass + else: + raise block_root = signed_block.message.hash_tree_root() assert store.blocks[block_root] == signed_block.message @@ -125,6 +162,8 @@ def add_block(spec, store, signed_block, test_steps=None, valid=True): } }) + return store.block_states[signed_block.message.hash_tree_root()] + def get_formatted_head_output(spec, store): head = spec.get_head(store) @@ -135,11 +174,40 @@ def get_formatted_head_output(spec, store): } -def apply_next_epoch_with_attestations(spec, state, store, test_steps=None): +def apply_next_epoch_with_attestations(spec, + state, + store, + fill_cur_epoch, + fill_prev_epoch, + participation_fn=None, + test_steps=None): if test_steps is None: test_steps = [] - _, new_signed_blocks, post_state = next_epoch_with_attestations(spec, state, True, False) + _, new_signed_blocks, post_state = next_epoch_with_attestations( + spec, state, fill_cur_epoch, fill_prev_epoch, participation_fn=participation_fn) + for signed_block in new_signed_blocks: + block = signed_block.message + yield from tick_and_add_block(spec, store, signed_block, test_steps) + block_root = block.hash_tree_root() + assert store.blocks[block_root] == block + last_signed_block = signed_block + + assert store.block_states[block_root].hash_tree_root() == post_state.hash_tree_root() + + return post_state, store, last_signed_block + + +def apply_next_slots_with_attestations(spec, + state, + store, + slots, + fill_cur_epoch, + fill_prev_epoch, + test_steps, + participation_fn=None): + _, new_signed_blocks, post_state = next_slots_with_attestations( + spec, state, slots, fill_cur_epoch, fill_prev_epoch, participation_fn=participation_fn) for signed_block in new_signed_blocks: block = signed_block.message yield from tick_and_add_block(spec, store, signed_block, test_steps) 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 c54a7a7be..c3374d4f8 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 @@ -1,6 +1,13 @@ -from eth2spec.utils.ssz.ssz_impl import hash_tree_root +import random +from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.test.context import MINIMAL, spec_state_test, with_all_phases, with_presets +from eth2spec.test.helpers.attestations import ( + next_epoch_with_attestations, + next_slots_with_attestations, + state_transition_with_full_block, + state_transition_with_full_attestations_block, +) from eth2spec.test.helpers.block import ( build_empty_block_for_next_slot, build_empty_block, @@ -13,14 +20,27 @@ from eth2spec.test.helpers.fork_choice import ( add_block, tick_and_add_block, apply_next_epoch_with_attestations, + apply_next_slots_with_attestations, ) from eth2spec.test.helpers.state import ( next_epoch, + next_slots, state_transition_and_sign_block, transition_to, ) +rng = random.Random(2020) + + +def _drop_random_one_third(_slot, _index, indices): + committee_len = len(indices) + assert committee_len >= 3 + filter_len = committee_len // 3 + participant_count = committee_len - filter_len + return rng.sample(indices, participant_count) + + @with_all_phases @spec_state_test def test_basic(spec, state): @@ -68,7 +88,8 @@ def test_on_block_checkpoints(spec, state): next_epoch(spec, state) on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) - state, store, last_signed_block = yield from apply_next_epoch_with_attestations(spec, state, store, test_steps) + state, store, last_signed_block = yield from apply_next_epoch_with_attestations( + spec, state, store, True, False, test_steps=test_steps) last_block_root = hash_tree_root(last_signed_block.message) assert spec.get_head(store) == last_block_root @@ -152,7 +173,8 @@ def test_on_block_before_finalized(spec, state): # Create a finalized chain for _ in range(4): - state, store, _ = yield from apply_next_epoch_with_attestations(spec, state, store, test_steps) + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, False, test_steps=test_steps) assert store.finalized_checkpoint.epoch == 2 # Fail receiving block of `GENESIS_SLOT + 1` slot @@ -180,7 +202,8 @@ def test_on_block_finalized_skip_slots(spec, state): # Create a finalized chain for _ in range(4): - state, store, _ = yield from apply_next_epoch_with_attestations(spec, state, store, test_steps) + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, False, test_steps=test_steps) assert store.finalized_checkpoint.epoch == 2 # Another chain @@ -219,7 +242,8 @@ def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): # Finalized for _ in range(3): - state, store, _ = yield from apply_next_epoch_with_attestations(spec, state, store, test_steps) + 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 # Now build a block at later slot than finalized epoch @@ -233,3 +257,435 @@ def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): yield from tick_and_add_block(spec, store, signed_block, test_steps, valid=False) yield 'steps', test_steps + + +@with_all_phases +@spec_state_test +@with_presets([MINIMAL], reason="mainnet config requires too many pre-generated public/private keys") +def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): + """ + Test `should_update_justified_checkpoint`: + compute_slots_since_epoch_start(get_current_slot(store)) < SAFE_SLOTS_TO_UPDATE_JUSTIFIED + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # Skip epoch 0 & 1 + for _ in range(2): + next_epoch(spec, state) + # Fill epoch 2 + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, False, test_steps=test_steps) + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 2 + # Skip epoch 3 & 4 + for _ in range(2): + next_epoch(spec, state) + # Epoch 5: Attest current epoch + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, False, participation_fn=_drop_random_one_third, test_steps=test_steps) + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 + assert state.current_justified_checkpoint.epoch == 2 + assert store.justified_checkpoint.epoch == 2 + assert state.current_justified_checkpoint == store.justified_checkpoint + + # Skip epoch 6 + next_epoch(spec, state) + + pre_state = state.copy() + + # Build a block to justify epoch 5 + signed_block = state_transition_with_full_block(spec, state, True, True) + assert state.finalized_checkpoint.epoch == 0 + assert state.current_justified_checkpoint.epoch == 5 + assert state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch + assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH < spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + # Run on_block + yield from tick_and_add_block(spec, store, signed_block, test_steps) + # Ensure justified_checkpoint has been changed but finality is unchanged + assert store.justified_checkpoint.epoch == 5 + assert store.justified_checkpoint == state.current_justified_checkpoint + assert store.finalized_checkpoint.epoch == pre_state.finalized_checkpoint.epoch == 0 + + yield 'steps', test_steps + + +@with_all_phases +@with_presets([MINIMAL], reason="It assumes that `MAX_ATTESTATIONS` >= 2/3 attestations of an epoch") +@spec_state_test +def test_on_block_outside_safe_slots_but_finality(spec, state): + """ + Test `should_update_justified_checkpoint` case + - compute_slots_since_epoch_start(get_current_slot(store)) > SAFE_SLOTS_TO_UPDATE_JUSTIFIED + - new_justified_checkpoint and store.justified_checkpoint.root are NOT conflicting + + Thus should_update_justified_checkpoint returns True. + + Part of this script is similar to `test_new_justified_is_later_than_store_justified`. + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # Skip epoch 0 + next_epoch(spec, state) + # Fill epoch 1 to 3, attest current epoch + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, False, test_steps=test_steps) + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + + # Skip epoch 4-6 + for _ in range(3): + next_epoch(spec, state) + + # epoch 7 + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps) + assert state.finalized_checkpoint.epoch == 2 + assert state.current_justified_checkpoint.epoch == 7 + + # epoch 8, attest the first 5 blocks + state, store, _ = yield from apply_next_slots_with_attestations( + spec, state, store, 5, True, True, test_steps) + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 7 + + # Propose a block at epoch 9, 5th slot + next_epoch(spec, state) + next_slots(spec, state, 4) + signed_block = state_transition_with_full_attestations_block(spec, state, True, True) + yield from tick_and_add_block(spec, store, signed_block, test_steps) + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 7 + + # Propose an empty block at epoch 10, SAFE_SLOTS_TO_UPDATE_JUSTIFIED + 2 slot + # This block would trigger justification and finality updates on store + next_epoch(spec, state) + next_slots(spec, state, 4) + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + assert state.finalized_checkpoint.epoch == 7 + assert state.current_justified_checkpoint.epoch == 8 + # Step time past safe slots and run on_block + if store.time < spec.compute_time_at_slot(state, signed_block.message.slot): + time = store.genesis_time + signed_block.message.slot * spec.config.SECONDS_PER_SLOT + on_tick_and_append_step(spec, store, time, test_steps) + assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + yield from add_block(spec, store, signed_block, test_steps) + + # Ensure justified_checkpoint finality has been changed + assert store.finalized_checkpoint.epoch == 7 + assert store.finalized_checkpoint == state.finalized_checkpoint + assert store.justified_checkpoint.epoch == 8 + assert store.justified_checkpoint == state.current_justified_checkpoint + + yield 'steps', test_steps + + +@with_all_phases +@with_presets([MINIMAL], reason="It assumes that `MAX_ATTESTATIONS` >= 2/3 attestations of an epoch") +@spec_state_test +def test_new_justified_is_later_than_store_justified(spec, state): + """ + J: Justified + F: Finalized + fork_1_state (forked from genesis): + epoch + [0] <- [1] <- [2] <- [3] <- [4] + F J + + fork_2_state (forked from fork_1_state's epoch 2): + epoch + └──── [3] <- [4] <- [5] <- [6] + F J + + fork_3_state (forked from genesis): + [0] <- [1] <- [2] <- [3] <- [4] <- [5] + F J + """ + # The 1st fork, from genesis + fork_1_state = state.copy() + # The 3rd fork, from genesis + fork_3_state = state.copy() + + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # ----- Process fork_1_state + # Skip epoch 0 + next_epoch(spec, fork_1_state) + # Fill epoch 1 with previous epoch attestations + fork_1_state, store, _ = yield from apply_next_epoch_with_attestations( + spec, fork_1_state, store, False, True, test_steps=test_steps) + + # Fork `fork_2_state` at the start of epoch 2 + fork_2_state = fork_1_state.copy() + assert spec.get_current_epoch(fork_2_state) == 2 + + # Skip epoch 2 + next_epoch(spec, fork_1_state) + # # Fill epoch 3 & 4 with previous epoch attestations + for _ in range(2): + fork_1_state, store, _ = yield from apply_next_epoch_with_attestations( + spec, fork_1_state, store, False, True, test_steps=test_steps) + + assert fork_1_state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 + assert fork_1_state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert store.justified_checkpoint == fork_1_state.current_justified_checkpoint + + # ------ fork_2_state: Create a chain to set store.best_justified_checkpoint + # NOTE: The goal is to make `store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch` + all_blocks = [] + + # Proposed an empty block at epoch 2, 1st slot + block = build_empty_block_for_next_slot(spec, fork_2_state) + signed_block = state_transition_and_sign_block(spec, fork_2_state, block) + yield from tick_and_add_block(spec, store, signed_block, test_steps) + assert fork_2_state.current_justified_checkpoint.epoch == 0 + + # Skip to epoch 4 + for _ in range(2): + next_epoch(spec, fork_2_state) + assert fork_2_state.current_justified_checkpoint.epoch == 0 + + # Propose a block at epoch 4, 5th slot + # Propose a block at epoch 5, 5th slot + for _ in range(2): + next_epoch(spec, fork_2_state) + next_slots(spec, fork_2_state, 4) + signed_block = state_transition_with_full_attestations_block(spec, fork_2_state, True, True) + yield from tick_and_add_block(spec, store, signed_block, test_steps) + assert fork_2_state.current_justified_checkpoint.epoch == 0 + + # Propose a block at epoch 6, SAFE_SLOTS_TO_UPDATE_JUSTIFIED + 2 slot + next_epoch(spec, fork_2_state) + next_slots(spec, fork_2_state, spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + 2) + signed_block = state_transition_with_full_attestations_block(spec, fork_2_state, True, True) + assert fork_2_state.finalized_checkpoint.epoch == 0 + assert fork_2_state.current_justified_checkpoint.epoch == 5 + # Check SAFE_SLOTS_TO_UPDATE_JUSTIFIED + spec.on_tick(store, store.genesis_time + fork_2_state.slot * spec.config.SECONDS_PER_SLOT) + assert spec.compute_slots_since_epoch_start(spec.get_current_slot(store)) >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + # Run on_block + yield from add_block(spec, store, signed_block, test_steps) + assert store.finalized_checkpoint.epoch == 0 + assert store.justified_checkpoint.epoch == 3 + assert store.best_justified_checkpoint.epoch == 5 + + # ------ fork_3_state: Create another chain to test the + # "Update justified if new justified is later than store justified" case + all_blocks = [] + for _ in range(3): + next_epoch(spec, fork_3_state) + + # epoch 3 + _, signed_blocks, fork_3_state = next_epoch_with_attestations(spec, fork_3_state, True, True) + all_blocks += signed_blocks + assert fork_3_state.finalized_checkpoint.epoch == 0 + + # epoch 4, attest the first 5 blocks + _, blocks, fork_3_state = next_slots_with_attestations(spec, fork_3_state, 5, True, True) + all_blocks += blocks.copy() + assert fork_3_state.finalized_checkpoint.epoch == 0 + + # Propose a block at epoch 5, 5th slot + next_epoch(spec, fork_3_state) + next_slots(spec, fork_3_state, 4) + signed_block = state_transition_with_full_block(spec, fork_3_state, True, True) + all_blocks.append(signed_block.copy()) + assert fork_3_state.finalized_checkpoint.epoch == 0 + + # Propose a block at epoch 6, 5th slot + next_epoch(spec, fork_3_state) + next_slots(spec, fork_3_state, 4) + signed_block = state_transition_with_full_block(spec, fork_3_state, True, True) + all_blocks.append(signed_block.copy()) + assert fork_3_state.finalized_checkpoint.epoch == 3 + assert fork_3_state.current_justified_checkpoint.epoch == 4 + + # FIXME: pending on the `on_block`, `on_attestation` fix + # # 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) + # # 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) + + # assert store.finalized_checkpoint == fork_3_state.finalized_checkpoint + # assert (store.justified_checkpoint + # == fork_3_state.current_justified_checkpoint + # != store.best_justified_checkpoint) + # assert (store.best_justified_checkpoint + # == fork_2_state.current_justified_checkpoint) + + yield 'steps', test_steps + + +@with_all_phases +@spec_state_test +def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): + """ + J: Justified + F: Finalized + state (forked from genesis): + epoch + [0] <- [1] <- [2] <- [3] <- [4] <- [5] + F J + + another_state (forked from epoch 0): + └──── [1] <- [2] <- [3] <- [4] <- [5] + F J + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # ----- Process state + # Goal: make `store.finalized_checkpoint.epoch == 0` and `store.justified_checkpoint.epoch == 3` + # Skip epoch 0 + next_epoch(spec, state) + + # Forking another_state + another_state = state.copy() + + # Fill epoch 1 with previous epoch attestations + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, False, True, test_steps=test_steps) + # Skip epoch 2 + next_epoch(spec, state) + # Fill epoch 3 & 4 with previous epoch attestations + for _ in range(2): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, False, True, test_steps=test_steps) + + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert store.justified_checkpoint == state.current_justified_checkpoint + + # Create another chain + # Goal: make `another_state.finalized_checkpoint.epoch == 2` and `another_state.justified_checkpoint.epoch == 3` + all_blocks = [] + # Fill epoch 1 & 2 with previous + current epoch attestations + for _ in range(3): + _, signed_blocks, another_state = next_epoch_with_attestations(spec, another_state, True, True) + all_blocks += signed_blocks + + assert another_state.finalized_checkpoint.epoch == 2 + assert another_state.current_justified_checkpoint.epoch == 3 + assert state.finalized_checkpoint.hash_tree_root() != another_state.finalized_checkpoint.hash_tree_root() + assert ( + state.current_justified_checkpoint.hash_tree_root() + != another_state.current_justified_checkpoint.hash_tree_root() + ) + # pre_store_justified_checkpoint_root = store.justified_checkpoint.root + + # FIXME: pending on the `on_block`, `on_attestation` fix + # # Apply blocks of `another_state` to `store` + # for block in all_blocks: + # # NOTE: Do not call `on_tick` here + # yield from add_block(spec, store, block, test_steps, allow_invalid_attestations=True) + + # finalized_slot = spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + # ancestor_at_finalized_slot = spec.get_ancestor(store, pre_store_justified_checkpoint_root, finalized_slot) + # assert ancestor_at_finalized_slot != store.finalized_checkpoint.root + + # assert store.finalized_checkpoint == another_state.finalized_checkpoint + # assert store.justified_checkpoint == another_state.current_justified_checkpoint + + yield 'steps', test_steps + + +@with_all_phases +@spec_state_test +def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): + """ + J: Justified + F: Finalized + state: + epoch + [0] <- [1] <- [2] <- [3] <- [4] <- [5] + F J + + another_state (forked from state at epoch 3): + └──── [4] <- [5] + F J + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # Process state + 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) + + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, False, test_steps=test_steps) + next_epoch(spec, state) + + for _ in range(2): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, False, True, test_steps=test_steps) + + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 4 + assert store.justified_checkpoint == state.current_justified_checkpoint + + # Create another chain + # Forking from epoch 3 + all_blocks = [] + slot = spec.compute_start_slot_at_epoch(3) + block_root = spec.get_block_root_at_slot(state, slot) + another_state = store.block_states[block_root].copy() + for _ in range(2): + _, signed_blocks, another_state = next_epoch_with_attestations(spec, another_state, True, True) + all_blocks += signed_blocks + + assert another_state.finalized_checkpoint.epoch == 3 + assert another_state.current_justified_checkpoint.epoch == 4 + + pre_store_justified_checkpoint_root = store.justified_checkpoint.root + for block in all_blocks: + # FIXME: Once `on_block` and `on_attestation` logic is fixed, + # fix test case and remove allow_invalid_attestations flag + yield from tick_and_add_block(spec, store, block, test_steps, allow_invalid_attestations=True) + + finalized_slot = spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + ancestor_at_finalized_slot = spec.get_ancestor(store, pre_store_justified_checkpoint_root, finalized_slot) + assert ancestor_at_finalized_slot == store.finalized_checkpoint.root + + assert store.finalized_checkpoint == another_state.finalized_checkpoint + assert store.justified_checkpoint != another_state.current_justified_checkpoint + + yield 'steps', test_steps diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py index 18e23ade2..92382c884 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py @@ -1,11 +1,9 @@ from copy import deepcopy -from eth2spec.utils.ssz.ssz_impl import hash_tree_root -from eth2spec.test.context import MINIMAL, with_all_phases, spec_state_test, with_presets -from eth2spec.test.helpers.attestations import ( - next_epoch_with_attestations, - next_slots_with_attestations, - state_transition_with_signed_full_block, +from eth2spec.utils.ssz.ssz_impl import hash_tree_root +from eth2spec.test.context import ( + spec_state_test, + with_all_phases, ) from eth2spec.test.helpers.block import ( build_empty_block_for_next_slot, @@ -15,58 +13,25 @@ from eth2spec.test.helpers.fork_choice import ( run_on_block, apply_next_epoch_with_attestations, ) -from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_block, next_slots - - -@with_all_phases -@spec_state_test -def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): - # Initialization - store = get_genesis_forkchoice_store(spec, state) - - next_epoch(spec, state) - spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - state, store, last_signed_block = yield from apply_next_epoch_with_attestations(spec, state, store) - next_epoch(spec, state) - spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - last_block_root = last_signed_block.message.hash_tree_root() - - # NOTE: Mock the justified checkpoint - just_state = store.block_states[last_block_root] - new_justified = spec.Checkpoint( - epoch=just_state.current_justified_checkpoint.epoch + 1, - root=b'\x77' * 32, - ) - just_state.current_justified_checkpoint = new_justified # Mutate `store` - - assert store.block_states[last_block_root].hash_tree_root() == just_state.hash_tree_root() - - block = build_empty_block_for_next_slot(spec, just_state) - - # NOTE: Mock store so that the modified state could be accessed - parent_block = last_signed_block.message.copy() - parent_block.state_root = just_state.hash_tree_root() - store.blocks[block.parent_root] = parent_block - store.block_states[block.parent_root] = just_state.copy() - assert block.parent_root in store.blocks.keys() - assert block.parent_root in store.block_states.keys() - - signed_block = state_transition_and_sign_block(spec, just_state.copy(), block) - assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH < spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED - run_on_block(spec, store, signed_block) - - assert store.justified_checkpoint == new_justified +from eth2spec.test.helpers.state import ( + next_epoch, + state_transition_and_sign_block, +) @with_all_phases @spec_state_test def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): + """ + NOTE: test_new_justified_is_later_than_store_justified also tests best_justified_checkpoint + """ # Initialization store = get_genesis_forkchoice_store(spec, state) next_epoch(spec, state) spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - state, store, last_signed_block = yield from apply_next_epoch_with_attestations(spec, state, store) + state, store, last_signed_block = yield from apply_next_epoch_with_attestations( + spec, state, store, True, False) last_block_root = hash_tree_root(last_signed_block.message) # NOTE: Mock fictitious justified checkpoint in store @@ -86,6 +51,7 @@ def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): spec.on_tick(store, store.time + spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED * spec.config.SECONDS_PER_SLOT) assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + previously_finalized = store.finalized_checkpoint previously_justified = store.justified_checkpoint # Add a series of new blocks with "better" justifications @@ -115,355 +81,7 @@ def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): run_on_block(spec, store, signed_block) + assert store.finalized_checkpoint == previously_finalized assert store.justified_checkpoint == previously_justified # ensure the best from the series was stored assert store.best_justified_checkpoint == best_justified_checkpoint - - -@with_all_phases -@spec_state_test -def test_on_block_outside_safe_slots_but_finality(spec, state): - # Initialization - store = get_genesis_forkchoice_store(spec, state) - - next_epoch(spec, state) - spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - state, store, last_signed_block = yield from apply_next_epoch_with_attestations(spec, state, store) - last_block_root = hash_tree_root(last_signed_block.message) - - # NOTE: Mock fictitious justified checkpoint in store - store.justified_checkpoint = spec.Checkpoint( - epoch=spec.compute_epoch_at_slot(last_signed_block.message.slot), - root=spec.Root("0x4a55535449464945440000000000000000000000000000000000000000000000") - ) - - next_epoch(spec, state) - spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - - # NOTE: Mock a new higher justified checkpoint not in branch of store's justified checkpoint - just_block = build_empty_block_for_next_slot(spec, state) - store.blocks[just_block.hash_tree_root()] = just_block - - # Step time past safe slots - spec.on_tick(store, store.time + spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED * spec.config.SECONDS_PER_SLOT) - assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED - - # NOTE: Mock justified and finalized update in state - just_fin_state = store.block_states[last_block_root] - new_justified = spec.Checkpoint( - epoch=spec.compute_epoch_at_slot(just_block.slot) + 1, - root=just_block.hash_tree_root(), - ) - assert new_justified.epoch > store.justified_checkpoint.epoch - new_finalized = spec.Checkpoint( - epoch=spec.compute_epoch_at_slot(just_block.slot), - root=just_block.parent_root, - ) - assert new_finalized.epoch > store.finalized_checkpoint.epoch - just_fin_state.current_justified_checkpoint = new_justified - just_fin_state.finalized_checkpoint = new_finalized - - # Build and add block that includes the new justified/finalized info - block = build_empty_block_for_next_slot(spec, just_fin_state) - signed_block = state_transition_and_sign_block(spec, deepcopy(just_fin_state), block) - - # NOTE: Mock store so that the modified state could be accessed - parent_block = last_signed_block.message.copy() - parent_block.state_root = just_fin_state.hash_tree_root() - store.blocks[block.parent_root] = parent_block - store.block_states[block.parent_root] = just_fin_state.copy() - assert block.parent_root in store.blocks.keys() - assert block.parent_root in store.block_states.keys() - - run_on_block(spec, store, signed_block) - - assert store.finalized_checkpoint == new_finalized - assert store.justified_checkpoint == new_justified - - -@with_all_phases -@with_presets([MINIMAL], reason="It assumes that `MAX_ATTESTATIONS` >= 2/3 attestations of an epoch") -@spec_state_test -def test_new_justified_is_later_than_store_justified(spec, state): - """ - J: Justified - F: Finalized - fork_1_state (forked from genesis): - epoch - [0] <- [1] <- [2] <- [3] <- [4] - F J - - fork_2_state (forked from fork_1_state's epoch 2): - epoch - └──── [3] <- [4] <- [5] <- [6] - F J - - fork_3_state (forked from genesis): - [0] <- [1] <- [2] <- [3] <- [4] <- [5] - F J - """ - # The 1st fork, from genesis - fork_1_state = state.copy() - # The 3rd fork, from genesis - fork_3_state = state.copy() - - # Initialization - store = get_genesis_forkchoice_store(spec, fork_1_state) - - # ----- Process fork_1_state - # Skip epoch 0 - next_epoch(spec, fork_1_state) - # Fill epoch 1 with previous epoch attestations - _, signed_blocks, fork_1_state = next_epoch_with_attestations(spec, fork_1_state, False, True) - for block in signed_blocks: - spec.on_tick(store, store.genesis_time + fork_1_state.slot * spec.config.SECONDS_PER_SLOT) - run_on_block(spec, store, block) - - # Fork `fork_2_state` at the start of epoch 2 - fork_2_state = fork_1_state.copy() - assert spec.get_current_epoch(fork_2_state) == 2 - - # Skip epoch 2 - next_epoch(spec, fork_1_state) - # # Fill epoch 3 & 4 with previous epoch attestations - for _ in range(2): - _, signed_blocks, fork_1_state = next_epoch_with_attestations(spec, fork_1_state, False, True) - for block in signed_blocks: - spec.on_tick(store, store.genesis_time + fork_1_state.slot * spec.config.SECONDS_PER_SLOT) - run_on_block(spec, store, block) - - assert fork_1_state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 - assert fork_1_state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 - assert store.justified_checkpoint.hash_tree_root() == fork_1_state.current_justified_checkpoint.hash_tree_root() - - # ------ fork_2_state: Create a chain to set store.best_justified_checkpoint - # NOTE: The goal is to make `store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch` - all_blocks = [] - - # Proposed an empty block at epoch 2, 1st slot - block = build_empty_block_for_next_slot(spec, fork_2_state) - signed_block = state_transition_and_sign_block(spec, fork_2_state, block) - all_blocks.append(signed_block.copy()) - assert fork_2_state.current_justified_checkpoint.epoch == 0 - - # Skip to epoch 4 - for _ in range(2): - next_epoch(spec, fork_2_state) - assert fork_2_state.current_justified_checkpoint.epoch == 0 - - # Propose a block at epoch 4, 5th slot - # Propose a block at epoch 5, 5th slot - for _ in range(2): - next_epoch(spec, fork_2_state) - next_slots(spec, fork_2_state, 4) - signed_block = state_transition_with_signed_full_block(spec, fork_2_state, True, True) - all_blocks.append(signed_block.copy()) - assert fork_2_state.current_justified_checkpoint.epoch == 0 - - # Propose a block at epoch 6, SAFE_SLOTS_TO_UPDATE_JUSTIFIED + 2 slot - next_epoch(spec, fork_2_state) - next_slots(spec, fork_2_state, spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + 2) - signed_block = state_transition_with_signed_full_block(spec, fork_2_state, True, True) - all_blocks.append(signed_block.copy()) - 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) - assert spec.compute_slots_since_epoch_start(spec.get_current_slot(store)) >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED - - # 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) - run_on_block(spec, store, block) - - assert store.finalized_checkpoint.epoch == 0 - assert store.justified_checkpoint.epoch == 3 - assert store.best_justified_checkpoint.epoch == 5 - - # ------ fork_3_state: Create another chain to test the - # "Update justified if new justified is later than store justified" case - all_blocks = [] - for _ in range(3): - next_epoch(spec, fork_3_state) - - # epoch 3 - _, signed_blocks, fork_3_state = next_epoch_with_attestations(spec, fork_3_state, True, True) - all_blocks += signed_blocks - assert fork_3_state.finalized_checkpoint.epoch == 0 - - # epoch 4, attest the first 5 blocks - _, blocks, fork_3_state = next_slots_with_attestations(spec, fork_3_state, 5, True, True) - all_blocks += blocks.copy() - assert fork_3_state.finalized_checkpoint.epoch == 0 - - # Propose a block at epoch 5, 5th slot - next_epoch(spec, fork_3_state) - next_slots(spec, fork_3_state, 4) - signed_block = state_transition_with_signed_full_block(spec, fork_3_state, True, True) - all_blocks.append(signed_block.copy()) - assert fork_3_state.finalized_checkpoint.epoch == 0 - - # Propose a block at epoch 6, 5th slot - next_epoch(spec, fork_3_state) - next_slots(spec, fork_3_state, 4) - signed_block = state_transition_with_signed_full_block(spec, fork_3_state, True, True) - all_blocks.append(signed_block.copy()) - assert fork_3_state.finalized_checkpoint.epoch == 3 - assert fork_3_state.current_justified_checkpoint.epoch == 4 - - # Apply blocks of `fork_3_state` to `store` - for block in all_blocks: - if store.time < spec.compute_time_at_slot(fork_2_state, block.message.slot): - spec.on_tick(store, store.genesis_time + block.message.slot * spec.config.SECONDS_PER_SLOT) - run_on_block(spec, store, block) - - assert store.finalized_checkpoint.hash_tree_root() == fork_3_state.finalized_checkpoint.hash_tree_root() - assert (store.justified_checkpoint.hash_tree_root() - == fork_3_state.current_justified_checkpoint.hash_tree_root() - != store.best_justified_checkpoint.hash_tree_root()) - assert (store.best_justified_checkpoint.hash_tree_root() - == fork_2_state.current_justified_checkpoint.hash_tree_root()) - - -@with_all_phases -@spec_state_test -def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): - """ - J: Justified - F: Finalized - state (forked from genesis): - epoch - [0] <- [1] <- [2] <- [3] <- [4] <- [5] - F J - - another_state (forked from epoch 0): - └──── [1] <- [2] <- [3] <- [4] <- [5] - F J - """ - # Initialization - store = get_genesis_forkchoice_store(spec, state) - - # ----- Process state - # Goal: make `store.finalized_checkpoint.epoch == 0` and `store.justified_checkpoint.epoch == 3` - # Skip epoch 0 - next_epoch(spec, state) - - # Forking another_state - another_state = state.copy() - - # Fill epoch 1 with previous epoch attestations - _, signed_blocks, state = next_epoch_with_attestations(spec, state, False, True) - for block in signed_blocks: - spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - run_on_block(spec, store, block) - # Skip epoch 2 - next_epoch(spec, state) - # Fill epoch 3 & 4 with previous epoch attestations - for _ in range(2): - _, signed_blocks, state = next_epoch_with_attestations(spec, state, False, True) - for block in signed_blocks: - spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - run_on_block(spec, store, block) - - assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 - assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 - assert store.justified_checkpoint.hash_tree_root() == state.current_justified_checkpoint.hash_tree_root() - - # Create another chain - # Goal: make `another_state.finalized_checkpoint.epoch == 2` and `another_state.justified_checkpoint.epoch == 3` - all_blocks = [] - # Fill epoch 1 & 2 with previous + current epoch attestations - for _ in range(3): - _, signed_blocks, another_state = next_epoch_with_attestations(spec, another_state, True, True) - all_blocks += signed_blocks - - assert another_state.finalized_checkpoint.epoch == 2 - assert another_state.current_justified_checkpoint.epoch == 3 - assert state.finalized_checkpoint.hash_tree_root() != another_state.finalized_checkpoint.hash_tree_root() - assert ( - state.current_justified_checkpoint.hash_tree_root() - != another_state.current_justified_checkpoint.hash_tree_root() - ) - pre_store_justified_checkpoint_root = store.justified_checkpoint.root - - # Apply blocks of `another_state` to `store` - for block in all_blocks: - # NOTE: Do not call `on_tick` here - run_on_block(spec, store, block) - - finalized_slot = spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) - ancestor_at_finalized_slot = spec.get_ancestor(store, pre_store_justified_checkpoint_root, finalized_slot) - assert ancestor_at_finalized_slot != store.finalized_checkpoint.root - - assert store.finalized_checkpoint.hash_tree_root() == another_state.finalized_checkpoint.hash_tree_root() - assert store.justified_checkpoint.hash_tree_root() == another_state.current_justified_checkpoint.hash_tree_root() - - -@with_all_phases -@spec_state_test -def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): - """ - J: Justified - F: Finalized - state: - epoch - [0] <- [1] <- [2] <- [3] <- [4] <- [5] - F J - - another_state (forked from state at epoch 3): - └──── [4] <- [5] - F J - """ - # Initialization - store = get_genesis_forkchoice_store(spec, state) - - # Process state - next_epoch(spec, state) - spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - _, signed_blocks, state = next_epoch_with_attestations(spec, state, False, True) - for block in signed_blocks: - spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - run_on_block(spec, store, block) - _, signed_blocks, state = next_epoch_with_attestations(spec, state, True, False) - for block in signed_blocks: - spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - run_on_block(spec, store, block) - next_epoch(spec, state) - spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - for _ in range(2): - _, signed_blocks, state = next_epoch_with_attestations(spec, state, False, True) - for block in signed_blocks: - spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - run_on_block(spec, store, block) - - assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 - assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 4 - assert store.justified_checkpoint.hash_tree_root() == state.current_justified_checkpoint.hash_tree_root() - - # Create another chain - # Forking from epoch 3 - all_blocks = [] - slot = spec.compute_start_slot_at_epoch(3) - block_root = spec.get_block_root_at_slot(state, slot) - another_state = store.block_states[block_root].copy() - for _ in range(2): - _, signed_blocks, another_state = next_epoch_with_attestations(spec, another_state, True, True) - all_blocks += signed_blocks - - assert another_state.finalized_checkpoint.epoch == 3 - assert another_state.current_justified_checkpoint.epoch == 4 - - pre_store_justified_checkpoint_root = store.justified_checkpoint.root - for block in all_blocks: - if store.time < spec.compute_time_at_slot(another_state, block.message.slot): - spec.on_tick(store, store.genesis_time + block.message.slot * spec.config.SECONDS_PER_SLOT) - run_on_block(spec, store, block) - - finalized_slot = spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) - ancestor_at_finalized_slot = spec.get_ancestor(store, pre_store_justified_checkpoint_root, finalized_slot) - assert ancestor_at_finalized_slot == store.finalized_checkpoint.root - - assert store.finalized_checkpoint.hash_tree_root() == another_state.finalized_checkpoint.hash_tree_root() - assert store.justified_checkpoint.hash_tree_root() != another_state.current_justified_checkpoint.hash_tree_root() From 27763bdd869d98193e7be0a8b39d312f0bb6d1d4 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 30 Jun 2021 05:40:26 +0800 Subject: [PATCH 010/135] clean up --- .../test/phase0/fork_choice/test_on_block.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 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 c3374d4f8..634610fe7 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 @@ -86,7 +86,7 @@ def test_on_block_checkpoints(spec, state): # Run for 1 epoch with full attestations next_epoch(spec, state) - on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) + on_tick_and_append_step(spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) state, store, last_signed_block = yield from apply_next_epoch_with_attestations( spec, state, store, True, False, test_steps=test_steps) @@ -95,7 +95,7 @@ def test_on_block_checkpoints(spec, state): # Forward 1 epoch next_epoch(spec, state) - on_tick_and_append_step(spec, store, store.time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) + on_tick_and_append_step(spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) # Mock the finalized_checkpoint and build a block on it fin_state = store.block_states[last_block_root].copy() @@ -596,11 +596,9 @@ def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): assert another_state.finalized_checkpoint.epoch == 2 assert another_state.current_justified_checkpoint.epoch == 3 - assert state.finalized_checkpoint.hash_tree_root() != another_state.finalized_checkpoint.hash_tree_root() - assert ( - state.current_justified_checkpoint.hash_tree_root() - != another_state.current_justified_checkpoint.hash_tree_root() - ) + assert state.finalized_checkpoint != another_state.finalized_checkpoint + assert state.current_justified_checkpoint != another_state.current_justified_checkpoint + # pre_store_justified_checkpoint_root = store.justified_checkpoint.root # FIXME: pending on the `on_block`, `on_attestation` fix From 63ca480ea3f36f80e9f542a29262bc8e6ed3ef5d Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 14 Jul 2021 20:02:21 +0800 Subject: [PATCH 011/135] Add condition check in `on_tick` to ensure that `store.justified_checkpoint` is a descendant of `store.finalized_checkpoint` --- specs/phase0/fork-choice.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 181a874fb..d4a3a9b85 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -327,9 +327,13 @@ def on_tick(store: Store, time: uint64) -> None: # Not a new epoch, return if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0): return - # Update store.justified_checkpoint if a better checkpoint is known + + # Update store.justified_checkpoint if a better checkpoint on the store.finalized_checkpoint chain if store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch: - store.justified_checkpoint = store.best_justified_checkpoint + finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + ancestor_at_finalized_slot = get_ancestor(store, store.best_justified_checkpoint.root, finalized_slot) + if ancestor_at_finalized_slot == store.finalized_checkpoint.root: + store.justified_checkpoint = store.best_justified_checkpoint ``` #### `on_block` From cc3690ce3899cfe22a4d44890d63739f3aed093f Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 14 Jul 2021 20:05:14 +0800 Subject: [PATCH 012/135] Add unit tests to test the new condition. --- .../unittests/fork_choice/test_on_tick.py | 98 +++++++++++++++++-- 1 file changed, 90 insertions(+), 8 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_tick.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_tick.py index ef643fd6b..40606197e 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_tick.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_tick.py @@ -1,5 +1,13 @@ from eth2spec.test.context import with_all_phases, spec_state_test from eth2spec.test.helpers.fork_choice import get_genesis_forkchoice_store +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.state import ( + next_epoch, + state_transition_and_sign_block, + transition_to, +) def run_on_tick(spec, store, time, new_justified_checkpoint=False): @@ -26,18 +34,92 @@ def test_basic(spec, state): @with_all_phases @spec_state_test -def test_update_justified_single(spec, state): +def test_update_justified_single_on_store_finalized_chain(spec, state): store = get_genesis_forkchoice_store(spec, state) - next_epoch = spec.get_current_epoch(state) + 1 - next_epoch_start_slot = spec.compute_start_slot_at_epoch(next_epoch) - seconds_until_next_epoch = next_epoch_start_slot * spec.config.SECONDS_PER_SLOT - store.time - store.best_justified_checkpoint = spec.Checkpoint( - epoch=store.justified_checkpoint.epoch + 1, - root=b'\x55' * 32, + # [Mock store.best_justified_checkpoint] + # Create a block at epoch 1 + next_epoch(spec, state) + block = build_empty_block_for_next_slot(spec, state) + state_transition_and_sign_block(spec, state, block) + store.blocks[block.hash_tree_root()] = block.copy() + store.block_states[block.hash_tree_root()] = state.copy() + parent_block = block.copy() + # To make compute_slots_since_epoch_start(current_slot) == 0, transition to the end of the epoch + slot = state.slot + spec.SLOTS_PER_EPOCH - (state.slot + spec.SLOTS_PER_EPOCH) % spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, slot) + # Create a block at the start of epoch 2 + block = build_empty_block_for_next_slot(spec, state) + # Mock state + state.current_justified_checkpoint = spec.Checkpoint( + epoch=spec.compute_epoch_at_slot(parent_block.slot), + root=parent_block.hash_tree_root(), + ) + state_transition_and_sign_block(spec, state, block) + store.blocks[block.hash_tree_root()] = block + store.block_states[block.hash_tree_root()] = state + # Mock store.best_justified_checkpoint + store.best_justified_checkpoint = state.current_justified_checkpoint.copy() + + run_on_tick( + spec, + store, + store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, + new_justified_checkpoint=True ) - run_on_tick(spec, store, store.time + seconds_until_next_epoch, True) + +@with_all_phases +@spec_state_test +def test_update_justified_single_not_on_store_finalized_chain(spec, state): + store = get_genesis_forkchoice_store(spec, state) + init_state = state.copy() + + # Chain grows + # Create a block at epoch 1 + next_epoch(spec, state) + block = build_empty_block_for_next_slot(spec, state) + block.body.graffiti = b'\x11' * 32 + state_transition_and_sign_block(spec, state, block) + store.blocks[block.hash_tree_root()] = block.copy() + store.block_states[block.hash_tree_root()] = state.copy() + # Mock store.finalized_checkpoint + store.finalized_checkpoint = spec.Checkpoint( + epoch=spec.compute_epoch_at_slot(block.slot), + root=block.hash_tree_root(), + ) + + # [Mock store.best_justified_checkpoint] + # Create a block at epoch 1 + state = init_state.copy() + next_epoch(spec, state) + block = build_empty_block_for_next_slot(spec, state) + block.body.graffiti = b'\x22' * 32 + state_transition_and_sign_block(spec, state, block) + store.blocks[block.hash_tree_root()] = block.copy() + store.block_states[block.hash_tree_root()] = state.copy() + parent_block = block.copy() + # To make compute_slots_since_epoch_start(current_slot) == 0, transition to the end of the epoch + slot = state.slot + spec.SLOTS_PER_EPOCH - (state.slot + spec.SLOTS_PER_EPOCH) % spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, slot) + # Create a block at the start of epoch 2 + block = build_empty_block_for_next_slot(spec, state) + # Mock state + state.current_justified_checkpoint = spec.Checkpoint( + epoch=spec.compute_epoch_at_slot(parent_block.slot), + root=parent_block.hash_tree_root(), + ) + state_transition_and_sign_block(spec, state, block) + store.blocks[block.hash_tree_root()] = block.copy() + store.block_states[block.hash_tree_root()] = state.copy() + # Mock store.best_justified_checkpoint + store.best_justified_checkpoint = state.current_justified_checkpoint.copy() + + run_on_tick( + spec, + store, + store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, + ) @with_all_phases From 3b34f16e5a6d61cc67d6ffec8f4f4e70f604adca Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 22 Jul 2021 11:07:04 -0600 Subject: [PATCH 013/135] add base merge p2p spec --- specs/merge/p2p-interface.md | 111 +++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 specs/merge/p2p-interface.md diff --git a/specs/merge/p2p-interface.md b/specs/merge/p2p-interface.md new file mode 100644 index 000000000..4af3f8e10 --- /dev/null +++ b/specs/merge/p2p-interface.md @@ -0,0 +1,111 @@ +# Ethereum Merge networking specification + +This document contains the networking specification for Ethereum 2.0 clients added during the Merge deployment. + +The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite. This document should be viewed as additive to the documents from [Phase 0](../phase0/p2p-interface.md) and from [Altair](../altair/p2p-interface.md) +and will be referred to as the "Phase 0 document" and "Altair document" respectively, hereafter. +Readers should understand the Phase 0 and Altair documents and use them as a basis to understand the changes outlined in this document. + +## Table of contents + + + + + + - [Warning](#warning) +- [Modifications in the Merge](#modifications-in-the-merge) + - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) + - [Topics and messages](#topics-and-messages) + - [Global topics](#global-topics) + - [`beacon_block`](#beacon_block) + - [Transitioning the gossip](#transitioning-the-gossip) + - [The Req/Resp domain](#the-reqresp-domain) + - [Messages](#messages) + - [BeaconBlocksByRange v2](#beaconblocksbyrange-v2) + - [BeaconBlocksByRoot v2](#beaconblocksbyroot-v2) + + + + +## Warning + +This document is currently illustrative for early Merge testnets and some parts are subject to change. +Refer to the note in the [validator guide](./validator.md) for further details. + +# Modifications in the Merge + +## The gossip domain: gossipsub + +Some gossip meshes are upgraded in the Merge to support upgraded types. + +### Topics and messages + +Topics follow the same specification as in prior upgrades. +All topics remain stable except the beacon block topic which is updated with the modified type. + +The specification around the creation, validation, and dissemination of messages has not changed from the Phase 0 and Altair documents. + +The derivation of the `message-id` remains stable. + +The new topics along with the type of the `data` field of a gossipsub message are given in this table: + +| Name | Message Type | +| - | - | +| `beacon_block` | `SignedBeaconBlock` (modified) | + +Note that the `ForkDigestValue` path segment of the topic separates the old and the new `beacon_block` topics. + +#### Global topics + +The Merge changes the type of the global beacon block topic. + +##### `beacon_block` + +The existing specification for this topic does not change from prior upgrades, +but the type of the payload does change to the (modified) `SignedBeaconBlock` found in the Merge. +This type changes due to the addition of `execution_payload` to the inner `BeaconBlockBody`. + +See the Merge [state transition document](./beacon-chain.md#beaconblockbody) for further details. + +### Transitioning the gossip + +See gossip transition details found in the [Altair document](../altair/p2p) for +details on how to handle transitioning gossip topics for the Merge. + +## The Req/Resp domain + +### Messages + +#### BeaconBlocksByRange v2 + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_range/2/` + +Request and Response remain unchanged. +`MERGE_FORK_VERSION` is used as an additional `context` to specify the Merge block type. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + +[0]: # (eth2spec: skip) + +| `fork_version` | Chunk SSZ type | +| ------------------------ | -------------------------- | +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `MERGE_FORK_VERSION` | `merge.SignedBeaconBlock` | + +#### BeaconBlocksByRoot v2 + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/2/` + +Request and Response remain unchanged. +`MERGE_FORK_VERSION` is used as an additional `context` to specify the Merge block type. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + +[1]: # (eth2spec: skip) + +| `fork_version` | Chunk SSZ type | +| ------------------------ | -------------------------- | +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `MERGE_FORK_VERSION` | `merge.SignedBeaconBlock` | From bc936768c74cd8afb72fd383dd76750ebdc00c8e Mon Sep 17 00:00:00 2001 From: protolambda Date: Thu, 17 Jun 2021 23:17:47 +0200 Subject: [PATCH 014/135] global selection of shard proposers --- specs/sharding/beacon-chain.md | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 517892b8c..41497d8d8 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -43,7 +43,6 @@ - [Beacon state accessors](#beacon-state-accessors) - [Updated `get_committee_count_per_slot`](#updated-get_committee_count_per_slot) - [`get_active_shard_count`](#get_active_shard_count) - - [`get_shard_committee`](#get_shard_committee) - [`compute_proposer_index`](#compute_proposer_index) - [`get_shard_proposer_index`](#get_shard_proposer_index) - [`get_start_shard`](#get_start_shard) @@ -369,24 +368,6 @@ def get_active_shard_count(state: BeaconState, epoch: Epoch) -> uint64: return INITIAL_ACTIVE_SHARDS ``` -#### `get_shard_committee` - -```python -def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) -> Sequence[ValidatorIndex]: - """ - Return the shard committee of the given ``epoch`` of the given ``shard``. - """ - source_epoch = compute_committee_source_epoch(epoch, SHARD_COMMITTEE_PERIOD) - active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) - seed = get_seed(beacon_state, source_epoch, DOMAIN_SHARD_COMMITTEE) - return compute_committee( - indices=active_validator_indices, - seed=seed, - index=shard, - count=get_active_shard_count(beacon_state, epoch), - ) -``` - #### `compute_proposer_index` Updated version to get a proposer index that will only allow proposers with a certain minimum balance, @@ -423,8 +404,7 @@ def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard Return the proposer's index of shard block at ``slot``. """ epoch = compute_epoch_at_slot(slot) - committee = get_shard_committee(beacon_state, epoch, shard) - seed = hash(get_seed(beacon_state, epoch, DOMAIN_SHARD_PROPOSER) + uint_to_bytes(slot)) + seed = hash(get_seed(beacon_state, epoch, DOMAIN_SHARD_PROPOSER) + uint_to_bytes(slot) + uint_to_bytes(shard)) # Proposer must have sufficient balance to pay for worst case fee burn EFFECTIVE_BALANCE_MAX_DOWNWARD_DEVIATION = ( @@ -435,7 +415,8 @@ def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard beacon_state.shard_gasprice * MAX_SAMPLES_PER_BLOCK // TARGET_SAMPLES_PER_BLOCK + EFFECTIVE_BALANCE_MAX_DOWNWARD_DEVIATION ) - return compute_proposer_index(beacon_state, committee, seed, min_effective_balance) + indices = get_active_validator_indices(state, epoch) + return compute_proposer_index(beacon_state, indices, seed, min_effective_balance) ``` #### `get_start_shard` From 4b2523961723717c49a61e6f5587d4802211a33d Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 18 Jun 2021 02:21:21 +0200 Subject: [PATCH 015/135] builders make blobs, proposers make blocks --- specs/sharding/beacon-chain.md | 264 +++++++++++++++++++++++++++----- specs/sharding/p2p-interface.md | 45 ------ 2 files changed, 224 insertions(+), 85 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 41497d8d8..acba8e2a5 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -9,6 +9,7 @@ - [Introduction](#introduction) + - [Glossary](#glossary) - [Custom types](#custom-types) - [Constants](#constants) - [Misc](#misc) @@ -23,15 +24,25 @@ - [Updated containers](#updated-containers) - [`AttestationData`](#attestationdata) - [`BeaconBlockBody`](#beaconblockbody) + - [`Builder`](#builder) - [`BeaconState`](#beaconstate) - [New containers](#new-containers) - [`DataCommitment`](#datacommitment) + - [ShardBlobBody](#shardblobbody) - [`ShardBlobBodySummary`](#shardblobbodysummary) + - [`ShardBlob`](#shardblob) - [`ShardBlobHeader`](#shardblobheader) + - [`SignedShardBlob`](#signedshardblob) - [`SignedShardBlobHeader`](#signedshardblobheader) + - [ShardBlock](#shardblock) + - [`ShardBlockHeader`](#shardblockheader) + - [`SignedShardBlock`](#signedshardblock) + - [`SignedShardBlockHeader`](#signedshardblockheader) - [`PendingShardHeader`](#pendingshardheader) - [`ShardBlobReference`](#shardblobreference) - [`SignedShardBlobReference`](#signedshardblobreference) + - [`ShardBlockReference`](#shardblockreference) + - [`SignedShardBlockReference`](#signedshardblockreference) - [`ShardProposerSlashing`](#shardproposerslashing) - [`ShardWork`](#shardwork) - [Helper functions](#helper-functions) @@ -51,6 +62,7 @@ - [Block processing](#block-processing) - [Operations](#operations) - [Extended Attestation processing](#extended-attestation-processing) + - [`charge_builder`](#charge_builder) - [`process_shard_header`](#process_shard_header) - [`process_shard_proposer_slashing`](#process_shard_proposer_slashing) - [Epoch transition](#epoch-transition) @@ -68,6 +80,13 @@ This document describes the extensions made to the Phase 0 design of The Beacon based on the ideas [here](https://hackmd.io/G-Iy5jqyT7CXWEz8Ssos8g) and more broadly [here](https://arxiv.org/abs/1809.09044), using KZG10 commitments to commit to data to remove any need for fraud proofs (and hence, safety-critical synchrony assumptions) in the design. +### Glossary + +- **Data**: A list of KZG points, to translate a byte string into +- **Blob**: Data with commitments and meta-data, like a flattened bundle of L2 transactions. +- **Builder**: Builds blobs and bids for proposal slots with fee-paying blob-headers, responsible for availability. +- **Shard proposer**: Validator, selects a signed blob-header, taking bids for shard data opportunity. +- **Shard block**: Unique per `(slot, shard, proposer)`, selected signed blob ## Custom types @@ -78,6 +97,7 @@ We define the following Python custom types for type hinting and readability: | `Shard` | `uint64` | A shard number | | `BLSCommitment` | `Bytes48` | A G1 curve point | | `BLSPoint` | `uint256` | A number `x` in the range `0 <= x < MODULUS` | +| `BuilderIndex` | `uint64` | Builder registry index | ## Constants @@ -97,7 +117,7 @@ The following values are (non-configurable) constants used throughout the specif | Name | Value | | - | - | | `DOMAIN_SHARD_PROPOSER` | `DomainType('0x80000000')` | -| `DOMAIN_SHARD_COMMITTEE` | `DomainType('0x81000000')` | +| `DOMAIN_SHARD_BUILDER` | `DomainType('0x81000000')` | ### Shard Work Status @@ -118,6 +138,7 @@ The following values are (non-configurable) constants used throughout the specif | `MAX_SHARD_PROPOSER_SLASHINGS` | `2**4` (= 16) | Maximum amount of shard proposer slashing operations per block | | `MAX_SHARD_HEADERS_PER_SHARD` | `4` | | | `SHARD_STATE_MEMORY_SLOTS` | `uint64(2**8)` (= 256) | Number of slots for which shard commitments and confirmation status is directly available in the state | +| `BUILDER_REGISTRY_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | builders | ### Shard block samples @@ -162,8 +183,8 @@ class AttestationData(Container): # FFG vote source: Checkpoint target: Checkpoint - # Shard header root - shard_header_root: Root # [New in Sharding] + # Hash-tree-root of ShardBlock + shard_block_root: Root # [New in Sharding] ``` ### `BeaconBlockBody` @@ -171,7 +192,16 @@ class AttestationData(Container): ```python class BeaconBlockBody(merge.BeaconBlockBody): # [extends The Merge block body] shard_proposer_slashings: List[ShardProposerSlashing, MAX_SHARD_PROPOSER_SLASHINGS] - shard_headers: List[SignedShardBlobHeader, MAX_SHARDS * MAX_SHARD_HEADERS_PER_SHARD] + shard_headers: List[SignedShardBlockHeader, MAX_SHARDS * MAX_SHARD_HEADERS_PER_SHARD] +``` + +### `Builder` + +```python +class Builder(Container): + pubkey: BLSPubkey + # TODO: fields for either an expiry mechanism (refunding execution account with remaining balance) + # and/or a builder-transaction mechanism. ``` ### `BeaconState` @@ -182,6 +212,9 @@ class BeaconState(merge.BeaconState): previous_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH] current_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH] # [New fields] + # Builder registry. + builders: List[Builder, BUILDER_REGISTRY_LIMIT] + builder_balances: List[Gwei, BUILDER_REGISTRY_LIMIT] # A ring buffer of the latest slots, with information per active shard. shard_buffer: Vector[List[ShardWork, MAX_SHARDS], SHARD_STATE_MEMORY_SLOTS] shard_gasprice: uint64 @@ -189,9 +222,6 @@ class BeaconState(merge.BeaconState): ## New containers -The shard data itself is network-layer only, and can be found in the [P2P specification](./p2p-interface.md). -The beacon chain registers just the commitments of the shard data. - ### `DataCommitment` ```python @@ -202,8 +232,33 @@ class DataCommitment(Container): length: uint64 ``` +### ShardBlobBody + +Unsigned shard data, bundled by a shard-builder. +Unique, signing different bodies as shard proposer for the same `(slot, shard)` is slashable. + +```python +class ShardBlobBody(Container): + # The actual data commitment + commitment: DataCommitment + # Proof that the degree < commitment.length + degree_proof: BLSCommitment + # The actual data. Should match the commitment and degree proof. + data: List[BLSPoint, POINTS_PER_SAMPLE * MAX_SAMPLES_PER_BLOCK] + # Latest block root of the Beacon Chain, before shard_blob.slot + beacon_block_root: Root + # Builder of the data, pays data-fee to proposer + builder_index: BuilderIndex + # TODO: fee payment amount fields (EIP 1559 like) +``` + ### `ShardBlobBodySummary` +Summary version of the `ShardBlobBody`, omitting the data payload, while preserving the data-commitments. + +The commitments are not further collapsed to a single hash, +to avoid an extra network roundtrip between proposer and builder, to include the header on-chain more quickly. + ```python class ShardBlobBodySummary(Container): # The actual data commitment @@ -214,36 +269,110 @@ class ShardBlobBodySummary(Container): data_root: Root # Latest block root of the Beacon Chain, before shard_blob.slot beacon_block_root: Root + # Builder of the data, pays data-fee to proposer + builder_index: BuilderIndex + # TODO: fee payment amount fields (EIP 1559 like) +``` + +### `ShardBlob` + +`ShardBlobBody` wrapped with the header data that is unique to the shard blob proposal. + +```python +class ShardBlob(Container): + slot: Slot + shard: Shard + # Blob contents + body: ShardBlobBody ``` ### `ShardBlobHeader` +Header version of `ShardBlob`. Separates designation (slot, shard) and contents (blob). + ```python class ShardBlobHeader(Container): - # Slot and shard that this header is intended for slot: Slot shard: Shard - # SSZ-summary of ShardBlobBody + # Blob contents, without the full data body_summary: ShardBlobBodySummary - # Proposer of the shard-blob - proposer_index: ValidatorIndex +``` + +### `SignedShardBlob` + +Full blob data, signed by the shard builder, ensuring fee payment. + +```python +class SignedShardBlob(Container): + message: ShardBlob + signature: BLSSignature ``` ### `SignedShardBlobHeader` +Header of the blob, the signature is equally applicable to `SignedShardBlob`. +Shard proposers can accept `SignedShardBlobHeader` as a data-transaction. + ```python class SignedShardBlobHeader(Container): message: ShardBlobHeader signature: BLSSignature ``` +### ShardBlock + +Full blob data signed by builder, to be confirmed by proxy as `ShardBlockHeader`. + +```python +class ShardBlock(Container): + # Shard data with fee payment by bundle builder + signed_blob: SignedShardBlob + # Proposer of the shard-blob + proposer_index: ValidatorIndex +``` + +### `ShardBlockHeader` + +Header version of `ShardBlock`, selecting a `SignedShardBlobHeader`. + +```python +class ShardBlockHeader(Container): + # Shard commitments and fee payment by blob builder + signed_blob_header: SignedShardBlobHeader + # Proposer of the shard-blob + proposer_index: ValidatorIndex +``` + +### `SignedShardBlock` + +Shard blob, signed for payment, and signed for proposal. Propagated to attesters. + +```python +class SignedShardBlock(Container): + message: ShardBlock + signature: BLSSignature +``` + +### `SignedShardBlockHeader` + +Header version of `SignedShardBlock`, substituting the full data within the blob for just the hash-tree-root. + +The signature is equally applicable to `SignedShardBlock`, +which the builder can publish as soon as the signed header is seen. + +```python +class SignedShardBlockHeader(Container): + message: ShardBlockHeader + signature: BLSSignature +``` + ### `PendingShardHeader` ```python class PendingShardHeader(Container): # KZG10 commitment to the data commitment: DataCommitment - # hash_tree_root of the ShardHeader (stored so that attestations can be checked against it) + # hash_tree_root of the ShardBlockHeader (stored so that attestations can be checked against it) root: Root # Who voted for the header votes: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] @@ -255,31 +384,50 @@ class PendingShardHeader(Container): ### `ShardBlobReference` +Reference version of `ShardBlobHeader`, substituting the body for just a hash-tree-root. + ```python class ShardBlobReference(Container): - # Slot and shard that this reference is intended for slot: Slot shard: Shard - # Hash-tree-root of ShardBlobBody + # Blob hash-tree-root for reference, enough for uniqueness body_root: Root - # Proposer of the shard-blob - proposer_index: ValidatorIndex ``` ### `SignedShardBlobReference` +`ShardBlobReference`, signed by the blob builder. The builder-signature is part of the block identity. + ```python class SignedShardBlobReference(Container): message: ShardBlobReference signature: BLSSignature ``` +### `ShardBlockReference` + +```python +class ShardBlockReference(Container): + # Blob, minimized for efficient slashing + signed_blob_reference: SignedShardBlobReference + # Proposer of the shard-blob + proposer_index: ValidatorIndex +``` + +### `SignedShardBlockReference` + +```python +class SignedShardBlockReference(Container): + message: ShardBlockReference + signature: BLSSignature +``` + ### `ShardProposerSlashing` ```python class ShardProposerSlashing(Container): - signed_reference_1: SignedShardBlobReference - signed_reference_2: SignedShardBlobReference + signed_reference_1: SignedShardBlockReference + signed_reference_2: SignedShardBlockReference ``` ### `ShardWork` @@ -516,7 +664,7 @@ def update_pending_shard_work(state: BeaconState, attestation: Attestation) -> N current_headers: Sequence[PendingShardHeader] = committee_work.status.value # Find the corresponding header, abort if it cannot be found - header_index = [header.root for header in current_headers].index(attestation.data.shard_header_root) + header_index = [header.root for header in current_headers].index(attestation.data.shard_block_root) pending_header: PendingShardHeader = current_headers[header_index] full_committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index) @@ -554,36 +702,63 @@ def update_pending_shard_work(state: BeaconState, attestation: Attestation) -> N ) ``` + +#### `charge_builder` + +```python +def charge_builder(state: BeaconState, index: BuilderIndex, fee: Gwei) -> None: + """ + Decrease the builder balance at index ``index`` by ``fee``, with underflow check. + """ + assert state.builder_balances[index] >= fee + state.builder_balances[index] -= fee +``` + ##### `process_shard_header` ```python -def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeader) -> None: - header = signed_header.message +def process_shard_header(state: BeaconState, signed_block_header: SignedShardBlockHeader) -> None: + block_header: ShardBlockHeader = signed_block_header.message + signed_blob_header: SignedShardBlobHeader = block_header.signed_blob_header + blob_header: ShardBlobHeader = signed_blob_header.message + slot = blob_header.slot + shard = blob_header.shard + # Verify the header is not 0, and not from the future. - assert Slot(0) < header.slot <= state.slot - header_epoch = compute_epoch_at_slot(header.slot) + assert Slot(0) < slot <= state.slot + header_epoch = compute_epoch_at_slot(slot) # Verify that the header is within the processing time window assert header_epoch in [get_previous_epoch(state), get_current_epoch(state)] # Verify that the shard is active - assert header.shard < get_active_shard_count(state, header_epoch) + assert shard < get_active_shard_count(state, header_epoch) # Verify that the block root matches, # to ensure the header will only be included in this specific Beacon Chain sub-tree. - assert header.body_summary.beacon_block_root == get_block_root_at_slot(state, header.slot - 1) + assert blob_header.body_summary.beacon_block_root == get_block_root_at_slot(state, slot - 1) # Check that this data is still pending - committee_work = state.shard_buffer[header.slot % SHARD_STATE_MEMORY_SLOTS][header.shard] + committee_work = state.shard_buffer[slot % SHARD_STATE_MEMORY_SLOTS][shard] assert committee_work.status.selector == SHARD_WORK_PENDING # Check that this header is not yet in the pending list current_headers: List[PendingShardHeader, MAX_SHARD_HEADERS_PER_SHARD] = committee_work.status.value - header_root = hash_tree_root(header) + header_root = hash_tree_root(block_header) assert header_root not in [pending_header.root for pending_header in current_headers] # Verify proposer - assert header.proposer_index == get_shard_proposer_index(state, header.slot, header.shard) - # Verify signature - signing_root = compute_signing_root(header, get_domain(state, DOMAIN_SHARD_PROPOSER)) - assert bls.Verify(state.validators[header.proposer_index].pubkey, signing_root, signed_header.signature) + assert block_header.proposer_index == get_shard_proposer_index(state, slot, shard) + # Verify proposer signature + block_signing_root = compute_signing_root(block_header, get_domain(state, DOMAIN_SHARD_PROPOSER)) + proposer_pubkey = state.validators[block_header.proposer_index].pubkey + assert bls.Verify(proposer_pubkey, block_signing_root, signed_block_header.signature) + + # Verify builder requirements + blob_summary: ShardBlobBodySummary = blob_header.body_summary + builder_index = blob_summary.builder_index + + # Verify builder signature + builder = state.builders[builder_index] + blob_signing_root = compute_signing_root(blob_header, get_domain(state, DOMAIN_SHARD_BUILDER)) # TODO new constant + assert bls.Verify(builder.pubkey, blob_signing_root, signed_blob_header.signature) # Verify the length by verifying the degree. body_summary = header.body_summary @@ -594,12 +769,16 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade == bls.Pairing(body_summary.commitment.point, G2_SETUP[-body_summary.commitment.length]) ) + # Charge builder, with hard balance requirement + fee = Gwei(123) # TODO + charge_builder(state, builder_index, fee) + # Initialize the pending header - index = compute_committee_index_from_shard(state, header.slot, header.shard) - committee_length = len(get_beacon_committee(state, header.slot, index)) + index = compute_committee_index_from_shard(state, slot, shard) + committee_length = len(get_beacon_committee(state, slot, index)) initial_votes = Bitlist[MAX_VALIDATORS_PER_COMMITTEE]([0] * committee_length) pending_header = PendingShardHeader( - commitment=body_summary.commitment, + commitment=blob_summary.commitment, root=header_root, votes=initial_votes, weight=0, @@ -619,17 +798,22 @@ The goal is to ensure that a proof can only be constructed if `deg(B) < l` (ther ```python def process_shard_proposer_slashing(state: BeaconState, proposer_slashing: ShardProposerSlashing) -> None: - reference_1 = proposer_slashing.signed_reference_1.message - reference_2 = proposer_slashing.signed_reference_2.message + reference_1: ShardBlockReference = proposer_slashing.signed_reference_1.message + reference_2 : ShardBlockReference = proposer_slashing.signed_reference_2.message + blob_1 = reference_1.signed_blob_reference.message + blob_2 = reference_2.signed_blob_reference.message # Verify header slots match - assert reference_1.slot == reference_2.slot + assert blob_1.slot == blob_2.slot # Verify header shards match - assert reference_1.shard == reference_2.shard + assert blob_1.shard == blob_2.shard # Verify header proposer indices match assert reference_1.proposer_index == reference_2.proposer_index - # Verify the headers are different (i.e. different body) - assert reference_1 != reference_2 + # Verify the headers are different (i.e. different body, or different builder signature) + assert ( + blob_1.body_root != blob_2.body_root + or reference_1.signed_blob_reference.signature != reference_2.signed_blob_reference.signature + ) # Verify the proposer is slashable proposer = state.validators[reference_1.proposer_index] assert is_slashable_validator(proposer, get_current_epoch(state)) diff --git a/specs/sharding/p2p-interface.md b/specs/sharding/p2p-interface.md index baf9494f2..d00321f36 100644 --- a/specs/sharding/p2p-interface.md +++ b/specs/sharding/p2p-interface.md @@ -11,10 +11,6 @@ - [Introduction](#introduction) - [Constants](#constants) - [Misc](#misc) -- [New containers](#new-containers) - - [ShardBlobBody](#shardblobbody) - - [ShardBlob](#shardblob) - - [SignedShardBlob](#signedshardblob) - [Gossip domain](#gossip-domain) - [Topics and messages](#topics-and-messages) - [Shard blob subnets](#shard-blob-subnets) @@ -40,47 +36,6 @@ The adjustments and additions for Shards are outlined in this document. | ---- | ----- | ----------- | | `SHARD_BLOB_SUBNET_COUNT` | `64` | The number of `shard_blob_{subnet_id}` subnets used in the gossipsub protocol. | -## New containers - -### ShardBlobBody - -```python -class ShardBlobBody(Container): - # The actual data commitment - commitment: DataCommitment - # Proof that the degree < commitment.length - degree_proof: BLSCommitment - # The actual data. Should match the commitment and degree proof. - data: List[BLSPoint, POINTS_PER_SAMPLE * MAX_SAMPLES_PER_BLOCK] - # Latest block root of the Beacon Chain, before shard_blob.slot - beacon_block_root: Root -``` - -The user MUST always verify the commitments in the `body` are valid for the `data` in the `body`. - -### ShardBlob - -```python -class ShardBlob(Container): - # Slot and shard that this blob is intended for - slot: Slot - shard: Shard - # Shard data with related commitments and beacon anchor - body: ShardBlobBody - # Proposer of the shard-blob - proposer_index: ValidatorIndex -``` - -This is the expanded form of the `ShardBlobHeader` type. - -### SignedShardBlob - -```python -class SignedShardBlob(Container): - message: ShardBlob - signature: BLSSignature -``` - ## Gossip domain ### Topics and messages From 9e10f582993fc1f48f01c6b57e737c34b84ce4c5 Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 18 Jun 2021 02:48:58 +0200 Subject: [PATCH 016/135] update networking spec --- specs/sharding/p2p-interface.md | 85 ++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/specs/sharding/p2p-interface.md b/specs/sharding/p2p-interface.md index d00321f36..94dd75128 100644 --- a/specs/sharding/p2p-interface.md +++ b/specs/sharding/p2p-interface.md @@ -14,7 +14,7 @@ - [Gossip domain](#gossip-domain) - [Topics and messages](#topics-and-messages) - [Shard blob subnets](#shard-blob-subnets) - - [`shard_blob_{subnet_id}`](#shard_blob_subnet_id) + - [`shard_block_{subnet_id}`](#shard_block_subnet_id) - [Global topics](#global-topics) - [`shard_header`](#shard_header) - [`shard_proposer_slashing`](#shard_proposer_slashing) @@ -34,7 +34,7 @@ The adjustments and additions for Shards are outlined in this document. | Name | Value | Description | | ---- | ----- | ----------- | -| `SHARD_BLOB_SUBNET_COUNT` | `64` | The number of `shard_blob_{subnet_id}` subnets used in the gossipsub protocol. | +| `SHARD_BLOCK_SUBNET_COUNT` | `64` | The number of `shard_block_{subnet_id}` subnets used in the gossipsub protocol. | ## Gossip domain @@ -44,22 +44,22 @@ Following the same scheme as the [Phase0 gossip topics](../phase0/p2p-interface. | Name | Message Type | |----------------------------------|---------------------------| -| `shard_blob_{subnet_id}` | `SignedShardBlob` | -| `shard_header` | `SignedShardBlobHeader` | +| `shard_block_{subnet_id}` | `SignedShardBlock` | +| `shard_block_header` | `SignedShardBlockHeader` | | `shard_proposer_slashing` | `ShardProposerSlashing` | The [DAS network specification](./das-p2p.md) defines additional topics. -#### Shard blob subnets +#### Shard block subnets -Shard blob subnets are used to propagate shard blobs to subsections of the network. +Shard block subnets are used by builders to make their blobs available after selection by shard proposers. -##### `shard_blob_{subnet_id}` +##### `shard_block_{subnet_id}` -Shard block data, in the form of a `SignedShardBlob` is published to the `shard_blob_{subnet_id}` subnets. +Shard block data, in the form of a `SignedShardBlock` is published to the `shard_block_{subnet_id}` subnets. ```python -def compute_subnet_for_shard_blob(state: BeaconState, slot: Slot, shard: Shard) -> uint64: +def compute_subnet_for_shard_block(state: BeaconState, slot: Slot, shard: Shard) -> uint64: """ Compute the correct subnet for a shard blob publication. Note, this mimics compute_subnet_for_attestation(). @@ -69,11 +69,19 @@ def compute_subnet_for_shard_blob(state: BeaconState, slot: Slot, shard: Shard) slots_since_epoch_start = Slot(slot % SLOTS_PER_EPOCH) committees_since_epoch_start = committees_per_slot * slots_since_epoch_start - return uint64((committees_since_epoch_start + committee_index) % SHARD_BLOB_SUBNET_COUNT) + return uint64((committees_since_epoch_start + committee_index) % SHARD_BLOCK_SUBNET_COUNT) ``` -The following validations MUST pass before forwarding the `signed_blob` (with inner `message` as `blob`) on the horizontal subnet or creating samples for it. -- _[IGNORE]_ The `blob` is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- +The following validations MUST pass before forwarding the `signed_block` on the horizontal subnet or creating samples for it. + +We define some aliases to the nested contents of `signed_block`: +```python +block: ShardBlock = signed_block.message +signed_blob: SignedShardBlob = block.signed_blob +blob: ShardBlob = signed_blob.message +``` + +- _[IGNORE]_ The `block` is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that `blob.slot <= current_slot` (a client MAY queue future blobs for processing at the appropriate slot). - _[IGNORE]_ The `blob` is new enough to be still be processed -- @@ -81,36 +89,49 @@ The following validations MUST pass before forwarding the `signed_blob` (with in - _[REJECT]_ The shard should have a committee at slot -- i.e. validate that `compute_committee_index_from_shard(state, blob.slot, blob.shard)` doesn't raise an error - _[REJECT]_ The shard blob is for the correct subnet -- - i.e. `compute_subnet_for_shard_blob(state, blob.slot, blob.shard) == subnet_id` -- _[IGNORE]_ The blob is the first blob with valid signature received for the `(blob.proposer_index, blob.slot, blob.shard)` combination. -- _[REJECT]_ As already limited by the SSZ list-limit, it is important the blob is well-formatted and not too large. + i.e. `compute_subnet_for_shard_block(state, blob.slot, blob.shard) == subnet_id` +- _[IGNORE]_ The block is the first block with valid signature received for the `(block.proposer_index, blob.slot, blob.shard)` combination. +- _[REJECT]_ The blob is not too large, the data MUST NOT be larger than the SSZ list-limit, and a client MAY be more strict. - _[REJECT]_ The `blob.body.data` MUST NOT contain any point `p >= MODULUS`. Although it is a `uint256`, not the full 256 bit range is valid. -- _[REJECT]_ The proposer signature, `signed_blob.signature`, is valid with respect to the `proposer_index` pubkey. -- _[REJECT]_ The blob is proposed by the expected `proposer_index` for the blob's slot +- _[REJECT]_ The block proposer signature, `signed_block.signature`, is valid with respect to the `proposer_index` pubkey. +- _[REJECT]_ The blob builder exists and has sufficient balance to back the fee payment. +- _[REJECT]_ The blob builder signature, `signed_blob.signature`, is valid with respect to the `builder_index` pubkey. +- _[REJECT]_ The block is proposed by the expected `proposer_index` for the block's slot in the context of the current shuffling (defined by `blob.body.beacon_block_root`/`slot`). If the `proposer_index` cannot immediately be verified against the expected shuffling, - the block MAY be queued for later processing while proposers for the blob's branch are calculated -- + the block MAY be queued for later processing while proposers for the block's branch are calculated -- in such a case _do not_ `REJECT`, instead `IGNORE` this message. #### Global topics -There are two additional global topics for Sharding, one is used to propagate shard blob headers (`shard_header`) to -all nodes on the network. Another one is used to propagate validator message (`shard_proposer_slashing`). +There are two additional global topics for Sharding. -##### `shard_header` +One is used to propagate shard block headers (`shard_block_header`) to all nodes on the network. +Another one is used to propagate shard proposer slashings (`shard_proposer_slashing`). -Shard header data, in the form of a `SignedShardBlobHeader` is published to the global `shard_header` subnet. +##### `shard_block_header` -The following validations MUST pass before forwarding the `signed_shard_blob_header` (with inner `message` as `header`) on the network. -- _[IGNORE]_ The `header` is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- - i.e. validate that `header.slot <= current_slot` +Shard header data, in the form of a `SignedShardBlockHeader` is published to the global `shard_block_header` subnet. +Shard block headers select shard blob bids by builders, and should be timely to ensure builders can publish the full shard block timely. + +The following validations MUST pass before forwarding the `signed_block_header` (with inner `message` as `header`) on the network. + +We define some aliases to the nested contents of `signed_block_header`: +```python +block_header: ShardBlockHeader = signed_block_header.message +signed_blob_header: SignedShardBlobHeader = header.signed_blob_header +blob_header: ShardBlobHeader = signed_blob_header.message +``` + +- _[IGNORE]_ The header is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- + i.e. validate that `blob_header.slot <= current_slot` (a client MAY queue future headers for processing at the appropriate slot). -- _[IGNORE]_ The `header` is new enough to be still be processed -- - i.e. validate that `compute_epoch_at_slot(header.slot) >= get_previous_epoch(state)` -- _[IGNORE]_ The header is the first header with valid signature received for the `(header.proposer_index, header.slot, header.shard)` combination. -- _[REJECT]_ The shard should have a committee at slot -- - i.e. validate that `compute_committee_index_from_shard(state, header.slot, header.shard)` doesn't raise an error -- _[REJECT]_ The proposer signature, `signed_shard_blob_header.signature`, is valid with respect to the `proposer_index` pubkey. +- _[IGNORE]_ The header is new enough to be still be processed -- + i.e. validate that `compute_epoch_at_slot(blob_header.slot) >= get_previous_epoch(state)` +- _[IGNORE]_ The header is the first header with valid signature received for the `(block_header.proposer_index, blob_header.slot, blob_header.shard)` combination. +- _[REJECT]_ The `shard` MUST have a committee at the `slot` -- + i.e. validate that `compute_committee_index_from_shard(state, blob_header.slot, blob_header.shard)` doesn't raise an error +- _[REJECT]_ The proposer signature, `signed_shard_block_header.signature`, is valid with respect to the `block_header.proposer_index` pubkey. - _[REJECT]_ The header is proposed by the expected `proposer_index` for the block's slot in the context of the current shuffling (defined by `header.body_summary.beacon_block_root`/`slot`). If the `proposer_index` cannot immediately be verified against the expected shuffling, @@ -124,6 +145,6 @@ Shard proposer slashings, in the form of `ShardProposerSlashing`, are published The following validations MUST pass before forwarding the `shard_proposer_slashing` on to the network. - _[IGNORE]_ The shard proposer slashing is the first valid shard proposer slashing received - for the proposer with index `proposer_slashing.signed_header_1.message.proposer_index`. + for the proposer with index `proposer_slashing.signed_reference_1.message.proposer_index`. The `slot` and `shard` are ignored, there are no per-shard slashings. - _[REJECT]_ All of the conditions within `process_shard_proposer_slashing` pass validation. From b3d5858cc9c42db65005206e6f3f1f31ce983209 Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 18 Jun 2021 04:02:06 +0200 Subject: [PATCH 017/135] update data fee payment, todo --- specs/sharding/beacon-chain.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index acba8e2a5..fe0c53bc4 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -770,8 +770,11 @@ def process_shard_header(state: BeaconState, signed_block_header: SignedShardBlo ) # Charge builder, with hard balance requirement - fee = Gwei(123) # TODO + fee = Gwei(123) # TODO EIP 1559 like fee? Burn some of it? charge_builder(state, builder_index, fee) + # TODO: proposer is charged for confirmed headers (see charge_confirmed_shard_fees). + # Need to align incentive, so proposer does not gain from including unconfirmed headers + increase_balance(state, block_header.proposer_index, fee) # Initialize the pending header index = compute_committee_index_from_shard(state, slot, shard) From 2a105f45819e262f27a3ea14279c5a5ca2a3cdd7 Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 18 Jun 2021 04:02:34 +0200 Subject: [PATCH 018/135] fix toc --- specs/sharding/p2p-interface.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/sharding/p2p-interface.md b/specs/sharding/p2p-interface.md index 94dd75128..e9b561312 100644 --- a/specs/sharding/p2p-interface.md +++ b/specs/sharding/p2p-interface.md @@ -13,10 +13,10 @@ - [Misc](#misc) - [Gossip domain](#gossip-domain) - [Topics and messages](#topics-and-messages) - - [Shard blob subnets](#shard-blob-subnets) + - [Shard block subnets](#shard-block-subnets) - [`shard_block_{subnet_id}`](#shard_block_subnet_id) - [Global topics](#global-topics) - - [`shard_header`](#shard_header) + - [`shard_block_header`](#shard_block_header) - [`shard_proposer_slashing`](#shard_proposer_slashing) From 5726cb9374bf31b8bc208f4703ee9109fb709367 Mon Sep 17 00:00:00 2001 From: protolambda Date: Mon, 21 Jun 2021 00:55:45 +0200 Subject: [PATCH 019/135] aggregate builder and proposer for simplified typing and optimized verification --- specs/sharding/beacon-chain.md | 201 +++++++++++---------------------- 1 file changed, 63 insertions(+), 138 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index fe0c53bc4..ebfaa89eb 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -34,15 +34,8 @@ - [`ShardBlobHeader`](#shardblobheader) - [`SignedShardBlob`](#signedshardblob) - [`SignedShardBlobHeader`](#signedshardblobheader) - - [ShardBlock](#shardblock) - - [`ShardBlockHeader`](#shardblockheader) - - [`SignedShardBlock`](#signedshardblock) - - [`SignedShardBlockHeader`](#signedshardblockheader) - [`PendingShardHeader`](#pendingshardheader) - [`ShardBlobReference`](#shardblobreference) - - [`SignedShardBlobReference`](#signedshardblobreference) - - [`ShardBlockReference`](#shardblockreference) - - [`SignedShardBlockReference`](#signedshardblockreference) - [`ShardProposerSlashing`](#shardproposerslashing) - [`ShardWork`](#shardwork) - [Helper functions](#helper-functions) @@ -116,8 +109,7 @@ The following values are (non-configurable) constants used throughout the specif | Name | Value | | - | - | -| `DOMAIN_SHARD_PROPOSER` | `DomainType('0x80000000')` | -| `DOMAIN_SHARD_BUILDER` | `DomainType('0x81000000')` | +| `DOMAIN_SHARD_BLOB` | `DomainType('0x80000000')` | ### Shard Work Status @@ -247,8 +239,6 @@ class ShardBlobBody(Container): data: List[BLSPoint, POINTS_PER_SAMPLE * MAX_SAMPLES_PER_BLOCK] # Latest block root of the Beacon Chain, before shard_blob.slot beacon_block_root: Root - # Builder of the data, pays data-fee to proposer - builder_index: BuilderIndex # TODO: fee payment amount fields (EIP 1559 like) ``` @@ -269,8 +259,6 @@ class ShardBlobBodySummary(Container): data_root: Root # Latest block root of the Beacon Chain, before shard_blob.slot beacon_block_root: Root - # Builder of the data, pays data-fee to proposer - builder_index: BuilderIndex # TODO: fee payment amount fields (EIP 1559 like) ``` @@ -282,18 +270,26 @@ class ShardBlobBodySummary(Container): class ShardBlob(Container): slot: Slot shard: Shard + # Proposer of the shard-blob + proposer_index: ValidatorIndex + # Builder of the data, pays data-fee to proposer + builder_index: BuilderIndex # Blob contents body: ShardBlobBody ``` ### `ShardBlobHeader` -Header version of `ShardBlob`. Separates designation (slot, shard) and contents (blob). +Header version of `ShardBlob`. ```python class ShardBlobHeader(Container): slot: Slot shard: Shard + # Proposer of the shard-blob + proposer_index: ValidatorIndex + # Builder of the data, pays data-fee to proposer + builder_index: BuilderIndex # Blob contents, without the full data body_summary: ShardBlobBodySummary ``` @@ -316,53 +312,8 @@ Shard proposers can accept `SignedShardBlobHeader` as a data-transaction. ```python class SignedShardBlobHeader(Container): message: ShardBlobHeader - signature: BLSSignature -``` - -### ShardBlock - -Full blob data signed by builder, to be confirmed by proxy as `ShardBlockHeader`. - -```python -class ShardBlock(Container): - # Shard data with fee payment by bundle builder - signed_blob: SignedShardBlob - # Proposer of the shard-blob - proposer_index: ValidatorIndex -``` - -### `ShardBlockHeader` - -Header version of `ShardBlock`, selecting a `SignedShardBlobHeader`. - -```python -class ShardBlockHeader(Container): - # Shard commitments and fee payment by blob builder - signed_blob_header: SignedShardBlobHeader - # Proposer of the shard-blob - proposer_index: ValidatorIndex -``` - -### `SignedShardBlock` - -Shard blob, signed for payment, and signed for proposal. Propagated to attesters. - -```python -class SignedShardBlock(Container): - message: ShardBlock - signature: BLSSignature -``` - -### `SignedShardBlockHeader` - -Header version of `SignedShardBlock`, substituting the full data within the blob for just the hash-tree-root. - -The signature is equally applicable to `SignedShardBlock`, -which the builder can publish as soon as the signed header is seen. - -```python -class SignedShardBlockHeader(Container): - message: ShardBlockHeader + # Signature by builder. + # Once accepted by proposer, the signatures is the aggregate of both. signature: BLSSignature ``` @@ -390,44 +341,26 @@ Reference version of `ShardBlobHeader`, substituting the body for just a hash-tr class ShardBlobReference(Container): slot: Slot shard: Shard - # Blob hash-tree-root for reference, enough for uniqueness - body_root: Root -``` - -### `SignedShardBlobReference` - -`ShardBlobReference`, signed by the blob builder. The builder-signature is part of the block identity. - -```python -class SignedShardBlobReference(Container): - message: ShardBlobReference - signature: BLSSignature -``` - -### `ShardBlockReference` - -```python -class ShardBlockReference(Container): - # Blob, minimized for efficient slashing - signed_blob_reference: SignedShardBlobReference # Proposer of the shard-blob proposer_index: ValidatorIndex -``` - -### `SignedShardBlockReference` - -```python -class SignedShardBlockReference(Container): - message: ShardBlockReference - signature: BLSSignature + # Builder of the data + builder_index: BuilderIndex + # Blob hash-tree-root for slashing reference + body_root: Root ``` ### `ShardProposerSlashing` ```python class ShardProposerSlashing(Container): - signed_reference_1: SignedShardBlockReference - signed_reference_2: SignedShardBlockReference + slot: Slot + shard: Shard + proposer_index: ValidatorIndex + builder_index: BuilderIndex + body_root_1: Root + body_root_2: Root + signature_1: BLSSignature + signature_2: BLSSignature ``` ### `ShardWork` @@ -552,7 +485,7 @@ def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard Return the proposer's index of shard block at ``slot``. """ epoch = compute_epoch_at_slot(slot) - seed = hash(get_seed(beacon_state, epoch, DOMAIN_SHARD_PROPOSER) + uint_to_bytes(slot) + uint_to_bytes(shard)) + seed = hash(get_seed(beacon_state, epoch, DOMAIN_SHARD_BLOB) + uint_to_bytes(slot) + uint_to_bytes(shard)) # Proposer must have sufficient balance to pay for worst case fee burn EFFECTIVE_BALANCE_MAX_DOWNWARD_DEVIATION = ( @@ -717,12 +650,10 @@ def charge_builder(state: BeaconState, index: BuilderIndex, fee: Gwei) -> None: ##### `process_shard_header` ```python -def process_shard_header(state: BeaconState, signed_block_header: SignedShardBlockHeader) -> None: - block_header: ShardBlockHeader = signed_block_header.message - signed_blob_header: SignedShardBlobHeader = block_header.signed_blob_header - blob_header: ShardBlobHeader = signed_blob_header.message - slot = blob_header.slot - shard = blob_header.shard +def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeader) -> None: + header: ShardBlobHeader = signed_header.message + slot = header.slot + shard = header.shard # Verify the header is not 0, and not from the future. assert Slot(0) < slot <= state.slot @@ -733,7 +664,7 @@ def process_shard_header(state: BeaconState, signed_block_header: SignedShardBlo assert shard < get_active_shard_count(state, header_epoch) # Verify that the block root matches, # to ensure the header will only be included in this specific Beacon Chain sub-tree. - assert blob_header.body_summary.beacon_block_root == get_block_root_at_slot(state, slot - 1) + assert header.body_summary.beacon_block_root == get_block_root_at_slot(state, slot - 1) # Check that this data is still pending committee_work = state.shard_buffer[slot % SHARD_STATE_MEMORY_SLOTS][shard] @@ -741,24 +672,17 @@ def process_shard_header(state: BeaconState, signed_block_header: SignedShardBlo # Check that this header is not yet in the pending list current_headers: List[PendingShardHeader, MAX_SHARD_HEADERS_PER_SHARD] = committee_work.status.value - header_root = hash_tree_root(block_header) + header_root = hash_tree_root(header) assert header_root not in [pending_header.root for pending_header in current_headers] - # Verify proposer - assert block_header.proposer_index == get_shard_proposer_index(state, slot, shard) - # Verify proposer signature - block_signing_root = compute_signing_root(block_header, get_domain(state, DOMAIN_SHARD_PROPOSER)) - proposer_pubkey = state.validators[block_header.proposer_index].pubkey - assert bls.Verify(proposer_pubkey, block_signing_root, signed_block_header.signature) + # Verify proposer matches + assert header.proposer_index == get_shard_proposer_index(state, slot, shard) - # Verify builder requirements - blob_summary: ShardBlobBodySummary = blob_header.body_summary - builder_index = blob_summary.builder_index - - # Verify builder signature - builder = state.builders[builder_index] - blob_signing_root = compute_signing_root(blob_header, get_domain(state, DOMAIN_SHARD_BUILDER)) # TODO new constant - assert bls.Verify(builder.pubkey, blob_signing_root, signed_blob_header.signature) + # Verify builder and proposer aggregate signature + blob_signing_root = compute_signing_root(header, get_domain(state, DOMAIN_SHARD_BLOB)) + builder_pubkey = state.builders[header.builder_index].pubkey + proposer_pubkey = state.validators[header.proposer_index].pubkey + assert bls.FastAggregateVerify([builder_pubkey, proposer_pubkey], blob_signing_root, signed_header.signature) # Verify the length by verifying the degree. body_summary = header.body_summary @@ -771,10 +695,10 @@ def process_shard_header(state: BeaconState, signed_block_header: SignedShardBlo # Charge builder, with hard balance requirement fee = Gwei(123) # TODO EIP 1559 like fee? Burn some of it? - charge_builder(state, builder_index, fee) + charge_builder(state, header.builder_index, fee) # TODO: proposer is charged for confirmed headers (see charge_confirmed_shard_fees). # Need to align incentive, so proposer does not gain from including unconfirmed headers - increase_balance(state, block_header.proposer_index, fee) + increase_balance(state, blob_header.proposer_index, fee) # Initialize the pending header index = compute_committee_index_from_shard(state, slot, shard) @@ -801,32 +725,33 @@ The goal is to ensure that a proof can only be constructed if `deg(B) < l` (ther ```python def process_shard_proposer_slashing(state: BeaconState, proposer_slashing: ShardProposerSlashing) -> None: - reference_1: ShardBlockReference = proposer_slashing.signed_reference_1.message - reference_2 : ShardBlockReference = proposer_slashing.signed_reference_2.message - blob_1 = reference_1.signed_blob_reference.message - blob_2 = reference_2.signed_blob_reference.message + # Verify the headers are different + assert proposer_slashing.body_root_1 != proposer_slashing.body_root_2 + + slot = proposer_slashing.slot + shard = proposer_slashing.shard + proposer_index = proposer_slashing.proposer_index + builder_index = proposer_slashing.builder_index - # Verify header slots match - assert blob_1.slot == blob_2.slot - # Verify header shards match - assert blob_1.shard == blob_2.shard - # Verify header proposer indices match - assert reference_1.proposer_index == reference_2.proposer_index - # Verify the headers are different (i.e. different body, or different builder signature) - assert ( - blob_1.body_root != blob_2.body_root - or reference_1.signed_blob_reference.signature != reference_2.signed_blob_reference.signature - ) # Verify the proposer is slashable - proposer = state.validators[reference_1.proposer_index] + proposer = state.validators[proposer_index] assert is_slashable_validator(proposer, get_current_epoch(state)) - # Verify signatures - for signed_header in (proposer_slashing.signed_reference_1, proposer_slashing.signed_reference_2): - domain = get_domain(state, DOMAIN_SHARD_PROPOSER, compute_epoch_at_slot(signed_header.message.slot)) - signing_root = compute_signing_root(signed_header.message, domain) - assert bls.Verify(proposer.pubkey, signing_root, signed_header.signature) - slash_validator(state, reference_1.proposer_index) + reference_1 = ShardBlobReference(slot=slot, shard=shard, + proposer_index=proposer_index, builder_index=builder_index, + body_root= proposer_slashing.body_root_1) + reference_2 = ShardBlobReference(slot=slot, shard=shard, + proposer_index=proposer_index, builder_index=builder_index, + body_root= proposer_slashing.body_root_2) + proposer_pubkey = proposer.pubkey + builder_pubkey = state.builders[builder_index].pubkey + domain = get_domain(state, DOMAIN_SHARD_PROPOSER, compute_epoch_at_slot(slot)) + signing_root_1 = compute_signing_root(reference_1, domain) + signing_root_2 = compute_signing_root(reference_2, domain) + assert bls.FastAggregateVerify([builder_pubkey, proposer_pubkey], signing_root_1, proposer_slashing.signature_1) + assert bls.FastAggregateVerify([builder_pubkey, proposer_pubkey], signing_root_2, proposer_slashing.signature_2) + + slash_validator(state, proposer_index) ``` ### Epoch transition From 5034e2d7bc194613a11e63359f4dcff2d1dd993a Mon Sep 17 00:00:00 2001 From: protolambda Date: Mon, 21 Jun 2021 23:33:10 +0200 Subject: [PATCH 020/135] update shard spec wording + fix shard slashings --- specs/sharding/beacon-chain.md | 54 ++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index ebfaa89eb..4bf5f7a03 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -78,8 +78,7 @@ using KZG10 commitments to commit to data to remove any need for fraud proofs (a - **Data**: A list of KZG points, to translate a byte string into - **Blob**: Data with commitments and meta-data, like a flattened bundle of L2 transactions. - **Builder**: Builds blobs and bids for proposal slots with fee-paying blob-headers, responsible for availability. -- **Shard proposer**: Validator, selects a signed blob-header, taking bids for shard data opportunity. -- **Shard block**: Unique per `(slot, shard, proposer)`, selected signed blob +- **Shard proposer**: Validator, taking bids for shard data opportunity, co-signs with builder to propose the blob. ## Custom types @@ -132,12 +131,12 @@ The following values are (non-configurable) constants used throughout the specif | `SHARD_STATE_MEMORY_SLOTS` | `uint64(2**8)` (= 256) | Number of slots for which shard commitments and confirmation status is directly available in the state | | `BUILDER_REGISTRY_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | builders | -### Shard block samples +### Shard blob samples | Name | Value | Notes | | - | - | - | -| `MAX_SAMPLES_PER_BLOCK` | `uint64(2**11)` (= 2,048) | 248 * 2,048 = 507,904 bytes | -| `TARGET_SAMPLES_PER_BLOCK` | `uint64(2**10)` (= 1,024) | 248 * 1,024 = 253,952 bytes | +| `MAX_SAMPLES_PER_BLOB` | `uint64(2**11)` (= 2,048) | 248 * 2,048 = 507,904 bytes | +| `TARGET_SAMPLES_PER_BLOB` | `uint64(2**10)` (= 1,024) | 248 * 1,024 = 253,952 bytes | ### Precomputed size verification points @@ -145,7 +144,7 @@ The following values are (non-configurable) constants used throughout the specif | - | - | | `G1_SETUP` | Type `List[G1]`. The G1-side trusted setup `[G, G*s, G*s**2....]`; note that the first point is the generator. | | `G2_SETUP` | Type `List[G2]`. The G2-side trusted setup `[G, G*s, G*s**2....]` | -| `ROOT_OF_UNITY` | `pow(PRIMITIVE_ROOT_OF_UNITY, (MODULUS - 1) // int(MAX_SAMPLES_PER_BLOCK * POINTS_PER_SAMPLE), MODULUS)` | +| `ROOT_OF_UNITY` | `pow(PRIMITIVE_ROOT_OF_UNITY, (MODULUS - 1) // int(MAX_SAMPLES_PER_BLOB * POINTS_PER_SAMPLE), MODULUS)` | ### Gwei values @@ -176,7 +175,7 @@ class AttestationData(Container): source: Checkpoint target: Checkpoint # Hash-tree-root of ShardBlock - shard_block_root: Root # [New in Sharding] + shard_blob_root: Root # [New in Sharding] ``` ### `BeaconBlockBody` @@ -236,7 +235,7 @@ class ShardBlobBody(Container): # Proof that the degree < commitment.length degree_proof: BLSCommitment # The actual data. Should match the commitment and degree proof. - data: List[BLSPoint, POINTS_PER_SAMPLE * MAX_SAMPLES_PER_BLOCK] + data: List[BLSPoint, POINTS_PER_SAMPLE * MAX_SAMPLES_PER_BLOB] # Latest block root of the Beacon Chain, before shard_blob.slot beacon_block_root: Root # TODO: fee payment amount fields (EIP 1559 like) @@ -296,7 +295,7 @@ class ShardBlobHeader(Container): ### `SignedShardBlob` -Full blob data, signed by the shard builder, ensuring fee payment. +Full blob data, signed by the shard builder (ensuring fee payment) and shard proposer (ensuring a single proposal). ```python class SignedShardBlob(Container): @@ -307,7 +306,7 @@ class SignedShardBlob(Container): ### `SignedShardBlobHeader` Header of the blob, the signature is equally applicable to `SignedShardBlob`. -Shard proposers can accept `SignedShardBlobHeader` as a data-transaction. +Shard proposers can accept `SignedShardBlobHeader` as a data-transaction by co-signing the header. ```python class SignedShardBlobHeader(Container): @@ -356,7 +355,8 @@ class ShardProposerSlashing(Container): slot: Slot shard: Shard proposer_index: ValidatorIndex - builder_index: BuilderIndex + builder_index_1: BuilderIndex + builder_index_2: BuilderIndex body_root_1: Root body_root_2: Root signature_1: BLSSignature @@ -400,13 +400,13 @@ def compute_previous_slot(slot: Slot) -> Slot: ```python def compute_updated_gasprice(prev_gasprice: Gwei, shard_block_length: uint64, adjustment_quotient: uint64) -> Gwei: - if shard_block_length > TARGET_SAMPLES_PER_BLOCK: - delta = max(1, prev_gasprice * (shard_block_length - TARGET_SAMPLES_PER_BLOCK) - // TARGET_SAMPLES_PER_BLOCK // adjustment_quotient) + if shard_block_length > TARGET_SAMPLES_PER_BLOB: + delta = max(1, prev_gasprice * (shard_block_length - TARGET_SAMPLES_PER_BLOB) + // TARGET_SAMPLES_PER_BLOB // adjustment_quotient) return min(prev_gasprice + delta, MAX_GASPRICE) else: - delta = max(1, prev_gasprice * (TARGET_SAMPLES_PER_BLOCK - shard_block_length) - // TARGET_SAMPLES_PER_BLOCK // adjustment_quotient) + delta = max(1, prev_gasprice * (TARGET_SAMPLES_PER_BLOB - shard_block_length) + // TARGET_SAMPLES_PER_BLOB // adjustment_quotient) return max(prev_gasprice, MIN_GASPRICE + delta) - delta ``` @@ -493,7 +493,7 @@ def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard * HYSTERESIS_DOWNWARD_MULTIPLIER // HYSTERESIS_QUOTIENT ) min_effective_balance = ( - beacon_state.shard_gasprice * MAX_SAMPLES_PER_BLOCK // TARGET_SAMPLES_PER_BLOCK + beacon_state.shard_gasprice * MAX_SAMPLES_PER_BLOB // TARGET_SAMPLES_PER_BLOB + EFFECTIVE_BALANCE_MAX_DOWNWARD_DEVIATION ) indices = get_active_validator_indices(state, epoch) @@ -597,7 +597,7 @@ def update_pending_shard_work(state: BeaconState, attestation: Attestation) -> N current_headers: Sequence[PendingShardHeader] = committee_work.status.value # Find the corresponding header, abort if it cannot be found - header_index = [header.root for header in current_headers].index(attestation.data.shard_block_root) + header_index = [header.root for header in current_headers].index(attestation.data.shard_blob_root) pending_header: PendingShardHeader = current_headers[header_index] full_committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index) @@ -731,25 +731,27 @@ def process_shard_proposer_slashing(state: BeaconState, proposer_slashing: Shard slot = proposer_slashing.slot shard = proposer_slashing.shard proposer_index = proposer_slashing.proposer_index - builder_index = proposer_slashing.builder_index # Verify the proposer is slashable proposer = state.validators[proposer_index] assert is_slashable_validator(proposer, get_current_epoch(state)) reference_1 = ShardBlobReference(slot=slot, shard=shard, - proposer_index=proposer_index, builder_index=builder_index, + proposer_index=proposer_index, + builder_index=proposer_slashing.builder_index_1, body_root= proposer_slashing.body_root_1) reference_2 = ShardBlobReference(slot=slot, shard=shard, - proposer_index=proposer_index, builder_index=builder_index, + proposer_index=proposer_index, + builder_index=proposer_slashing.builder_index_1, body_root= proposer_slashing.body_root_2) - proposer_pubkey = proposer.pubkey - builder_pubkey = state.builders[builder_index].pubkey + # The builders are not slashed, the proposer co-signed with them + builder_pubkey_1 = state.builders[proposer_slashing.builder_index_1].pubkey + builder_pubkey_2 = state.builders[proposer_slashing.builder_index_2].pubkey domain = get_domain(state, DOMAIN_SHARD_PROPOSER, compute_epoch_at_slot(slot)) signing_root_1 = compute_signing_root(reference_1, domain) signing_root_2 = compute_signing_root(reference_2, domain) - assert bls.FastAggregateVerify([builder_pubkey, proposer_pubkey], signing_root_1, proposer_slashing.signature_1) - assert bls.FastAggregateVerify([builder_pubkey, proposer_pubkey], signing_root_2, proposer_slashing.signature_2) + assert bls.FastAggregateVerify([builder_pubkey_1, proposer.pubkey], signing_root_1, proposer_slashing.signature_1) + assert bls.FastAggregateVerify([builder_pubkey_2, proposer.pubkey], signing_root_2, proposer_slashing.signature_2) slash_validator(state, proposer_index) ``` @@ -828,7 +830,7 @@ def charge_confirmed_shard_fees(state: BeaconState) -> None: proposer = get_shard_proposer_index(state, slot, Shard(shard_index)) fee = ( (state.shard_gasprice * commitment.length) - // TARGET_SAMPLES_PER_BLOCK + // TARGET_SAMPLES_PER_BLOB ) decrease_balance(state, proposer, fee) From b25afc88fd2686f9d9e94447b7db6f3e25d391da Mon Sep 17 00:00:00 2001 From: protolambda Date: Tue, 22 Jun 2021 00:10:47 +0200 Subject: [PATCH 021/135] update networking spec with aggregate proposer/builder types, update TOCs --- specs/sharding/beacon-chain.md | 2 +- specs/sharding/p2p-interface.md | 140 +++++++++++++++++++------------- 2 files changed, 83 insertions(+), 59 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 4bf5f7a03..6378389a7 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -17,7 +17,7 @@ - [Shard Work Status](#shard-work-status) - [Preset](#preset) - [Misc](#misc-1) - - [Shard block samples](#shard-block-samples) + - [Shard blob samples](#shard-blob-samples) - [Precomputed size verification points](#precomputed-size-verification-points) - [Gwei values](#gwei-values) - [Configuration](#configuration) diff --git a/specs/sharding/p2p-interface.md b/specs/sharding/p2p-interface.md index e9b561312..4eb3f6f2e 100644 --- a/specs/sharding/p2p-interface.md +++ b/specs/sharding/p2p-interface.md @@ -13,10 +13,11 @@ - [Misc](#misc) - [Gossip domain](#gossip-domain) - [Topics and messages](#topics-and-messages) - - [Shard block subnets](#shard-block-subnets) - - [`shard_block_{subnet_id}`](#shard_block_subnet_id) + - [Shard blob subnets](#shard-blob-subnets) + - [`shard_blob_{subnet_id}`](#shard_blob_subnet_id) - [Global topics](#global-topics) - - [`shard_block_header`](#shard_block_header) + - [`shard_blob_header`](#shard_blob_header) + - [`shard_blob_tx`](#shard_blob_tx) - [`shard_proposer_slashing`](#shard_proposer_slashing) @@ -34,7 +35,7 @@ The adjustments and additions for Shards are outlined in this document. | Name | Value | Description | | ---- | ----- | ----------- | -| `SHARD_BLOCK_SUBNET_COUNT` | `64` | The number of `shard_block_{subnet_id}` subnets used in the gossipsub protocol. | +| `SHARD_BLOB_SUBNET_COUNT` | `64` | The number of `shard_blob_{subnet_id}` subnets used in the gossipsub protocol. | ## Gossip domain @@ -42,24 +43,25 @@ The adjustments and additions for Shards are outlined in this document. Following the same scheme as the [Phase0 gossip topics](../phase0/p2p-interface.md#topics-and-messages), names and payload types are: -| Name | Message Type | -|----------------------------------|---------------------------| -| `shard_block_{subnet_id}` | `SignedShardBlock` | -| `shard_block_header` | `SignedShardBlockHeader` | -| `shard_proposer_slashing` | `ShardProposerSlashing` | +| Name | Message Type | +|---------------------------------|--------------------------| +| `shard_blob_{subnet_id}` | `SignedShardBlob` | +| `shard_blob_header` | `SignedShardBlobHeader` | +| `shard_blob_tx` | `SignedShardBlobHeader` | +| `shard_proposer_slashing` | `ShardProposerSlashing` | The [DAS network specification](./das-p2p.md) defines additional topics. -#### Shard block subnets +#### Shard blob subnets -Shard block subnets are used by builders to make their blobs available after selection by shard proposers. +Shard blob subnets are used by builders to make their blobs available after selection by shard proposers. -##### `shard_block_{subnet_id}` +##### `shard_blob_{subnet_id}` -Shard block data, in the form of a `SignedShardBlock` is published to the `shard_block_{subnet_id}` subnets. +Shard blob data, in the form of a `SignedShardBlob` is published to the `shard_blob_{subnet_id}` subnets. ```python -def compute_subnet_for_shard_block(state: BeaconState, slot: Slot, shard: Shard) -> uint64: +def compute_subnet_for_shard_blob(state: BeaconState, slot: Slot, shard: Shard) -> uint64: """ Compute the correct subnet for a shard blob publication. Note, this mimics compute_subnet_for_attestation(). @@ -69,75 +71,97 @@ def compute_subnet_for_shard_block(state: BeaconState, slot: Slot, shard: Shard) slots_since_epoch_start = Slot(slot % SLOTS_PER_EPOCH) committees_since_epoch_start = committees_per_slot * slots_since_epoch_start - return uint64((committees_since_epoch_start + committee_index) % SHARD_BLOCK_SUBNET_COUNT) + return uint64((committees_since_epoch_start + committee_index) % SHARD_BLOB_SUBNET_COUNT) ``` -The following validations MUST pass before forwarding the `signed_block` on the horizontal subnet or creating samples for it. +The following validations MUST pass before forwarding the `signed_blob`, +on the horizontal subnet or creating samples for it. Alias `blob = signed_blob.message`. -We define some aliases to the nested contents of `signed_block`: -```python -block: ShardBlock = signed_block.message -signed_blob: SignedShardBlob = block.signed_blob -blob: ShardBlob = signed_blob.message -``` - -- _[IGNORE]_ The `block` is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- +- _[IGNORE]_ The `blob` is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that `blob.slot <= current_slot` (a client MAY queue future blobs for processing at the appropriate slot). -- _[IGNORE]_ The `blob` is new enough to be still be processed -- +- _[IGNORE]_ The `blob` is new enough to still be processed -- i.e. validate that `compute_epoch_at_slot(blob.slot) >= get_previous_epoch(state)` -- _[REJECT]_ The shard should have a committee at slot -- +- _[REJECT]_ The shard blob is for an active shard -- + i.e. `blob.shard < get_active_shard_count(state, compute_epoch_at_slot(blob.slot))` +- _[REJECT]_ The `blob.shard` MUST have a committee at the `blob.slot` -- i.e. validate that `compute_committee_index_from_shard(state, blob.slot, blob.shard)` doesn't raise an error - _[REJECT]_ The shard blob is for the correct subnet -- - i.e. `compute_subnet_for_shard_block(state, blob.slot, blob.shard) == subnet_id` -- _[IGNORE]_ The block is the first block with valid signature received for the `(block.proposer_index, blob.slot, blob.shard)` combination. + i.e. `compute_subnet_for_shard_blob(state, blob.slot, blob.shard) == subnet_id` +- _[IGNORE]_ The blob is the first blob with valid signature received for the `(blob.proposer_index, blob.slot, blob.shard)` combination. - _[REJECT]_ The blob is not too large, the data MUST NOT be larger than the SSZ list-limit, and a client MAY be more strict. - _[REJECT]_ The `blob.body.data` MUST NOT contain any point `p >= MODULUS`. Although it is a `uint256`, not the full 256 bit range is valid. -- _[REJECT]_ The block proposer signature, `signed_block.signature`, is valid with respect to the `proposer_index` pubkey. - _[REJECT]_ The blob builder exists and has sufficient balance to back the fee payment. -- _[REJECT]_ The blob builder signature, `signed_blob.signature`, is valid with respect to the `builder_index` pubkey. -- _[REJECT]_ The block is proposed by the expected `proposer_index` for the block's slot +- _[REJECT]_ The blob signature is valid for the aggregate of proposer and builder, `signed_blob.signature`, + i.e. `bls.FastAggregateVerify([builder_pubkey, proposer_pubkey], blob_signing_root, signed_blob.signature)`. +- _[REJECT]_ The blob is proposed by the expected `proposer_index` for the blob's `slot` and `shard`, in the context of the current shuffling (defined by `blob.body.beacon_block_root`/`slot`). If the `proposer_index` cannot immediately be verified against the expected shuffling, - the block MAY be queued for later processing while proposers for the block's branch are calculated -- + the blob MAY be queued for later processing while proposers for the blob's branch are calculated -- in such a case _do not_ `REJECT`, instead `IGNORE` this message. #### Global topics -There are two additional global topics for Sharding. +There are three additional global topics for Sharding. -One is used to propagate shard block headers (`shard_block_header`) to all nodes on the network. -Another one is used to propagate shard proposer slashings (`shard_proposer_slashing`). +- `shard_blob_header`: co-signed headers, to be included on-chain, and signaling builders to publish full data. +- `shard_blob_tx`: builder-signed headers, also known as "data transaction". +- `shard_proposer_slashing`: slashings of duplicate shard proposals -##### `shard_block_header` +##### `shard_blob_header` -Shard header data, in the form of a `SignedShardBlockHeader` is published to the global `shard_block_header` subnet. -Shard block headers select shard blob bids by builders, and should be timely to ensure builders can publish the full shard block timely. +Shard header data, in the form of a `SignedShardBlobHeader` is published to the global `shard_blob_header` subnet. +Shard blob headers select shard blob bids by builders, +and should be timely to ensure builders can publish the full shard blob before subsequent attestations. -The following validations MUST pass before forwarding the `signed_block_header` (with inner `message` as `header`) on the network. +The following validations MUST pass before forwarding the `signed_blob_header` on the network. Alias `header = signed_blob_header.message` -We define some aliases to the nested contents of `signed_block_header`: -```python -block_header: ShardBlockHeader = signed_block_header.message -signed_blob_header: SignedShardBlobHeader = header.signed_blob_header -blob_header: ShardBlobHeader = signed_blob_header.message -``` - -- _[IGNORE]_ The header is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- - i.e. validate that `blob_header.slot <= current_slot` +- _[IGNORE]_ The `header` is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- + i.e. validate that `header.slot <= current_slot` (a client MAY queue future headers for processing at the appropriate slot). -- _[IGNORE]_ The header is new enough to be still be processed -- - i.e. validate that `compute_epoch_at_slot(blob_header.slot) >= get_previous_epoch(state)` -- _[IGNORE]_ The header is the first header with valid signature received for the `(block_header.proposer_index, blob_header.slot, blob_header.shard)` combination. -- _[REJECT]_ The `shard` MUST have a committee at the `slot` -- - i.e. validate that `compute_committee_index_from_shard(state, blob_header.slot, blob_header.shard)` doesn't raise an error -- _[REJECT]_ The proposer signature, `signed_shard_block_header.signature`, is valid with respect to the `block_header.proposer_index` pubkey. -- _[REJECT]_ The header is proposed by the expected `proposer_index` for the block's slot +- _[IGNORE]_ The header is new enough to still be processed -- + i.e. validate that `compute_epoch_at_slot(header.slot) >= get_previous_epoch(state)` +- _[REJECT]_ The shard header is for an active shard -- + i.e. `header.shard < get_active_shard_count(state, compute_epoch_at_slot(header.slot))` +- _[REJECT]_ The `header.shard` MUST have a committee at the `header.slot` -- + i.e. validate that `compute_committee_index_from_shard(state, header.slot, header.shard)` doesn't raise an error +- _[IGNORE]_ The header is the first header with valid signature received for the `(header.proposer_index, header.slot, header.shard)` combination. +- _[REJECT]_ The blob builder exists and has sufficient balance to back the fee payment. +- _[REJECT]_ The header signature is valid for the aggregate of proposer and builder, `signed_blob_header.signature`, + i.e. `bls.FastAggregateVerify([builder_pubkey, proposer_pubkey], blob_signing_root, signed_blob_header.signature)`. +- _[REJECT]_ The header is proposed by the expected `proposer_index` for the blob's `header.slot` and `header.shard` in the context of the current shuffling (defined by `header.body_summary.beacon_block_root`/`slot`). If the `proposer_index` cannot immediately be verified against the expected shuffling, - the block MAY be queued for later processing while proposers for the block's branch are calculated -- + the blob MAY be queued for later processing while proposers for the blob's branch are calculated -- in such a case _do not_ `REJECT`, instead `IGNORE` this message. +##### `shard_blob_tx` + +Shard data-transactions, in the form of a `SignedShardBlobHeader` is published to the global `shard_blob_tx` subnet. +These shard blob headers are signed solely by the blob-builder. + +The following validations MUST pass before forwarding the `signed_blob_header` on the network. Alias `header = signed_blob_header.message` + +- _[IGNORE]_ The `header` is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- + i.e. validate that `header.slot <= current_slot` + (a client MAY queue future headers for processing at the appropriate slot). +- _[IGNORE]_ The header is new enough to still be processed -- + i.e. validate that `compute_epoch_at_slot(header.slot) >= get_previous_epoch(state)` +- _[REJECT]_ The shard header is for an active shard -- + i.e. `header.shard < get_active_shard_count(state, compute_epoch_at_slot(header.slot))` +- _[REJECT]_ The `header.shard` MUST have a committee at the `header.slot` -- + i.e. validate that `compute_committee_index_from_shard(state, header.slot, header.shard)` doesn't raise an error +- _[IGNORE]_ The header is the first header with valid signature received for the `(header.builder_index, header.slot, header.shard)` combination. +- _[REJECT]_ The blob builder exists and has sufficient balance to back the fee payment. +- _[IGNORE]_ The header fee SHOULD be higher than previously seen headers for `(header.slot, header.shard)`, from any builder. + Propagating nodes MAY increase fee increments in case of spam. +- _[REJECT]_ The header signature is valid for ONLY the builder, `signed_blob_header.signature`, + i.e. `bls.Verify(builder_pubkey, blob_signing_root, signed_blob_header.signature)`. The signature is not an aggregate with the proposer. +- _[REJECT]_ The header is designated for proposal by the expected `proposer_index` for the blob's `header.slot` and `header.shard` + in the context of the current shuffling (defined by `header.body_summary.beacon_block_root`/`slot`). + If the `proposer_index` cannot immediately be verified against the expected shuffling, + the blob MAY be queued for later processing while proposers for the blob's branch are calculated -- + in such a case _do not_ `REJECT`, instead `IGNORE` this message. ##### `shard_proposer_slashing` @@ -145,6 +169,6 @@ Shard proposer slashings, in the form of `ShardProposerSlashing`, are published The following validations MUST pass before forwarding the `shard_proposer_slashing` on to the network. - _[IGNORE]_ The shard proposer slashing is the first valid shard proposer slashing received - for the proposer with index `proposer_slashing.signed_reference_1.message.proposer_index`. - The `slot` and `shard` are ignored, there are no per-shard slashings. + for the proposer with index `proposer_slashing.proposer_index`. + The `proposer_slashing.slot` and `proposer_slashing.shard` are ignored, there are no repeated or per-shard slashings. - _[REJECT]_ All of the conditions within `process_shard_proposer_slashing` pass validation. From f791fe7d1c322f3e8f2121f6c7a1abd675652085 Mon Sep 17 00:00:00 2001 From: protolambda Date: Wed, 23 Jun 2021 23:33:46 +0200 Subject: [PATCH 022/135] implement review suggestions Co-authored-by: Anton Nashatyrev --- specs/sharding/beacon-chain.md | 89 ++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 6378389a7..63b9457c4 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -24,9 +24,9 @@ - [Updated containers](#updated-containers) - [`AttestationData`](#attestationdata) - [`BeaconBlockBody`](#beaconblockbody) - - [`Builder`](#builder) - [`BeaconState`](#beaconstate) - [New containers](#new-containers) + - [`Builder`](#builder) - [`DataCommitment`](#datacommitment) - [ShardBlobBody](#shardblobbody) - [`ShardBlobBodySummary`](#shardblobbodysummary) @@ -77,8 +77,8 @@ using KZG10 commitments to commit to data to remove any need for fraud proofs (a - **Data**: A list of KZG points, to translate a byte string into - **Blob**: Data with commitments and meta-data, like a flattened bundle of L2 transactions. -- **Builder**: Builds blobs and bids for proposal slots with fee-paying blob-headers, responsible for availability. -- **Shard proposer**: Validator, taking bids for shard data opportunity, co-signs with builder to propose the blob. +- **Builder**: Independent actor that builds blobs and bids for proposal slots via fee-paying blob-headers, responsible for availability. +- **Shard proposer**: Validator taking bids from blob builders for shard data opportunity, co-signs with builder to propose the blob. ## Custom types @@ -129,7 +129,7 @@ The following values are (non-configurable) constants used throughout the specif | `MAX_SHARD_PROPOSER_SLASHINGS` | `2**4` (= 16) | Maximum amount of shard proposer slashing operations per block | | `MAX_SHARD_HEADERS_PER_SHARD` | `4` | | | `SHARD_STATE_MEMORY_SLOTS` | `uint64(2**8)` (= 256) | Number of slots for which shard commitments and confirmation status is directly available in the state | -| `BUILDER_REGISTRY_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | builders | +| `BLOB_BUILDER_REGISTRY_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | shard blob builders | ### Shard blob samples @@ -174,7 +174,7 @@ class AttestationData(Container): # FFG vote source: Checkpoint target: Checkpoint - # Hash-tree-root of ShardBlock + # Hash-tree-root of ShardBlob shard_blob_root: Root # [New in Sharding] ``` @@ -183,16 +183,7 @@ class AttestationData(Container): ```python class BeaconBlockBody(merge.BeaconBlockBody): # [extends The Merge block body] shard_proposer_slashings: List[ShardProposerSlashing, MAX_SHARD_PROPOSER_SLASHINGS] - shard_headers: List[SignedShardBlockHeader, MAX_SHARDS * MAX_SHARD_HEADERS_PER_SHARD] -``` - -### `Builder` - -```python -class Builder(Container): - pubkey: BLSPubkey - # TODO: fields for either an expiry mechanism (refunding execution account with remaining balance) - # and/or a builder-transaction mechanism. + shard_headers: List[SignedShardBlobHeader, MAX_SHARDS * MAX_SHARD_HEADERS_PER_SHARD] ``` ### `BeaconState` @@ -203,9 +194,9 @@ class BeaconState(merge.BeaconState): previous_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH] current_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH] # [New fields] - # Builder registry. - builders: List[Builder, BUILDER_REGISTRY_LIMIT] - builder_balances: List[Gwei, BUILDER_REGISTRY_LIMIT] + # Blob builder registry. + blob_builders: List[Builder, BLOB_BUILDER_REGISTRY_LIMIT] + blob_builder_balances: List[Gwei, BLOB_BUILDER_REGISTRY_LIMIT] # A ring buffer of the latest slots, with information per active shard. shard_buffer: Vector[List[ShardWork, MAX_SHARDS], SHARD_STATE_MEMORY_SLOTS] shard_gasprice: uint64 @@ -213,6 +204,15 @@ class BeaconState(merge.BeaconState): ## New containers +### `Builder` + +```python +class Builder(Container): + pubkey: BLSPubkey + # TODO: fields for either an expiry mechanism (refunding execution account with remaining balance) + # and/or a builder-transaction mechanism. +``` + ### `DataCommitment` ```python @@ -269,10 +269,10 @@ class ShardBlobBodySummary(Container): class ShardBlob(Container): slot: Slot shard: Shard - # Proposer of the shard-blob - proposer_index: ValidatorIndex # Builder of the data, pays data-fee to proposer builder_index: BuilderIndex + # Proposer of the shard-blob + proposer_index: ValidatorIndex # Blob contents body: ShardBlobBody ``` @@ -285,10 +285,10 @@ Header version of `ShardBlob`. class ShardBlobHeader(Container): slot: Slot shard: Shard - # Proposer of the shard-blob - proposer_index: ValidatorIndex # Builder of the data, pays data-fee to proposer builder_index: BuilderIndex + # Proposer of the shard-blob + proposer_index: ValidatorIndex # Blob contents, without the full data body_summary: ShardBlobBodySummary ``` @@ -322,7 +322,7 @@ class SignedShardBlobHeader(Container): class PendingShardHeader(Container): # KZG10 commitment to the data commitment: DataCommitment - # hash_tree_root of the ShardBlockHeader (stored so that attestations can be checked against it) + # hash_tree_root of the ShardBlobHeader (stored so that attestations can be checked against it) root: Root # Who voted for the header votes: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] @@ -340,10 +340,10 @@ Reference version of `ShardBlobHeader`, substituting the body for just a hash-tr class ShardBlobReference(Container): slot: Slot shard: Shard - # Proposer of the shard-blob - proposer_index: ValidatorIndex # Builder of the data builder_index: BuilderIndex + # Proposer of the shard-blob + proposer_index: ValidatorIndex # Blob hash-tree-root for slashing reference body_root: Root ``` @@ -643,8 +643,12 @@ def charge_builder(state: BeaconState, index: BuilderIndex, fee: Gwei) -> None: """ Decrease the builder balance at index ``index`` by ``fee``, with underflow check. """ - assert state.builder_balances[index] >= fee - state.builder_balances[index] -= fee + # TODO: apply stricter requirement to protect against fee-acceptance race conditions, e.g.: + # - balance per shard (or builders per shard) + # - balance / shard_count > fee + # TODO: also consider requirement to pay for base-fee of shard-data + assert state.blob_builder_balances[index] >= fee + state.blob_builder_balances[index] -= fee ``` ##### `process_shard_header` @@ -680,7 +684,7 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade # Verify builder and proposer aggregate signature blob_signing_root = compute_signing_root(header, get_domain(state, DOMAIN_SHARD_BLOB)) - builder_pubkey = state.builders[header.builder_index].pubkey + builder_pubkey = state.blob_builders[header.builder_index].pubkey proposer_pubkey = state.validators[header.proposer_index].pubkey assert bls.FastAggregateVerify([builder_pubkey, proposer_pubkey], blob_signing_root, signed_header.signature) @@ -698,7 +702,7 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade charge_builder(state, header.builder_index, fee) # TODO: proposer is charged for confirmed headers (see charge_confirmed_shard_fees). # Need to align incentive, so proposer does not gain from including unconfirmed headers - increase_balance(state, blob_header.proposer_index, fee) + increase_balance(state, header.proposer_index, fee) # Initialize the pending header index = compute_committee_index_from_shard(state, slot, shard) @@ -725,28 +729,29 @@ The goal is to ensure that a proof can only be constructed if `deg(B) < l` (ther ```python def process_shard_proposer_slashing(state: BeaconState, proposer_slashing: ShardProposerSlashing) -> None: - # Verify the headers are different - assert proposer_slashing.body_root_1 != proposer_slashing.body_root_2 - slot = proposer_slashing.slot shard = proposer_slashing.shard proposer_index = proposer_slashing.proposer_index + reference_1 = ShardBlobReference(slot=slot, shard=shard, + proposer_index=proposer_index, + builder_index=proposer_slashing.builder_index_1, + body_root=proposer_slashing.body_root_1) + reference_2 = ShardBlobReference(slot=slot, shard=shard, + proposer_index=proposer_index, + builder_index=proposer_slashing.builder_index_2, + body_root=proposer_slashing.body_root_2) + + # Verify the signed messages are different + assert reference_1 != reference_2 + # Verify the proposer is slashable proposer = state.validators[proposer_index] assert is_slashable_validator(proposer, get_current_epoch(state)) - reference_1 = ShardBlobReference(slot=slot, shard=shard, - proposer_index=proposer_index, - builder_index=proposer_slashing.builder_index_1, - body_root= proposer_slashing.body_root_1) - reference_2 = ShardBlobReference(slot=slot, shard=shard, - proposer_index=proposer_index, - builder_index=proposer_slashing.builder_index_1, - body_root= proposer_slashing.body_root_2) # The builders are not slashed, the proposer co-signed with them - builder_pubkey_1 = state.builders[proposer_slashing.builder_index_1].pubkey - builder_pubkey_2 = state.builders[proposer_slashing.builder_index_2].pubkey + builder_pubkey_1 = state.blob_builders[proposer_slashing.builder_index_1].pubkey + builder_pubkey_2 = state.blob_builders[proposer_slashing.builder_index_2].pubkey domain = get_domain(state, DOMAIN_SHARD_PROPOSER, compute_epoch_at_slot(slot)) signing_root_1 = compute_signing_root(reference_1, domain) signing_root_2 = compute_signing_root(reference_2, domain) From a7f58ef08acb81a88b279b449335faebc6c717b2 Mon Sep 17 00:00:00 2001 From: protolambda Date: Wed, 14 Jul 2021 13:19:00 +0200 Subject: [PATCH 023/135] fix comment + handle missing pending headers --- specs/sharding/beacon-chain.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 63b9457c4..2ab0f68cd 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -367,7 +367,7 @@ class ShardProposerSlashing(Container): ```python class ShardWork(Container): - # Upon confirmation the data is reduced to just the header. + # Upon confirmation the data is reduced to just the commitment. status: Union[ # See Shard Work Status enum None, # SHARD_WORK_UNCONFIRMED DataCommitment, # SHARD_WORK_CONFIRMED @@ -597,7 +597,15 @@ def update_pending_shard_work(state: BeaconState, attestation: Attestation) -> N current_headers: Sequence[PendingShardHeader] = committee_work.status.value # Find the corresponding header, abort if it cannot be found - header_index = [header.root for header in current_headers].index(attestation.data.shard_blob_root) + header_index = len(current_headers) + for i, header in enumerate(current_headers): + if attestation.data.shard_blob_root == header.root: + header_index = i + break + # Attestations for an unknown header do not count towards shard confirmations, but can otherwise be valid. + if header_index == len(current_headers): + # TODO: Attestations may be re-included if headers are included late. + return pending_header: PendingShardHeader = current_headers[header_index] full_committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index) From 35df4b2d4f81b79dba5651614c5581f8bb400a90 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 23 Jul 2021 07:54:26 -0600 Subject: [PATCH 024/135] rephrase context enum for blocks_by requests --- specs/merge/p2p-interface.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/merge/p2p-interface.md b/specs/merge/p2p-interface.md index 4af3f8e10..712a17549 100644 --- a/specs/merge/p2p-interface.md +++ b/specs/merge/p2p-interface.md @@ -81,7 +81,7 @@ details on how to handle transitioning gossip topics for the Merge. **Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_range/2/` Request and Response remain unchanged. -`MERGE_FORK_VERSION` is used as an additional `context` to specify the Merge block type. +The Merge fork-digest is introduced to the `context` enum to specify the Merge block type. Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: @@ -98,7 +98,7 @@ Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: **Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/2/` Request and Response remain unchanged. -`MERGE_FORK_VERSION` is used as an additional `context` to specify the Merge block type. +The Merge fork-digest is introduced to the `context` enum to specify the Merge block type. Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: From 1a966d1e378dc719aa12f6eacae854f531eab9fe Mon Sep 17 00:00:00 2001 From: protolambda Date: Mon, 26 Jul 2021 15:26:55 +0200 Subject: [PATCH 025/135] work in progress new sharding fee mechanism --- specs/sharding/beacon-chain.md | 297 +++++++++++++++++---------------- 1 file changed, 156 insertions(+), 141 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 2ab0f68cd..40eabe548 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -15,8 +15,11 @@ - [Misc](#misc) - [Domain types](#domain-types) - [Shard Work Status](#shard-work-status) -- [Preset](#preset) - [Misc](#misc-1) + - [Participation flag indices](#participation-flag-indices) + - [Incentivization weights](#incentivization-weights) +- [Preset](#preset) + - [Misc](#misc-2) - [Shard blob samples](#shard-blob-samples) - [Precomputed size verification points](#precomputed-size-verification-points) - [Gwei values](#gwei-values) @@ -28,6 +31,7 @@ - [New containers](#new-containers) - [`Builder`](#builder) - [`DataCommitment`](#datacommitment) + - [`AttestedDataCommitment`](#attesteddatacommitment) - [ShardBlobBody](#shardblobbody) - [`ShardBlobBodySummary`](#shardblobbodysummary) - [`ShardBlob`](#shardblob) @@ -39,15 +43,15 @@ - [`ShardProposerSlashing`](#shardproposerslashing) - [`ShardWork`](#shardwork) - [Helper functions](#helper-functions) - - [Misc](#misc-2) + - [Misc](#misc-3) - [`next_power_of_two`](#next_power_of_two) - [`compute_previous_slot`](#compute_previous_slot) - - [`compute_updated_gasprice`](#compute_updated_gasprice) + - [`compute_updated_sample_price`](#compute_updated_sample_price) - [`compute_committee_source_epoch`](#compute_committee_source_epoch) + - [`batch_apply_participation_flag`](#batch_apply_participation_flag) - [Beacon state accessors](#beacon-state-accessors) - [Updated `get_committee_count_per_slot`](#updated-get_committee_count_per_slot) - [`get_active_shard_count`](#get_active_shard_count) - - [`compute_proposer_index`](#compute_proposer_index) - [`get_shard_proposer_index`](#get_shard_proposer_index) - [`get_start_shard`](#get_start_shard) - [`compute_shard_from_committee_index`](#compute_shard_from_committee_index) @@ -55,12 +59,10 @@ - [Block processing](#block-processing) - [Operations](#operations) - [Extended Attestation processing](#extended-attestation-processing) - - [`charge_builder`](#charge_builder) - [`process_shard_header`](#process_shard_header) - [`process_shard_proposer_slashing`](#process_shard_proposer_slashing) - [Epoch transition](#epoch-transition) - [`process_pending_shard_confirmations`](#process_pending_shard_confirmations) - - [`charge_confirmed_shard_fees`](#charge_confirmed_shard_fees) - [`reset_pending_shard_work`](#reset_pending_shard_work) @@ -118,6 +120,30 @@ The following values are (non-configurable) constants used throughout the specif | `SHARD_WORK_CONFIRMED` | `1` | Confirmed, reduced to just the commitment | | `SHARD_WORK_PENDING` | `2` | Pending, a list of competing headers | +### Misc + +TODO: `PARTICIPATION_FLAG_WEIGHTS` backwards-compatibility is difficult, depends on usage. + +| Name | Value | +| - | - | +| `PARTICIPATION_FLAG_WEIGHTS` | `[TIMELY_SOURCE_WEIGHT, TIMELY_TARGET_WEIGHT, TIMELY_HEAD_WEIGHT, TIMELY_SHARD_WEIGHT]` | + +### Participation flag indices + +| Name | Value | +| - | - | +| `TIMELY_SHARD_FLAG_INDEX` | `3` | + +### Incentivization weights + +TODO: determine weight for shard attestations + +| Name | Value | +| - | - | +| `TIMELY_SHARD_WEIGHT` | `uint64(8)` | + +TODO: `WEIGHT_DENOMINATOR` needs to be adjusted, but this breaks a lot of Altair code. + ## Preset ### Misc @@ -125,7 +151,7 @@ The following values are (non-configurable) constants used throughout the specif | Name | Value | Notes | | - | - | - | | `MAX_SHARDS` | `uint64(2**10)` (= 1,024) | Theoretical max shard count (used to determine data structure sizes) | -| `GASPRICE_ADJUSTMENT_COEFFICIENT` | `uint64(2**3)` (= 8) | Gasprice may decrease/increase by at most exp(1 / this value) *per epoch* | +| `SAMPLE_PRICE_ADJUSTMENT_COEFFICIENT` | `uint64(2**3)` (= 8) | Sample price may decrease/increase by at most exp(1 / this value) *per epoch* | | `MAX_SHARD_PROPOSER_SLASHINGS` | `2**4` (= 16) | Maximum amount of shard proposer slashing operations per block | | `MAX_SHARD_HEADERS_PER_SHARD` | `4` | | | `SHARD_STATE_MEMORY_SLOTS` | `uint64(2**8)` (= 256) | Number of slots for which shard commitments and confirmation status is directly available in the state | @@ -150,8 +176,8 @@ The following values are (non-configurable) constants used throughout the specif | Name | Value | Unit | Description | | - | - | - | - | -| `MAX_GASPRICE` | `Gwei(2**33)` (= 8,589,934,592) | Gwei | Max gasprice charged for a TARGET-sized shard block | -| `MIN_GASPRICE` | `Gwei(2**3)` (= 8) | Gwei | Min gasprice charged for a TARGET-sized shard block | +| `MAX_SAMPLE_PRICE` | `Gwei(2**33)` (= 8,589,934,592) | Gwei | Max sample charged for a TARGET-sized shard blob | +| `MIN_SAMPLE_PRICE` | `Gwei(2**3)` (= 8) | Gwei | Min sample price charged for a TARGET-sized shard blob | ## Configuration @@ -199,7 +225,7 @@ class BeaconState(merge.BeaconState): blob_builder_balances: List[Gwei, BLOB_BUILDER_REGISTRY_LIMIT] # A ring buffer of the latest slots, with information per active shard. shard_buffer: Vector[List[ShardWork, MAX_SHARDS], SHARD_STATE_MEMORY_SLOTS] - shard_gasprice: uint64 + shard_sample_price: uint64 ``` ## New containers @@ -223,6 +249,18 @@ class DataCommitment(Container): length: uint64 ``` +### `AttestedDataCommitment` + +```python +class AttestedDataCommitment(Container): + # KZG10 commitment to the data, and length + commitment: DataCommitment + # hash_tree_root of the ShardBlobHeader (stored so that attestations can be checked against it) + root: Root + # The proposer who included the shard-header + includer_index: ValidatorIndex +``` + ### ShardBlobBody Unsigned shard data, bundled by a shard-builder. @@ -238,7 +276,10 @@ class ShardBlobBody(Container): data: List[BLSPoint, POINTS_PER_SAMPLE * MAX_SAMPLES_PER_BLOB] # Latest block root of the Beacon Chain, before shard_blob.slot beacon_block_root: Root - # TODO: fee payment amount fields (EIP 1559 like) + # fee payment fields (EIP 1559 like) + # TODO: express in MWei instead? + max_priority_fee_per_sample: Gwei + max_fee_per_sample: Gwei ``` ### `ShardBlobBodySummary` @@ -258,7 +299,10 @@ class ShardBlobBodySummary(Container): data_root: Root # Latest block root of the Beacon Chain, before shard_blob.slot beacon_block_root: Root - # TODO: fee payment amount fields (EIP 1559 like) + # fee payment fields (EIP 1559 like) + # TODO: express in MWei instead? + max_priority_fee_per_sample: Gwei + max_fee_per_sample: Gwei ``` ### `ShardBlob` @@ -320,10 +364,8 @@ class SignedShardBlobHeader(Container): ```python class PendingShardHeader(Container): - # KZG10 commitment to the data - commitment: DataCommitment - # hash_tree_root of the ShardBlobHeader (stored so that attestations can be checked against it) - root: Root + # The commitment that is attested + attested: AttestedDataCommitment # Who voted for the header votes: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] # Sum of effective balances of votes @@ -370,7 +412,7 @@ class ShardWork(Container): # Upon confirmation the data is reduced to just the commitment. status: Union[ # See Shard Work Status enum None, # SHARD_WORK_UNCONFIRMED - DataCommitment, # SHARD_WORK_CONFIRMED + ConfirmedDataCommitment, # SHARD_WORK_CONFIRMED List[PendingShardHeader, MAX_SHARD_HEADERS_PER_SHARD] # SHARD_WORK_PENDING ] ``` @@ -396,18 +438,16 @@ def compute_previous_slot(slot: Slot) -> Slot: return Slot(0) ``` -#### `compute_updated_gasprice` +#### `compute_updated_sample_price` ```python -def compute_updated_gasprice(prev_gasprice: Gwei, shard_block_length: uint64, adjustment_quotient: uint64) -> Gwei: - if shard_block_length > TARGET_SAMPLES_PER_BLOB: - delta = max(1, prev_gasprice * (shard_block_length - TARGET_SAMPLES_PER_BLOB) - // TARGET_SAMPLES_PER_BLOB // adjustment_quotient) - return min(prev_gasprice + delta, MAX_GASPRICE) +def compute_updated_sample_price(prev_price: Gwei, samples: uint64, adjustment_quotient: uint64) -> Gwei: + if samples > TARGET_SAMPLES_PER_BLOB: + delta = max(1, prev_price * (samples - TARGET_SAMPLES_PER_BLOB) // TARGET_SAMPLES_PER_BLOB // adjustment_quotient) + return min(prev_price + delta, MAX_SAMPLE_PRICE) else: - delta = max(1, prev_gasprice * (TARGET_SAMPLES_PER_BLOB - shard_block_length) - // TARGET_SAMPLES_PER_BLOB // adjustment_quotient) - return max(prev_gasprice, MIN_GASPRICE + delta) - delta + delta = max(1, prev_price * (TARGET_SAMPLES_PER_BLOB - samples) // TARGET_SAMPLES_PER_BLOB // adjustment_quotient) + return max(prev_price, MIN_SAMPLE_PRICE + delta) - delta ``` #### `compute_committee_source_epoch` @@ -423,6 +463,20 @@ def compute_committee_source_epoch(epoch: Epoch, period: uint64) -> Epoch: return source_epoch ``` +#### `batch_apply_participation_flag` + +```python +def batch_apply_participation_flag(state: BeaconState, bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE], + epoch: Epoch, full_committee: Sequence[ValidatorIndex], flag_index: int): + if epoch == get_current_epoch(state): + epoch_participation = state.current_epoch_participation + else: + epoch_participation = state.previous_epoch_participation + for bit, index in zip(bits, full_committee): + if bit: + epoch_participation[index] = add_flag(epoch_participation[index], flag_index) +``` + ### Beacon state accessors #### Updated `get_committee_count_per_slot` @@ -449,34 +503,6 @@ def get_active_shard_count(state: BeaconState, epoch: Epoch) -> uint64: return INITIAL_ACTIVE_SHARDS ``` -#### `compute_proposer_index` - -Updated version to get a proposer index that will only allow proposers with a certain minimum balance, -ensuring that the balance is always sufficient to cover gas costs. - -```python -def compute_proposer_index(beacon_state: BeaconState, - indices: Sequence[ValidatorIndex], - seed: Bytes32, - min_effective_balance: Gwei = Gwei(0)) -> ValidatorIndex: - """ - Return from ``indices`` a random index sampled by effective balance. - """ - assert len(indices) > 0 - MAX_RANDOM_BYTE = 2**8 - 1 - i = uint64(0) - total = uint64(len(indices)) - while True: - candidate_index = indices[compute_shuffled_index(i % total, total, seed)] - random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] - effective_balance = beacon_state.validators[candidate_index].effective_balance - if effective_balance <= min_effective_balance: - continue - if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: - return candidate_index - i += 1 -``` - #### `get_shard_proposer_index` ```python @@ -486,18 +512,8 @@ def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard """ epoch = compute_epoch_at_slot(slot) seed = hash(get_seed(beacon_state, epoch, DOMAIN_SHARD_BLOB) + uint_to_bytes(slot) + uint_to_bytes(shard)) - - # Proposer must have sufficient balance to pay for worst case fee burn - EFFECTIVE_BALANCE_MAX_DOWNWARD_DEVIATION = ( - EFFECTIVE_BALANCE_INCREMENT - EFFECTIVE_BALANCE_INCREMENT - * HYSTERESIS_DOWNWARD_MULTIPLIER // HYSTERESIS_QUOTIENT - ) - min_effective_balance = ( - beacon_state.shard_gasprice * MAX_SAMPLES_PER_BLOB // TARGET_SAMPLES_PER_BLOB - + EFFECTIVE_BALANCE_MAX_DOWNWARD_DEVIATION - ) indices = get_active_validator_indices(state, epoch) - return compute_proposer_index(beacon_state, indices, seed, min_effective_balance) + return compute_proposer_index(beacon_state, indices, seed) ``` #### `get_start_shard` @@ -564,6 +580,12 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: for_ops(body.shard_proposer_slashings, process_shard_proposer_slashing) # Limit is dynamic based on active shard count assert len(body.shard_headers) <= MAX_SHARD_HEADERS_PER_SHARD * get_active_shard_count(state, get_current_epoch(state)) + # Included shard headers must be sorted by shard index, the base-fee is adjusted in sequence (capacity is staggered) + # Duplicates (same slot and shard) are allowed, although slashable, only the first affects capacity. + if len(body.shard_headers) > 0: + shard = 0 + for i, header in body.shard_headers[1:] + for_ops(body.shard_headers, process_shard_header) # New attestation processing for_ops(body.attestations, process_attestation) @@ -576,22 +598,30 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: ```python def process_attestation(state: BeaconState, attestation: Attestation) -> None: altair.process_attestation(state, attestation) - update_pending_shard_work(state, attestation) + process_attested_shard_work(state, attestation) ``` ```python -def update_pending_shard_work(state: BeaconState, attestation: Attestation) -> None: +def process_attested_shard_work(state: BeaconState, attestation: Attestation) -> None: attestation_shard = compute_shard_from_committee_index( state, attestation.data.slot, attestation.data.index, ) + full_committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index) + buffer_index = attestation.data.slot % SHARD_STATE_MEMORY_SLOTS committee_work = state.shard_buffer[buffer_index][attestation_shard] # Skip attestation vote accounting if the header is not pending if committee_work.status.selector != SHARD_WORK_PENDING: - # TODO In Altair: set participation bit flag, if attestation matches winning header. + # If the data was already confirmed, check if this matches, to apply the flag to the attesters. + if committee_work.status.selector == SHARD_WORK_CONFIRMED: + attested: AttestedDataCommitment = current_headers[header_index] + if attested.root == attestation.data.shard_blob_root: + batch_apply_participation_flag(state, attestation.aggregation_bits, + attestation.data.target.epoch, + full_committee, TIMELY_SHARD_FLAG_INDEX) return current_headers: Sequence[PendingShardHeader] = committee_work.status.value @@ -599,16 +629,16 @@ def update_pending_shard_work(state: BeaconState, attestation: Attestation) -> N # Find the corresponding header, abort if it cannot be found header_index = len(current_headers) for i, header in enumerate(current_headers): - if attestation.data.shard_blob_root == header.root: + if attestation.data.shard_blob_root == header.attested.root: header_index = i break + # Attestations for an unknown header do not count towards shard confirmations, but can otherwise be valid. if header_index == len(current_headers): - # TODO: Attestations may be re-included if headers are included late. + # Note: Attestations may be re-included if headers are included late. return pending_header: PendingShardHeader = current_headers[header_index] - full_committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index) # The weight may be outdated if it is not the initial weight, and from a previous epoch if pending_header.weight != 0 and compute_epoch_at_slot(pending_header.update_slot) < get_current_epoch(state): @@ -629,8 +659,11 @@ def update_pending_shard_work(state: BeaconState, attestation: Attestation) -> N # Check if the PendingShardHeader is eligible for expedited confirmation, requiring 2/3 of balance attesting if pending_header.weight * 3 >= full_committee_balance * 2: - # TODO In Altair: set participation bit flag for voters of this early winning header - if pending_header.commitment == DataCommitment(): + # participants of the winning header are remembered with participation flags + batch_apply_participation_flag(state, pending_header.votes, attestation.data.target.epoch, + full_committee, TIMELY_SHARD_FLAG_INDEX) + + if pending_header.attested.commitment == DataCommitment(): # The committee voted to not confirm anything state.shard_buffer[buffer_index][attestation_shard].status.change( selector=SHARD_WORK_UNCONFIRMED, @@ -639,26 +672,10 @@ def update_pending_shard_work(state: BeaconState, attestation: Attestation) -> N else: state.shard_buffer[buffer_index][attestation_shard].status.change( selector=SHARD_WORK_CONFIRMED, - value=pending_header.commitment, + value=pending_header.attested, ) ``` - -#### `charge_builder` - -```python -def charge_builder(state: BeaconState, index: BuilderIndex, fee: Gwei) -> None: - """ - Decrease the builder balance at index ``index`` by ``fee``, with underflow check. - """ - # TODO: apply stricter requirement to protect against fee-acceptance race conditions, e.g.: - # - balance per shard (or builders per shard) - # - balance / shard_count > fee - # TODO: also consider requirement to pay for base-fee of shard-data - assert state.blob_builder_balances[index] >= fee - state.blob_builder_balances[index] -= fee -``` - ##### `process_shard_header` ```python @@ -673,7 +690,16 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade # Verify that the header is within the processing time window assert header_epoch in [get_previous_epoch(state), get_current_epoch(state)] # Verify that the shard is active - assert shard < get_active_shard_count(state, header_epoch) + start_shard = get_start_shard(state, slot) + committees_per_slot = get_committee_count_per_slot(state, header_epoch) + end_shard = start_shard + committees_per_slot + shard_count = get_active_shard_count(state, header_epoch) + # Per slot, there may be max. shard_count committees. + # If there are shard_count * 2/3 per slot, then wrap around. + if end_shard >= shard_count: + assert not (end_shard - shard_count <= shard < start_shard) + else: + assert start_shard <= shard < end_shard # Verify that the block root matches, # to ensure the header will only be included in this specific Beacon Chain sub-tree. assert header.body_summary.beacon_block_root == get_block_root_at_slot(state, slot - 1) @@ -685,7 +711,7 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade # Check that this header is not yet in the pending list current_headers: List[PendingShardHeader, MAX_SHARD_HEADERS_PER_SHARD] = committee_work.status.value header_root = hash_tree_root(header) - assert header_root not in [pending_header.root for pending_header in current_headers] + assert header_root not in [pending_header.attested.root for pending_header in current_headers] # Verify proposer matches assert header.proposer_index == get_shard_proposer_index(state, slot, shard) @@ -705,20 +731,46 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade == bls.Pairing(body_summary.commitment.point, G2_SETUP[-body_summary.commitment.length]) ) - # Charge builder, with hard balance requirement - fee = Gwei(123) # TODO EIP 1559 like fee? Burn some of it? - charge_builder(state, header.builder_index, fee) - # TODO: proposer is charged for confirmed headers (see charge_confirmed_shard_fees). - # Need to align incentive, so proposer does not gain from including unconfirmed headers - increase_balance(state, header.proposer_index, fee) + # Charge EIP 1559 fee, builder pays for opportunity, and is responsible for later availability, + # or fail to publish at their own expense. + samples = blob_summary.commitment.length + # TODO: overflows, need bigger int type + max_fee = blob_summary.max_fee_per_sample * samples + + # Builder must have sufficient balance, even if max_fee is not completely utilized + assert state.blob_builder_balances[header.builder_index] > max_fee + + base_fee = state.shard_sample_price * samples + # Base fee must be paid + assert max_fee >= base_fee + + # Remaining fee goes towards proposer for prioritizing, up to a maximum + max_priority_fee = blob_summary.max_priority_fee_per_sample * samples + priority_fee = min(max_fee - base_fee, max_priority_fee) + + # Burn base fee, take priority fee + decrease_balance(state, header.builder_index, base_fee + priority_fee) + # Pay out priority fee + increase_balance(state, header.proposer_index, priority_fee) + + # Track updated sample price + adjustment_quotient = ( + get_active_shard_count(state, previous_epoch) + * SLOTS_PER_EPOCH * SAMPLE_PRICE_ADJUSTMENT_COEFFICIENT + ) + state.shard_sample_price = compute_updated_sample_price( + state.shard_sample_price, blob_summary.commitment.length, adjustment_quotient) # Initialize the pending header index = compute_committee_index_from_shard(state, slot, shard) committee_length = len(get_beacon_committee(state, slot, index)) initial_votes = Bitlist[MAX_VALIDATORS_PER_COMMITTEE]([0] * committee_length) pending_header = PendingShardHeader( - commitment=blob_summary.commitment, - root=header_root, + attested=AttestedDataCommitment( + commitment=blob_summary.commitment, + root=header_root, + includer_index=get_beacon_proposer_index(state), + ) votes=initial_votes, weight=0, update_slot=state.slot, @@ -777,13 +829,12 @@ This epoch transition overrides the Merge epoch transition: def process_epoch(state: BeaconState) -> None: # Sharding pre-processing process_pending_shard_confirmations(state) - charge_confirmed_shard_fees(state) reset_pending_shard_work(state) # Base functionality process_justification_and_finalization(state) process_inactivity_updates(state) - process_rewards_and_penalties(state) + process_rewards_and_penalties(state) # Note: modified, see new TIMELY_SHARD_FLAG_INDEX process_registry_updates(state) process_slashings(state) process_eth1_data_reset(state) @@ -815,45 +866,10 @@ def process_pending_shard_confirmations(state: BeaconState) -> None: if committee_work.status.selector == SHARD_WORK_PENDING: winning_header = max(committee_work.status.value, key=lambda header: header.weight) # TODO In Altair: set participation bit flag of voters for winning header - if winning_header.commitment == DataCommitment(): + if winning_header.attested.commitment == DataCommitment(): committee_work.status.change(selector=SHARD_WORK_UNCONFIRMED, value=None) else: - committee_work.status.change(selector=SHARD_WORK_CONFIRMED, value=winning_header.commitment) -``` - -#### `charge_confirmed_shard_fees` - -```python -def charge_confirmed_shard_fees(state: BeaconState) -> None: - new_gasprice = state.shard_gasprice - previous_epoch = get_previous_epoch(state) - previous_epoch_start_slot = compute_start_slot_at_epoch(previous_epoch) - adjustment_quotient = ( - get_active_shard_count(state, previous_epoch) - * SLOTS_PER_EPOCH * GASPRICE_ADJUSTMENT_COEFFICIENT - ) - # Iterate through confirmed shard-headers - for slot in range(previous_epoch_start_slot, previous_epoch_start_slot + SLOTS_PER_EPOCH): - buffer_index = slot % SHARD_STATE_MEMORY_SLOTS - for shard_index in range(len(state.shard_buffer[buffer_index])): - committee_work = state.shard_buffer[buffer_index][shard_index] - if committee_work.status.selector == SHARD_WORK_CONFIRMED: - commitment: DataCommitment = committee_work.status.value - # Charge EIP 1559 fee - proposer = get_shard_proposer_index(state, slot, Shard(shard_index)) - fee = ( - (state.shard_gasprice * commitment.length) - // TARGET_SAMPLES_PER_BLOB - ) - decrease_balance(state, proposer, fee) - - # Track updated gas price - new_gasprice = compute_updated_gasprice( - new_gasprice, - commitment.length, - adjustment_quotient, - ) - state.shard_gasprice = new_gasprice + committee_work.status.change(selector=SHARD_WORK_CONFIRMED, value=winning_header.attested) ``` #### `reset_pending_shard_work` @@ -881,8 +897,7 @@ def reset_pending_shard_work(state: BeaconState) -> None: selector=SHARD_WORK_PENDING, value=List[PendingShardHeader, MAX_SHARD_HEADERS_PER_SHARD]( PendingShardHeader( - commitment=DataCommitment(), - root=Root(), + attested=AttestedDataCommitment() votes=Bitlist[MAX_VALIDATORS_PER_COMMITTEE]([0] * committee_length), weight=0, update_slot=slot, From 9a1a30c3d469e1ab55234df28a8ee97b994d0180 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Mon, 26 Jul 2021 19:30:50 +0600 Subject: [PATCH 026/135] Rebase Merge spec with London --- specs/merge/beacon-chain.md | 55 +++++++++++++++++++ .../test/helpers/execution_payload.py | 5 +- .../pyspec/eth2spec/test/helpers/genesis.py | 1 + 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 01c54e017..1a9c933d6 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -59,6 +59,10 @@ This patch adds transaction execution to the beacon chain as part of the Merge f | `MAX_BYTES_PER_OPAQUE_TRANSACTION` | `uint64(2**20)` (= 1,048,576) | | `MAX_TRANSACTIONS_PER_PAYLOAD` | `uint64(2**14)` (= 16,384) | | `BYTES_PER_LOGS_BLOOM` | `uint64(2**8)` (= 256) | +| `GAS_LIMIT_DIVISOR` | `uint64(2**10)` (= 1,024) | +| `MIN_GAS_LIMIT` | `uint64(5000)` (= 5,000) | +| `BASE_FEE_MAX_CHANGE_DENOMINATOR` | `uint64(2**3)` (= 8) | +| `ELASTICITY_MULTIPLIER` | `uint64(2**1)` (= 2) | ## Containers @@ -141,6 +145,7 @@ class ExecutionPayload(Container): gas_limit: uint64 gas_used: uint64 timestamp: uint64 + base_fee_per_gas: uint64 # base fee introduced in eip-1559 # Extra payload fields block_hash: Hash32 # Hash of execution block transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] @@ -161,6 +166,7 @@ class ExecutionPayloadHeader(Container): gas_limit: uint64 gas_used: uint64 timestamp: uint64 + base_fee_per_gas: uint64 # Extra payload fields block_hash: Hash32 # Hash of execution block transactions_root: Root @@ -239,6 +245,51 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: ### Execution payload processing +#### `is_valid_gas_limit` + +```python +def is_valid_gas_limit(payload: ExecutionPayload, parent: ExecutionPayloadHeader) -> bool: + parent_gas_limit = parent.gas_limit + + # check if the payload used too much gas + if payload.gas_used > payload.gas_limit: + return False + + # check if the payload changed the gas limit too much + if payload.gas_limit >= parent_gas_limit + parent_gas_limit // GAS_LIMIT_DIVISOR: + return False + if payload.gas_limit <= parent_gas_limit - parent_gas_limit // GAS_LIMIT_DIVISOR: + return False + + # check if the gas limit is at least the minimum gas limit + if payload.gas_limit < MIN_GAS_LIMIT: + return False + + return True +``` + +#### `compute_base_fee_per_gas` + +```python +def compute_base_fee_per_gas(payload: ExecutionPayload, parent: ExecutionPayloadHeader) -> uint64: + parent_gas_target = parent.gas_limit // ELASTICITY_MULTIPLIER + parent_base_fee_per_gas = parent.base_fee_per_gas + parent_gas_used = payload.gas_used + + if parent_gas_used == parent_gas_target: + return parent_base_fee_per_gas + elif parent_gas_used > parent_gas_target: + gas_used_delta = parent_gas_used - parent_gas_target + base_fee_per_gas_delta = \ + max(parent_base_fee_per_gas * gas_used_delta // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR, 1) + return parent_base_fee_per_gas + base_fee_per_gas_delta + else: + gas_used_delta = parent_gas_target - parent_gas_used + base_fee_per_gas_delta = \ + parent_base_fee_per_gas * gas_used_delta // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR + return max(parent_base_fee_per_gas - base_fee_per_gas_delta, 0) +``` + #### `process_execution_payload` *Note:* This function depends on `process_randao` function call as it retrieves the most recent randao mix from the `state`. Implementations that are considering parallel processing of execution payload with respect to beacon chain state transition function should work around this dependency. @@ -250,6 +301,8 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe assert payload.parent_hash == state.latest_execution_payload_header.block_hash assert payload.block_number == state.latest_execution_payload_header.block_number + uint64(1) assert payload.random == get_randao_mix(state, get_current_epoch(state)) + assert payload.base_fee_per_gas == compute_base_fee_per_gas(payload, state.latest_execution_payload_header) + assert is_valid_gas_limit(payload, state.latest_execution_payload_header) # Verify timestamp assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) # Verify the execution payload is valid @@ -266,6 +319,7 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe gas_limit=payload.gas_limit, gas_used=payload.gas_used, timestamp=payload.timestamp, + base_fee_per_gas=payload.base_fee_per_gas, block_hash=payload.block_hash, transactions_root=hash_tree_root(payload.transactions), ) @@ -321,6 +375,7 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, state.latest_execution_payload_header.block_hash = eth1_block_hash state.latest_execution_payload_header.timestamp = eth1_timestamp state.latest_execution_payload_header.random = eth1_block_hash + state.latest_execution_payload_header.gas_limit = MIN_GAS_LIMIT return state ``` diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index c41a05079..5c7eb6de0 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -17,12 +17,14 @@ def build_empty_execution_payload(spec, state, randao_mix=None): logs_bloom=spec.ByteVector[spec.BYTES_PER_LOGS_BLOOM](), # TODO: zeroed logs bloom for empty logs ok? block_number=latest.block_number + 1, random=randao_mix, - gas_limit=latest.gas_limit, # retain same limit + gas_limit=max(latest.gas_limit, spec.MIN_GAS_LIMIT), gas_used=0, # empty block, 0 gas timestamp=timestamp, + base_fee_per_gas=spec.uint64(0), block_hash=spec.Hash32(), transactions=empty_txs, ) + payload.base_fee_per_gas = spec.compute_base_fee_per_gas(latest, payload) # TODO: real RLP + block hash logic would be nice, requires RLP and keccak256 dependency however. payload.block_hash = spec.Hash32(spec.hash(payload.hash_tree_root() + b"FAKE RLP HASH")) @@ -41,6 +43,7 @@ def get_execution_payload_header(spec, execution_payload): gas_limit=execution_payload.gas_limit, gas_used=execution_payload.gas_used, timestamp=execution_payload.timestamp, + base_fee_per_gas=execution_payload.base_fee_per_gas, block_hash=execution_payload.block_hash, transactions_root=spec.hash_tree_root(execution_payload.transactions) ) diff --git a/tests/core/pyspec/eth2spec/test/helpers/genesis.py b/tests/core/pyspec/eth2spec/test/helpers/genesis.py index a9eb59f67..fc14c0aef 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/genesis.py +++ b/tests/core/pyspec/eth2spec/test/helpers/genesis.py @@ -78,5 +78,6 @@ def create_genesis_state(spec, validator_balances, activation_threshold): # Initialize the execution payload header (with block number and genesis time set to 0) state.latest_execution_payload_header.block_hash = eth1_block_hash state.latest_execution_payload_header.random = eth1_block_hash + state.latest_execution_payload_header.gas_limit = spec.MIN_GAS_LIMIT return state From 789e10ea7c890a909aeaaf7028a49aee9d795ff1 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Mon, 26 Jul 2021 20:09:57 +0600 Subject: [PATCH 027/135] Update toc --- specs/merge/beacon-chain.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 1a9c933d6..1a99a1227 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -31,6 +31,8 @@ - [`on_payload`](#on_payload) - [Block processing](#block-processing) - [Execution payload processing](#execution-payload-processing) + - [`is_valid_gas_limit`](#is_valid_gas_limit) + - [`compute_base_fee_per_gas`](#compute_base_fee_per_gas) - [`process_execution_payload`](#process_execution_payload) - [Testing](#testing) From 756eb90bfed131f7c63b0416f3b8b14efabbd6da Mon Sep 17 00:00:00 2001 From: protolambda Date: Mon, 26 Jul 2021 16:27:19 +0200 Subject: [PATCH 028/135] consider per-slot sample target adjustment, to avoid racing and ordering problems --- specs/sharding/beacon-chain.md | 43 ++++++++++++++-------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 40eabe548..80e2ec85c 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -441,7 +441,8 @@ def compute_previous_slot(slot: Slot) -> Slot: #### `compute_updated_sample_price` ```python -def compute_updated_sample_price(prev_price: Gwei, samples: uint64, adjustment_quotient: uint64) -> Gwei: +def compute_updated_sample_price(prev_price: Gwei, samples: uint64, active_shards: uint64) -> Gwei: + adjustment_quotient = active_shards * SLOTS_PER_EPOCH * SAMPLE_PRICE_ADJUSTMENT_COEFFICIENT if samples > TARGET_SAMPLES_PER_BLOB: delta = max(1, prev_price * (samples - TARGET_SAMPLES_PER_BLOB) // TARGET_SAMPLES_PER_BLOB // adjustment_quotient) return min(prev_price + delta, MAX_SAMPLE_PRICE) @@ -578,19 +579,19 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: for_ops(body.attester_slashings, process_attester_slashing) # New shard proposer slashing processing for_ops(body.shard_proposer_slashings, process_shard_proposer_slashing) - # Limit is dynamic based on active shard count + + # Limit is dynamic: based on active shard count assert len(body.shard_headers) <= MAX_SHARD_HEADERS_PER_SHARD * get_active_shard_count(state, get_current_epoch(state)) - # Included shard headers must be sorted by shard index, the base-fee is adjusted in sequence (capacity is staggered) - # Duplicates (same slot and shard) are allowed, although slashable, only the first affects capacity. - if len(body.shard_headers) > 0: - shard = 0 - for i, header in body.shard_headers[1:] - for_ops(body.shard_headers, process_shard_header) + # New attestation processing for_ops(body.attestations, process_attestation) for_ops(body.deposits, process_deposit) for_ops(body.voluntary_exits, process_voluntary_exit) + + # TODO: to avoid parallel shards racing, and avoid inclusion-order problems, + # update the fee price per slot, instead of per header. + # state.shard_sample_price = compute_updated_sample_price(state.shard_sample_price, ?, shard_count) ``` ##### Extended Attestation processing @@ -689,17 +690,15 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade header_epoch = compute_epoch_at_slot(slot) # Verify that the header is within the processing time window assert header_epoch in [get_previous_epoch(state), get_current_epoch(state)] - # Verify that the shard is active - start_shard = get_start_shard(state, slot) - committees_per_slot = get_committee_count_per_slot(state, header_epoch) - end_shard = start_shard + committees_per_slot + # Verify that the shard is valid shard_count = get_active_shard_count(state, header_epoch) - # Per slot, there may be max. shard_count committees. - # If there are shard_count * 2/3 per slot, then wrap around. - if end_shard >= shard_count: - assert not (end_shard - shard_count <= shard < start_shard) - else: - assert start_shard <= shard < end_shard + assert shard < shard_count + # Verify that a committee is able to attest this (slot, shard) + start_shard = get_start_shard(state, slot) + committee_index = (shard_count + shard - start_shard) % shard_count + committees_per_slot = get_committee_count_per_slot(state, header_epoch) + assert committee_index <= committees_per_slot + # Verify that the block root matches, # to ensure the header will only be included in this specific Beacon Chain sub-tree. assert header.body_summary.beacon_block_root == get_block_root_at_slot(state, slot - 1) @@ -753,14 +752,6 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade # Pay out priority fee increase_balance(state, header.proposer_index, priority_fee) - # Track updated sample price - adjustment_quotient = ( - get_active_shard_count(state, previous_epoch) - * SLOTS_PER_EPOCH * SAMPLE_PRICE_ADJUSTMENT_COEFFICIENT - ) - state.shard_sample_price = compute_updated_sample_price( - state.shard_sample_price, blob_summary.commitment.length, adjustment_quotient) - # Initialize the pending header index = compute_committee_index_from_shard(state, slot, shard) committee_length = len(get_beacon_committee(state, slot, index)) From d47d2f92cc8fe630ba1f182634a0186bf76924d4 Mon Sep 17 00:00:00 2001 From: protolambda Date: Tue, 27 Jul 2021 14:48:21 +0200 Subject: [PATCH 029/135] shard fees: implement review suggestions from @nashatyrev --- specs/sharding/beacon-chain.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 80e2ec85c..93ac2278a 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -412,7 +412,7 @@ class ShardWork(Container): # Upon confirmation the data is reduced to just the commitment. status: Union[ # See Shard Work Status enum None, # SHARD_WORK_UNCONFIRMED - ConfirmedDataCommitment, # SHARD_WORK_CONFIRMED + AttestedDataCommitment, # SHARD_WORK_CONFIRMED List[PendingShardHeader, MAX_SHARD_HEADERS_PER_SHARD] # SHARD_WORK_PENDING ] ``` @@ -737,7 +737,7 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade max_fee = blob_summary.max_fee_per_sample * samples # Builder must have sufficient balance, even if max_fee is not completely utilized - assert state.blob_builder_balances[header.builder_index] > max_fee + assert state.blob_builder_balances[header.builder_index] >= max_fee base_fee = state.shard_sample_price * samples # Base fee must be paid @@ -748,7 +748,8 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade priority_fee = min(max_fee - base_fee, max_priority_fee) # Burn base fee, take priority fee - decrease_balance(state, header.builder_index, base_fee + priority_fee) + # priority_fee <= max_fee - base_fee, thus priority_fee + base_fee <= max_fee, thus sufficient balance. + state.blob_builder_balances[header.builder_index] -= base_fee + priority_fee # Pay out priority fee increase_balance(state, header.proposer_index, priority_fee) From 0daaafbc98e32925120fb4a22665286548e70988 Mon Sep 17 00:00:00 2001 From: protolambda Date: Wed, 28 Jul 2021 22:00:23 +0200 Subject: [PATCH 030/135] fix union value retrieval, thanks @terencechain --- specs/sharding/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 93ac2278a..25c3423b7 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -618,7 +618,7 @@ def process_attested_shard_work(state: BeaconState, attestation: Attestation) -> if committee_work.status.selector != SHARD_WORK_PENDING: # If the data was already confirmed, check if this matches, to apply the flag to the attesters. if committee_work.status.selector == SHARD_WORK_CONFIRMED: - attested: AttestedDataCommitment = current_headers[header_index] + attested: AttestedDataCommitment = committee_work.status.value if attested.root == attestation.data.shard_blob_root: batch_apply_participation_flag(state, attestation.aggregation_bits, attestation.data.target.epoch, From c311712bcad2c795b71a34e8a81d47cb1b38f3ae Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Fri, 30 Jul 2021 12:36:02 +0600 Subject: [PATCH 031/135] Apply suggestions from code review Co-authored-by: Hsiao-Wei Wang --- specs/merge/beacon-chain.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 1a99a1227..08d357c3c 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -147,7 +147,7 @@ class ExecutionPayload(Container): gas_limit: uint64 gas_used: uint64 timestamp: uint64 - base_fee_per_gas: uint64 # base fee introduced in eip-1559 + base_fee_per_gas: uint64 # base fee introduced in EIP-1559 # Extra payload fields block_hash: Hash32 # Hash of execution block transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] @@ -253,17 +253,17 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: def is_valid_gas_limit(payload: ExecutionPayload, parent: ExecutionPayloadHeader) -> bool: parent_gas_limit = parent.gas_limit - # check if the payload used too much gas + # Check if the payload used too much gas if payload.gas_used > payload.gas_limit: return False - # check if the payload changed the gas limit too much + # Check if the payload changed the gas limit too much if payload.gas_limit >= parent_gas_limit + parent_gas_limit // GAS_LIMIT_DIVISOR: return False if payload.gas_limit <= parent_gas_limit - parent_gas_limit // GAS_LIMIT_DIVISOR: return False - # check if the gas limit is at least the minimum gas limit + # Check if the gas limit is at least the minimum gas limit if payload.gas_limit < MIN_GAS_LIMIT: return False @@ -282,13 +282,16 @@ def compute_base_fee_per_gas(payload: ExecutionPayload, parent: ExecutionPayload return parent_base_fee_per_gas elif parent_gas_used > parent_gas_target: gas_used_delta = parent_gas_used - parent_gas_target - base_fee_per_gas_delta = \ - max(parent_base_fee_per_gas * gas_used_delta // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR, 1) + base_fee_per_gas_delta = max( + parent_base_fee_per_gas * gas_used_delta // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR, + 1, + ) return parent_base_fee_per_gas + base_fee_per_gas_delta else: gas_used_delta = parent_gas_target - parent_gas_used - base_fee_per_gas_delta = \ + base_fee_per_gas_delta = ( parent_base_fee_per_gas * gas_used_delta // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR + ) return max(parent_base_fee_per_gas - base_fee_per_gas_delta, 0) ``` From 5d5a9e392b0925460d2f189920745bddf84edd02 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Fri, 30 Jul 2021 12:43:54 +0600 Subject: [PATCH 032/135] Rename GAS_LIMIT_DIVISOR to GAS_LIMIT_DENOMINATOR --- specs/merge/beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 08d357c3c..03784ffa8 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -61,7 +61,7 @@ This patch adds transaction execution to the beacon chain as part of the Merge f | `MAX_BYTES_PER_OPAQUE_TRANSACTION` | `uint64(2**20)` (= 1,048,576) | | `MAX_TRANSACTIONS_PER_PAYLOAD` | `uint64(2**14)` (= 16,384) | | `BYTES_PER_LOGS_BLOOM` | `uint64(2**8)` (= 256) | -| `GAS_LIMIT_DIVISOR` | `uint64(2**10)` (= 1,024) | +| `GAS_LIMIT_DENOMINATOR` | `uint64(2**10)` (= 1,024) | | `MIN_GAS_LIMIT` | `uint64(5000)` (= 5,000) | | `BASE_FEE_MAX_CHANGE_DENOMINATOR` | `uint64(2**3)` (= 8) | | `ELASTICITY_MULTIPLIER` | `uint64(2**1)` (= 2) | @@ -258,9 +258,9 @@ def is_valid_gas_limit(payload: ExecutionPayload, parent: ExecutionPayloadHeader return False # Check if the payload changed the gas limit too much - if payload.gas_limit >= parent_gas_limit + parent_gas_limit // GAS_LIMIT_DIVISOR: + if payload.gas_limit >= parent_gas_limit + parent_gas_limit // GAS_LIMIT_DENOMINATOR: return False - if payload.gas_limit <= parent_gas_limit - parent_gas_limit // GAS_LIMIT_DIVISOR: + if payload.gas_limit <= parent_gas_limit - parent_gas_limit // GAS_LIMIT_DENOMINATOR: return False # Check if the gas limit is at least the minimum gas limit From d58ffc7dfc69a11305444f8db23d0352920a3316 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Fri, 30 Jul 2021 16:10:33 +0600 Subject: [PATCH 033/135] Add genesis settings section --- specs/merge/beacon-chain.md | 16 +++++++++++++++- .../eth2spec/test/helpers/execution_payload.py | 4 ++-- .../core/pyspec/eth2spec/test/helpers/genesis.py | 3 ++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 03784ffa8..1341cc9cb 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -12,6 +12,8 @@ - [Custom types](#custom-types) - [Constants](#constants) - [Execution](#execution) +- [Configuration](#configuration) + - [Genesis settings](#genesis-settings) - [Containers](#containers) - [Extended containers](#extended-containers) - [`BeaconBlockBody`](#beaconblockbody) @@ -66,6 +68,17 @@ This patch adds transaction execution to the beacon chain as part of the Merge f | `BASE_FEE_MAX_CHANGE_DENOMINATOR` | `uint64(2**3)` (= 8) | | `ELASTICITY_MULTIPLIER` | `uint64(2**1)` (= 2) | +## Configuration + +### Genesis settings + +*Note*: These configuration settings do not apply to the mainnet and are utilized only by pure Merge testing. + +| Name | Value | +| - | - | +| `GENESIS_GAS_LIMIT` | `uint64(30000000)` (= 30,000,000) | +| `GENESIS_BASE_FEE_PER_GAS` | `uint64(1000000000)` (= 1,000,000,000) | + ## Containers ### Extended containers @@ -380,7 +393,8 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, state.latest_execution_payload_header.block_hash = eth1_block_hash state.latest_execution_payload_header.timestamp = eth1_timestamp state.latest_execution_payload_header.random = eth1_block_hash - state.latest_execution_payload_header.gas_limit = MIN_GAS_LIMIT + state.latest_execution_payload_header.gas_limit = GENESIS_GAS_LIMIT + state.latest_execution_payload_header.base_fee_per_gas = GENESIS_BASE_FEE_PER_GAS return state ``` diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index 5c7eb6de0..ce653a986 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -17,14 +17,14 @@ def build_empty_execution_payload(spec, state, randao_mix=None): logs_bloom=spec.ByteVector[spec.BYTES_PER_LOGS_BLOOM](), # TODO: zeroed logs bloom for empty logs ok? block_number=latest.block_number + 1, random=randao_mix, - gas_limit=max(latest.gas_limit, spec.MIN_GAS_LIMIT), + gas_limit=latest.gas_limit, # retain same limit gas_used=0, # empty block, 0 gas timestamp=timestamp, base_fee_per_gas=spec.uint64(0), block_hash=spec.Hash32(), transactions=empty_txs, ) - payload.base_fee_per_gas = spec.compute_base_fee_per_gas(latest, payload) + payload.base_fee_per_gas = spec.compute_base_fee_per_gas(payload, latest) # TODO: real RLP + block hash logic would be nice, requires RLP and keccak256 dependency however. payload.block_hash = spec.Hash32(spec.hash(payload.hash_tree_root() + b"FAKE RLP HASH")) diff --git a/tests/core/pyspec/eth2spec/test/helpers/genesis.py b/tests/core/pyspec/eth2spec/test/helpers/genesis.py index fc14c0aef..0e9af4cff 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/genesis.py +++ b/tests/core/pyspec/eth2spec/test/helpers/genesis.py @@ -78,6 +78,7 @@ def create_genesis_state(spec, validator_balances, activation_threshold): # Initialize the execution payload header (with block number and genesis time set to 0) state.latest_execution_payload_header.block_hash = eth1_block_hash state.latest_execution_payload_header.random = eth1_block_hash - state.latest_execution_payload_header.gas_limit = spec.MIN_GAS_LIMIT + state.latest_execution_payload_header.gas_limit = spec.GENESIS_GAS_LIMIT + state.latest_execution_payload_header.base_fee_per_gas = spec.GENESIS_BASE_FEE_PER_GAS return state From f1982d4fc3ff2f6320c7f47fcd96c302fc91e95a Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Fri, 30 Jul 2021 16:18:05 +0600 Subject: [PATCH 034/135] Replace underflow check with respective comment --- specs/merge/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 1341cc9cb..590e2bc4f 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -305,7 +305,7 @@ def compute_base_fee_per_gas(payload: ExecutionPayload, parent: ExecutionPayload base_fee_per_gas_delta = ( parent_base_fee_per_gas * gas_used_delta // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR ) - return max(parent_base_fee_per_gas - base_fee_per_gas_delta, 0) + return parent_base_fee_per_gas - base_fee_per_gas_delta # This subtraction can't underflow ``` #### `process_execution_payload` From ab78339350f9e2a66e5aa2dfe98a028393fb474b Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 30 Jul 2021 21:54:55 +0200 Subject: [PATCH 035/135] fix variable name of summary field --- specs/sharding/beacon-chain.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 25c3423b7..13a58e088 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -732,9 +732,9 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade # Charge EIP 1559 fee, builder pays for opportunity, and is responsible for later availability, # or fail to publish at their own expense. - samples = blob_summary.commitment.length + samples = body_summary.commitment.length # TODO: overflows, need bigger int type - max_fee = blob_summary.max_fee_per_sample * samples + max_fee = body_summary.max_fee_per_sample * samples # Builder must have sufficient balance, even if max_fee is not completely utilized assert state.blob_builder_balances[header.builder_index] >= max_fee @@ -744,7 +744,7 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade assert max_fee >= base_fee # Remaining fee goes towards proposer for prioritizing, up to a maximum - max_priority_fee = blob_summary.max_priority_fee_per_sample * samples + max_priority_fee = body_summary.max_priority_fee_per_sample * samples priority_fee = min(max_fee - base_fee, max_priority_fee) # Burn base fee, take priority fee @@ -759,7 +759,7 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade initial_votes = Bitlist[MAX_VALIDATORS_PER_COMMITTEE]([0] * committee_length) pending_header = PendingShardHeader( attested=AttestedDataCommitment( - commitment=blob_summary.commitment, + commitment=body_summary.commitment, root=header_root, includer_index=get_beacon_proposer_index(state), ) From add5810d71ade5a80c65363336f1c1a43a0b4a2f Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 30 Jul 2021 22:06:04 +0200 Subject: [PATCH 036/135] remove unused pending attestation fields --- specs/sharding/beacon-chain.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 13a58e088..94495ef46 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -216,10 +216,6 @@ class BeaconBlockBody(merge.BeaconBlockBody): # [extends The Merge block body] ```python class BeaconState(merge.BeaconState): - # [Updated fields] (Warning: this changes with Altair, Sharding will rebase to use participation-flags) - previous_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH] - current_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH] - # [New fields] # Blob builder registry. blob_builders: List[Builder, BLOB_BUILDER_REGISTRY_LIMIT] blob_builder_balances: List[Gwei, BLOB_BUILDER_REGISTRY_LIMIT] From 2d17c8c3c442c5a3c8d4b1e47e1c335f7fcacd4f Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 30 Jul 2021 22:22:43 +0200 Subject: [PATCH 037/135] move back INITIAL_ACTIVE_SHARDS to preset, avoid changing mainnet config --- specs/sharding/beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 94495ef46..e81e7e764 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -151,6 +151,7 @@ TODO: `WEIGHT_DENOMINATOR` needs to be adjusted, but this breaks a lot of Altair | Name | Value | Notes | | - | - | - | | `MAX_SHARDS` | `uint64(2**10)` (= 1,024) | Theoretical max shard count (used to determine data structure sizes) | +| `INITIAL_ACTIVE_SHARDS` | `uint64(2**6)` (= 64) | Initial shard count | | `SAMPLE_PRICE_ADJUSTMENT_COEFFICIENT` | `uint64(2**3)` (= 8) | Sample price may decrease/increase by at most exp(1 / this value) *per epoch* | | `MAX_SHARD_PROPOSER_SLASHINGS` | `2**4` (= 16) | Maximum amount of shard proposer slashing operations per block | | `MAX_SHARD_HEADERS_PER_SHARD` | `4` | | @@ -181,9 +182,8 @@ TODO: `WEIGHT_DENOMINATOR` needs to be adjusted, but this breaks a lot of Altair ## Configuration -| Name | Value | Notes | -| - | - | - | -| `INITIAL_ACTIVE_SHARDS` | `uint64(2**6)` (= 64) | Initial shard count | +Note: some preset variables may become run-time configurable for testnets, but default to a preset while the spec is unstable. +E.g. `INITIAL_ACTIVE_SHARDS`, `MAX_SAMPLES_PER_BLOB` and `TARGET_SAMPLES_PER_BLOB`. ## Updated containers From 91968de423a8614e1fe20fac7b1b563c4123f7ca Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 30 Jul 2021 22:31:26 +0200 Subject: [PATCH 038/135] update sharding presets --- presets/mainnet/sharding.yaml | 20 +++++++++++--------- presets/minimal/sharding.yaml | 17 ++++++++++------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/presets/mainnet/sharding.yaml b/presets/mainnet/sharding.yaml index 2b78855fc..120a716c9 100644 --- a/presets/mainnet/sharding.yaml +++ b/presets/mainnet/sharding.yaml @@ -1,22 +1,24 @@ # Mainnet preset - Sharding -# Beacon-chain -# --------------------------------------------------------------- # Misc +# --------------------------------------------------------------- # 2**10 (= 1,024) MAX_SHARDS: 1024 -# 2**6 = 64 +# 2**6 (= 64) INITIAL_ACTIVE_SHARDS: 64 # 2**3 (= 8) -GASPRICE_ADJUSTMENT_COEFFICIENT: 8 +SAMPLE_PRICE_ADJUSTMENT_COEFFICIENT: 8 # 2**4 (= 16) MAX_SHARD_PROPOSER_SLASHINGS: 16 - -# Shard block configs -# --------------------------------------------------------------- +# MAX_SHARD_HEADERS_PER_SHARD: 4 # 2**8 (= 256) SHARD_STATE_MEMORY_SLOTS: 256 +# 2**40 (= 1,099,511,627,776) +BLOB_BUILDER_REGISTRY_LIMIT: 1099511627776 + +# Shard blob samples +# --------------------------------------------------------------- # 2**11 (= 2,048) MAX_SAMPLES_PER_BLOCK: 2048 # 2**10 (= 1,1024) @@ -25,6 +27,6 @@ TARGET_SAMPLES_PER_BLOCK: 1024 # Gwei values # --------------------------------------------------------------- # 2**33 (= 8,589,934,592) Gwei -MAX_GASPRICE: 8589934592 +MAX_SAMPLE_PRICE: 8589934592 # 2**3 (= 8) Gwei -MIN_GASPRICE: 8 +MIN_SAMPLE_PRICE: 8 diff --git a/presets/minimal/sharding.yaml b/presets/minimal/sharding.yaml index 10f79c96e..6b8d223b4 100644 --- a/presets/minimal/sharding.yaml +++ b/presets/minimal/sharding.yaml @@ -1,6 +1,6 @@ # Minimal preset - Sharding -# Beacon-chain +# Misc # --------------------------------------------------------------- # Misc # [customized] reduced for testing @@ -8,15 +8,18 @@ MAX_SHARDS: 8 # [customized] reduced for testing INITIAL_ACTIVE_SHARDS: 2 # 2**3 (= 8) -GASPRICE_ADJUSTMENT_COEFFICIENT: 8 +SAMPLE_PRICE_ADJUSTMENT_COEFFICIENT: 8 # [customized] reduced for testing MAX_SHARD_PROPOSER_SLASHINGS: 4 - -# Shard block configs -# --------------------------------------------------------------- +# MAX_SHARD_HEADERS_PER_SHARD: 4 # 2**8 (= 256) SHARD_STATE_MEMORY_SLOTS: 256 +# 2**40 (= 1,099,511,627,776) +BLOB_BUILDER_REGISTRY_LIMIT: 1099511627776 + +# Shard blob samples +# --------------------------------------------------------------- # 2**11 (= 2,048) MAX_SAMPLES_PER_BLOCK: 2048 # 2**10 (= 1,1024) @@ -25,6 +28,6 @@ TARGET_SAMPLES_PER_BLOCK: 1024 # Gwei values # --------------------------------------------------------------- # 2**33 (= 8,589,934,592) Gwei -MAX_GASPRICE: 8589934592 +MAX_SAMPLE_PRICE: 8589934592 # 2**3 (= 8) Gwei -MIN_GASPRICE: 8 +MIN_SAMPLE_PRICE: 8 From 322f072703636b7b70a459fe6af80d1fd42a20c1 Mon Sep 17 00:00:00 2001 From: protolambda Date: Sat, 31 Jul 2021 13:22:26 +0200 Subject: [PATCH 039/135] sharding: remove outdated comment, timely shard attesters are marked in attestation-processing, no need for epoch processing additions --- specs/sharding/beacon-chain.md | 1 - 1 file changed, 1 deletion(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index e81e7e764..f5b212c6e 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -853,7 +853,6 @@ def process_pending_shard_confirmations(state: BeaconState) -> None: committee_work = state.shard_buffer[buffer_index][shard_index] if committee_work.status.selector == SHARD_WORK_PENDING: winning_header = max(committee_work.status.value, key=lambda header: header.weight) - # TODO In Altair: set participation bit flag of voters for winning header if winning_header.attested.commitment == DataCommitment(): committee_work.status.change(selector=SHARD_WORK_UNCONFIRMED, value=None) else: From b262854bb1ee5850fb51b2682eb858a766194ecc Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 3 Aug 2021 21:46:11 +0800 Subject: [PATCH 040/135] Rename the `eth2_*` functions to `eth_` --- setup.py | 6 +++--- specs/altair/beacon-chain.md | 4 ++-- specs/altair/bls.md | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/setup.py b/setup.py index 4c6969a26..ccc3afbe4 100644 --- a/setup.py +++ b/setup.py @@ -56,7 +56,7 @@ def floorlog2(x: int) -> uint64: OPTIMIZED_BLS_AGGREGATE_PUBKEYS = ''' -def eth2_aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey: +def eth_aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey: return bls.AggregatePKs(pubkeys) ''' @@ -480,8 +480,8 @@ def get_generalized_index(ssz_class: Any, *path: Sequence[PyUnion[int, SSZVariab @classmethod def implement_optimizations(cls, functions: Dict[str, str]) -> Dict[str, str]: - if "eth2_aggregate_pubkeys" in functions: - functions["eth2_aggregate_pubkeys"] = OPTIMIZED_BLS_AGGREGATE_PUBKEYS.strip() + if "eth_aggregate_pubkeys" in functions: + functions["eth_aggregate_pubkeys"] = OPTIMIZED_BLS_AGGREGATE_PUBKEYS.strip() return super().implement_optimizations(functions) # diff --git a/specs/altair/beacon-chain.md b/specs/altair/beacon-chain.md index a8d7fd8ef..7c4d20fe5 100644 --- a/specs/altair/beacon-chain.md +++ b/specs/altair/beacon-chain.md @@ -287,7 +287,7 @@ def get_next_sync_committee(state: BeaconState) -> SyncCommittee: """ indices = get_next_sync_committee_indices(state) pubkeys = [state.validators[index].pubkey for index in indices] - aggregate_pubkey = eth2_aggregate_pubkeys(pubkeys) + aggregate_pubkey = eth_aggregate_pubkeys(pubkeys) return SyncCommittee(pubkeys=pubkeys, aggregate_pubkey=aggregate_pubkey) ``` @@ -544,7 +544,7 @@ def process_sync_aggregate(state: BeaconState, sync_aggregate: SyncAggregate) -> previous_slot = max(state.slot, Slot(1)) - Slot(1) domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, compute_epoch_at_slot(previous_slot)) signing_root = compute_signing_root(get_block_root_at_slot(state, previous_slot), domain) - assert eth2_fast_aggregate_verify(participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature) + assert eth_fast_aggregate_verify(participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature) # Compute participant and proposer rewards total_active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT diff --git a/specs/altair/bls.md b/specs/altair/bls.md index 529236056..32f55572b 100644 --- a/specs/altair/bls.md +++ b/specs/altair/bls.md @@ -9,8 +9,8 @@ - [Introduction](#introduction) - [Constants](#constants) - [Extensions](#extensions) - - [`eth2_aggregate_pubkeys`](#eth2_aggregate_pubkeys) - - [`eth2_fast_aggregate_verify`](#eth2_fast_aggregate_verify) + - [`eth_aggregate_pubkeys`](#eth_aggregate_pubkeys) + - [`eth_fast_aggregate_verify`](#eth_fast_aggregate_verify) @@ -29,14 +29,14 @@ Knowledge of the [phase 0 specification](../phase0/beacon-chain.md) is assumed, ## Extensions -### `eth2_aggregate_pubkeys` +### `eth_aggregate_pubkeys` An additional function `AggregatePKs` is defined to extend the [IETF BLS signature draft standard v4](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04) spec referenced in the phase 0 document. ```python -def eth2_aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey: +def eth_aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey: """ Return the aggregate public key for the public keys in ``pubkeys``. @@ -52,10 +52,10 @@ def eth2_aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey: return result ``` -### `eth2_fast_aggregate_verify` +### `eth_fast_aggregate_verify` ```python -def eth2_fast_aggregate_verify(pubkeys: Sequence[BLSPubkey], message: Bytes32, signature: BLSSignature) -> bool: +def eth_fast_aggregate_verify(pubkeys: Sequence[BLSPubkey], message: Bytes32, signature: BLSSignature) -> bool: """ Wrapper to ``bls.FastAggregateVerify`` accepting the ``G2_POINT_AT_INFINITY`` signature when ``pubkeys`` is empty. """ From d8d068640011541ef99fe29cc6438c1c986168ac Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 4 Aug 2021 01:55:18 +0800 Subject: [PATCH 041/135] Add tests for the Altair BLS helpers --- tests/formats/bls/README.md | 2 + tests/formats/bls/eth2_aggregate_pubkeys.md | 19 +++ .../formats/bls/eth2_fast_aggregate_verify.md | 17 +++ tests/formats/bls/fast_aggregate_verify.md | 2 +- tests/generators/bls/main.py | 144 +++++++++++++++++- 5 files changed, 176 insertions(+), 8 deletions(-) create mode 100644 tests/formats/bls/eth2_aggregate_pubkeys.md create mode 100644 tests/formats/bls/eth2_fast_aggregate_verify.md diff --git a/tests/formats/bls/README.md b/tests/formats/bls/README.md index 65154ba1c..65018631a 100644 --- a/tests/formats/bls/README.md +++ b/tests/formats/bls/README.md @@ -7,6 +7,8 @@ The BLS test suite runner has the following handlers: - [`aggregate_verify`](./aggregate_verify.md) - [`aggregate`](./aggregate.md) +- [`eth2_aggregate_pubkeys`](./eth2_aggregate_pubkeys.md) +- [`eth2_fast_aggregate_verify`](./eth2_fast_aggregate_verify.md) - [`fast_aggregate_verify`](./fast_aggregate_verify.md) - [`sign`](./sign.md) - [`verify`](./verify.md) diff --git a/tests/formats/bls/eth2_aggregate_pubkeys.md b/tests/formats/bls/eth2_aggregate_pubkeys.md new file mode 100644 index 000000000..dd35b3166 --- /dev/null +++ b/tests/formats/bls/eth2_aggregate_pubkeys.md @@ -0,0 +1,19 @@ +# Test format: Ethereum-customized BLS pubkeys aggregation + +A BLS pubkeys aggregation combines a series of pubkeys into a single pubkey. + +## Test case format + +The test data is declared in a `data.yaml` file: + +```yaml +input: List[BLS Pubkey] -- list of input BLS pubkeys +output: BLS Pubkey -- expected output, single BLS pubkeys or empty. +``` + +- `BLS Pubkey` here is encoded as a string: hexadecimal encoding of 48 bytes (96 nibbles), prefixed with `0x`. +- No output value if the input is invalid. + +## Condition + +The `eth2_aggregate_pubkeys` handler should aggregate the signatures in the `input`, and the result should match the expected `output`. diff --git a/tests/formats/bls/eth2_fast_aggregate_verify.md b/tests/formats/bls/eth2_fast_aggregate_verify.md new file mode 100644 index 000000000..ddc1b5208 --- /dev/null +++ b/tests/formats/bls/eth2_fast_aggregate_verify.md @@ -0,0 +1,17 @@ +# Test format: Ethereum-customized BLS fast aggregate verify + +Verify the signature against the given pubkeys and one message. + +## Test case format + +The test data is declared in a `data.yaml` file: + +```yaml +input: + pubkeys: List[bytes48] -- the pubkey + message: bytes32 -- the message + signature: bytes96 -- the signature to verify against pubkeys and message +output: bool -- VALID or INVALID +``` + +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`. diff --git a/tests/formats/bls/fast_aggregate_verify.md b/tests/formats/bls/fast_aggregate_verify.md index 7e3899a15..3366cbb79 100644 --- a/tests/formats/bls/fast_aggregate_verify.md +++ b/tests/formats/bls/fast_aggregate_verify.md @@ -1,4 +1,4 @@ -# Test format: BLS sign message +# Test format: BLS fast aggregate verify Verify the signature against the given pubkeys and one message. diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py index 3ebaa1354..877324a2e 100644 --- a/tests/generators/bls/main.py +++ b/tests/generators/bls/main.py @@ -12,8 +12,10 @@ from eth_utils import ( import milagro_bls_binding as milagro_bls from eth2spec.utils import bls -from eth2spec.test.helpers.constants import PHASE0 +from eth2spec.test.helpers.constants import PHASE0, ALTAIR +from eth2spec.test.helpers.typing import SpecForkName from eth2spec.gen_helpers.gen_base import gen_runner, gen_typing +from eth2spec.altair import spec def to_bytes(i): @@ -51,6 +53,7 @@ PRIVKEYS = [ ] PUBKEYS = [bls.SkToPk(privkey) for privkey in PRIVKEYS] +ZERO_PUBKEY = b'\x00' * 48 Z1_PUBKEY = b'\xc0' + b'\x00' * 47 NO_SIGNATURE = b'\x00' * 96 Z2_SIGNATURE = b'\xc0' + b'\x00' * 95 @@ -355,7 +358,130 @@ def case05_aggregate_verify(): } +def case06_eth2_aggregate_pubkeys(): + aggregate_pubkey = spec.eth2_aggregate_pubkeys(PUBKEYS) + assert aggregate_pubkey == milagro_bls._AggregatePKs(PUBKEYS) + yield f'eth2_aggregate_pubkeys_some_pubkeys', { + 'input': [encode_hex(pubkey) for pubkey in PUBKEYS], + 'output': encode_hex(aggregate_pubkey), + } + + # Invalid pubkeys -- len(pubkeys) == 0 + expect_exception(spec.eth2_aggregate_pubkeys, []) + expect_exception(milagro_bls._AggregatePKs, []) + yield f'eth2_aggregate_pubkeys_', { + 'input': [], + 'output': None, + } + + # Invalid pubkeys -- [ZERO_PUBKEY] + expect_exception(spec.eth2_aggregate_pubkeys, [ZERO_PUBKEY]) + expect_exception(milagro_bls._AggregatePKs, [ZERO_PUBKEY]) + yield f'eth2_aggregate_pubkeys_all_zero_pubkey', { + 'input': [encode_hex(ZERO_PUBKEY)], + 'output': None, + } + + # TODO: TBD + # Valid to aggregate G1 point at infinity + # aggregate_pubkey = spec.eth2_aggregate_pubkeys([Z1_PUBKEY]) + # assert aggregate_pubkey == milagro_bls._AggregatePKs([Z1_PUBKEY]) == Z1_PUBKEY + # yield f'eth2_aggregate_pubkeys_infinity_pubkey', { + # 'input': [encode_hex(Z1_PUBKEY)], + # 'output': encode_hex(aggregate_pubkey), + # } + + +def case07_eth2_fast_aggregate_verify(): + """ + Similar to `case04_fast_aggregate_verify` except for the empty case + """ + for i, message in enumerate(MESSAGES): + privkeys = PRIVKEYS[:i + 1] + sigs = [bls.Sign(privkey, message) for privkey in privkeys] + aggregate_signature = bls.Aggregate(sigs) + pubkeys = [bls.SkToPk(privkey) for privkey in privkeys] + pubkeys_serial = [encode_hex(pubkey) for pubkey in pubkeys] + + # Valid signature + identifier = f'{pubkeys_serial}_{encode_hex(message)}' + assert spec.eth2_fast_aggregate_verify(pubkeys, message, aggregate_signature) + yield f'eth2_fast_aggregate_verify_valid_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { + 'input': { + 'pubkeys': pubkeys_serial, + 'message': encode_hex(message), + 'signature': encode_hex(aggregate_signature), + }, + 'output': True, + } + + # Invalid signature -- extra pubkey + pubkeys_extra = pubkeys + [bls.SkToPk(PRIVKEYS[-1])] + pubkeys_extra_serial = [encode_hex(pubkey) for pubkey in pubkeys_extra] + identifier = f'{pubkeys_extra_serial}_{encode_hex(message)}' + assert not spec.eth2_fast_aggregate_verify(pubkeys_extra, message, aggregate_signature) + yield f'eth_fast_aggregate_verify_extra_pubkey_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { + 'input': { + 'pubkeys': pubkeys_extra_serial, + 'message': encode_hex(message), + 'signature': encode_hex(aggregate_signature), + }, + 'output': False, + } + + # Invalid signature -- tampered with signature + tampered_signature = aggregate_signature[:-4] + b'\xff\xff\xff\xff' + identifier = f'{pubkeys_serial}_{encode_hex(message)}' + assert not spec.eth2_fast_aggregate_verify(pubkeys, message, tampered_signature) + yield f'eth2_fast_aggregate_verify_tampered_signature_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { + 'input': { + 'pubkeys': pubkeys_serial, + 'message': encode_hex(message), + 'signature': encode_hex(tampered_signature), + }, + 'output': False, + } + + # NOTE: Unlike `FastAggregateVerify`, len(pubkeys) == 0 and signature == Z1_SIGNATURE is VALID + assert spec.eth2_fast_aggregate_verify([], message, Z2_SIGNATURE) + yield f'eth2_fast_aggregate_verify_na_pubkeys_and_infinity_signature', { + 'input': { + 'pubkeys': [], + 'message': encode_hex(message), + 'signature': encode_hex(Z2_SIGNATURE), + }, + 'output': True, + } + + # Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == 0x00... + assert not spec.eth2_fast_aggregate_verify([], message, NO_SIGNATURE) + yield f'eth2_fast_aggregate_verify_na_pubkeys_and_na_signature', { + 'input': { + 'pubkeys': [], + 'message': encode_hex(message), + 'signature': encode_hex(NO_SIGNATURE), + }, + 'output': False, + } + + # Invalid pubkeys and signature -- pubkeys contains point at infinity + pubkeys = PUBKEYS.copy() + pubkeys_with_infinity = pubkeys + [Z1_PUBKEY] + signatures = [bls.Sign(privkey, SAMPLE_MESSAGE) for privkey in PRIVKEYS] + aggregate_signature = bls.Aggregate(signatures) + assert not spec.eth2_fast_aggregate_verify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature) + yield f'eth2_fast_aggregate_verify_infinity_pubkey', { + 'input': { + 'pubkeys': [encode_hex(pubkey) for pubkey in pubkeys_with_infinity], + 'message': encode_hex(SAMPLE_MESSAGE), + 'signature': encode_hex(aggregate_signature), + }, + 'output': False, + } + + def create_provider(handler_name: str, + fork_name: SpecForkName, test_case_fn: Callable[[], Iterable[Tuple[str, Dict[str, Any]]]]) -> gen_typing.TestProvider: def prepare_fn() -> None: @@ -368,7 +494,7 @@ def create_provider(handler_name: str, print(data) (case_name, case_content) = data yield gen_typing.TestCase( - fork_name=PHASE0, + fork_name=fork_name, preset_name='general', runner_name='bls', handler_name=handler_name, @@ -383,9 +509,13 @@ def create_provider(handler_name: str, if __name__ == "__main__": bls.use_py_ecc() # Py-ecc is chosen instead of Milagro, since the code is better understood to be correct. gen_runner.run_generator("bls", [ - create_provider('sign', case01_sign), - create_provider('verify', case02_verify), - create_provider('aggregate', case03_aggregate), - create_provider('fast_aggregate_verify', case04_fast_aggregate_verify), - create_provider('aggregate_verify', case05_aggregate_verify), + # PHASE0 + create_provider('sign', PHASE0, case01_sign), + create_provider('verify', PHASE0, case02_verify), + create_provider('aggregate', PHASE0, case03_aggregate), + create_provider('fast_aggregate_verify', PHASE0, case04_fast_aggregate_verify), + create_provider('aggregate_verify', PHASE0, case05_aggregate_verify), + # ALTAIR + create_provider('eth2_aggregate_pubkeys', ALTAIR, case06_eth2_aggregate_pubkeys), + create_provider('eth2_fast_aggregate_verify', ALTAIR, case07_eth2_fast_aggregate_verify), ]) From 424f8387473ef6b98ceac01704174472c917c194 Mon Sep 17 00:00:00 2001 From: Diederik Loerakker Date: Wed, 4 Aug 2021 12:44:42 +0200 Subject: [PATCH 042/135] Update specs/sharding/beacon-chain.md Co-authored-by: Danny Ryan --- specs/sharding/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index f5b212c6e..49ece701f 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -182,7 +182,7 @@ TODO: `WEIGHT_DENOMINATOR` needs to be adjusted, but this breaks a lot of Altair ## Configuration -Note: some preset variables may become run-time configurable for testnets, but default to a preset while the spec is unstable. +Note: Some preset variables may become run-time configurable for testnets, but default to a preset while the spec is unstable. E.g. `INITIAL_ACTIVE_SHARDS`, `MAX_SAMPLES_PER_BLOB` and `TARGET_SAMPLES_PER_BLOB`. ## Updated containers From 43a1617ffa94a8483f9cda7abec12dce88a2bef3 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 4 Aug 2021 21:12:19 +0800 Subject: [PATCH 043/135] Ensure that the given PKs are valid PKs + fix typos --- specs/altair/bls.md | 4 +++ specs/phase0/beacon-chain.md | 1 + tests/core/pyspec/eth2spec/utils/bls.py | 7 ++++ tests/generators/bls/main.py | 48 ++++++++++++++----------- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/specs/altair/bls.md b/specs/altair/bls.md index 529236056..e31da02ff 100644 --- a/specs/altair/bls.md +++ b/specs/altair/bls.md @@ -46,6 +46,10 @@ def eth2_aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey: Refer to the BLS signature draft standard for more information. """ assert len(pubkeys) > 0 + for pubkey in pubkeys: + # Ensure that the given inputs are valid pubkeys + assert bls.KeyValidate(pubkey) + result = copy(pubkeys[0]) for pubkey in pubkeys[1:]: result += pubkey diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 0169e2725..5ab9b75fd 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -647,6 +647,7 @@ The [IETF BLS signature draft standard v4](https://tools.ietf.org/html/draft-irt - `def Aggregate(signatures: Sequence[BLSSignature]) -> BLSSignature` - `def FastAggregateVerify(pubkeys: Sequence[BLSPubkey], message: Bytes, signature: BLSSignature) -> bool` - `def AggregateVerify(pubkeys: Sequence[BLSPubkey], messages: Sequence[Bytes], signature: BLSSignature) -> bool` +- `def KeyValidate(pubkey: BLSPubkey) -> bool` The above functions are accessed through the `bls` module, e.g. `bls.Verify`. diff --git a/tests/core/pyspec/eth2spec/utils/bls.py b/tests/core/pyspec/eth2spec/utils/bls.py index dc4daca49..5bda0232f 100644 --- a/tests/core/pyspec/eth2spec/utils/bls.py +++ b/tests/core/pyspec/eth2spec/utils/bls.py @@ -95,6 +95,13 @@ def signature_to_G2(signature): @only_with_bls(alt_return=STUB_PUBKEY) def AggregatePKs(pubkeys): + if bls == py_ecc_bls: + for pubkey in pubkeys: + assert bls.KeyValidate(pubkey) + elif bls == milagro_bls: + # milagro_bls._AggregatePKs checks KeyValidate internally + pass + return bls._AggregatePKs(list(pubkeys)) diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py index 877324a2e..467560e70 100644 --- a/tests/generators/bls/main.py +++ b/tests/generators/bls/main.py @@ -369,7 +369,7 @@ def case06_eth2_aggregate_pubkeys(): # Invalid pubkeys -- len(pubkeys) == 0 expect_exception(spec.eth2_aggregate_pubkeys, []) expect_exception(milagro_bls._AggregatePKs, []) - yield f'eth2_aggregate_pubkeys_', { + yield f'eth2_aggregate_pubkeys_empty_list', { 'input': [], 'output': None, } @@ -377,19 +377,27 @@ def case06_eth2_aggregate_pubkeys(): # Invalid pubkeys -- [ZERO_PUBKEY] expect_exception(spec.eth2_aggregate_pubkeys, [ZERO_PUBKEY]) expect_exception(milagro_bls._AggregatePKs, [ZERO_PUBKEY]) - yield f'eth2_aggregate_pubkeys_all_zero_pubkey', { + yield f'eth2_aggregate_pubkeys_na_pubkey', { 'input': [encode_hex(ZERO_PUBKEY)], 'output': None, } - # TODO: TBD - # Valid to aggregate G1 point at infinity - # aggregate_pubkey = spec.eth2_aggregate_pubkeys([Z1_PUBKEY]) - # assert aggregate_pubkey == milagro_bls._AggregatePKs([Z1_PUBKEY]) == Z1_PUBKEY - # yield f'eth2_aggregate_pubkeys_infinity_pubkey', { - # 'input': [encode_hex(Z1_PUBKEY)], - # 'output': encode_hex(aggregate_pubkey), - # } + # Invalid pubkeys -- G1 point at infinity + expect_exception(spec.eth2_aggregate_pubkeys, [Z1_PUBKEY]) + expect_exception(milagro_bls._AggregatePKs, [Z1_PUBKEY]) + yield f'eth2_aggregate_pubkeys_infinity_pubkey', { + 'input': [encode_hex(Z1_PUBKEY)], + 'output': None, + } + + # Invalid pubkeys -- b'\x40\x00\x00\x00....\x00' pubkey + x40_pubkey = b'\x40' + b'\00' * 47 + expect_exception(spec.eth2_aggregate_pubkeys, [x40_pubkey]) + expect_exception(milagro_bls._AggregatePKs, [x40_pubkey]) + yield f'eth2_aggregate_pubkeys_x40_pubkey', { + 'input': [encode_hex(x40_pubkey)], + 'output': None, + } def case07_eth2_fast_aggregate_verify(): @@ -420,7 +428,7 @@ def case07_eth2_fast_aggregate_verify(): pubkeys_extra_serial = [encode_hex(pubkey) for pubkey in pubkeys_extra] identifier = f'{pubkeys_extra_serial}_{encode_hex(message)}' assert not spec.eth2_fast_aggregate_verify(pubkeys_extra, message, aggregate_signature) - yield f'eth_fast_aggregate_verify_extra_pubkey_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { + yield f'eth2_fast_aggregate_verify_extra_pubkey_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { 'input': { 'pubkeys': pubkeys_extra_serial, 'message': encode_hex(message), @@ -480,8 +488,8 @@ def case07_eth2_fast_aggregate_verify(): } -def create_provider(handler_name: str, - fork_name: SpecForkName, +def create_provider(fork_name: SpecForkName, + handler_name: str, test_case_fn: Callable[[], Iterable[Tuple[str, Dict[str, Any]]]]) -> gen_typing.TestProvider: def prepare_fn() -> None: @@ -510,12 +518,12 @@ if __name__ == "__main__": bls.use_py_ecc() # Py-ecc is chosen instead of Milagro, since the code is better understood to be correct. gen_runner.run_generator("bls", [ # PHASE0 - create_provider('sign', PHASE0, case01_sign), - create_provider('verify', PHASE0, case02_verify), - create_provider('aggregate', PHASE0, case03_aggregate), - create_provider('fast_aggregate_verify', PHASE0, case04_fast_aggregate_verify), - create_provider('aggregate_verify', PHASE0, case05_aggregate_verify), + create_provider(PHASE0, 'sign', case01_sign), + create_provider(PHASE0, 'verify', case02_verify), + create_provider(PHASE0, 'aggregate', case03_aggregate), + create_provider(PHASE0, 'fast_aggregate_verify', case04_fast_aggregate_verify), + create_provider(PHASE0, 'aggregate_verify', case05_aggregate_verify), # ALTAIR - create_provider('eth2_aggregate_pubkeys', ALTAIR, case06_eth2_aggregate_pubkeys), - create_provider('eth2_fast_aggregate_verify', ALTAIR, case07_eth2_fast_aggregate_verify), + create_provider(ALTAIR, 'eth2_aggregate_pubkeys', case06_eth2_aggregate_pubkeys), + create_provider(ALTAIR, 'eth2_fast_aggregate_verify', case07_eth2_fast_aggregate_verify), ]) From fc3e651817e1775deae5f8252d546974ab24bd87 Mon Sep 17 00:00:00 2001 From: protolambda Date: Wed, 4 Aug 2021 23:25:25 +0200 Subject: [PATCH 044/135] samples -> samples_length --- specs/sharding/beacon-chain.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 49ece701f..bbb4ae930 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -437,13 +437,13 @@ def compute_previous_slot(slot: Slot) -> Slot: #### `compute_updated_sample_price` ```python -def compute_updated_sample_price(prev_price: Gwei, samples: uint64, active_shards: uint64) -> Gwei: +def compute_updated_sample_price(prev_price: Gwei, samples_length: uint64, active_shards: uint64) -> Gwei: adjustment_quotient = active_shards * SLOTS_PER_EPOCH * SAMPLE_PRICE_ADJUSTMENT_COEFFICIENT - if samples > TARGET_SAMPLES_PER_BLOB: - delta = max(1, prev_price * (samples - TARGET_SAMPLES_PER_BLOB) // TARGET_SAMPLES_PER_BLOB // adjustment_quotient) + if samples_length > TARGET_SAMPLES_PER_BLOB: + delta = max(1, prev_price * (samples_length - TARGET_SAMPLES_PER_BLOB) // TARGET_SAMPLES_PER_BLOB // adjustment_quotient) return min(prev_price + delta, MAX_SAMPLE_PRICE) else: - delta = max(1, prev_price * (TARGET_SAMPLES_PER_BLOB - samples) // TARGET_SAMPLES_PER_BLOB // adjustment_quotient) + delta = max(1, prev_price * (TARGET_SAMPLES_PER_BLOB - samples_length) // TARGET_SAMPLES_PER_BLOB // adjustment_quotient) return max(prev_price, MIN_SAMPLE_PRICE + delta) - delta ``` From 2ff143c719eb8f6a310d9cb0a851ea4bfc28d001 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 4 Aug 2021 16:28:42 -0700 Subject: [PATCH 045/135] Add test for exited validators during inactivity leak --- .../test_process_inactivity_updates.py | 55 +++++++++++++++++++ .../eth2spec/test/helpers/voluntary_exits.py | 19 +++++++ 2 files changed, 74 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py index f7d2fa9c8..5a334f28a 100644 --- a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py +++ b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py @@ -3,10 +3,15 @@ from random import Random from eth2spec.test.context import spec_state_test, with_altair_and_later from eth2spec.test.helpers.inactivity_scores import randomize_inactivity_scores, zero_inactivity_scores from eth2spec.test.helpers.state import ( + next_epoch, next_epoch_via_block, set_full_participation, set_empty_participation, ) +from eth2spec.test.helpers.voluntary_exits import ( + exit_validators, + get_exited_validators +) from eth2spec.test.helpers.epoch_processing import ( run_epoch_processing_with ) @@ -266,3 +271,53 @@ def test_some_slashed_full_random_leaking(spec, state): # Check still in leak assert spec.is_in_inactivity_leak(state) + + +@with_altair_and_later +@spec_state_test +@leaking() +def test_some_exited_full_random_leaking(spec, state): + rng = Random(1102233) + + exit_count = 3 + + # randomize ahead of time to check exited validators do not have + # mutations applied to their inactivity scores + randomize_inactivity_scores(spec, state, rng=rng) + + assert not get_exited_validators(spec, state) + exited_indices = exit_validators(spec, state, exit_count, rng=rng) + assert not get_exited_validators(spec, state) + + # advance the state to effect the exits + target_epoch = max(state.validators[index].exit_epoch for index in exited_indices) + # validators that have exited in the previous epoch or earlier will not + # have their inactivity scores modified, the test advances the state past this point + # to confirm this invariant: + previous_epoch = spec.get_previous_epoch(state) + for _ in range(target_epoch - previous_epoch): + next_epoch(spec, state) + assert len(get_exited_validators(spec, state)) == exit_count + + previous_scores = state.inactivity_scores.copy() + + yield from run_inactivity_scores_test( + spec, state, + randomize_previous_epoch_participation, rng=rng, + ) + + # ensure exited validators have their score "frozen" at exit + # but otherwise there was a change + some_changed = False + for index in range(len(state.validators)): + if index in exited_indices: + assert previous_scores[index] == state.inactivity_scores[index] + else: + previous_score = previous_scores[index] + current_score = state.inactivity_scores[index] + if previous_score != current_score: + some_changed = True + assert some_changed + + # Check still in leak + assert spec.is_in_inactivity_leak(state) diff --git a/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py b/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py index 28232cc23..73d4598b3 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py +++ b/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py @@ -1,3 +1,4 @@ +from random import Random from eth2spec.utils import bls from eth2spec.test.helpers.keys import privkeys @@ -23,3 +24,21 @@ def sign_voluntary_exit(spec, state, voluntary_exit, privkey): message=voluntary_exit, signature=bls.Sign(privkey, signing_root) ) + + +# +# Helpers for applying effects of a voluntary exit +# +def get_exited_validators(spec, state): + current_epoch = spec.get_current_epoch(state) + return [index for (index, validator) in enumerate(state.validators) if validator.exit_epoch <= current_epoch] + + +def exit_validators(spec, state, validator_count, rng=None): + if rng is None: + rng = Random(1337) + + indices = rng.sample(range(len(state.validators)), validator_count) + for index in indices: + spec.initiate_validator_exit(state, index) + return indices From a8383be878dc69540903b5a9eee8a555b79880e9 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 5 Aug 2021 11:12:36 +0800 Subject: [PATCH 046/135] Apply suggestions from code review Co-authored-by: Alex Stokes --- tests/formats/bls/eth2_aggregate_pubkeys.md | 4 ++-- tests/generators/bls/main.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/formats/bls/eth2_aggregate_pubkeys.md b/tests/formats/bls/eth2_aggregate_pubkeys.md index dd35b3166..b07d9bd06 100644 --- a/tests/formats/bls/eth2_aggregate_pubkeys.md +++ b/tests/formats/bls/eth2_aggregate_pubkeys.md @@ -1,6 +1,6 @@ -# Test format: Ethereum-customized BLS pubkeys aggregation +# Test format: Ethereum-customized BLS pubkey aggregation -A BLS pubkeys aggregation combines a series of pubkeys into a single pubkey. +A BLS pubkey aggregation combines a series of pubkeys into a single pubkey. ## Test case format diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py index 467560e70..42754a581 100644 --- a/tests/generators/bls/main.py +++ b/tests/generators/bls/main.py @@ -450,7 +450,7 @@ def case07_eth2_fast_aggregate_verify(): 'output': False, } - # NOTE: Unlike `FastAggregateVerify`, len(pubkeys) == 0 and signature == Z1_SIGNATURE is VALID + # NOTE: Unlike `FastAggregateVerify`, len(pubkeys) == 0 and signature == Z2_SIGNATURE is VALID assert spec.eth2_fast_aggregate_verify([], message, Z2_SIGNATURE) yield f'eth2_fast_aggregate_verify_na_pubkeys_and_infinity_signature', { 'input': { From 3b86bd340f493dcfdbc8d3b9c9d93f630e4736ac Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 5 Aug 2021 11:20:49 +0800 Subject: [PATCH 047/135] Rename eth2_* to eth_* --- tests/formats/bls/README.md | 4 +- ...te_pubkeys.md => eth_aggregate_pubkeys.md} | 2 +- ...verify.md => eth_fast_aggregate_verify.md} | 0 tests/generators/bls/main.py | 52 +++++++++---------- 4 files changed, 29 insertions(+), 29 deletions(-) rename tests/formats/bls/{eth2_aggregate_pubkeys.md => eth_aggregate_pubkeys.md} (78%) rename tests/formats/bls/{eth2_fast_aggregate_verify.md => eth_fast_aggregate_verify.md} (100%) diff --git a/tests/formats/bls/README.md b/tests/formats/bls/README.md index 65018631a..77a9654a8 100644 --- a/tests/formats/bls/README.md +++ b/tests/formats/bls/README.md @@ -7,8 +7,8 @@ The BLS test suite runner has the following handlers: - [`aggregate_verify`](./aggregate_verify.md) - [`aggregate`](./aggregate.md) -- [`eth2_aggregate_pubkeys`](./eth2_aggregate_pubkeys.md) -- [`eth2_fast_aggregate_verify`](./eth2_fast_aggregate_verify.md) +- [`eth_aggregate_pubkeys`](./eth_aggregate_pubkeys.md) +- [`eth_fast_aggregate_verify`](./eth_fast_aggregate_verify.md) - [`fast_aggregate_verify`](./fast_aggregate_verify.md) - [`sign`](./sign.md) - [`verify`](./verify.md) diff --git a/tests/formats/bls/eth2_aggregate_pubkeys.md b/tests/formats/bls/eth_aggregate_pubkeys.md similarity index 78% rename from tests/formats/bls/eth2_aggregate_pubkeys.md rename to tests/formats/bls/eth_aggregate_pubkeys.md index b07d9bd06..86d0e3cd0 100644 --- a/tests/formats/bls/eth2_aggregate_pubkeys.md +++ b/tests/formats/bls/eth_aggregate_pubkeys.md @@ -16,4 +16,4 @@ output: BLS Pubkey -- expected output, single BLS pubkeys or empty. ## Condition -The `eth2_aggregate_pubkeys` handler should aggregate the signatures in the `input`, and the result should match the expected `output`. +The `eth_aggregate_pubkeys` handler should aggregate the signatures in the `input`, and the result should match the expected `output`. diff --git a/tests/formats/bls/eth2_fast_aggregate_verify.md b/tests/formats/bls/eth_fast_aggregate_verify.md similarity index 100% rename from tests/formats/bls/eth2_fast_aggregate_verify.md rename to tests/formats/bls/eth_fast_aggregate_verify.md diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py index 42754a581..0d6f4942e 100644 --- a/tests/generators/bls/main.py +++ b/tests/generators/bls/main.py @@ -358,49 +358,49 @@ def case05_aggregate_verify(): } -def case06_eth2_aggregate_pubkeys(): - aggregate_pubkey = spec.eth2_aggregate_pubkeys(PUBKEYS) +def case06_eth_aggregate_pubkeys(): + aggregate_pubkey = spec.eth_aggregate_pubkeys(PUBKEYS) assert aggregate_pubkey == milagro_bls._AggregatePKs(PUBKEYS) - yield f'eth2_aggregate_pubkeys_some_pubkeys', { + yield f'eth_aggregate_pubkeys_some_pubkeys', { 'input': [encode_hex(pubkey) for pubkey in PUBKEYS], 'output': encode_hex(aggregate_pubkey), } # Invalid pubkeys -- len(pubkeys) == 0 - expect_exception(spec.eth2_aggregate_pubkeys, []) + expect_exception(spec.eth_aggregate_pubkeys, []) expect_exception(milagro_bls._AggregatePKs, []) - yield f'eth2_aggregate_pubkeys_empty_list', { + yield f'eth_aggregate_pubkeys_empty_list', { 'input': [], 'output': None, } # Invalid pubkeys -- [ZERO_PUBKEY] - expect_exception(spec.eth2_aggregate_pubkeys, [ZERO_PUBKEY]) + expect_exception(spec.eth_aggregate_pubkeys, [ZERO_PUBKEY]) expect_exception(milagro_bls._AggregatePKs, [ZERO_PUBKEY]) - yield f'eth2_aggregate_pubkeys_na_pubkey', { + yield f'eth_aggregate_pubkeys_na_pubkey', { 'input': [encode_hex(ZERO_PUBKEY)], 'output': None, } # Invalid pubkeys -- G1 point at infinity - expect_exception(spec.eth2_aggregate_pubkeys, [Z1_PUBKEY]) + expect_exception(spec.eth_aggregate_pubkeys, [Z1_PUBKEY]) expect_exception(milagro_bls._AggregatePKs, [Z1_PUBKEY]) - yield f'eth2_aggregate_pubkeys_infinity_pubkey', { + yield f'eth_aggregate_pubkeys_infinity_pubkey', { 'input': [encode_hex(Z1_PUBKEY)], 'output': None, } # Invalid pubkeys -- b'\x40\x00\x00\x00....\x00' pubkey x40_pubkey = b'\x40' + b'\00' * 47 - expect_exception(spec.eth2_aggregate_pubkeys, [x40_pubkey]) + expect_exception(spec.eth_aggregate_pubkeys, [x40_pubkey]) expect_exception(milagro_bls._AggregatePKs, [x40_pubkey]) - yield f'eth2_aggregate_pubkeys_x40_pubkey', { + yield f'eth_aggregate_pubkeys_x40_pubkey', { 'input': [encode_hex(x40_pubkey)], 'output': None, } -def case07_eth2_fast_aggregate_verify(): +def case07_eth_fast_aggregate_verify(): """ Similar to `case04_fast_aggregate_verify` except for the empty case """ @@ -413,8 +413,8 @@ def case07_eth2_fast_aggregate_verify(): # Valid signature identifier = f'{pubkeys_serial}_{encode_hex(message)}' - assert spec.eth2_fast_aggregate_verify(pubkeys, message, aggregate_signature) - yield f'eth2_fast_aggregate_verify_valid_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { + assert spec.eth_fast_aggregate_verify(pubkeys, message, aggregate_signature) + yield f'eth_fast_aggregate_verify_valid_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { 'input': { 'pubkeys': pubkeys_serial, 'message': encode_hex(message), @@ -427,8 +427,8 @@ def case07_eth2_fast_aggregate_verify(): pubkeys_extra = pubkeys + [bls.SkToPk(PRIVKEYS[-1])] pubkeys_extra_serial = [encode_hex(pubkey) for pubkey in pubkeys_extra] identifier = f'{pubkeys_extra_serial}_{encode_hex(message)}' - assert not spec.eth2_fast_aggregate_verify(pubkeys_extra, message, aggregate_signature) - yield f'eth2_fast_aggregate_verify_extra_pubkey_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { + assert not spec.eth_fast_aggregate_verify(pubkeys_extra, message, aggregate_signature) + yield f'eth_fast_aggregate_verify_extra_pubkey_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { 'input': { 'pubkeys': pubkeys_extra_serial, 'message': encode_hex(message), @@ -440,8 +440,8 @@ def case07_eth2_fast_aggregate_verify(): # Invalid signature -- tampered with signature tampered_signature = aggregate_signature[:-4] + b'\xff\xff\xff\xff' identifier = f'{pubkeys_serial}_{encode_hex(message)}' - assert not spec.eth2_fast_aggregate_verify(pubkeys, message, tampered_signature) - yield f'eth2_fast_aggregate_verify_tampered_signature_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { + assert not spec.eth_fast_aggregate_verify(pubkeys, message, tampered_signature) + yield f'eth_fast_aggregate_verify_tampered_signature_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { 'input': { 'pubkeys': pubkeys_serial, 'message': encode_hex(message), @@ -451,8 +451,8 @@ def case07_eth2_fast_aggregate_verify(): } # NOTE: Unlike `FastAggregateVerify`, len(pubkeys) == 0 and signature == Z2_SIGNATURE is VALID - assert spec.eth2_fast_aggregate_verify([], message, Z2_SIGNATURE) - yield f'eth2_fast_aggregate_verify_na_pubkeys_and_infinity_signature', { + assert spec.eth_fast_aggregate_verify([], message, Z2_SIGNATURE) + yield f'eth_fast_aggregate_verify_na_pubkeys_and_infinity_signature', { 'input': { 'pubkeys': [], 'message': encode_hex(message), @@ -462,8 +462,8 @@ def case07_eth2_fast_aggregate_verify(): } # Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == 0x00... - assert not spec.eth2_fast_aggregate_verify([], message, NO_SIGNATURE) - yield f'eth2_fast_aggregate_verify_na_pubkeys_and_na_signature', { + assert not spec.eth_fast_aggregate_verify([], message, NO_SIGNATURE) + yield f'eth_fast_aggregate_verify_na_pubkeys_and_na_signature', { 'input': { 'pubkeys': [], 'message': encode_hex(message), @@ -477,8 +477,8 @@ def case07_eth2_fast_aggregate_verify(): pubkeys_with_infinity = pubkeys + [Z1_PUBKEY] signatures = [bls.Sign(privkey, SAMPLE_MESSAGE) for privkey in PRIVKEYS] aggregate_signature = bls.Aggregate(signatures) - assert not spec.eth2_fast_aggregate_verify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature) - yield f'eth2_fast_aggregate_verify_infinity_pubkey', { + assert not spec.eth_fast_aggregate_verify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature) + yield f'eth_fast_aggregate_verify_infinity_pubkey', { 'input': { 'pubkeys': [encode_hex(pubkey) for pubkey in pubkeys_with_infinity], 'message': encode_hex(SAMPLE_MESSAGE), @@ -524,6 +524,6 @@ if __name__ == "__main__": create_provider(PHASE0, 'fast_aggregate_verify', case04_fast_aggregate_verify), create_provider(PHASE0, 'aggregate_verify', case05_aggregate_verify), # ALTAIR - create_provider(ALTAIR, 'eth2_aggregate_pubkeys', case06_eth2_aggregate_pubkeys), - create_provider(ALTAIR, 'eth2_fast_aggregate_verify', case07_eth2_fast_aggregate_verify), + create_provider(ALTAIR, 'eth_aggregate_pubkeys', case06_eth_aggregate_pubkeys), + create_provider(ALTAIR, 'eth_fast_aggregate_verify', case07_eth_fast_aggregate_verify), ]) From 93af122b2db71b9d33f6e5fc963273a6adb8c211 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 5 Aug 2021 12:09:30 +0800 Subject: [PATCH 048/135] PR feedback from @ralexstokes and add single pubkey aggregate tests --- specs/altair/bls.md | 5 +- .../test_process_attester_slashing.py | 8 +- tests/core/pyspec/eth2spec/utils/bls.py | 8 +- tests/generators/bls/main.py | 90 +++++++++++-------- 4 files changed, 61 insertions(+), 50 deletions(-) diff --git a/specs/altair/bls.md b/specs/altair/bls.md index a09c6b3e3..06b0313a9 100644 --- a/specs/altair/bls.md +++ b/specs/altair/bls.md @@ -46,9 +46,8 @@ def eth_aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey: Refer to the BLS signature draft standard for more information. """ assert len(pubkeys) > 0 - for pubkey in pubkeys: - # Ensure that the given inputs are valid pubkeys - assert bls.KeyValidate(pubkey) + # Ensure that the given inputs are valid pubkeys + assert all(bls.KeyValidate(pubkey) for pubkey in pubkeys) result = copy(pubkeys[0]) for pubkey in pubkeys[1:]: diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py index b620a7342..13d64e03b 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py @@ -306,7 +306,7 @@ def test_att1_empty_indices(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True) attester_slashing.attestation_1.attesting_indices = [] - attester_slashing.attestation_1.signature = spec.bls.Z2_SIGNATURE + attester_slashing.attestation_1.signature = spec.bls.G2_POINT_AT_INFINITY yield from run_attester_slashing_processing(spec, state, attester_slashing, False) @@ -318,7 +318,7 @@ def test_att2_empty_indices(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=False) attester_slashing.attestation_2.attesting_indices = [] - attester_slashing.attestation_2.signature = spec.bls.Z2_SIGNATURE + attester_slashing.attestation_2.signature = spec.bls.G2_POINT_AT_INFINITY yield from run_attester_slashing_processing(spec, state, attester_slashing, False) @@ -330,10 +330,10 @@ def test_all_empty_indices(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=False) attester_slashing.attestation_1.attesting_indices = [] - attester_slashing.attestation_1.signature = spec.bls.Z2_SIGNATURE + attester_slashing.attestation_1.signature = spec.bls.G2_POINT_AT_INFINITY attester_slashing.attestation_2.attesting_indices = [] - attester_slashing.attestation_2.signature = spec.bls.Z2_SIGNATURE + attester_slashing.attestation_2.signature = spec.bls.G2_POINT_AT_INFINITY yield from run_attester_slashing_processing(spec, state, attester_slashing, False) diff --git a/tests/core/pyspec/eth2spec/utils/bls.py b/tests/core/pyspec/eth2spec/utils/bls.py index 5bda0232f..9211e0ff0 100644 --- a/tests/core/pyspec/eth2spec/utils/bls.py +++ b/tests/core/pyspec/eth2spec/utils/bls.py @@ -10,9 +10,8 @@ bls = py_ecc_bls STUB_SIGNATURE = b'\x11' * 96 STUB_PUBKEY = b'\x22' * 48 -Z1_PUBKEY = b'\xc0' + b'\x00' * 47 -Z2_SIGNATURE = b'\xc0' + b'\x00' * 95 -STUB_COORDINATES = _signature_to_G2(Z2_SIGNATURE) +G2_POINT_AT_INFINITY = b'\xc0' + b'\x00' * 95 +STUB_COORDINATES = _signature_to_G2(G2_POINT_AT_INFINITY) def use_milagro(): @@ -96,8 +95,7 @@ def signature_to_G2(signature): @only_with_bls(alt_return=STUB_PUBKEY) def AggregatePKs(pubkeys): if bls == py_ecc_bls: - for pubkey in pubkeys: - assert bls.KeyValidate(pubkey) + assert all(bls.KeyValidate(pubkey) for pubkey in pubkeys) elif bls == milagro_bls: # milagro_bls._AggregatePKs checks KeyValidate internally pass diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py index 0d6f4942e..75468b162 100644 --- a/tests/generators/bls/main.py +++ b/tests/generators/bls/main.py @@ -54,9 +54,11 @@ PRIVKEYS = [ PUBKEYS = [bls.SkToPk(privkey) for privkey in PRIVKEYS] ZERO_PUBKEY = b'\x00' * 48 -Z1_PUBKEY = b'\xc0' + b'\x00' * 47 -NO_SIGNATURE = b'\x00' * 96 -Z2_SIGNATURE = b'\xc0' + b'\x00' * 95 +G1_POINT_AT_INFINITY = b'\xc0' + b'\x00' * 47 + +ZERO_SIGNATURE = b'\x00' * 96 +G2_POINT_AT_INFINITY = b'\xc0' + b'\x00' * 95 + ZERO_PRIVKEY = 0 ZERO_PRIVKEY_BYTES = b'\x00' * 32 @@ -149,13 +151,13 @@ def case02_verify(): } # Invalid pubkey and signature with the point at infinity - assert not bls.Verify(Z1_PUBKEY, SAMPLE_MESSAGE, Z2_SIGNATURE) - assert not milagro_bls.Verify(Z1_PUBKEY, SAMPLE_MESSAGE, Z2_SIGNATURE) + assert not bls.Verify(G1_POINT_AT_INFINITY, SAMPLE_MESSAGE, G2_POINT_AT_INFINITY) + assert not milagro_bls.Verify(G1_POINT_AT_INFINITY, SAMPLE_MESSAGE, G2_POINT_AT_INFINITY) yield f'verify_infinity_pubkey_and_infinity_signature', { 'input': { - 'pubkey': encode_hex(Z1_PUBKEY), + 'pubkey': encode_hex(G1_POINT_AT_INFINITY), 'message': encode_hex(SAMPLE_MESSAGE), - 'signature': encode_hex(Z2_SIGNATURE), + 'signature': encode_hex(G2_POINT_AT_INFINITY), }, 'output': False, } @@ -181,10 +183,10 @@ def case03_aggregate(): } # Valid to aggregate G2 point at infinity - aggregate_sig = bls.Aggregate([Z2_SIGNATURE]) - assert aggregate_sig == milagro_bls.Aggregate([Z2_SIGNATURE]) == Z2_SIGNATURE + aggregate_sig = bls.Aggregate([G2_POINT_AT_INFINITY]) + assert aggregate_sig == milagro_bls.Aggregate([G2_POINT_AT_INFINITY]) == G2_POINT_AT_INFINITY yield f'aggregate_infinity_signature', { - 'input': [encode_hex(Z2_SIGNATURE)], + 'input': [encode_hex(G2_POINT_AT_INFINITY)], 'output': encode_hex(aggregate_sig), } @@ -240,32 +242,32 @@ def case04_fast_aggregate_verify(): } # Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == Z1_SIGNATURE - assert not bls.FastAggregateVerify([], message, Z2_SIGNATURE) - assert not milagro_bls.FastAggregateVerify([], message, Z2_SIGNATURE) + assert not bls.FastAggregateVerify([], message, G2_POINT_AT_INFINITY) + assert not milagro_bls.FastAggregateVerify([], message, G2_POINT_AT_INFINITY) yield f'fast_aggregate_verify_na_pubkeys_and_infinity_signature', { 'input': { 'pubkeys': [], 'message': encode_hex(message), - 'signature': encode_hex(Z2_SIGNATURE), + 'signature': encode_hex(G2_POINT_AT_INFINITY), }, 'output': False, } # Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == 0x00... - assert not bls.FastAggregateVerify([], message, NO_SIGNATURE) - assert not milagro_bls.FastAggregateVerify([], message, NO_SIGNATURE) - yield f'fast_aggregate_verify_na_pubkeys_and_na_signature', { + assert not bls.FastAggregateVerify([], message, ZERO_SIGNATURE) + assert not milagro_bls.FastAggregateVerify([], message, ZERO_SIGNATURE) + yield f'fast_aggregate_verify_na_pubkeys_and_zero_signature', { 'input': { 'pubkeys': [], 'message': encode_hex(message), - 'signature': encode_hex(NO_SIGNATURE), + 'signature': encode_hex(ZERO_SIGNATURE), }, 'output': False, } # Invalid pubkeys and signature -- pubkeys contains point at infinity pubkeys = PUBKEYS.copy() - pubkeys_with_infinity = pubkeys + [Z1_PUBKEY] + pubkeys_with_infinity = pubkeys + [G1_POINT_AT_INFINITY] signatures = [bls.Sign(privkey, SAMPLE_MESSAGE) for privkey in PRIVKEYS] aggregate_signature = bls.Aggregate(signatures) assert not bls.FastAggregateVerify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature) @@ -320,31 +322,31 @@ def case05_aggregate_verify(): } # Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == Z1_SIGNATURE - assert not bls.AggregateVerify([], [], Z2_SIGNATURE) - assert not milagro_bls.AggregateVerify([], [], Z2_SIGNATURE) + assert not bls.AggregateVerify([], [], G2_POINT_AT_INFINITY) + assert not milagro_bls.AggregateVerify([], [], G2_POINT_AT_INFINITY) yield f'aggregate_verify_na_pubkeys_and_infinity_signature', { 'input': { 'pubkeys': [], 'messages': [], - 'signature': encode_hex(Z2_SIGNATURE), + 'signature': encode_hex(G2_POINT_AT_INFINITY), }, 'output': False, } # Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == 0x00... - assert not bls.AggregateVerify([], [], NO_SIGNATURE) - assert not milagro_bls.AggregateVerify([], [], NO_SIGNATURE) - yield f'aggregate_verify_na_pubkeys_and_na_signature', { + assert not bls.AggregateVerify([], [], ZERO_SIGNATURE) + assert not milagro_bls.AggregateVerify([], [], ZERO_SIGNATURE) + yield f'aggregate_verify_na_pubkeys_and_zero_signature', { 'input': { 'pubkeys': [], 'messages': [], - 'signature': encode_hex(NO_SIGNATURE), + 'signature': encode_hex(ZERO_SIGNATURE), }, 'output': False, } # Invalid pubkeys and signature -- pubkeys contains point at infinity - pubkeys_with_infinity = pubkeys + [Z1_PUBKEY] + pubkeys_with_infinity = pubkeys + [G1_POINT_AT_INFINITY] messages_with_sample = messages + [SAMPLE_MESSAGE] assert not bls.AggregateVerify(pubkeys_with_infinity, messages_with_sample, aggregate_signature) assert not milagro_bls.AggregateVerify(pubkeys_with_infinity, messages_with_sample, aggregate_signature) @@ -359,9 +361,21 @@ def case05_aggregate_verify(): def case06_eth_aggregate_pubkeys(): + for pubkey in PUBKEYS: + encoded_pubkey = encode_hex(pubkey) + aggregate_pubkey = spec.eth_aggregate_pubkeys([pubkey]) + # Should be unchanged + assert aggregate_pubkey == milagro_bls._AggregatePKs([pubkey]) == pubkey + # Valid pubkey + yield f'eth_aggregate_pubkeys_valid_{(hash(bytes(encoded_pubkey, "utf-8"))[:8]).hex()}', { + 'input': [encode_hex(pubkey)], + 'output': encode_hex(aggregate_pubkey), + } + + # Valid pubkeys aggregate_pubkey = spec.eth_aggregate_pubkeys(PUBKEYS) assert aggregate_pubkey == milagro_bls._AggregatePKs(PUBKEYS) - yield f'eth_aggregate_pubkeys_some_pubkeys', { + yield f'eth_aggregate_pubkeys_valid_pubkeys', { 'input': [encode_hex(pubkey) for pubkey in PUBKEYS], 'output': encode_hex(aggregate_pubkey), } @@ -377,16 +391,16 @@ def case06_eth_aggregate_pubkeys(): # Invalid pubkeys -- [ZERO_PUBKEY] expect_exception(spec.eth_aggregate_pubkeys, [ZERO_PUBKEY]) expect_exception(milagro_bls._AggregatePKs, [ZERO_PUBKEY]) - yield f'eth_aggregate_pubkeys_na_pubkey', { + yield f'eth_aggregate_pubkeys_zero_pubkey', { 'input': [encode_hex(ZERO_PUBKEY)], 'output': None, } # Invalid pubkeys -- G1 point at infinity - expect_exception(spec.eth_aggregate_pubkeys, [Z1_PUBKEY]) - expect_exception(milagro_bls._AggregatePKs, [Z1_PUBKEY]) + expect_exception(spec.eth_aggregate_pubkeys, [G1_POINT_AT_INFINITY]) + expect_exception(milagro_bls._AggregatePKs, [G1_POINT_AT_INFINITY]) yield f'eth_aggregate_pubkeys_infinity_pubkey', { - 'input': [encode_hex(Z1_PUBKEY)], + 'input': [encode_hex(G1_POINT_AT_INFINITY)], 'output': None, } @@ -450,31 +464,31 @@ def case07_eth_fast_aggregate_verify(): 'output': False, } - # NOTE: Unlike `FastAggregateVerify`, len(pubkeys) == 0 and signature == Z2_SIGNATURE is VALID - assert spec.eth_fast_aggregate_verify([], message, Z2_SIGNATURE) + # NOTE: Unlike `FastAggregateVerify`, len(pubkeys) == 0 and signature == G2_POINT_AT_INFINITY is VALID + assert spec.eth_fast_aggregate_verify([], message, G2_POINT_AT_INFINITY) yield f'eth_fast_aggregate_verify_na_pubkeys_and_infinity_signature', { 'input': { 'pubkeys': [], 'message': encode_hex(message), - 'signature': encode_hex(Z2_SIGNATURE), + 'signature': encode_hex(G2_POINT_AT_INFINITY), }, 'output': True, } # Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == 0x00... - assert not spec.eth_fast_aggregate_verify([], message, NO_SIGNATURE) - yield f'eth_fast_aggregate_verify_na_pubkeys_and_na_signature', { + assert not spec.eth_fast_aggregate_verify([], message, ZERO_SIGNATURE) + yield f'eth_fast_aggregate_verify_na_pubkeys_and_zero_signature', { 'input': { 'pubkeys': [], 'message': encode_hex(message), - 'signature': encode_hex(NO_SIGNATURE), + 'signature': encode_hex(ZERO_SIGNATURE), }, 'output': False, } # Invalid pubkeys and signature -- pubkeys contains point at infinity pubkeys = PUBKEYS.copy() - pubkeys_with_infinity = pubkeys + [Z1_PUBKEY] + pubkeys_with_infinity = pubkeys + [G1_POINT_AT_INFINITY] signatures = [bls.Sign(privkey, SAMPLE_MESSAGE) for privkey in PRIVKEYS] aggregate_signature = bls.Aggregate(signatures) assert not spec.eth_fast_aggregate_verify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature) From 6f56e33f00647bfc75cc752a1d62d8bee4b295af Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 5 Aug 2021 16:54:22 -0700 Subject: [PATCH 049/135] Update tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py Co-authored-by: Danny Ryan --- .../altair/epoch_processing/test_process_inactivity_updates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py index 5a334f28a..30359f822 100644 --- a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py +++ b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py @@ -285,7 +285,7 @@ def test_some_exited_full_random_leaking(spec, state): # mutations applied to their inactivity scores randomize_inactivity_scores(spec, state, rng=rng) - assert not get_exited_validators(spec, state) + assert not any(get_exited_validators(spec, state)) exited_indices = exit_validators(spec, state, exit_count, rng=rng) assert not get_exited_validators(spec, state) From 170d7dc023e77cbdb60a119e768a364c565960f7 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 5 Aug 2021 16:54:29 -0700 Subject: [PATCH 050/135] Update tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py Co-authored-by: Danny Ryan --- .../altair/epoch_processing/test_process_inactivity_updates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py index 30359f822..9bc0f4841 100644 --- a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py +++ b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py @@ -287,7 +287,7 @@ def test_some_exited_full_random_leaking(spec, state): assert not any(get_exited_validators(spec, state)) exited_indices = exit_validators(spec, state, exit_count, rng=rng) - assert not get_exited_validators(spec, state) + assert not any(get_exited_validators(spec, state)) # advance the state to effect the exits target_epoch = max(state.validators[index].exit_epoch for index in exited_indices) From ad4445fa9e0609c8d449c7a880e44179a1421a8e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 6 Aug 2021 16:39:35 +0800 Subject: [PATCH 051/135] Apply PR feedback from Danny and clean up the BLS test format docs --- tests/formats/bls/aggregate.md | 2 ++ tests/formats/bls/aggregate_verify.md | 13 ++++++++++--- tests/formats/bls/eth_aggregate_pubkeys.md | 2 +- tests/formats/bls/eth_fast_aggregate_verify.md | 13 ++++++++++--- tests/formats/bls/fast_aggregate_verify.md | 13 ++++++++++--- 5 files changed, 33 insertions(+), 10 deletions(-) diff --git a/tests/formats/bls/aggregate.md b/tests/formats/bls/aggregate.md index af8444540..81ce85fe6 100644 --- a/tests/formats/bls/aggregate.md +++ b/tests/formats/bls/aggregate.md @@ -14,6 +14,8 @@ output: BLS Signature -- expected output, single BLS signature or empty. - `BLS Signature` here is encoded as a string: hexadecimal encoding of 96 bytes (192 nibbles), prefixed with `0x`. - No output value if the input is invalid. +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`. + ## Condition The `aggregate` handler should aggregate the signatures in the `input`, and the result should match the expected `output`. diff --git a/tests/formats/bls/aggregate_verify.md b/tests/formats/bls/aggregate_verify.md index 3985de9f4..9b251af46 100644 --- a/tests/formats/bls/aggregate_verify.md +++ b/tests/formats/bls/aggregate_verify.md @@ -8,10 +8,17 @@ The test data is declared in a `data.yaml` file: ```yaml input: - pubkeys: List[bytes48] -- the pubkeys + pubkeys: List[BLS Pubkey] -- the pubkeys messages: List[bytes32] -- the messages - signature: bytes96 -- the signature to verify against pubkeys and messages -output: bool -- VALID or INVALID + signature: BLS Signature -- the signature to verify against pubkeys and messages +output: bool -- true (VALID) or false (INVALID) ``` +- `BLS Pubkey` here is encoded as a string: hexadecimal encoding of 48 bytes (96 nibbles), prefixed with `0x`. +- `BLS Signature` here is encoded as a string: hexadecimal encoding of 96 bytes (192 nibbles), prefixed with `0x`. + All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`. + +## Condition + +The `aggregate_verify` handler should verify the signature with pubkeys and messages in the `input`, and the result should match the expected `output`. diff --git a/tests/formats/bls/eth_aggregate_pubkeys.md b/tests/formats/bls/eth_aggregate_pubkeys.md index 86d0e3cd0..4f66adec2 100644 --- a/tests/formats/bls/eth_aggregate_pubkeys.md +++ b/tests/formats/bls/eth_aggregate_pubkeys.md @@ -8,7 +8,7 @@ The test data is declared in a `data.yaml` file: ```yaml input: List[BLS Pubkey] -- list of input BLS pubkeys -output: BLS Pubkey -- expected output, single BLS pubkeys or empty. +output: BLSPubkey -- expected output, single BLS pubkeys or empty. ``` - `BLS Pubkey` here is encoded as a string: hexadecimal encoding of 48 bytes (96 nibbles), prefixed with `0x`. diff --git a/tests/formats/bls/eth_fast_aggregate_verify.md b/tests/formats/bls/eth_fast_aggregate_verify.md index ddc1b5208..83b5484e0 100644 --- a/tests/formats/bls/eth_fast_aggregate_verify.md +++ b/tests/formats/bls/eth_fast_aggregate_verify.md @@ -8,10 +8,17 @@ The test data is declared in a `data.yaml` file: ```yaml input: - pubkeys: List[bytes48] -- the pubkey + pubkeys: List[BLS Pubkey] -- list of input BLS pubkeys message: bytes32 -- the message - signature: bytes96 -- the signature to verify against pubkeys and message -output: bool -- VALID or INVALID + signature: BLS Signature -- the signature to verify against pubkeys and message +output: bool -- true (VALID) or false (INVALID) ``` +- `BLS Pubkey` here is encoded as a string: hexadecimal encoding of 48 bytes (96 nibbles), prefixed with `0x`. +- `BLS Signature` here is encoded as a string: hexadecimal encoding of 96 bytes (192 nibbles), prefixed with `0x`. + All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`. + +## Condition + +The `eth_fast_aggregate_verify` handler should verify the signature with pubkeys and message in the `input`, and the result should match the expected `output`. diff --git a/tests/formats/bls/fast_aggregate_verify.md b/tests/formats/bls/fast_aggregate_verify.md index 3366cbb79..38ea29bb5 100644 --- a/tests/formats/bls/fast_aggregate_verify.md +++ b/tests/formats/bls/fast_aggregate_verify.md @@ -8,10 +8,17 @@ The test data is declared in a `data.yaml` file: ```yaml input: - pubkeys: List[bytes48] -- the pubkey + pubkeys: List[BLS Pubkey] -- list of input BLS pubkeys message: bytes32 -- the message - signature: bytes96 -- the signature to verify against pubkeys and message -output: bool -- VALID or INVALID + signature: BLS Signature -- the signature to verify against pubkeys and message +output: bool -- true (VALID) or false (INVALID) ``` +- `BLS Pubkey` here is encoded as a string: hexadecimal encoding of 48 bytes (96 nibbles), prefixed with `0x`. +- `BLS Signature` here is encoded as a string: hexadecimal encoding of 96 bytes (192 nibbles), prefixed with `0x`. + All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`. + +## Condition + +The `fast_aggregate_verify` handler should verify the signature with pubkeys and message in the `input`, and the result should match the expected `output`. From 600f55ba7ff34a95756ba202537c46666a643e9d Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 9 Aug 2021 17:50:56 -0600 Subject: [PATCH 052/135] add basic execution-layer p2p beacon_block validations --- specs/merge/p2p-interface.md | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/specs/merge/p2p-interface.md b/specs/merge/p2p-interface.md index 712a17549..c79d14b05 100644 --- a/specs/merge/p2p-interface.md +++ b/specs/merge/p2p-interface.md @@ -61,12 +61,32 @@ The Merge changes the type of the global beacon block topic. ##### `beacon_block` -The existing specification for this topic does not change from prior upgrades, -but the type of the payload does change to the (modified) `SignedBeaconBlock` found in the Merge. -This type changes due to the addition of `execution_payload` to the inner `BeaconBlockBody`. - +The *type* of the payload of this topic changes to the (modified) `SignedBeaconBlock` found in the Merge. +Specifically, this type changes with the addition of `execution_payload` to the inner `BeaconBlockBody`. See the Merge [state transition document](./beacon-chain.md#beaconblockbody) for further details. +In addition to the gossip validations for this topic from prior specifications, +the following validations MUST pass before forwarding the `signed_beacon_block` on the network. +Alias `block = signed_beacon_block.message`, `execution_payload = block.body.execution_payload`. +- If the merge is complete with respect to the head state -- i.e. `is_merge_complete(state)` -- + then validate the following: + - _[REJECT]_ The block's execution payload must be non-empty -- + i.e. `execution_payload != ExecutionPayload()` +- If the execution is enabled for the block -- i.e. `is_execution_enabled(state, block.body)` + then validate the following: + - _[REJECT]_ The block's execution payload timestamp is correct with respect to the slot + -- i.e. `execution_payload.timestamp == compute_time_at_slot(state, block.slot)`. + - _[REJECT]_ Gas used is less than the gas limit -- + i.e. `execution_payload.gas_used <= execution_payload.gas_limit`. + - _[REJECT]_ The execution payload block hash is not equal to the parent hash -- + i.e. `execution_payload.block_hash != execution_payload.parent_hash`. + - _[REJECT]_ The execution payload transaction list data is within expected size limits, + the data MUST NOT be larger than the SSZ list-limit, + and a client MAY be more strict. + +*Note*: Additional [gossip validations](https://github.com/ethereum/devp2p/blob/master/caps/eth.md#block-encoding-and-validity) +(see block "data validity" conditions) that rely more heavily on execution-layer state and logic are currently under consideration. + ### Transitioning the gossip See gossip transition details found in the [Altair document](../altair/p2p) for From ef71a4af1d35c0882a4dd584267b1d4d285976a3 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Tue, 10 Aug 2021 17:15:07 +0600 Subject: [PATCH 053/135] Polishing as per code review --- specs/merge/beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 590e2bc4f..8837c0ea3 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -13,7 +13,7 @@ - [Constants](#constants) - [Execution](#execution) - [Configuration](#configuration) - - [Genesis settings](#genesis-settings) + - [Genesis testing settings](#genesis-testing-settings) - [Containers](#containers) - [Extended containers](#extended-containers) - [`BeaconBlockBody`](#beaconblockbody) @@ -70,7 +70,7 @@ This patch adds transaction execution to the beacon chain as part of the Merge f ## Configuration -### Genesis settings +### Genesis testing settings *Note*: These configuration settings do not apply to the mainnet and are utilized only by pure Merge testing. @@ -314,7 +314,7 @@ def compute_base_fee_per_gas(payload: ExecutionPayload, parent: ExecutionPayload ```python def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None: - # Verify consistency of the parent hash, block number and random + # Verify consistency of the parent hash, block number, random, base fee per gas and gas limit if is_merge_complete(state): assert payload.parent_hash == state.latest_execution_payload_header.block_hash assert payload.block_number == state.latest_execution_payload_header.block_number + uint64(1) From d005fee67df0cdc9bed448ac44d6912c19f8dcbb Mon Sep 17 00:00:00 2001 From: Diederik Loerakker Date: Tue, 10 Aug 2021 13:48:26 +0200 Subject: [PATCH 054/135] sharding p2p code review fixes Co-authored-by: Danny Ryan --- specs/sharding/p2p-interface.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/specs/sharding/p2p-interface.md b/specs/sharding/p2p-interface.md index 4eb3f6f2e..e5394abc2 100644 --- a/specs/sharding/p2p-interface.md +++ b/specs/sharding/p2p-interface.md @@ -89,10 +89,10 @@ on the horizontal subnet or creating samples for it. Alias `blob = signed_blob.m - _[REJECT]_ The shard blob is for the correct subnet -- i.e. `compute_subnet_for_shard_blob(state, blob.slot, blob.shard) == subnet_id` - _[IGNORE]_ The blob is the first blob with valid signature received for the `(blob.proposer_index, blob.slot, blob.shard)` combination. -- _[REJECT]_ The blob is not too large, the data MUST NOT be larger than the SSZ list-limit, and a client MAY be more strict. +- _[REJECT]_ The blob is not too large -- the data MUST NOT be larger than the SSZ list-limit, and a client MAY apply stricter bounds. - _[REJECT]_ The `blob.body.data` MUST NOT contain any point `p >= MODULUS`. Although it is a `uint256`, not the full 256 bit range is valid. -- _[REJECT]_ The blob builder exists and has sufficient balance to back the fee payment. -- _[REJECT]_ The blob signature is valid for the aggregate of proposer and builder, `signed_blob.signature`, +- _[REJECT]_ The blob builder defined by `blob.builder_index` exists and has sufficient balance to back the fee payment. +- _[REJECT]_ The blob signature, `signed_blob.signature`, is valid for the aggregate of proposer and builder -- i.e. `bls.FastAggregateVerify([builder_pubkey, proposer_pubkey], blob_signing_root, signed_blob.signature)`. - _[REJECT]_ The blob is proposed by the expected `proposer_index` for the blob's `slot` and `shard`, in the context of the current shuffling (defined by `blob.body.beacon_block_root`/`slot`). @@ -104,17 +104,17 @@ on the horizontal subnet or creating samples for it. Alias `blob = signed_blob.m There are three additional global topics for Sharding. -- `shard_blob_header`: co-signed headers, to be included on-chain, and signaling builders to publish full data. +- `shard_blob_header`: co-signed headers to be included on-chain and to serve as a signal to the builder to publish full data. - `shard_blob_tx`: builder-signed headers, also known as "data transaction". -- `shard_proposer_slashing`: slashings of duplicate shard proposals +- `shard_proposer_slashing`: slashings of duplicate shard proposals. ##### `shard_blob_header` Shard header data, in the form of a `SignedShardBlobHeader` is published to the global `shard_blob_header` subnet. -Shard blob headers select shard blob bids by builders, +Shard blob headers select shard blob bids by builders and should be timely to ensure builders can publish the full shard blob before subsequent attestations. -The following validations MUST pass before forwarding the `signed_blob_header` on the network. Alias `header = signed_blob_header.message` +The following validations MUST pass before forwarding the `signed_blob_header` on the network. Alias `header = signed_blob_header.message`. - _[IGNORE]_ The `header` is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that `header.slot <= current_slot` @@ -126,8 +126,8 @@ The following validations MUST pass before forwarding the `signed_blob_header` o - _[REJECT]_ The `header.shard` MUST have a committee at the `header.slot` -- i.e. validate that `compute_committee_index_from_shard(state, header.slot, header.shard)` doesn't raise an error - _[IGNORE]_ The header is the first header with valid signature received for the `(header.proposer_index, header.slot, header.shard)` combination. -- _[REJECT]_ The blob builder exists and has sufficient balance to back the fee payment. -- _[REJECT]_ The header signature is valid for the aggregate of proposer and builder, `signed_blob_header.signature`, +- _[REJECT]_ The blob builder defined by `blob.builder_index` exists and has sufficient balance to back the fee payment. +- _[REJECT]_ The header signature, `signed_blob_header.signature`, is valid for the aggregate of proposer and builder -- i.e. `bls.FastAggregateVerify([builder_pubkey, proposer_pubkey], blob_signing_root, signed_blob_header.signature)`. - _[REJECT]_ The header is proposed by the expected `proposer_index` for the blob's `header.slot` and `header.shard` in the context of the current shuffling (defined by `header.body_summary.beacon_block_root`/`slot`). @@ -137,12 +137,12 @@ The following validations MUST pass before forwarding the `signed_blob_header` o ##### `shard_blob_tx` -Shard data-transactions, in the form of a `SignedShardBlobHeader` is published to the global `shard_blob_tx` subnet. +Shard data-transactions in the form of a `SignedShardBlobHeader` are published to the global `shard_blob_tx` subnet. These shard blob headers are signed solely by the blob-builder. -The following validations MUST pass before forwarding the `signed_blob_header` on the network. Alias `header = signed_blob_header.message` +The following validations MUST pass before forwarding the `signed_blob_header` on the network. Alias `header = signed_blob_header.message`. -- _[IGNORE]_ The `header` is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- +- _[IGNORE]_ The header is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that `header.slot <= current_slot` (a client MAY queue future headers for processing at the appropriate slot). - _[IGNORE]_ The header is new enough to still be processed -- @@ -152,10 +152,10 @@ The following validations MUST pass before forwarding the `signed_blob_header` o - _[REJECT]_ The `header.shard` MUST have a committee at the `header.slot` -- i.e. validate that `compute_committee_index_from_shard(state, header.slot, header.shard)` doesn't raise an error - _[IGNORE]_ The header is the first header with valid signature received for the `(header.builder_index, header.slot, header.shard)` combination. -- _[REJECT]_ The blob builder exists and has sufficient balance to back the fee payment. +- _[REJECT]_ The blob builder, define by `header.builder_index`, exists and has sufficient balance to back the fee payment. - _[IGNORE]_ The header fee SHOULD be higher than previously seen headers for `(header.slot, header.shard)`, from any builder. Propagating nodes MAY increase fee increments in case of spam. -- _[REJECT]_ The header signature is valid for ONLY the builder, `signed_blob_header.signature`, +- _[REJECT]_ The header signature, `signed_blob_header.signature`, is valid for ONLY the builder -- i.e. `bls.Verify(builder_pubkey, blob_signing_root, signed_blob_header.signature)`. The signature is not an aggregate with the proposer. - _[REJECT]_ The header is designated for proposal by the expected `proposer_index` for the blob's `header.slot` and `header.shard` in the context of the current shuffling (defined by `header.body_summary.beacon_block_root`/`slot`). From da893c123e933ace3663f0306e19fb79d85b7d8f Mon Sep 17 00:00:00 2001 From: protolambda Date: Tue, 10 Aug 2021 23:18:59 +0200 Subject: [PATCH 055/135] update p2p shard blob/header/tx propagation windows --- specs/sharding/p2p-interface.md | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/specs/sharding/p2p-interface.md b/specs/sharding/p2p-interface.md index e5394abc2..93ff1e26d 100644 --- a/specs/sharding/p2p-interface.md +++ b/specs/sharding/p2p-interface.md @@ -36,6 +36,9 @@ The adjustments and additions for Shards are outlined in this document. | Name | Value | Description | | ---- | ----- | ----------- | | `SHARD_BLOB_SUBNET_COUNT` | `64` | The number of `shard_blob_{subnet_id}` subnets used in the gossipsub protocol. | +| `SHARD_TX_PROPAGATION_GRACE_SLOTS` | `4` | The number of slots for a late transaction to propagate | +| `SHARD_TX_PROPAGATION_BUFFER_SLOTS` | `8` | The number of slots for an early transaction to propagate | + ## Gossip domain @@ -77,9 +80,9 @@ def compute_subnet_for_shard_blob(state: BeaconState, slot: Slot, shard: Shard) The following validations MUST pass before forwarding the `signed_blob`, on the horizontal subnet or creating samples for it. Alias `blob = signed_blob.message`. -- _[IGNORE]_ The `blob` is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- - i.e. validate that `blob.slot <= current_slot` - (a client MAY queue future blobs for processing at the appropriate slot). +- _[IGNORE]_ The `blob` is published 1 slot early or later (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- + i.e. validate that `blob.slot <= current_slot + 1` + (a client MAY queue future blobs for propagation at the appropriate slot). - _[IGNORE]_ The `blob` is new enough to still be processed -- i.e. validate that `compute_epoch_at_slot(blob.slot) >= get_previous_epoch(state)` - _[REJECT]_ The shard blob is for an active shard -- @@ -116,15 +119,15 @@ and should be timely to ensure builders can publish the full shard blob before s The following validations MUST pass before forwarding the `signed_blob_header` on the network. Alias `header = signed_blob_header.message`. -- _[IGNORE]_ The `header` is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- - i.e. validate that `header.slot <= current_slot` - (a client MAY queue future headers for processing at the appropriate slot). +- _[IGNORE]_ The `header` is published 1 slot early or later (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- + i.e. validate that `header.slot <= current_slot + 1` + (a client MAY queue future headers for propagation at the appropriate slot). - _[IGNORE]_ The header is new enough to still be processed -- i.e. validate that `compute_epoch_at_slot(header.slot) >= get_previous_epoch(state)` - _[REJECT]_ The shard header is for an active shard -- i.e. `header.shard < get_active_shard_count(state, compute_epoch_at_slot(header.slot))` - _[REJECT]_ The `header.shard` MUST have a committee at the `header.slot` -- - i.e. validate that `compute_committee_index_from_shard(state, header.slot, header.shard)` doesn't raise an error + i.e. validate that `compute_committee_index_from_shard(state, header.slot, header.shard)` doesn't raise an error. - _[IGNORE]_ The header is the first header with valid signature received for the `(header.proposer_index, header.slot, header.shard)` combination. - _[REJECT]_ The blob builder defined by `blob.builder_index` exists and has sufficient balance to back the fee payment. - _[REJECT]_ The header signature, `signed_blob_header.signature`, is valid for the aggregate of proposer and builder -- @@ -142,15 +145,15 @@ These shard blob headers are signed solely by the blob-builder. The following validations MUST pass before forwarding the `signed_blob_header` on the network. Alias `header = signed_blob_header.message`. -- _[IGNORE]_ The header is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- - i.e. validate that `header.slot <= current_slot` - (a client MAY queue future headers for processing at the appropriate slot). -- _[IGNORE]_ The header is new enough to still be processed -- - i.e. validate that `compute_epoch_at_slot(header.slot) >= get_previous_epoch(state)` +- _[IGNORE]_ The header is not propagating more than `SHARD_TX_PROPAGATION_BUFFER_SLOTS` slots ahead of time -- + i.e. validate that `header.slot <= current_slot + SHARD_TX_PROPAGATION_BUFFER_SLOTS`. +- _[IGNORE]_ The header is not propagating later than `SHARD_TX_PROPAGATION_GRACE_SLOTS` slots too late -- + i.e. validate that `header.slot + SHARD_TX_PROPAGATION_GRACE_SLOTS >= current_slot` - _[REJECT]_ The shard header is for an active shard -- i.e. `header.shard < get_active_shard_count(state, compute_epoch_at_slot(header.slot))` - _[REJECT]_ The `header.shard` MUST have a committee at the `header.slot` -- - i.e. validate that `compute_committee_index_from_shard(state, header.slot, header.shard)` doesn't raise an error + i.e. validate that `compute_committee_index_from_shard(state, header.slot, header.shard)` doesn't raise an error. +- _[IGNORE]_ The header is not stale -- i.e. the corresponding shard proposer has not already selected a header for `(header.slot, header.shard)`. - _[IGNORE]_ The header is the first header with valid signature received for the `(header.builder_index, header.slot, header.shard)` combination. - _[REJECT]_ The blob builder, define by `header.builder_index`, exists and has sufficient balance to back the fee payment. - _[IGNORE]_ The header fee SHOULD be higher than previously seen headers for `(header.slot, header.shard)`, from any builder. From 68db644ae97199faad5b1f3b183f40acd386bb3a Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Wed, 4 Aug 2021 15:05:21 +0300 Subject: [PATCH 056/135] Rename DataCommitment.length field to samples_count and fix degree_proof validation --- specs/sharding/beacon-chain.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index bbb4ae930..9269d1461 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -242,7 +242,7 @@ class DataCommitment(Container): # KZG10 commitment to the data point: BLSCommitment # Length of the data in samples - length: uint64 + samples_count: uint64 ``` ### `AttestedDataCommitment` @@ -266,7 +266,7 @@ Unique, signing different bodies as shard proposer for the same `(slot, shard)` class ShardBlobBody(Container): # The actual data commitment commitment: DataCommitment - # Proof that the degree < commitment.length + # Proof that the degree < commitment.samples_count * POINTS_PER_SAMPLE degree_proof: BLSCommitment # The actual data. Should match the commitment and degree proof. data: List[BLSPoint, POINTS_PER_SAMPLE * MAX_SAMPLES_PER_BLOB] @@ -289,7 +289,7 @@ to avoid an extra network roundtrip between proposer and builder, to include the class ShardBlobBodySummary(Container): # The actual data commitment commitment: DataCommitment - # Proof that the degree < commitment.length + # Proof that the degree < commitment.samples_count * POINTS_PER_SAMPLE degree_proof: BLSCommitment # Hash-tree-root as summary of the data field data_root: Root @@ -719,16 +719,17 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade # Verify the length by verifying the degree. body_summary = header.body_summary - if body_summary.commitment.length == 0: + points_count = body_summary.commitment.samples_count * POINTS_PER_SAMPLE + if points_count == 0: assert body_summary.degree_proof == G1_SETUP[0] assert ( bls.Pairing(body_summary.degree_proof, G2_SETUP[0]) - == bls.Pairing(body_summary.commitment.point, G2_SETUP[-body_summary.commitment.length]) + == bls.Pairing(body_summary.commitment.point, G2_SETUP[-points_count]) ) # Charge EIP 1559 fee, builder pays for opportunity, and is responsible for later availability, # or fail to publish at their own expense. - samples = body_summary.commitment.length + samples = body_summary.commitment.samples_count # TODO: overflows, need bigger int type max_fee = body_summary.max_fee_per_sample * samples From 301157c0273b9046d2c86760064769972e18cf98 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Fri, 13 Aug 2021 14:21:37 +0600 Subject: [PATCH 057/135] Change base_fee_per_gas type to Bytes32 --- specs/merge/beacon-chain.md | 35 ++----------------- .../test/helpers/execution_payload.py | 3 +- 2 files changed, 4 insertions(+), 34 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 8837c0ea3..0d9f03079 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -34,7 +34,6 @@ - [Block processing](#block-processing) - [Execution payload processing](#execution-payload-processing) - [`is_valid_gas_limit`](#is_valid_gas_limit) - - [`compute_base_fee_per_gas`](#compute_base_fee_per_gas) - [`process_execution_payload`](#process_execution_payload) - [Testing](#testing) @@ -65,8 +64,6 @@ This patch adds transaction execution to the beacon chain as part of the Merge f | `BYTES_PER_LOGS_BLOOM` | `uint64(2**8)` (= 256) | | `GAS_LIMIT_DENOMINATOR` | `uint64(2**10)` (= 1,024) | | `MIN_GAS_LIMIT` | `uint64(5000)` (= 5,000) | -| `BASE_FEE_MAX_CHANGE_DENOMINATOR` | `uint64(2**3)` (= 8) | -| `ELASTICITY_MULTIPLIER` | `uint64(2**1)` (= 2) | ## Configuration @@ -77,7 +74,7 @@ This patch adds transaction execution to the beacon chain as part of the Merge f | Name | Value | | - | - | | `GENESIS_GAS_LIMIT` | `uint64(30000000)` (= 30,000,000) | -| `GENESIS_BASE_FEE_PER_GAS` | `uint64(1000000000)` (= 1,000,000,000) | +| `GENESIS_BASE_FEE_PER_GAS` | `Bytes32('0x000000000000000000000000000000000000000000000000000000003b9aca00')` (= 1,000,000,000) | ## Containers @@ -160,7 +157,7 @@ class ExecutionPayload(Container): gas_limit: uint64 gas_used: uint64 timestamp: uint64 - base_fee_per_gas: uint64 # base fee introduced in EIP-1559 + base_fee_per_gas: Bytes32 # base fee introduced in EIP-1559 # Extra payload fields block_hash: Hash32 # Hash of execution block transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] @@ -181,7 +178,7 @@ class ExecutionPayloadHeader(Container): gas_limit: uint64 gas_used: uint64 timestamp: uint64 - base_fee_per_gas: uint64 + base_fee_per_gas: Bytes32 # Extra payload fields block_hash: Hash32 # Hash of execution block transactions_root: Root @@ -283,31 +280,6 @@ def is_valid_gas_limit(payload: ExecutionPayload, parent: ExecutionPayloadHeader return True ``` -#### `compute_base_fee_per_gas` - -```python -def compute_base_fee_per_gas(payload: ExecutionPayload, parent: ExecutionPayloadHeader) -> uint64: - parent_gas_target = parent.gas_limit // ELASTICITY_MULTIPLIER - parent_base_fee_per_gas = parent.base_fee_per_gas - parent_gas_used = payload.gas_used - - if parent_gas_used == parent_gas_target: - return parent_base_fee_per_gas - elif parent_gas_used > parent_gas_target: - gas_used_delta = parent_gas_used - parent_gas_target - base_fee_per_gas_delta = max( - parent_base_fee_per_gas * gas_used_delta // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR, - 1, - ) - return parent_base_fee_per_gas + base_fee_per_gas_delta - else: - gas_used_delta = parent_gas_target - parent_gas_used - base_fee_per_gas_delta = ( - parent_base_fee_per_gas * gas_used_delta // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR - ) - return parent_base_fee_per_gas - base_fee_per_gas_delta # This subtraction can't underflow -``` - #### `process_execution_payload` *Note:* This function depends on `process_randao` function call as it retrieves the most recent randao mix from the `state`. Implementations that are considering parallel processing of execution payload with respect to beacon chain state transition function should work around this dependency. @@ -319,7 +291,6 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe assert payload.parent_hash == state.latest_execution_payload_header.block_hash assert payload.block_number == state.latest_execution_payload_header.block_number + uint64(1) assert payload.random == get_randao_mix(state, get_current_epoch(state)) - assert payload.base_fee_per_gas == compute_base_fee_per_gas(payload, state.latest_execution_payload_header) assert is_valid_gas_limit(payload, state.latest_execution_payload_header) # Verify timestamp assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index ce653a986..43be965a5 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -20,11 +20,10 @@ def build_empty_execution_payload(spec, state, randao_mix=None): gas_limit=latest.gas_limit, # retain same limit gas_used=0, # empty block, 0 gas timestamp=timestamp, - base_fee_per_gas=spec.uint64(0), + base_fee_per_gas=latest.base_fee_per_gas, # retain same base_fee block_hash=spec.Hash32(), transactions=empty_txs, ) - payload.base_fee_per_gas = spec.compute_base_fee_per_gas(payload, latest) # TODO: real RLP + block hash logic would be nice, requires RLP and keccak256 dependency however. payload.block_hash = spec.Hash32(spec.hash(payload.hash_tree_root() + b"FAKE RLP HASH")) From e3cad13497f10fef5a1c16446d116e074e72e252 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Mon, 16 Aug 2021 18:35:11 +0600 Subject: [PATCH 058/135] Make base_fee_per_gas little-endian --- specs/merge/beacon-chain.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 0d9f03079..5defa6bcb 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -74,7 +74,7 @@ This patch adds transaction execution to the beacon chain as part of the Merge f | Name | Value | | - | - | | `GENESIS_GAS_LIMIT` | `uint64(30000000)` (= 30,000,000) | -| `GENESIS_BASE_FEE_PER_GAS` | `Bytes32('0x000000000000000000000000000000000000000000000000000000003b9aca00')` (= 1,000,000,000) | +| `GENESIS_BASE_FEE_PER_GAS` | `Bytes32('0x00ca9a3b00000000000000000000000000000000000000000000000000000000')` (= 1,000,000,000) | ## Containers @@ -144,6 +144,8 @@ class BeaconState(Container): #### `ExecutionPayload` +*Note*: The `base_fee_per_gas` field is serialized in little-endian. + ```python class ExecutionPayload(Container): # Execution block header fields @@ -157,7 +159,7 @@ class ExecutionPayload(Container): gas_limit: uint64 gas_used: uint64 timestamp: uint64 - base_fee_per_gas: Bytes32 # base fee introduced in EIP-1559 + base_fee_per_gas: Bytes32 # base fee introduced in EIP-1559, little-endian serialized # Extra payload fields block_hash: Hash32 # Hash of execution block transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] From 471cc870c30bf294b939e94e58f9340b2ca2c298 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 16 Aug 2021 13:10:58 -0600 Subject: [PATCH 059/135] remove print statements --- tests/core/pyspec/eth2spec/test/helpers/block_processing.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/block_processing.py b/tests/core/pyspec/eth2spec/test/helpers/block_processing.py index e82f62ed0..8721a772e 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/block_processing.py +++ b/tests/core/pyspec/eth2spec/test/helpers/block_processing.py @@ -48,11 +48,9 @@ def run_block_processing_to(spec, state, block, process_name: str): A test prepares a pre-state by calling this function, output the pre-state, and it can then proceed to run the returned callable, and output a post-state. """ - print(f"state.slot {state.slot} block.slot {block.slot}") # transition state to slot before block state transition if state.slot < block.slot: spec.process_slots(state, block.slot) - print(f"state.slot {state.slot} block.slot {block.slot} A") # process components of block transition for name, call in get_process_calls(spec).items(): From a542fd3a8a7e61dd66dca233ef32425691daa9e3 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 18 Aug 2021 08:45:23 -0600 Subject: [PATCH 060/135] modify README for consensus-specs renaming --- README.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f5ac598b6..bc4ba100b 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ -# Ethereum 2.0 Specifications +# Ethereum Proof-of-Stake Consensus Specifications [![Join the chat at https://discord.gg/qGpsxSA](https://img.shields.io/badge/chat-on%20discord-blue.svg)](https://discord.gg/qGpsxSA) [![Join the chat at https://gitter.im/ethereum/sharding](https://badges.gitter.im/ethereum/sharding.svg)](https://gitter.im/ethereum/sharding?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -To learn more about sharding and Ethereum 2.0 (Serenity), see the [sharding FAQ](https://eth.wiki/sharding/Sharding-FAQs) and the [research compendium](https://notes.ethereum.org/s/H1PGqDhpm). +To learn more about proof-of-stake and sharding, see the [PoS FAQ](https://eth.wiki/en/concepts/proof-of-stake-faqs), [sharding FAQ](https://eth.wiki/sharding/Sharding-FAQs) and the [research compendium](https://notes.ethereum.org/s/H1PGqDhpm). -This repository hosts the current Eth2 specifications. Discussions about design rationale and proposed changes can be brought up and discussed as issues. Solidified, agreed-upon changes to the spec can be made through pull requests. +This repository hosts the current Ethereum proof-of-stake specifications. Discussions about design rationale and proposed changes can be brought up and discussed as issues. Solidified, agreed-upon changes to the spec can be made through pull requests. ## Specs [![GitHub release](https://img.shields.io/github/v/release/ethereum/eth2.0-specs)](https://github.com/ethereum/eth2.0-specs/releases/) [![PyPI version](https://badge.fury.io/py/eth2spec.svg)](https://badge.fury.io/py/eth2spec) -Core specifications for Eth2 clients can be found in [specs](specs/). These are divided into features. +Core specifications for Ethereum proof-of-stake clients can be found in [specs](specs/). These are divided into features. Features are researched and developed in parallel, and then consolidated into sequential upgrades when ready. The current features are: @@ -73,13 +73,12 @@ Sharding follows the merge, and is divided into three parts: Additional specifications and standards outside of requisite client functionality can be found in the following repos: -* [Eth2 APIs](https://github.com/ethereum/eth2.0-apis) -* [Eth2 Metrics](https://github.com/ethereum/eth2.0-metrics/) -* [Interop Standards in Eth2 PM](https://github.com/ethereum/eth2.0-pm/tree/master/interop) +* [Beacon APIs](https://github.com/ethereum/beacon-apis) +* [Beacon Metrics](https://github.com/ethereum/beacon-metrics/) ## Design goals -The following are the broad design goals for Ethereum 2.0: +The following are the broad design goals for the Ethereum proof-of-stake consensus specifications: * to minimize complexity, even at the cost of some losses in efficiency * to remain live through major network partitions and when very large portions of nodes go offline * to select all components such that they are either quantum secure or can be easily swapped out for quantum secure counterparts when available @@ -97,3 +96,7 @@ The following are the broad design goals for Ethereum 2.0: Documentation on the different components used during spec writing can be found here: * [YAML Test Generators](tests/generators/README.md) * [Executable Python Spec, with Py-tests](tests/core/pyspec/README.md) + +## Consensus spec tests + +Conformance tests built from the executable python spec are available in the [Ethereum Proof-of-Stake Consensus Spec Tests](https://github.com/ethereum/consensus-spec-tests) repo. Compressed tarballs are available in [releases](https://github.com/ethereum/consensus-spec-tests/releases). From 817d2ee7bf86922329345334dcbb6ac285d58e5d Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 16 Aug 2021 13:11:31 -0600 Subject: [PATCH 061/135] Refactor sync committee tests so rewards are verified for all test cases --- .../test_process_sync_aggregate.py | 249 +----------------- .../test_process_sync_aggregate_random.py | 165 ++++++++++++ .../eth2spec/test/helpers/sync_committee.py | 108 +++++++- 3 files changed, 274 insertions(+), 248 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/altair/block_processing/test_process_sync_aggregate_random.py diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/test_process_sync_aggregate.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/test_process_sync_aggregate.py index e4176ee58..4be6737e2 100644 --- a/tests/core/pyspec/eth2spec/test/altair/block_processing/test_process_sync_aggregate.py +++ b/tests/core/pyspec/eth2spec/test/altair/block_processing/test_process_sync_aggregate.py @@ -2,7 +2,6 @@ import random from eth2spec.test.helpers.block import ( build_empty_block_for_next_slot, ) -from eth2spec.test.helpers.block_processing import run_block_processing_to from eth2spec.test.helpers.state import ( state_transition_and_sign_block, transition_to, @@ -12,60 +11,17 @@ from eth2spec.test.helpers.constants import ( ) from eth2spec.test.helpers.sync_committee import ( compute_aggregate_sync_committee_signature, - compute_sync_committee_participant_reward_and_penalty, - compute_sync_committee_proposer_reward, compute_committee_indices, + get_committee_indices, + run_sync_committee_processing, + run_successful_sync_committee_test, ) from eth2spec.test.context import ( - default_activation_threshold, - expect_assertion_error, - misc_balances, - single_phase, with_altair_and_later, - with_custom_state, with_presets, spec_state_test, always_bls, - spec_test, ) -from eth2spec.utils.hash_function import hash - - -def run_sync_committee_processing(spec, state, block, expect_exception=False): - """ - Processes everything up to the sync committee work, then runs the sync committee work in isolation, and - produces a pre-state and post-state (None if exception) specifically for sync-committee processing changes. - """ - # process up to the sync committee work - call = run_block_processing_to(spec, state, block, 'process_sync_aggregate') - yield 'pre', state - yield 'sync_aggregate', block.body.sync_aggregate - if expect_exception: - expect_assertion_error(lambda: call(state, block)) - yield 'post', None - else: - call(state, block) - yield 'post', state - - -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]) @with_altair_and_later @@ -177,58 +133,6 @@ def test_invalid_signature_extra_participant(spec, state): yield from run_sync_committee_processing(spec, state, block, expect_exception=True) -def validate_sync_committee_rewards(spec, pre_state, post_state, committee_indices, committee_bits, proposer_index): - for index in range(len(post_state.validators)): - reward = 0 - penalty = 0 - if index in committee_indices: - _reward, _penalty = compute_sync_committee_participant_reward_and_penalty( - spec, - pre_state, - index, - committee_indices, - committee_bits, - ) - reward += _reward - penalty += _penalty - - if proposer_index == index: - reward += compute_sync_committee_proposer_reward( - spec, - pre_state, - committee_indices, - committee_bits, - ) - - assert post_state.balances[index] == pre_state.balances[index] + reward - penalty - - -def run_successful_sync_committee_test(spec, state, committee_indices, committee_bits): - pre_state = state.copy() - - block = build_empty_block_for_next_slot(spec, state) - block.body.sync_aggregate = spec.SyncAggregate( - sync_committee_bits=committee_bits, - sync_committee_signature=compute_aggregate_sync_committee_signature( - spec, - state, - block.slot - 1, - [index for index, bit in zip(committee_indices, committee_bits) if bit], - ) - ) - - yield from run_sync_committee_processing(spec, state, block) - - validate_sync_committee_rewards( - spec, - pre_state, - state, - committee_indices, - committee_bits, - block.proposer_index, - ) - - @with_altair_and_later @with_presets([MINIMAL], reason="to create nonduplicate committee") @spec_state_test @@ -502,150 +406,3 @@ 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") - - -def _test_harness_for_randomized_test_case(spec, state, duplicates=False, participation_fn=None): - committee_indices = get_committee_indices(spec, state, duplicates=duplicates) - - if participation_fn: - participating_indices = participation_fn(committee_indices) - else: - participating_indices = committee_indices - - committee_bits = [index in participating_indices for index in committee_indices] - committee_size = len(committee_indices) - if duplicates: - assert committee_size > len(set(committee_indices)) - else: - assert committee_size == len(set(committee_indices)) - - yield from run_successful_sync_committee_test(spec, state, committee_indices, committee_bits) - - -@with_altair_and_later -@with_presets([MAINNET], reason="to create duplicate committee") -@spec_state_test -def test_random_only_one_participant_with_duplicates(spec, state): - rng = random.Random(101) - yield from _test_harness_for_randomized_test_case( - spec, - state, - duplicates=True, - participation_fn=lambda comm: [rng.choice(comm)], - ) - - -@with_altair_and_later -@with_presets([MAINNET], reason="to create duplicate committee") -@spec_state_test -def test_random_low_participation_with_duplicates(spec, state): - rng = random.Random(201) - yield from _test_harness_for_randomized_test_case( - spec, - state, - duplicates=True, - participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.25)), - ) - - -@with_altair_and_later -@with_presets([MAINNET], reason="to create duplicate committee") -@spec_state_test -def test_random_high_participation_with_duplicates(spec, state): - rng = random.Random(301) - yield from _test_harness_for_randomized_test_case( - spec, - state, - duplicates=True, - participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.75)), - ) - - -@with_altair_and_later -@with_presets([MAINNET], reason="to create duplicate committee") -@spec_state_test -def test_random_all_but_one_participating_with_duplicates(spec, state): - rng = random.Random(401) - yield from _test_harness_for_randomized_test_case( - spec, - state, - duplicates=True, - participation_fn=lambda comm: rng.sample(comm, len(comm) - 1), - ) - - -@with_altair_and_later -@with_presets([MAINNET], reason="to create duplicate committee") -@spec_test -@with_custom_state(balances_fn=misc_balances, threshold_fn=default_activation_threshold) -@single_phase -def test_random_misc_balances_and_half_participation_with_duplicates(spec, state): - rng = random.Random(1401) - yield from _test_harness_for_randomized_test_case( - spec, - state, - duplicates=True, - 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 -def test_random_only_one_participant_without_duplicates(spec, state): - rng = random.Random(501) - yield from _test_harness_for_randomized_test_case( - spec, - state, - participation_fn=lambda comm: [rng.choice(comm)], - ) - - -@with_altair_and_later -@with_presets([MINIMAL], reason="to create nonduplicate committee") -@spec_state_test -def test_random_low_participation_without_duplicates(spec, state): - rng = random.Random(601) - yield from _test_harness_for_randomized_test_case( - spec, - state, - participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.25)), - ) - - -@with_altair_and_later -@with_presets([MINIMAL], reason="to create nonduplicate committee") -@spec_state_test -def test_random_high_participation_without_duplicates(spec, state): - rng = random.Random(701) - yield from _test_harness_for_randomized_test_case( - spec, - state, - participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.75)), - ) - - -@with_altair_and_later -@with_presets([MINIMAL], reason="to create nonduplicate committee") -@spec_state_test -def test_random_all_but_one_participating_without_duplicates(spec, state): - rng = random.Random(801) - yield from _test_harness_for_randomized_test_case( - spec, - state, - participation_fn=lambda comm: rng.sample(comm, len(comm) - 1), - ) - - -@with_altair_and_later -@with_presets([MINIMAL], reason="to create nonduplicate committee") -@spec_test -@with_custom_state(balances_fn=misc_balances, threshold_fn=default_activation_threshold) -@single_phase -def test_random_misc_balances_and_half_participation_without_duplicates(spec, state): - rng = random.Random(1501) - 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/altair/block_processing/test_process_sync_aggregate_random.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/test_process_sync_aggregate_random.py new file mode 100644 index 000000000..75845e060 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/block_processing/test_process_sync_aggregate_random.py @@ -0,0 +1,165 @@ +import random +from eth2spec.test.helpers.constants import ( + MAINNET, MINIMAL, +) +from eth2spec.test.helpers.sync_committee import ( + get_committee_indices, + run_successful_sync_committee_test, +) +from eth2spec.test.context import ( + with_altair_and_later, + spec_state_test, + default_activation_threshold, + misc_balances, + single_phase, + with_custom_state, + with_presets, + spec_test, +) + + +def _test_harness_for_randomized_test_case(spec, state, duplicates=False, participation_fn=None): + committee_indices = get_committee_indices(spec, state, duplicates=duplicates) + + if participation_fn: + participating_indices = participation_fn(committee_indices) + else: + participating_indices = committee_indices + + committee_bits = [index in participating_indices for index in committee_indices] + committee_size = len(committee_indices) + if duplicates: + assert committee_size > len(set(committee_indices)) + else: + assert committee_size == len(set(committee_indices)) + + yield from run_successful_sync_committee_test(spec, state, committee_indices, committee_bits) + + +@with_altair_and_later +@with_presets([MAINNET], reason="to create duplicate committee") +@spec_state_test +def test_random_only_one_participant_with_duplicates(spec, state): + rng = random.Random(101) + yield from _test_harness_for_randomized_test_case( + spec, + state, + duplicates=True, + participation_fn=lambda comm: [rng.choice(comm)], + ) + + +@with_altair_and_later +@with_presets([MAINNET], reason="to create duplicate committee") +@spec_state_test +def test_random_low_participation_with_duplicates(spec, state): + rng = random.Random(201) + yield from _test_harness_for_randomized_test_case( + spec, + state, + duplicates=True, + participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.25)), + ) + + +@with_altair_and_later +@with_presets([MAINNET], reason="to create duplicate committee") +@spec_state_test +def test_random_high_participation_with_duplicates(spec, state): + rng = random.Random(301) + yield from _test_harness_for_randomized_test_case( + spec, + state, + duplicates=True, + participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.75)), + ) + + +@with_altair_and_later +@with_presets([MAINNET], reason="to create duplicate committee") +@spec_state_test +def test_random_all_but_one_participating_with_duplicates(spec, state): + rng = random.Random(401) + yield from _test_harness_for_randomized_test_case( + spec, + state, + duplicates=True, + participation_fn=lambda comm: rng.sample(comm, len(comm) - 1), + ) + + +@with_altair_and_later +@with_presets([MAINNET], reason="to create duplicate committee") +@spec_test +@with_custom_state(balances_fn=misc_balances, threshold_fn=default_activation_threshold) +@single_phase +def test_random_misc_balances_and_half_participation_with_duplicates(spec, state): + rng = random.Random(1401) + yield from _test_harness_for_randomized_test_case( + spec, + state, + duplicates=True, + 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 +def test_random_only_one_participant_without_duplicates(spec, state): + rng = random.Random(501) + yield from _test_harness_for_randomized_test_case( + spec, + state, + participation_fn=lambda comm: [rng.choice(comm)], + ) + + +@with_altair_and_later +@with_presets([MINIMAL], reason="to create nonduplicate committee") +@spec_state_test +def test_random_low_participation_without_duplicates(spec, state): + rng = random.Random(601) + yield from _test_harness_for_randomized_test_case( + spec, + state, + participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.25)), + ) + + +@with_altair_and_later +@with_presets([MINIMAL], reason="to create nonduplicate committee") +@spec_state_test +def test_random_high_participation_without_duplicates(spec, state): + rng = random.Random(701) + yield from _test_harness_for_randomized_test_case( + spec, + state, + participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.75)), + ) + + +@with_altair_and_later +@with_presets([MINIMAL], reason="to create nonduplicate committee") +@spec_state_test +def test_random_all_but_one_participating_without_duplicates(spec, state): + rng = random.Random(801) + yield from _test_harness_for_randomized_test_case( + spec, + state, + participation_fn=lambda comm: rng.sample(comm, len(comm) - 1), + ) + + +@with_altair_and_later +@with_presets([MINIMAL], reason="to create nonduplicate committee") +@spec_test +@with_custom_state(balances_fn=misc_balances, threshold_fn=default_activation_threshold) +@single_phase +def test_random_misc_balances_and_half_participation_without_duplicates(spec, state): + rng = random.Random(1501) + 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 71be65044..e59f679e1 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py +++ b/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py @@ -1,10 +1,15 @@ from collections import Counter +from eth2spec.test.context import ( + expect_assertion_error, +) from eth2spec.test.helpers.keys import privkeys from eth2spec.test.helpers.block import ( build_empty_block_for_next_slot, ) +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,5 +80,104 @@ def compute_committee_indices(spec, state, committee): Given a ``committee``, calculate and return the related indices """ all_pubkeys = [v.pubkey for v in state.validators] - committee_indices = [all_pubkeys.index(pubkey) for pubkey in committee.pubkeys] - return committee_indices + return [all_pubkeys.index(pubkey) for pubkey in committee.pubkeys] + + +def validate_sync_committee_rewards(spec, pre_state, post_state, committee_indices, committee_bits, proposer_index): + for index in range(len(post_state.validators)): + reward = 0 + penalty = 0 + if index in committee_indices: + _reward, _penalty = compute_sync_committee_participant_reward_and_penalty( + spec, + pre_state, + index, + committee_indices, + committee_bits, + ) + reward += _reward + penalty += _penalty + + if proposer_index == index: + reward += compute_sync_committee_proposer_reward( + spec, + pre_state, + committee_indices, + committee_bits, + ) + + assert post_state.balances[index] == pre_state.balances[index] + reward - penalty + + +def run_sync_committee_processing(spec, state, block, expect_exception=False): + """ + Processes everything up to the sync committee work, then runs the sync committee work in isolation, and + produces a pre-state and post-state (None if exception) specifically for sync-committee processing changes. + """ + pre_state = state.copy() + # process up to the sync committee work + call = run_block_processing_to(spec, state, block, 'process_sync_aggregate') + yield 'pre', state + yield 'sync_aggregate', block.body.sync_aggregate + if expect_exception: + expect_assertion_error(lambda: call(state, block)) + yield 'post', None + else: + call(state, block) + yield 'post', state + if expect_exception: + assert pre_state.balances == state.balances + else: + committee_indices = compute_committee_indices( + spec, + state, + state.current_sync_committee, + ) + committee_bits = block.body.sync_aggregate.sync_committee_bits + validate_sync_committee_rewards( + spec, + pre_state, + state, + committee_indices, + committee_bits, + block.proposer_index + ) + + +def _build_block_for_next_slot_with_sync_participation(spec, state, committee_indices, committee_bits): + block = build_empty_block_for_next_slot(spec, state) + block.body.sync_aggregate = spec.SyncAggregate( + sync_committee_bits=committee_bits, + sync_committee_signature=compute_aggregate_sync_committee_signature( + spec, + state, + block.slot - 1, + [index for index, bit in zip(committee_indices, committee_bits) if bit], + ) + ) + return block + + +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 5a17fa65b238eb1507f16a7d6db447075de1774d Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 17 Aug 2021 08:28:45 -0600 Subject: [PATCH 062/135] group test files into subdirectory --- .../test/altair/block_processing/sync_aggregate/__init__.py | 0 .../{ => sync_aggregate}/test_process_sync_aggregate.py | 0 .../{ => sync_aggregate}/test_process_sync_aggregate_random.py | 0 tests/generators/operations/main.py | 3 ++- 4 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/__init__.py rename tests/core/pyspec/eth2spec/test/altair/block_processing/{ => sync_aggregate}/test_process_sync_aggregate.py (100%) rename tests/core/pyspec/eth2spec/test/altair/block_processing/{ => sync_aggregate}/test_process_sync_aggregate_random.py (100%) diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/__init__.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/test_process_sync_aggregate.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/altair/block_processing/test_process_sync_aggregate.py rename to tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/test_process_sync_aggregate_random.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/altair/block_processing/test_process_sync_aggregate_random.py rename to tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py diff --git a/tests/generators/operations/main.py b/tests/generators/operations/main.py index 57fc6dd96..d2653d87d 100644 --- a/tests/generators/operations/main.py +++ b/tests/generators/operations/main.py @@ -12,8 +12,9 @@ if __name__ == "__main__": 'voluntary_exit', ]} altair_mods = { - **{key: 'eth2spec.test.altair.block_processing.test_process_' + key for key in [ + **{key: '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 5a918dbdf3dbb591b415903e297dcfa6863abba7 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 18 Aug 2021 08:55:22 -0600 Subject: [PATCH 063/135] Add test count to test gen diagnostics --- .../pyspec/eth2spec/gen_helpers/gen_base/gen_runner.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 2b02d1b5c..be5720265 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 @@ -96,6 +96,8 @@ 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)}") + generated_test_count = 0 + skipped_test_count = 0 for tprov in test_providers: # runs anything that we don't want to repeat for every test case. tprov.prepare() @@ -149,6 +151,7 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]): output_part("ssz", name, dump_ssz_fn(data, name, file_mode)) except SkippedTest as e: print(e) + skipped_test_count += 1 shutil.rmtree(case_dir) continue @@ -172,10 +175,13 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]): if not written_part: shutil.rmtree(case_dir) else: + generated_test_count += 1 # Only remove `INCOMPLETE` tag file os.remove(incomplete_tag_file) - print(f"completed {generator_name}") + summary_message = f"completed generation of {generator_name} with {generated_test_count} tests" + summary_message += f" ({skipped_test_count} skipped tests)" + print(summary_message) def dump_yaml_fn(data: Any, name: str, file_mode: str, yaml_encoder: YAML): From 4c1156d504279069799924dbbc79cf13d9454ae3 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 18 Aug 2021 17:11:38 -0600 Subject: [PATCH 064/135] rename eth1 and eth2 throughout specs and readme where reasonable --- specs/altair/beacon-chain.md | 2 +- specs/altair/bls.md | 2 +- specs/altair/fork.md | 4 +-- specs/altair/p2p-interface.md | 4 +-- specs/altair/sync-protocol.md | 8 ++--- specs/altair/validator.md | 4 +-- specs/custody_game/beacon-chain.md | 4 +-- specs/custody_game/validator.md | 6 ++-- specs/das/das-core.md | 2 +- specs/das/fork-choice.md | 4 +-- specs/das/p2p-interface.md | 2 +- specs/das/sampling.md | 2 +- specs/merge/beacon-chain.md | 2 +- specs/merge/fork-choice.md | 2 +- specs/merge/fork.md | 2 +- specs/merge/p2p-interface.md | 4 +-- specs/merge/validator.md | 2 +- specs/phase0/beacon-chain.md | 14 ++++---- specs/phase0/deposit-contract.md | 14 ++++---- specs/phase0/fork-choice.md | 6 ++-- specs/phase0/p2p-interface.md | 36 +++++++++---------- specs/phase0/validator.md | 20 +++++------ specs/phase0/weak-subjectivity.md | 8 ++--- specs/sharding/beacon-chain.md | 2 +- specs/sharding/p2p-interface.md | 2 +- tests/core/pyspec/README.md | 6 ++-- tests/core/pyspec/eth2spec/config/README.md | 4 +-- .../pyspec/eth2spec/gen_helpers/README.md | 2 +- tests/formats/README.md | 4 +-- tests/formats/ssz_static/README.md | 2 +- tests/generators/README.md | 10 +++--- tests/generators/shuffling/README.md | 4 +-- tests/generators/ssz_static/README.md | 2 +- 33 files changed, 96 insertions(+), 96 deletions(-) diff --git a/specs/altair/beacon-chain.md b/specs/altair/beacon-chain.md index 7c4d20fe5..dbd3e6368 100644 --- a/specs/altair/beacon-chain.md +++ b/specs/altair/beacon-chain.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 Altair Beacon chain changes +# Altair -- The Beacon Chain ## Table of contents diff --git a/specs/altair/bls.md b/specs/altair/bls.md index 06b0313a9..8492e95c3 100644 --- a/specs/altair/bls.md +++ b/specs/altair/bls.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 Altair BLS extensions +# Altair -- BLS extensions ## Table of contents diff --git a/specs/altair/fork.md b/specs/altair/fork.md index f7aa89c42..3c2ea843e 100644 --- a/specs/altair/fork.md +++ b/specs/altair/fork.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 Altair fork +# Altair -- Fork Logic **Notice**: This document is a work-in-progress for researchers and implementers. @@ -17,7 +17,7 @@ ## Introduction -This document describes the process of the first upgrade of Ethereum 2.0: the Altair hard fork, introducing light client support and other improvements. +This document describes the process of the first upgrade of the eacon chain: the Altair hard fork, introducing light client support and other improvements. ## Configuration diff --git a/specs/altair/p2p-interface.md b/specs/altair/p2p-interface.md index 6820fd10a..f2700b311 100644 --- a/specs/altair/p2p-interface.md +++ b/specs/altair/p2p-interface.md @@ -1,6 +1,6 @@ -# Ethereum Altair networking specification +# Altair -- Networking -This document contains the networking specification for Ethereum 2.0 clients added during the Altair deployment. +This document contains the networking specification for Altair. This document should be viewed as additive to the [document from Phase 0](../phase0/p2p-interface.md) and will be referred to as the "Phase 0 document" hereafter. Readers should understand the Phase 0 document and use it as a basis to understand the changes outlined in this document. diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index 119d0f998..24c35f891 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -1,4 +1,4 @@ -# Minimal Light Client +# Altair -- Minimal Light Client **Notice**: This document is a work-in-progress for researchers and implementers. @@ -28,8 +28,8 @@ ## Introduction -Eth2 is designed to be light client friendly for constrained environments to -access Eth2 with reasonable safety and liveness. +The beacon chain is designed to be light client friendly for constrained environments to +access Ethereum with reasonable safety and liveness. Such environments include resource-constrained devices (e.g. phones for trust-minimised wallets) and metered VMs (e.g. blockchain VMs for cross-chain bridges). @@ -184,7 +184,7 @@ def process_light_client_update(store: LightClientStore, update: LightClientUpda ): # Apply update if (1) 2/3 quorum is reached and (2) we have a finality proof. # Note that (2) means that the current light client design needs finality. - # It may be changed to re-organizable light client design. See the on-going issue eth2.0-specs#2182. + # It may be changed to re-organizable light client design. See the on-going issue consensus-specs#2182. apply_light_client_update(store.snapshot, update) store.valid_updates = set() elif current_slot > store.snapshot.header.slot + update_timeout: diff --git a/specs/altair/validator.md b/specs/altair/validator.md index badef2de5..81e6b8192 100644 --- a/specs/altair/validator.md +++ b/specs/altair/validator.md @@ -1,6 +1,6 @@ -# Ethereum 2.0 Altair -- Honest Validator +# Altair -- Honest Validator -This is an accompanying document to [Ethereum 2.0 Altair -- The Beacon Chain](./beacon-chain.md), which describes the expected actions of a "validator" participating in the Ethereum 2.0 protocol. +This is an accompanying document to [Altair -- The Beacon Chain](./beacon-chain.md), which describes the expected actions of a "validator" participating in the Ethereum 2.0 protocol. ## Table of contents diff --git a/specs/custody_game/beacon-chain.md b/specs/custody_game/beacon-chain.md index ad99527f7..6f9f61cf9 100644 --- a/specs/custody_game/beacon-chain.md +++ b/specs/custody_game/beacon-chain.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 Custody Game -- Beacon Chain +# Custody Game -- The Beacon Chain **Notice**: This document is a work-in-progress for researchers and implementers. @@ -57,7 +57,7 @@ ## Introduction -This document details the beacon chain additions and changes of Ethereum 2.0 to support the shard data custody game, +This document details the beacon chain additions and changes of to support the shard data custody game, building upon the [Sharding](../sharding/beacon-chain.md) specification. ## Constants diff --git a/specs/custody_game/validator.md b/specs/custody_game/validator.md index a495924c9..05ceb854d 100644 --- a/specs/custody_game/validator.md +++ b/specs/custody_game/validator.md @@ -1,8 +1,8 @@ -# Ethereum 2.0 Custody Game -- Honest Validator +# Custody Game -- Honest Validator **Notice**: This document is a work-in-progress for researchers and implementers. -This is an accompanying document to the [Ethereum 2.0 Custody Game](./), which describes the expected actions of a "validator" -participating in the Ethereum 2.0 Custody Game. +This is an accompanying document to the [Custody Game](./), which describes the expected actions of a "validator" +participating in the shard data Custody Game. ## Table of contents diff --git a/specs/das/das-core.md b/specs/das/das-core.md index ce39565a1..f683cbbe1 100644 --- a/specs/das/das-core.md +++ b/specs/das/das-core.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 Data Availability Sampling -- Core +# Data Availability Sampling -- Core **Notice**: This document is a work-in-progress for researchers and implementers. diff --git a/specs/das/fork-choice.md b/specs/das/fork-choice.md index ec9f3ab59..f8ee68eab 100644 --- a/specs/das/fork-choice.md +++ b/specs/das/fork-choice.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 Data Availability Sampling -- Fork Choice +# Data Availability Sampling -- Fork Choice **Notice**: This document is a work-in-progress for researchers and implementers. @@ -17,7 +17,7 @@ ## Introduction -This document is the beacon chain fork choice spec for Ethereum 2.0 Data Availability Sampling. The only change that we add from phase 0 is that we add a concept of "data dependencies"; +This document is the beacon chain fork choice spec for Data Availability Sampling. The only change that we add from phase 0 is that we add a concept of "data dependencies"; a block is only eligible for consideration in the fork choice after a data availability test has been successfully completed for all dependencies. The "root" of a shard block for data dependency purposes is considered to be a `DataCommitment` object, which is a pair of a Kate commitment and a length. diff --git a/specs/das/p2p-interface.md b/specs/das/p2p-interface.md index fcc7616da..b4a0a9d2c 100644 --- a/specs/das/p2p-interface.md +++ b/specs/das/p2p-interface.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 Data Availability Sampling -- Network specification +# Data Availability Sampling -- Networking **Notice**: This document is a work-in-progress for researchers and implementers. diff --git a/specs/das/sampling.md b/specs/das/sampling.md index aedcf5fd5..53685c650 100644 --- a/specs/das/sampling.md +++ b/specs/das/sampling.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 Data Availability Sampling +# Data Availability Sampling -- Sampling **Notice**: This document is a work-in-progress for researchers and implementers. diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 5defa6bcb..ed12d3eb6 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 The Merge +# The Merge -- The Beacon Chain **Notice**: This document is a work-in-progress for researchers and implementers. diff --git a/specs/merge/fork-choice.md b/specs/merge/fork-choice.md index e21b54a7e..900d8b331 100644 --- a/specs/merge/fork-choice.md +++ b/specs/merge/fork-choice.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 The Merge +# The Merge -- Fork Choice **Notice**: This document is a work-in-progress for researchers and implementers. diff --git a/specs/merge/fork.md b/specs/merge/fork.md index c89e00cf9..0a1271d82 100644 --- a/specs/merge/fork.md +++ b/specs/merge/fork.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 The Merge +# The Merge -- Fork Logic **Notice**: This document is a work-in-progress for researchers and implementers. diff --git a/specs/merge/p2p-interface.md b/specs/merge/p2p-interface.md index c79d14b05..85a96c31e 100644 --- a/specs/merge/p2p-interface.md +++ b/specs/merge/p2p-interface.md @@ -1,6 +1,6 @@ -# Ethereum Merge networking specification +# The Merge -- Networking -This document contains the networking specification for Ethereum 2.0 clients added during the Merge deployment. +This document contains the networking specification for the Merge. The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite. This document should be viewed as additive to the documents from [Phase 0](../phase0/p2p-interface.md) and from [Altair](../altair/p2p-interface.md) and will be referred to as the "Phase 0 document" and "Altair document" respectively, hereafter. diff --git a/specs/merge/validator.md b/specs/merge/validator.md index baf716760..84207e49c 100644 --- a/specs/merge/validator.md +++ b/specs/merge/validator.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 The Merge +# The Merge -- Honest Validator **Notice**: This document is a work-in-progress for researchers and implementers. diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 5ab9b75fd..e2e235acf 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 Phase 0 -- The Beacon Chain +# Phase 0 -- The Beacon Chain ## Table of contents @@ -140,10 +140,10 @@ ## Introduction -This document represents the specification for Phase 0 of Ethereum 2.0 -- The Beacon Chain. +This document represents the specification for Phase 0 -- The Beacon Chain. -At the core of Ethereum 2.0 is a system chain called the "beacon chain". The beacon chain stores and manages the registry of validators. In the initial deployment phases of Ethereum 2.0, the only mechanism to become a validator is to make a one-way ETH transaction to a deposit contract on Ethereum 1.0. Activation as a validator happens when Ethereum 1.0 deposit receipts are processed by the beacon chain, the activation balance is reached, and a queuing process is completed. Exit is either voluntary or done forcibly as a penalty for misbehavior. -The primary source of load on the beacon chain is "attestations". Attestations are simultaneously availability votes for a shard block (in a later Eth2 upgrade) and proof-of-stake votes for a beacon block (Phase 0). +At the core of Ethereum proof-of-stake is a system chain called the "beacon chain". The beacon chain stores and manages the registry of validators. In the initial deployment phases of proof-of-stake, the only mechanism to become a validator is to make a one-way ETH transaction to a deposit contract on the Ethereum proof-of-work chain. Activation as a validator happens when deposit receipts are processed by the beacon chain, the activation balance is reached, and a queuing process is completed. Exit is either voluntary or done forcibly as a penalty for misbehavior. +The primary source of load on the beacon chain is "attestations". Attestations are simultaneously availability votes for a shard block (in a later upgrade) and proof-of-stake votes for a beacon block (Phase 0). ## Notation @@ -1166,13 +1166,13 @@ def slash_validator(state: BeaconState, ## Genesis -Before the Ethereum 2.0 genesis has been triggered, and for every Ethereum 1.0 block, let `candidate_state = initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)` where: +Before the Ethereum beacon chain genesis has been triggered, and for every Ethereum proof-of-work block, let `candidate_state = initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)` where: -- `eth1_block_hash` is the hash of the Ethereum 1.0 block +- `eth1_block_hash` is the hash of the Ethereum proof-of-work block - `eth1_timestamp` is the Unix timestamp corresponding to `eth1_block_hash` - `deposits` is the sequence of all deposits, ordered chronologically, up to (and including) the block with hash `eth1_block_hash` -Eth1 blocks must only be considered once they are at least `SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE` seconds old (i.e. `eth1_timestamp + SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE <= current_unix_time`). Due to this constraint, if `GENESIS_DELAY < SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE`, then the `genesis_time` can happen before the time/state is first known. Values should be configured to avoid this case. +Proof-of-work blocks must only be considered once they are at least `SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE` seconds old (i.e. `eth1_timestamp + SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE <= current_unix_time`). Due to this constraint, if `GENESIS_DELAY < SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE`, then the `genesis_time` can happen before the time/state is first known. Values should be configured to avoid this case. ```python def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, diff --git a/specs/phase0/deposit-contract.md b/specs/phase0/deposit-contract.md index 02e762dae..51786129c 100644 --- a/specs/phase0/deposit-contract.md +++ b/specs/phase0/deposit-contract.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 Phase 0 -- Deposit Contract +# Phase 0 -- Deposit Contract ## Table of contents @@ -8,7 +8,7 @@ - [Introduction](#introduction) - [Constants](#constants) - [Configuration](#configuration) -- [Ethereum 1.0 deposit contract](#ethereum-10-deposit-contract) +- [Staking deposit contract](#staking-deposit-contract) - [`deposit` function](#deposit-function) - [Deposit amount](#deposit-amount) - [Withdrawal credentials](#withdrawal-credentials) @@ -20,7 +20,7 @@ ## Introduction -This document represents the specification for the beacon chain deposit contract, part of Ethereum 2.0 Phase 0. +This document represents the specification for the beacon chain deposit contract, part of Phase 0. ## Constants @@ -42,9 +42,9 @@ These configurations are updated for releases and may be out of sync during `dev | `DEPOSIT_NETWORK_ID` | `1` | | `DEPOSIT_CONTRACT_ADDRESS` | `0x00000000219ab540356cBB839Cbe05303d7705Fa` | -## Ethereum 1.0 deposit contract +## Staking deposit contract -The initial deployment phases of Ethereum 2.0 are implemented without consensus changes to Ethereum 1.0. A deposit contract at address `DEPOSIT_CONTRACT_ADDRESS` is added to the Ethereum 1.0 chain defined by the [chain-id](https://eips.ethereum.org/EIPS/eip-155) -- `DEPOSIT_CHAIN_ID` -- and the network-id -- `DEPOSIT_NETWORK_ID` -- for deposits of ETH to the beacon chain. Validator balances will be withdrawable to the shards in Phase 2. +The initial deployment phases of Ethereum proof-of-stake are implemented without consensus changes to the existing Ethereum proof-of-work chain. A deposit contract at address `DEPOSIT_CONTRACT_ADDRESS` is added to the Ethereum proof-of-work chain defined by the [chain-id](https://eips.ethereum.org/EIPS/eip-155) -- `DEPOSIT_CHAIN_ID` -- and the network-id -- `DEPOSIT_NETWORK_ID` -- for deposits of ETH to the beacon chain. Validator balances will be withdrawable to the execution-layer in a followup fork after the Merge. _Note_: See [here](https://chainid.network/) for a comprehensive list of public Ethereum chain chain-id's and network-id's. @@ -54,7 +54,7 @@ The deposit contract has a public `deposit` function to make deposits. It takes #### Deposit amount -The amount of ETH (rounded down to the closest Gwei) sent to the deposit contract is the deposit amount, which must be of size at least `MIN_DEPOSIT_AMOUNT` Gwei. Note that ETH consumed by the deposit contract is no longer usable on Ethereum 1.0. +The amount of ETH (rounded down to the closest Gwei) sent to the deposit contract is the deposit amount, which must be of size at least `MIN_DEPOSIT_AMOUNT` Gwei. Note that ETH consumed by the deposit contract is no longer usable on the execution-layer until sometime after the Merge. #### Withdrawal credentials @@ -68,7 +68,7 @@ Support for new withdrawal prefixes can be added without modifying the deposit c #### `DepositEvent` log -Every Ethereum 1.0 deposit emits a `DepositEvent` log for consumption by the beacon chain. The deposit contract does little validation, pushing most of the validator onboarding logic to the beacon chain. In particular, the proof of possession (a BLS12-381 signature) is not verified by the deposit contract. +Every deposit emits a `DepositEvent` log for consumption by the beacon chain. The deposit contract does little validation, pushing most of the validator onboarding logic to the beacon chain. In particular, the proof of possession (a BLS12-381 signature) is not verified by the deposit contract. ## Solidity code diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 181a874fb..7d382a9ab 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 Phase 0 -- Beacon Chain Fork Choice +# Phase 0 -- Beacon Chain Fork Choice ## Table of contents @@ -35,7 +35,7 @@ ## Introduction -This document is the beacon chain fork choice spec, part of Ethereum 2.0 Phase 0. It assumes the [beacon chain state transition function spec](./beacon-chain.md). +This document is the beacon chain fork choice spec, part of Phase 0. It assumes the [beacon chain state transition function spec](./beacon-chain.md). ## Fork choice @@ -51,7 +51,7 @@ Any of the above handlers that trigger an unhandled exception (e.g. a failed ass 1) **Leap seconds**: Slots will last `SECONDS_PER_SLOT + 1` or `SECONDS_PER_SLOT - 1` seconds around leap seconds. This is automatically handled by [UNIX time](https://en.wikipedia.org/wiki/Unix_time). 2) **Honest clocks**: Honest nodes are assumed to have clocks synchronized within `SECONDS_PER_SLOT` seconds of each other. -3) **Eth1 data**: The large `ETH1_FOLLOW_DISTANCE` specified in the [honest validator document](./validator.md) should ensure that `state.latest_eth1_data` of the canonical Ethereum 2.0 chain remains consistent with the canonical Ethereum 1.0 chain. If not, emergency manual intervention will be required. +3) **Eth1 data**: The large `ETH1_FOLLOW_DISTANCE` specified in the [honest validator document](./validator.md) should ensure that `state.latest_eth1_data` of the canonical beacon chain remains consistent with the canonical Ethereum proof-of-work chain. If not, emergency manual intervention will be required. 4) **Manual forks**: Manual forks may arbitrarily change the fork choice rule but are expected to be enacted at epoch transitions, with the fork details reflected in `state.fork`. 5) **Implementation**: The implementation found in this specification is constructed for ease of understanding rather than for optimization in computation, space, or any other resource. A number of optimized alternatives can be found [here](https://github.com/protolambda/lmd-ghost). diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 29fea5713..2bb97fabe 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -1,13 +1,13 @@ -# Ethereum 2.0 networking specification +# Phase 0 -- Networking -This document contains the networking specification for Ethereum 2.0 clients. +This document contains the networking specification for Phase 0. It consists of four main sections: 1. A specification of the network fundamentals. -2. A specification of the three network interaction *domains* of Eth2: (a) the gossip domain, (b) the discovery domain, and (c) the Req/Resp domain. +2. A specification of the three network interaction *domains* of the proof-of-stake consensus layer: (a) the gossip domain, (b) the discovery domain, and (c) the Req/Resp domain. 3. The rationale and further explanation for the design choices made in the previous two sections. -4. An analysis of the maturity/state of the libp2p features required by this spec across the languages in which Eth2 clients are being developed. +4. An analysis of the maturity/state of the libp2p features required by this spec across the languages in which clients are being developed. ## Table of contents @@ -19,7 +19,7 @@ It consists of four main sections: - [Encryption and identification](#encryption-and-identification) - [Protocol Negotiation](#protocol-negotiation) - [Multiplexing](#multiplexing) -- [Eth2 network interaction domains](#eth2-network-interaction-domains) +- [Consensus layer network interaction domains](#consensus-layer-network-interaction-domains) - [Configuration](#configuration) - [MetaData](#metadata) - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) @@ -113,7 +113,7 @@ It consists of four main sections: # Network fundamentals -This section outlines the specification for the networking stack in Ethereum 2.0 clients. +This section outlines the specification for the networking stack in Ethereum consensus layer clients. ## Transport @@ -163,7 +163,7 @@ and MAY support [yamux](https://github.com/hashicorp/yamux/blob/master/spec.md). If both are supported by the client, yamux MUST take precedence during negotiation. See the [Rationale](#design-decision-rationale) section below for tradeoffs. -# Eth2 network interaction domains +# Consensus layer network interaction domains ## Configuration @@ -435,7 +435,7 @@ The following validations MUST pass before forwarding the `attestation` on the s Attestation broadcasting is grouped into subnets defined by a topic. The number of subnets is defined via `ATTESTATION_SUBNET_COUNT`. The correct subnet for an attestation can be calculated with `compute_subnet_for_attestation`. -`beacon_attestation_{subnet_id}` topics, are rotated through throughout the epoch in a similar fashion to rotating through shards in committees (future Eth2 upgrade). +`beacon_attestation_{subnet_id}` topics, are rotated through throughout the epoch in a similar fashion to rotating through shards in committees (future beacon chain upgrade). The subnets are rotated through with `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)` subnets per slot. Unaggregated attestations are sent as `Attestation`s to the subnet topic, @@ -933,7 +933,7 @@ If a node's `MetaData.attnets` is composed of all zeros, the ENR MAY optionally #### `eth2` field ENRs MUST carry a generic `eth2` key with an 16-byte value of the node's current fork digest, next fork version, -and next fork epoch to ensure connections are made with peers on the intended eth2 network. +and next fork epoch to ensure connections are made with peers on the intended Ethereum network. | Key | Value | |:-------------|:--------------------| @@ -998,7 +998,7 @@ The libp2p QUIC transport inherently relies on TLS 1.3 per requirement in sectio of the [QUIC protocol specification](https://tools.ietf.org/html/draft-ietf-quic-transport-22#section-7) and the accompanying [QUIC-TLS document](https://tools.ietf.org/html/draft-ietf-quic-tls-22). -The usage of one handshake procedure or the other shall be transparent to the Eth2 application layer, +The usage of one handshake procedure or the other shall be transparent to the application layer, once the libp2p Host/Node object has been configured appropriately. ### What are the advantages of using TCP/QUIC/Websockets? @@ -1019,7 +1019,7 @@ Provided that we use the same port numbers and encryption mechanisms as HTTP/3, and we may only become subject to standard IP-based firewall filtering—something we can counteract via other mechanisms. WebSockets and/or WebRTC transports are necessary for interaction with browsers, -and will become increasingly important as we incorporate browser-based light clients to the Eth2 network. +and will become increasingly important as we incorporate browser-based light clients to the Ethereum network. ### Why do we not just support a single transport? @@ -1186,7 +1186,7 @@ Enforcing hashes for topic names would preclude us from leveraging such features No security or privacy guarantees are lost as a result of choosing plaintext topic names, since the domain is finite anyway, and calculating a digest's preimage would be trivial. -Furthermore, the Eth2 topic names are shorter than their digest equivalents (assuming SHA-256 hash), +Furthermore, the topic names are shorter than their digest equivalents (assuming SHA-256 hash), so hashing topics would bloat messages unnecessarily. ### Why are we using the `StrictNoSign` signature policy? @@ -1211,7 +1211,7 @@ Some examples of where messages could be duplicated: ### Why are these specific gossip parameters chosen? - `D`, `D_low`, `D_high`, `D_lazy`: recommended defaults. -- `heartbeat_interval`: 0.7 seconds, recommended for eth2 in the [GossipSub evaluation report by Protocol Labs](https://gateway.ipfs.io/ipfs/QmRAFP5DBnvNjdYSbWhEhVRJJDFCLpPyvew5GwCCB4VxM4). +- `heartbeat_interval`: 0.7 seconds, recommended for the beacon chain in the [GossipSub evaluation report by Protocol Labs](https://gateway.ipfs.io/ipfs/QmRAFP5DBnvNjdYSbWhEhVRJJDFCLpPyvew5GwCCB4VxM4). - `fanout_ttl`: 60 seconds, recommended default. Fanout is primarily used by committees publishing attestations to subnets. This happens once per epoch per validator and the subnet changes each epoch @@ -1285,7 +1285,7 @@ due to not being fully synced to ensure that such (amplified) DOS attacks are no In Phase 0, peers for attestation subnets will be found using the `attnets` entry in the ENR. -Although this method will be sufficient for early phases of Eth2, we aim to use the more appropriate discv5 topics for this and other similar tasks in the future. +Although this method will be sufficient for early upgrade of the beacon chain, we aim to use the more appropriate discv5 topics for this and other similar tasks in the future. ENRs should ultimately not be used for this purpose. They are best suited to store identity, location, and capability information, rather than more volatile advertisements. @@ -1319,7 +1319,7 @@ Requests are segregated by protocol ID to: 4. Enable flexibility and agility for clients adopting spec changes that impact the request, by signalling to peers exactly which subset of new/old requests they support. 5. Enable clients to explicitly choose backwards compatibility at the request granularity. Without this, clients would be forced to support entire versions of the coarser request protocol. -6. Parallelise RFCs (or Eth2 EIPs). +6. Parallelise RFCs (or EIPs). By decoupling requests from one another, each RFC that affects the request protocol can be deployed/tested/debated independently without relying on a synchronization point to version the general top-level protocol. 1. This has the benefit that clients can explicitly choose which RFCs to deploy @@ -1506,13 +1506,13 @@ discv5 uses ENRs and we will presumably need to: ### Why do we not form ENRs and find peers until genesis block/state is known? -Although client software might very well be running locally prior to the solidification of the eth2 genesis state and block, +Although client software might very well be running locally prior to the solidification of the beacon chain genesis state and block, clients cannot form valid ENRs prior to this point. ENRs contain `fork_digest` which utilizes the `genesis_validators_root` for a cleaner separation between chains so prior to knowing genesis, we cannot use `fork_digest` to cleanly find peers on our intended chain. Once genesis data is known, we can then form ENRs and safely find peers. -When using an eth1 deposit contract for deposits, `fork_digest` will be known `GENESIS_DELAY` (7 days in mainnet configuration) before `genesis_time`, +When using a proof-of-work deposit contract for deposits, `fork_digest` will be known `GENESIS_DELAY` (7 days in mainnet configuration) before `genesis_time`, providing ample time to find peers and form initial connections and gossip subnets prior to genesis. ## Compression/Encoding @@ -1586,4 +1586,4 @@ It is advisable to derive these lengths from the SSZ type definitions in use, to # libp2p implementations matrix This section will soon contain a matrix showing the maturity/state of the libp2p features required -by this spec across the languages in which Eth2 clients are being developed. +by this spec across the languages in which clients are being developed. diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index a548003e1..814859c12 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -1,6 +1,6 @@ -# Ethereum 2.0 Phase 0 -- Honest Validator +# Phase 0 -- Honest Validator -This is an accompanying document to [Ethereum 2.0 Phase 0 -- The Beacon Chain](./beacon-chain.md), which describes the expected actions of a "validator" participating in the Ethereum 2.0 protocol. +This is an accompanying document to [Phase 0 -- The Beacon Chain](./beacon-chain.md), which describes the expected actions of a "validator" participating in the Ethereum proof-of-stake protocol. ## Table of contents @@ -74,9 +74,9 @@ This is an accompanying document to [Ethereum 2.0 Phase 0 -- The Beacon Chain](. ## Introduction -This document represents the expected behavior of an "honest validator" with respect to Phase 0 of the Ethereum 2.0 protocol. This document does not distinguish between a "node" (i.e. the functionality of following and reading the beacon chain) and a "validator client" (i.e. the functionality of actively participating in consensus). The separation of concerns between these (potentially) two pieces of software is left as a design decision that is out of scope. +This document represents the expected behavior of an "honest validator" with respect to Phase 0 of the Ethereum proof-of-stake protocol. This document does not distinguish between a "node" (i.e. the functionality of following and reading the beacon chain) and a "validator client" (i.e. the functionality of actively participating in consensus). The separation of concerns between these (potentially) two pieces of software is left as a design decision that is out of scope. -A validator is an entity that participates in the consensus of the Ethereum 2.0 protocol. This is an optional role for users in which they can post ETH as collateral and verify and attest to the validity of blocks to seek financial returns in exchange for building and securing the protocol. This is similar to proof-of-work networks in which miners provide collateral in the form of hardware/hash-power to seek returns in exchange for building and securing the protocol. +A validator is an entity that participates in the consensus of the Ethereum proof-of-stake protocol. This is an optional role for users in which they can post ETH as collateral and verify and attest to the validity of blocks to seek financial returns in exchange for building and securing the protocol. This is similar to proof-of-work networks in which miners provide collateral in the form of hardware/hash-power to seek returns in exchange for building and securing the protocol. ## Prerequisites @@ -162,7 +162,7 @@ The `withdrawal_credentials` field must be such that: * `withdrawal_credentials[1:12] == b'\x00' * 11` * `withdrawal_credentials[12:] == eth1_withdrawal_address` -After the merge of the current Ethereum application layer (Eth1) into the Beacon Chain (Eth2), +After the merge of the current Ethereum application layer into the Beacon Chain, withdrawals to `eth1_withdrawal_address` will be normal ETH transfers (with no payload other than the validator's ETH) triggered by a user transaction that will set the gas price and gas limit as well pay fees. As long as the account or contract with address `eth1_withdrawal_address` can receive ETH transfers, @@ -170,7 +170,7 @@ the future withdrawal protocol is agnostic to all other implementation details. ### Submit deposit -In Phase 0, all incoming validator deposits originate from the Ethereum 1.0 chain defined by `DEPOSIT_CHAIN_ID` and `DEPOSIT_NETWORK_ID`. Deposits are made to the [deposit contract](./deposit-contract.md) located at `DEPOSIT_CONTRACT_ADDRESS`. +In Phase 0, all incoming validator deposits originate from the Ethereum proof-of-work chain defined by `DEPOSIT_CHAIN_ID` and `DEPOSIT_NETWORK_ID`. Deposits are made to the [deposit contract](./deposit-contract.md) located at `DEPOSIT_CONTRACT_ADDRESS`. To submit a deposit: @@ -182,13 +182,13 @@ To submit a deposit: - Let `deposit_message` be a `DepositMessage` with all the `DepositData` contents except the `signature`. - Let `signature` be the result of `bls.Sign` of the `compute_signing_root(deposit_message, domain)` with `domain=compute_domain(DOMAIN_DEPOSIT)`. (_Warning_: Deposits _must_ be signed with `GENESIS_FORK_VERSION`, calling `compute_domain` without a second argument defaults to the correct version). - Let `deposit_data_root` be `hash_tree_root(deposit_data)`. -- Send a transaction on the Ethereum 1.0 chain to `DEPOSIT_CONTRACT_ADDRESS` executing `def deposit(pubkey: bytes[48], withdrawal_credentials: bytes[32], signature: bytes[96], deposit_data_root: bytes32)` along with a deposit of `amount` Gwei. +- Send a transaction on the Ethereum proof-of-work chain to `DEPOSIT_CONTRACT_ADDRESS` executing `def deposit(pubkey: bytes[48], withdrawal_credentials: bytes[32], signature: bytes[96], deposit_data_root: bytes32)` along with a deposit of `amount` Gwei. *Note*: Deposits made for the same `pubkey` are treated as for the same validator. A singular `Validator` will be added to `state.validators` with each additional deposit amount added to the validator's balance. A validator can only be activated when total deposits for the validator pubkey meet or exceed `MAX_EFFECTIVE_BALANCE`. ### Process deposit -Deposits cannot be processed into the beacon chain until the Eth1 block in which they were deposited or any of its descendants is added to the beacon chain `state.eth1_data`. This takes _a minimum_ of `ETH1_FOLLOW_DISTANCE` Eth1 blocks (~8 hours) plus `EPOCHS_PER_ETH1_VOTING_PERIOD` epochs (~6.8 hours). Once the requisite Eth1 data is added, the deposit will normally be added to a beacon chain block and processed into the `state.validators` within an epoch or two. The validator is then in a queue to be activated. +Deposits cannot be processed into the beacon chain until the proof-of-work block in which they were deposited or any of its descendants is added to the beacon chain `state.eth1_data`. This takes _a minimum_ of `ETH1_FOLLOW_DISTANCE` Eth1 blocks (~8 hours) plus `EPOCHS_PER_ETH1_VOTING_PERIOD` epochs (~6.8 hours). Once the requisite proof-of-work block data is added, the deposit will normally be added to a beacon chain block and processed into the `state.validators` within an epoch or two. The validator is then in a queue to be activated. ### Validator index @@ -401,9 +401,9 @@ Up to `MAX_ATTESTATIONS`, aggregate attestations can be included in the `block`. ##### Deposits -If there are any unprocessed deposits for the existing `state.eth1_data` (i.e. `state.eth1_data.deposit_count > state.eth1_deposit_index`), then pending deposits _must_ be added to the block. The expected number of deposits is exactly `min(MAX_DEPOSITS, eth1_data.deposit_count - state.eth1_deposit_index)`. These [`deposits`](./beacon-chain.md#deposit) are constructed from the `Deposit` logs from the [Eth1 deposit contract](./deposit-contract.md) and must be processed in sequential order. The deposits included in the `block` must satisfy the verification conditions found in [deposits processing](./beacon-chain.md#deposits). +If there are any unprocessed deposits for the existing `state.eth1_data` (i.e. `state.eth1_data.deposit_count > state.eth1_deposit_index`), then pending deposits _must_ be added to the block. The expected number of deposits is exactly `min(MAX_DEPOSITS, eth1_data.deposit_count - state.eth1_deposit_index)`. These [`deposits`](./beacon-chain.md#deposit) are constructed from the `Deposit` logs from the [deposit contract](./deposit-contract.md) and must be processed in sequential order. The deposits included in the `block` must satisfy the verification conditions found in [deposits processing](./beacon-chain.md#deposits). -The `proof` for each deposit must be constructed against the deposit root contained in `state.eth1_data` rather than the deposit root at the time the deposit was initially logged from the 1.0 chain. This entails storing a full deposit merkle tree locally and computing updated proofs against the `eth1_data.deposit_root` as needed. See [`minimal_merkle.py`](https://github.com/ethereum/research/blob/master/spec_pythonizer/utils/merkle_minimal.py) for a sample implementation. +The `proof` for each deposit must be constructed against the deposit root contained in `state.eth1_data` rather than the deposit root at the time the deposit was initially logged from the proof-of-work chain. This entails storing a full deposit merkle tree locally and computing updated proofs against the `eth1_data.deposit_root` as needed. See [`minimal_merkle.py`](https://github.com/ethereum/research/blob/master/spec_pythonizer/utils/merkle_minimal.py) for a sample implementation. ##### Voluntary exits diff --git a/specs/phase0/weak-subjectivity.md b/specs/phase0/weak-subjectivity.md index 3748a7391..52953c34d 100644 --- a/specs/phase0/weak-subjectivity.md +++ b/specs/phase0/weak-subjectivity.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 Phase 0 -- Weak Subjectivity Guide +# Phase 0 -- Weak Subjectivity Guide ## Table of contents @@ -26,11 +26,11 @@ ## Introduction -This document is a guide for implementing the Weak Subjectivity protections in Phase 0 of Ethereum 2.0. +This document is a guide for implementing the Weak Subjectivity protections in Phase 0. This document is still a work-in-progress, and is subject to large changes. For more information about weak subjectivity and why it is required, please refer to: -- [Weak Subjectivity in Eth2.0](https://notes.ethereum.org/@adiasg/weak-subjectvity-eth2) +- [Weak Subjectivity in Ethereum Proof-of-Stake](https://notes.ethereum.org/@adiasg/weak-subjectvity-eth2) - [Proof of Stake: How I Learned to Love Weak Subjectivity](https://blog.ethereum.org/2014/11/25/proof-stake-learned-love-weak-subjectivity/) ## Prerequisites @@ -77,7 +77,7 @@ a safety margin of at least `1/3 - SAFETY_DECAY/100`. A detailed analysis of the calculation of the weak subjectivity period is made in [this report](https://github.com/runtimeverification/beacon-chain-verification/blob/master/weak-subjectivity/weak-subjectivity-analysis.pdf). -*Note*: The expressions in the report use fractions, whereas eth2.0-specs uses only `uint64` arithmetic. The expressions have been simplified to avoid computing fractions, and more details can be found [here](https://www.overleaf.com/read/wgjzjdjpvpsd). +*Note*: The expressions in the report use fractions, whereas the consensus-specs only use `uint64` arithmetic. The expressions have been simplified to avoid computing fractions, and more details can be found [here](https://www.overleaf.com/read/wgjzjdjpvpsd). *Note*: The calculations here use `Ether` instead of `Gwei`, because the large magnitude of balances in `Gwei` can cause an overflow while computing using `uint64` arithmetic operations. Using `Ether` reduces the magnitude of the multiplicative factors by an order of `ETH_TO_GWEI` (`= 10**9`) and avoid the scope for overflows in `uint64`. diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 9269d1461..2e2c3d487 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 Sharding -- Beacon Chain changes +# Sharding -- The Beacon Chain **Notice**: This document is a work-in-progress for researchers and implementers. diff --git a/specs/sharding/p2p-interface.md b/specs/sharding/p2p-interface.md index 93ff1e26d..c47d2ee07 100644 --- a/specs/sharding/p2p-interface.md +++ b/specs/sharding/p2p-interface.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 Sharding -- Network specification +# Sharding -- Networking **Notice**: This document is a work-in-progress for researchers and implementers. diff --git a/tests/core/pyspec/README.md b/tests/core/pyspec/README.md index 4a4e20854..baa132277 100644 --- a/tests/core/pyspec/README.md +++ b/tests/core/pyspec/README.md @@ -1,6 +1,6 @@ -# Eth2 Executable Python Spec (PySpec) +# Executable Python Spec (PySpec) -The executable Python spec is built from the Eth2 specification, +The executable Python spec is built from the consensus specifications, complemented with the necessary helper functions for hashing, BLS, and more. With this executable spec, @@ -27,7 +27,7 @@ to enable debuggers to navigate between packages and generated code, without fra By default, when installing the `eth2spec` as package in non-develop mode, the distutils implementation of the `setup` runs `build`, which is extended to run the same `pyspec` work, but outputs into the standard `./build/lib` output. -This enables the `eth2.0-specs` repository to be installed like any other python package. +This enables the `consensus-specs` repository to be installed like any other python package. ## Py-tests diff --git a/tests/core/pyspec/eth2spec/config/README.md b/tests/core/pyspec/eth2spec/config/README.md index 5a64d39ba..c03d890c2 100644 --- a/tests/core/pyspec/eth2spec/config/README.md +++ b/tests/core/pyspec/eth2spec/config/README.md @@ -1,4 +1,4 @@ -# Eth2 config util +# Consensus specs config util For run-time configuration, see [Configs documentation](../../../../../configs/README.md). @@ -13,7 +13,7 @@ from eth2spec.phase0 import mainnet as spec from pathlib import Path # To load the default configurations -config_util.load_defaults(Path("eth2.0-specs/configs")) # change path to point to equivalent of specs `configs` dir. +config_util.load_defaults(Path("consensus-specs/configs")) # change path to point to equivalent of specs `configs` dir. # After loading the defaults, a config can be chosen: 'mainnet', 'minimal', or custom network config (by file path) spec.config = spec.Configuration(**config_util.load_config_file(Path('mytestnet.yaml'))) ``` diff --git a/tests/core/pyspec/eth2spec/gen_helpers/README.md b/tests/core/pyspec/eth2spec/gen_helpers/README.md index 5bd76b99f..bf791ccfe 100644 --- a/tests/core/pyspec/eth2spec/gen_helpers/README.md +++ b/tests/core/pyspec/eth2spec/gen_helpers/README.md @@ -1,4 +1,4 @@ -# Eth2 test generator helpers +# Consensus test generator helpers ## `gen_base` diff --git a/tests/formats/README.md b/tests/formats/README.md index 39968832f..e4f2bcb29 100644 --- a/tests/formats/README.md +++ b/tests/formats/README.md @@ -1,6 +1,6 @@ # General test format -This document defines the YAML format and structure used for Eth2 testing. +This document defines the YAML format and structure used for consensus spec testing. ## Table of contents @@ -151,7 +151,7 @@ Between all types of tests, a few formats are common: - **`.yaml`**: A YAML file containing structured data to describe settings or test contents. - **`.ssz`**: A file containing raw SSZ-encoded data. Previously widely used in tests, but replaced with compressed variant. - **`.ssz_snappy`**: Like `.ssz`, but compressed with Snappy block compression. - Snappy block compression is already applied to SSZ in Eth2 gossip, available in client implementations, and thus chosen as compression method. + Snappy block compression is already applied to SSZ in consensus-layer gossip, available in client implementations, and thus chosen as compression method. #### Special output parts diff --git a/tests/formats/ssz_static/README.md b/tests/formats/ssz_static/README.md index 1ac91cb13..ffa737334 100644 --- a/tests/formats/ssz_static/README.md +++ b/tests/formats/ssz_static/README.md @@ -1,7 +1,7 @@ # SSZ, static tests This set of test-suites provides static testing for SSZ: - to instantiate just the known Eth2 SSZ types from binary data. + to instantiate just the known Ethereum SSZ types from binary data. This series of tests is based on the spec-maintained `eth2spec/utils/ssz/ssz_impl.py`, i.e. fully consistent with the SSZ spec. diff --git a/tests/generators/README.md b/tests/generators/README.md index 731184326..a84331dcb 100644 --- a/tests/generators/README.md +++ b/tests/generators/README.md @@ -1,9 +1,9 @@ -# Eth2 test generators +# Consensus test generators -This directory contains all the generators for tests, consumed by Eth2 client implementations. +This directory contains all the generators for tests, consumed by consensus-layer client implementations. Any issues with the generators and/or generated tests should be filed in the repository that hosts the generator outputs, - here: [ethereum/eth2.0-spec-tests](https://github.com/ethereum/eth2.0-spec-tests). + here: [ethereum/consensus-spec-tests](https://github.com/ethereum/consensus-spec-tests). On releases, test generators are run by the release manager. Test-generation of mainnet tests can take a significant amount of time, and is better left out of a CI setup. @@ -36,7 +36,7 @@ Prerequisites: ### Cleaning -This removes the existing virtual environments (`/tests/generators//venv`) and generated tests (`../eth2.0-spec-tests/tests`). +This removes the existing virtual environments (`/tests/generators//venv`) and generated tests (`../consensus-spec-tests/tests`). ```bash make clean @@ -226,5 +226,5 @@ Do note that generators should be easy to maintain, lean, and based on the spec. If a test generator is not needed anymore, undo the steps described above and make a new release: 1. Remove the generator directory. -2. Remove the generated tests in the [`eth2.0-spec-tests`](https://github.com/ethereum/eth2.0-spec-tests) repository by opening a pull request there. +2. Remove the generated tests in the [`consensus-spec-tests`](https://github.com/ethereum/consensus-spec-tests) repository by opening a pull request there. 3. Make a new release. diff --git a/tests/generators/shuffling/README.md b/tests/generators/shuffling/README.md index fae06ad7e..81ddaba15 100644 --- a/tests/generators/shuffling/README.md +++ b/tests/generators/shuffling/README.md @@ -1,10 +1,10 @@ # Shuffling Tests -Tests for the swap-or-not shuffling in Eth2. +Tests for the swap-or-not shuffling in the beacon chain. Tips for initial shuffling write: - run with `round_count = 1` first, do the same with pyspec. - start with permute index - optimized shuffling implementations: - - vitalik, Python: https://github.com/ethereum/eth2.0-specs/pull/576#issue-250741806 + - vitalik, Python: https://github.com/ethereum/consensus-specs/pull/576#issue-250741806 - protolambda, Go: https://github.com/protolambda/eth2-shuffle diff --git a/tests/generators/ssz_static/README.md b/tests/generators/ssz_static/README.md index 160d1ebb4..3434fe174 100644 --- a/tests/generators/ssz_static/README.md +++ b/tests/generators/ssz_static/README.md @@ -1,6 +1,6 @@ # SSZ-static The purpose of this test-generator is to provide test-vectors for the most important applications of SSZ: - the serialization and hashing of Eth2 data types. + the serialization and hashing of Ethereum data type. Test-format documentation can be found [here](../../formats/ssz_static/README.md). From 3be3643bcb7ed9131feecac87c634776b5fce5ae Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 19 Aug 2021 12:41:51 +0800 Subject: [PATCH 065/135] Rename `eth2.0-spec-tests` to `consensus-spec-tests` --- .gitignore | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 788410645..76fe21ddd 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ build/ output/ dist/ -eth2.0-spec-tests/ +consensus-spec-tests/ .pytest_cache .mypy_cache diff --git a/Makefile b/Makefile index d637d26cf..9f32b4568 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ TEST_GENERATORS_DIR = ./tests/generators PY_SPEC_DIR = $(TEST_LIBS_DIR)/pyspec ETH2SPEC_MODULE_DIR = $(PY_SPEC_DIR)/eth2spec TEST_REPORT_DIR = $(PY_SPEC_DIR)/test-reports -TEST_VECTOR_DIR = ../eth2.0-spec-tests/tests +TEST_VECTOR_DIR = ../consensus-spec-tests/tests GENERATOR_DIR = ./tests/generators SOLIDITY_DEPOSIT_CONTRACT_DIR = ./solidity_deposit_contract SOLIDITY_DEPOSIT_CONTRACT_SOURCE = ${SOLIDITY_DEPOSIT_CONTRACT_DIR}/deposit_contract.sol From 7f70a7704b022857ca05a6d084c6f0c0f5c8bd60 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 18 Aug 2021 15:43:33 -0600 Subject: [PATCH 066/135] fix validator state during deposit randomization --- tests/core/pyspec/eth2spec/test/helpers/deposits.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/helpers/deposits.py b/tests/core/pyspec/eth2spec/test/helpers/deposits.py index 48057a1d9..4e2621522 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/deposits.py +++ b/tests/core/pyspec/eth2spec/test/helpers/deposits.py @@ -1,5 +1,6 @@ from random import Random +from eth2spec.test.context import is_post_altair from eth2spec.test.helpers.keys import pubkeys, privkeys from eth2spec.utils import bls from eth2spec.utils.merkle_minimal import calc_merkle_tree_from_leaves, get_merkle_proof @@ -15,6 +16,8 @@ def mock_deposit(spec, state, index): state.validators[index].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH state.validators[index].activation_epoch = spec.FAR_FUTURE_EPOCH state.validators[index].effective_balance = spec.MAX_EFFECTIVE_BALANCE + if is_post_altair(spec): + state.inactivity_scores[index] = 0 assert not spec.is_active_validator(state.validators[index], spec.get_current_epoch(state)) From b809d867369929f5015e31f6f698de73d6e43a43 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 18 Aug 2021 15:45:27 -0600 Subject: [PATCH 067/135] add test for `process_inactivity_updates` for randomized state --- .../test_process_inactivity_updates.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py index 9bc0f4841..2a971f4f0 100644 --- a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py +++ b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py @@ -18,6 +18,7 @@ from eth2spec.test.helpers.epoch_processing import ( from eth2spec.test.helpers.random import ( randomize_attestation_participation, randomize_previous_epoch_participation, + randomize_state, ) from eth2spec.test.helpers.rewards import leaking @@ -321,3 +322,58 @@ def test_some_exited_full_random_leaking(spec, state): # Check still in leak assert spec.is_in_inactivity_leak(state) + + +def _run_randomized_state_test_for_inactivity_updates(spec, state, rng=Random(13377331)): + randomize_inactivity_scores(spec, state, rng=rng) + randomize_state(spec, state, rng=rng) + + exited_validators = get_exited_validators(spec, state) + exited_but_not_slashed = [] + for index in exited_validators: + validator = state.validators[index] + if validator.slashed: + continue + exited_but_not_slashed.append(index) + + assert len(exited_but_not_slashed) > 0 + + some_exited_validator = exited_but_not_slashed[0] + + pre_score_for_exited_validator = state.inactivity_scores[some_exited_validator] + + assert pre_score_for_exited_validator != 0 + + assert len(set(state.inactivity_scores)) > 1 + + yield from run_inactivity_scores_test(spec, state) + + post_score_for_exited_validator = state.inactivity_scores[some_exited_validator] + assert pre_score_for_exited_validator == post_score_for_exited_validator + + +@with_altair_and_later +@spec_state_test +def test_randomized_state(spec, state): + """ + This test ensures that once a validator has exited, + their inactivity score does not change. + """ + rng = Random(10011001) + _run_randomized_state_test_for_inactivity_updates(spec, state, rng=rng) + + +@with_altair_and_later +@spec_state_test +@leaking() +def test_randomized_state_leaking(spec, state): + """ + This test ensures that once a validator has exited, + their inactivity score does not change, even during a leak. + Note that slashed validators are still subject to mutations + (refer ``get_eligible_validator_indices`). + """ + rng = Random(10011002) + _run_randomized_state_test_for_inactivity_updates(spec, state, rng=rng) + # Check still in leak + assert spec.is_in_inactivity_leak(state) From 911a4169fef9d8f9debcdfd3f34327210e80acfd Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 19 Aug 2021 07:59:05 -0600 Subject: [PATCH 068/135] spelling Co-authored-by: Diederik Loerakker --- specs/altair/fork.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/altair/fork.md b/specs/altair/fork.md index 3c2ea843e..f80064a83 100644 --- a/specs/altair/fork.md +++ b/specs/altair/fork.md @@ -17,7 +17,7 @@ ## Introduction -This document describes the process of the first upgrade of the eacon chain: the Altair hard fork, introducing light client support and other improvements. +This document describes the process of the first upgrade of the beacon chain: the Altair hard fork, introducing light client support and other improvements. ## Configuration From 59d8ee34f9894a2b41a7d04bfc7b323693151a58 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 19 Aug 2021 23:24:04 +0800 Subject: [PATCH 069/135] Update some "1.0" "2.0" wording --- specs/altair/validator.md | 6 +++--- specs/phase0/p2p-interface.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/specs/altair/validator.md b/specs/altair/validator.md index 81e6b8192..e449c425e 100644 --- a/specs/altair/validator.md +++ b/specs/altair/validator.md @@ -1,6 +1,6 @@ # Altair -- Honest Validator -This is an accompanying document to [Altair -- The Beacon Chain](./beacon-chain.md), which describes the expected actions of a "validator" participating in the Ethereum 2.0 protocol. +This is an accompanying document to [Altair -- The Beacon Chain](./beacon-chain.md), which describes the expected actions of a "validator" participating in the Ethereum consensus protocol. ## Table of contents @@ -49,8 +49,8 @@ This is an accompanying document to [Altair -- The Beacon Chain](./beacon-chain. ## Introduction -This document represents the expected behavior of an "honest validator" with respect to the Altair upgrade of the Ethereum 2.0 protocol. -It builds on the [previous document for the behavior of an "honest validator" from Phase 0](../phase0/validator.md) of the Ethereum 2.0 protocol. +This document represents the expected behavior of an "honest validator" with respect to the Altair upgrade of the Ethereum consensus protocol. +It builds on the [previous document for the behavior of an "honest validator" from Phase 0](../phase0/validator.md) of the Ethereum consensus protocol. This previous document is referred to below as the "Phase 0 document". Altair introduces a new type of committee: the sync committee. Sync committees are responsible for signing each block of the canonical chain and there exists an efficient algorithm for light clients to sync the chain using the output of the sync committees. diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 2bb97fabe..4d2aebcdc 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -904,7 +904,7 @@ This integration enables the libp2p stack to subsequently form connections and s ### ENR structure -The Ethereum Node Record (ENR) for an Ethereum 2.0 client MUST contain the following entries +The Ethereum Node Record (ENR) for an Ethereum consensus client MUST contain the following entries (exclusive of the sequence number and signature, which MUST be present in an ENR): - The compressed secp256k1 publickey, 33 bytes (`secp256k1` field). @@ -1477,7 +1477,7 @@ On the other hand, libp2p Kademlia DHT is a fully-fledged DHT protocol/implement with content routing and storage capabilities, both of which are irrelevant in this context. Eth 1.0 nodes will evolve to support discv5. -By sharing the discovery network between Eth 1.0 and 2.0, +By sharing the discovery network between Eth consensus-layer and execution-layer clients, we benefit from the additive effect on network size that enhances resilience and resistance against certain attacks, to which smaller networks are more vulnerable. It should also help light clients of both networks find nodes with specific capabilities. From 206126644f822cb312931b5d7ad5216469d59124 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 19 Aug 2021 23:34:54 +0800 Subject: [PATCH 070/135] More fixes --- specs/phase0/p2p-interface.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 4d2aebcdc..f5c138f99 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -19,7 +19,7 @@ It consists of four main sections: - [Encryption and identification](#encryption-and-identification) - [Protocol Negotiation](#protocol-negotiation) - [Multiplexing](#multiplexing) -- [Consensus layer network interaction domains](#consensus-layer-network-interaction-domains) +- [Consensus-layer network interaction domains](#consensus-layer-network-interaction-domains) - [Configuration](#configuration) - [MetaData](#metadata) - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) @@ -113,7 +113,7 @@ It consists of four main sections: # Network fundamentals -This section outlines the specification for the networking stack in Ethereum consensus layer clients. +This section outlines the specification for the networking stack in Ethereum consensus-layer clients. ## Transport @@ -163,7 +163,7 @@ and MAY support [yamux](https://github.com/hashicorp/yamux/blob/master/spec.md). If both are supported by the client, yamux MUST take precedence during negotiation. See the [Rationale](#design-decision-rationale) section below for tradeoffs. -# Consensus layer network interaction domains +# Consensus-layer network interaction domains ## Configuration @@ -1476,8 +1476,8 @@ discv5 supports self-certified, flexible peer records (ENRs) and topic-based adv On the other hand, libp2p Kademlia DHT is a fully-fledged DHT protocol/implementations with content routing and storage capabilities, both of which are irrelevant in this context. -Eth 1.0 nodes will evolve to support discv5. -By sharing the discovery network between Eth consensus-layer and execution-layer clients, +Ethereum execution-layer nodes will evolve to support discv5. +By sharing the discovery network between Ethereum consensus-layer and execution-layer clients, we benefit from the additive effect on network size that enhances resilience and resistance against certain attacks, to which smaller networks are more vulnerable. It should also help light clients of both networks find nodes with specific capabilities. @@ -1502,7 +1502,7 @@ discv5 uses ENRs and we will presumably need to: 1. Add `multiaddr` to the dictionary, so that nodes can advertise their multiaddr under a reserved namespace in ENRs. – and/or – 2. Define a bi-directional conversion function between multiaddrs and the corresponding denormalized fields in an ENR - (ip, ip6, tcp, tcp6, etc.), for compatibility with nodes that do not support multiaddr natively (e.g. Eth 1.0 nodes). + (ip, ip6, tcp, tcp6, etc.), for compatibility with nodes that do not support multiaddr natively (e.g. Ethereum execution-layer nodes). ### Why do we not form ENRs and find peers until genesis block/state is known? From a3953a10fe695a00a44b20ea77f28be411833327 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 19 Aug 2021 10:11:02 -0600 Subject: [PATCH 071/135] minor format --- specs/altair/validator.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/altair/validator.md b/specs/altair/validator.md index e449c425e..461c9a70d 100644 --- a/specs/altair/validator.md +++ b/specs/altair/validator.md @@ -1,6 +1,6 @@ # Altair -- Honest Validator -This is an accompanying document to [Altair -- The Beacon Chain](./beacon-chain.md), which describes the expected actions of a "validator" participating in the Ethereum consensus protocol. +This is an accompanying document to [Altair -- The Beacon Chain](./beacon-chain.md), which describes the expected actions of a "validator" participating in the Ethereum proof-of-stake protocol. ## Table of contents @@ -49,8 +49,8 @@ This is an accompanying document to [Altair -- The Beacon Chain](./beacon-chain. ## Introduction -This document represents the expected behavior of an "honest validator" with respect to the Altair upgrade of the Ethereum consensus protocol. -It builds on the [previous document for the behavior of an "honest validator" from Phase 0](../phase0/validator.md) of the Ethereum consensus protocol. +This document represents the expected behavior of an "honest validator" with respect to the Altair upgrade of the Ethereum proof-of-stake protocol. +It builds on the [previous document for the behavior of an "honest validator" from Phase 0](../phase0/validator.md) of the Ethereum proof-of-stake protocol. This previous document is referred to below as the "Phase 0 document". Altair introduces a new type of committee: the sync committee. Sync committees are responsible for signing each block of the canonical chain and there exists an efficient algorithm for light clients to sync the chain using the output of the sync committees. From cdf1914e981246b9a438454a0e91f25a2865f7a4 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 19 Aug 2021 11:00:14 -0600 Subject: [PATCH 072/135] Extend randomized block tests to all phases Some existing randomized block tests only ran for phase 0, when they should run for all phases to increase overall test coverage. --- .../pyspec/eth2spec/test/phase0/sanity/test_blocks.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 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 33e9854b2..a79ed94d2 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py @@ -1051,25 +1051,25 @@ def test_eth1_data_votes_no_consensus(spec, state): yield 'post', state -@with_phases([PHASE0]) +@with_all_phases @spec_state_test def test_full_random_operations_0(spec, state): yield from run_test_full_random_operations(spec, state, rng=Random(2020)) -@with_phases([PHASE0]) +@with_all_phases @spec_state_test def test_full_random_operations_1(spec, state): yield from run_test_full_random_operations(spec, state, rng=Random(2021)) -@with_phases([PHASE0]) +@with_all_phases @spec_state_test def test_full_random_operations_2(spec, state): yield from run_test_full_random_operations(spec, state, rng=Random(2022)) -@with_phases([PHASE0]) +@with_all_phases @spec_state_test def test_full_random_operations_3(spec, state): yield from run_test_full_random_operations(spec, state, rng=Random(2023)) From eadefa274d2fac1840e14ae7f0c07a75b5941fb6 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 19 Aug 2021 12:54:21 -0600 Subject: [PATCH 073/135] WIP: broad-spectrum randomized block tests --- .../test/phase0/sanity/test_blocks_random.py | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py new file mode 100644 index 000000000..ffc37c7c2 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -0,0 +1,67 @@ +from eth2spec.test.helpers.state import ( + next_epoch, + next_slot, +) +from eth2spec.test.context import ( + with_all_phases, + spec_state_test, +) + + +def generate_randomized_scenarios(): + # TODO: WIP schema + return { + # ("randomize_state", "ensure all validator states present: pending/deposited, activated, exited, slashed"), + # ("randomized balances", "ensure distribution of bals"), + # ("transition to leak if not already, maybe", "assert is or is not leaking"), + "setup": [], + "epochs_to_skip": 0, # 0, 1, 2, N, EPOCHS_TO_INACTIVITY_LEAK, + "slots_to_skip": 0, # 0, 1, 2, N, SLOTS_PER_EPOCH - 1, + "transitions": [ # TODO: consider large numbers of blocks, load on generated data + { + "block_producer": lambda spec, state: spec.SignedBeaconBlock(), + "epochs_to_skip": 0, # 0, 1, 2, N, EPOCHS_TO_INACTIVITY_LEAK, + "slots_to_skip": 0, # 0, 1, 2, N, SLOTS_PER_EPOCH - 1, + } + ], + } + + +def id_from_scenario(test_description): + return '-'.join(':'.join((str(k),str(v))) for k,v in test_description.items()) + + +def pytest_generate_tests(metafunc): + """ + Pytest hook to generate test cases from dynamically computed data + """ + generated_name = "test_description" + generated_values = generate_randomized_scenarios() + metafunc.parametrize(generated_name, generated_values, ids=id_from_scenario, scope="module") + + +def pytest_generate_tests_adapter(f): + """ + Adapter decorator to allow dynamic test case generation + while leveraging existing decorators specific to spec tests. + """ + def wrapper(test_description, *args, **kwargs): + kwargs["test_description"] = test_description + f(*args, **kwargs) + return wrapper + + +@pytest_generate_tests_adapter +@with_all_phases +@spec_state_test +def test_harness_for_randomized_blocks(spec, state, test_description): + for mutation, validation in test_description["setup"]: + mutation(spec, state) + validation(spec, state) + for _ in range(len(test_description["epochs_to_skip"])): + next_epoch(spec, state) + for _ in range(len(test_description["slots_to_skip"])): + next_slot(spec, state) + for transition in test_description["transitions"]: + # TODO apply transition + pass From 29c7184b7b62e3666da5a28d5788b5a0574585e7 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 20 Aug 2021 08:42:22 -0600 Subject: [PATCH 074/135] bump VERSION.txt to 1.1.0-beta.3 --- 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 fd5a6fed6..a0e0952ff 100644 --- a/tests/core/pyspec/eth2spec/VERSION.txt +++ b/tests/core/pyspec/eth2spec/VERSION.txt @@ -1 +1 @@ -1.1.0-beta.2 \ No newline at end of file +1.1.0-beta.3 \ No newline at end of file From 00df808f59e6212ffc28d46fe39f99b63ed7253b Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Fri, 20 Aug 2021 13:35:23 -0600 Subject: [PATCH 075/135] expose functionality to make random block --- .../eth2spec/test/helpers/multi_operations.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py index 4b6c9b25d..dc64882b8 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -125,10 +125,7 @@ def get_random_voluntary_exits(spec, state, to_be_slashed_indices, rng): return prepare_signed_exits(spec, state, exit_indices) -def run_test_full_random_operations(spec, state, rng=Random(2080)): - # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit - state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH - +def build_random_block_from_state(spec, state, rng=Random(2188)): # prepare state for deposits before building block deposits = prepare_state_and_get_random_deposits(spec, state, rng) @@ -148,6 +145,15 @@ def run_test_full_random_operations(spec, state, rng=Random(2080)): slashed_indices = slashed_indices.union(attester_slashing.attestation_2.attesting_indices) block.body.voluntary_exits = get_random_voluntary_exits(spec, state, slashed_indices, rng) + return block + + +def run_test_full_random_operations(spec, state, rng=Random(2080)): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + block = build_random_block_from_state(spec, state, rng) + yield 'pre', state signed_block = state_transition_and_sign_block(spec, state, block) From 4420d1381666a405b82429e76a50ffb513eb9ea1 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Fri, 20 Aug 2021 13:35:42 -0600 Subject: [PATCH 076/135] add helper to check existence of many validator types --- .../pyspec/eth2spec/test/helpers/state.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/helpers/state.py b/tests/core/pyspec/eth2spec/test/helpers/state.py index 666023fec..b4c9e1d67 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/state.py +++ b/tests/core/pyspec/eth2spec/test/helpers/state.py @@ -1,5 +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 def get_balance(state, index): @@ -133,3 +134,24 @@ def _set_empty_participation(spec, state, current=True, previous=True): def set_empty_participation(spec, state, rng=None): _set_empty_participation(spec, state) + + +def ensure_state_has_validators_across_lifecycle(spec, state): + """ + Scan the validator registry to ensure there is at least 1 validator + for each of the following lifecycle states: + 1. Pending / deposited + 2. Active + 3. Exited + 4. Slashed + """ + has_pending = any(filter(spec.is_eligible_for_activation_queue, state.validators)) + + 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_slashed = any(filter(lambda v: v.slashed, state.validators)) + + return has_pending and has_active and has_exited and has_slashed From 619e82889881354e1fef8b43e0f683b4c9f3105e Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Fri, 20 Aug 2021 13:57:26 -0600 Subject: [PATCH 077/135] Progress on block test gen --- .../test/phase0/sanity/test_blocks_random.py | 290 ++++++++++++++++-- 1 file changed, 263 insertions(+), 27 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py index ffc37c7c2..9b10ab355 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -1,34 +1,240 @@ +import itertools +from random import Random +from typing import Callable +from tests.core.pyspec.eth2spec.test.context import default_activation_threshold +from eth2spec.test.helpers.multi_operations import ( + build_random_block_from_state, +) from eth2spec.test.helpers.state import ( next_epoch, next_slot, + ensure_state_has_validators_across_lifecycle, + state_transition_and_sign_block, +) +from eth2spec.test.helpers.random import ( + randomize_state, ) from eth2spec.test.context import ( with_all_phases, - spec_state_test, + always_bls, + spec_test, + with_custom_state, + default_activation_threshold, + single_phase, + misc_balances, ) +# primitives +## epochs -def generate_randomized_scenarios(): - # TODO: WIP schema +def _epochs_until_leak(spec): + return spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + + +def _epochs_for_shard_committee_period(spec): + return spec.config.SHARD_COMMITTEE_PERIOD + + +## slots + +def _last_slot_in_epoch(spec): + return spec.SLOTS_PER_EPOCH - 1 + + +def _random_slot_in_epoch(rng): + def f(spec): + return rng.randrange(1, spec.SLOTS_PER_EPOCH - 2) + return f + + +def _penultimate_slot_in_epoch(spec): + return spec.SLOTS_PER_EPOCH - 2 + + +## blocks + +def _no_block(_spec, _pre_state, _signed_blocks): + return None + + +def _random_block_for_next_slot(spec, pre_state, _signed_blocks): + return build_random_block_from_state(spec, pre_state) + + +## validations + +def _no_op_validation(spec, state): + return True + + +def _validate_is_leaking(spec, state): + return spec.is_in_inactivity_leak(state) + + +# transitions + +def _no_op_transition(): + return {} + + +def _epoch_transition(n=0): return { - # ("randomize_state", "ensure all validator states present: pending/deposited, activated, exited, slashed"), - # ("randomized balances", "ensure distribution of bals"), - # ("transition to leak if not already, maybe", "assert is or is not leaking"), - "setup": [], - "epochs_to_skip": 0, # 0, 1, 2, N, EPOCHS_TO_INACTIVITY_LEAK, - "slots_to_skip": 0, # 0, 1, 2, N, SLOTS_PER_EPOCH - 1, - "transitions": [ # TODO: consider large numbers of blocks, load on generated data - { - "block_producer": lambda spec, state: spec.SignedBeaconBlock(), - "epochs_to_skip": 0, # 0, 1, 2, N, EPOCHS_TO_INACTIVITY_LEAK, - "slots_to_skip": 0, # 0, 1, 2, N, SLOTS_PER_EPOCH - 1, - } - ], + "epochs_to_skip": n, } -def id_from_scenario(test_description): - return '-'.join(':'.join((str(k),str(v))) for k,v in test_description.items()) +def _slot_transition(n=0): + return { + "slots_to_skip": n, + } + + +def _transition_to_leaking(): + return { + "epochs_to_skip": _epochs_until_leak, + "validation": _validate_is_leaking, + } + + +## block transitions + +def _transition_with_random_block(epochs=None, slots=None): + """ + Build a block transition with randomized data. + Provide optional sub-transitions to advance some + number of epochs or slots before applying the random block. + """ + transition = { + "block_producer": _random_block_for_next_slot, + } + if epochs: + transition.update(epochs) + if slots: + transition.update(slots) + return transition + + +# setup and test gen + +def _randomized_scenario_setup(): + """ + Return a sequence of pairs of ("mutator", "validator"), + a function that accepts (spec, state) arguments and performs some change + and a function that accepts (spec, state) arguments and validates some change was made. + """ + def _skip_epochs(epoch_producer): + def f(spec, state): + """ + The unoptimized spec implementation is too slow to advance via ``next_epoch``. + Instead, just overwrite the ``state.slot`` and continue... + """ + epochs_to_skip = epoch_producer(spec) + slots_to_skip = epochs_to_skip * spec.SLOTS_PER_EPOCH + state.slot += slots_to_skip + return f + + return ( + # NOTE: the block randomization function assumes at least 1 shard committee period + # so advance the state before doing anything else. + (_skip_epochs(_epochs_for_shard_committee_period), _no_op_validation), + (randomize_state, ensure_state_has_validators_across_lifecycle), + ) + + +def _normalize_transition(transition): + """ + Provide "empty" or "no op" sub-transitions + to a given transition. + """ + if isinstance(transition, Callable): + transition = transition() + if "epochs_to_skip" not in transition: + transition["epochs_to_skip"] = 0 + if "slots_to_skip" not in transition: + transition["slots_to_skip"] = 0 + if "block_producer" not in transition: + transition["block_producer"] = _no_block + if "validation" not in transition: + transition["validation"] = _no_op_validation + return transition + + +def _normalize_scenarios(scenarios): + """ + "Normalize" a "scenario" so that a producer of a test case + does not need to provide every expected key/value. + """ + for scenario in scenarios: + if "setup" not in scenario: + scenario["setup"] = _randomized_scenario_setup() + + transitions = scenario["transitions"] + for i, transition in enumerate(transitions): + transitions[i] = _normalize_transition(transition) + + +def _generate_randomized_scenarios(): + """ + Generates a set of randomized testing scenarios. + Return a sequence of "scenarios" where each scenario: + 1. Provides some setup + 2. Provides a sequence of transitions that mutate the state in some way, + possibly yielding blocks along the way + NOTE: scenarios are "normalized" with empty/no-op elements before returning + to the test generation to facilitate brevity when writing scenarios by hand. + NOTE: the main block driver builds a block for the **next** slot, so + the slot transitions are offset by -1 to target certain boundaries. + """ + rng = Random(1336) + leak_transitions = (_no_op_transition, _transition_to_leaking) + + # go forward 0 or 1 epochs + epochs_set = (_epoch_transition(n=0), _epoch_transition(n=1)) + # within those epochs, go forward to: + slots_set = ( + # the first slot in an epoch (see note in docstring about offsets...) + _slot_transition(_last_slot_in_epoch), + # the second slot in an epoch + _slot_transition(n=0), + # some random number of slots, but not at epoch boundaries + _slot_transition(_random_slot_in_epoch(rng)), + # the last slot in an epoch (see note in docstring about offsets...) + _slot_transition(_penultimate_slot_in_epoch), + ) + # build a set of block transitions from combinations of sub-transitions + block_transitions = list( + _transition_with_random_block(epochs=epochs, slots=slots) + for epochs, slots in itertools.product(epochs_set, slots_set) + ) + + # and preface each block transition with the possible leak transitions + # (... either no leak or transition to a leak before applying the block transition) + scenarios = [ + {"transitions": list(t)} + for t in itertools.product(leak_transitions, block_transitions) + ] + _normalize_scenarios(scenarios) + return scenarios + + +def _id_from_scenario(test_description): + """ + Construct a test name for ``pytest`` infra. + """ + def _to_id_part(prefix, x): + suffix = str(x) + if isinstance(x, Callable): + suffix = x.__name__ + return f"{prefix}{suffix}" + + def _id_from_transition(transition): + return ",".join(( + _to_id_part("epochs:", transition["epochs_to_skip"]), + _to_id_part("slots:", transition["slots_to_skip"]), + _to_id_part("with-block:", transition["block_producer"]) + )) + + return "|".join(map(_id_from_transition, test_description["transitions"])) def pytest_generate_tests(metafunc): @@ -36,8 +242,8 @@ def pytest_generate_tests(metafunc): Pytest hook to generate test cases from dynamically computed data """ generated_name = "test_description" - generated_values = generate_randomized_scenarios() - metafunc.parametrize(generated_name, generated_values, ids=id_from_scenario, scope="module") + generated_values = _generate_randomized_scenarios() + metafunc.parametrize(generated_name, generated_values, ids=_id_from_scenario, scope="module") def pytest_generate_tests_adapter(f): @@ -51,17 +257,47 @@ def pytest_generate_tests_adapter(f): return wrapper +def _iter_temporal(spec, callable_or_int): + """ + Intended to advance some number of {epochs, slots}. + Caller can provide a constant integer or a callable deriving a number from + the ``spec`` under consideration. + """ + numeric = callable_or_int + if isinstance(callable_or_int, Callable): + numeric = callable_or_int(spec) + for i in range(numeric): + yield i + + @pytest_generate_tests_adapter @with_all_phases -@spec_state_test +@with_custom_state(balances_fn=misc_balances, threshold_fn=default_activation_threshold) +@spec_test +@single_phase +@always_bls def test_harness_for_randomized_blocks(spec, state, test_description): for mutation, validation in test_description["setup"]: mutation(spec, state) validation(spec, state) - for _ in range(len(test_description["epochs_to_skip"])): - next_epoch(spec, state) - for _ in range(len(test_description["slots_to_skip"])): - next_slot(spec, state) + + yield "pre", state + + blocks = [] for transition in test_description["transitions"]: - # TODO apply transition - pass + epochs_to_skip = _iter_temporal(spec, transition["epochs_to_skip"]) + for _ in epochs_to_skip: + next_epoch(spec, state) + slots_to_skip = _iter_temporal(spec, transition["slots_to_skip"]) + for _ in slots_to_skip: + next_slot(spec, state) + + block = transition["block_producer"](spec, state, blocks) + if block: + signed_block = state_transition_and_sign_block(spec, state, block) + blocks.append(signed_block) + + assert transition["validation"](spec, state) + + yield "blocks", blocks + yield "post", state From 92aabcd207c7564668ce70e04217d43cdd10c1e6 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Fri, 20 Aug 2021 18:49:04 -0600 Subject: [PATCH 078/135] add randomized block tests to test generator --- tests/generators/sanity/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/generators/sanity/main.py b/tests/generators/sanity/main.py index 8caedc8e5..63efa3897 100644 --- a/tests/generators/sanity/main.py +++ b/tests/generators/sanity/main.py @@ -5,6 +5,7 @@ from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators if __name__ == "__main__": phase_0_mods = {key: 'eth2spec.test.phase0.sanity.test_' + key for key in [ 'blocks', + 'blocks_random', 'slots', ]} altair_mods = {**{key: 'eth2spec.test.altair.sanity.test_' + key for key in [ From 5094193f9ab4c751a439b4c2de4763f65b17e3f1 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Sat, 21 Aug 2021 16:59:02 -0700 Subject: [PATCH 079/135] formatting --- .../pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py index 9b10ab355..8767ad12c 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -186,7 +186,6 @@ def _generate_randomized_scenarios(): the slot transitions are offset by -1 to target certain boundaries. """ rng = Random(1336) - leak_transitions = (_no_op_transition, _transition_to_leaking) # go forward 0 or 1 epochs epochs_set = (_epoch_transition(n=0), _epoch_transition(n=1)) @@ -209,6 +208,7 @@ def _generate_randomized_scenarios(): # and preface each block transition with the possible leak transitions # (... either no leak or transition to a leak before applying the block transition) + leak_transitions = (_no_op_transition, _transition_to_leaking) scenarios = [ {"transitions": list(t)} for t in itertools.product(leak_transitions, block_transitions) From 6da2c7a91671868b6c3bdcc0daf1482d07a1b153 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Sat, 21 Aug 2021 16:59:54 -0700 Subject: [PATCH 080/135] ensure all validators in randomized test are active --- tests/core/pyspec/eth2spec/test/context.py | 15 +++++++++++++++ .../test/phase0/sanity/test_blocks_random.py | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 7fddc0762..a6828f7e0 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -152,6 +152,21 @@ def misc_balances(spec): return balances +def misc_balances_in_default_range(spec): + """ + Helper method to create a series of balances that includes some misc. balances but + none that are below the ``EJECTION_BALANCE``. + """ + num_validators = spec.SLOTS_PER_EPOCH * 8 + floor = spec.config.EJECTION_BALANCE + spec.EFFECTIVE_BALANCE_INCREMENT + balances = [ + max(spec.MAX_EFFECTIVE_BALANCE * 2 * i // num_validators, floor) for i in range(num_validators) + ] + rng = Random(1234) + rng.shuffle(balances) + return balances + + def low_single_balance(spec): """ Helper method to create a single of balance of 1 Gwei. diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py index 8767ad12c..f015362ac 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -1,7 +1,7 @@ import itertools from random import Random from typing import Callable -from tests.core.pyspec.eth2spec.test.context import default_activation_threshold +from tests.core.pyspec.eth2spec.test.context import default_activation_threshold, misc_balances_in_default_range from eth2spec.test.helpers.multi_operations import ( build_random_block_from_state, ) @@ -272,7 +272,7 @@ def _iter_temporal(spec, callable_or_int): @pytest_generate_tests_adapter @with_all_phases -@with_custom_state(balances_fn=misc_balances, threshold_fn=default_activation_threshold) +@with_custom_state(balances_fn=misc_balances_in_default_range, threshold_fn=default_activation_threshold) @spec_test @single_phase @always_bls From 86643d805a82e6e8ec0bf700e3e99d1b8452ce21 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Sat, 21 Aug 2021 17:36:50 -0700 Subject: [PATCH 081/135] adjust some helper code for randomized environment 1. randomized block helpers assume most of the validator set is not slashed 2. `randomize_state` helper slashes or exits ~1/2 of the validator set So, adjust helpers to be less aggresive with exits and slashings and to skip elements as needed if we happen to make something by a validator who has been slashed. --- .../pyspec/eth2spec/test/helpers/random.py | 26 ++++++++----- .../test/phase0/sanity/test_blocks_random.py | 37 ++++++++++++++++--- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index 70c871a34..8f095aebb 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -20,16 +20,20 @@ def set_some_new_deposits(spec, state, rng): state.validators[index].activation_eligibility_epoch = spec.get_current_epoch(state) -def exit_random_validators(spec, state, rng): +def exit_random_validators(spec, state, rng, fraction=None): + if fraction is None: + # Exit ~1/2 + fraction = 0.5 + if spec.get_current_epoch(state) < 5: # Move epochs forward to allow for some validators already exited/withdrawable for _ in range(5): next_epoch(spec, state) current_epoch = spec.get_current_epoch(state) - # Exit ~1/2 of validators for index in spec.get_active_validator_indices(state, current_epoch): - if rng.choice([True, False]): + sampled = rng.random() < fraction + if not sampled: continue validator = state.validators[index] @@ -41,11 +45,15 @@ def exit_random_validators(spec, state, rng): validator.withdrawable_epoch = current_epoch + 1 -def slash_random_validators(spec, state, rng): - # Slash ~1/2 of validators +def slash_random_validators(spec, state, rng, fraction=None): + if fraction is None: + # Slash ~1/2 of validators + fraction = 0.5 + for index in range(len(state.validators)): # slash at least one validator - if index == 0 or rng.choice([True, False]): + sampled = rng.random() < fraction + if index == 0 or sampled: spec.slash_validator(state, index) @@ -115,8 +123,8 @@ def randomize_attestation_participation(spec, state, rng=Random(8020)): randomize_epoch_participation(spec, state, spec.get_current_epoch(state), rng) -def randomize_state(spec, state, rng=Random(8020)): +def randomize_state(spec, state, rng=Random(8020), exit_fraction=None, slash_fraction=None): set_some_new_deposits(spec, state, rng) - exit_random_validators(spec, state, rng) - slash_random_validators(spec, state, rng) + exit_random_validators(spec, state, rng, fraction=exit_fraction) + slash_random_validators(spec, state, rng, fraction=slash_fraction) randomize_attestation_participation(spec, state, rng) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py index f015362ac..d4740d080 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -1,7 +1,7 @@ import itertools from random import Random from typing import Callable -from tests.core.pyspec.eth2spec.test.context import default_activation_threshold, misc_balances_in_default_range +from tests.core.pyspec.eth2spec.test.context import misc_balances_in_default_range, zero_activation_threshold from eth2spec.test.helpers.multi_operations import ( build_random_block_from_state, ) @@ -24,7 +24,17 @@ from eth2spec.test.context import ( misc_balances, ) +# May need to make several attempts to find a block that does not correspond to a slashed +# proposer with the randomization helpers... +BLOCK_ATTEMPTS = 32 + # primitives +## state + +def _randomize_state(spec, state): + return randomize_state(spec, state, exit_fraction=0.1, slash_fraction=0.1) + + ## epochs def _epochs_until_leak(spec): @@ -57,8 +67,23 @@ def _no_block(_spec, _pre_state, _signed_blocks): return None -def _random_block_for_next_slot(spec, pre_state, _signed_blocks): - return build_random_block_from_state(spec, pre_state) +def _random_block(spec, state, _signed_blocks): + """ + Produce a random block. + NOTE: this helper may mutate state, as it will attempt + to produce a block over ``BLOCK_ATTEMPTS`` slots in order + to find a valid block in the event that the proposer has already been slashed. + """ + block = build_random_block_from_state(spec, state) + for _ in range(BLOCK_ATTEMPTS): + proposer = state.validators[block.proposer_index] + if proposer.slashed: + next_slot(spec, state) + block = build_random_block_from_state(spec, state) + else: + return block + else: + raise AssertionError("could not find a block with an unslashed proposer, check ``state`` input") ## validations @@ -105,7 +130,7 @@ def _transition_with_random_block(epochs=None, slots=None): number of epochs or slots before applying the random block. """ transition = { - "block_producer": _random_block_for_next_slot, + "block_producer": _random_block, } if epochs: transition.update(epochs) @@ -137,7 +162,7 @@ def _randomized_scenario_setup(): # NOTE: the block randomization function assumes at least 1 shard committee period # so advance the state before doing anything else. (_skip_epochs(_epochs_for_shard_committee_period), _no_op_validation), - (randomize_state, ensure_state_has_validators_across_lifecycle), + (_randomize_state, ensure_state_has_validators_across_lifecycle), ) @@ -272,7 +297,7 @@ def _iter_temporal(spec, callable_or_int): @pytest_generate_tests_adapter @with_all_phases -@with_custom_state(balances_fn=misc_balances_in_default_range, threshold_fn=default_activation_threshold) +@with_custom_state(balances_fn=misc_balances_in_default_range, threshold_fn=zero_activation_threshold) @spec_test @single_phase @always_bls From 7bc2f9547a038d7ee9c271c262c114a3bf31f699 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Sat, 21 Aug 2021 17:52:26 -0700 Subject: [PATCH 082/135] skip validators when building a random block if they are slashed --- .../eth2spec/test/helpers/multi_operations.py | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py index dc64882b8..2629206e7 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -45,7 +45,11 @@ def run_slash_and_exit(spec, state, slash_index, exit_index, valid=True): def get_random_proposer_slashings(spec, state, rng): num_slashings = rng.randrange(spec.MAX_PROPOSER_SLASHINGS) - indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy() + active_indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy() + indices = [ + index for index in active_indices + if not state.validators[index].slashed + ] slashings = [ get_valid_proposer_slashing( spec, state, @@ -58,7 +62,11 @@ def get_random_proposer_slashings(spec, state, rng): def get_random_attester_slashings(spec, state, rng): num_slashings = rng.randrange(spec.MAX_ATTESTER_SLASHINGS) - indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy() + active_indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy() + indices = [ + index for index in active_indices + if not state.validators[index].slashed + ] slot_range = list(range(state.slot - spec.SLOTS_PER_HISTORICAL_ROOT + 1, state.slot)) slashings = [ get_valid_attester_slashing_by_indices( @@ -119,9 +127,14 @@ def prepare_state_and_get_random_deposits(spec, state, rng): def get_random_voluntary_exits(spec, state, to_be_slashed_indices, rng): num_exits = rng.randrange(spec.MAX_VOLUNTARY_EXITS) - indices = set(spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy()) + active_indices = set(spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy()) + indices = set( + index for index in active_indices + if not state.validators[index].slashed + ) eligible_indices = indices - to_be_slashed_indices - exit_indices = [eligible_indices.pop() for _ in range(num_exits)] + indices_count = min(num_exits, len(eligible_indices)) + exit_indices = [eligible_indices.pop() for _ in range(indices_count)] return prepare_signed_exits(spec, state, exit_indices) From fde71cbe74b9aefa87593f54b9b6e246eda5d17e Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Sat, 21 Aug 2021 18:07:00 -0700 Subject: [PATCH 083/135] add warnings if empty block --- .../test/phase0/sanity/test_blocks_random.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py index d4740d080..d7749909e 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -1,4 +1,5 @@ import itertools +import warnings from random import Random from typing import Callable from tests.core.pyspec.eth2spec.test.context import misc_balances_in_default_range, zero_activation_threshold @@ -24,6 +25,23 @@ from eth2spec.test.context import ( misc_balances, ) +def _warn_if_empty_operations(block): + if len(block.body.deposits) == 0: + warnings.warn(f"deposits missing in block at slot {block.slot}") + + if len(block.body.proposer_slashings) == 0: + warnings.warn(f"proposer slashings missing in block at slot {block.slot}") + + if len(block.body.attester_slashings) == 0: + warnings.warn(f"attester slashings missing in block at slot {block.slot}") + + if len(block.body.attestations) == 0: + warnings.warn(f"attestations missing in block at slot {block.slot}") + + if len(block.body.voluntary_exits) == 0: + warnings.warn(f"voluntary exits missing in block at slot {block.slot}") + + # May need to make several attempts to find a block that does not correspond to a slashed # proposer with the randomization helpers... BLOCK_ATTEMPTS = 32 @@ -81,6 +99,7 @@ def _random_block(spec, state, _signed_blocks): next_slot(spec, state) block = build_random_block_from_state(spec, state) else: + _warn_if_empty_operations(block) return block else: raise AssertionError("could not find a block with an unslashed proposer, check ``state`` input") From 9e6a51ef70f3e93d0fe4c5c802ee3c2d20b62ff9 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Sat, 21 Aug 2021 18:07:12 -0700 Subject: [PATCH 084/135] update fn name for test id --- .../pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py index d7749909e..98b251add 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -70,9 +70,9 @@ def _last_slot_in_epoch(spec): def _random_slot_in_epoch(rng): - def f(spec): + def _a_slot_in_epoch(spec): return rng.randrange(1, spec.SLOTS_PER_EPOCH - 2) - return f + return _a_slot_in_epoch def _penultimate_slot_in_epoch(spec): From b17ada2d67ba9c944c75ebc9c9ff52e796a6c8f3 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Sat, 21 Aug 2021 18:24:26 -0700 Subject: [PATCH 085/135] only target phase 0 and altair for now --- .../pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py index 98b251add..05ae545b8 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -1,8 +1,9 @@ import itertools import warnings from random import Random +from tests.core.pyspec.eth2spec.test.helpers.constants import PHASE0, ALTAIR from typing import Callable -from tests.core.pyspec.eth2spec.test.context import misc_balances_in_default_range, zero_activation_threshold +from tests.core.pyspec.eth2spec.test.context import misc_balances_in_default_range, with_phases, zero_activation_threshold from eth2spec.test.helpers.multi_operations import ( build_random_block_from_state, ) @@ -315,7 +316,7 @@ def _iter_temporal(spec, callable_or_int): @pytest_generate_tests_adapter -@with_all_phases +@with_phases([PHASE0, ALTAIR]) @with_custom_state(balances_fn=misc_balances_in_default_range, threshold_fn=zero_activation_threshold) @spec_test @single_phase From 513f57f74c923d7300fe06a879d56a8340be6d6f Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Sun, 22 Aug 2021 09:55:41 -0700 Subject: [PATCH 086/135] formatting --- .../eth2spec/test/phase0/sanity/test_blocks_random.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py index 05ae545b8..cead514a5 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -233,7 +233,10 @@ def _generate_randomized_scenarios(): rng = Random(1336) # go forward 0 or 1 epochs - epochs_set = (_epoch_transition(n=0), _epoch_transition(n=1)) + epochs_set = ( + _epoch_transition(n=0), + _epoch_transition(n=1), + ) # within those epochs, go forward to: slots_set = ( # the first slot in an epoch (see note in docstring about offsets...) @@ -253,7 +256,10 @@ def _generate_randomized_scenarios(): # and preface each block transition with the possible leak transitions # (... either no leak or transition to a leak before applying the block transition) - leak_transitions = (_no_op_transition, _transition_to_leaking) + leak_transitions = ( + _no_op_transition, + _transition_to_leaking, + ) scenarios = [ {"transitions": list(t)} for t in itertools.product(leak_transitions, block_transitions) From 820affd2aaa8e9f0d889a125d43ef0d62936eb8c Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Sun, 22 Aug 2021 09:58:54 -0700 Subject: [PATCH 087/135] extend validator set so randomized helpers have more room for operation --- tests/core/pyspec/eth2spec/test/context.py | 5 +++-- .../eth2spec/test/phase0/sanity/test_blocks_random.py | 11 +++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index a6828f7e0..f6f120d55 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -152,12 +152,13 @@ def misc_balances(spec): return balances -def misc_balances_in_default_range(spec): +def misc_balances_in_default_range_with_many_validators(spec): """ Helper method to create a series of balances that includes some misc. balances but none that are below the ``EJECTION_BALANCE``. """ - num_validators = spec.SLOTS_PER_EPOCH * 8 + # Double validators to facilitate randomized testing + num_validators = spec.SLOTS_PER_EPOCH * 8 * 2 floor = spec.config.EJECTION_BALANCE + spec.EFFECTIVE_BALANCE_INCREMENT balances = [ max(spec.MAX_EFFECTIVE_BALANCE * 2 * i // num_validators, floor) for i in range(num_validators) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py index cead514a5..52868c744 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -3,7 +3,11 @@ import warnings from random import Random from tests.core.pyspec.eth2spec.test.helpers.constants import PHASE0, ALTAIR from typing import Callable -from tests.core.pyspec.eth2spec.test.context import misc_balances_in_default_range, with_phases, zero_activation_threshold +from tests.core.pyspec.eth2spec.test.context import ( + misc_balances_in_default_range_with_many_validators, + with_phases, + zero_activation_threshold, +) from eth2spec.test.helpers.multi_operations import ( build_random_block_from_state, ) @@ -323,7 +327,10 @@ def _iter_temporal(spec, callable_or_int): @pytest_generate_tests_adapter @with_phases([PHASE0, ALTAIR]) -@with_custom_state(balances_fn=misc_balances_in_default_range, threshold_fn=zero_activation_threshold) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) @spec_test @single_phase @always_bls From 270814e20fbc9b80cbe6c34eb1dff5534a4cd52e Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Sun, 22 Aug 2021 09:59:28 -0700 Subject: [PATCH 088/135] fix bug with `_epochs_until_leak` helper --- .../eth2spec/test/phase0/sanity/test_blocks_random.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py index 52868c744..e0043332d 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -61,7 +61,11 @@ def _randomize_state(spec, state): ## epochs def _epochs_until_leak(spec): - return spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + """ + State is "leaking" if the current epoch is at least + this value after the last finalized epoch. + """ + return spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + 1 def _epochs_for_shard_committee_period(spec): From 993997aca5ddf88d17db341791ac89156adee77b Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Sun, 22 Aug 2021 10:07:31 -0700 Subject: [PATCH 089/135] ensure no leak on "normal" transitions --- .../test/phase0/sanity/test_blocks_random.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py index e0043332d..c3b55e49d 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -124,8 +124,19 @@ def _validate_is_leaking(spec, state): return spec.is_in_inactivity_leak(state) +def _validate_is_not_leaking(spec, state): + return not _validate_is_leaking(spec, state) + + # transitions +def _with_validation(transition, validation): + if isinstance(transition, Callable): + transition = transition() + transition["validation"] = validation + return transition + + def _no_op_transition(): return {} @@ -264,8 +275,9 @@ def _generate_randomized_scenarios(): # and preface each block transition with the possible leak transitions # (... either no leak or transition to a leak before applying the block transition) + _transition_without_leak = _with_validation(_no_op_transition, _validate_is_not_leaking) leak_transitions = ( - _no_op_transition, + _transition_without_leak, _transition_to_leaking, ) scenarios = [ From f76a29c1f9b2ede7cc2ce371d1e5b94df2fef85f Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Sun, 22 Aug 2021 10:52:22 -0700 Subject: [PATCH 090/135] patch state to not be leaking at start --- .../test/phase0/sanity/test_blocks_random.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py index c3b55e49d..c379d5815 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -197,10 +197,35 @@ def _randomized_scenario_setup(): state.slot += slots_to_skip return f + def _simulate_honest_execution(spec, state): + """ + Want to start tests not in a leak state; the finality data + 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, + ) + return ( # NOTE: the block randomization function assumes at least 1 shard committee period # so advance the state before doing anything else. (_skip_epochs(_epochs_for_shard_committee_period), _no_op_validation), + (_simulate_honest_execution, _no_op_validation), (_randomize_state, ensure_state_has_validators_across_lifecycle), ) From ce471b702e57114e3c66a8cec13f8f14b627ca46 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Sun, 22 Aug 2021 10:54:00 -0700 Subject: [PATCH 091/135] code org --- .../pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py index c379d5815..93f31a4da 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -160,6 +160,8 @@ def _transition_to_leaking(): } +_transition_without_leak = _with_validation(_no_op_transition, _validate_is_not_leaking) + ## block transitions def _transition_with_random_block(epochs=None, slots=None): @@ -300,7 +302,6 @@ def _generate_randomized_scenarios(): # and preface each block transition with the possible leak transitions # (... either no leak or transition to a leak before applying the block transition) - _transition_without_leak = _with_validation(_no_op_transition, _validate_is_not_leaking) leak_transitions = ( _transition_without_leak, _transition_to_leaking, From 0c401a3e2a470b8f567f53e03254ffe6536ae0f1 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Sun, 22 Aug 2021 12:13:40 -0700 Subject: [PATCH 092/135] filter for exit eligibility in helper --- .../eth2spec/test/helpers/multi_operations.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py index 2629206e7..54e45b335 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -125,12 +125,24 @@ def prepare_state_and_get_random_deposits(spec, state, rng): return deposits +def _eligible_for_exit(spec, state, index): + validator = state.validators[index] + + not_slashed = not validator.slashed + + current_epoch = spec.get_current_epoch(state) + activation_epoch = validator.activation_epoch + active_for_long_enough = current_epoch >= activation_epoch + spec.config.SHARD_COMMITTEE_PERIOD + + return not_slashed and active_for_long_enough + + def get_random_voluntary_exits(spec, state, to_be_slashed_indices, rng): num_exits = rng.randrange(spec.MAX_VOLUNTARY_EXITS) active_indices = set(spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy()) indices = set( index for index in active_indices - if not state.validators[index].slashed + if _eligible_for_exit(spec, state, index) ) eligible_indices = indices - to_be_slashed_indices indices_count = min(num_exits, len(eligible_indices)) From 253f927c0a45c4830c54f2fd99163e8191f9b6b3 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Sun, 22 Aug 2021 12:14:17 -0700 Subject: [PATCH 093/135] fix randomness seed across randomized test --- .../pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py index 93f31a4da..8928b9309 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -1,6 +1,6 @@ import itertools import warnings -from random import Random +import random from tests.core.pyspec.eth2spec.test.helpers.constants import PHASE0, ALTAIR from typing import Callable from tests.core.pyspec.eth2spec.test.context import ( @@ -276,7 +276,7 @@ def _generate_randomized_scenarios(): NOTE: the main block driver builds a block for the **next** slot, so the slot transitions are offset by -1 to target certain boundaries. """ - rng = Random(1336) + rng = random.Random(1336) # go forward 0 or 1 epochs epochs_set = ( @@ -377,6 +377,7 @@ def _iter_temporal(spec, callable_or_int): @single_phase @always_bls def test_harness_for_randomized_blocks(spec, state, test_description): + random.seed(1337) for mutation, validation in test_description["setup"]: mutation(spec, state) validation(spec, state) From 2db01ba6d06bb3290f6d2ac02b59152cee4615c0 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Sun, 22 Aug 2021 13:33:30 -0700 Subject: [PATCH 094/135] use fixed seed for block randomization --- .../eth2spec/test/phase0/sanity/test_blocks_random.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py index 8928b9309..7279e19d0 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -30,6 +30,8 @@ from eth2spec.test.context import ( misc_balances, ) +rng = random.Random(1337) + def _warn_if_empty_operations(block): if len(block.body.deposits) == 0: warnings.warn(f"deposits missing in block at slot {block.slot}") @@ -101,7 +103,7 @@ def _random_block(spec, state, _signed_blocks): to produce a block over ``BLOCK_ATTEMPTS`` slots in order to find a valid block in the event that the proposer has already been slashed. """ - block = build_random_block_from_state(spec, state) + block = build_random_block_from_state(spec, state, rng) for _ in range(BLOCK_ATTEMPTS): proposer = state.validators[block.proposer_index] if proposer.slashed: @@ -276,8 +278,6 @@ def _generate_randomized_scenarios(): NOTE: the main block driver builds a block for the **next** slot, so the slot transitions are offset by -1 to target certain boundaries. """ - rng = random.Random(1336) - # go forward 0 or 1 epochs epochs_set = ( _epoch_transition(n=0), @@ -377,7 +377,6 @@ def _iter_temporal(spec, callable_or_int): @single_phase @always_bls def test_harness_for_randomized_blocks(spec, state, test_description): - random.seed(1337) for mutation, validation in test_description["setup"]: mutation(spec, state) validation(spec, state) From fe1b9961ff413d7ca72eab3e9525a8851b92fe56 Mon Sep 17 00:00:00 2001 From: ericsson Date: Mon, 23 Aug 2021 14:21:38 +0300 Subject: [PATCH 095/135] Fix typos in sharding.md --- specs/sharding/beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 2e2c3d487..c7e528810 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -257,7 +257,7 @@ class AttestedDataCommitment(Container): includer_index: ValidatorIndex ``` -### ShardBlobBody +### `ShardBlobBody` Unsigned shard data, bundled by a shard-builder. Unique, signing different bodies as shard proposer for the same `(slot, shard)` is slashable. @@ -759,7 +759,7 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade commitment=body_summary.commitment, root=header_root, includer_index=get_beacon_proposer_index(state), - ) + ), votes=initial_votes, weight=0, update_slot=state.slot, @@ -885,7 +885,7 @@ def reset_pending_shard_work(state: BeaconState) -> None: selector=SHARD_WORK_PENDING, value=List[PendingShardHeader, MAX_SHARD_HEADERS_PER_SHARD]( PendingShardHeader( - attested=AttestedDataCommitment() + attested=AttestedDataCommitment(), votes=Bitlist[MAX_VALIDATORS_PER_COMMITTEE]([0] * committee_length), weight=0, update_slot=slot, From 43a6beceb68c7c60c5738c5dc00abec00b7c1d77 Mon Sep 17 00:00:00 2001 From: ericsson Date: Mon, 23 Aug 2021 14:46:06 +0300 Subject: [PATCH 096/135] make doctoc happy --- specs/sharding/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index c7e528810..031d8087f 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -32,7 +32,7 @@ - [`Builder`](#builder) - [`DataCommitment`](#datacommitment) - [`AttestedDataCommitment`](#attesteddatacommitment) - - [ShardBlobBody](#shardblobbody) + - [`ShardBlobBody`](#shardblobbody) - [`ShardBlobBodySummary`](#shardblobbodysummary) - [`ShardBlob`](#shardblob) - [`ShardBlobHeader`](#shardblobheader) From 838c263c4a82ffc7c43a426bfb91adc958e001c4 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 23 Aug 2021 23:21:15 +0800 Subject: [PATCH 097/135] Apply suggestions from code review Co-authored-by: Aditya Asgaonkar --- .../test/phase0/unittests/fork_choice/test_on_tick.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_tick.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_tick.py index 40606197e..0d9f6ddf5 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_tick.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_tick.py @@ -46,7 +46,7 @@ def test_update_justified_single_on_store_finalized_chain(spec, state): store.block_states[block.hash_tree_root()] = state.copy() parent_block = block.copy() # To make compute_slots_since_epoch_start(current_slot) == 0, transition to the end of the epoch - slot = state.slot + spec.SLOTS_PER_EPOCH - (state.slot + spec.SLOTS_PER_EPOCH) % spec.SLOTS_PER_EPOCH - 1 + slot = state.slot + spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH - 1 transition_to(spec, state, slot) # Create a block at the start of epoch 2 block = build_empty_block_for_next_slot(spec, state) @@ -100,7 +100,7 @@ def test_update_justified_single_not_on_store_finalized_chain(spec, state): store.block_states[block.hash_tree_root()] = state.copy() parent_block = block.copy() # To make compute_slots_since_epoch_start(current_slot) == 0, transition to the end of the epoch - slot = state.slot + spec.SLOTS_PER_EPOCH - (state.slot + spec.SLOTS_PER_EPOCH) % spec.SLOTS_PER_EPOCH - 1 + slot = state.slot + spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH - 1 transition_to(spec, state, slot) # Create a block at the start of epoch 2 block = build_empty_block_for_next_slot(spec, state) From 96c05adcf824ca403e5e5a22d3ed0e72886def23 Mon Sep 17 00:00:00 2001 From: ericsson Date: Mon, 23 Aug 2021 18:52:09 +0300 Subject: [PATCH 098/135] Fix typing problem: `is_merge_block` accepts `BeaconBlockBody` as a second argument, while `BeaconBlock` is provided --- specs/merge/fork-choice.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/merge/fork-choice.md b/specs/merge/fork-choice.md index 900d8b331..15ef1c301 100644 --- a/specs/merge/fork-choice.md +++ b/specs/merge/fork-choice.md @@ -129,7 +129,7 @@ def on_block(store: Store, signed_block: SignedBeaconBlock, transition_store: Tr 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): + 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) From 33552279bf218b109ad42f76408aa7c122544adf Mon Sep 17 00:00:00 2001 From: ericsson Date: Mon, 23 Aug 2021 20:09:01 +0300 Subject: [PATCH 099/135] Fix typos in `get_shard_proposer_index`: `beacon_state` vs `state` --- specs/sharding/beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 031d8087f..0249590b8 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -503,14 +503,14 @@ def get_active_shard_count(state: BeaconState, epoch: Epoch) -> uint64: #### `get_shard_proposer_index` ```python -def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard) -> ValidatorIndex: +def get_shard_proposer_index(state: BeaconState, slot: Slot, shard: Shard) -> ValidatorIndex: """ Return the proposer's index of shard block at ``slot``. """ epoch = compute_epoch_at_slot(slot) - seed = hash(get_seed(beacon_state, epoch, DOMAIN_SHARD_BLOB) + uint_to_bytes(slot) + uint_to_bytes(shard)) + seed = hash(get_seed(state, epoch, DOMAIN_SHARD_BLOB) + uint_to_bytes(slot) + uint_to_bytes(shard)) indices = get_active_validator_indices(state, epoch) - return compute_proposer_index(beacon_state, indices, seed) + return compute_proposer_index(state, indices, seed) ``` #### `get_start_shard` From 34d42b640d8cc72d11df3af4c3935bc619eac71c Mon Sep 17 00:00:00 2001 From: ericsson Date: Mon, 23 Aug 2021 20:09:41 +0300 Subject: [PATCH 100/135] Fix typo in `get_start_shard` --- specs/sharding/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 0249590b8..f54394275 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -520,7 +520,7 @@ def get_start_shard(state: BeaconState, slot: Slot) -> Shard: """ Return the start shard at ``slot``. """ - epoch = compute_epoch_at_slot(Slot(_slot)) + epoch = compute_epoch_at_slot(Slot(slot)) committee_count = get_committee_count_per_slot(state, epoch) active_shard_count = get_active_shard_count(state, epoch) return committee_count * slot % active_shard_count From 361d97c54bf0f97dd1a31142186b0ac02e7be83c Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 23 Aug 2021 09:54:00 -0700 Subject: [PATCH 101/135] fix bug with proposer search --- .../pyspec/eth2spec/test/helpers/multi_operations.py | 4 ++-- .../eth2spec/test/phase0/sanity/test_blocks_random.py | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py index 54e45b335..c53cc40c7 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -150,7 +150,7 @@ def get_random_voluntary_exits(spec, state, to_be_slashed_indices, rng): return prepare_signed_exits(spec, state, exit_indices) -def build_random_block_from_state(spec, state, rng=Random(2188)): +def build_random_block_from_state_for_next_slot(spec, state, rng=Random(2188)): # prepare state for deposits before building block deposits = prepare_state_and_get_random_deposits(spec, state, rng) @@ -177,7 +177,7 @@ def run_test_full_random_operations(spec, state, rng=Random(2080)): # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH - block = build_random_block_from_state(spec, state, rng) + block = build_random_block_from_state_for_next_slot(spec, state, rng) yield 'pre', state diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py index 7279e19d0..fb7b308f6 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -9,7 +9,7 @@ from tests.core.pyspec.eth2spec.test.context import ( zero_activation_threshold, ) from eth2spec.test.helpers.multi_operations import ( - build_random_block_from_state, + build_random_block_from_state_for_next_slot, ) from eth2spec.test.helpers.state import ( next_epoch, @@ -103,13 +103,16 @@ def _random_block(spec, state, _signed_blocks): to produce a block over ``BLOCK_ATTEMPTS`` slots in order to find a valid block in the event that the proposer has already been slashed. """ - block = build_random_block_from_state(spec, state, rng) + temp_state = state.copy() + next_slot(spec, temp_state) for _ in range(BLOCK_ATTEMPTS): - proposer = state.validators[block.proposer_index] + proposer_index = spec.get_beacon_proposer_index(temp_state) + proposer = state.validators[proposer_index] if proposer.slashed: next_slot(spec, state) - block = build_random_block_from_state(spec, state) + next_slot(spec, temp_state) else: + block = build_random_block_from_state_for_next_slot(spec, state) _warn_if_empty_operations(block) return block else: From 6316c7d364888264b209c6cbd01b9e6373fe6c0b Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 23 Aug 2021 10:29:39 -0700 Subject: [PATCH 102/135] ensure at least 1 attester slashing --- tests/core/pyspec/eth2spec/test/helpers/multi_operations.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py index c53cc40c7..962ab6404 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -61,7 +61,9 @@ def get_random_proposer_slashings(spec, state, rng): def get_random_attester_slashings(spec, state, rng): - num_slashings = rng.randrange(spec.MAX_ATTESTER_SLASHINGS) + # ensure at least one attester slashing, the max count + # is small so not much room for random inclusion + num_slashings = max(1, rng.randrange(spec.MAX_ATTESTER_SLASHINGS)) active_indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy() indices = [ index for index in active_indices From 16423880aa6d8335125e6c73507848c846289c4a Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 23 Aug 2021 11:00:33 -0700 Subject: [PATCH 103/135] add multiple blocks to each test --- .../test/phase0/sanity/test_blocks_random.py | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py index fb7b308f6..4051f9c7d 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -52,6 +52,8 @@ def _warn_if_empty_operations(block): # May need to make several attempts to find a block that does not correspond to a slashed # proposer with the randomization helpers... BLOCK_ATTEMPTS = 32 +# Ensure this many blocks are present in *each* randomized scenario +BLOCK_TRANSITIONS_COUNT = 2 # primitives ## state @@ -269,6 +271,15 @@ def _normalize_scenarios(scenarios): transitions[i] = _normalize_transition(transition) +def _flatten(t): + leak_transition = t[0] + result = [leak_transition] + for transition_batch in t[1]: + for transition in transition_batch: + result.append(transition) + return result + + def _generate_randomized_scenarios(): """ Generates a set of randomized testing scenarios. @@ -297,20 +308,24 @@ def _generate_randomized_scenarios(): # the last slot in an epoch (see note in docstring about offsets...) _slot_transition(_penultimate_slot_in_epoch), ) - # build a set of block transitions from combinations of sub-transitions - block_transitions = list( - _transition_with_random_block(epochs=epochs, slots=slots) - for epochs, slots in itertools.product(epochs_set, slots_set) + # and produce a block... + blocks_set = ( + _transition_with_random_block, ) + # build a set of block transitions from combinations of sub-transitions + transitions_generator = ( + itertools.product(epochs_set, slots_set, blocks_set) for + _ in range(BLOCK_TRANSITIONS_COUNT) + ) + block_transitions = zip(*transitions_generator) - # and preface each block transition with the possible leak transitions - # (... either no leak or transition to a leak before applying the block transition) + # and preface each set of block transitions with the possible leak transitions leak_transitions = ( _transition_without_leak, _transition_to_leaking, ) scenarios = [ - {"transitions": list(t)} + {"transitions": _flatten(t)} for t in itertools.product(leak_transitions, block_transitions) ] _normalize_scenarios(scenarios) From 20e3934fa2fd66ca7dcc53b67f75fe088ae93ac2 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 23 Aug 2021 11:12:40 -0700 Subject: [PATCH 104/135] do not exit validators who are already exited --- tests/core/pyspec/eth2spec/test/helpers/multi_operations.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py index 962ab6404..52234fa97 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -136,7 +136,9 @@ def _eligible_for_exit(spec, state, index): activation_epoch = validator.activation_epoch active_for_long_enough = current_epoch >= activation_epoch + spec.config.SHARD_COMMITTEE_PERIOD - return not_slashed and active_for_long_enough + not_exited = validator.exit_epoch == spec.FAR_FUTURE_EPOCH + + return not_slashed and active_for_long_enough and not_exited def get_random_voluntary_exits(spec, state, to_be_slashed_indices, rng): From 31d46247ce403651d98dc4a2bcbb41c799a4533e Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 23 Aug 2021 11:48:22 -0700 Subject: [PATCH 105/135] file re-org to re-use for later forks --- .../test/phase0/sanity/test_blocks_random.py | 403 +----------------- .../pyspec/eth2spec/test/utils/__init__.py | 5 + .../core/pyspec/eth2spec/test/utils/random.py | 398 +++++++++++++++++ .../pyspec/eth2spec/test/{ => utils}/utils.py | 0 4 files changed, 412 insertions(+), 394 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/utils/__init__.py create mode 100644 tests/core/pyspec/eth2spec/test/utils/random.py rename tests/core/pyspec/eth2spec/test/{ => utils}/utils.py (100%) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py index 4051f9c7d..6ea793206 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -1,392 +1,30 @@ -import itertools -import warnings -import random -from tests.core.pyspec.eth2spec.test.helpers.constants import PHASE0, ALTAIR -from typing import Callable +from tests.core.pyspec.eth2spec.test.helpers.constants import PHASE0 from tests.core.pyspec.eth2spec.test.context import ( misc_balances_in_default_range_with_many_validators, with_phases, zero_activation_threshold, ) -from eth2spec.test.helpers.multi_operations import ( - build_random_block_from_state_for_next_slot, -) -from eth2spec.test.helpers.state import ( - next_epoch, - next_slot, - ensure_state_has_validators_across_lifecycle, - state_transition_and_sign_block, -) -from eth2spec.test.helpers.random import ( - randomize_state, -) from eth2spec.test.context import ( - with_all_phases, always_bls, spec_test, with_custom_state, - default_activation_threshold, single_phase, - misc_balances, ) - -rng = random.Random(1337) - -def _warn_if_empty_operations(block): - if len(block.body.deposits) == 0: - warnings.warn(f"deposits missing in block at slot {block.slot}") - - if len(block.body.proposer_slashings) == 0: - warnings.warn(f"proposer slashings missing in block at slot {block.slot}") - - if len(block.body.attester_slashings) == 0: - warnings.warn(f"attester slashings missing in block at slot {block.slot}") - - if len(block.body.attestations) == 0: - warnings.warn(f"attestations missing in block at slot {block.slot}") - - if len(block.body.voluntary_exits) == 0: - warnings.warn(f"voluntary exits missing in block at slot {block.slot}") - - -# May need to make several attempts to find a block that does not correspond to a slashed -# proposer with the randomization helpers... -BLOCK_ATTEMPTS = 32 -# Ensure this many blocks are present in *each* randomized scenario -BLOCK_TRANSITIONS_COUNT = 2 - -# primitives -## state - -def _randomize_state(spec, state): - return randomize_state(spec, state, exit_fraction=0.1, slash_fraction=0.1) - - -## epochs - -def _epochs_until_leak(spec): - """ - State is "leaking" if the current epoch is at least - this value after the last finalized epoch. - """ - return spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + 1 - - -def _epochs_for_shard_committee_period(spec): - return spec.config.SHARD_COMMITTEE_PERIOD - - -## slots - -def _last_slot_in_epoch(spec): - return spec.SLOTS_PER_EPOCH - 1 - - -def _random_slot_in_epoch(rng): - def _a_slot_in_epoch(spec): - return rng.randrange(1, spec.SLOTS_PER_EPOCH - 2) - return _a_slot_in_epoch - - -def _penultimate_slot_in_epoch(spec): - return spec.SLOTS_PER_EPOCH - 2 - - -## blocks - -def _no_block(_spec, _pre_state, _signed_blocks): - return None - - -def _random_block(spec, state, _signed_blocks): - """ - Produce a random block. - NOTE: this helper may mutate state, as it will attempt - to produce a block over ``BLOCK_ATTEMPTS`` slots in order - to find a valid block in the event that the proposer has already been slashed. - """ - temp_state = state.copy() - next_slot(spec, temp_state) - for _ in range(BLOCK_ATTEMPTS): - proposer_index = spec.get_beacon_proposer_index(temp_state) - proposer = state.validators[proposer_index] - if proposer.slashed: - next_slot(spec, state) - next_slot(spec, temp_state) - else: - block = build_random_block_from_state_for_next_slot(spec, state) - _warn_if_empty_operations(block) - return block - else: - raise AssertionError("could not find a block with an unslashed proposer, check ``state`` input") - - -## validations - -def _no_op_validation(spec, state): - return True - - -def _validate_is_leaking(spec, state): - return spec.is_in_inactivity_leak(state) - - -def _validate_is_not_leaking(spec, state): - return not _validate_is_leaking(spec, state) - - -# transitions - -def _with_validation(transition, validation): - if isinstance(transition, Callable): - transition = transition() - transition["validation"] = validation - return transition - - -def _no_op_transition(): - return {} - - -def _epoch_transition(n=0): - return { - "epochs_to_skip": n, - } - - -def _slot_transition(n=0): - return { - "slots_to_skip": n, - } - - -def _transition_to_leaking(): - return { - "epochs_to_skip": _epochs_until_leak, - "validation": _validate_is_leaking, - } - - -_transition_without_leak = _with_validation(_no_op_transition, _validate_is_not_leaking) - -## block transitions - -def _transition_with_random_block(epochs=None, slots=None): - """ - Build a block transition with randomized data. - Provide optional sub-transitions to advance some - number of epochs or slots before applying the random block. - """ - transition = { - "block_producer": _random_block, - } - if epochs: - transition.update(epochs) - if slots: - transition.update(slots) - return transition - - -# setup and test gen - -def _randomized_scenario_setup(): - """ - Return a sequence of pairs of ("mutator", "validator"), - a function that accepts (spec, state) arguments and performs some change - and a function that accepts (spec, state) arguments and validates some change was made. - """ - def _skip_epochs(epoch_producer): - def f(spec, state): - """ - The unoptimized spec implementation is too slow to advance via ``next_epoch``. - Instead, just overwrite the ``state.slot`` and continue... - """ - epochs_to_skip = epoch_producer(spec) - slots_to_skip = epochs_to_skip * spec.SLOTS_PER_EPOCH - state.slot += slots_to_skip - return f - - def _simulate_honest_execution(spec, state): - """ - Want to start tests not in a leak state; the finality data - 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, - ) - - return ( - # NOTE: the block randomization function assumes at least 1 shard committee period - # so advance the state before doing anything else. - (_skip_epochs(_epochs_for_shard_committee_period), _no_op_validation), - (_simulate_honest_execution, _no_op_validation), - (_randomize_state, ensure_state_has_validators_across_lifecycle), - ) - - -def _normalize_transition(transition): - """ - Provide "empty" or "no op" sub-transitions - to a given transition. - """ - if isinstance(transition, Callable): - transition = transition() - if "epochs_to_skip" not in transition: - transition["epochs_to_skip"] = 0 - if "slots_to_skip" not in transition: - transition["slots_to_skip"] = 0 - if "block_producer" not in transition: - transition["block_producer"] = _no_block - if "validation" not in transition: - transition["validation"] = _no_op_validation - return transition - - -def _normalize_scenarios(scenarios): - """ - "Normalize" a "scenario" so that a producer of a test case - does not need to provide every expected key/value. - """ - for scenario in scenarios: - if "setup" not in scenario: - scenario["setup"] = _randomized_scenario_setup() - - transitions = scenario["transitions"] - for i, transition in enumerate(transitions): - transitions[i] = _normalize_transition(transition) - - -def _flatten(t): - leak_transition = t[0] - result = [leak_transition] - for transition_batch in t[1]: - for transition in transition_batch: - result.append(transition) - return result - - -def _generate_randomized_scenarios(): - """ - Generates a set of randomized testing scenarios. - Return a sequence of "scenarios" where each scenario: - 1. Provides some setup - 2. Provides a sequence of transitions that mutate the state in some way, - possibly yielding blocks along the way - NOTE: scenarios are "normalized" with empty/no-op elements before returning - to the test generation to facilitate brevity when writing scenarios by hand. - NOTE: the main block driver builds a block for the **next** slot, so - the slot transitions are offset by -1 to target certain boundaries. - """ - # go forward 0 or 1 epochs - epochs_set = ( - _epoch_transition(n=0), - _epoch_transition(n=1), - ) - # within those epochs, go forward to: - slots_set = ( - # the first slot in an epoch (see note in docstring about offsets...) - _slot_transition(_last_slot_in_epoch), - # the second slot in an epoch - _slot_transition(n=0), - # some random number of slots, but not at epoch boundaries - _slot_transition(_random_slot_in_epoch(rng)), - # the last slot in an epoch (see note in docstring about offsets...) - _slot_transition(_penultimate_slot_in_epoch), - ) - # and produce a block... - blocks_set = ( - _transition_with_random_block, - ) - # build a set of block transitions from combinations of sub-transitions - transitions_generator = ( - itertools.product(epochs_set, slots_set, blocks_set) for - _ in range(BLOCK_TRANSITIONS_COUNT) - ) - block_transitions = zip(*transitions_generator) - - # and preface each set of block transitions with the possible leak transitions - leak_transitions = ( - _transition_without_leak, - _transition_to_leaking, - ) - scenarios = [ - {"transitions": _flatten(t)} - for t in itertools.product(leak_transitions, block_transitions) - ] - _normalize_scenarios(scenarios) - return scenarios - - -def _id_from_scenario(test_description): - """ - Construct a test name for ``pytest`` infra. - """ - def _to_id_part(prefix, x): - suffix = str(x) - if isinstance(x, Callable): - suffix = x.__name__ - return f"{prefix}{suffix}" - - def _id_from_transition(transition): - return ",".join(( - _to_id_part("epochs:", transition["epochs_to_skip"]), - _to_id_part("slots:", transition["slots_to_skip"]), - _to_id_part("with-block:", transition["block_producer"]) - )) - - return "|".join(map(_id_from_transition, test_description["transitions"])) - +from eth2spec.test.utils.random import ( + generate_randomized_tests, + pytest_generate_tests_adapter, + run_generated_randomized_test, +) def pytest_generate_tests(metafunc): """ Pytest hook to generate test cases from dynamically computed data """ - generated_name = "test_description" - generated_values = _generate_randomized_scenarios() - metafunc.parametrize(generated_name, generated_values, ids=_id_from_scenario, scope="module") - - -def pytest_generate_tests_adapter(f): - """ - Adapter decorator to allow dynamic test case generation - while leveraging existing decorators specific to spec tests. - """ - def wrapper(test_description, *args, **kwargs): - kwargs["test_description"] = test_description - f(*args, **kwargs) - return wrapper - - -def _iter_temporal(spec, callable_or_int): - """ - Intended to advance some number of {epochs, slots}. - Caller can provide a constant integer or a callable deriving a number from - the ``spec`` under consideration. - """ - numeric = callable_or_int - if isinstance(callable_or_int, Callable): - numeric = callable_or_int(spec) - for i in range(numeric): - yield i + generate_randomized_tests(metafunc) @pytest_generate_tests_adapter -@with_phases([PHASE0, ALTAIR]) +@with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, threshold_fn=zero_activation_threshold @@ -395,27 +33,4 @@ def _iter_temporal(spec, callable_or_int): @single_phase @always_bls def test_harness_for_randomized_blocks(spec, state, test_description): - for mutation, validation in test_description["setup"]: - mutation(spec, state) - validation(spec, state) - - yield "pre", state - - blocks = [] - for transition in test_description["transitions"]: - epochs_to_skip = _iter_temporal(spec, transition["epochs_to_skip"]) - for _ in epochs_to_skip: - next_epoch(spec, state) - slots_to_skip = _iter_temporal(spec, transition["slots_to_skip"]) - for _ in slots_to_skip: - next_slot(spec, state) - - block = transition["block_producer"](spec, state, blocks) - if block: - signed_block = state_transition_and_sign_block(spec, state, block) - blocks.append(signed_block) - - assert transition["validation"](spec, state) - - yield "blocks", blocks - yield "post", state + yield from run_generated_randomized_test(spec, state, test_description) diff --git a/tests/core/pyspec/eth2spec/test/utils/__init__.py b/tests/core/pyspec/eth2spec/test/utils/__init__.py new file mode 100644 index 000000000..56e4d44ca --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/utils/__init__.py @@ -0,0 +1,5 @@ +from .utils import ( + vector_test, + with_meta_tags, + build_transition_test, +) diff --git a/tests/core/pyspec/eth2spec/test/utils/random.py b/tests/core/pyspec/eth2spec/test/utils/random.py new file mode 100644 index 000000000..f28f7ff70 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/utils/random.py @@ -0,0 +1,398 @@ +""" +Utility code to generate randomized block tests +""" + +import itertools +import random +import warnings +from typing import Callable +from eth2spec.test.helpers.multi_operations import ( + build_random_block_from_state_for_next_slot, +) +from eth2spec.test.helpers.state import ( + next_slot, + next_epoch, + ensure_state_has_validators_across_lifecycle, + state_transition_and_sign_block, +) +from eth2spec.test.helpers.random import ( + randomize_state, +) + +rng = random.Random(1337) + +def _warn_if_empty_operations(block): + if len(block.body.deposits) == 0: + warnings.warn(f"deposits missing in block at slot {block.slot}") + + if len(block.body.proposer_slashings) == 0: + warnings.warn(f"proposer slashings missing in block at slot {block.slot}") + + if len(block.body.attester_slashings) == 0: + warnings.warn(f"attester slashings missing in block at slot {block.slot}") + + if len(block.body.attestations) == 0: + warnings.warn(f"attestations missing in block at slot {block.slot}") + + if len(block.body.voluntary_exits) == 0: + warnings.warn(f"voluntary exits missing in block at slot {block.slot}") + + +# May need to make several attempts to find a block that does not correspond to a slashed +# proposer with the randomization helpers... +BLOCK_ATTEMPTS = 32 +# Ensure this many blocks are present in *each* randomized scenario +BLOCK_TRANSITIONS_COUNT = 2 + +# primitives +## state + +def _randomize_state(spec, state): + return randomize_state(spec, state, exit_fraction=0.1, slash_fraction=0.1) + + +## epochs + +def _epochs_until_leak(spec): + """ + State is "leaking" if the current epoch is at least + this value after the last finalized epoch. + """ + return spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + 1 + + +def _epochs_for_shard_committee_period(spec): + return spec.config.SHARD_COMMITTEE_PERIOD + + +## slots + +def _last_slot_in_epoch(spec): + return spec.SLOTS_PER_EPOCH - 1 + + +def _random_slot_in_epoch(rng): + def _a_slot_in_epoch(spec): + return rng.randrange(1, spec.SLOTS_PER_EPOCH - 2) + return _a_slot_in_epoch + + +def _penultimate_slot_in_epoch(spec): + return spec.SLOTS_PER_EPOCH - 2 + + +## blocks + +def _no_block(_spec, _pre_state, _signed_blocks): + return None + + +def _random_block(spec, state, _signed_blocks): + """ + Produce a random block. + NOTE: this helper may mutate state, as it will attempt + to produce a block over ``BLOCK_ATTEMPTS`` slots in order + to find a valid block in the event that the proposer has already been slashed. + """ + temp_state = state.copy() + next_slot(spec, temp_state) + for _ in range(BLOCK_ATTEMPTS): + proposer_index = spec.get_beacon_proposer_index(temp_state) + proposer = state.validators[proposer_index] + if proposer.slashed: + next_slot(spec, state) + next_slot(spec, temp_state) + else: + block = build_random_block_from_state_for_next_slot(spec, state) + _warn_if_empty_operations(block) + return block + else: + raise AssertionError("could not find a block with an unslashed proposer, check ``state`` input") + + +## validations + +def _no_op_validation(spec, state): + return True + + +def _validate_is_leaking(spec, state): + return spec.is_in_inactivity_leak(state) + + +def _validate_is_not_leaking(spec, state): + return not _validate_is_leaking(spec, state) + + +# transitions + +def _with_validation(transition, validation): + if isinstance(transition, Callable): + transition = transition() + transition["validation"] = validation + return transition + + +def _no_op_transition(): + return {} + + +def _epoch_transition(n=0): + return { + "epochs_to_skip": n, + } + + +def _slot_transition(n=0): + return { + "slots_to_skip": n, + } + + +def _transition_to_leaking(): + return { + "epochs_to_skip": _epochs_until_leak, + "validation": _validate_is_leaking, + } + + +_transition_without_leak = _with_validation(_no_op_transition, _validate_is_not_leaking) + +## block transitions + +def _transition_with_random_block(block_randomizer): + """ + Build a block transition with randomized data. + Provide optional sub-transitions to advance some + number of epochs or slots before applying the random block. + """ + return { + "block_producer": block_randomizer, + } + + +# setup and test gen + +def _randomized_scenario_setup(state_randomizer): + """ + Return a sequence of pairs of ("mutator", "validator"), + a function that accepts (spec, state) arguments and performs some change + and a function that accepts (spec, state) arguments and validates some change was made. + """ + def _skip_epochs(epoch_producer): + def f(spec, state): + """ + The unoptimized spec implementation is too slow to advance via ``next_epoch``. + Instead, just overwrite the ``state.slot`` and continue... + """ + epochs_to_skip = epoch_producer(spec) + slots_to_skip = epochs_to_skip * spec.SLOTS_PER_EPOCH + state.slot += slots_to_skip + return f + + def _simulate_honest_execution(spec, state): + """ + Want to start tests not in a leak state; the finality data + 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, + ) + + return ( + # NOTE: the block randomization function assumes at least 1 shard committee period + # so advance the state before doing anything else. + (_skip_epochs(_epochs_for_shard_committee_period), _no_op_validation), + (_simulate_honest_execution, _no_op_validation), + (state_randomizer, ensure_state_has_validators_across_lifecycle), + ) + + +def _normalize_transition(transition): + """ + Provide "empty" or "no op" sub-transitions + to a given transition. + """ + if isinstance(transition, Callable): + transition = transition() + if "epochs_to_skip" not in transition: + transition["epochs_to_skip"] = 0 + if "slots_to_skip" not in transition: + transition["slots_to_skip"] = 0 + if "block_producer" not in transition: + transition["block_producer"] = _no_block + if "validation" not in transition: + transition["validation"] = _no_op_validation + return transition + + +def _normalize_scenarios(scenarios, state_randomizer): + """ + "Normalize" a "scenario" so that a producer of a test case + does not need to provide every expected key/value. + """ + for scenario in scenarios: + if "setup" not in scenario: + scenario["setup"] = _randomized_scenario_setup(state_randomizer) + + transitions = scenario["transitions"] + for i, transition in enumerate(transitions): + transitions[i] = _normalize_transition(transition) + + +def _flatten(t): + leak_transition = t[0] + result = [leak_transition] + for transition_batch in t[1]: + for transition in transition_batch: + result.append(transition) + return result + + +def _generate_randomized_scenarios(state_randomizer, block_randomizer): + """ + Generates a set of randomized testing scenarios. + Return a sequence of "scenarios" where each scenario: + 1. Provides some setup + 2. Provides a sequence of transitions that mutate the state in some way, + possibly yielding blocks along the way + NOTE: scenarios are "normalized" with empty/no-op elements before returning + to the test generation to facilitate brevity when writing scenarios by hand. + NOTE: the main block driver builds a block for the **next** slot, so + the slot transitions are offset by -1 to target certain boundaries. + """ + # go forward 0 or 1 epochs + epochs_set = ( + _epoch_transition(n=0), + _epoch_transition(n=1), + ) + # within those epochs, go forward to: + slots_set = ( + # the first slot in an epoch (see note in docstring about offsets...) + _slot_transition(_last_slot_in_epoch), + # the second slot in an epoch + _slot_transition(n=0), + # some random number of slots, but not at epoch boundaries + _slot_transition(_random_slot_in_epoch(rng)), + # the last slot in an epoch (see note in docstring about offsets...) + _slot_transition(_penultimate_slot_in_epoch), + ) + # and produce a block... + blocks_set = ( + _transition_with_random_block(block_randomizer), + ) + # build a set of block transitions from combinations of sub-transitions + transitions_generator = ( + itertools.product(epochs_set, slots_set, blocks_set) for + _ in range(BLOCK_TRANSITIONS_COUNT) + ) + block_transitions = zip(*transitions_generator) + + # and preface each set of block transitions with the possible leak transitions + leak_transitions = ( + _transition_without_leak, + _transition_to_leaking, + ) + scenarios = [ + {"transitions": _flatten(t)} + for t in itertools.product(leak_transitions, block_transitions) + ] + _normalize_scenarios(scenarios, state_randomizer) + return scenarios + + +def _id_from_scenario(test_description): + """ + Construct a test name for ``pytest`` infra. + """ + def _to_id_part(prefix, x): + suffix = str(x) + if isinstance(x, Callable): + suffix = x.__name__ + return f"{prefix}{suffix}" + + def _id_from_transition(transition): + return ",".join(( + _to_id_part("epochs:", transition["epochs_to_skip"]), + _to_id_part("slots:", transition["slots_to_skip"]), + _to_id_part("with-block:", transition["block_producer"]) + )) + + return "|".join(map(_id_from_transition, test_description["transitions"])) + +# Generate a series of randomized block tests: + +def generate_randomized_tests(metafunc, state_randomizer=_randomize_state, block_randomizer=_random_block): + """ + Pytest hook to generate test cases from dynamically computed data + """ + generated_name = "test_description" + generated_values = _generate_randomized_scenarios(state_randomizer, block_randomizer) + metafunc.parametrize(generated_name, generated_values, ids=_id_from_scenario, scope="module") + + +def pytest_generate_tests_adapter(f): + """ + Adapter decorator to allow dynamic test case generation + while leveraging existing decorators specific to spec tests. + """ + def wrapper(test_description, *args, **kwargs): + kwargs["test_description"] = test_description + f(*args, **kwargs) + return wrapper + +# Run the generated tests: + +def _iter_temporal(spec, callable_or_int): + """ + Intended to advance some number of {epochs, slots}. + Caller can provide a constant integer or a callable deriving a number from + the ``spec`` under consideration. + """ + numeric = callable_or_int + if isinstance(callable_or_int, Callable): + numeric = callable_or_int(spec) + for i in range(numeric): + yield i + + +def run_generated_randomized_test(spec, state, test_description): + for mutation, validation in test_description["setup"]: + mutation(spec, state) + validation(spec, state) + + yield "pre", state + + blocks = [] + for transition in test_description["transitions"]: + epochs_to_skip = _iter_temporal(spec, transition["epochs_to_skip"]) + for _ in epochs_to_skip: + next_epoch(spec, state) + slots_to_skip = _iter_temporal(spec, transition["slots_to_skip"]) + for _ in slots_to_skip: + next_slot(spec, state) + + block = transition["block_producer"](spec, state, blocks) + if block: + signed_block = state_transition_and_sign_block(spec, state, block) + blocks.append(signed_block) + + assert transition["validation"](spec, state) + + yield "blocks", blocks + yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/utils.py b/tests/core/pyspec/eth2spec/test/utils/utils.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/utils.py rename to tests/core/pyspec/eth2spec/test/utils/utils.py From ff6863e6899a92c89006d985625587ef6da4ac68 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 23 Aug 2021 12:01:05 -0700 Subject: [PATCH 106/135] fix bug with deposit generation code --- tests/core/pyspec/eth2spec/test/helpers/multi_operations.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py index 52234fa97..32998b3ba 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -103,6 +103,7 @@ def prepare_state_and_get_random_deposits(spec, state, rng): deposits = [] # First build deposit data leaves + root = None for i in range(num_deposits): index = len(state.validators) + i _, root, deposit_data_leaves = build_deposit( @@ -115,7 +116,9 @@ def prepare_state_and_get_random_deposits(spec, state, rng): signed=True, ) - state.eth1_data.deposit_root = root + if root: + # NOTE: if ``num_deposits == 0``, ``root`` is never assigned to + state.eth1_data.deposit_root = root state.eth1_data.deposit_count += num_deposits # Then for that context, build deposits/proofs From 7b9d70fcec86f82a33256c6fa52bcab6e8cfce07 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 23 Aug 2021 12:15:09 -0700 Subject: [PATCH 107/135] allow test customization (for future forks) --- tests/core/pyspec/eth2spec/test/utils/random.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/utils/random.py b/tests/core/pyspec/eth2spec/test/utils/random.py index f28f7ff70..b50a67349 100644 --- a/tests/core/pyspec/eth2spec/test/utils/random.py +++ b/tests/core/pyspec/eth2spec/test/utils/random.py @@ -16,7 +16,7 @@ from eth2spec.test.helpers.state import ( state_transition_and_sign_block, ) from eth2spec.test.helpers.random import ( - randomize_state, + randomize_state as randomize_state_helper, ) rng = random.Random(1337) @@ -47,8 +47,8 @@ BLOCK_TRANSITIONS_COUNT = 2 # primitives ## state -def _randomize_state(spec, state): - return randomize_state(spec, state, exit_fraction=0.1, slash_fraction=0.1) +def randomize_state(spec, state): + randomize_state_helper(spec, state, exit_fraction=0.1, slash_fraction=0.1) ## epochs @@ -87,7 +87,7 @@ def _no_block(_spec, _pre_state, _signed_blocks): return None -def _random_block(spec, state, _signed_blocks): +def random_block(spec, state, _signed_blocks): """ Produce a random block. NOTE: this helper may mutate state, as it will attempt @@ -337,7 +337,7 @@ def _id_from_scenario(test_description): # Generate a series of randomized block tests: -def generate_randomized_tests(metafunc, state_randomizer=_randomize_state, block_randomizer=_random_block): +def generate_randomized_tests(metafunc, state_randomizer=randomize_state, block_randomizer=random_block): """ Pytest hook to generate test cases from dynamically computed data """ From 58c6f33e8522a176b2065bec9e21aa5fce1ffe6b Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 23 Aug 2021 12:15:22 -0700 Subject: [PATCH 108/135] ensure at least one proposer slashing --- tests/core/pyspec/eth2spec/test/helpers/multi_operations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py index 32998b3ba..68ca02b91 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -44,7 +44,7 @@ def run_slash_and_exit(spec, state, slash_index, exit_index, valid=True): def get_random_proposer_slashings(spec, state, rng): - num_slashings = rng.randrange(spec.MAX_PROPOSER_SLASHINGS) + num_slashings = max(1, rng.randrange(spec.MAX_PROPOSER_SLASHINGS)) active_indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy() indices = [ index for index in active_indices From cc04da8e795ca9d8774f033434765e5be2edafe6 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 23 Aug 2021 12:30:05 -0700 Subject: [PATCH 109/135] add randomized block tests for altair --- .../test/altair/sanity/test_blocks_random.py | 63 +++++++++++++++++++ .../eth2spec/test/helpers/multi_operations.py | 20 ++++++ .../core/pyspec/eth2spec/test/utils/random.py | 4 +- tests/generators/sanity/main.py | 1 + 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks_random.py diff --git a/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks_random.py new file mode 100644 index 000000000..b0c0e86b5 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks_random.py @@ -0,0 +1,63 @@ +from tests.core.pyspec.eth2spec.test.helpers.constants import ALTAIR +from tests.core.pyspec.eth2spec.test.context import ( + misc_balances_in_default_range_with_many_validators, + with_phases, + zero_activation_threshold, +) +from eth2spec.test.helpers.multi_operations import ( + get_random_sync_aggregate, +) +from eth2spec.test.helpers.inactivity_scores import ( + randomize_inactivity_scores, +) +from eth2spec.test.context import ( + always_bls, + spec_test, + with_custom_state, + single_phase, +) +from eth2spec.test.utils.random import ( + generate_randomized_tests, + pytest_generate_tests_adapter, + run_generated_randomized_test, + random_block, + randomize_state, +) + +SYNC_AGGREGATE_PARTICIPATION_BUCKETS = 4 + +def _randomize_altair_state(spec, state): + randomize_state(spec, state, exit_fraction=0.1, slash_fraction=0.1) + randomize_inactivity_scores(spec, state) + + +def _randomize_altair_block(spec, state, signed_blocks): + block = random_block(spec, state, signed_blocks) + fraction_missed = len(signed_blocks) / SYNC_AGGREGATE_PARTICIPATION_BUCKETS + fraction_participated = 1.0 - fraction_missed + block.body.sync_aggregate = get_random_sync_aggregate(spec, state, fraction_participated=fraction_participated) + return block + + +def pytest_generate_tests(metafunc): + """ + Pytest hook to generate test cases from dynamically computed data + """ + generate_randomized_tests( + metafunc, + state_randomizer=_randomize_altair_state, + block_randomizer=_randomize_altair_block, + ) + + +@pytest_generate_tests_adapter +@with_phases([ALTAIR]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_harness_for_randomized_blocks(spec, state, test_description): + yield from run_generated_randomized_test(spec, state, test_description) diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py index 68ca02b91..10de14253 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -7,6 +7,10 @@ from eth2spec.test.helpers.state import ( from eth2spec.test.helpers.block import ( build_empty_block_for_next_slot, ) +from eth2spec.test.helpers.sync_committee import ( + compute_committee_indices, + compute_aggregate_sync_committee_signature, +) from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing_by_indices from eth2spec.test.helpers.attestations import get_valid_attestation @@ -192,3 +196,19 @@ def run_test_full_random_operations(spec, state, rng=Random(2080)): yield 'blocks', [signed_block] yield 'post', state + + +def get_random_sync_aggregate(spec, state, fraction_participated=1.0, rng=Random(2099)): + committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) + participant_count = int(len(committee_indices) * fraction_participated) + participants = rng.sample(committee_indices, participant_count) + signature = compute_aggregate_sync_committee_signature( + spec, + state, + state.slot, + participants, + ) + return spec.SyncAggregate( + sync_committee_bits=[index in participants for index in committee_indices], + sync_committee_signature=signature, + ) diff --git a/tests/core/pyspec/eth2spec/test/utils/random.py b/tests/core/pyspec/eth2spec/test/utils/random.py index b50a67349..5a7067b3f 100644 --- a/tests/core/pyspec/eth2spec/test/utils/random.py +++ b/tests/core/pyspec/eth2spec/test/utils/random.py @@ -47,8 +47,8 @@ BLOCK_TRANSITIONS_COUNT = 2 # primitives ## state -def randomize_state(spec, state): - randomize_state_helper(spec, state, exit_fraction=0.1, slash_fraction=0.1) +def randomize_state(spec, state, exit_fraction=0.1, slash_fraction=0.1): + randomize_state_helper(spec, state, exit_fraction=exit_fraction, slash_fraction=slash_fraction) ## epochs diff --git a/tests/generators/sanity/main.py b/tests/generators/sanity/main.py index 63efa3897..89e622f1c 100644 --- a/tests/generators/sanity/main.py +++ b/tests/generators/sanity/main.py @@ -10,6 +10,7 @@ if __name__ == "__main__": ]} altair_mods = {**{key: 'eth2spec.test.altair.sanity.test_' + key for key in [ 'blocks', + 'blocks_random', ]}, **phase_0_mods} # also run the previous phase 0 tests # Altair-specific test cases are ignored, but should be included after the Merge is rebased onto Altair work. From d037c6662acb4329269a848af9106a70e8eacb35 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 23 Aug 2021 12:40:42 -0700 Subject: [PATCH 110/135] lint fix --- .../test/altair/sanity/test_blocks_random.py | 1 + .../test/phase0/sanity/test_blocks_random.py | 1 + .../pyspec/eth2spec/test/utils/__init__.py | 7 +++++++ .../core/pyspec/eth2spec/test/utils/random.py | 18 ++++++++++++------ 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks_random.py index b0c0e86b5..bc391b1f7 100644 --- a/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks_random.py @@ -26,6 +26,7 @@ from eth2spec.test.utils.random import ( SYNC_AGGREGATE_PARTICIPATION_BUCKETS = 4 + def _randomize_altair_state(spec, state): randomize_state(spec, state, exit_fraction=0.1, slash_fraction=0.1) randomize_inactivity_scores(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py index 6ea793206..1244785cd 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -16,6 +16,7 @@ from eth2spec.test.utils.random import ( run_generated_randomized_test, ) + def pytest_generate_tests(metafunc): """ Pytest hook to generate test cases from dynamically computed data diff --git a/tests/core/pyspec/eth2spec/test/utils/__init__.py b/tests/core/pyspec/eth2spec/test/utils/__init__.py index 56e4d44ca..f6b2a8a44 100644 --- a/tests/core/pyspec/eth2spec/test/utils/__init__.py +++ b/tests/core/pyspec/eth2spec/test/utils/__init__.py @@ -3,3 +3,10 @@ from .utils import ( with_meta_tags, build_transition_test, ) + + +__all__ = [ # avoid "unused import" lint error + "vector_test", + "with_meta_tags", + "build_transition_test", +] diff --git a/tests/core/pyspec/eth2spec/test/utils/random.py b/tests/core/pyspec/eth2spec/test/utils/random.py index 5a7067b3f..da8e391e8 100644 --- a/tests/core/pyspec/eth2spec/test/utils/random.py +++ b/tests/core/pyspec/eth2spec/test/utils/random.py @@ -21,6 +21,7 @@ from eth2spec.test.helpers.random import ( rng = random.Random(1337) + def _warn_if_empty_operations(block): if len(block.body.deposits) == 0: warnings.warn(f"deposits missing in block at slot {block.slot}") @@ -45,13 +46,14 @@ BLOCK_ATTEMPTS = 32 BLOCK_TRANSITIONS_COUNT = 2 # primitives -## state +# state + def randomize_state(spec, state, exit_fraction=0.1, slash_fraction=0.1): randomize_state_helper(spec, state, exit_fraction=exit_fraction, slash_fraction=slash_fraction) -## epochs +# epochs def _epochs_until_leak(spec): """ @@ -65,7 +67,7 @@ def _epochs_for_shard_committee_period(spec): return spec.config.SHARD_COMMITTEE_PERIOD -## slots +# slots def _last_slot_in_epoch(spec): return spec.SLOTS_PER_EPOCH - 1 @@ -81,7 +83,7 @@ def _penultimate_slot_in_epoch(spec): return spec.SLOTS_PER_EPOCH - 2 -## blocks +# blocks def _no_block(_spec, _pre_state, _signed_blocks): return None @@ -110,7 +112,7 @@ def random_block(spec, state, _signed_blocks): raise AssertionError("could not find a block with an unslashed proposer, check ``state`` input") -## validations +# validations def _no_op_validation(spec, state): return True @@ -158,7 +160,8 @@ def _transition_to_leaking(): _transition_without_leak = _with_validation(_no_op_transition, _validate_is_not_leaking) -## block transitions +# block transitions + def _transition_with_random_block(block_randomizer): """ @@ -173,6 +176,7 @@ def _transition_with_random_block(block_randomizer): # setup and test gen + def _randomized_scenario_setup(state_randomizer): """ Return a sequence of pairs of ("mutator", "validator"), @@ -337,6 +341,7 @@ def _id_from_scenario(test_description): # Generate a series of randomized block tests: + def generate_randomized_tests(metafunc, state_randomizer=randomize_state, block_randomizer=random_block): """ Pytest hook to generate test cases from dynamically computed data @@ -358,6 +363,7 @@ def pytest_generate_tests_adapter(f): # Run the generated tests: + def _iter_temporal(spec, callable_or_int): """ Intended to advance some number of {epochs, slots}. From 505bdba8f8aaa7be89591e28b8fb8e7e83780ca1 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 23 Aug 2021 12:49:36 -0700 Subject: [PATCH 111/135] fix imports --- .../pyspec/eth2spec/test/altair/sanity/test_blocks_random.py | 4 ++-- .../pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks_random.py index bc391b1f7..d546588d7 100644 --- a/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks_random.py @@ -1,5 +1,5 @@ -from tests.core.pyspec.eth2spec.test.helpers.constants import ALTAIR -from tests.core.pyspec.eth2spec.test.context import ( +from eth2spec.test.helpers.constants import ALTAIR +from eth2spec.test.context import ( misc_balances_in_default_range_with_many_validators, with_phases, zero_activation_threshold, diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py index 1244785cd..96193c3f9 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py @@ -1,5 +1,5 @@ -from tests.core.pyspec.eth2spec.test.helpers.constants import PHASE0 -from tests.core.pyspec.eth2spec.test.context import ( +from eth2spec.test.helpers.constants import PHASE0 +from eth2spec.test.context import ( misc_balances_in_default_range_with_many_validators, with_phases, zero_activation_threshold, From c27e4d140e444ee3543d3cdd9e1f3a7413c61cfa Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 24 Aug 2021 11:21:14 -0700 Subject: [PATCH 112/135] move to code-gen under new test generator --- .../eth2spec/test/altair/random/__init__.py | 0 .../test/altair/random/test_random.py | 421 ++++++++++++++++++ .../test/altair/sanity/test_blocks_random.py | 64 --- .../eth2spec/test/phase0/random/__init__.py | 0 .../test/phase0/random/test_random.py | 421 ++++++++++++++++++ .../test/phase0/sanity/test_blocks_random.py | 37 -- .../core/pyspec/eth2spec/test/utils/random.py | 273 ++++-------- tests/generators/random/Makefile | 7 + tests/generators/random/README.md | 29 ++ tests/generators/random/generate.py | 243 ++++++++++ tests/generators/random/main.py | 18 + tests/generators/random/requirements.txt | 2 + tests/generators/sanity/main.py | 2 - 13 files changed, 1226 insertions(+), 291 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/altair/random/__init__.py create mode 100644 tests/core/pyspec/eth2spec/test/altair/random/test_random.py delete mode 100644 tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks_random.py create mode 100644 tests/core/pyspec/eth2spec/test/phase0/random/__init__.py create mode 100644 tests/core/pyspec/eth2spec/test/phase0/random/test_random.py delete mode 100644 tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py create mode 100644 tests/generators/random/Makefile create mode 100644 tests/generators/random/README.md create mode 100644 tests/generators/random/generate.py create mode 100644 tests/generators/random/main.py create mode 100644 tests/generators/random/requirements.txt diff --git a/tests/core/pyspec/eth2spec/test/altair/random/__init__.py b/tests/core/pyspec/eth2spec/test/altair/random/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/pyspec/eth2spec/test/altair/random/test_random.py b/tests/core/pyspec/eth2spec/test/altair/random/test_random.py new file mode 100644 index 000000000..86f8f97b1 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/random/test_random.py @@ -0,0 +1,421 @@ +""" +This module is generated from the ``random`` test generator. +Please do not edit this file manually. +See the README for that generator for more information. +""" + +from eth2spec.test.helpers.constants import ALTAIR +from eth2spec.test.context import ( + misc_balances_in_default_range_with_many_validators, + with_phases, + zero_activation_threshold, +) +from eth2spec.test.context import ( + always_bls, + spec_test, + with_custom_state, + single_phase, +) +from eth2spec.test.utils.random import ( + run_generated_randomized_test, +) + + +@with_phases([ALTAIR]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_0(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([ALTAIR]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_1(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([ALTAIR]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_2(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([ALTAIR]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_3(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([ALTAIR]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_4(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([ALTAIR]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_5(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([ALTAIR]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_6(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([ALTAIR]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_7(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([ALTAIR]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_8(spec, state): + # scenario as high-level, informal text: + # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([ALTAIR]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_9(spec, state): + # scenario as high-level, informal text: + # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([ALTAIR]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_10(spec, state): + # scenario as high-level, informal text: + # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([ALTAIR]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_11(spec, state): + # scenario as high-level, informal text: + # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([ALTAIR]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_12(spec, state): + # scenario as high-level, informal text: + # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([ALTAIR]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_13(spec, state): + # scenario as high-level, informal text: + # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([ALTAIR]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_14(spec, state): + # scenario as high-level, informal text: + # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([ALTAIR]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_15(spec, state): + # scenario as high-level, informal text: + # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) diff --git a/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks_random.py deleted file mode 100644 index d546588d7..000000000 --- a/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks_random.py +++ /dev/null @@ -1,64 +0,0 @@ -from eth2spec.test.helpers.constants import ALTAIR -from eth2spec.test.context import ( - misc_balances_in_default_range_with_many_validators, - with_phases, - zero_activation_threshold, -) -from eth2spec.test.helpers.multi_operations import ( - get_random_sync_aggregate, -) -from eth2spec.test.helpers.inactivity_scores import ( - randomize_inactivity_scores, -) -from eth2spec.test.context import ( - always_bls, - spec_test, - with_custom_state, - single_phase, -) -from eth2spec.test.utils.random import ( - generate_randomized_tests, - pytest_generate_tests_adapter, - run_generated_randomized_test, - random_block, - randomize_state, -) - -SYNC_AGGREGATE_PARTICIPATION_BUCKETS = 4 - - -def _randomize_altair_state(spec, state): - randomize_state(spec, state, exit_fraction=0.1, slash_fraction=0.1) - randomize_inactivity_scores(spec, state) - - -def _randomize_altair_block(spec, state, signed_blocks): - block = random_block(spec, state, signed_blocks) - fraction_missed = len(signed_blocks) / SYNC_AGGREGATE_PARTICIPATION_BUCKETS - fraction_participated = 1.0 - fraction_missed - block.body.sync_aggregate = get_random_sync_aggregate(spec, state, fraction_participated=fraction_participated) - return block - - -def pytest_generate_tests(metafunc): - """ - Pytest hook to generate test cases from dynamically computed data - """ - generate_randomized_tests( - metafunc, - state_randomizer=_randomize_altair_state, - block_randomizer=_randomize_altair_block, - ) - - -@pytest_generate_tests_adapter -@with_phases([ALTAIR]) -@with_custom_state( - balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold -) -@spec_test -@single_phase -@always_bls -def test_harness_for_randomized_blocks(spec, state, test_description): - yield from run_generated_randomized_test(spec, state, test_description) diff --git a/tests/core/pyspec/eth2spec/test/phase0/random/__init__.py b/tests/core/pyspec/eth2spec/test/phase0/random/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py b/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py new file mode 100644 index 000000000..bfa93330f --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py @@ -0,0 +1,421 @@ +""" +This module is generated from the ``random`` test generator. +Please do not edit this file manually. +See the README for that generator for more information. +""" + +from eth2spec.test.helpers.constants import PHASE0 +from eth2spec.test.context import ( + misc_balances_in_default_range_with_many_validators, + with_phases, + zero_activation_threshold, +) +from eth2spec.test.context import ( + always_bls, + spec_test, + with_custom_state, + single_phase, +) +from eth2spec.test.utils.random import ( + run_generated_randomized_test, +) + + +@with_phases([PHASE0]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_0(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([PHASE0]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_1(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([PHASE0]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_2(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([PHASE0]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_3(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([PHASE0]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_4(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([PHASE0]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_5(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([PHASE0]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_6(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([PHASE0]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_7(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([PHASE0]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_8(spec, state): + # scenario as high-level, informal text: + # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([PHASE0]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_9(spec, state): + # scenario as high-level, informal text: + # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([PHASE0]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_10(spec, state): + # scenario as high-level, informal text: + # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([PHASE0]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_11(spec, state): + # scenario as high-level, informal text: + # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([PHASE0]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_12(spec, state): + # scenario as high-level, informal text: + # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([PHASE0]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_13(spec, state): + # scenario as high-level, informal text: + # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([PHASE0]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_14(spec, state): + # scenario as high-level, informal text: + # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@with_phases([PHASE0]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_15(spec, state): + # scenario as high-level, informal text: + # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py deleted file mode 100644 index 96193c3f9..000000000 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks_random.py +++ /dev/null @@ -1,37 +0,0 @@ -from eth2spec.test.helpers.constants import PHASE0 -from eth2spec.test.context import ( - misc_balances_in_default_range_with_many_validators, - with_phases, - zero_activation_threshold, -) -from eth2spec.test.context import ( - always_bls, - spec_test, - with_custom_state, - single_phase, -) -from eth2spec.test.utils.random import ( - generate_randomized_tests, - pytest_generate_tests_adapter, - run_generated_randomized_test, -) - - -def pytest_generate_tests(metafunc): - """ - Pytest hook to generate test cases from dynamically computed data - """ - generate_randomized_tests(metafunc) - - -@pytest_generate_tests_adapter -@with_phases([PHASE0]) -@with_custom_state( - balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold -) -@spec_test -@single_phase -@always_bls -def test_harness_for_randomized_blocks(spec, state, test_description): - yield from run_generated_randomized_test(spec, state, test_description) diff --git a/tests/core/pyspec/eth2spec/test/utils/random.py b/tests/core/pyspec/eth2spec/test/utils/random.py index da8e391e8..b12fca96b 100644 --- a/tests/core/pyspec/eth2spec/test/utils/random.py +++ b/tests/core/pyspec/eth2spec/test/utils/random.py @@ -2,12 +2,20 @@ Utility code to generate randomized block tests """ -import itertools -import random +import sys import warnings +from random import Random from typing import Callable + from eth2spec.test.helpers.multi_operations import ( build_random_block_from_state_for_next_slot, + get_random_sync_aggregate, +) +from eth2spec.test.helpers.inactivity_scores import ( + randomize_inactivity_scores, +) +from eth2spec.test.helpers.random import ( + randomize_state as randomize_state_helper, ) from eth2spec.test.helpers.state import ( next_slot, @@ -15,37 +23,8 @@ from eth2spec.test.helpers.state import ( ensure_state_has_validators_across_lifecycle, state_transition_and_sign_block, ) -from eth2spec.test.helpers.random import ( - randomize_state as randomize_state_helper, -) -rng = random.Random(1337) - - -def _warn_if_empty_operations(block): - if len(block.body.deposits) == 0: - warnings.warn(f"deposits missing in block at slot {block.slot}") - - if len(block.body.proposer_slashings) == 0: - warnings.warn(f"proposer slashings missing in block at slot {block.slot}") - - if len(block.body.attester_slashings) == 0: - warnings.warn(f"attester slashings missing in block at slot {block.slot}") - - if len(block.body.attestations) == 0: - warnings.warn(f"attestations missing in block at slot {block.slot}") - - if len(block.body.voluntary_exits) == 0: - warnings.warn(f"voluntary exits missing in block at slot {block.slot}") - - -# May need to make several attempts to find a block that does not correspond to a slashed -# proposer with the randomization helpers... -BLOCK_ATTEMPTS = 32 -# Ensure this many blocks are present in *each* randomized scenario -BLOCK_TRANSITIONS_COUNT = 2 - -# primitives +# primitives: # state @@ -53,6 +32,11 @@ def randomize_state(spec, state, exit_fraction=0.1, slash_fraction=0.1): randomize_state_helper(spec, state, exit_fraction=exit_fraction, slash_fraction=slash_fraction) +def randomize_state_altair(spec, state): + randomize_state(spec, state, exit_fraction=0.1, slash_fraction=0.1) + randomize_inactivity_scores(spec, state) + + # epochs def _epochs_until_leak(spec): @@ -69,26 +53,46 @@ def _epochs_for_shard_committee_period(spec): # slots -def _last_slot_in_epoch(spec): +def last_slot_in_epoch(spec): return spec.SLOTS_PER_EPOCH - 1 -def _random_slot_in_epoch(rng): - def _a_slot_in_epoch(spec): - return rng.randrange(1, spec.SLOTS_PER_EPOCH - 2) - return _a_slot_in_epoch +def random_slot_in_epoch(spec, rng=Random(1336)): + return rng.randrange(1, spec.SLOTS_PER_EPOCH - 2) -def _penultimate_slot_in_epoch(spec): +def penultimate_slot_in_epoch(spec): return spec.SLOTS_PER_EPOCH - 2 # blocks -def _no_block(_spec, _pre_state, _signed_blocks): +def no_block(_spec, _pre_state, _signed_blocks): return None +# May need to make several attempts to find a block that does not correspond to a slashed +# proposer with the randomization helpers... +BLOCK_ATTEMPTS = 32 + + +def _warn_if_empty_operations(block): + if len(block.body.deposits) == 0: + warnings.warn(f"deposits missing in block at slot {block.slot}") + + if len(block.body.proposer_slashings) == 0: + warnings.warn(f"proposer slashings missing in block at slot {block.slot}") + + if len(block.body.attester_slashings) == 0: + warnings.warn(f"attester slashings missing in block at slot {block.slot}") + + if len(block.body.attestations) == 0: + warnings.warn(f"attestations missing in block at slot {block.slot}") + + if len(block.body.voluntary_exits) == 0: + warnings.warn(f"voluntary exits missing in block at slot {block.slot}") + + def random_block(spec, state, _signed_blocks): """ Produce a random block. @@ -112,9 +116,20 @@ def random_block(spec, state, _signed_blocks): raise AssertionError("could not find a block with an unslashed proposer, check ``state`` input") +SYNC_AGGREGATE_PARTICIPATION_BUCKETS = 4 + + +def random_block_altair(spec, state, signed_blocks): + block = random_block(spec, state, signed_blocks) + fraction_missed = len(signed_blocks) / SYNC_AGGREGATE_PARTICIPATION_BUCKETS + fraction_participated = 1.0 - fraction_missed + block.body.sync_aggregate = get_random_sync_aggregate(spec, state, fraction_participated=fraction_participated) + return block + + # validations -def _no_op_validation(spec, state): +def no_op_validation(spec, state): return True @@ -139,31 +154,31 @@ def _no_op_transition(): return {} -def _epoch_transition(n=0): +def epoch_transition(n=0): return { "epochs_to_skip": n, } -def _slot_transition(n=0): +def slot_transition(n=0): return { "slots_to_skip": n, } -def _transition_to_leaking(): +def transition_to_leaking(): return { "epochs_to_skip": _epochs_until_leak, "validation": _validate_is_leaking, } -_transition_without_leak = _with_validation(_no_op_transition, _validate_is_not_leaking) +transition_without_leak = _with_validation(_no_op_transition, _validate_is_not_leaking) # block transitions -def _transition_with_random_block(block_randomizer): +def transition_with_random_block(block_randomizer): """ Build a block transition with randomized data. Provide optional sub-transitions to advance some @@ -221,163 +236,43 @@ def _randomized_scenario_setup(state_randomizer): return ( # NOTE: the block randomization function assumes at least 1 shard committee period # so advance the state before doing anything else. - (_skip_epochs(_epochs_for_shard_committee_period), _no_op_validation), - (_simulate_honest_execution, _no_op_validation), + (_skip_epochs(_epochs_for_shard_committee_period), no_op_validation), + (_simulate_honest_execution, no_op_validation), (state_randomizer, ensure_state_has_validators_across_lifecycle), ) - -def _normalize_transition(transition): - """ - Provide "empty" or "no op" sub-transitions - to a given transition. - """ - if isinstance(transition, Callable): - transition = transition() - if "epochs_to_skip" not in transition: - transition["epochs_to_skip"] = 0 - if "slots_to_skip" not in transition: - transition["slots_to_skip"] = 0 - if "block_producer" not in transition: - transition["block_producer"] = _no_block - if "validation" not in transition: - transition["validation"] = _no_op_validation - return transition - - -def _normalize_scenarios(scenarios, state_randomizer): - """ - "Normalize" a "scenario" so that a producer of a test case - does not need to provide every expected key/value. - """ - for scenario in scenarios: - if "setup" not in scenario: - scenario["setup"] = _randomized_scenario_setup(state_randomizer) - - transitions = scenario["transitions"] - for i, transition in enumerate(transitions): - transitions[i] = _normalize_transition(transition) - - -def _flatten(t): - leak_transition = t[0] - result = [leak_transition] - for transition_batch in t[1]: - for transition in transition_batch: - result.append(transition) - return result - - -def _generate_randomized_scenarios(state_randomizer, block_randomizer): - """ - Generates a set of randomized testing scenarios. - Return a sequence of "scenarios" where each scenario: - 1. Provides some setup - 2. Provides a sequence of transitions that mutate the state in some way, - possibly yielding blocks along the way - NOTE: scenarios are "normalized" with empty/no-op elements before returning - to the test generation to facilitate brevity when writing scenarios by hand. - NOTE: the main block driver builds a block for the **next** slot, so - the slot transitions are offset by -1 to target certain boundaries. - """ - # go forward 0 or 1 epochs - epochs_set = ( - _epoch_transition(n=0), - _epoch_transition(n=1), - ) - # within those epochs, go forward to: - slots_set = ( - # the first slot in an epoch (see note in docstring about offsets...) - _slot_transition(_last_slot_in_epoch), - # the second slot in an epoch - _slot_transition(n=0), - # some random number of slots, but not at epoch boundaries - _slot_transition(_random_slot_in_epoch(rng)), - # the last slot in an epoch (see note in docstring about offsets...) - _slot_transition(_penultimate_slot_in_epoch), - ) - # and produce a block... - blocks_set = ( - _transition_with_random_block(block_randomizer), - ) - # build a set of block transitions from combinations of sub-transitions - transitions_generator = ( - itertools.product(epochs_set, slots_set, blocks_set) for - _ in range(BLOCK_TRANSITIONS_COUNT) - ) - block_transitions = zip(*transitions_generator) - - # and preface each set of block transitions with the possible leak transitions - leak_transitions = ( - _transition_without_leak, - _transition_to_leaking, - ) - scenarios = [ - {"transitions": _flatten(t)} - for t in itertools.product(leak_transitions, block_transitions) - ] - _normalize_scenarios(scenarios, state_randomizer) - return scenarios - - -def _id_from_scenario(test_description): - """ - Construct a test name for ``pytest`` infra. - """ - def _to_id_part(prefix, x): - suffix = str(x) - if isinstance(x, Callable): - suffix = x.__name__ - return f"{prefix}{suffix}" - - def _id_from_transition(transition): - return ",".join(( - _to_id_part("epochs:", transition["epochs_to_skip"]), - _to_id_part("slots:", transition["slots_to_skip"]), - _to_id_part("with-block:", transition["block_producer"]) - )) - - return "|".join(map(_id_from_transition, test_description["transitions"])) - -# Generate a series of randomized block tests: - - -def generate_randomized_tests(metafunc, state_randomizer=randomize_state, block_randomizer=random_block): - """ - Pytest hook to generate test cases from dynamically computed data - """ - generated_name = "test_description" - generated_values = _generate_randomized_scenarios(state_randomizer, block_randomizer) - metafunc.parametrize(generated_name, generated_values, ids=_id_from_scenario, scope="module") - - -def pytest_generate_tests_adapter(f): - """ - Adapter decorator to allow dynamic test case generation - while leveraging existing decorators specific to spec tests. - """ - def wrapper(test_description, *args, **kwargs): - kwargs["test_description"] = test_description - f(*args, **kwargs) - return wrapper - # Run the generated tests: -def _iter_temporal(spec, callable_or_int): +# while the test implementation works via code-gen, +# references to helper code in this module are serialized as str names. +# to resolve this references at runtime, we need a reference to this module: +_this_module = sys.modules[__name__] + +def _resolve_ref(ref): + if isinstance(ref, str): + return getattr(_this_module, ref) + return ref + + +def _iter_temporal(spec, description): """ Intended to advance some number of {epochs, slots}. Caller can provide a constant integer or a callable deriving a number from the ``spec`` under consideration. """ - numeric = callable_or_int - if isinstance(callable_or_int, Callable): - numeric = callable_or_int(spec) + numeric = _resolve_ref(description) + if isinstance(numeric, Callable): + numeric = numeric(spec) for i in range(numeric): yield i def run_generated_randomized_test(spec, state, test_description): + if "setup" not in test_description: + state_randomizer = _resolve_ref(test_description.get("state_randomizer", randomize_state)) + test_description["setup"] = _randomized_scenario_setup(state_randomizer) + for mutation, validation in test_description["setup"]: mutation(spec, state) validation(spec, state) @@ -393,12 +288,14 @@ def run_generated_randomized_test(spec, state, test_description): for _ in slots_to_skip: next_slot(spec, state) - block = transition["block_producer"](spec, state, blocks) + block_producer = _resolve_ref(transition["block_producer"]) + block = block_producer(spec, state, blocks) if block: signed_block = state_transition_and_sign_block(spec, state, block) blocks.append(signed_block) - assert transition["validation"](spec, state) + validation = _resolve_ref(transition["validation"]) + assert validation(spec, state) yield "blocks", blocks yield "post", state diff --git a/tests/generators/random/Makefile b/tests/generators/random/Makefile new file mode 100644 index 000000000..a3c845243 --- /dev/null +++ b/tests/generators/random/Makefile @@ -0,0 +1,7 @@ +all: + . ./venv/bin/activate + pip install -r requirements.txt + rm -f ../../core/pyspec/eth2spec/test/phase0/random/test_random.py + rm -f ../../core/pyspec/eth2spec/test/altair/random/test_random.py + python generate.py phase0 > ../../core/pyspec/eth2spec/test/phase0/random/test_random.py + python generate.py altair > ../../core/pyspec/eth2spec/test/altair/random/test_random.py diff --git a/tests/generators/random/README.md b/tests/generators/random/README.md new file mode 100644 index 000000000..e1942b7f4 --- /dev/null +++ b/tests/generators/random/README.md @@ -0,0 +1,29 @@ +# Randomized tests + +Randomized tests in the format of `sanity` tests, with randomized operations. + +Information on the format of the tests can be found in the [sanity test formats documentation](../../formats/sanity/README.md). + +# To generate test sources + +```bash +$ make +``` + +The necessary commands are in the `Makefile`, as the only target. + +The generated files are committed to the repo so you should not need to do this. + +# To run tests + +Use the usual `pytest` mechanics used elsewhere in this repo. + +# To generate spec tests (from the generated files) + +Run the test generator in the usual way. + +E.g. from the root of this repo, you can run: + +```bash +$ make gen_random +``` diff --git a/tests/generators/random/generate.py b/tests/generators/random/generate.py new file mode 100644 index 000000000..b31aac6a3 --- /dev/null +++ b/tests/generators/random/generate.py @@ -0,0 +1,243 @@ +""" +This test format currently uses code generation to assemble the tests +as the current test infra does not have a facility to dynamically +generate tests that can be seen by ``pytest``. + +This will likley change in future releases of the testing infra. + +NOTE: To add additional scenarios, add test cases below in ``_generate_randomized_scenarios``. +""" + +import sys +import warnings +from typing import Callable +import itertools + +from eth2spec.test.utils.random import ( + no_block, + no_op_validation, + randomize_state, + randomize_state_altair, + random_block, + random_block_altair, + last_slot_in_epoch, + random_slot_in_epoch, + penultimate_slot_in_epoch, + epoch_transition, + slot_transition, + transition_with_random_block, + transition_to_leaking, + transition_without_leak, +) +from eth2spec.test.helpers.constants import PHASE0, ALTAIR + + +# Ensure this many blocks are present in *each* randomized scenario +BLOCK_TRANSITIONS_COUNT = 2 + + +def _normalize_transition(transition): + """ + Provide "empty" or "no op" sub-transitions + to a given transition. + """ + if isinstance(transition, Callable): + transition = transition() + if "epochs_to_skip" not in transition: + transition["epochs_to_skip"] = 0 + if "slots_to_skip" not in transition: + transition["slots_to_skip"] = 0 + if "block_producer" not in transition: + transition["block_producer"] = no_block + if "validation" not in transition: + transition["validation"] = no_op_validation + return transition + + +def _normalize_scenarios(scenarios): + """ + "Normalize" a "scenario" so that a producer of a test case + does not need to provide every expected key/value. + """ + for scenario in scenarios: + transitions = scenario["transitions"] + for i, transition in enumerate(transitions): + transitions[i] = _normalize_transition(transition) + + +def _flatten(t): + leak_transition = t[0] + result = [leak_transition] + for transition_batch in t[1]: + for transition in transition_batch: + result.append(transition) + return result + + +def _generate_randomized_scenarios(block_randomizer): + """ + Generates a set of randomized testing scenarios. + Return a sequence of "scenarios" where each scenario: + 1. Provides some setup + 2. Provides a sequence of transitions that mutate the state in some way, + possibly yielding blocks along the way + NOTE: scenarios are "normalized" with empty/no-op elements before returning + to the test generation to facilitate brevity when writing scenarios by hand. + NOTE: the main block driver builds a block for the **next** slot, so + the slot transitions are offset by -1 to target certain boundaries. + """ + # go forward 0 or 1 epochs + epochs_set = ( + epoch_transition(n=0), + epoch_transition(n=1), + ) + # within those epochs, go forward to: + slots_set = ( + # the first slot in an epoch (see note in docstring about offsets...) + slot_transition(last_slot_in_epoch), + # the second slot in an epoch + slot_transition(n=0), + # some random number of slots, but not at epoch boundaries + slot_transition(random_slot_in_epoch), + # the last slot in an epoch (see note in docstring about offsets...) + slot_transition(penultimate_slot_in_epoch), + ) + # and produce a block... + blocks_set = ( + transition_with_random_block(block_randomizer), + ) + # build a set of block transitions from combinations of sub-transitions + transitions_generator = ( + itertools.product(epochs_set, slots_set, blocks_set) for + _ in range(BLOCK_TRANSITIONS_COUNT) + ) + block_transitions = zip(*transitions_generator) + + # and preface each set of block transitions with the possible leak transitions + leak_transitions = ( + transition_without_leak, + transition_to_leaking, + ) + scenarios = [ + {"transitions": _flatten(t)} + for t in itertools.product(leak_transitions, block_transitions) + ] + _normalize_scenarios(scenarios) + return scenarios + + +def _id_from_scenario(test_description): + """ + Construct a test name for ``pytest`` infra. + """ + def _to_id_part(prefix, x): + suffix = str(x) + if isinstance(x, Callable): + suffix = x.__name__ + return f"{prefix}{suffix}" + + def _id_from_transition(transition): + return ",".join(( + _to_id_part("epochs:", transition["epochs_to_skip"]), + _to_id_part("slots:", transition["slots_to_skip"]), + _to_id_part("with-block:", transition["block_producer"]) + )) + + return "|".join(map(_id_from_transition, test_description["transitions"])) + + +test_imports_template = """\"\"\" +This module is generated from the ``random`` test generator. +Please do not edit this file manually. +See the README for that generator for more information. +\"\"\" + +from eth2spec.test.helpers.constants import {phase} +from eth2spec.test.context import ( + misc_balances_in_default_range_with_many_validators, + with_phases, + zero_activation_threshold, +) +from eth2spec.test.context import ( + always_bls, + spec_test, + with_custom_state, + single_phase, +) +from eth2spec.test.utils.random import ( + run_generated_randomized_test, +)""" + +test_template = """ +@with_phases([{phase}]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold +) +@spec_test +@single_phase +@always_bls +def test_randomized_{index}(spec, state): + # scenario as high-level, informal text: +{name_as_comment} + scenario = {scenario} + yield from run_generated_randomized_test( + spec, + state, + scenario, + )""" + + +def _to_comment(name, indent_level): + parts = name.split("|") + indentation = " " * indent_level + parts = [ + indentation + "# " + part for part in parts + ] + return "\n".join(parts) + + +def run_generate_tests_to_std_out(phase, state_randomizer, block_randomizer): + scenarios = _generate_randomized_scenarios(block_randomizer) + test_content = {"phase": phase.upper()} + test_imports = test_imports_template.format(**test_content) + test_file = [test_imports] + for index, scenario in enumerate(scenarios): + # required for setup phase + scenario["state_randomizer"] = state_randomizer.__name__ + + # need to pass name, rather than function reference... + transitions = scenario["transitions"] + for transition in transitions: + for name, value in transition.items(): + if isinstance(value, Callable): + transition[name] = value.__name__ + + test_content = test_content.copy() + name = _id_from_scenario(scenario) + test_content["name_as_comment"] = _to_comment(name, 1) + test_content["index"] = index + test_content["scenario"] = scenario + test_instance = test_template.format(**test_content) + test_file.append(test_instance) + print("\n\n".join(test_file)) + + +if __name__ == "__main__": + did_generate = False + if PHASE0 in sys.argv: + did_generate = True + run_generate_tests_to_std_out( + PHASE0, + state_randomizer=randomize_state, + block_randomizer=random_block, + ) + if ALTAIR in sys.argv: + did_generate = True + run_generate_tests_to_std_out( + ALTAIR, + state_randomizer=randomize_state_altair, + block_randomizer=random_block_altair, + ) + if not did_generate: + warnings.warn("no phase given for test generation") diff --git a/tests/generators/random/main.py b/tests/generators/random/main.py new file mode 100644 index 000000000..f6f1b1847 --- /dev/null +++ b/tests/generators/random/main.py @@ -0,0 +1,18 @@ +from eth2spec.test.helpers.constants import PHASE0, ALTAIR +from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators + + +if __name__ == "__main__": + phase_0_mods = {key: 'eth2spec.test.phase0.random.test_' + key for key in [ + 'random', + ]} + altair_mods = {key: 'eth2spec.test.altair.random.test_' + key for key in [ + 'random', + ]} + + all_mods = { + PHASE0: phase_0_mods, + ALTAIR: altair_mods, + } + + run_state_test_generators(runner_name="random", all_mods=all_mods) diff --git a/tests/generators/random/requirements.txt b/tests/generators/random/requirements.txt new file mode 100644 index 000000000..182248686 --- /dev/null +++ b/tests/generators/random/requirements.txt @@ -0,0 +1,2 @@ +pytest>=4.4 +../../../[generator] diff --git a/tests/generators/sanity/main.py b/tests/generators/sanity/main.py index 89e622f1c..8caedc8e5 100644 --- a/tests/generators/sanity/main.py +++ b/tests/generators/sanity/main.py @@ -5,12 +5,10 @@ from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators if __name__ == "__main__": phase_0_mods = {key: 'eth2spec.test.phase0.sanity.test_' + key for key in [ 'blocks', - 'blocks_random', 'slots', ]} altair_mods = {**{key: 'eth2spec.test.altair.sanity.test_' + key for key in [ 'blocks', - 'blocks_random', ]}, **phase_0_mods} # also run the previous phase 0 tests # Altair-specific test cases are ignored, but should be included after the Merge is rebased onto Altair work. From d1f3ec59afba70ee39e5c1bcf15e371248c6189c Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 24 Aug 2021 11:56:19 -0700 Subject: [PATCH 113/135] ensure at least 1 operation when making random block --- .../pyspec/eth2spec/test/helpers/multi_operations.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py index 10de14253..3d941627b 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -87,7 +87,7 @@ def get_random_attester_slashings(spec, state, rng): def get_random_attestations(spec, state, rng): - num_attestations = rng.randrange(spec.MAX_ATTESTATIONS) + num_attestations = max(1, rng.randrange(spec.MAX_ATTESTATIONS)) attestations = [ get_valid_attestation( @@ -101,13 +101,12 @@ def get_random_attestations(spec, state, rng): def prepare_state_and_get_random_deposits(spec, state, rng): - num_deposits = rng.randrange(spec.MAX_DEPOSITS) + num_deposits = max(1, rng.randrange(spec.MAX_DEPOSITS)) deposit_data_leaves = [spec.DepositData() for _ in range(len(state.validators))] deposits = [] # First build deposit data leaves - root = None for i in range(num_deposits): index = len(state.validators) + i _, root, deposit_data_leaves = build_deposit( @@ -120,9 +119,8 @@ def prepare_state_and_get_random_deposits(spec, state, rng): signed=True, ) - if root: - # NOTE: if ``num_deposits == 0``, ``root`` is never assigned to - state.eth1_data.deposit_root = root + # NOTE: if ``num_deposits == 0``, ``root`` is never assigned to + state.eth1_data.deposit_root = root state.eth1_data.deposit_count += num_deposits # Then for that context, build deposits/proofs @@ -149,7 +147,7 @@ def _eligible_for_exit(spec, state, index): def get_random_voluntary_exits(spec, state, to_be_slashed_indices, rng): - num_exits = rng.randrange(spec.MAX_VOLUNTARY_EXITS) + num_exits = max(1, rng.randrange(spec.MAX_VOLUNTARY_EXITS)) active_indices = set(spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy()) indices = set( index for index in active_indices From 8e5a34c38a909db63a75057bde62d64b07f234a0 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 24 Aug 2021 12:37:28 -0700 Subject: [PATCH 114/135] adjust helper to account for additional slashings --- .../eth2spec/test/helpers/multi_operations.py | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py index 3d941627b..cfc2c4a0f 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -64,20 +64,33 @@ def get_random_proposer_slashings(spec, state, rng): return slashings -def get_random_attester_slashings(spec, state, rng): +def get_random_attester_slashings(spec, state, rng, slashed_indices=[]): + """ + Caller can supply ``slashed_indices`` if they are aware of other indices + that will be slashed by other operations in the same block as the one that + contains the output of this function. + """ # ensure at least one attester slashing, the max count # is small so not much room for random inclusion num_slashings = max(1, rng.randrange(spec.MAX_ATTESTER_SLASHINGS)) active_indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy() indices = [ index for index in active_indices - if not state.validators[index].slashed + if ( + not state.validators[index].slashed + and index not in slashed_indices + ) ] + sample_upper_bound = 4 + max_slashed_count = num_slashings * sample_upper_bound - 1 + if len(indices) < max_slashed_count: + return [] + slot_range = list(range(state.slot - spec.SLOTS_PER_HISTORICAL_ROOT + 1, state.slot)) slashings = [ get_valid_attester_slashing_by_indices( spec, state, - sorted([indices.pop(rng.randrange(len(indices))) for _ in range(rng.randrange(1, 4))]), + sorted([indices.pop(rng.randrange(len(indices))) for _ in range(rng.randrange(1, sample_upper_bound))]), slot=slot_range.pop(rng.randrange(len(slot_range))), signed_1=True, signed_2=True, ) @@ -164,8 +177,13 @@ def build_random_block_from_state_for_next_slot(spec, state, rng=Random(2188)): deposits = prepare_state_and_get_random_deposits(spec, state, rng) block = build_empty_block_for_next_slot(spec, state) - block.body.proposer_slashings = get_random_proposer_slashings(spec, state, rng) - block.body.attester_slashings = get_random_attester_slashings(spec, state, rng) + proposer_slashings = get_random_proposer_slashings(spec, state, rng) + block.body.proposer_slashings = proposer_slashings + slashed_indices = [ + slashing.signed_header_1.message.proposer_index + for slashing in proposer_slashings + ] + block.body.attester_slashings = get_random_attester_slashings(spec, state, rng, slashed_indices) block.body.attestations = get_random_attestations(spec, state, rng) block.body.deposits = deposits From 8a32bef58b186ff35ccf6f1709baf7d3aaadb772 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 24 Aug 2021 13:19:31 -0700 Subject: [PATCH 115/135] update skipped test count when test already exists --- tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_runner.py | 1 + 1 file changed, 1 insertion(+) 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 be5720265..eb72bf211 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 @@ -112,6 +112,7 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]): if case_dir.exists(): if not args.force and not incomplete_tag_file.exists(): + skipped_test_count += 1 print(f'Skipping already existing test: {case_dir}') continue else: From 933c1323dd2b51fa1c24df7737df94b281385196 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 24 Aug 2021 14:46:31 -0700 Subject: [PATCH 116/135] lint updates --- .../test/altair/random/test_random.py | 32 +++++++++---------- .../test/phase0/random/test_random.py | 32 +++++++++---------- .../core/pyspec/eth2spec/test/utils/random.py | 1 + tests/generators/random/generate.py | 2 +- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/random/test_random.py b/tests/core/pyspec/eth2spec/test/altair/random/test_random.py index 86f8f97b1..c7780925d 100644 --- a/tests/core/pyspec/eth2spec/test/altair/random/test_random.py +++ b/tests/core/pyspec/eth2spec/test/altair/random/test_random.py @@ -38,7 +38,7 @@ def test_randomized_0(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -63,7 +63,7 @@ def test_randomized_1(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -88,7 +88,7 @@ def test_randomized_2(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -113,7 +113,7 @@ def test_randomized_3(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -138,7 +138,7 @@ def test_randomized_4(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -163,7 +163,7 @@ def test_randomized_5(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -188,7 +188,7 @@ def test_randomized_6(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -213,7 +213,7 @@ def test_randomized_7(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -238,7 +238,7 @@ def test_randomized_8(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -263,7 +263,7 @@ def test_randomized_9(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -288,7 +288,7 @@ def test_randomized_10(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -313,7 +313,7 @@ def test_randomized_11(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -338,7 +338,7 @@ def test_randomized_12(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -363,7 +363,7 @@ def test_randomized_13(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -388,7 +388,7 @@ def test_randomized_14(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -413,7 +413,7 @@ def test_randomized_15(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, diff --git a/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py b/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py index bfa93330f..95dfeaeaf 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py @@ -38,7 +38,7 @@ def test_randomized_0(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -63,7 +63,7 @@ def test_randomized_1(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -88,7 +88,7 @@ def test_randomized_2(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -113,7 +113,7 @@ def test_randomized_3(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -138,7 +138,7 @@ def test_randomized_4(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -163,7 +163,7 @@ def test_randomized_5(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -188,7 +188,7 @@ def test_randomized_6(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -213,7 +213,7 @@ def test_randomized_7(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -238,7 +238,7 @@ def test_randomized_8(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -263,7 +263,7 @@ def test_randomized_9(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -288,7 +288,7 @@ def test_randomized_10(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -313,7 +313,7 @@ def test_randomized_11(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -338,7 +338,7 @@ def test_randomized_12(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -363,7 +363,7 @@ def test_randomized_13(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -388,7 +388,7 @@ def test_randomized_14(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -413,7 +413,7 @@ def test_randomized_15(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} + scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, diff --git a/tests/core/pyspec/eth2spec/test/utils/random.py b/tests/core/pyspec/eth2spec/test/utils/random.py index b12fca96b..2f727e749 100644 --- a/tests/core/pyspec/eth2spec/test/utils/random.py +++ b/tests/core/pyspec/eth2spec/test/utils/random.py @@ -249,6 +249,7 @@ def _randomized_scenario_setup(state_randomizer): # to resolve this references at runtime, we need a reference to this module: _this_module = sys.modules[__name__] + def _resolve_ref(ref): if isinstance(ref, str): return getattr(_this_module, ref) diff --git a/tests/generators/random/generate.py b/tests/generators/random/generate.py index b31aac6a3..2f6b306d1 100644 --- a/tests/generators/random/generate.py +++ b/tests/generators/random/generate.py @@ -180,7 +180,7 @@ test_template = """ def test_randomized_{index}(spec, state): # scenario as high-level, informal text: {name_as_comment} - scenario = {scenario} + scenario = {scenario} # noqa: E501 yield from run_generated_randomized_test( spec, state, From f7c0dc36bed1e002c5768459d0cb6b93f1050c55 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 24 Aug 2021 14:57:56 -0700 Subject: [PATCH 117/135] skip running heavy randomized tests in CI --- .../eth2spec/test/altair/random/test_random.py | 17 +++++++++++++++++ tests/core/pyspec/eth2spec/test/context.py | 11 +++++++++++ .../eth2spec/test/phase0/random/test_random.py | 17 +++++++++++++++++ tests/generators/random/generate.py | 2 ++ 4 files changed, 47 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/altair/random/test_random.py b/tests/core/pyspec/eth2spec/test/altair/random/test_random.py index c7780925d..d022c2ca1 100644 --- a/tests/core/pyspec/eth2spec/test/altair/random/test_random.py +++ b/tests/core/pyspec/eth2spec/test/altair/random/test_random.py @@ -9,6 +9,7 @@ from eth2spec.test.context import ( misc_balances_in_default_range_with_many_validators, with_phases, zero_activation_threshold, + only_generator, ) from eth2spec.test.context import ( always_bls, @@ -21,6 +22,7 @@ from eth2spec.test.utils.random import ( ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -46,6 +48,7 @@ def test_randomized_0(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -71,6 +74,7 @@ def test_randomized_1(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -96,6 +100,7 @@ def test_randomized_2(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -121,6 +126,7 @@ def test_randomized_3(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -146,6 +152,7 @@ def test_randomized_4(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -171,6 +178,7 @@ def test_randomized_5(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -196,6 +204,7 @@ def test_randomized_6(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -221,6 +230,7 @@ def test_randomized_7(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -246,6 +256,7 @@ def test_randomized_8(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -271,6 +282,7 @@ def test_randomized_9(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -296,6 +308,7 @@ def test_randomized_10(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -321,6 +334,7 @@ def test_randomized_11(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -346,6 +360,7 @@ def test_randomized_12(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -371,6 +386,7 @@ def test_randomized_13(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -396,6 +412,7 @@ def test_randomized_14(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index f6f120d55..346cdc8f1 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -456,6 +456,17 @@ with_altair_and_later = with_phases([ALTAIR, MERGE]) with_merge_and_later = with_phases([MERGE]) # TODO: include sharding when spec stabilizes. +def only_generator(reason): + def _decorator(inner): + def _wrapper(*args, **kwargs): + if is_pytest: + dump_skipping_message(reason) + return None + return inner(*args, **kwargs) + return _wrapper + return _decorator + + def fork_transition_test(pre_fork_name, post_fork_name, fork_epoch=None): """ A decorator to construct a "transition" test from one fork of the eth2 spec diff --git a/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py b/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py index 95dfeaeaf..5bb8c3791 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py @@ -9,6 +9,7 @@ from eth2spec.test.context import ( misc_balances_in_default_range_with_many_validators, with_phases, zero_activation_threshold, + only_generator, ) from eth2spec.test.context import ( always_bls, @@ -21,6 +22,7 @@ from eth2spec.test.utils.random import ( ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -46,6 +48,7 @@ def test_randomized_0(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -71,6 +74,7 @@ def test_randomized_1(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -96,6 +100,7 @@ def test_randomized_2(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -121,6 +126,7 @@ def test_randomized_3(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -146,6 +152,7 @@ def test_randomized_4(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -171,6 +178,7 @@ def test_randomized_5(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -196,6 +204,7 @@ def test_randomized_6(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -221,6 +230,7 @@ def test_randomized_7(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -246,6 +256,7 @@ def test_randomized_8(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -271,6 +282,7 @@ def test_randomized_9(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -296,6 +308,7 @@ def test_randomized_10(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -321,6 +334,7 @@ def test_randomized_11(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -346,6 +360,7 @@ def test_randomized_12(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -371,6 +386,7 @@ def test_randomized_13(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, @@ -396,6 +412,7 @@ def test_randomized_14(spec, state): ) +@only_generator("randomized test for broad coverage, not point-to-point CI") @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, diff --git a/tests/generators/random/generate.py b/tests/generators/random/generate.py index 2f6b306d1..3f34a6bc0 100644 --- a/tests/generators/random/generate.py +++ b/tests/generators/random/generate.py @@ -157,6 +157,7 @@ from eth2spec.test.context import ( misc_balances_in_default_range_with_many_validators, with_phases, zero_activation_threshold, + only_generator, ) from eth2spec.test.context import ( always_bls, @@ -169,6 +170,7 @@ from eth2spec.test.utils.random import ( )""" test_template = """ +@only_generator(\"randomized test for broad coverage, not point-to-point CI\") @with_phases([{phase}]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, From 33c96127dac894f942ed3225400541499fa2ea30 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 24 Aug 2021 16:21:45 -0700 Subject: [PATCH 118/135] fix bug with random sync aggregate helper --- .../pyspec/eth2spec/test/helpers/multi_operations.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py index cfc2c4a0f..83494182e 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -217,14 +217,18 @@ def run_test_full_random_operations(spec, state, rng=Random(2080)): def get_random_sync_aggregate(spec, state, fraction_participated=1.0, rng=Random(2099)): committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) participant_count = int(len(committee_indices) * fraction_participated) - participants = rng.sample(committee_indices, participant_count) + participant_indices = rng.sample(range(len(committee_indices)), participant_count) + participants = [ + committee_indices[index] + for index in participant_indices + ] signature = compute_aggregate_sync_committee_signature( spec, state, - state.slot, + state.slot - 1, participants, ) return spec.SyncAggregate( - sync_committee_bits=[index in participants for index in committee_indices], + sync_committee_bits=[index in participant_indices for index in range(len(committee_indices))], sync_committee_signature=signature, ) From 7874e8db881fc0a6812d0af8261925c91a62ac56 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 24 Aug 2021 16:25:25 -0700 Subject: [PATCH 119/135] clean up unnecessary comment --- tests/core/pyspec/eth2spec/test/helpers/multi_operations.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py index 83494182e..c0a58dbca 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -132,7 +132,6 @@ def prepare_state_and_get_random_deposits(spec, state, rng): signed=True, ) - # NOTE: if ``num_deposits == 0``, ``root`` is never assigned to state.eth1_data.deposit_root = root state.eth1_data.deposit_count += num_deposits From 02bc6541d99770a17a3b1127e36870971713a843 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 24 Aug 2021 16:28:13 -0700 Subject: [PATCH 120/135] extend Makefile --- tests/generators/random/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/generators/random/Makefile b/tests/generators/random/Makefile index a3c845243..799001280 100644 --- a/tests/generators/random/Makefile +++ b/tests/generators/random/Makefile @@ -1,4 +1,5 @@ all: + if ! test -d venv; then python3 -m venv venv; fi; . ./venv/bin/activate pip install -r requirements.txt rm -f ../../core/pyspec/eth2spec/test/phase0/random/test_random.py From 4d4f4e89f48aa4d5c14a41159d6bcb021f8ec1a3 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 25 Aug 2021 10:38:01 -0700 Subject: [PATCH 121/135] be specific about which slot we want a sync committee root for --- .../core/pyspec/eth2spec/test/helpers/multi_operations.py | 4 ++-- tests/core/pyspec/eth2spec/test/utils/random.py | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py index c0a58dbca..075b03aa0 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -213,7 +213,7 @@ def run_test_full_random_operations(spec, state, rng=Random(2080)): yield 'post', state -def get_random_sync_aggregate(spec, state, fraction_participated=1.0, rng=Random(2099)): +def get_random_sync_aggregate(spec, state, slot, fraction_participated=1.0, rng=Random(2099)): committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) participant_count = int(len(committee_indices) * fraction_participated) participant_indices = rng.sample(range(len(committee_indices)), participant_count) @@ -224,7 +224,7 @@ def get_random_sync_aggregate(spec, state, fraction_participated=1.0, rng=Random signature = compute_aggregate_sync_committee_signature( spec, state, - state.slot - 1, + slot, participants, ) return spec.SyncAggregate( diff --git a/tests/core/pyspec/eth2spec/test/utils/random.py b/tests/core/pyspec/eth2spec/test/utils/random.py index 2f727e749..d17a3a9b2 100644 --- a/tests/core/pyspec/eth2spec/test/utils/random.py +++ b/tests/core/pyspec/eth2spec/test/utils/random.py @@ -123,7 +123,12 @@ def random_block_altair(spec, state, signed_blocks): block = random_block(spec, state, signed_blocks) fraction_missed = len(signed_blocks) / SYNC_AGGREGATE_PARTICIPATION_BUCKETS fraction_participated = 1.0 - fraction_missed - block.body.sync_aggregate = get_random_sync_aggregate(spec, state, fraction_participated=fraction_participated) + block.body.sync_aggregate = get_random_sync_aggregate( + spec, + state, + block.slot - 1, + fraction_participated=fraction_participated, + ) return block From e72edf07f9e1f35fd3cf7f21970ce85848905841 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 25 Aug 2021 10:40:15 -0700 Subject: [PATCH 122/135] consolidate call to `max` into `randrange` --- .../core/pyspec/eth2spec/test/helpers/multi_operations.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py index 075b03aa0..73ebedfe0 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -48,7 +48,7 @@ def run_slash_and_exit(spec, state, slash_index, exit_index, valid=True): def get_random_proposer_slashings(spec, state, rng): - num_slashings = max(1, rng.randrange(spec.MAX_PROPOSER_SLASHINGS)) + num_slashings = rng.randrange(1, spec.MAX_PROPOSER_SLASHINGS) active_indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy() indices = [ index for index in active_indices @@ -72,7 +72,7 @@ def get_random_attester_slashings(spec, state, rng, slashed_indices=[]): """ # ensure at least one attester slashing, the max count # is small so not much room for random inclusion - num_slashings = max(1, rng.randrange(spec.MAX_ATTESTER_SLASHINGS)) + num_slashings = rng.randrange(1, spec.MAX_ATTESTER_SLASHINGS) active_indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy() indices = [ index for index in active_indices @@ -100,7 +100,7 @@ def get_random_attester_slashings(spec, state, rng, slashed_indices=[]): def get_random_attestations(spec, state, rng): - num_attestations = max(1, rng.randrange(spec.MAX_ATTESTATIONS)) + num_attestations = rng.randrange(1, spec.MAX_ATTESTATIONS) attestations = [ get_valid_attestation( @@ -114,7 +114,7 @@ def get_random_attestations(spec, state, rng): def prepare_state_and_get_random_deposits(spec, state, rng): - num_deposits = max(1, rng.randrange(spec.MAX_DEPOSITS)) + num_deposits = rng.randrange(1, spec.MAX_DEPOSITS) deposit_data_leaves = [spec.DepositData() for _ in range(len(state.validators))] deposits = [] From e575b222be7798ad945669baa9a0b44abd1952a2 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 25 Aug 2021 10:54:55 -0700 Subject: [PATCH 123/135] clarify readme --- tests/generators/random/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/generators/random/README.md b/tests/generators/random/README.md index e1942b7f4..35de22d01 100644 --- a/tests/generators/random/README.md +++ b/tests/generators/random/README.md @@ -1,6 +1,6 @@ # Randomized tests -Randomized tests in the format of `sanity` tests, with randomized operations. +Randomized tests in the format of `sanity` blocks tests, with randomized operations. Information on the format of the tests can be found in the [sanity test formats documentation](../../formats/sanity/README.md). From 81971a89573804d0570fb8ef5cb5fc6d3fd90270 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 25 Aug 2021 11:01:26 -0700 Subject: [PATCH 124/135] update readme for pytest --- tests/generators/random/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/generators/random/README.md b/tests/generators/random/README.md index 35de22d01..fd1728441 100644 --- a/tests/generators/random/README.md +++ b/tests/generators/random/README.md @@ -16,7 +16,9 @@ The generated files are committed to the repo so you should not need to do this. # To run tests -Use the usual `pytest` mechanics used elsewhere in this repo. +Each of the generated test does produce a `pytest` test instance but by default is +currently skipped. Running the test via the generator (see next) will trigger any errors +that would arise during the running of `pytest`. # To generate spec tests (from the generated files) From 0da1fe947dbea4ce6c9272630cc7d19df286bc54 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 25 Aug 2021 11:09:35 -0700 Subject: [PATCH 125/135] clarify how the random block generator works --- tests/core/pyspec/eth2spec/test/utils/random.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/utils/random.py b/tests/core/pyspec/eth2spec/test/utils/random.py index d17a3a9b2..d0fde4565 100644 --- a/tests/core/pyspec/eth2spec/test/utils/random.py +++ b/tests/core/pyspec/eth2spec/test/utils/random.py @@ -100,6 +100,15 @@ def random_block(spec, state, _signed_blocks): to produce a block over ``BLOCK_ATTEMPTS`` slots in order to find a valid block in the event that the proposer has already been slashed. """ + # NOTE: ``state`` has been "randomized" at this point and so will likely + # contain a large number of slashed validators. This function needs to return + # a valid block so it needs to check that the proposer of the next slot is not + # slashed. + # To do this, generate a ``temp_state`` to use for checking the propser in the next slot. + # This ensures no accidental mutations happen to the ``state`` the caller expects to get back + # after this function returns. + # Using a copy of the state for proposer sampling is also sound as any inputs used for the + # shuffling are fixed a few epochs prior to ``spec.get_current_epoch(state)``. temp_state = state.copy() next_slot(spec, temp_state) for _ in range(BLOCK_ATTEMPTS): From a6f8870e18598431716d69e8eaa199cdea25dded Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 25 Aug 2021 11:15:27 -0700 Subject: [PATCH 126/135] update makefile to use correct python version --- tests/generators/random/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/generators/random/Makefile b/tests/generators/random/Makefile index 799001280..1b518bfde 100644 --- a/tests/generators/random/Makefile +++ b/tests/generators/random/Makefile @@ -1,8 +1,8 @@ all: if ! test -d venv; then python3 -m venv venv; fi; . ./venv/bin/activate - pip install -r requirements.txt + pip3 install -r requirements.txt rm -f ../../core/pyspec/eth2spec/test/phase0/random/test_random.py rm -f ../../core/pyspec/eth2spec/test/altair/random/test_random.py - python generate.py phase0 > ../../core/pyspec/eth2spec/test/phase0/random/test_random.py - python generate.py altair > ../../core/pyspec/eth2spec/test/altair/random/test_random.py + python3 generate.py phase0 > ../../core/pyspec/eth2spec/test/phase0/random/test_random.py + python3 generate.py altair > ../../core/pyspec/eth2spec/test/altair/random/test_random.py From 14518d4d642d2ca7f94e709dc155256b0c568a81 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 25 Aug 2021 11:15:59 -0700 Subject: [PATCH 127/135] update name of utility module to be more specific --- tests/core/pyspec/eth2spec/test/altair/random/test_random.py | 2 +- tests/core/pyspec/eth2spec/test/phase0/random/test_random.py | 2 +- .../test/utils/{random.py => randomized_block_tests.py} | 0 tests/generators/random/generate.py | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) rename tests/core/pyspec/eth2spec/test/utils/{random.py => randomized_block_tests.py} (100%) diff --git a/tests/core/pyspec/eth2spec/test/altair/random/test_random.py b/tests/core/pyspec/eth2spec/test/altair/random/test_random.py index d022c2ca1..b581659fe 100644 --- a/tests/core/pyspec/eth2spec/test/altair/random/test_random.py +++ b/tests/core/pyspec/eth2spec/test/altair/random/test_random.py @@ -17,7 +17,7 @@ from eth2spec.test.context import ( with_custom_state, single_phase, ) -from eth2spec.test.utils.random import ( +from eth2spec.test.utils.randomized_block_tests import ( run_generated_randomized_test, ) diff --git a/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py b/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py index 5bb8c3791..abf74e43f 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py @@ -17,7 +17,7 @@ from eth2spec.test.context import ( with_custom_state, single_phase, ) -from eth2spec.test.utils.random import ( +from eth2spec.test.utils.randomized_block_tests import ( run_generated_randomized_test, ) diff --git a/tests/core/pyspec/eth2spec/test/utils/random.py b/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/utils/random.py rename to tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py diff --git a/tests/generators/random/generate.py b/tests/generators/random/generate.py index 3f34a6bc0..d6215bb1f 100644 --- a/tests/generators/random/generate.py +++ b/tests/generators/random/generate.py @@ -13,7 +13,7 @@ import warnings from typing import Callable import itertools -from eth2spec.test.utils.random import ( +from eth2spec.test.utils.randomized_block_tests import ( no_block, no_op_validation, randomize_state, @@ -165,7 +165,7 @@ from eth2spec.test.context import ( with_custom_state, single_phase, ) -from eth2spec.test.utils.random import ( +from eth2spec.test.utils.randomized_block_tests import ( run_generated_randomized_test, )""" From 377797fd0dfcd7d60ca688760779a2a44be13768 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 25 Aug 2021 11:20:17 -0700 Subject: [PATCH 128/135] code layout change --- .../eth2spec/test/helpers/multi_operations.py | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py index 73ebedfe0..bbfc9ffc6 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -171,6 +171,26 @@ def get_random_voluntary_exits(spec, state, to_be_slashed_indices, rng): return prepare_signed_exits(spec, state, exit_indices) +def get_random_sync_aggregate(spec, state, slot, fraction_participated=1.0, rng=Random(2099)): + committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) + participant_count = int(len(committee_indices) * fraction_participated) + participant_indices = rng.sample(range(len(committee_indices)), participant_count) + participants = [ + committee_indices[index] + for index in participant_indices + ] + signature = compute_aggregate_sync_committee_signature( + spec, + state, + slot, + participants, + ) + return spec.SyncAggregate( + sync_committee_bits=[index in participant_indices for index in range(len(committee_indices))], + sync_committee_signature=signature, + ) + + def build_random_block_from_state_for_next_slot(spec, state, rng=Random(2188)): # prepare state for deposits before building block deposits = prepare_state_and_get_random_deposits(spec, state, rng) @@ -211,23 +231,3 @@ def run_test_full_random_operations(spec, state, rng=Random(2080)): yield 'blocks', [signed_block] yield 'post', state - - -def get_random_sync_aggregate(spec, state, slot, fraction_participated=1.0, rng=Random(2099)): - committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) - participant_count = int(len(committee_indices) * fraction_participated) - participant_indices = rng.sample(range(len(committee_indices)), participant_count) - participants = [ - committee_indices[index] - for index in participant_indices - ] - signature = compute_aggregate_sync_committee_signature( - spec, - state, - slot, - participants, - ) - return spec.SyncAggregate( - sync_committee_bits=[index in participant_indices for index in range(len(committee_indices))], - sync_committee_signature=signature, - ) From 961953ac15df9246edc3e36c7bf2ad436911b06f Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 25 Aug 2021 13:10:41 -0700 Subject: [PATCH 129/135] update parameter name --- .../eth2spec/test/utils/randomized_block_tests.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 d0fde4565..4b25fb5ac 100644 --- a/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py +++ b/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py @@ -283,19 +283,19 @@ def _iter_temporal(spec, description): yield i -def run_generated_randomized_test(spec, state, test_description): - if "setup" not in test_description: - state_randomizer = _resolve_ref(test_description.get("state_randomizer", randomize_state)) - test_description["setup"] = _randomized_scenario_setup(state_randomizer) +def run_generated_randomized_test(spec, state, scenario): + if "setup" not in scenario: + state_randomizer = _resolve_ref(scenario.get("state_randomizer", randomize_state)) + scenario["setup"] = _randomized_scenario_setup(state_randomizer) - for mutation, validation in test_description["setup"]: + for mutation, validation in scenario["setup"]: mutation(spec, state) validation(spec, state) yield "pre", state blocks = [] - for transition in test_description["transitions"]: + for transition in scenario["transitions"]: epochs_to_skip = _iter_temporal(spec, transition["epochs_to_skip"]) for _ in epochs_to_skip: next_epoch(spec, state) From 047ff5b09910c8b6fec54f56af2e748c1e4348ef Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 25 Aug 2021 14:16:39 -0700 Subject: [PATCH 130/135] unify visibility on names for doc purposes --- .../test/utils/randomized_block_tests.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) 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 4b25fb5ac..63c242782 100644 --- a/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py +++ b/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py @@ -39,7 +39,7 @@ def randomize_state_altair(spec, state): # epochs -def _epochs_until_leak(spec): +def epochs_until_leak(spec): """ State is "leaking" if the current epoch is at least this value after the last finalized epoch. @@ -47,7 +47,7 @@ def _epochs_until_leak(spec): return spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + 1 -def _epochs_for_shard_committee_period(spec): +def epochs_for_shard_committee_period(spec): return spec.config.SHARD_COMMITTEE_PERIOD @@ -147,24 +147,24 @@ def no_op_validation(spec, state): return True -def _validate_is_leaking(spec, state): +def validate_is_leaking(spec, state): return spec.is_in_inactivity_leak(state) -def _validate_is_not_leaking(spec, state): - return not _validate_is_leaking(spec, state) +def validate_is_not_leaking(spec, state): + return not validate_is_leaking(spec, state) # transitions -def _with_validation(transition, validation): +def with_validation(transition, validation): if isinstance(transition, Callable): transition = transition() transition["validation"] = validation return transition -def _no_op_transition(): +def no_op_transition(): return {} @@ -182,12 +182,12 @@ def slot_transition(n=0): def transition_to_leaking(): return { - "epochs_to_skip": _epochs_until_leak, - "validation": _validate_is_leaking, + "epochs_to_skip": epochs_until_leak, + "validation": validate_is_leaking, } -transition_without_leak = _with_validation(_no_op_transition, _validate_is_not_leaking) +transition_without_leak = with_validation(no_op_transition, validate_is_not_leaking) # block transitions @@ -250,7 +250,7 @@ def _randomized_scenario_setup(state_randomizer): return ( # NOTE: the block randomization function assumes at least 1 shard committee period # so advance the state before doing anything else. - (_skip_epochs(_epochs_for_shard_committee_period), no_op_validation), + (_skip_epochs(epochs_for_shard_committee_period), no_op_validation), (_simulate_honest_execution, no_op_validation), (state_randomizer, ensure_state_has_validators_across_lifecycle), ) From e2dc9f9ec2f5643c833af500e1edf18e7c31b8de Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 25 Aug 2021 14:42:41 -0700 Subject: [PATCH 131/135] update generation of randomized scenarios for more variability --- .../test/altair/random/test_random.py | 120 +++++++++--------- .../test/phase0/random/test_random.py | 120 +++++++++--------- tests/generators/random/generate.py | 19 ++- 3 files changed, 136 insertions(+), 123 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/random/test_random.py b/tests/core/pyspec/eth2spec/test/altair/random/test_random.py index b581659fe..d00da3f9e 100644 --- a/tests/core/pyspec/eth2spec/test/altair/random/test_random.py +++ b/tests/core/pyspec/eth2spec/test/altair/random/test_random.py @@ -35,12 +35,12 @@ def test_randomized_0(spec, state): # scenario as high-level, informal text: # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:last_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -60,13 +60,13 @@ def test_randomized_0(spec, state): def test_randomized_1(spec, state): # scenario as high-level, informal text: # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -87,12 +87,12 @@ def test_randomized_2(spec, state): # scenario as high-level, informal text: # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -113,12 +113,12 @@ def test_randomized_3(spec, state): # scenario as high-level, informal text: # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -142,9 +142,9 @@ def test_randomized_4(spec, state): # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair # epochs:1,slots:0,with-block:no_block - # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -164,13 +164,13 @@ def test_randomized_4(spec, state): def test_randomized_5(spec, state): # scenario as high-level, informal text: # epochs:0,slots:0,with-block:no_block - # epochs:1,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - # epochs:1,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -191,12 +191,12 @@ def test_randomized_6(spec, state): # scenario as high-level, informal text: # epochs:0,slots:0,with-block:no_block # epochs:1,slots:0,with-block:no_block - # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - # epochs:1,slots:0,with-block:no_block - # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -217,12 +217,12 @@ def test_randomized_7(spec, state): # scenario as high-level, informal text: # epochs:0,slots:0,with-block:no_block # epochs:1,slots:0,with-block:no_block - # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair # epochs:1,slots:0,with-block:no_block - # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -241,14 +241,14 @@ def test_randomized_7(spec, state): @always_bls def test_randomized_8(spec, state): # scenario as high-level, informal text: - # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:last_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_altair + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -267,14 +267,14 @@ def test_randomized_8(spec, state): @always_bls def test_randomized_9(spec, state): # scenario as high-level, informal text: - # epochs:_epochs_until_leak,slots:0,with-block:no_block - # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:0,with-block:no_block + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -293,14 +293,14 @@ def test_randomized_9(spec, state): @always_bls def test_randomized_10(spec, state): # scenario as high-level, informal text: - # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -319,14 +319,14 @@ def test_randomized_10(spec, state): @always_bls def test_randomized_11(spec, state): # scenario as high-level, informal text: - # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -345,14 +345,14 @@ def test_randomized_11(spec, state): @always_bls def test_randomized_12(spec, state): # scenario as high-level, informal text: - # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:1,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair # epochs:1,slots:0,with-block:no_block - # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -371,14 +371,14 @@ def test_randomized_12(spec, state): @always_bls def test_randomized_13(spec, state): # scenario as high-level, informal text: - # epochs:_epochs_until_leak,slots:0,with-block:no_block - # epochs:1,slots:0,with-block:no_block + # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - # epochs:1,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -397,14 +397,14 @@ def test_randomized_13(spec, state): @always_bls def test_randomized_14(spec, state): # scenario as high-level, informal text: - # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:1,slots:0,with-block:no_block - # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - # epochs:1,slots:0,with-block:no_block - # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -423,14 +423,14 @@ def test_randomized_14(spec, state): @always_bls def test_randomized_15(spec, state): # scenario as high-level, informal text: - # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:1,slots:0,with-block:no_block - # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair # epochs:1,slots:0,with-block:no_block - # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, diff --git a/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py b/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py index abf74e43f..89a457bed 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py @@ -35,12 +35,12 @@ def test_randomized_0(spec, state): # scenario as high-level, informal text: # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:last_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -60,13 +60,13 @@ def test_randomized_0(spec, state): def test_randomized_1(spec, state): # scenario as high-level, informal text: # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -87,12 +87,12 @@ def test_randomized_2(spec, state): # scenario as high-level, informal text: # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -113,12 +113,12 @@ def test_randomized_3(spec, state): # scenario as high-level, informal text: # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -142,9 +142,9 @@ def test_randomized_4(spec, state): # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block # epochs:1,slots:0,with-block:no_block - # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -164,13 +164,13 @@ def test_randomized_4(spec, state): def test_randomized_5(spec, state): # scenario as high-level, informal text: # epochs:0,slots:0,with-block:no_block - # epochs:1,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - # epochs:1,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -191,12 +191,12 @@ def test_randomized_6(spec, state): # scenario as high-level, informal text: # epochs:0,slots:0,with-block:no_block # epochs:1,slots:0,with-block:no_block - # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - # epochs:1,slots:0,with-block:no_block - # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -217,12 +217,12 @@ def test_randomized_7(spec, state): # scenario as high-level, informal text: # epochs:0,slots:0,with-block:no_block # epochs:1,slots:0,with-block:no_block - # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block # epochs:1,slots:0,with-block:no_block - # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': '_validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -241,14 +241,14 @@ def test_randomized_7(spec, state): @always_bls def test_randomized_8(spec, state): # scenario as high-level, informal text: - # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:last_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -267,14 +267,14 @@ def test_randomized_8(spec, state): @always_bls def test_randomized_9(spec, state): # scenario as high-level, informal text: - # epochs:_epochs_until_leak,slots:0,with-block:no_block - # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:0,with-block:no_block + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -293,14 +293,14 @@ def test_randomized_9(spec, state): @always_bls def test_randomized_10(spec, state): # scenario as high-level, informal text: - # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -319,14 +319,14 @@ def test_randomized_10(spec, state): @always_bls def test_randomized_11(spec, state): # scenario as high-level, informal text: - # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -345,14 +345,14 @@ def test_randomized_11(spec, state): @always_bls def test_randomized_12(spec, state): # scenario as high-level, informal text: - # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:1,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block # epochs:1,slots:0,with-block:no_block - # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -371,14 +371,14 @@ def test_randomized_12(spec, state): @always_bls def test_randomized_13(spec, state): # scenario as high-level, informal text: - # epochs:_epochs_until_leak,slots:0,with-block:no_block - # epochs:1,slots:0,with-block:no_block + # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - # epochs:1,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -397,14 +397,14 @@ def test_randomized_13(spec, state): @always_bls def test_randomized_14(spec, state): # scenario as high-level, informal text: - # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:1,slots:0,with-block:no_block - # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - # epochs:1,slots:0,with-block:no_block - # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -423,14 +423,14 @@ def test_randomized_14(spec, state): @always_bls def test_randomized_15(spec, state): # scenario as high-level, informal text: - # epochs:_epochs_until_leak,slots:0,with-block:no_block + # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:1,slots:0,with-block:no_block - # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block # epochs:1,slots:0,with-block:no_block - # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': '_epochs_until_leak', 'validation': '_validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 yield from run_generated_randomized_test( spec, state, diff --git a/tests/generators/random/generate.py b/tests/generators/random/generate.py index d6215bb1f..7f033f957 100644 --- a/tests/generators/random/generate.py +++ b/tests/generators/random/generate.py @@ -9,6 +9,7 @@ NOTE: To add additional scenarios, add test cases below in ``_generate_randomize """ import sys +import random import warnings from typing import Callable import itertools @@ -70,7 +71,11 @@ def _flatten(t): result = [leak_transition] for transition_batch in t[1]: for transition in transition_batch: - result.append(transition) + if isinstance(transition, tuple): + for subtransition in transition: + result.append(subtransition) + else: + result.append(transition) return result @@ -106,10 +111,18 @@ def _generate_randomized_scenarios(block_randomizer): blocks_set = ( transition_with_random_block(block_randomizer), ) + + rng = random.Random(1447) + all_skips = list(itertools.product(epochs_set, slots_set)) + randomized_skips = ( + rng.sample(all_skips, len(all_skips)) + for _ in range(BLOCK_TRANSITIONS_COUNT) + ) + # build a set of block transitions from combinations of sub-transitions transitions_generator = ( - itertools.product(epochs_set, slots_set, blocks_set) for - _ in range(BLOCK_TRANSITIONS_COUNT) + itertools.product(prefix, blocks_set) + for prefix in randomized_skips ) block_transitions = zip(*transitions_generator) From c206a2772aba5a0c129995843e705ed0f7090793 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 25 Aug 2021 14:46:17 -0700 Subject: [PATCH 132/135] update docs via PR feedback --- tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 63c242782..338f88b6a 100644 --- a/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py +++ b/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py @@ -208,7 +208,7 @@ def transition_with_random_block(block_randomizer): def _randomized_scenario_setup(state_randomizer): """ - Return a sequence of pairs of ("mutator", "validator"), + Return a sequence of pairs of ("mutation", "validation"), a function that accepts (spec, state) arguments and performs some change and a function that accepts (spec, state) arguments and validates some change was made. """ From 5b0d2627c33b2228c92d7efa82139d09da64a2d4 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 25 Aug 2021 15:13:20 -0700 Subject: [PATCH 133/135] apply pr feedback on randrange --- tests/core/pyspec/eth2spec/test/helpers/multi_operations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py index bbfc9ffc6..14b281a95 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -159,7 +159,7 @@ def _eligible_for_exit(spec, state, index): def get_random_voluntary_exits(spec, state, to_be_slashed_indices, rng): - num_exits = max(1, rng.randrange(spec.MAX_VOLUNTARY_EXITS)) + num_exits = rng.randrange(1, spec.MAX_VOLUNTARY_EXITS) active_indices = set(spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy()) indices = set( index for index in active_indices From 4b3022a76794f9b06e66d319d0163b0f43abb582 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 25 Aug 2021 16:19:46 -0600 Subject: [PATCH 134/135] Update tests/generators/random/generate.py --- tests/generators/random/generate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/generators/random/generate.py b/tests/generators/random/generate.py index 7f033f957..825cac128 100644 --- a/tests/generators/random/generate.py +++ b/tests/generators/random/generate.py @@ -141,7 +141,7 @@ def _generate_randomized_scenarios(block_randomizer): def _id_from_scenario(test_description): """ - Construct a test name for ``pytest`` infra. + Construct a name for the scenario based its data. """ def _to_id_part(prefix, x): suffix = str(x) From 874ea80cb8a3c77a321bb29b1979376cbc52a296 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 25 Aug 2021 17:18:02 -0700 Subject: [PATCH 135/135] use more precise name for altair block randomizer and re-gen tests --- .../test/altair/random/test_random.py | 96 +++++++++---------- .../test/utils/randomized_block_tests.py | 7 +- tests/generators/random/generate.py | 4 +- 3 files changed, 55 insertions(+), 52 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/random/test_random.py b/tests/core/pyspec/eth2spec/test/altair/random/test_random.py index d00da3f9e..2250101bd 100644 --- a/tests/core/pyspec/eth2spec/test/altair/random/test_random.py +++ b/tests/core/pyspec/eth2spec/test/altair/random/test_random.py @@ -36,11 +36,11 @@ def test_randomized_0(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation # epochs:1,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -62,11 +62,11 @@ def test_randomized_1(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:1,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -88,11 +88,11 @@ def test_randomized_2(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation # epochs:0,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -114,11 +114,11 @@ def test_randomized_3(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation # epochs:1,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -140,11 +140,11 @@ def test_randomized_4(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:1,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation # epochs:1,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -166,11 +166,11 @@ def test_randomized_5(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation # epochs:0,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -192,11 +192,11 @@ def test_randomized_6(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:1,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation # epochs:0,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -218,11 +218,11 @@ def test_randomized_7(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:1,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation # epochs:1,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation + scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -244,11 +244,11 @@ def test_randomized_8(spec, state): # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation # epochs:1,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -270,11 +270,11 @@ def test_randomized_9(spec, state): # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:1,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -296,11 +296,11 @@ def test_randomized_10(spec, state): # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation # epochs:0,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -322,11 +322,11 @@ def test_randomized_11(spec, state): # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation # epochs:1,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -348,11 +348,11 @@ def test_randomized_12(spec, state): # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:1,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation # epochs:1,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -374,11 +374,11 @@ def test_randomized_13(spec, state): # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation # epochs:0,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -400,11 +400,11 @@ def test_randomized_14(spec, state): # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:1,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation # epochs:0,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -426,11 +426,11 @@ def test_randomized_15(spec, state): # epochs:epochs_until_leak,slots:0,with-block:no_block # epochs:1,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation # epochs:1,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block - # epochs:0,slots:0,with-block:random_block_altair - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation + scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 yield from run_generated_randomized_test( spec, state, 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 338f88b6a..44dab0e0e 100644 --- a/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py +++ b/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py @@ -128,9 +128,12 @@ def random_block(spec, state, _signed_blocks): SYNC_AGGREGATE_PARTICIPATION_BUCKETS = 4 -def random_block_altair(spec, state, signed_blocks): +def random_block_altair_with_cycling_sync_committee_participation(spec, + state, + signed_blocks): block = random_block(spec, state, signed_blocks) - fraction_missed = len(signed_blocks) / SYNC_AGGREGATE_PARTICIPATION_BUCKETS + block_index = len(signed_blocks) % SYNC_AGGREGATE_PARTICIPATION_BUCKETS + fraction_missed = block_index * (1 / SYNC_AGGREGATE_PARTICIPATION_BUCKETS) fraction_participated = 1.0 - fraction_missed block.body.sync_aggregate = get_random_sync_aggregate( spec, diff --git a/tests/generators/random/generate.py b/tests/generators/random/generate.py index 7f033f957..099be5f35 100644 --- a/tests/generators/random/generate.py +++ b/tests/generators/random/generate.py @@ -20,7 +20,7 @@ from eth2spec.test.utils.randomized_block_tests import ( randomize_state, randomize_state_altair, random_block, - random_block_altair, + random_block_altair_with_cycling_sync_committee_participation, last_slot_in_epoch, random_slot_in_epoch, penultimate_slot_in_epoch, @@ -252,7 +252,7 @@ if __name__ == "__main__": run_generate_tests_to_std_out( ALTAIR, state_randomizer=randomize_state_altair, - block_randomizer=random_block_altair, + block_randomizer=random_block_altair_with_cycling_sync_committee_participation, ) if not did_generate: warnings.warn("no phase given for test generation")