From bb327d274e46119b56f30f2352a61e09966f6888 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sat, 19 Jun 2021 03:38:01 +0800 Subject: [PATCH 01/28] Add more Altair validator guide unit tests Add `test_get_sync_committee_message` Add `test_get_sync_committee_selection_proof` Add `always_bls` deco Add `test_is_sync_committee_aggregator` Add `test_get_contribution_and_proof` and `test_get_contribution_and_proof_signature` + fixes --- .../unittests/validator/test_validator.py | 145 ++++++++++++++++-- 1 file changed, 133 insertions(+), 12 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/unittests/validator/test_validator.py b/tests/core/pyspec/eth2spec/test/altair/unittests/validator/test_validator.py index 048e5f43d..e46b6dc5f 100644 --- a/tests/core/pyspec/eth2spec/test/altair/unittests/validator/test_validator.py +++ b/tests/core/pyspec/eth2spec/test/altair/unittests/validator/test_validator.py @@ -1,15 +1,16 @@ import random from collections import defaultdict from eth2spec.utils.ssz.ssz_typing import Bitvector +from eth2spec.utils import bls from eth2spec.test.helpers.block import build_empty_block -from eth2spec.test.helpers.keys import pubkey_to_privkey +from eth2spec.test.helpers.keys import pubkey_to_privkey, privkeys, pubkeys from eth2spec.test.helpers.state import transition_to from eth2spec.test.helpers.sync_committee import compute_sync_committee_signature -from eth2spec.utils.bls import only_with_bls from eth2spec.test.context import ( + always_bls, + spec_state_test, with_altair_and_later, with_presets, - with_state, ) from eth2spec.test.helpers.constants import ( MINIMAL, @@ -29,8 +30,8 @@ def ensure_assignments_in_sync_committee( @with_altair_and_later -@with_state -def test_is_assigned_to_sync_committee(phases, spec, state): +@spec_state_test +def test_is_assigned_to_sync_committee(spec, state): epoch = spec.get_current_epoch(state) validator_indices = spec.get_active_validator_indices(state, epoch) validator_count = len(validator_indices) @@ -90,11 +91,11 @@ def _get_sync_committee_signature( ) -@only_with_bls() @with_altair_and_later @with_presets([MINIMAL], reason="too slow") -@with_state -def test_process_sync_committee_contributions(phases, spec, state): +@spec_state_test +@always_bls +def test_process_sync_committee_contributions(spec, state): # skip over slots at genesis transition_to(spec, state, state.slot + 3) @@ -137,6 +138,28 @@ def test_process_sync_committee_contributions(phases, spec, state): spec.process_block(state, block) +@with_altair_and_later +@spec_state_test +@always_bls +def test_get_sync_committee_message(spec, state): + validator_index = 0 + block_root = spec.Root(b'\x12' * 32) + sync_committee_message = spec.get_sync_committee_message( + state=state, + block_root=block_root, + validator_index=validator_index, + privkey=privkeys[validator_index], + ) + assert sync_committee_message.slot == state.slot + assert sync_committee_message.beacon_block_root == block_root + assert sync_committee_message.validator_index == validator_index + epoch = spec.get_current_epoch(state) + domain = spec.get_domain(state, spec.DOMAIN_SYNC_COMMITTEE, epoch) + signing_root = spec.compute_signing_root(block_root, domain) + signature = bls.Sign(privkeys[validator_index], signing_root) + assert sync_committee_message.signature == signature + + def _validator_index_for_pubkey(state, pubkey): return list(map(lambda v: v.pubkey, state.validators)).index(pubkey) @@ -155,8 +178,8 @@ def _get_expected_subnets_by_pubkey(sync_committee_members): @with_altair_and_later @with_presets([MINIMAL], reason="too slow") -@with_state -def test_compute_subnets_for_sync_committee(state, spec, phases): +@spec_state_test +def test_compute_subnets_for_sync_committee(state, spec): # Transition to the head of the next period transition_to(spec, state, spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD) @@ -184,8 +207,8 @@ def test_compute_subnets_for_sync_committee(state, spec, phases): @with_altair_and_later @with_presets([MINIMAL], reason="too slow") -@with_state -def test_compute_subnets_for_sync_committee_slot_period_boundary(state, spec, phases): +@spec_state_test +def test_compute_subnets_for_sync_committee_slot_period_boundary(state, spec): # Transition to the end of the period transition_to(spec, state, spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD - 1) @@ -209,3 +232,101 @@ def test_compute_subnets_for_sync_committee_slot_period_boundary(state, spec, ph subnets = spec.compute_subnets_for_sync_committee(state, validator_index) expected_subnets = expected_subnets_by_pubkey[pubkey] assert subnets == expected_subnets + + +@with_altair_and_later +@spec_state_test +@always_bls +def test_get_sync_committee_selection_proof(spec, state): + slot = 1 + subcommittee_index = 0 + privkey = privkeys[1] + sync_committee_selection_proof = spec.get_sync_committee_selection_proof( + state, + slot, + subcommittee_index, + privkey, + ) + + domain = spec.get_domain(state, spec.DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF, spec.compute_epoch_at_slot(slot)) + signing_data = spec.SyncAggregatorSelectionData( + slot=slot, + subcommittee_index=subcommittee_index, + ) + signing_root = spec.compute_signing_root(signing_data, domain) + pubkey = pubkeys[1] + assert bls.Verify(pubkey, signing_root, sync_committee_selection_proof) + + +@with_altair_and_later +@spec_state_test +@always_bls +def test_is_sync_committee_aggregator(spec, state): + sample_count = int(spec.SYNC_COMMITTEE_SIZE // spec.SYNC_COMMITTEE_SUBNET_COUNT) + is_aggregator_count = 0 + for i in range(sample_count): + signature = spec.hash(i.to_bytes(32, byteorder="little")) + if spec.is_sync_committee_aggregator(signature): + is_aggregator_count += 1 + + assert is_aggregator_count == spec.TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE + + +@with_altair_and_later +@spec_state_test +def test_get_contribution_and_proof(spec, state): + aggregator_index = 10 + privkey = privkeys[3] + contribution = spec.SyncCommitteeContribution( + slot=10, + beacon_block_root=b'\x12' * 32, + subcommittee_index=1, + aggregation_bits=spec.Bitvector[spec.SYNC_COMMITTEE_SIZE // spec.SYNC_COMMITTEE_SUBNET_COUNT](), + signature=b'\x32' * 96, + ) + selection_proof = spec.get_sync_committee_selection_proof( + state, + contribution.slot, + contribution.subcommittee_index, + privkey, + ) + contribution_and_proof = spec.get_contribution_and_proof( + state, + aggregator_index, + contribution, + privkey, + ) + assert contribution_and_proof == spec.ContributionAndProof( + aggregator_index=aggregator_index, + contribution=contribution, + selection_proof=selection_proof, + ) + + +@with_altair_and_later +@spec_state_test +@always_bls +def test_get_contribution_and_proof_signature(spec, state): + privkey = privkeys[3] + pubkey = pubkeys[3] + + contribution_and_proof = spec.ContributionAndProof( + aggregator_index=10, + contribution=spec.SyncCommitteeContribution( + slot=10, + beacon_block_root=b'\x12' * 32, + subcommittee_index=1, + aggregation_bits=spec.Bitvector[spec.SYNC_COMMITTEE_SIZE // spec.SYNC_COMMITTEE_SUBNET_COUNT](), + signature=b'\x34' * 96, + ), + selection_proof=b'\x56' * 96, + ) + contribution_and_proof_signature = spec.get_contribution_and_proof_signature( + state, + contribution_and_proof, + privkey, + ) + contribution = contribution_and_proof.contribution + domain = spec.get_domain(state, spec.DOMAIN_CONTRIBUTION_AND_PROOF, spec.compute_epoch_at_slot(contribution.slot)) + signing_root = spec.compute_signing_root(contribution_and_proof, domain) + assert bls.Verify(pubkey, signing_root, contribution_and_proof_signature) From 0ed032d392e82df971e5a6626aed4070235368bd Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 1 Jul 2021 12:53:12 -0600 Subject: [PATCH 02/28] add more process_sync_committee_updates tests --- .../test_process_sync_committee_updates.py | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_sync_committee_updates.py b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_sync_committee_updates.py index 1c5caff81..cbfa631cd 100644 --- a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_sync_committee_updates.py +++ b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_sync_committee_updates.py @@ -41,6 +41,15 @@ def run_sync_committees_progress_test(spec, state): # of this `EPOCHS_PER_SYNC_COMMITTEE_PERIOD` third_sync_committee = spec.get_next_sync_committee(state) + # Ensure assignments have changed: + assert state.next_sync_committee != second_sync_committee + if current_period > 0: + assert state.current_sync_committee != first_sync_committee + else: + # Current and next are duplicated in genesis period so remain stable + assert state.current_sync_committee == first_sync_committee + + # Ensure expected committees were calculated assert state.current_sync_committee == second_sync_committee assert state.next_sync_committee == third_sync_committee @@ -75,5 +84,42 @@ def test_sync_committees_progress_not_genesis(spec, state): @single_phase @always_bls @with_presets([MINIMAL], reason="too slow") -def test_sync_committees_progress_misc_balances(spec, state): +def test_sync_committees_progress_misc_balances_genesis(spec, state): + # Genesis epoch period has an exceptional case + assert spec.get_current_epoch(state) == spec.GENESIS_EPOCH + yield from run_sync_committees_progress_test(spec, state) + + +@with_altair_and_later +@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@spec_test +@single_phase +@always_bls +@with_presets([MINIMAL], reason="too slow") +def test_sync_committees_progress_misc_balances_not_genesis(spec, state): + # Transition out of the genesis epoch period to test non-exceptional case + assert spec.get_current_epoch(state) == spec.GENESIS_EPOCH + slot_in_next_period = state.slot + spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + transition_to(spec, state, slot_in_next_period) + + yield from run_sync_committees_progress_test(spec, state) + + +@with_altair_and_later +@spec_state_test +@always_bls +@with_presets([MINIMAL], reason="too slow") +def test_sync_committees_no_progress_not_boundary(spec, state): + assert spec.get_current_epoch(state) == spec.GENESIS_EPOCH + slot_not_at_period_boundary = state.slot + spec.SLOTS_PER_EPOCH + transition_to(spec, state, slot_not_at_period_boundary) + + first_sync_committee = state.current_sync_committee + second_sync_committee = state.next_sync_committee + + yield from run_epoch_processing_with(spec, state, 'process_sync_committee_updates') + + # Ensure assignments have not changed: + assert state.current_sync_committee == first_sync_committee + assert state.next_sync_committee == second_sync_committee From 927b26636e5a5f8eff80d23e4cdaf52d53a13213 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 1 Jul 2021 13:12:15 -0600 Subject: [PATCH 03/28] add more test_process_participation_flag_updates tests --- ...test_process_participation_flag_updates.py | 55 ++++++++++++++++--- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_participation_flag_updates.py b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_participation_flag_updates.py index 82acba322..8aa44bc56 100644 --- a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_participation_flag_updates.py +++ b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_participation_flag_updates.py @@ -12,6 +12,13 @@ from eth2spec.test.helpers.state import next_epoch_via_block from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with +def get_full_flags(spec): + full_flags = spec.ParticipationFlags(0) + for flag_index in range(len(spec.PARTICIPATION_FLAG_WEIGHTS)): + full_flags = spec.add_flag(full_flags, flag_index) + return full_flags + + def run_process_participation_flag_updates(spec, state): old = state.current_epoch_participation.copy() yield from run_epoch_processing_with(spec, state, 'process_participation_flag_updates') @@ -33,12 +40,30 @@ def test_all_zeroed(spec, state): def test_filled(spec, state): next_epoch_via_block(spec, state) - full_flags = spec.ParticipationFlags(0) - for flag_index in range(len(spec.PARTICIPATION_FLAG_WEIGHTS)): - full_flags = spec.add_flag(full_flags, flag_index) + state.previous_epoch_participation = [get_full_flags(spec)] * len(state.validators) + state.current_epoch_participation = [get_full_flags(spec)] * len(state.validators) - state.previous_epoch_participation = [full_flags] * len(state.validators) - state.current_epoch_participation = [full_flags] * len(state.validators) + yield from run_process_participation_flag_updates(spec, state) + + +@with_altair_and_later +@spec_state_test +def test_previous_filled(spec, state): + next_epoch_via_block(spec, state) + + state.previous_epoch_participation = [get_full_flags(spec)] * len(state.validators) + state.current_epoch_participation = [0] * len(state.validators) + + yield from run_process_participation_flag_updates(spec, state) + + +@with_altair_and_later +@spec_state_test +def test_current_filled(spec, state): + next_epoch_via_block(spec, state) + + state.previous_epoch_participation = [0] * len(state.validators) + state.current_epoch_participation = [get_full_flags(spec)] * len(state.validators) yield from run_process_participation_flag_updates(spec, state) @@ -55,9 +80,25 @@ def random_flags(spec, state, seed: int, previous=True, current=True): @with_altair_and_later @spec_state_test -def test_random(spec, state): +def test_random_0(spec, state): next_epoch_via_block(spec, state) - random_flags(spec, state, 10) + random_flags(spec, state, 100) + yield from run_process_participation_flag_updates(spec, state) + + +@with_altair_and_later +@spec_state_test +def test_random_1(spec, state): + next_epoch_via_block(spec, state) + random_flags(spec, state, 101) + yield from run_process_participation_flag_updates(spec, state) + + +@with_altair_and_later +@spec_state_test +def test_random_2(spec, state): + next_epoch_via_block(spec, state) + random_flags(spec, state, 102) yield from run_process_participation_flag_updates(spec, state) From 2aa5bf8384228b7d448e3e2c8e2e0b2ad63e318d Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 1 Jul 2021 16:35:03 -0700 Subject: [PATCH 04/28] assert active validator set is larger than sync committee size ensure set is larger, rather than just equal to --- .../test/altair/block_processing/test_process_sync_aggregate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 fa7f89fdc..1dbe435b9 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 @@ -170,7 +170,7 @@ def test_sync_committee_rewards_nonduplicate_committee(spec, state): active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state))) # Preconditions of this test case - assert active_validator_count >= spec.SYNC_COMMITTEE_SIZE + assert active_validator_count > spec.SYNC_COMMITTEE_SIZE assert committee_size == len(set(committee_indices)) yield from run_successful_sync_committee_test(spec, state, committee_indices, committee_bits) From 21aaab5a1e7c5c1b21c302ce24418e628dac03f0 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 1 Jul 2021 16:36:54 -0700 Subject: [PATCH 05/28] add test for sync aggregate with bad domain in signature --- .../test_process_sync_aggregate.py | 23 +++++++++++++++++++ .../eth2spec/test/helpers/sync_committee.py | 9 +++++--- 2 files changed, 29 insertions(+), 3 deletions(-) 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 1dbe435b9..4b2f6f738 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 @@ -63,6 +63,29 @@ def get_committee_indices(spec, state, duplicates=False): state.randao_mixes[randao_index] = hash(state.randao_mixes[randao_index]) +@with_altair_and_later +@spec_state_test +@always_bls +def test_invalid_signature_bad_domain(spec, state): + committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) + rng = random.Random(2020) + random_participant = rng.choice(committee_indices) + + block = build_empty_block_for_next_slot(spec, state) + # Exclude one participant whose signature was included. + block.body.sync_aggregate = spec.SyncAggregate( + sync_committee_bits=[index != random_participant for index in committee_indices], + sync_committee_signature=compute_aggregate_sync_committee_signature( + spec, + state, + block.slot - 1, + committee_indices, # full committee signs + domain_type=spec.DOMAIN_BEACON_ATTESTER, + ) + ) + yield from run_sync_committee_processing(spec, state, block, expect_exception=True) + + @with_altair_and_later @spec_state_test @always_bls diff --git a/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py b/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py index fa753db52..71be65044 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py +++ b/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py @@ -7,8 +7,10 @@ from eth2spec.test.helpers.block import ( from eth2spec.utils import bls -def compute_sync_committee_signature(spec, state, slot, privkey, block_root=None): - domain = spec.get_domain(state, spec.DOMAIN_SYNC_COMMITTEE, spec.compute_epoch_at_slot(slot)) +def compute_sync_committee_signature(spec, state, slot, privkey, block_root=None, domain_type=None): + if not domain_type: + domain_type = spec.DOMAIN_SYNC_COMMITTEE + domain = spec.get_domain(state, domain_type, spec.compute_epoch_at_slot(slot)) if block_root is None: if slot == state.slot: block_root = build_empty_block_for_next_slot(spec, state).parent_root @@ -18,7 +20,7 @@ def compute_sync_committee_signature(spec, state, slot, privkey, block_root=None return bls.Sign(privkey, signing_root) -def compute_aggregate_sync_committee_signature(spec, state, slot, participants, block_root=None): +def compute_aggregate_sync_committee_signature(spec, state, slot, participants, block_root=None, domain_type=None): if len(participants) == 0: return spec.G2_POINT_AT_INFINITY @@ -32,6 +34,7 @@ def compute_aggregate_sync_committee_signature(spec, state, slot, participants, slot, privkey, block_root=block_root, + domain_type=domain_type, ) ) return bls.Aggregate(signatures) From b3a879c990e4f4148d899461e09e30b4f65ce98d Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 1 Jul 2021 16:40:21 -0700 Subject: [PATCH 06/28] add tests for duplicated sync committee members with various amounts of participation --- .../test_process_sync_aggregate.py | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) 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 4b2f6f738..7e607afcf 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 @@ -202,7 +202,41 @@ def test_sync_committee_rewards_nonduplicate_committee(spec, state): @with_altair_and_later @with_presets([MAINNET], reason="to create duplicate committee") @spec_state_test -def test_sync_committee_rewards_duplicate_committee(spec, state): +def test_sync_committee_rewards_duplicate_committee_no_participation(spec, state): + committee_indices = get_committee_indices(spec, state, duplicates=True) + committee_size = len(committee_indices) + committee_bits = [False] * committee_size + assert len(committee_bits) == committee_size + active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state))) + + # Preconditions of this test case + assert active_validator_count < spec.SYNC_COMMITTEE_SIZE + 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_sync_committee_rewards_duplicate_committee_half_participation(spec, state): + committee_indices = get_committee_indices(spec, state, duplicates=True) + committee_size = len(committee_indices) + committee_bits = [True] * (committee_size // 2) + [False] * (committee_size // 2) + assert len(committee_bits) == committee_size + active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state))) + + # Preconditions of this test case + assert active_validator_count < spec.SYNC_COMMITTEE_SIZE + 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_sync_committee_rewards_duplicate_committee_full_participation(spec, state): committee_indices = get_committee_indices(spec, state, duplicates=True) committee_size = len(committee_indices) committee_bits = [True] * committee_size From faf1ba1f4f3a735a3bc20edcf1a15a7f99b3bb95 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 1 Jul 2021 17:29:09 -0700 Subject: [PATCH 07/28] clean up test this test does not need to collect the intermediate blocks it produces --- .../altair/block_processing/test_process_sync_aggregate.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 7e607afcf..e765658b3 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 @@ -276,7 +276,6 @@ def test_sync_committee_rewards_empty_participants(spec, state): def test_invalid_signature_past_block(spec, state): committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) - blocks = [] for _ in range(2): # NOTE: need to transition twice to move beyond the degenerate case at genesis block = build_empty_block_for_next_slot(spec, state) @@ -291,8 +290,7 @@ def test_invalid_signature_past_block(spec, state): ) ) - signed_block = state_transition_and_sign_block(spec, state, block) - blocks.append(signed_block) + state_transition_and_sign_block(spec, state, block) invalid_block = build_empty_block_for_next_slot(spec, state) # Invalid signature from a slot other than the previous From 49c433746d3237e189819b326a0169b4224f9ac5 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 1 Jul 2021 17:29:33 -0700 Subject: [PATCH 08/28] sync aggregate test with proposer in the committee --- .../test_process_sync_aggregate.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) 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 e765658b3..1b9495dc7 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 @@ -382,3 +382,41 @@ def test_valid_signature_future_committee(spec, state): ) yield from run_sync_committee_processing(spec, state, block) + + +@with_altair_and_later +@spec_state_test +@always_bls +@with_presets([MINIMAL], reason="prefer short search to find matching proposer") +def test_proposer_in_committee_without_participation(spec, state): + committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) + + # NOTE: seem to reliably be getting a matching proposer in the first epoch w/ ``MINIMAL`` preset. + for _ in range(spec.SLOTS_PER_EPOCH): + block = build_empty_block_for_next_slot(spec, state) + proposer_index = block.proposer_index + proposer_pubkey = state.validators[proposer_index].pubkey + proposer_is_in_sync_committee = proposer_pubkey in state.current_sync_committee.pubkeys + if proposer_is_in_sync_committee: + participation = [index != proposer_index for index in committee_indices] + participants = [index for index in committee_indices if index != proposer_index] + else: + participation = [True for _ in committee_indices] + participants = committee_indices + # Valid sync committee signature here... + block.body.sync_aggregate = spec.SyncAggregate( + sync_committee_bits=participation, + sync_committee_signature=compute_aggregate_sync_committee_signature( + spec, + state, + block.slot - 1, + participants, + ) + ) + + if proposer_is_in_sync_committee: + yield from run_sync_committee_processing(spec, state, block) + return + else: + state_transition_and_sign_block(spec, state, block) + raise AssertionError("failed to find a proposer in the sync committee set; check test setup") From f9b4d7f28719f9e62dadb62c32a32e7d89bd7fbd Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 1 Jul 2021 17:36:30 -0700 Subject: [PATCH 09/28] add test for sync aggregate with proposer in sync committee --- .../test_process_sync_aggregate.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) 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 1b9495dc7..55bbafd6f 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 @@ -415,6 +415,42 @@ def test_proposer_in_committee_without_participation(spec, state): ) if proposer_is_in_sync_committee: + assert state.validators[block.proposer_index].pubkey in state.current_sync_committee.pubkeys + yield from run_sync_committee_processing(spec, state, block) + return + else: + state_transition_and_sign_block(spec, state, block) + raise AssertionError("failed to find a proposer in the sync committee set; check test setup") + + +@with_altair_and_later +@spec_state_test +@always_bls +@with_presets([MINIMAL], reason="prefer short search to find matching proposer") +def test_proposer_in_committee_with_participation(spec, state): + committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) + participation = [True for _ in committee_indices] + + # NOTE: seem to reliably be getting a matching proposer in the first epoch w/ ``MINIMAL`` preset. + for _ in range(spec.SLOTS_PER_EPOCH): + block = build_empty_block_for_next_slot(spec, state) + proposer_index = block.proposer_index + proposer_pubkey = state.validators[proposer_index].pubkey + proposer_is_in_sync_committee = proposer_pubkey in state.current_sync_committee.pubkeys + + # Valid sync committee signature here... + block.body.sync_aggregate = spec.SyncAggregate( + sync_committee_bits=participation, + sync_committee_signature=compute_aggregate_sync_committee_signature( + spec, + state, + block.slot - 1, + committee_indices, + ) + ) + + if proposer_is_in_sync_committee: + assert state.validators[block.proposer_index].pubkey in state.current_sync_committee.pubkeys yield from run_sync_committee_processing(spec, state, block) return else: From 7bde1729b117faa122012afc1d286cf46420940c Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 2 Jul 2021 14:42:02 +0800 Subject: [PATCH 10/28] Add Altair networking helper tests --- .../altair/unittests/networking/__init__.py | 0 .../unittests/networking/test_networking.py | 47 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 tests/core/pyspec/eth2spec/test/altair/unittests/networking/__init__.py create mode 100644 tests/core/pyspec/eth2spec/test/altair/unittests/networking/test_networking.py diff --git a/tests/core/pyspec/eth2spec/test/altair/unittests/networking/__init__.py b/tests/core/pyspec/eth2spec/test/altair/unittests/networking/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/pyspec/eth2spec/test/altair/unittests/networking/test_networking.py b/tests/core/pyspec/eth2spec/test/altair/unittests/networking/test_networking.py new file mode 100644 index 000000000..37d50611d --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/unittests/networking/test_networking.py @@ -0,0 +1,47 @@ +from eth2spec.test.context import ( + with_altair_and_later, + spec_state_test, +) +from eth2spec.test.helpers.state import ( + transition_to, +) + + +@with_altair_and_later +@spec_state_test +def test_get_sync_subcommittee_pubkeys_current_sync_committee(state, spec): + # Transition to the head of the next period + transition_to(spec, state, spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD) + + next_slot_epoch = spec.compute_epoch_at_slot(state.slot + 1) + assert ( + spec.compute_sync_committee_period(spec.get_current_epoch(state)) + == spec.compute_sync_committee_period(next_slot_epoch) + ) + sync_committee = state.current_sync_committee + sync_subcommittee_size = spec.SYNC_COMMITTEE_SIZE // spec.SYNC_COMMITTEE_SUBNET_COUNT + subcommittee_index = 1 + i = subcommittee_index * sync_subcommittee_size + + expect = sync_committee.pubkeys[i:i + sync_subcommittee_size] + assert spec.get_sync_subcommittee_pubkeys(state, subcommittee_index) == expect + + +@with_altair_and_later +@spec_state_test +def test_get_sync_subcommittee_pubkeys_next_sync_committee(state, spec): + # Transition to the end of the current period + transition_to(spec, state, spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD - 1) + + next_slot_epoch = spec.compute_epoch_at_slot(state.slot + 1) + assert ( + spec.compute_sync_committee_period(spec.get_current_epoch(state)) + != spec.compute_sync_committee_period(next_slot_epoch) + ) + sync_committee = state.next_sync_committee + sync_subcommittee_size = spec.SYNC_COMMITTEE_SIZE // spec.SYNC_COMMITTEE_SUBNET_COUNT + subcommittee_index = 1 + i = subcommittee_index * sync_subcommittee_size + + expect = sync_committee.pubkeys[i:i + sync_subcommittee_size] + assert spec.get_sync_subcommittee_pubkeys(state, subcommittee_index) == expect From e95d71f85d6675d49859db831ce45e66c5ddb984 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 2 Jul 2021 09:25:08 -0600 Subject: [PATCH 11/28] pr feedback --- .../test_process_sync_committee_updates.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_sync_committee_updates.py b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_sync_committee_updates.py index cbfa631cd..1667a12b2 100644 --- a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_sync_committee_updates.py +++ b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_sync_committee_updates.py @@ -21,8 +21,8 @@ from eth2spec.test.helpers.epoch_processing import ( # def run_sync_committees_progress_test(spec, state): - first_sync_committee = state.current_sync_committee - second_sync_committee = state.next_sync_committee + first_sync_committee = state.current_sync_committee.copy() + second_sync_committee = state.next_sync_committee.copy() current_period = spec.get_current_epoch(state) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD next_period = current_period + 1 @@ -115,8 +115,8 @@ def test_sync_committees_no_progress_not_boundary(spec, state): slot_not_at_period_boundary = state.slot + spec.SLOTS_PER_EPOCH transition_to(spec, state, slot_not_at_period_boundary) - first_sync_committee = state.current_sync_committee - second_sync_committee = state.next_sync_committee + first_sync_committee = state.current_sync_committee.copy() + second_sync_committee = state.next_sync_committee.copy() yield from run_epoch_processing_with(spec, state, 'process_sync_committee_updates') From 2d4ff72b2dccd8ec900681b621d4313a48b87099 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Fri, 2 Jul 2021 08:38:03 -0700 Subject: [PATCH 12/28] Update tests/core/pyspec/eth2spec/test/altair/block_processing/test_process_sync_aggregate.py Co-authored-by: Hsiao-Wei Wang --- .../altair/block_processing/test_process_sync_aggregate.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 55bbafd6f..fe847b741 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 @@ -417,10 +417,11 @@ def test_proposer_in_committee_without_participation(spec, state): if proposer_is_in_sync_committee: assert state.validators[block.proposer_index].pubkey in state.current_sync_committee.pubkeys yield from run_sync_committee_processing(spec, state, block) - return + break else: state_transition_and_sign_block(spec, state, block) - raise AssertionError("failed to find a proposer in the sync committee set; check test setup") + else: + raise AssertionError("failed to find a proposer in the sync committee set; check test setup") @with_altair_and_later From 1865cdb4fc157ad3dc9f04a07bc1550518dd1ef1 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 2 Jul 2021 15:28:05 -0600 Subject: [PATCH 13/28] add comment --- .../test/altair/block_processing/test_process_sync_aggregate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 fe847b741..1228136ac 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 @@ -80,7 +80,7 @@ def test_invalid_signature_bad_domain(spec, state): state, block.slot - 1, committee_indices, # full committee signs - domain_type=spec.DOMAIN_BEACON_ATTESTER, + domain_type=spec.DOMAIN_BEACON_ATTESTER, # Incorrect domain ) ) yield from run_sync_committee_processing(spec, state, block, expect_exception=True) From ac8b4000d38ea5cefb07754a305cbaa0b88587aa Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 6 Jul 2021 17:28:55 -0700 Subject: [PATCH 14/28] remove unnecessary assert --- .../test/altair/block_processing/test_process_sync_aggregate.py | 1 - 1 file changed, 1 deletion(-) 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 1228136ac..9d8ec665f 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 @@ -206,7 +206,6 @@ def test_sync_committee_rewards_duplicate_committee_no_participation(spec, state committee_indices = get_committee_indices(spec, state, duplicates=True) committee_size = len(committee_indices) committee_bits = [False] * committee_size - assert len(committee_bits) == committee_size active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state))) # Preconditions of this test case From fdcb0780d2e9616610afb78d3cfb242cd757f55a Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 6 Jul 2021 18:04:35 -0700 Subject: [PATCH 15/28] add randomized testing for sync aggregate block processing --- .../test_process_sync_aggregate.py | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) 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 9d8ec665f..29eb8882e 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 @@ -17,8 +17,12 @@ from eth2spec.test.helpers.sync_committee import ( compute_committee_indices, ) 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, @@ -456,3 +460,148 @@ 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") +@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") +@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) + ) From c8d22b1d9189e36449afb050142d97a496ae3f08 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 7 Jul 2021 13:44:22 +0100 Subject: [PATCH 16/28] fix minor cosmetic typo --- 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 7823a664d..59ec3ce48 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -211,7 +211,7 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) # Verify the execution payload is valid assert execution_engine.on_payload(payload) - # Cache execution payload + # Cache execution payload header state.latest_execution_payload_header = ExecutionPayloadHeader( parent_hash=payload.parent_hash, coinbase=payload.coinbase, From 3161846aed642d5d1a44926bbf60fda81e3b52d1 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 7 Jul 2021 10:29:14 -0600 Subject: [PATCH 17/28] Apply suggestions from code review --- .../test_process_sync_aggregate.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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 29eb8882e..b9ac8a954 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 @@ -489,7 +489,7 @@ def test_random_only_one_participant_with_duplicates(spec, state): spec, state, duplicates=True, - participation_fn=lambda comm: [rng.choice(comm)] + participation_fn=lambda comm: [rng.choice(comm)], ) @@ -502,7 +502,7 @@ def test_random_low_participation_with_duplicates(spec, state): spec, state, duplicates=True, - participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.25)) + participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.25)), ) @@ -515,7 +515,7 @@ def test_random_high_participation_with_duplicates(spec, state): spec, state, duplicates=True, - participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.75)) + participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.75)), ) @@ -528,7 +528,7 @@ def test_random_all_but_one_participating_with_duplicates(spec, state): spec, state, duplicates=True, - participation_fn=lambda comm: rng.sample(comm, len(comm) - 1) + participation_fn=lambda comm: rng.sample(comm, len(comm) - 1), ) @@ -542,7 +542,7 @@ def test_random_misc_balances_and_half_participation_with_duplicates(spec, state spec, state, duplicates=True, - participation_fn=lambda comm: rng.sample(comm, len(comm) // 2) + participation_fn=lambda comm: rng.sample(comm, len(comm) // 2), ) @@ -554,7 +554,7 @@ def test_random_only_one_participant_without_duplicates(spec, state): yield from _test_harness_for_randomized_test_case( spec, state, - participation_fn=lambda comm: [rng.choice(comm)] + participation_fn=lambda comm: [rng.choice(comm)], ) @@ -566,7 +566,7 @@ def test_random_low_participation_without_duplicates(spec, state): yield from _test_harness_for_randomized_test_case( spec, state, - participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.25)) + participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.25)), ) @@ -578,7 +578,7 @@ def test_random_high_participation_without_duplicates(spec, state): yield from _test_harness_for_randomized_test_case( spec, state, - participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.75)) + participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.75)), ) @@ -590,7 +590,7 @@ def test_random_all_but_one_participating_without_duplicates(spec, state): yield from _test_harness_for_randomized_test_case( spec, state, - participation_fn=lambda comm: rng.sample(comm, len(comm) - 1) + participation_fn=lambda comm: rng.sample(comm, len(comm) - 1), ) @@ -603,5 +603,5 @@ def test_random_misc_balances_and_half_participation_without_duplicates(spec, st yield from _test_harness_for_randomized_test_case( spec, state, - participation_fn=lambda comm: rng.sample(comm, len(comm) // 2) + participation_fn=lambda comm: rng.sample(comm, len(comm) // 2), ) From 3a59c1b789503ba727101b4b179d526cfb04465d Mon Sep 17 00:00:00 2001 From: terence tsao Date: Wed, 7 Jul 2021 10:04:26 -0700 Subject: [PATCH 18/28] Rename a few instances of signature to message --- specs/altair/p2p-interface.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/specs/altair/p2p-interface.md b/specs/altair/p2p-interface.md index 6945704f4..e5e391ff5 100644 --- a/specs/altair/p2p-interface.md +++ b/specs/altair/p2p-interface.md @@ -105,7 +105,7 @@ Note that the `ForkDigestValue` path segment of the topic separates the old and #### Global topics -Altair changes the type of the global beacon block topic and adds one global topic to propagate partially aggregated sync committee signatures to all potential proposers of beacon blocks. +Altair changes the type of the global beacon block topic and adds one global topic to propagate partially aggregated sync committee messages to all potential proposers of beacon blocks. ##### `beacon_block` @@ -117,7 +117,7 @@ See the [state transition document](./beacon-chain.md#beaconblockbody) for Altai ##### `sync_committee_contribution_and_proof` -This topic is used to propagate partially aggregated sync committee signatures to be included in future blocks. +This topic is used to propagate partially aggregated sync committee messages to be included in future blocks. The following validations MUST pass before forwarding the `signed_contribution_and_proof` on the network; define `contribution_and_proof = signed_contribution_and_proof.message`, `contribution = contribution_and_proof.contribution`, and the following function `get_sync_subcommittee_pubkeys` for convenience: @@ -151,18 +151,18 @@ def get_sync_subcommittee_pubkeys(state: BeaconState, subcommittee_index: uint64 #### Sync committee subnets -Sync committee subnets are used to propagate unaggregated sync committee signatures to subsections of the network. +Sync committee subnets are used to propagate unaggregated sync committee messages to subsections of the network. ##### `sync_committee_{subnet_id}` -The `sync_committee_{subnet_id}` topics are used to propagate unaggregated sync committee signatures to the subnet `subnet_id` to be aggregated before being gossiped to the global `sync_committee_contribution_and_proof` topic. +The `sync_committee_{subnet_id}` topics are used to propagate unaggregated sync committee messages to the subnet `subnet_id` to be aggregated before being gossiped to the global `sync_committee_contribution_and_proof` topic. The following validations MUST pass before forwarding the `sync_committee_message` on the network: -- _[IGNORE]_ The signature's slot is for the current slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance), i.e. `sync_committee_message.slot == current_slot`. +- _[IGNORE]_ The message's slot is for the current slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance), i.e. `sync_committee_message.slot == current_slot`. - _[REJECT]_ The `subnet_id` is valid for the given validator, i.e. `subnet_id in compute_subnets_for_sync_committee(state, sync_committee_message.validator_index)`. Note this validation implies the validator is part of the broader current sync committee along with the correct subcommittee. -- _[IGNORE]_ There has been no other valid sync committee signature for the declared `slot` for the validator referenced by `sync_committee_message.validator_index` +- _[IGNORE]_ There has been no other valid sync committee message for the declared `slot` for the validator referenced by `sync_committee_message.validator_index` (this requires maintaining a cache of size `SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT` for each subnet that can be flushed after each slot). Note this validation is _per topic_ so that for a given `slot`, multiple messages could be forwarded with the same `validator_index` as long as the `subnet_id`s are distinct. - _[REJECT]_ The `signature` is valid for the message `beacon_block_root` for the validator referenced by `validator_index`. @@ -170,14 +170,14 @@ The following validations MUST pass before forwarding the `sync_committee_messag #### Sync committees and aggregation The aggregation scheme closely follows the design of the attestation aggregation scheme. -Sync committee signatures are broadcast into "subnets" defined by a topic. +Sync committee messages are broadcast into "subnets" defined by a topic. The number of subnets is defined by `SYNC_COMMITTEE_SUBNET_COUNT` in the [Altair validator guide](./validator.md#constants). Sync committee members are divided into "subcommittees" which are then assigned to a subnet for the duration of tenure in the sync committee. Individual validators can be duplicated in the broader sync committee such that they are included multiple times in a given subcommittee or across multiple subcommittees. -Unaggregated signatures (along with metadata) are sent as `SyncCommitteeMessage`s on the `sync_committee_{subnet_id}` topics. +Unaggregated messages (along with metadata) are sent as `SyncCommitteeMessage`s on the `sync_committee_{subnet_id}` topics. -Aggregated sync committee signatures are packaged into (signed) `SyncCommitteeContribution` along with proofs and gossiped to the `sync_committee_contribution_and_proof` topic. +Aggregated sync committee messages are packaged into (signed) `SyncCommitteeContribution` along with proofs and gossiped to the `sync_committee_contribution_and_proof` topic. ### Transitioning the gossip From 6f31077afa40e9cc1bacfa3f3068bc19439d6742 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Wed, 7 Jul 2021 10:42:52 -0700 Subject: [PATCH 19/28] Update `get_next_sync_committee` notes --- specs/altair/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/altair/beacon-chain.md b/specs/altair/beacon-chain.md index 3bd82aec8..adbc693ee 100644 --- a/specs/altair/beacon-chain.md +++ b/specs/altair/beacon-chain.md @@ -278,7 +278,7 @@ def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorInd #### `get_next_sync_committee` -*Note*: The function `get_next_sync_committee` should only be called at sync committee period boundaries. +*Note*: The function `get_next_sync_committee` should only be called at sync committee period boundaries and upgrading state to Altair. ```python def get_next_sync_committee(state: BeaconState) -> SyncCommittee: From 48382ce09c496cb9474397e33cb7ea60f0d225ad Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 7 Jul 2021 12:17:39 -0600 Subject: [PATCH 20/28] extend inactivity updates tests --- .../test_process_inactivity_updates.py | 171 ++++++++++++++---- .../altair/rewards/test_inactivity_scores.py | 4 +- .../test/altair/sanity/test_blocks.py | 56 +++++- .../test/helpers/inactivity_scores.py | 4 + .../pyspec/eth2spec/test/helpers/random.py | 7 + .../pyspec/eth2spec/test/helpers/rewards.py | 6 +- .../pyspec/eth2spec/test/helpers/state.py | 43 ++++- 7 files changed, 242 insertions(+), 49 deletions(-) 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 1cc8cb9ae..cbee4249c 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 @@ -1,26 +1,20 @@ 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 +from eth2spec.test.helpers.inactivity_scores import randomize_inactivity_scores, zero_inactivity_scores from eth2spec.test.helpers.state import ( next_epoch_via_block, + set_full_participation, set_full_participation_previous_epoch, + set_empty_participation, ) from eth2spec.test.helpers.epoch_processing import ( run_epoch_processing_with ) from eth2spec.test.helpers.random import ( randomize_attestation_participation, + randomize_previous_epoch_participation, ) - - -def set_full_participation(spec, state): - full_flags = spec.ParticipationFlags(0) - for flag_index in range(len(spec.PARTICIPATION_FLAG_WEIGHTS)): - full_flags = spec.add_flag(full_flags, flag_index) - - for index in range(len(state.validators)): - state.current_epoch_participation[index] = full_flags - state.previous_epoch_participation[index] = full_flags +from eth2spec.test.helpers.rewards import leaking def run_process_inactivity_updates(spec, state): @@ -33,58 +27,169 @@ def test_genesis(spec, state): yield from run_process_inactivity_updates(spec, state) +@with_altair_and_later +@spec_state_test +def test_genesis_random_scores(spec, state): + rng = Random(10102) + state.inactivity_scores = [rng.randint(0, 100) for _ in state.inactivity_scores] + pre_scores = state.inactivity_scores.copy() + + yield from run_process_inactivity_updates(spec, state) + + assert state.inactivity_scores == pre_scores + + # # Genesis epoch processing is skipped # Thus all of following tests all go past genesis epoch to test core functionality # +def run_inactivity_scores_test(spec, state, participation_fn=None, inactivity_scores_fn=None, rng=Random(10101)): + next_epoch_via_block(spec, state) + if participation_fn is not None: + participation_fn(spec, state, rng=rng) + if inactivity_scores_fn is not None: + inactivity_scores_fn(spec, state, rng=rng) + yield from run_process_inactivity_updates(spec, state) + + @with_altair_and_later @spec_state_test def test_all_zero_inactivity_scores_empty_participation(spec, state): - next_epoch_via_block(spec, state) - state.inactivity_scores = [0] * len(state.validators) - yield from run_process_inactivity_updates(spec, state) + yield from run_inactivity_scores_test(spec, state, set_empty_participation, zero_inactivity_scores) + assert set(state.inactivity_scores) == set([0]) + + +@with_altair_and_later +@spec_state_test +@leaking() +def test_all_zero_inactivity_scores_empty_participation_leaking(spec, state): + yield from run_inactivity_scores_test(spec, state, set_empty_participation, zero_inactivity_scores) + + # Should still in be leak + assert spec.is_in_inactivity_leak(state) + + for score in state.inactivity_scores: + assert score > 0 @with_altair_and_later @spec_state_test def test_all_zero_inactivity_scores_random_participation(spec, state): - next_epoch_via_block(spec, state) - state.inactivity_scores = [0] * len(state.validators) - randomize_attestation_participation(spec, state, rng=Random(5555)) - yield from run_process_inactivity_updates(spec, state) + yield from run_inactivity_scores_test( + spec, state, + randomize_attestation_participation, zero_inactivity_scores, rng=Random(5555), + ) + assert set(state.inactivity_scores) == set([0]) + + +@with_altair_and_later +@spec_state_test +@leaking() +def test_all_zero_inactivity_scores_random_participation_leaking(spec, state): + # Only randompize participation in previous epoch to remain in leak + yield from run_inactivity_scores_test( + spec, state, + randomize_previous_epoch_participation, zero_inactivity_scores, rng=Random(5555), + ) + + # Check still in leak + assert spec.is_in_inactivity_leak(state) + + assert 0 in state.inactivity_scores + assert len(set(state.inactivity_scores)) > 1 @with_altair_and_later @spec_state_test def test_all_zero_inactivity_scores_full_participation(spec, state): - next_epoch_via_block(spec, state) - set_full_participation(spec, state) - state.inactivity_scores = [0] * len(state.validators) - yield from run_process_inactivity_updates(spec, state) + yield from run_inactivity_scores_test( + spec, state, + set_full_participation, zero_inactivity_scores, + ) + + assert set(state.inactivity_scores) == set([0]) + + +@with_altair_and_later +@spec_state_test +@leaking() +def test_all_zero_inactivity_scores_full_participation_leaking(spec, state): + # Only set full participation in previous epoch to remain in leak + yield from run_inactivity_scores_test( + spec, state, + set_full_participation_previous_epoch, zero_inactivity_scores, + ) + + # Check still in leak + assert spec.is_in_inactivity_leak(state) + + assert set(state.inactivity_scores) == set([0]) @with_altair_and_later @spec_state_test def test_random_inactivity_scores_empty_participation(spec, state): - next_epoch_via_block(spec, state) - randomize_inactivity_scores(spec, state, rng=Random(9999)) - yield from run_process_inactivity_updates(spec, state) + yield from run_inactivity_scores_test( + spec, state, + set_empty_participation, randomize_inactivity_scores, Random(9999), + ) + + +@with_altair_and_later +@spec_state_test +@leaking() +def test_random_inactivity_scores_empty_participation_leaking(spec, state): + yield from run_inactivity_scores_test( + spec, state, + set_empty_participation, randomize_inactivity_scores, Random(9999), + ) + + # Check still in leak + assert spec.is_in_inactivity_leak(state) @with_altair_and_later @spec_state_test def test_random_inactivity_scores_random_participation(spec, state): - next_epoch_via_block(spec, state) - randomize_attestation_participation(spec, state, rng=Random(22222)) - randomize_inactivity_scores(spec, state, rng=Random(22222)) - yield from run_process_inactivity_updates(spec, state) + yield from run_inactivity_scores_test( + spec, state, + randomize_attestation_participation, randomize_inactivity_scores, Random(22222), + ) + + +@with_altair_and_later +@spec_state_test +@leaking() +def test_random_inactivity_scores_random_participation_leaking(spec, state): + # Only randompize participation in previous epoch to remain in leak + yield from run_inactivity_scores_test( + spec, state, + randomize_previous_epoch_participation, randomize_inactivity_scores, Random(22222), + ) + + # Check still in leak + assert spec.is_in_inactivity_leak(state) @with_altair_and_later @spec_state_test def test_random_inactivity_scores_full_participation(spec, state): - next_epoch_via_block(spec, state) - set_full_participation(spec, state) - randomize_inactivity_scores(spec, state, rng=Random(33333)) - yield from run_process_inactivity_updates(spec, state) + yield from run_inactivity_scores_test( + spec, state, + set_full_participation, randomize_inactivity_scores, Random(33333), + ) + + +@with_altair_and_later +@spec_state_test +@leaking() +def test_random_inactivity_scores_full_participation_leaking(spec, state): + # Only set full participation in previous epoch to remain in leak + yield from run_inactivity_scores_test( + spec, state, + set_full_participation_previous_epoch, randomize_inactivity_scores, Random(33333), + ) + + # Check still in leak + assert spec.is_in_inactivity_leak(state) diff --git a/tests/core/pyspec/eth2spec/test/altair/rewards/test_inactivity_scores.py b/tests/core/pyspec/eth2spec/test/altair/rewards/test_inactivity_scores.py index b6b362a8b..80ef91d0f 100644 --- a/tests/core/pyspec/eth2spec/test/altair/rewards/test_inactivity_scores.py +++ b/tests/core/pyspec/eth2spec/test/altair/rewards/test_inactivity_scores.py @@ -112,7 +112,7 @@ def test_random_high_inactivity_scores_leaking(spec, state): @with_altair_and_later @spec_state_test -@leaking(epochs=5) -def test_random_high_inactivity_scores_leaking_5_epochs(spec, state): +@leaking(epochs=8) +def test_random_high_inactivity_scores_leaking_8_epochs(spec, state): randomize_inactivity_scores(spec, state, minimum=500000, maximum=5000000, rng=Random(9998)) yield from rewards_helpers.run_test_full_random(spec, state, rng=Random(9998)) diff --git a/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py index 0c85bd1e2..d87522bda 100644 --- a/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py @@ -1,8 +1,9 @@ -import random +from random import Random + from eth2spec.test.helpers.state import ( state_transition_and_sign_block, next_epoch, - next_epoch_via_block, + set_full_participation_previous_epoch, ) from eth2spec.test.helpers.block import ( build_empty_block_for_next_slot, @@ -15,12 +16,14 @@ from eth2spec.test.context import ( with_altair_and_later, spec_state_test, ) +from eth2spec.test.helpers.rewards import leaking +from eth2spec.test.helpers.inactivity_scores import randomize_inactivity_scores -def run_sync_committee_sanity_test(spec, state, fraction_full=1.0): +def run_sync_committee_sanity_test(spec, state, fraction_full=1.0, rng=Random(454545)): all_pubkeys = [v.pubkey for v in state.validators] committee = [all_pubkeys.index(pubkey) for pubkey in state.current_sync_committee.pubkeys] - participants = random.sample(committee, int(len(committee) * fraction_full)) + participants = rng.sample(committee, int(len(committee) * fraction_full)) yield 'pre', state @@ -51,7 +54,7 @@ def test_full_sync_committee_committee(spec, state): @spec_state_test def test_half_sync_committee_committee(spec, state): next_epoch(spec, state) - yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.5) + yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.5, rng=Random(1212)) @with_altair_and_later @@ -70,7 +73,7 @@ def test_full_sync_committee_committee_genesis(spec, state): @with_altair_and_later @spec_state_test def test_half_sync_committee_committee_genesis(spec, state): - yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.5) + yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.5, rng=Random(2323)) @with_altair_and_later @@ -81,11 +84,13 @@ def test_empty_sync_committee_committee_genesis(spec, state): @with_altair_and_later @spec_state_test -def test_inactivity_scores(spec, state): - for _ in range(spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2): - next_epoch_via_block(spec, state) - +@leaking() +def test_inactivity_scores_leaking(spec, state): assert spec.is_in_inactivity_leak(state) + + randomize_inactivity_scores(spec, state, rng=Random(5252)) + assert len(set(state.inactivity_scores)) > 1 + previous_inactivity_scores = state.inactivity_scores.copy() yield 'pre', state @@ -97,5 +102,36 @@ def test_inactivity_scores(spec, state): yield 'blocks', [signed_block] yield 'post', state + # No particiaption during a leak so all scores should increase for pre, post in zip(previous_inactivity_scores, state.inactivity_scores): assert post == pre + spec.config.INACTIVITY_SCORE_BIAS + + +@with_altair_and_later +@spec_state_test +@leaking() +def test_inactivity_scores_full_participation_leaking(spec, state): + randomize_inactivity_scores(spec, state, rng=Random(5252)) + assert len(set(state.inactivity_scores)) > 1 + + # Only set full participation for previous epoch to remain in leak + set_full_participation_previous_epoch(spec, state) + + previous_inactivity_scores = state.inactivity_scores.copy() + + yield 'pre', state + + # Block transition to next epoch + block = build_empty_block(spec, state, slot=state.slot + spec.SLOTS_PER_EPOCH) + signed_block = state_transition_and_sign_block(spec, state, block) + + assert spec.is_in_inactivity_leak(state) + + yield 'blocks', [signed_block] + yield 'post', state + + # Full particiaption during a leak so all scores should decrease by 1 + print(previous_inactivity_scores) + print(state.inactivity_scores) + for pre, post in zip(previous_inactivity_scores, state.inactivity_scores): + assert post == pre - 1 diff --git a/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py b/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py index 5c28bfc24..29f9038a8 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py +++ b/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py @@ -3,3 +3,7 @@ from random import Random def randomize_inactivity_scores(spec, state, minimum=0, maximum=50000, rng=Random(4242)): state.inactivity_scores = [rng.randint(minimum, maximum) for _ in range(len(state.validators))] + + +def zero_inactivity_scores(spec, state, rng=None): + state.inactivity_scores = [0] * len(state.validators) diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index 5b5e419ba..d804fa4b9 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -100,6 +100,13 @@ def randomize_epoch_participation(spec, state, epoch, rng): epoch_participation[index] = flags +def randomize_previous_epoch_participation(spec, state, rng=Random(8020)): + cached_prepare_state_with_attestations(spec, state) + randomize_epoch_participation(spec, state, spec.get_previous_epoch(state), rng) + if not is_post_altair(spec): + state.current_epoch_attestations = [] + + def randomize_attestation_participation(spec, state, rng=Random(8020)): cached_prepare_state_with_attestations(spec, state) randomize_epoch_participation(spec, state, spec.get_previous_epoch(state), rng) diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index 83b99f66d..874e47305 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -260,9 +260,9 @@ def run_get_inactivity_penalty_deltas(spec, state): def transition_state_to_leak(spec, state, epochs=None): if epochs is None: - # +1 to trigger inactivity_score transitions - epochs = spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + 1 - assert epochs >= spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + # +2 because finality delay is based on previous_epoch and must be more than `MIN_EPOCHS_TO_INACTIVITY_PENALTY` + epochs = spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2 + assert epochs > spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY for _ in range(epochs): next_epoch(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/helpers/state.py b/tests/core/pyspec/eth2spec/test/helpers/state.py index 05f0e9013..666023fec 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/state.py +++ b/tests/core/pyspec/eth2spec/test/helpers/state.py @@ -1,4 +1,4 @@ -from eth2spec.test.context import expect_assertion_error +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 @@ -92,3 +92,44 @@ def state_transition_and_sign_block(spec, state, block, expect_fail=False): transition_unsigned_block(spec, state, block) block.state_root = state.hash_tree_root() return sign_block(spec, state, block) + + +# +# WARNING: The following functions can only be used post-altair due to the manipulation of participation flags directly +# + + +def _set_full_participation(spec, state, current=True, previous=True): + assert is_post_altair(spec) + + full_flags = spec.ParticipationFlags(0) + for flag_index in range(len(spec.PARTICIPATION_FLAG_WEIGHTS)): + full_flags = spec.add_flag(full_flags, flag_index) + + for index in range(len(state.validators)): + if current: + state.current_epoch_participation[index] = full_flags.copy() + if previous: + state.previous_epoch_participation[index] = full_flags.copy() + + +def set_full_participation(spec, state, rng=None): + _set_full_participation(spec, state) + + +def set_full_participation_previous_epoch(spec, state, rng=None): + _set_full_participation(spec, state, current=False, previous=True) + + +def _set_empty_participation(spec, state, current=True, previous=True): + assert is_post_altair(spec) + + for index in range(len(state.validators)): + if current: + state.current_epoch_participation[index] = spec.ParticipationFlags(0) + if previous: + state.previous_epoch_participation[index] = spec.ParticipationFlags(0) + + +def set_empty_participation(spec, state, rng=None): + _set_empty_participation(spec, state) From 36d2dacdcfcdbac273d49b1bfdfe24cc1c6b0c39 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 7 Jul 2021 16:10:06 -0600 Subject: [PATCH 21/28] added slashed tests to leak score tests --- .../test_process_inactivity_updates.py | 63 +++++++++++++++++++ 1 file changed, 63 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 cbee4249c..6fdfc8a01 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 @@ -193,3 +193,66 @@ def test_random_inactivity_scores_full_participation_leaking(spec, state): # Check still in leak assert spec.is_in_inactivity_leak(state) + + +def slash_some_validators(spec, state, rng=Random(40404040)): + # Slash ~1/4 of validaors + for validator_index in range(len(state.validators)): + if rng.choice(range(4)) == 0: + spec.slash_validator(state, validator_index) + + +@with_altair_and_later +@spec_state_test +def test_some_slashed_zero_scores_full_participation(spec, state): + slash_some_validators(spec, state, rng=Random(33429)) + yield from run_inactivity_scores_test( + spec, state, + set_full_participation, zero_inactivity_scores, + ) + + assert set(state.inactivity_scores) == set([0]) + + +@with_altair_and_later +@spec_state_test +@leaking() +def test_some_slashed_zero_scores_full_participation_leaking(spec, state): + slash_some_validators(spec, state, rng=Random(33221)) + yield from run_inactivity_scores_test( + spec, state, + set_full_participation_previous_epoch, zero_inactivity_scores, + ) + + # Check still in leak + assert spec.is_in_inactivity_leak(state) + + # Ensure some zero scores (non-slashed values) and non-zero scores (slashed vals) in there + for score, validator in zip(state.inactivity_scores, state.validators): + if validator.slashed: + assert score > 0 + else: + assert score == 0 + + +@with_altair_and_later +@spec_state_test +def test_some_slashed_full_random(spec, state): + rng = Random(1010222) + slash_some_validators(spec, state, rng=rng) + yield from run_inactivity_scores_test( + spec, state, + randomize_previous_epoch_participation, randomize_inactivity_scores, rng=rng, + ) + + +@with_altair_and_later +@spec_state_test +@leaking() +def test_some_slashed_full_random_leaking(spec, state): + rng = Random(1102233) + slash_some_validators(spec, state, rng=rng) + yield from run_inactivity_scores_test( + spec, state, + randomize_previous_epoch_participation, randomize_inactivity_scores, rng=rng, + ) From 88bb7562aade08594ad1682c9f7dec717c3a6e59 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 7 Jul 2021 16:11:58 -0600 Subject: [PATCH 22/28] Update specs/altair/beacon-chain.md --- specs/altair/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/altair/beacon-chain.md b/specs/altair/beacon-chain.md index adbc693ee..a8d7fd8ef 100644 --- a/specs/altair/beacon-chain.md +++ b/specs/altair/beacon-chain.md @@ -278,7 +278,7 @@ def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorInd #### `get_next_sync_committee` -*Note*: The function `get_next_sync_committee` should only be called at sync committee period boundaries and upgrading state to Altair. +*Note*: The function `get_next_sync_committee` should only be called at sync committee period boundaries and when [upgrading state to Altair](./fork.md#upgrading-the-state). ```python def get_next_sync_committee(state: BeaconState) -> SyncCommittee: From 19001055e9f276191924015c7907ed42361022c2 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 8 Jul 2021 06:14:47 -0700 Subject: [PATCH 23/28] Update tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py --- tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py index d87522bda..fd3f14875 100644 --- a/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py @@ -102,7 +102,7 @@ def test_inactivity_scores_leaking(spec, state): yield 'blocks', [signed_block] yield 'post', state - # No particiaption during a leak so all scores should increase + # No participation during a leak so all scores should increase for pre, post in zip(previous_inactivity_scores, state.inactivity_scores): assert post == pre + spec.config.INACTIVITY_SCORE_BIAS From a5a48d5a23256d8fce5a1f7b7629e49da4091adf Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 8 Jul 2021 12:18:54 -0600 Subject: [PATCH 24/28] PR feedback from @ralexstokes --- .../altair/epoch_processing/test_process_inactivity_updates.py | 3 ++- tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py | 2 -- tests/core/pyspec/eth2spec/test/helpers/rewards.py | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) 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 6fdfc8a01..e3a82ac74 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 @@ -57,6 +57,7 @@ def run_inactivity_scores_test(spec, state, participation_fn=None, inactivity_sc @spec_state_test def test_all_zero_inactivity_scores_empty_participation(spec, state): yield from run_inactivity_scores_test(spec, state, set_empty_participation, zero_inactivity_scores) + # Not yet in leak so no leak score added even though no participation assert set(state.inactivity_scores) == set([0]) @@ -87,7 +88,7 @@ def test_all_zero_inactivity_scores_random_participation(spec, state): @spec_state_test @leaking() def test_all_zero_inactivity_scores_random_participation_leaking(spec, state): - # Only randompize participation in previous epoch to remain in leak + # Only randomize participation in previous epoch to remain in leak yield from run_inactivity_scores_test( spec, state, randomize_previous_epoch_participation, zero_inactivity_scores, rng=Random(5555), diff --git a/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py index fd3f14875..662891762 100644 --- a/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py @@ -131,7 +131,5 @@ def test_inactivity_scores_full_participation_leaking(spec, state): yield 'post', state # Full particiaption during a leak so all scores should decrease by 1 - print(previous_inactivity_scores) - print(state.inactivity_scores) for pre, post in zip(previous_inactivity_scores, state.inactivity_scores): assert post == pre - 1 diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index 874e47305..1867db08f 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -266,6 +266,7 @@ def transition_state_to_leak(spec, state, epochs=None): for _ in range(epochs): next_epoch(spec, state) + assert spec.is_in_inactivity_leak(state) _cache_dict = LRU(size=10) From f460870c112b124128685cf174b9779bc2e5ad3d Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 8 Jul 2021 12:50:45 -0600 Subject: [PATCH 25/28] clean up some leak tests --- .../test_process_inactivity_updates.py | 14 ++++++++------ tests/core/pyspec/eth2spec/test/helpers/random.py | 2 ++ .../eth2spec/test/phase0/rewards/test_leak.py | 4 ++-- 3 files changed, 12 insertions(+), 8 deletions(-) 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 e3a82ac74..fd1bf3c57 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 @@ -4,7 +4,7 @@ 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_via_block, - set_full_participation, set_full_participation_previous_epoch, + set_full_participation, set_empty_participation, ) from eth2spec.test.helpers.epoch_processing import ( @@ -57,7 +57,6 @@ def run_inactivity_scores_test(spec, state, participation_fn=None, inactivity_sc @spec_state_test def test_all_zero_inactivity_scores_empty_participation(spec, state): yield from run_inactivity_scores_test(spec, state, set_empty_participation, zero_inactivity_scores) - # Not yet in leak so no leak score added even though no participation assert set(state.inactivity_scores) == set([0]) @@ -119,7 +118,7 @@ def test_all_zero_inactivity_scores_full_participation_leaking(spec, state): # Only set full participation in previous epoch to remain in leak yield from run_inactivity_scores_test( spec, state, - set_full_participation_previous_epoch, zero_inactivity_scores, + set_full_participation, zero_inactivity_scores, ) # Check still in leak @@ -189,7 +188,7 @@ def test_random_inactivity_scores_full_participation_leaking(spec, state): # Only set full participation in previous epoch to remain in leak yield from run_inactivity_scores_test( spec, state, - set_full_participation_previous_epoch, randomize_inactivity_scores, Random(33333), + set_full_participation, randomize_inactivity_scores, Random(33333), ) # Check still in leak @@ -222,7 +221,7 @@ def test_some_slashed_zero_scores_full_participation_leaking(spec, state): slash_some_validators(spec, state, rng=Random(33221)) yield from run_inactivity_scores_test( spec, state, - set_full_participation_previous_epoch, zero_inactivity_scores, + set_full_participation, zero_inactivity_scores, ) # Check still in leak @@ -243,7 +242,7 @@ def test_some_slashed_full_random(spec, state): slash_some_validators(spec, state, rng=rng) yield from run_inactivity_scores_test( spec, state, - randomize_previous_epoch_participation, randomize_inactivity_scores, rng=rng, + randomize_attestation_participation, randomize_inactivity_scores, rng=rng, ) @@ -257,3 +256,6 @@ def test_some_slashed_full_random_leaking(spec, state): spec, state, randomize_previous_epoch_participation, randomize_inactivity_scores, rng=rng, ) + + # Check still in leak + assert spec.is_in_inactivity_leak(state) diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index d804fa4b9..70c871a34 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -105,6 +105,8 @@ def randomize_previous_epoch_participation(spec, state, rng=Random(8020)): randomize_epoch_participation(spec, state, spec.get_previous_epoch(state), rng) if not is_post_altair(spec): state.current_epoch_attestations = [] + else: + state.current_epoch_participation = [spec.ParticipationFlags(0b0000_0000) for _ in range(len(state.validators))] def randomize_attestation_participation(spec, state, rng=Random(8020)): diff --git a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_leak.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_leak.py index 0ad2491f0..178b4ff77 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_leak.py +++ b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_leak.py @@ -145,8 +145,8 @@ def test_full_random_leak(spec, state): @with_all_phases @spec_state_test -@leaking(epochs=5) -def test_full_random_five_epoch_leak(spec, state): +@leaking(epochs=7) +def test_full_random_seven_epoch_leak(spec, state): yield from rewards_helpers.run_test_full_random(spec, state) From 6d0deddbe5933f628fa53d5cb71ac6136429f13f Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 12 Jul 2021 09:03:43 -0600 Subject: [PATCH 26/28] modify target sync committee aggregators to more sound 16 --- specs/altair/validator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/altair/validator.md b/specs/altair/validator.md index 49146da00..8742900bc 100644 --- a/specs/altair/validator.md +++ b/specs/altair/validator.md @@ -73,7 +73,7 @@ This document is currently illustrative for early Altair testnets and some parts | Name | Value | Unit | | - | - | :-: | -| `TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE` | `2**2` (= 4) | validators | +| `TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE` | `2**4` (= 16) | validators | | `SYNC_COMMITTEE_SUBNET_COUNT` | `4` | The number of sync committee subnets used in the gossipsub aggregation protocol. | ## Containers From 93a31f9011e3dcec167cdbca4d2171142931b149 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 13 Jul 2021 00:16:31 +0800 Subject: [PATCH 27/28] Fix test: use mainnet preset and accept deviation --- .../altair/unittests/validator/test_validator.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/unittests/validator/test_validator.py b/tests/core/pyspec/eth2spec/test/altair/unittests/validator/test_validator.py index e46b6dc5f..dd9214040 100644 --- a/tests/core/pyspec/eth2spec/test/altair/unittests/validator/test_validator.py +++ b/tests/core/pyspec/eth2spec/test/altair/unittests/validator/test_validator.py @@ -13,6 +13,7 @@ from eth2spec.test.context import ( with_presets, ) from eth2spec.test.helpers.constants import ( + MAINNET, MINIMAL, ) @@ -260,16 +261,21 @@ def test_get_sync_committee_selection_proof(spec, state): @with_altair_and_later @spec_state_test -@always_bls +@with_presets([MAINNET], reason="to test against the mainnet SYNC_COMMITTEE_SIZE") def test_is_sync_committee_aggregator(spec, state): - sample_count = int(spec.SYNC_COMMITTEE_SIZE // spec.SYNC_COMMITTEE_SUBNET_COUNT) + sample_count = int(spec.SYNC_COMMITTEE_SIZE // spec.SYNC_COMMITTEE_SUBNET_COUNT) * 100 is_aggregator_count = 0 for i in range(sample_count): signature = spec.hash(i.to_bytes(32, byteorder="little")) if spec.is_sync_committee_aggregator(signature): is_aggregator_count += 1 - assert is_aggregator_count == spec.TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE + # Accept ~10% deviation + assert ( + spec.TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE * 100 * 0.9 + <= is_aggregator_count + <= spec.TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE * 100 * 1.1 + ) @with_altair_and_later From b25d690ee85f140d63de5c5eadb4067de1358a08 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 13 Jul 2021 16:18:37 -0600 Subject: [PATCH 28/28] bump VERSION.txt to 1.1.0-beta.1 --- 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 a40920e99..854663a0e 100644 --- a/tests/core/pyspec/eth2spec/VERSION.txt +++ b/tests/core/pyspec/eth2spec/VERSION.txt @@ -1 +1 @@ -1.1.0-alpha.8 \ No newline at end of file +1.1.0-beta.1 \ No newline at end of file