From ab2ee0e2c2898ccb8398d555e64a8a6d34ebbeec Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 24 Apr 2020 17:06:27 +0100 Subject: [PATCH] Restoring chunk challenges and testing --- Makefile | 2 +- configs/minimal.yaml | 4 +- setup.py | 2 +- specs/phase1/beacon-chain.md | 95 +++++ specs/phase1/custody-game.md | 169 ++++++--- .../eth2spec/test/helpers/attestations.py | 15 +- .../pyspec/eth2spec/test/helpers/custody.py | 72 ++-- .../test/helpers/phase1/attestations.py | 6 +- .../test_process_chunk_challenge.py | 238 ++++++++++++ .../test_process_custody_slashing.py | 349 ++++++++++++++++++ .../test_process_final_custody_updates.py | 319 ++++++++++++++++ .../test_process_reveal_deadlines.py | 46 +++ 12 files changed, 1216 insertions(+), 101 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py create mode 100644 tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py create mode 100644 tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_final_custody_updates.py create mode 100644 tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_reveal_deadlines.py diff --git a/Makefile b/Makefile index e8f3d21bc..8cd7daf58 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,7 @@ pyspec: # installs the packages to run pyspec tests install_test: - python3 -m venv venv; . venv/bin/activate; pip3 install .[test] .[lint] + python3.8 -m venv venv; . venv/bin/activate; pip3 install .[lint]; pip3 install -e .[test] test: pyspec . venv/bin/activate; cd $(PY_SPEC_DIR); \ diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 9daf428b4..256c2b3fa 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -205,9 +205,9 @@ RANDAO_PENALTY_EPOCHS: 2 # [customized] quicker for testing EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096 # 2**11 (= 2,048) epochs -EPOCHS_PER_CUSTODY_PERIOD: 2048 +EPOCHS_PER_CUSTODY_PERIOD: 8 # 2**11 (= 2,048) epochs -CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048 +CUSTODY_PERIOD_TO_RANDAO_PADDING: 8 # 2**7 (= 128) epochs MAX_REVEAL_LATENESS_DECREMENT: 128 diff --git a/setup.py b/setup.py index 911eb65b0..91c29a6cc 100644 --- a/setup.py +++ b/setup.py @@ -121,7 +121,7 @@ from lru import LRU from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.utils.ssz.ssz_typing import ( View, boolean, Container, List, Vector, uint64, uint8, bit, - ByteList, Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector, + ByteList, ByteVector, Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector, ) from eth2spec.utils import bls diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 596b3818f..18c5f347f 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -109,6 +109,99 @@ Configuration is not namespaced. Instead it is strictly an extension; | `DOMAIN_SHARD_PROPOSAL` | `DomainType('0x80000000')` | | | `DOMAIN_SHARD_COMMITTEE` | `DomainType('0x81000000')` | | | `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` | | +| `MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS` | `2**20` (= 1,048,576) | +| `BYTES_PER_CUSTODY_CHUNK` | `2**12` | bytes | +| `MAX_CUSTODY_RESPONSE_DEPTH` | `ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK) | - | - | + + + +## New containers + +### `CustodyChunkChallenge` + +```python +class CustodyChunkChallenge(Container): + responder_index: ValidatorIndex + shard_transition: ShardTransition + attestation: Attestation + data_index: uint64 + chunk_index: uint64 +``` + +### `CustodyChunkChallengeRecord` + +```python +class CustodyChunkChallengeRecord(Container): + challenge_index: uint64 + challenger_index: ValidatorIndex + responder_index: ValidatorIndex + inclusion_epoch: Epoch + data_root: Root + depth: uint64 + chunk_index: uint64 +``` + +#### `CustodyChunkResponse` + +```python +class CustodyChunkResponse(Container): + challenge_index: uint64 + chunk_index: uint64 + chunk: ByteVector[BYTES_PER_CUSTODY_CHUNK] + branch: List[Root, MAX_CUSTODY_RESPONSE_DEPTH] +``` + +#### `CustodySlashing` + +```python +class CustodySlashing(Container): + # Attestation.custody_bits_blocks[data_index][committee.index(malefactor_index)] is the target custody bit to check. + # (Attestation.data.shard_transition_root as ShardTransition).shard_data_roots[data_index] is the root of the data. + data_index: uint64 + malefactor_index: ValidatorIndex + malefactor_secret: BLSSignature + whistleblower_index: ValidatorIndex + shard_transition: ShardTransition + attestation: Attestation + data: ByteList[MAX_SHARD_BLOCK_SIZE] +``` + +#### `SignedCustodySlashing` + +```python +class SignedCustodySlashing(Container): + message: CustodySlashing + signature: BLSSignature +``` + + +#### `CustodyKeyReveal` + +```python +class CustodyKeyReveal(Container): + # Index of the validator whose key is being revealed + revealer_index: ValidatorIndex + # Reveal (masked signature) + reveal: BLSSignature +``` + +#### `EarlyDerivedSecretReveal` + +Represents an early (punishable) reveal of one of the derived secrets, where derived secrets are RANDAO reveals and custody reveals (both are part of the same domain). + +```python +class EarlyDerivedSecretReveal(Container): + # Index of the validator whose key is being revealed + revealed_index: ValidatorIndex + # RANDAO epoch of the key that is being revealed + epoch: Epoch + # Reveal (masked signature) + reveal: BLSSignature + # Index of the validator who revealed (whistleblower) + masker_index: ValidatorIndex + # Mask used to hide the actual reveal signature (prevent reveal from being stolen) + mask: Bytes32 +``` ## Updated containers @@ -285,6 +378,8 @@ class BeaconState(Container): # at RANDAO reveal period % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS exposed_derived_secrets: Vector[List[ValidatorIndex, MAX_EARLY_DERIVED_SECRET_REVEALS * SLOTS_PER_EPOCH], EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS] + custody_chunk_challenge_records: List[CustodyChunkChallengeRecord, MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS] + custody_chunk_challenge_index: uint64 ``` ## New containers diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 6315955f5..f069c4cd2 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -65,6 +65,9 @@ This document details the beacon chain additions and changes in Phase 1 of Ether | `EPOCHS_PER_CUSTODY_PERIOD` | `2**11` (= 2,048) | epochs | ~9 days | | `CUSTODY_PERIOD_TO_RANDAO_PADDING` | `2**11` (= 2,048) | epochs | ~9 days | | `MAX_REVEAL_LATENESS_DECREMENT` | `2**7` (= 128) | epochs | ~14 hours | +| `CHUNK_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days | +| `MAX_CHUNK_CHALLENGE_DELAY` | `2**11` (= 16,384) | epochs | ~9 days | +| `CUSTODY_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days | ### Max operations per block @@ -72,6 +75,7 @@ This document details the beacon chain additions and changes in Phase 1 of Ether | - | - | | `MAX_CUSTODY_KEY_REVEALS` | `2**8` (= 256) | | `MAX_EARLY_DERIVED_SECRET_REVEALS` | `1` | +| `MAX_CUSTODY_CHUNK_CHALLENGES` | `2**2` (= 4) | | `MAX_CUSTODY_SLASHINGS` | `1` | ### Reward and penalty quotients @@ -93,61 +97,20 @@ The following types are defined, mapping into `DomainType` (little endian): ### New Beacon Chain operations -#### `CustodySlashing` - -```python -class CustodySlashing(Container): - # Attestation.custody_bits_blocks[data_index][committee.index(malefactor_index)] is the target custody bit to check. - # (Attestation.data.shard_transition_root as ShardTransition).shard_data_roots[data_index] is the root of the data. - data_index: uint64 - malefactor_index: ValidatorIndex - malefactor_secret: BLSSignature - whistleblower_index: ValidatorIndex - shard_transition: ShardTransition - attestation: Attestation - data: ByteList[MAX_SHARD_BLOCK_SIZE] -``` - -#### `SignedCustodySlashing` - -```python -class SignedCustodySlashing(Container): - message: CustodySlashing - signature: BLSSignature -``` - - -#### `CustodyKeyReveal` - -```python -class CustodyKeyReveal(Container): - # Index of the validator whose key is being revealed - revealer_index: ValidatorIndex - # Reveal (masked signature) - reveal: BLSSignature -``` - -#### `EarlyDerivedSecretReveal` - -Represents an early (punishable) reveal of one of the derived secrets, where derived secrets are RANDAO reveals and custody reveals (both are part of the same domain). - -```python -class EarlyDerivedSecretReveal(Container): - # Index of the validator whose key is being revealed - revealed_index: ValidatorIndex - # RANDAO epoch of the key that is being revealed - epoch: Epoch - # Reveal (masked signature) - reveal: BLSSignature - # Index of the validator who revealed (whistleblower) - masker_index: ValidatorIndex - # Mask used to hide the actual reveal signature (prevent reveal from being stolen) - mask: Bytes32 -``` - - ## Helpers +### `replace_empty_or_append` + +```python +def replace_empty_or_append(list: List, new_element: Any) -> int: + for i in range(len(list)): + if list[i] == empty(typeof(new_element)): + list[i] = new_element + return i + list.append(new_element) + return len(list) - 1 +``` + ### `legendre_bit` Returns the Legendre symbol `(a/q)` normalizes as a bit (i.e. `((a/q) + 1) // 2`). In a production implementation, a well-optimized library (e.g. GMP) should be used for this. @@ -248,6 +211,84 @@ def process_custody_game_operations(state: BeaconState, body: BeaconBlockBody) - for_ops(body.custody_slashings, process_custody_slashing) ``` +#### Chunk challenges + +Verify that `len(block.body.custody_chunk_challenges) <= MAX_CUSTODY_CHUNK_CHALLENGES`. + +For each `challenge` in `block.body.custody_chunk_challenges`, run the following function: + +```python +def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge) -> None: + # Verify the attestation + assert is_valid_indexed_attestation(state, get_indexed_attestation(state, challenge.attestation)) + # Verify it is not too late to challenge + assert (challenge.attestation.data.target.epoch + MAX_CHUNK_CHALLENGE_DELAY + >= get_current_epoch(state)) + responder = state.validators[challenge.responder_index] + assert (responder.exit_epoch == FAR_FUTURE_EPOCH + or responder.exit_epoch + MAX_CHUNK_CHALLENGE_DELAY >= get_current_epoch(state)) + # Verify responder is slashable + assert is_slashable_validator(responder, get_current_epoch(state)) + # Verify the responder participated in the attestation + attesters = get_attesting_indices(state, challenge.attestation.data, challenge.attestation.aggregation_bits) + assert challenge.responder_index in attesters + # Verify shard transition is correctly given + assert hash_tree_root(challenge.shard_transition) == challenge.attestation.data.shard_transition_root + data_root = challenge.shard_transition.shard_data_roots[challenge.data_index] + # Verify the challenge is not a duplicate + for record in state.custody_chunk_challenge_records: + assert ( + record.data_root != challenge.attestation.data.crosslink.data_root or + record.chunk_index != challenge.chunk_index + ) + # Verify depth + transition_chunks = (challenge.shard_transition.shard_block_lengths[challenge.data_index] + BYTES_PER_CUSTODY_CHUNK - 1) // BYTES_PER_CUSTODY_CHUNK + depth = ceillog2(transition_chunks) + assert challenge.chunk_index < transition_chunks + # Add new chunk challenge record + new_record = CustodyChunkChallengeRecord( + challenge_index=state.custody_chunk_challenge_index, + challenger_index=get_beacon_proposer_index(state), + responder_index=challenge.responder_index, + inclusion_epoch=get_current_epoch(state), + data_root=challenge.shard_transition.shard_data_roots[challenge.data_index], + depth=depth, + chunk_index=challenge.chunk_index, + ) + replace_empty_or_append(state.custody_chunk_challenge_records, new_record) + + state.custody_chunk_challenge_index += 1 + # Postpone responder withdrawability + responder.withdrawable_epoch = FAR_FUTURE_EPOCH +``` + +#### Custody chunk response + +```python +def process_chunk_challenge_response(state: BeaconState, + response: CustodyChunkResponse) -> None: + + challenge = next((record for record in state.custody_chunk_challenge_records if record.challenge_index == response.challenge_index), None) + assert(challenge is not None) + + # Verify chunk index + assert response.chunk_index == challenge.chunk_index + # Verify the chunk matches the crosslink data root + assert is_valid_merkle_branch( + leaf=hash_tree_root(response.chunk), + branch=response.branch, + depth=challenge.depth, + index=response.chunk_index, + root=challenge.data_root, + ) + # Clear the challenge + records = state.custody_chunk_challenge_records + records[records.index(challenge)] = CustodyChunkChallengeRecord() + # Reward the proposer + proposer_index = get_beacon_proposer_index(state) + increase_balance(state, proposer_index, get_base_reward(state, proposer_index) // MINOR_REWARD_QUOTIENT) +``` + #### Custody key reveals ```python @@ -431,14 +472,22 @@ Run `process_reveal_deadlines(state)` after `process_registry_updates(state)`: def process_reveal_deadlines(state: BeaconState) -> None: epoch = get_current_epoch(state) for index, validator in enumerate(state.validators): - if get_custody_period_for_validator(ValidatorIndex(index), epoch) > validator.next_custody_secret_to_reveal: - # ------------------ WARNING ----------------------- # - # UNSAFE REMOVAL OF SLASHING TO PRIORITIZE PHASE 0 CI # - # Must find generic way to handle key reveals in tests # - # ---------------------------------------------------- # + if get_custody_period_for_validator(ValidatorIndex(index), epoch) > validator.next_custody_secret_to_reveal + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD): + slash_validator(state, ValidatorIndex(index)) +``` - # slash_validator(state, ValidatorIndex(index)) - pass +Run `process_challenge_deadlines(state)` immediately after `process_reveal_deadlines(state)`: + +```python +# begin insert @process_challenge_deadlines + process_challenge_deadlines(state) +# end insert @process_challenge_deadlines +def process_challenge_deadlines(state: BeaconState) -> None: + for custody_chunk_challenge in state.custody_chunk_challenge_records: + if get_current_epoch(state) > custody_chunk_challenge.inclusion_epoch + CUSTODY_RESPONSE_DEADLINE: + slash_validator(state, custody_chunk_challenge.responder_index, custody_chunk_challenge.challenger_index) + records = state.custody_chunk_challenge_records + records[records.index(custody_chunk_challenge)] = CustodyChunkChallengeRecord() ``` ### Final updates diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 8215f5c5b..325fee260 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -43,7 +43,7 @@ def run_attestation_processing(spec, state, attestation, valid=True): yield 'post', state -def build_attestation_data(spec, state, slot, index): +def build_attestation_data(spec, state, slot, index, shard_transition_root=None): assert state.slot >= slot if slot == state.slot: @@ -72,6 +72,7 @@ def build_attestation_data(spec, state, slot, index): beacon_block_root=block_root, source=spec.Checkpoint(epoch=source_epoch, root=source_root), target=spec.Checkpoint(epoch=spec.compute_epoch_at_slot(slot), root=epoch_boundary_root), + shard_transition_root=shard_transition_root, ) @@ -89,7 +90,7 @@ def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False) return attestation -def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=False): +def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=False, shard_transition_root=None): ''' Construct on-time attestation for next slot ''' @@ -98,10 +99,10 @@ def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=Fal if index is None: index = 0 - return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=True) + return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=True, shard_transition_root=shard_transition_root) -def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False): +def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False, shard_transition_root=None): ''' Construct on-time attestation for next slot ''' @@ -110,16 +111,16 @@ def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False) if index is None: index = 0 - return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=False) + return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=False, shard_transition_root=shard_transition_root) -def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signed=False, on_time=True): +def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signed=False, on_time=True, shard_transition_root=None): if slot is None: slot = state.slot if index is None: index = 0 - attestation_data = build_attestation_data(spec, state, slot, index) + attestation_data = build_attestation_data(spec, state, slot, index, shard_transition_root=shard_transition_root) beacon_committee = spec.get_beacon_committee( state, diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index 7c51675cd..45c29be9a 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -1,8 +1,8 @@ from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls -from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, Bitvector +from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, Bitvector, ByteList from eth2spec.utils.ssz.ssz_impl import hash_tree_root -from eth2spec.utils.merkle_minimal import get_merkle_tree, get_merkle_proof +from eth2spec.utils.merkle_minimal import get_merkle_root, get_merkle_tree, get_merkle_proof from remerkleable.core import pack_bits_to_chunks from remerkleable.tree import subtree_fill_to_contents, get_depth @@ -61,7 +61,7 @@ def bitlist_from_int(max_len, num_bits, n): return Bitlist[max_len](*[(n >> i) & 0b1 for i in range(num_bits)]) -def get_valid_bit_challenge(spec, state, attestation, invalid_custody_bit=False): +def get_valid_custody_slashing(spec, state, attestation, invalid_custody_bit=False): beacon_committee = spec.get_beacon_committee( state, attestation.data.slot, @@ -96,21 +96,39 @@ def get_valid_bit_challenge(spec, state, attestation, invalid_custody_bit=False) ) +def get_valid_chunk_challenge(spec, state, attestation, shard_transition): + shard = spec.compute_shard_from_committee_index(state, attestation.data.index, attestation.data.slot) + crosslink_committee = spec.get_beacon_committee( + state, + attestation.data.slot, + attestation.data.index + ) + responder_index = crosslink_committee[0] + data_index = len(shard_transition.shard_block_lengths) - 1 + + chunk_count = (shard_transition.shard_block_lengths[data_index] + spec.BYTES_PER_CUSTODY_CHUNK - 1) // spec.BYTES_PER_CUSTODY_CHUNK + + return spec.CustodyChunkChallenge( + responder_index=responder_index, + attestation=attestation, + chunk_index=chunk_count - 1, + data_index=data_index, + shard_transition=shard_transition, + ) + + def custody_chunkify(spec, x): chunks = [bytes(x[i:i + spec.BYTES_PER_CUSTODY_CHUNK]) for i in range(0, len(x), spec.BYTES_PER_CUSTODY_CHUNK)] chunks[-1] = chunks[-1].ljust(spec.BYTES_PER_CUSTODY_CHUNK, b"\0") return chunks -def get_valid_custody_response(spec, state, bit_challenge, custody_data, challenge_index, invalid_chunk_bit=False): +def get_valid_custody_chunk_response(spec, state, chunk_challenge, block_length, challenge_index, + invalid_chunk_data=False): + custody_data = get_custody_test_vector(block_length) chunks = custody_chunkify(spec, custody_data) - chunk_index = len(chunks) - 1 - chunk_bit = spec.get_custody_chunk_bit(bit_challenge.responder_key, chunks[chunk_index]) - - while chunk_bit == bit_challenge.chunk_bits[chunk_index] ^ invalid_chunk_bit: - chunk_index -= 1 - chunk_bit = spec.get_custody_chunk_bit(bit_challenge.responder_key, chunks[chunk_index]) + chunk_index = chunk_challenge.chunk_index chunks_hash_tree_roots = [hash_tree_root(ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](chunk)) for chunk in chunks] chunks_hash_tree_roots += [ @@ -120,31 +138,29 @@ def get_valid_custody_response(spec, state, bit_challenge, custody_data, challen data_branch = get_merkle_proof(data_tree, chunk_index) - bitlist_chunk_index = chunk_index // BYTES_PER_CHUNK - print(bitlist_chunk_index) - bitlist_chunk_nodes = pack_bits_to_chunks(bit_challenge.chunk_bits) - bitlist_tree = subtree_fill_to_contents(bitlist_chunk_nodes, get_depth(spec.MAX_CUSTODY_CHUNKS)) - print(bitlist_tree) - bitlist_chunk_branch = None # TODO; extract proof from merkle tree - - bitlist_chunk_index = chunk_index // 256 - - chunk_bits_leaf = Bitvector[256](bit_challenge.chunk_bits[bitlist_chunk_index * 256: - (bitlist_chunk_index + 1) * 256]) - - return spec.CustodyResponse( + return spec.CustodyChunkResponse( challenge_index=challenge_index, chunk_index=chunk_index, chunk=ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](chunks[chunk_index]), - data_branch=data_branch, - chunk_bits_branch=bitlist_chunk_branch, - chunk_bits_leaf=chunk_bits_leaf, + branch=data_branch, ) def get_custody_test_vector(bytelength): - ints = bytelength // 4 - return b"".join(i.to_bytes(4, "little") for i in range(ints)) + ints = bytelength // 4 + 1 + return (b"".join(i.to_bytes(4, "little") for i in range(ints)))[:bytelength] + + +def get_shard_transition(spec, start_slot, block_lengths): + b = [hash_tree_root(ByteVector[x](get_custody_test_vector(x))) for x in block_lengths] + shard_transition = spec.ShardTransition( + start_slot=start_slot, + shard_block_lengths=block_lengths, + shard_data_roots=b, + shard_states=[spec.Root() for x in block_lengths], + proposer_signature_aggregate=spec.BLSSignature(), + ) + return shard_transition def get_custody_merkle_root(data): diff --git a/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py index 0e16e1fac..061553ef9 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py @@ -5,21 +5,23 @@ from eth2spec.test.helpers.keys import privkeys import eth2spec.test.helpers.attestations as phase0_attestations -def get_valid_on_time_attestation(spec, state, index=None, signed=False): +def get_valid_on_time_attestation(spec, state, index=None, signed=False, shard_transition_root=None): ''' Construct on-time attestation for next slot ''' if index is None: index = 0 - attestation = phase0_attestations.get_valid_attestation(spec, state, state.slot, index, False) + attestation = phase0_attestations.get_valid_attestation(spec, state, state.slot, index, False, shard_transition_root=shard_transition_root) shard = spec.get_shard(state, attestation) offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), state.slot + 1) + print(offset_slots) for _ in offset_slots: attestation.custody_bits_blocks.append( Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits]) ) + print(len(attestation.custody_bits_blocks)) if signed: sign_attestation(spec, state, attestation) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py new file mode 100644 index 000000000..09b25a1ad --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py @@ -0,0 +1,238 @@ +from eth2spec.test.helpers.custody import ( + get_valid_chunk_challenge, + get_valid_custody_chunk_response, + get_custody_test_vector, + get_custody_merkle_root, + get_shard_transition, +) +from eth2spec.test.helpers.attestations import ( + get_valid_on_time_attestation, +) +from eth2spec.test.helpers.state import transition_to +from eth2spec.utils.ssz.ssz_impl import hash_tree_root +from eth2spec.test.context import ( + with_all_phases_except, + spec_state_test, + expect_assertion_error, +) +from eth2spec.test.phase_0.block_processing.test_process_attestation import run_attestation_processing + + +def run_chunk_challenge_processing(spec, state, custody_chunk_challenge, valid=True): + """ + Run ``process_chunk_challenge``, yielding: + - pre-state ('pre') + - CustodyBitChallenge ('custody_chunk_challenge') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + yield 'pre', state + yield 'custody_chunk_challenge', custody_chunk_challenge + + if not valid: + expect_assertion_error(lambda: spec.custody_chunk_challenge(state, custody_chunk_challenge)) + yield 'post', None + return + + spec.process_chunk_challenge(state, custody_chunk_challenge) + + assert state.custody_chunk_challenge_records[state.custody_chunk_challenge_index - 1].responder_index == \ + custody_chunk_challenge.responder_index + assert state.custody_chunk_challenge_records[state.custody_chunk_challenge_index - 1].chunk_index == \ + custody_chunk_challenge.chunk_index + + yield 'post', state + + +def run_custody_chunk_response_processing(spec, state, custody_response, valid=True): + """ + Run ``process_chunk_challenge_response``, yielding: + - pre-state ('pre') + - CustodyResponse ('custody_response') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + yield 'pre', state + yield 'custody_response', custody_response + + if not valid: + expect_assertion_error(lambda: spec.process_custody_response(state, custody_response)) + yield 'post', None + return + + spec.process_chunk_challenge_response(state, custody_response) + + assert state.custody_chunk_challenge_records[custody_response.challenge_index] == spec.CustodyChunkChallengeRecord() + + yield 'post', state + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_challenge_appended(spec, state): + transition_to(spec, state, state.slot + 1) + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + + transition_chunks = (shard_transition.shard_block_lengths[data_index] + spec.BYTES_PER_CUSTODY_CHUNK - 1) // spec.BYTES_PER_CUSTODY_CHUNK + test_vector = get_custody_test_vector(shard_transition.shard_block_lengths[data_index]) + shard_root = get_custody_merkle_root(test_vector) + + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD) + + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) + + yield from run_chunk_challenge_processing(spec, state, challenge) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_multiple_epochs_custody(spec, state): + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 3) + + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) + + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) + + yield from run_chunk_challenge_processing(spec, state, challenge) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_many_epochs_custody(spec, state): + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 100) + + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) + + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) + + yield from run_chunk_challenge_processing(spec, state, challenge) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_off_chain_attestation(spec, state): + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) + + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) + + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) + + yield from run_chunk_challenge_processing(spec, state, challenge) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_custody_response(spec, state): + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) + + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) + + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) + + _, _, _ = run_chunk_challenge_processing(spec, state, challenge) + + chunk_challenge_index = state.custody_chunk_challenge_index - 1 + + custody_response = get_valid_custody_chunk_response(spec, state, challenge, 2**15 // 3, chunk_challenge_index) + + yield from run_custody_chunk_response_processing(spec, state, custody_response) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_custody_response_multiple_epochs(spec, state): + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 3) + + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) + + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) + + _, _, _ = run_chunk_challenge_processing(spec, state, challenge) + + chunk_challenge_index = state.custody_chunk_challenge_index - 1 + + custody_response = get_valid_custody_chunk_response(spec, state, challenge, 2**15 // 3, chunk_challenge_index) + + yield from run_custody_chunk_response_processing(spec, state, custody_response) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_custody_response_many_epochs(spec, state): + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 100) + + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) + + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) + + _, _, _ = run_chunk_challenge_processing(spec, state, challenge) + + chunk_challenge_index = state.custody_chunk_challenge_index - 1 + + custody_response = get_valid_custody_chunk_response(spec, state, challenge, 2**15 // 3, chunk_challenge_index) + + yield from run_custody_chunk_response_processing(spec, state, custody_response) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py new file mode 100644 index 000000000..358aa5c0d --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py @@ -0,0 +1,349 @@ +from eth2spec.test.helpers.custody import ( + get_valid_bit_challenge, + get_valid_custody_bit_response, + get_custody_test_vector, + get_custody_merkle_root, +) +from eth2spec.test.helpers.attestations import ( + get_valid_attestation, +) +from eth2spec.utils.ssz.ssz_impl import hash_tree_root +from eth2spec.test.helpers.state import next_epoch, get_balance +from eth2spec.test.helpers.block import apply_empty_block +from eth2spec.test.context import ( + with_all_phases_except, + spec_state_test, + expect_assertion_error, +) +from eth2spec.test.phase_0.block_processing.test_process_attestation import run_attestation_processing + + +def run_bit_challenge_processing(spec, state, custody_bit_challenge, valid=True): + """ + Run ``process_bit_challenge``, yielding: + - pre-state ('pre') + - CustodyBitChallenge ('custody_bit_challenge') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + yield 'pre', state + yield 'custody_bit_challenge', custody_bit_challenge + + if not valid: + expect_assertion_error(lambda: spec.process_bit_challenge(state, custody_bit_challenge)) + yield 'post', None + return + + spec.process_bit_challenge(state, custody_bit_challenge) + + assert state.custody_bit_challenge_records[state.custody_bit_challenge_index - 1].chunk_bits_merkle_root == \ + hash_tree_root(custody_bit_challenge.chunk_bits) + assert state.custody_bit_challenge_records[state.custody_bit_challenge_index - 1].challenger_index == \ + custody_bit_challenge.challenger_index + assert state.custody_bit_challenge_records[state.custody_bit_challenge_index - 1].responder_index == \ + custody_bit_challenge.responder_index + + yield 'post', state + + +def run_custody_bit_response_processing(spec, state, custody_response, valid=True): + """ + Run ``process_bit_challenge_response``, yielding: + - pre-state ('pre') + - CustodyResponse ('custody_response') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + yield 'pre', state + yield 'custody_response', custody_response + + if not valid: + expect_assertion_error(lambda: spec.process_custody_response(state, custody_response)) + yield 'post', None + return + + challenge = state.custody_bit_challenge_records[custody_response.challenge_index] + pre_slashed_balance = get_balance(state, challenge.challenger_index) + + spec.process_custody_response(state, custody_response) + + slashed_validator = state.validators[challenge.challenger_index] + + assert slashed_validator.slashed + assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH + assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + assert get_balance(state, challenge.challenger_index) < pre_slashed_balance + yield 'post', state + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_challenge_appended(spec, state): + state.slot = spec.SLOTS_PER_EPOCH + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + _, _, _ = run_attestation_processing(spec, state, attestation) + + state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD + + challenge = get_valid_bit_challenge(spec, state, attestation) + + yield from run_bit_challenge_processing(spec, state, challenge) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_multiple_epochs_custody(spec, state): + state.slot = spec.SLOTS_PER_EPOCH * 3 + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + _, _, _ = run_attestation_processing(spec, state, attestation) + + state.slot += spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1) + + challenge = get_valid_bit_challenge(spec, state, attestation) + + yield from run_bit_challenge_processing(spec, state, challenge) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_many_epochs_custody(spec, state): + state.slot = spec.SLOTS_PER_EPOCH * 100 + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + _, _, _ = run_attestation_processing(spec, state, attestation) + + state.slot += spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1) + + challenge = get_valid_bit_challenge(spec, state, attestation) + + yield from run_bit_challenge_processing(spec, state, challenge) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_off_chain_attestation(spec, state): + state.slot = spec.SLOTS_PER_EPOCH + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD + + challenge = get_valid_bit_challenge(spec, state, attestation) + + yield from run_bit_challenge_processing(spec, state, challenge) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_invalid_custody_bit_challenge(spec, state): + state.slot = spec.SLOTS_PER_EPOCH + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + _, _, _ = run_attestation_processing(spec, state, attestation) + + state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD + + challenge = get_valid_bit_challenge(spec, state, attestation, invalid_custody_bit=True) + + yield from run_bit_challenge_processing(spec, state, challenge, valid=False) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_max_reveal_lateness_1(spec, state): + next_epoch(spec, state) + apply_empty_block(spec, state) + + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + + next_epoch(spec, state) + apply_empty_block(spec, state) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + challenge = get_valid_bit_challenge(spec, state, attestation) + + responder_index = challenge.responder_index + target_epoch = attestation.data.target.epoch + + state.validators[responder_index].max_reveal_lateness = 3 + + latest_reveal_epoch = spec.get_randao_epoch_for_custody_period( + spec.get_custody_period_for_validator(state, responder_index, target_epoch), + responder_index + ) + 2 * spec.EPOCHS_PER_CUSTODY_PERIOD + state.validators[responder_index].max_reveal_lateness + + while spec.get_current_epoch(state) < latest_reveal_epoch - 2: + next_epoch(spec, state) + apply_empty_block(spec, state) + + yield from run_bit_challenge_processing(spec, state, challenge) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_max_reveal_lateness_2(spec, state): + next_epoch(spec, state) + apply_empty_block(spec, state) + + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + + next_epoch(spec, state) + apply_empty_block(spec, state) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + challenge = get_valid_bit_challenge(spec, state, attestation) + + responder_index = challenge.responder_index + + state.validators[responder_index].max_reveal_lateness = 3 + + for i in range(spec.get_randao_epoch_for_custody_period( + spec.get_custody_period_for_validator(state, responder_index), + responder_index + ) + 2 * spec.EPOCHS_PER_CUSTODY_PERIOD + state.validators[responder_index].max_reveal_lateness - 1): + next_epoch(spec, state) + apply_empty_block(spec, state) + + yield from run_bit_challenge_processing(spec, state, challenge, False) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_custody_response(spec, state): + state.slot = spec.SLOTS_PER_EPOCH + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + _, _, _ = run_attestation_processing(spec, state, attestation) + + state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD + + challenge = get_valid_bit_challenge(spec, state, attestation) + + _, _, _ = run_bit_challenge_processing(spec, state, challenge) + + bit_challenge_index = state.custody_bit_challenge_index - 1 + + custody_response = get_valid_custody_bit_response(spec, state, challenge, test_vector, bit_challenge_index) + + yield from run_custody_bit_response_processing(spec, state, custody_response) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_custody_response_multiple_epochs(spec, state): + state.slot = spec.SLOTS_PER_EPOCH * 3 + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + _, _, _ = run_attestation_processing(spec, state, attestation) + + state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD + + challenge = get_valid_bit_challenge(spec, state, attestation) + + _, _, _ = run_bit_challenge_processing(spec, state, challenge) + + bit_challenge_index = state.custody_bit_challenge_index - 1 + + custody_response = get_valid_custody_bit_response(spec, state, challenge, test_vector, bit_challenge_index) + + yield from run_custody_bit_response_processing(spec, state, custody_response) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_custody_response_many_epochs(spec, state): + state.slot = spec.SLOTS_PER_EPOCH * 100 + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + _, _, _ = run_attestation_processing(spec, state, attestation) + + state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD + + challenge = get_valid_bit_challenge(spec, state, attestation) + + _, _, _ = run_bit_challenge_processing(spec, state, challenge) + + bit_challenge_index = state.custody_bit_challenge_index - 1 + + custody_response = get_valid_custody_bit_response(spec, state, challenge, test_vector, bit_challenge_index) + + yield from run_custody_bit_response_processing(spec, state, custody_response) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_final_custody_updates.py b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_final_custody_updates.py new file mode 100644 index 000000000..d200ed2b3 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_final_custody_updates.py @@ -0,0 +1,319 @@ +from eth2spec.test.helpers.custody import ( + get_valid_bit_challenge, + get_valid_chunk_challenge, + get_valid_custody_bit_response, + get_valid_custody_chunk_response, + get_valid_custody_key_reveal, + get_custody_test_vector, + get_custody_merkle_root, +) +from eth2spec.test.helpers.attestations import ( + get_valid_attestation, +) +from eth2spec.test.helpers.state import next_epoch +from eth2spec.test.helpers.block import apply_empty_block +from eth2spec.test.context import ( + with_all_phases_except, + spec_state_test, +) +from eth2spec.test.phase_0.block_processing.test_process_attestation import run_attestation_processing +from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with + +from eth2spec.test.phase_1.block_processing.test_process_bit_challenge import ( + run_bit_challenge_processing, + run_custody_bit_response_processing, +) +from eth2spec.test.phase_1.block_processing.test_process_chunk_challenge import ( + run_chunk_challenge_processing, + run_custody_chunk_response_processing, +) +from eth2spec.test.phase_1.block_processing.test_process_custody_key_reveal import run_custody_key_reveal_processing + + +def run_process_final_custody_updates(spec, state): + yield from run_epoch_processing_with(spec, state, 'process_final_custody_updates') + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_validator_withdrawal_delay(spec, state): + spec.initiate_validator_exit(state, 0) + assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + yield from run_process_final_custody_updates(spec, state) + + assert state.validators[0].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_validator_withdrawal_reenable_after_custody_reveal(spec, state): + spec.initiate_validator_exit(state, 0) + assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + next_epoch(spec, state) + apply_empty_block(spec, state) + + assert state.validators[0].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + while spec.get_current_epoch(state) < state.validators[0].exit_epoch: + next_epoch(spec, state) + apply_empty_block(spec, state) + + while (state.validators[0].next_custody_secret_to_reveal + <= spec.get_custody_period_for_validator(state, 0, state.validators[0].exit_epoch - 1)): + custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=0) + _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) + + yield from run_process_final_custody_updates(spec, state) + + assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_validator_withdrawal_suspend_after_bit_challenge(spec, state): + state.slot = spec.SLOTS_PER_EPOCH + + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + next_epoch(spec, state) + apply_empty_block(spec, state) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + validator_index = spec.get_crosslink_committee( + state, + attestation.data.target.epoch, + attestation.data.crosslink.shard + )[0] + + spec.initiate_validator_exit(state, validator_index) + assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + next_epoch(spec, state) + apply_empty_block(spec, state) + + assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch: + next_epoch(spec, state) + apply_empty_block(spec, state) + + while (state.validators[validator_index].next_custody_secret_to_reveal + <= spec.get_custody_period_for_validator( + state, + validator_index, + state.validators[validator_index].exit_epoch - 1)): + custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index) + _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) + + next_epoch(spec, state) + apply_empty_block(spec, state) + + challenge = get_valid_bit_challenge(spec, state, attestation) + + _, _, _ = run_bit_challenge_processing(spec, state, challenge) + + yield from run_process_final_custody_updates(spec, state) + + assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_validator_withdrawal_suspend_after_chunk_challenge(spec, state): + state.slot = spec.SLOTS_PER_EPOCH + + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + next_epoch(spec, state) + apply_empty_block(spec, state) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + validator_index = spec.get_crosslink_committee( + state, + attestation.data.target.epoch, + attestation.data.crosslink.shard + )[0] + + spec.initiate_validator_exit(state, validator_index) + assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + next_epoch(spec, state) + apply_empty_block(spec, state) + + assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch: + next_epoch(spec, state) + apply_empty_block(spec, state) + + while (state.validators[validator_index].next_custody_secret_to_reveal + <= spec.get_custody_period_for_validator( + state, + validator_index, + state.validators[validator_index].exit_epoch - 1)): + custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index) + _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) + + next_epoch(spec, state) + apply_empty_block(spec, state) + + challenge = get_valid_chunk_challenge(spec, state, attestation) + + _, _, _ = run_chunk_challenge_processing(spec, state, challenge) + + yield from run_process_final_custody_updates(spec, state) + + assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_validator_withdrawal_resume_after_bit_challenge_response(spec, state): + state.slot = spec.SLOTS_PER_EPOCH + + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + next_epoch(spec, state) + apply_empty_block(spec, state) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + validator_index = spec.get_crosslink_committee( + state, + attestation.data.target.epoch, + attestation.data.crosslink.shard + )[0] + + spec.initiate_validator_exit(state, validator_index) + assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + next_epoch(spec, state) + apply_empty_block(spec, state) + + assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch: + next_epoch(spec, state) + apply_empty_block(spec, state) + + while (state.validators[validator_index].next_custody_secret_to_reveal + <= spec.get_custody_period_for_validator( + state, + validator_index, + state.validators[validator_index].exit_epoch - 1)): + custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index) + _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) + + next_epoch(spec, state) + apply_empty_block(spec, state) + + challenge = get_valid_bit_challenge(spec, state, attestation) + + _, _, _ = run_bit_challenge_processing(spec, state, challenge) + + next_epoch(spec, state) + apply_empty_block(spec, state) + + assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + bit_challenge_index = state.custody_bit_challenge_index - 1 + response = get_valid_custody_bit_response( + spec, + state, + challenge, + test_vector, + bit_challenge_index, + invalid_chunk_bit=False) + + _, _, _ = run_custody_bit_response_processing(spec, state, response) + + yield from run_process_final_custody_updates(spec, state) + + assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_validator_withdrawal_resume_after_chunk_challenge_response(spec, state): + state.slot = spec.SLOTS_PER_EPOCH + + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + next_epoch(spec, state) + apply_empty_block(spec, state) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + validator_index = spec.get_crosslink_committee( + state, + attestation.data.target.epoch, + attestation.data.crosslink.shard + )[0] + + spec.initiate_validator_exit(state, validator_index) + assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + next_epoch(spec, state) + apply_empty_block(spec, state) + + assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch: + next_epoch(spec, state) + apply_empty_block(spec, state) + + while (state.validators[validator_index].next_custody_secret_to_reveal + <= spec.get_custody_period_for_validator( + state, + validator_index, + state.validators[validator_index].exit_epoch - 1)): + custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index) + _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) + + next_epoch(spec, state) + apply_empty_block(spec, state) + + challenge = get_valid_chunk_challenge(spec, state, attestation) + + _, _, _ = run_chunk_challenge_processing(spec, state, challenge) + + next_epoch(spec, state) + apply_empty_block(spec, state) + + assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + chunk_challenge_index = state.custody_chunk_challenge_index - 1 + response = get_valid_custody_chunk_response(spec, state, challenge, test_vector, chunk_challenge_index) + + _, _, _ = run_custody_chunk_response_processing(spec, state, response) + + yield from run_process_final_custody_updates(spec, state) + + assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH diff --git a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_reveal_deadlines.py b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_reveal_deadlines.py new file mode 100644 index 000000000..688b046c8 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_reveal_deadlines.py @@ -0,0 +1,46 @@ +from eth2spec.test.helpers.custody import ( + get_valid_custody_key_reveal, +) +from eth2spec.test.helpers.state import next_epoch +from eth2spec.test.context import ( + with_all_phases_except, + spec_state_test, +) +from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with +from eth2spec.test.phase_1.block_processing.test_process_custody_key_reveal import run_custody_key_reveal_processing + + +def run_process_challenge_deadlines(spec, state): + yield from run_epoch_processing_with(spec, state, 'process_challenge_deadlines') + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_validator_slashed_after_reveal_deadline(spec, state): + assert state.validators[0].slashed == 0 + + state.slot += ((spec.CHUNK_RESPONSE_DEADLINE + spec.EPOCHS_PER_CUSTODY_PERIOD) + * spec.SLOTS_PER_EPOCH) + next_epoch(spec, state) + + yield from run_process_challenge_deadlines(spec, state) + + assert state.validators[0].slashed == 1 + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_validator_not_slashed_after_reveal(spec, state): + state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH + custody_key_reveal = get_valid_custody_key_reveal(spec, state) + + _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) + + assert state.validators[0].slashed == 0 + + state.slot += spec.CHUNK_RESPONSE_DEADLINE * spec.SLOTS_PER_EPOCH + next_epoch(spec, state) + + yield from run_process_challenge_deadlines(spec, state) + + assert state.validators[0].slashed == 0