diff --git a/scripts/build_spec.py b/scripts/build_spec.py index b1a8b3485..f3a76817f 100644 --- a/scripts/build_spec.py +++ b/scripts/build_spec.py @@ -112,6 +112,7 @@ def objects_to_spec(functions: Dict[str, str], constants: Dict[str, str], ssz_objects: Dict[str, str], imports: Dict[str, str], + version: str, ) -> str: """ Given all the objects that constitute a spec, combine them into a single pyfile. @@ -137,6 +138,7 @@ def objects_to_spec(functions: Dict[str, str], ssz_objects_instantiation_spec = '\n\n'.join(ssz_objects.values()) spec = ( imports + + '\n\n' + f"version = \'{version}\'\n" + '\n\n' + new_type_definitions + '\n' + SUNDRY_CONSTANTS_FUNCTIONS + '\n\n' + constants_spec @@ -229,7 +231,7 @@ def build_phase0_spec(phase0_sourcefile: str, fork_choice_sourcefile: str, for value in [fork_choice_spec, v_guide]: spec_objects = combine_spec_objects(spec_objects, value) dependency_order_spec(spec_objects) - spec = objects_to_spec(*spec_objects, PHASE0_IMPORTS) + spec = objects_to_spec(*spec_objects, PHASE0_IMPORTS, 'phase0') if outfile is not None: with open(outfile, 'w') as out: out.write(spec) @@ -258,7 +260,7 @@ def build_phase1_spec(phase0_beacon_sourcefile: str, for value in all_spescs[1:]: spec_objects = combine_spec_objects(spec_objects, value) dependency_order_spec(spec_objects) - spec = objects_to_spec(*spec_objects, PHASE1_IMPORTS) + spec = objects_to_spec(*spec_objects, PHASE1_IMPORTS, 'phase1') if outfile is not None: with open(outfile, 'w') as out: out.write(spec) diff --git a/specs/core/1_beacon-chain.md b/specs/core/1_beacon-chain.md index 3e88d01da..b0c0b5fbf 100644 --- a/specs/core/1_beacon-chain.md +++ b/specs/core/1_beacon-chain.md @@ -93,6 +93,24 @@ class PendingAttestation(Container): crosslink_success: boolean ``` +### `IndexedAttestation` + +```python +class IndexedAttestation(Container): + committee: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] + attestation: Attestation +``` + +#### Extended `AttesterSlashing` + +Note that the `attestation_1` and `attestation_2` have a new `IndexedAttestation` definition. + +```python +class AttesterSlashing(Container): + attestation_1: IndexedAttestation + attestation_2: IndexedAttestation +``` + ### Extended `Validator` ```python @@ -261,14 +279,6 @@ class ShardTransition(Container): proposer_signature_aggregate: BLSSignature ``` -### `AttestationAndCommittee` - -```python -class AttestationAndCommittee(Container): - committee: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] - attestation: Attestation -``` - ### `CompactCommittee` ```python @@ -390,9 +400,12 @@ def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Seque #### `get_indexed_attestation` ```python -def get_indexed_attestation(beacon_state: BeaconState, attestation: Attestation) -> AttestationAndCommittee: +def get_indexed_attestation(beacon_state: BeaconState, attestation: Attestation) -> IndexedAttestation: committee = get_beacon_committee(beacon_state, attestation.data.slot, attestation.data.index) - return AttestationAndCommittee(committee, attestation) + return IndexedAttestation( + committee=committee, + attestation=attestation, + ) ``` #### `get_updated_gasprice` @@ -446,7 +459,7 @@ def get_offset_slots(state: BeaconState, start_slot: Slot) -> Sequence[Slot]: Note that this replaces the Phase 0 `is_valid_indexed_attestation`. ```python -def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: AttestationAndCommittee) -> bool: +def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool: """ Check if ``indexed_attestation`` has valid indices and signature. """ @@ -467,7 +480,7 @@ def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: Attest )) else: assert not cbit - + return bls_verify_multiple( pubkeys=all_pubkeys, message_hashes=all_message_hashes, @@ -716,6 +729,47 @@ def process_attestations(state: BeaconState, block_body: BeaconBlockBody, attest state.previous_epoch_attestations.append(pending_attestation) ``` +##### New Attester slashing processing + +```python +def get_indices_from_committee( + committee: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE], + bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE]: + assert len(bits) == len(committee) + return List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE]( + [validator_index for i, validator_index in enumerate(committee) if bits[i]] + ) +``` + +```python +def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSlashing) -> None: + indexed_attestation_1 = attester_slashing.attestation_1 + indexed_attestation_2 = attester_slashing.attestation_2 + assert is_slashable_attestation_data( + indexed_attestation_1.attestation.data, + indexed_attestation_2.attestation.data, + ) + assert is_valid_indexed_attestation(state, indexed_attestation_1) + assert is_valid_indexed_attestation(state, indexed_attestation_2) + + indices_1 = get_indices_from_committee( + indexed_attestation_1.committee, + indexed_attestation_1.attestation.aggregation_bits, + ) + indices_2 = get_indices_from_committee( + indexed_attestation_2.committee, + indexed_attestation_2.attestation.aggregation_bits, + ) + + slashed_any = False + indices = set(indices_1).intersection(indices_2) + for index in sorted(indices): + if is_slashable_validator(state.validators[index], get_current_epoch(state)): + slash_validator(state, index) + slashed_any = True + assert slashed_any +``` + #### Shard transition false positives ```python diff --git a/test_libs/pyspec/eth2spec/test/helpers/attestations.py b/test_libs/pyspec/eth2spec/test/helpers/attestations.py index 3ed54888f..0c64a0316 100644 --- a/test_libs/pyspec/eth2spec/test/helpers/attestations.py +++ b/test_libs/pyspec/eth2spec/test/helpers/attestations.py @@ -82,8 +82,18 @@ def sign_aggregate_attestation(spec, state, attestation_data, participants: List def sign_indexed_attestation(spec, state, indexed_attestation): - participants = indexed_attestation.attesting_indices - indexed_attestation.signature = sign_aggregate_attestation(spec, state, indexed_attestation.data, participants) + if spec.version == 'phase0': + participants = indexed_attestation.attesting_indices + data = indexed_attestation.data + indexed_attestation.signature = sign_aggregate_attestation(spec, state, data, participants) + else: + participants = spec.get_indices_from_committee( + indexed_attestation.committee, + indexed_attestation.attestation.aggregation_bits, + ) + data = indexed_attestation.attestation.data + indexed_attestation.attestation.signature = sign_aggregate_attestation(spec, state, data, participants) + def sign_attestation(spec, state, attestation): diff --git a/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py b/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py index 85e807ec0..9a227625a 100644 --- a/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py +++ b/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py @@ -169,7 +169,11 @@ def test_same_data(spec, state): def test_no_double_or_surround(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True) - attester_slashing.attestation_1.data.target.epoch += 1 + if spec.version == 'phase0': + attester_slashing.attestation_1.data.target.epoch += 1 + else: + attester_slashing.attestation_1.attestation.data.target.epoch += 1 + sign_indexed_attestation(spec, state, attester_slashing.attestation_1) yield from run_attester_slashing_processing(spec, state, attester_slashing, False)