diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 897dae369..fe5c0d9a7 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -622,8 +622,7 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None: # Type 2: no shard transition, no custody bits # TODO: could only allow for older attestations. else: # Ensure delayed attestation - # Currently commented out because breaks a ton of phase 0 tests - # assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY < state.slot + assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY < state.slot # Late attestations cannot have a shard transition root assert data.shard_transition_root == Root() diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py index e34c32c0e..17d4f644f 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py @@ -1,9 +1,8 @@ from eth2spec.test.context import with_all_phases, spec_state_test -from eth2spec.test.helpers.attestations import get_valid_attestation +from eth2spec.test.helpers.attestations import get_valid_attestation, next_epoch_with_attestations from eth2spec.test.helpers.block import build_empty_block_for_next_slot from eth2spec.test.helpers.state import ( next_epoch, - next_epoch_with_attestations, state_transition_and_sign_block, ) diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_block.py index f50a00a9f..3d824811c 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_block.py @@ -4,7 +4,8 @@ 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.block import build_empty_block_for_next_slot, sign_block, transition_unsigned_block, \ build_empty_block -from eth2spec.test.helpers.state import next_epoch, next_epoch_with_attestations, state_transition_and_sign_block +from eth2spec.test.helpers.attestations import next_epoch_with_attestations +from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_block def run_on_block(spec, store, signed_block, valid=True): diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 0a6e3294d..8f11e3306 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -1,7 +1,8 @@ from typing import List -from eth2spec.test.helpers.block import build_empty_block_for_next_slot from eth2spec.test.context import expect_assertion_error +from eth2spec.test.helpers.state import next_slot, state_transition_and_sign_block +from eth2spec.test.helpers.block import build_empty_block_for_next_slot, transition_unsigned_block from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls from eth2spec.utils.ssz.ssz_typing import Bitlist @@ -74,7 +75,48 @@ def build_attestation_data(spec, state, slot, index): ) -def get_valid_attestation(spec, state, slot=None, index=None, signed=False): +def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False): + shard = spec.get_shard(state, attestation) + + next_state = state.copy() + next_slot(spec, next_state) + offset_slots = spec.get_offset_slots(next_state, spec.get_next_slot_for_shard(next_state, shard)) + for offset_slot in offset_slots: + attestation.custody_bits_blocks.append( + Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits]) + ) + + if signed: + sign_attestation(spec, state, attestation) + + return attestation + + +def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=False): + ''' + Construct on-time attestation for next slot + ''' + if slot is None: + slot = state.slot + if index is None: + index = 0 + + return get_valid_attestation(spec, state, slot, index, signed, True) + + +def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False): + ''' + Construct on-time attestation for next slot + ''' + if slot is None: + slot = state.slot + if index is None: + index = 0 + + return get_valid_attestation(spec, state, slot, index, signed, False) + + +def get_valid_attestation(spec, state, slot=None, index=None, signed=False, on_time=True): if slot is None: slot = state.slot if index is None: @@ -97,6 +139,10 @@ def get_valid_attestation(spec, state, slot=None, index=None, signed=False): fill_aggregate_attestation(spec, state, attestation) if signed: sign_attestation(spec, state, attestation) + + if spec.fork == 'phase1' and on_time: + attestation = convert_to_valid_on_time_attestation(spec, state, attestation, signed) + return attestation @@ -112,7 +158,6 @@ def sign_aggregate_attestation(spec, state, attestation_data, participants: List privkey ) ) - # TODO: we should try signing custody bits if spec.fork == 'phase1' return bls.Aggregate(signatures) @@ -130,7 +175,47 @@ def sign_indexed_attestation(spec, state, indexed_attestation): indexed_attestation.attestation.signature = sign_aggregate_attestation(spec, state, data, participants) +def sign_on_time_attestation(spec, state, attestation): + if not any(attestation.custody_bits_blocks): + sign_attestation(spec, state, attestation) + return + + committee = spec.get_beacon_committee(state, attestation.data.slot, attestation.data.index) + signatures = [] + for block_index, custody_bits in enumerate(attestation.custody_bits_blocks): + for participant, abit, cbit in zip(committee, attestation.aggregation_bits, custody_bits): + if not abit: + continue + signatures.append(get_attestation_custody_signature( + spec, + state, + attestation.data, + block_index, + cbit, + privkeys[participant] + )) + + attestation.signature = bls.Aggregate(signatures) + + +def get_attestation_custody_signature(spec, state, attestation_data, block_index, bit, privkey): + domain = spec.get_domain(state, spec.DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch) + signing_root = spec.compute_signing_root( + spec.AttestationCustodyBitWrapper( + attestation_data.hash_tree_root(), + block_index, + bit, + ), + domain, + ) + return bls.Sign(privkey, signing_root) + + def sign_attestation(spec, state, attestation): + if spec.fork == 'phase1' and any(attestation.custody_bits_blocks): + sign_on_time_attestation(spec, state,attestation) + return + participants = spec.get_attesting_indices( state, attestation.data, @@ -163,3 +248,35 @@ def add_attestations_to_state(spec, state, attestations, slot): spec.process_slots(state, slot) for attestation in attestations: spec.process_attestation(state, attestation) + + +def next_epoch_with_attestations(spec, + state, + fill_cur_epoch, + fill_prev_epoch): + assert state.slot % spec.SLOTS_PER_EPOCH == 0 + + post_state = state.copy() + signed_blocks = [] + for _ in range(spec.SLOTS_PER_EPOCH): + 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 + committees_per_slot = spec.get_committee_count_at_slot(state, slot_to_attest) + if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(post_state)): + for index in range(committees_per_slot): + cur_attestation = get_valid_attestation(spec, post_state, slot_to_attest, index=index, signed=True) + block.body.attestations.append(cur_attestation) + + if fill_prev_epoch: + slot_to_attest = post_state.slot - spec.SLOTS_PER_EPOCH + 1 + committees_per_slot = spec.get_committee_count_at_slot(state, slot_to_attest) + for index in range(committees_per_slot): + prev_attestation = get_valid_attestation( + spec, post_state, slot_to_attest, index=index, signed=True, on_time=False) + block.body.attestations.append(prev_attestation) + + signed_block = state_transition_and_sign_block(spec, post_state, block) + signed_blocks.append(signed_block) + + return state, signed_blocks, post_state diff --git a/tests/core/pyspec/eth2spec/test/helpers/state.py b/tests/core/pyspec/eth2spec/test/helpers/state.py index 5816785f7..9b5d132f9 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/state.py +++ b/tests/core/pyspec/eth2spec/test/helpers/state.py @@ -1,5 +1,4 @@ from eth2spec.test.context import expect_assertion_error -from eth2spec.test.helpers.attestations import get_valid_attestation from eth2spec.test.helpers.block import sign_block, build_empty_block_for_next_slot, transition_unsigned_block @@ -58,34 +57,3 @@ 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) - - -def next_epoch_with_attestations(spec, - state, - fill_cur_epoch, - fill_prev_epoch): - assert state.slot % spec.SLOTS_PER_EPOCH == 0 - - post_state = state.copy() - signed_blocks = [] - for _ in range(spec.SLOTS_PER_EPOCH): - 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 - committees_per_slot = spec.get_committee_count_at_slot(state, slot_to_attest) - if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(post_state)): - for index in range(committees_per_slot): - cur_attestation = get_valid_attestation(spec, post_state, slot_to_attest, index=index, signed=True) - block.body.attestations.append(cur_attestation) - - if fill_prev_epoch: - slot_to_attest = post_state.slot - spec.SLOTS_PER_EPOCH + 1 - committees_per_slot = spec.get_committee_count_at_slot(state, slot_to_attest) - for index in range(committees_per_slot): - prev_attestation = get_valid_attestation(spec, post_state, slot_to_attest, index=index, signed=True) - block.body.attestations.append(prev_attestation) - - signed_block = state_transition_and_sign_block(spec, post_state, block) - signed_blocks.append(signed_block) - - return state, signed_blocks, post_state diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py b/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py index 236fceef1..525f01fcc 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py @@ -13,7 +13,10 @@ from eth2spec.test.helpers.attestations import ( sign_attestation, ) from eth2spec.test.helpers.state import ( - next_epoch, next_slots + next_epoch, + next_slots, + next_slot, + next_epoch, ) from eth2spec.test.helpers.block import apply_empty_block from eth2spec.utils.ssz.ssz_typing import Bitlist @@ -166,7 +169,7 @@ def test_old_target_epoch(spec, state): attestation = get_valid_attestation(spec, state, signed=True) - state.slot = spec.SLOTS_PER_EPOCH * 2 # target epoch will be too old to handle + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 2) # target epoch will be too old to handle yield from run_attestation_processing(spec, state, attestation, False) @@ -222,7 +225,8 @@ def test_source_root_is_target_root(spec, state): @with_all_phases @spec_state_test def test_invalid_current_source_root(spec, state): - state.slot = spec.SLOTS_PER_EPOCH * 5 + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 5) + state.finalized_checkpoint.epoch = 2 state.previous_justified_checkpoint = spec.Checkpoint(epoch=3, root=b'\x01' * 32) @@ -259,6 +263,7 @@ def test_bad_source_root(spec, state): @with_all_phases @spec_state_test def test_empty_aggregation_bits(spec, state): + next_slot(spec, state) attestation = get_valid_attestation(spec, state) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_attestation.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_attestation.py index 430ad014b..288bb8e00 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_attestation.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_attestation.py @@ -6,9 +6,7 @@ from eth2spec.test.context import ( from eth2spec.test.helpers.state import next_slot, transition_to from eth2spec.test.helpers.attestations import ( run_attestation_processing, - # get_valid_attestation as get_valid_late_attestation, -) -from eth2spec.test.helpers.phase1.attestations import ( + get_valid_late_attestation, get_valid_on_time_attestation, ) @@ -29,9 +27,6 @@ def test_on_time_success(spec, state): @spec_state_test @always_bls def test_on_time_empty_custody_bits_blocks(spec, state): - # Causing this test to pass causes many phase0 tests to fail - pass - """ next_slot(spec, state) attestation = get_valid_late_attestation(spec, state, signed=True) @@ -40,7 +35,6 @@ def test_on_time_empty_custody_bits_blocks(spec, state): transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) yield from run_attestation_processing(spec, state, attestation, False) - """ @with_all_phases_except(['phase0']) diff --git a/tests/core/pyspec/eth2spec/test/test_finality.py b/tests/core/pyspec/eth2spec/test/test_finality.py index 8ae50d436..d793b6656 100644 --- a/tests/core/pyspec/eth2spec/test/test_finality.py +++ b/tests/core/pyspec/eth2spec/test/test_finality.py @@ -1,5 +1,6 @@ from eth2spec.test.context import spec_state_test, never_bls, with_all_phases -from eth2spec.test.helpers.state import next_epoch, next_epoch_with_attestations +from eth2spec.test.helpers.state import next_epoch +from eth2spec.test.helpers.attestations import next_epoch_with_attestations from eth2spec.test.helpers.block import apply_empty_block