From ca6af0c2e9bfba1667ea7b6a67a03144be7aa23b Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Sun, 5 Apr 2020 14:39:00 +0100 Subject: [PATCH 01/45] 256-bit custody atoms for better alignment with rest of the spec and greater efficiency --- specs/phase1/custody-game.md | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index eb243f8fb..b4033ea4d 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -49,8 +49,9 @@ This document details the beacon chain additions and changes in Phase 1 of Ether | Name | Value | Unit | | - | - | - | -| `BLS12_381_Q` | `4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787` | - | -| `BYTES_PER_CUSTODY_ATOM` | `48` | bytes | +| `CUSTODY_PRIME` | `2 ** 256 - 189` | - | +| `CUSTODY_SECRETS` | `3` | - | +| `BYTES_PER_CUSTODY_ATOM` | `32` | bytes | ## Configuration @@ -175,7 +176,7 @@ def legendre_bit(a: int, q: int) -> int: return 0 ``` -### `custody_atoms` +### `get_custody_atoms` Given one set of data, return the custody atoms: each atom will be combined with one legendre bit. @@ -186,16 +187,28 @@ def get_custody_atoms(bytez: bytes) -> Sequence[bytes]: for i in range(0, len(bytez), BYTES_PER_CUSTODY_ATOM)] ``` +### `get_custody_secrets` + +Extract the custody secrets from the signature + +```python +def get_custody_secrets(key: BLSSignature): + full_G2_element = bls.signature_to_G2(key) + signature = full_G2_element[0].coeffs + signature_bytes = sum(x.to_bytes(48, "little") for x in signature) + secrets = [int.from_bytes(x[i:i+BYTES_PER_CUSTODY_ATOM]) for i in range(0, len(signature_bytes), 32)] + return secrets +``` + ### `compute_custody_bit` ```python def compute_custody_bit(key: BLSSignature, data: bytes) -> bit: - full_G2_element = bls.signature_to_G2(key) - s = full_G2_element[0].coeffs + secrets = get_custody_secrets(key) custody_atoms = get_custody_atoms(data) n = len(custody_atoms) - a = sum(s[i % 2]**i * int.from_bytes(atom, "little") for i, atom in enumerate(custody_atoms) + s[n % 2]**n) - return legendre_bit(a, BLS12_381_Q) + uhf = sum(secrets[i % CUSTORY_SECRETS]**i * int.from_bytes(atom, "little") for i, atom in enumerate(custody_atoms)) + secrets[n % CUSTORY_SECRETS]**n + return legendre_bit(uhf + secrets[0], BLS12_381_Q) ``` ### `get_randao_epoch_for_custody_period` From bf34fdf0239541b713afeabe527c557d54c7c1b8 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Sun, 5 Apr 2020 15:10:09 +0100 Subject: [PATCH 02/45] Fix ToC --- specs/phase1/custody-game.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index b4033ea4d..370972502 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -24,7 +24,8 @@ - [`EarlyDerivedSecretReveal`](#earlyderivedsecretreveal) - [Helpers](#helpers) - [`legendre_bit`](#legendre_bit) - - [`custody_atoms`](#custody_atoms) + - [`get_custody_secrets`](#get_custody_secrets) + - [`get_custody_atoms`](#get_custody_atoms) - [`compute_custody_bit`](#compute_custody_bit) - [`get_randao_epoch_for_custody_period`](#get_randao_epoch_for_custody_period) - [`get_custody_period_for_validator`](#get_custody_period_for_validator) From c3c24b4fc4a1592a9dc6d42fb57694aae59ee383 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Sun, 5 Apr 2020 15:35:11 +0100 Subject: [PATCH 03/45] Fix lint --- specs/phase1/custody-game.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 370972502..c1ed24383 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -193,11 +193,12 @@ def get_custody_atoms(bytez: bytes) -> Sequence[bytes]: Extract the custody secrets from the signature ```python -def get_custody_secrets(key: BLSSignature): +def get_custody_secrets(key: BLSSignature) -> Sequence[int]: full_G2_element = bls.signature_to_G2(key) signature = full_G2_element[0].coeffs - signature_bytes = sum(x.to_bytes(48, "little") for x in signature) - secrets = [int.from_bytes(x[i:i+BYTES_PER_CUSTODY_ATOM]) for i in range(0, len(signature_bytes), 32)] + signature_bytes = b"".join(x.to_bytes(48, "little") for x in signature) + secrets = [int.from_bytes(signature_bytes[i:i + BYTES_PER_CUSTODY_ATOM], "little") + for i in range(0, len(signature_bytes), 32)] return secrets ``` @@ -208,8 +209,9 @@ def compute_custody_bit(key: BLSSignature, data: bytes) -> bit: secrets = get_custody_secrets(key) custody_atoms = get_custody_atoms(data) n = len(custody_atoms) - uhf = sum(secrets[i % CUSTORY_SECRETS]**i * int.from_bytes(atom, "little") for i, atom in enumerate(custody_atoms)) + secrets[n % CUSTORY_SECRETS]**n - return legendre_bit(uhf + secrets[0], BLS12_381_Q) + uhf = (sum(secrets[i % CUSTODY_SECRETS]**i * int.from_bytes(atom, "little") % CUSTODY_PRIME + for i, atom in enumerate(custody_atoms)) + secrets[n % CUSTODY_SECRETS]**n) % CUSTODY_PRIME + return legendre_bit(uhf + secrets[0], CUSTODY_PRIME) ``` ### `get_randao_epoch_for_custody_period` From 907c56dabdeae9020decb76437f7d6b772838078 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Sun, 5 Apr 2020 15:47:59 +0100 Subject: [PATCH 04/45] Fix ToC --- specs/phase1/custody-game.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index c1ed24383..6315955f5 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -24,8 +24,8 @@ - [`EarlyDerivedSecretReveal`](#earlyderivedsecretreveal) - [Helpers](#helpers) - [`legendre_bit`](#legendre_bit) - - [`get_custody_secrets`](#get_custody_secrets) - [`get_custody_atoms`](#get_custody_atoms) + - [`get_custody_secrets`](#get_custody_secrets) - [`compute_custody_bit`](#compute_custody_bit) - [`get_randao_epoch_for_custody_period`](#get_randao_epoch_for_custody_period) - [`get_custody_period_for_validator`](#get_custody_period_for_validator) From ab2ee0e2c2898ccb8398d555e64a8a6d34ebbeec Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 24 Apr 2020 17:06:27 +0100 Subject: [PATCH 05/45] 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 From 2449db1bb6457c0b9e96bd7ccd3c3e3873050a1d Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Mon, 27 Apr 2020 16:08:49 +0100 Subject: [PATCH 06/45] Phase 1 block tests are working --- specs/phase1/beacon-chain.md | 9 +- specs/phase1/custody-game.md | 30 +- .../eth2spec/test/helpers/attestations.py | 48 ++- .../pyspec/eth2spec/test/helpers/custody.py | 81 ++-- .../test_process_chunk_challenge.py | 14 +- .../test_process_custody_key_reveal.py | 16 - .../test_process_custody_slashing.py | 365 +++++------------- 7 files changed, 211 insertions(+), 352 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 18c5f347f..fc208cbaa 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -111,7 +111,7 @@ Configuration is not namespaced. Instead it is strictly an extension; | `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) | - | - | +| `CUSTODY_RESPONSE_DEPTH` | `ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK) | - | - | @@ -137,7 +137,6 @@ class CustodyChunkChallengeRecord(Container): responder_index: ValidatorIndex inclusion_epoch: Epoch data_root: Root - depth: uint64 chunk_index: uint64 ``` @@ -148,7 +147,7 @@ class CustodyChunkResponse(Container): challenge_index: uint64 chunk_index: uint64 chunk: ByteVector[BYTES_PER_CUSTODY_CHUNK] - branch: List[Root, MAX_CUSTODY_RESPONSE_DEPTH] + branch: Vector[Root, CUSTODY_RESPONSE_DEPTH] ``` #### `CustodySlashing` @@ -281,7 +280,8 @@ class Validator(Container): # (of the particular validator) in which the validator is activated # = get_custody_period_for_validator(...) next_custody_secret_to_reveal: uint64 - max_reveal_lateness: Epoch + # TODO: The max_reveal_lateness doesn't really make sense anymore. + # So how do we incentivise early custody key reveals now? ``` ### Extended `BeaconBlockBody` @@ -428,6 +428,7 @@ class ShardTransition(Container): # Shard block lengths shard_block_lengths: List[uint64, MAX_SHARD_BLOCKS_PER_ATTESTATION] # Shard data roots + # The root is of ByteVector[MAX_] shard_data_roots: List[Bytes32, MAX_SHARD_BLOCKS_PER_ATTESTATION] # Intermediate shard states shard_states: List[ShardState, MAX_SHARD_BLOCKS_PER_ATTESTATION] diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index f069c4cd2..3047fb263 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -17,11 +17,6 @@ - [Reward and penalty quotients](#reward-and-penalty-quotients) - [Signature domain types](#signature-domain-types) - [Data structures](#data-structures) - - [New Beacon Chain operations](#new-beacon-chain-operations) - - [`CustodySlashing`](#custodyslashing) - - [`SignedCustodySlashing`](#signedcustodyslashing) - - [`CustodyKeyReveal`](#custodykeyreveal) - - [`EarlyDerivedSecretReveal`](#earlyderivedsecretreveal) - [Helpers](#helpers) - [`legendre_bit`](#legendre_bit) - [`get_custody_atoms`](#get_custody_atoms) @@ -64,7 +59,6 @@ This document details the beacon chain additions and changes in Phase 1 of Ether | `EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS` | `2**14` (= 16,384) | epochs | ~73 days | | `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 | @@ -168,7 +162,7 @@ def get_custody_secrets(key: BLSSignature) -> Sequence[int]: ### `compute_custody_bit` ```python -def compute_custody_bit(key: BLSSignature, data: bytes) -> bit: +def compute_custody_bit(key: BLSSignature, data: ByteList[MAX_SHARD_BLOCK_SIZE]) -> bit: secrets = get_custody_secrets(key) custody_atoms = get_custody_atoms(data) n = len(custody_atoms) @@ -243,7 +237,6 @@ def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge ) # 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( @@ -252,7 +245,6 @@ def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge 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) @@ -277,7 +269,7 @@ def process_chunk_challenge_response(state: BeaconState, assert is_valid_merkle_branch( leaf=hash_tree_root(response.chunk), branch=response.branch, - depth=challenge.depth, + depth=CUSTODY_RESPONSE_DEPTH, index=response.chunk_index, root=challenge.data_root, ) @@ -311,18 +303,6 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> signing_root = compute_signing_root(epoch_to_sign, domain) assert bls.Verify(revealer.pubkey, signing_root, reveal.reveal) - # Decrement max reveal lateness if response is timely - if epoch_to_sign + EPOCHS_PER_CUSTODY_PERIOD >= get_current_epoch(state): - if revealer.max_reveal_lateness >= MAX_REVEAL_LATENESS_DECREMENT: - revealer.max_reveal_lateness -= MAX_REVEAL_LATENESS_DECREMENT - else: - revealer.max_reveal_lateness = 0 - else: - revealer.max_reveal_lateness = max( - revealer.max_reveal_lateness, - get_current_epoch(state) - epoch_to_sign - EPOCHS_PER_CUSTODY_PERIOD - ) - # Process reveal revealer.next_custody_secret_to_reveal += 1 @@ -417,13 +397,15 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) # TODO: custody_slashing.data is not chunked like shard blocks yet, result is lots of padding. + # ??? What does this mean? # TODO: can do a single combined merkle proof of data being attested. # Verify the shard transition is indeed attested by the attestation shard_transition = custody_slashing.shard_transition - assert hash_tree_root(shard_transition) == attestation.shard_transition_root + assert hash_tree_root(shard_transition) == attestation.data.shard_transition_root # Verify that the provided data matches the shard-transition - assert hash_tree_root(custody_slashing.data) == shard_transition.shard_data_roots[custody_slashing.data_index] + assert custody_slashing.data.get_backing().get_left().merkle_root() == shard_transition.shard_data_roots[custody_slashing.data_index] + assert len(custody_slashing.data) == shard_transition.shard_block_lengths[custody_slashing.data_index] # Verify existence and participation of claimed malefactor attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 325fee260..5e536002f 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -6,6 +6,8 @@ from eth2spec.test.helpers.block import build_empty_block_for_next_slot from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls from eth2spec.utils.ssz.ssz_typing import Bitlist +from eth2spec.utils.ssz.ssz_impl import hash_tree_root +from eth2spec.test.helpers.custody import get_custody_test_vector def run_attestation_processing(spec, state, attestation, valid=True): @@ -72,17 +74,43 @@ def build_attestation_data(spec, state, slot, index, shard_transition_root=None) 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, + shard_transition_root=shard_transition_root if shard_transition_root else spec.Root(), ) -def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False): +def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False, shard_transition=None, valid_custody_bits=None): shard = spec.get_shard(state, attestation) offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), state.slot + 1) - for offset_slot in offset_slots: + + if valid_custody_bits is not None: + beacon_committee = spec.get_beacon_committee( + state, + attestation.data.slot, + attestation.data.index, + ) + current_epoch = spec.get_current_epoch(state) + custody_secrets = [None for i in beacon_committee] + for i in range(len(beacon_committee)): + validator = state.validators[beacon_committee[i]] + + period = spec.get_custody_period_for_validator(beacon_committee[i], attestation.data.target.epoch) + + epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, beacon_committee[i]) + + domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch_to_sign) + signing_root = spec.compute_signing_root(spec.Epoch(epoch_to_sign), domain) + custody_secrets[i] = bls.Sign(privkeys[beacon_committee[i]], signing_root) + + + for i, offset_slot in enumerate(offset_slots): attestation.custody_bits_blocks.append( Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits]) ) + if valid_custody_bits is not None: + test_vector = get_custody_test_vector(shard_transition.shard_block_lengths[i]) + for j in range(len(attestation.custody_bits_blocks[i])): + if attestation.aggregation_bits[j]: + attestation.custody_bits_blocks[i][j] = spec.compute_custody_bit(custody_secrets[j], test_vector) ^ (not valid_custody_bits) if signed: sign_attestation(spec, state, attestation) @@ -90,7 +118,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, shard_transition_root=None): +def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=False, shard_transition=None, valid_custody_bits=None): ''' Construct on-time attestation for next slot ''' @@ -99,10 +127,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, shard_transition_root=shard_transition_root) + return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=True, shard_transition=shard_transition, valid_custody_bits=valid_custody_bits) -def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False, shard_transition_root=None): +def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False, shard_transition=None): ''' Construct on-time attestation for next slot ''' @@ -111,16 +139,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, shard_transition_root=shard_transition_root) + return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=False, shard_transition=shard_transition) -def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signed=False, on_time=True, shard_transition_root=None): +def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signed=False, on_time=True, shard_transition=None, valid_custody_bits=None): if slot is None: slot = state.slot if index is None: index = 0 - attestation_data = build_attestation_data(spec, state, slot, index, shard_transition_root=shard_transition_root) + attestation_data = build_attestation_data(spec, state, slot, index, shard_transition_root=hash_tree_root(shard_transition) if shard_transition else spec.Root()) beacon_committee = spec.get_beacon_committee( state, @@ -140,7 +168,7 @@ def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signe sign_attestation(spec, state, attestation) if spec.fork == 'phase1' and on_time: - attestation = convert_to_valid_on_time_attestation(spec, state, attestation, signed) + attestation = convert_to_valid_on_time_attestation(spec, state, attestation, signed, shard_transition=shard_transition, valid_custody_bits=valid_custody_bits) return attestation diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index 45c29be9a..f4b2d6a3d 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -1,10 +1,10 @@ from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls -from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, Bitvector, ByteList +from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, Bitvector, ByteList, uint64 from eth2spec.utils.ssz.ssz_impl import hash_tree_root 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 +from remerkleable.tree import subtree_fill_to_contents, get_depth, Node, Gindex, gindex_bit_iter, Root BYTES_PER_CHUNK = 32 @@ -61,39 +61,45 @@ 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_custody_slashing(spec, state, attestation, invalid_custody_bit=False): +def get_valid_custody_slashing(spec, state, attestation, shard_transition, invalid_custody_bit=False): beacon_committee = spec.get_beacon_committee( state, attestation.data.slot, - attestation.data.crosslink.shard, + attestation.data.index, ) - responder_index = beacon_committee[0] - challenger_index = beacon_committee[-1] + malefactor_index = beacon_committee[0] + whistleblower_index = beacon_committee[-1] epoch = spec.get_randao_epoch_for_custody_period(attestation.data.target.epoch, - responder_index) + malefactor_index) # Generate the responder key domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch) signing_root = spec.compute_signing_root(spec.Epoch(epoch), domain) - responder_key = bls.Sign(privkeys[responder_index], signing_root) + malefactor_key = bls.Sign(privkeys[malefactor_index], signing_root) + data_index = 0 + data=ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(shard_transition.shard_block_lengths[data_index])) + print(hash_tree_root(data)) + print(data.get_backing().get_left().merkle_root()) - chunk_count = spec.get_custody_chunk_count(attestation.data.crosslink) - - chunk_bits = bitlist_from_int(spec.MAX_CUSTODY_CHUNKS, chunk_count, 0) - - n = 0 - while spec.get_chunk_bits_root(chunk_bits) == attestation.custody_bits[0] ^ invalid_custody_bit: - chunk_bits = bitlist_from_int(spec.MAX_CUSTODY_CHUNKS, chunk_count, n) - n += 1 - - return spec.CustodyBitChallenge( - responder_index=responder_index, + slashing = spec.CustodySlashing( + data_index=data_index, + malefactor_index=malefactor_index, + malefactor_secret=malefactor_key, + whistleblower_index=whistleblower_index, + shard_transition=shard_transition, attestation=attestation, - challenger_index=challenger_index, - responder_key=responder_key, - chunk_bits=chunk_bits, + data=data, ) + slashing_domain = spec.get_domain(state, spec.DOMAIN_CUSTODY_BIT_SLASHING) + slashing_root = spec.compute_signing_root(slashing, domain) + + signed_slashing = spec.SignedCustodySlashing( + message=slashing, + signature=bls.Sign(privkeys[whistleblower_index], slashing_root) + ) + + return signed_slashing def get_valid_chunk_challenge(spec, state, attestation, shard_transition): @@ -123,20 +129,35 @@ def custody_chunkify(spec, x): return chunks +def build_proof(anchor, leaf_index): + if leaf_index <= 1: + return [] # Nothing to prove / invalid index + node = anchor + proof = [] + # Walk down, top to bottom to the leaf + bit_iter, _ = gindex_bit_iter(leaf_index) + for bit in bit_iter: + # Always take the opposite hand for the proof. + # 1 = right as leaf, thus get left + if bit: + proof.append(node.get_left().merkle_root()) + node = node.get_right() + else: + proof.append(node.get_right().merkle_root()) + node = node.get_left() + + return list(reversed(proof)) + + 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) + custody_data_block = ByteList[spec.MAX_SHARD_BLOCK_SIZE](custody_data) chunks = custody_chunkify(spec, custody_data) 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 += [ - hash_tree_root(ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](b"\0" * spec.BYTES_PER_CUSTODY_CHUNK)) - for i in range(2 ** spec.ceillog2(len(chunks)) - len(chunks))] - data_tree = get_merkle_tree(chunks_hash_tree_roots) - - data_branch = get_merkle_proof(data_tree, chunk_index) + data_branch = build_proof(custody_data_block.get_backing().get_left(), chunk_index + 2**spec.CUSTODY_RESPONSE_DEPTH) return spec.CustodyChunkResponse( challenge_index=challenge_index, @@ -152,7 +173,7 @@ def get_custody_test_vector(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] + b = [ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(x)).get_backing().get_left().merkle_root() for x in block_lengths] shard_transition = spec.ShardTransition( start_slot=start_slot, shard_block_lengths=block_lengths, 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 index 09b25a1ad..d409b468f 100644 --- 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 @@ -75,7 +75,7 @@ def test_challenge_appended(spec, state): 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)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=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]) @@ -101,7 +101,7 @@ def test_multiple_epochs_custody(spec, state): 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)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -123,7 +123,7 @@ def test_many_epochs_custody(spec, state): 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)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -145,7 +145,7 @@ def test_off_chain_attestation(spec, state): 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)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) @@ -163,7 +163,7 @@ def test_custody_response(spec, state): 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)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -191,7 +191,7 @@ def test_custody_response_multiple_epochs(spec, state): 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)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -219,7 +219,7 @@ def test_custody_response_many_epochs(spec, state): 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)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py index 8c2436d5b..fd16a344c 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py @@ -28,30 +28,14 @@ def run_custody_key_reveal_processing(spec, state, custody_key_reveal, valid=Tru pre_next_custody_secret_to_reveal = \ state.validators[revealer_index].next_custody_secret_to_reveal - pre_reveal_lateness = state.validators[revealer_index].max_reveal_lateness spec.process_custody_key_reveal(state, custody_key_reveal) post_next_custody_secret_to_reveal = \ state.validators[revealer_index].next_custody_secret_to_reveal - post_reveal_lateness = state.validators[revealer_index].max_reveal_lateness assert post_next_custody_secret_to_reveal == pre_next_custody_secret_to_reveal + 1 - if spec.get_current_epoch(state) > spec.get_randao_epoch_for_custody_period( - pre_next_custody_secret_to_reveal, - revealer_index - ) + spec.EPOCHS_PER_CUSTODY_PERIOD: - assert post_reveal_lateness > 0 - if pre_reveal_lateness == 0: - assert post_reveal_lateness == spec.get_current_epoch(state) - spec.get_randao_epoch_for_custody_period( - pre_next_custody_secret_to_reveal, - revealer_index - ) - spec.EPOCHS_PER_CUSTODY_PERIOD - else: - if pre_reveal_lateness > 0: - assert post_reveal_lateness < pre_reveal_lateness - yield 'post', state 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 index 358aa5c0d..706fd8f49 100644 --- 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 @@ -1,14 +1,15 @@ from eth2spec.test.helpers.custody import ( - get_valid_bit_challenge, - get_valid_custody_bit_response, + get_valid_custody_slashing, get_custody_test_vector, get_custody_merkle_root, + get_shard_transition, ) from eth2spec.test.helpers.attestations import ( - get_valid_attestation, + get_valid_on_time_attestation, ) from eth2spec.utils.ssz.ssz_impl import hash_tree_root -from eth2spec.test.helpers.state import next_epoch, get_balance +from eth2spec.utils.ssz.ssz_typing import ByteList +from eth2spec.test.helpers.state import next_epoch, get_balance, transition_to from eth2spec.test.helpers.block import apply_empty_block from eth2spec.test.context import ( with_all_phases_except, @@ -18,332 +19,174 @@ from eth2spec.test.context import ( 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): +def run_custody_slashing_processing(spec, state, custody_slashing, valid=True, correct=True): """ Run ``process_bit_challenge``, yielding: - pre-state ('pre') - - CustodyBitChallenge ('custody_bit_challenge') + - CustodySlashing ('custody_slashing') - post-state ('post'). If ``valid == False``, run expecting ``AssertionError`` """ yield 'pre', state - yield 'custody_bit_challenge', custody_bit_challenge + yield 'custody_slashing', custody_slashing if not valid: - expect_assertion_error(lambda: spec.process_bit_challenge(state, custody_bit_challenge)) + expect_assertion_error(lambda: spec.process_custody_slashing(state, custody_slashing)) yield 'post', None return - spec.process_bit_challenge(state, custody_bit_challenge) + if correct: + pre_slashed_balance = get_balance(state, custody_slashing.message.malefactor_index) + else: + pre_slashed_balance = get_balance(state, custody_slashing.message.whistleblower_index) - 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] + spec.process_custody_slashing(state, custody_slashing) + if correct: + slashed_validator = state.validators[custody_slashing.message.malefactor_index] + assert get_balance(state, custody_slashing.message.malefactor_index) < pre_slashed_balance + else: + slashed_validator = state.validators[custody_slashing.message.whistleblower_index] + assert get_balance(state, custody_slashing.message.whistleblower_index) < pre_slashed_balance + 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) +def test_custody_slashing(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=shard_transition, valid_custody_bits=False) - 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 + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) _, _, _ = run_attestation_processing(spec, state, attestation) - state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - challenge = get_valid_bit_challenge(spec, state, attestation) + data_index = 0 - yield from run_bit_challenge_processing(spec, state, challenge) + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) + + yield from run_custody_slashing_processing(spec, state, slashing, correct=True) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_incorrect_custody_slashing(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=shard_transition, valid_custody_bits=True) + + 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)) + + data_index = 0 + + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) + + yield from run_custody_slashing_processing(spec, state, slashing, correct=False) @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) + 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=shard_transition, valid_custody_bits=False) - 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 + transition_to(spec, state, 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) + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - challenge = get_valid_bit_challenge(spec, state, attestation) + data_index = 0 - yield from run_bit_challenge_processing(spec, state, challenge) + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) + + yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @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) + 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=shard_transition, valid_custody_bits=False) - 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 + transition_to(spec, state, 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) + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - challenge = get_valid_bit_challenge(spec, state, attestation) + data_index = 0 - yield from run_bit_challenge_processing(spec, state, challenge) + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) + + yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @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) + 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=shard_transition, valid_custody_bits=False) - 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 + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY - state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD + data_index = 0 - challenge = get_valid_bit_challenge(spec, state, attestation) + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) - yield from run_bit_challenge_processing(spec, state, challenge) + yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @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) +def test_invalid_custody_slashing(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=shard_transition, valid_custody_bits=False) - 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 + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) _, _, _ = run_attestation_processing(spec, state, attestation) - state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - challenge = get_valid_bit_challenge(spec, state, attestation, invalid_custody_bit=True) + data_index = 0 - yield from run_bit_challenge_processing(spec, state, challenge, valid=False) + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) + slashing.message.data = ByteList[spec.MAX_SHARD_BLOCK_SIZE]() -@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) + yield from run_custody_slashing_processing(spec, state, slashing, valid=False) From 0e2931b9b3490027e11a9a4a7c0851466d533d5d Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Tue, 28 Apr 2020 01:09:20 +0100 Subject: [PATCH 07/45] All tests passed --- configs/minimal.yaml | 2 - specs/phase1/beacon-chain.md | 54 +++++ specs/phase1/custody-game.md | 29 ++- specs/phase1/phase1-fork.md | 2 +- .../pyspec/eth2spec/test/helpers/custody.py | 5 +- .../test_process_final_custody_updates.py | 213 +++--------------- 6 files changed, 121 insertions(+), 184 deletions(-) diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 256c2b3fa..5f8b6b8a2 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -208,8 +208,6 @@ EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096 EPOCHS_PER_CUSTODY_PERIOD: 8 # 2**11 (= 2,048) epochs CUSTODY_PERIOD_TO_RANDAO_PADDING: 8 -# 2**7 (= 128) epochs -MAX_REVEAL_LATENESS_DECREMENT: 128 # Max operations # 2**8 (= 256) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index fc208cbaa..b6b21b721 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -282,6 +282,7 @@ class Validator(Container): next_custody_secret_to_reveal: uint64 # TODO: The max_reveal_lateness doesn't really make sense anymore. # So how do we incentivise early custody key reveals now? + all_custody_secrets_revealed_epoch: Epoch # to be initialized to FAR_FUTURE_EPOCH ``` ### Extended `BeaconBlockBody` @@ -925,6 +926,59 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: state.previous_epoch_attestations.append(pending_attestation) ``` +##### New deposits + +```python +def process_deposit(state: BeaconState, deposit: Deposit) -> None: + # Verify the Merkle branch + assert is_valid_merkle_branch( + leaf=hash_tree_root(deposit.data), + branch=deposit.proof, + depth=DEPOSIT_CONTRACT_TREE_DEPTH + 1, # Add 1 for the List length mix-in + index=state.eth1_deposit_index, + root=state.eth1_data.deposit_root, + ) + + # Deposits must be processed in order + state.eth1_deposit_index += 1 + + pubkey = deposit.data.pubkey + amount = deposit.data.amount + validator_pubkeys = [v.pubkey for v in state.validators] + if pubkey not in validator_pubkeys: + # Verify the deposit signature (proof of possession) which is not checked by the deposit contract + deposit_message = DepositMessage( + pubkey=deposit.data.pubkey, + withdrawal_credentials=deposit.data.withdrawal_credentials, + amount=deposit.data.amount, + ) + domain = compute_domain(DOMAIN_DEPOSIT) # Fork-agnostic domain since deposits are valid across forks + signing_root = compute_signing_root(deposit_message, domain) + if not bls.Verify(pubkey, signing_root, deposit.data.signature): + return + + # Add validator and balance entries + # TODO: This function is duplicated from phase 1 just because the validator definition + # has changed and we need to initialize it properly. Is there a better solution for + # this? + state.validators.append(Validator( + pubkey=pubkey, + withdrawal_credentials=deposit.data.withdrawal_credentials, + activation_eligibility_epoch=FAR_FUTURE_EPOCH, + activation_epoch=FAR_FUTURE_EPOCH, + exit_epoch=FAR_FUTURE_EPOCH, + withdrawable_epoch=FAR_FUTURE_EPOCH, + effective_balance=min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE), + next_custody_secret_to_reveal=get_custody_period_for_validator(ValidatorIndex(len(state.validators)), get_current_epoch(state)), + all_custody_secrets_revealed_epoch=FAR_FUTURE_EPOCH, + )) + state.balances.append(amount) + else: + # Increase balance by deposit amount + index = ValidatorIndex(validator_pubkeys.index(pubkey)) + increase_balance(state, index, amount) +``` + ##### New Attester slashing processing ```python diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 3047fb263..f40e936ce 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -293,7 +293,12 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> epoch_to_sign = get_randao_epoch_for_custody_period(revealer.next_custody_secret_to_reveal, reveal.revealer_index) custody_reveal_period = get_custody_period_for_validator(reveal.revealer_index, get_current_epoch(state)) - assert revealer.next_custody_secret_to_reveal < custody_reveal_period + # Only past custody periods can be revealed, except after exiting the exit + # period can be revealed + assert (revealer.next_custody_secret_to_reveal < custody_reveal_period + or (revealer.exit_epoch <= get_current_epoch(state) and + revealer.next_custody_secret_to_reveal + <= get_custody_period_for_validator(reveal.revealer_index, revealer.exit_epoch - 1))) # Revealed validator is active or exited, but not withdrawn assert is_slashable_validator(revealer, get_current_epoch(state)) @@ -304,6 +309,10 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> assert bls.Verify(revealer.pubkey, signing_root, reveal.reveal) # Process reveal + if (revealer.exit_epoch <= get_current_epoch(state) and + revealer.next_custody_secret_to_reveal + == get_custody_period_for_validator(reveal.revealer_index, revealer.exit_epoch - 1)): + revealer.all_custody_secrets_revealed_epoch = get_current_epoch(state) revealer.next_custody_secret_to_reveal += 1 # Reward Block Proposer @@ -480,4 +489,22 @@ After `process_final_updates(state)`, additional updates are made for the custod def process_custody_final_updates(state: BeaconState) -> None: # Clean up exposed RANDAO key reveals state.exposed_derived_secrets[get_current_epoch(state) % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS] = [] + + # Reset withdrawable epochs if challenge records are empty + records = state.custody_chunk_challenge_records + validator_indices_in_records = set( + [record.responder_index for record in records] + ) + for index, validator in enumerate(state.validators): + if validator.exit_epoch != FAR_FUTURE_EPOCH: + if (index in validator_indices_in_records + or validator.all_custody_secrets_revealed_epoch == FAR_FUTURE_EPOCH): + # Delay withdrawable epochs if challenge records are not empty or not all + # custody secrets revealed + validator.withdrawable_epoch = FAR_FUTURE_EPOCH + else: + # Reset withdrawable epochs if challenge records are empty + if validator.withdrawable_epoch == FAR_FUTURE_EPOCH: + validator.withdrawable_epoch = Epoch(validator.all_custody_secrets_revealed_epoch + + MIN_VALIDATOR_WITHDRAWABILITY_DELAY) ``` diff --git a/specs/phase1/phase1-fork.md b/specs/phase1/phase1-fork.md index 173fceeb4..9d5f282f3 100644 --- a/specs/phase1/phase1-fork.md +++ b/specs/phase1/phase1-fork.md @@ -79,7 +79,7 @@ def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState: exit_epoch=phase0_validator.exit_epoch, withdrawable_epoch=phase0_validator.withdrawable_epoch, next_custody_secret_to_reveal=get_custody_period_for_validator(ValidatorIndex(i), epoch), - max_reveal_lateness=0, # TODO custody refactor. Outdated? + all_custody_secrets_revealed_epoch=FAR_FUTURE_EPOCH, ) for i, phase0_validator in enumerate(pre.validators) ), balances=pre.balances, diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index f4b2d6a3d..2d937a2ee 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -37,9 +37,10 @@ def get_valid_early_derived_secret_reveal(spec, state, epoch=None): ) -def get_valid_custody_key_reveal(spec, state, period=None): +def get_valid_custody_key_reveal(spec, state, period=None, validator_index=None): current_epoch = spec.get_current_epoch(state) - revealer_index = spec.get_active_validator_indices(state, current_epoch)[0] + revealer_index = (spec.get_active_validator_indices(state, current_epoch)[0] + if validator_index is None else validator_index) revealer = state.validators[revealer_index] if period is None: 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 index d200ed2b3..566d795cf 100644 --- 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 @@ -1,16 +1,15 @@ 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, + get_shard_transition ) from eth2spec.test.helpers.attestations import ( - get_valid_attestation, + get_valid_on_time_attestation, ) -from eth2spec.test.helpers.state import next_epoch +from eth2spec.test.helpers.state import next_epoch, transition_to from eth2spec.test.helpers.block import apply_empty_block from eth2spec.test.context import ( with_all_phases_except, @@ -19,10 +18,6 @@ from eth2spec.test.context import ( 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, @@ -30,8 +25,8 @@ from eth2spec.test.phase_1.block_processing.test_process_chunk_challenge import 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') +def run_process_custody_final_updates(spec, state): + yield from run_epoch_processing_with(spec, state, 'process_custody_final_updates') @with_all_phases_except(['phase0']) @@ -40,7 +35,7 @@ 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) + yield from run_process_custody_final_updates(spec, state) assert state.validators[0].withdrawable_epoch == spec.FAR_FUTURE_EPOCH @@ -61,100 +56,39 @@ def test_validator_withdrawal_reenable_after_custody_reveal(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)): + <= spec.get_custody_period_for_validator(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) + yield from run_process_custody_final_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 + 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=shard_transition) - 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) + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) _, _, _ = run_attestation_processing(spec, state, attestation) - validator_index = spec.get_crosslink_committee( + validator_index = spec.get_beacon_committee( state, - attestation.data.target.epoch, - attestation.data.crosslink.shard + attestation.data.slot, + attestation.data.index )[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) + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH @@ -164,7 +98,6 @@ def test_validator_withdrawal_suspend_after_chunk_challenge(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) @@ -173,108 +106,33 @@ def test_validator_withdrawal_suspend_after_chunk_challenge(spec, state): next_epoch(spec, state) apply_empty_block(spec, state) - challenge = get_valid_chunk_challenge(spec, state, attestation) + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) _, _, _ = run_chunk_challenge_processing(spec, state, challenge) - yield from run_process_final_custody_updates(spec, state) + yield from run_process_custody_final_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 + 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=shard_transition) - 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) + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) _, _, _ = run_attestation_processing(spec, state, attestation) - validator_index = spec.get_crosslink_committee( + validator_index = spec.get_beacon_committee( state, - attestation.data.target.epoch, - attestation.data.crosslink.shard + attestation.data.slot, + attestation.data.index )[0] spec.initiate_validator_exit(state, validator_index) @@ -291,7 +149,6 @@ def test_validator_withdrawal_resume_after_chunk_challenge_response(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) @@ -300,7 +157,7 @@ def test_validator_withdrawal_resume_after_chunk_challenge_response(spec, state) next_epoch(spec, state) apply_empty_block(spec, state) - challenge = get_valid_chunk_challenge(spec, state, attestation) + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) _, _, _ = run_chunk_challenge_processing(spec, state, challenge) @@ -310,10 +167,10 @@ def test_validator_withdrawal_resume_after_chunk_challenge_response(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) + custody_response = get_valid_custody_chunk_response(spec, state, challenge, 2**15 // 3, chunk_challenge_index) - _, _, _ = run_custody_chunk_response_processing(spec, state, response) + _, _, _ = run_custody_chunk_response_processing(spec, state, custody_response) - yield from run_process_final_custody_updates(spec, state) + yield from run_process_custody_final_updates(spec, state) assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH From d58d7627b78885035dbbd0300e61e14266074da3 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Thu, 30 Apr 2020 19:25:18 +0100 Subject: [PATCH 08/45] Fix toc --- specs/phase1/beacon-chain.md | 11 ++++++++++- specs/phase1/custody-game.md | 4 ++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index b6b21b721..593f9ba0f 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -12,6 +12,14 @@ - [Custom types](#custom-types) - [Configuration](#configuration) - [Misc](#misc) +- [New containers](#new-containers) + - [`CustodyChunkChallenge`](#custodychunkchallenge) + - [`CustodyChunkChallengeRecord`](#custodychunkchallengerecord) + - [`CustodyChunkResponse`](#custodychunkresponse) + - [`CustodySlashing`](#custodyslashing) + - [`SignedCustodySlashing`](#signedcustodyslashing) + - [`CustodyKeyReveal`](#custodykeyreveal) + - [`EarlyDerivedSecretReveal`](#earlyderivedsecretreveal) - [Updated containers](#updated-containers) - [Extended `AttestationData`](#extended-attestationdata) - [Extended `Attestation`](#extended-attestation) @@ -23,7 +31,7 @@ - [Extended `BeaconBlock`](#extended-beaconblock) - [Extended `SignedBeaconBlock`](#extended-signedbeaconblock) - [Extended `BeaconState`](#extended-beaconstate) -- [New containers](#new-containers) +- [New containers](#new-containers-1) - [`ShardBlockWrapper`](#shardblockwrapper) - [`ShardSignableHeader`](#shardsignableheader) - [`ShardState`](#shardstate) @@ -61,6 +69,7 @@ - [`process_crosslink_for_shard`](#process_crosslink_for_shard) - [`process_crosslinks`](#process_crosslinks) - [`process_attestation`](#process_attestation) + - [New deposits](#new-deposits) - [New Attester slashing processing](#new-attester-slashing-processing) - [Shard transition false positives](#shard-transition-false-positives) - [Light client processing](#light-client-processing) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index f40e936ce..80ed544fe 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -17,7 +17,9 @@ - [Reward and penalty quotients](#reward-and-penalty-quotients) - [Signature domain types](#signature-domain-types) - [Data structures](#data-structures) + - [New Beacon Chain operations](#new-beacon-chain-operations) - [Helpers](#helpers) + - [`replace_empty_or_append`](#replace_empty_or_append) - [`legendre_bit`](#legendre_bit) - [`get_custody_atoms`](#get_custody_atoms) - [`get_custody_secrets`](#get_custody_secrets) @@ -26,6 +28,8 @@ - [`get_custody_period_for_validator`](#get_custody_period_for_validator) - [Per-block processing](#per-block-processing) - [Custody Game Operations](#custody-game-operations) + - [Chunk challenges](#chunk-challenges) + - [Custody chunk response](#custody-chunk-response) - [Custody key reveals](#custody-key-reveals) - [Early derived secret reveals](#early-derived-secret-reveals) - [Custody Slashings](#custody-slashings) From d30f11a7818eecabcccd6f9126edbf462675eab7 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 1 May 2020 00:16:00 +0100 Subject: [PATCH 09/45] Fix lint --- Makefile | 2 +- specs/phase1/beacon-chain.md | 5 +- specs/phase1/custody-game.md | 17 ++-- .../eth2spec/test/helpers/attestations.py | 32 ++++--- .../pyspec/eth2spec/test/helpers/custody.py | 32 +++---- .../test/helpers/phase1/attestations.py | 6 +- .../test_process_chunk_challenge.py | 85 ++++++++++++++----- .../test_process_custody_slashing.py | 46 ++++------ 8 files changed, 127 insertions(+), 98 deletions(-) diff --git a/Makefile b/Makefile index 8cd7daf58..18223430b 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,7 @@ codespell: lint: pyspec . venv/bin/activate; cd $(PY_SPEC_DIR); \ - flake8 --ignore=E252,W504,W503 --max-line-length=120 ./eth2spec \ + flake8 --ignore=E252,W504,W503,E128 --max-line-length=120 ./eth2spec \ && cd ./eth2spec && mypy --follow-imports=silent --warn-unused-ignores --ignore-missing-imports --check-untyped-defs --disallow-incomplete-defs --disallow-untyped-defs -p phase0 \ && mypy --follow-imports=silent --warn-unused-ignores --ignore-missing-imports --check-untyped-defs --disallow-incomplete-defs --disallow-untyped-defs -p phase1; diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 593f9ba0f..eda9fdd52 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -291,7 +291,7 @@ class Validator(Container): next_custody_secret_to_reveal: uint64 # TODO: The max_reveal_lateness doesn't really make sense anymore. # So how do we incentivise early custody key reveals now? - all_custody_secrets_revealed_epoch: Epoch # to be initialized to FAR_FUTURE_EPOCH + all_custody_secrets_revealed_epoch: Epoch # to be initialized to FAR_FUTURE_EPOCH ``` ### Extended `BeaconBlockBody` @@ -978,7 +978,8 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: exit_epoch=FAR_FUTURE_EPOCH, withdrawable_epoch=FAR_FUTURE_EPOCH, effective_balance=min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE), - next_custody_secret_to_reveal=get_custody_period_for_validator(ValidatorIndex(len(state.validators)), get_current_epoch(state)), + next_custody_secret_to_reveal=get_custody_period_for_validator(ValidatorIndex(len(state.validators)), + get_current_epoch(state)), all_custody_secrets_revealed_epoch=FAR_FUTURE_EPOCH, )) state.balances.append(amount) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 80ed544fe..9f5a8909b 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -102,7 +102,8 @@ The following types are defined, mapping into `DomainType` (little endian): ```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)): + if list[i] == type(new_element)(): + assert False list[i] = new_element return i list.append(new_element) @@ -236,11 +237,12 @@ def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge # 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.data_root != 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 + transition_chunks = (challenge.shard_transition.shard_block_lengths[challenge.data_index] + + BYTES_PER_CUSTODY_CHUNK - 1) // BYTES_PER_CUSTODY_CHUNK assert challenge.chunk_index < transition_chunks # Add new chunk challenge record new_record = CustodyChunkChallengeRecord( @@ -264,7 +266,8 @@ def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge 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) + 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 @@ -417,7 +420,8 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed shard_transition = custody_slashing.shard_transition assert hash_tree_root(shard_transition) == attestation.data.shard_transition_root # Verify that the provided data matches the shard-transition - assert custody_slashing.data.get_backing().get_left().merkle_root() == shard_transition.shard_data_roots[custody_slashing.data_index] + assert custody_slashing.data.get_backing().get_left().merkle_root() \ + == shard_transition.shard_data_roots[custody_slashing.data_index] assert len(custody_slashing.data) == shard_transition.shard_block_lengths[custody_slashing.data_index] # Verify existence and participation of claimed malefactor @@ -467,7 +471,8 @@ 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 + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD): + 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)) ``` diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 5e536002f..7f15c0048 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -78,7 +78,8 @@ def build_attestation_data(spec, state, slot, index, shard_transition_root=None) ) -def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False, shard_transition=None, valid_custody_bits=None): +def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False, shard_transition=None, + valid_custody_bits=None): shard = spec.get_shard(state, attestation) offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), state.slot + 1) @@ -88,20 +89,14 @@ def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False, attestation.data.slot, attestation.data.index, ) - current_epoch = spec.get_current_epoch(state) custody_secrets = [None for i in beacon_committee] for i in range(len(beacon_committee)): - validator = state.validators[beacon_committee[i]] - period = spec.get_custody_period_for_validator(beacon_committee[i], attestation.data.target.epoch) - epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, beacon_committee[i]) - domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch_to_sign) signing_root = spec.compute_signing_root(spec.Epoch(epoch_to_sign), domain) custody_secrets[i] = bls.Sign(privkeys[beacon_committee[i]], signing_root) - for i, offset_slot in enumerate(offset_slots): attestation.custody_bits_blocks.append( Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits]) @@ -110,7 +105,8 @@ def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False, test_vector = get_custody_test_vector(shard_transition.shard_block_lengths[i]) for j in range(len(attestation.custody_bits_blocks[i])): if attestation.aggregation_bits[j]: - attestation.custody_bits_blocks[i][j] = spec.compute_custody_bit(custody_secrets[j], test_vector) ^ (not valid_custody_bits) + attestation.custody_bits_blocks[i][j] = \ + spec.compute_custody_bit(custody_secrets[j], test_vector) ^ (not valid_custody_bits) if signed: sign_attestation(spec, state, attestation) @@ -118,7 +114,8 @@ 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, shard_transition=None, valid_custody_bits=None): +def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=False, + shard_transition=None, valid_custody_bits=None): ''' Construct on-time attestation for next slot ''' @@ -127,7 +124,9 @@ 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, shard_transition=shard_transition, valid_custody_bits=valid_custody_bits) + return get_valid_attestation(spec, state, slot=slot, index=index, + signed=signed, on_time=True, shard_transition=shard_transition, + valid_custody_bits=valid_custody_bits) def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False, shard_transition=None): @@ -139,16 +138,19 @@ 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, shard_transition=shard_transition) + return get_valid_attestation(spec, state, slot=slot, index=index, + signed=signed, on_time=False, shard_transition=shard_transition) -def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signed=False, on_time=True, shard_transition=None, valid_custody_bits=None): +def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signed=False, on_time=True, + shard_transition=None, valid_custody_bits=None): if slot is None: slot = state.slot if index is None: index = 0 - attestation_data = build_attestation_data(spec, state, slot, index, shard_transition_root=hash_tree_root(shard_transition) if shard_transition else spec.Root()) + attestation_data = build_attestation_data(spec, state, slot, index, + shard_transition_root=hash_tree_root(shard_transition) if shard_transition else spec.Root()) beacon_committee = spec.get_beacon_committee( state, @@ -168,7 +170,9 @@ def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signe sign_attestation(spec, state, attestation) if spec.fork == 'phase1' and on_time: - attestation = convert_to_valid_on_time_attestation(spec, state, attestation, signed, shard_transition=shard_transition, valid_custody_bits=valid_custody_bits) + attestation = convert_to_valid_on_time_attestation(spec, state, attestation, signed, + shard_transition=shard_transition, + valid_custody_bits=valid_custody_bits) return attestation diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index 2d937a2ee..898cf7731 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -1,10 +1,7 @@ from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls -from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, Bitvector, ByteList, uint64 -from eth2spec.utils.ssz.ssz_impl import hash_tree_root -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, Node, Gindex, gindex_bit_iter, Root +from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, ByteList +from remerkleable.tree import gindex_bit_iter BYTES_PER_CHUNK = 32 @@ -79,9 +76,8 @@ def get_valid_custody_slashing(spec, state, attestation, shard_transition, inval signing_root = spec.compute_signing_root(spec.Epoch(epoch), domain) malefactor_key = bls.Sign(privkeys[malefactor_index], signing_root) data_index = 0 - data=ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(shard_transition.shard_block_lengths[data_index])) - print(hash_tree_root(data)) - print(data.get_backing().get_left().merkle_root()) + data = ByteList[spec.MAX_SHARD_BLOCK_SIZE]( + get_custody_test_vector(shard_transition.shard_block_lengths[data_index])) slashing = spec.CustodySlashing( data_index=data_index, @@ -93,7 +89,7 @@ def get_valid_custody_slashing(spec, state, attestation, shard_transition, inval data=data, ) slashing_domain = spec.get_domain(state, spec.DOMAIN_CUSTODY_BIT_SLASHING) - slashing_root = spec.compute_signing_root(slashing, domain) + slashing_root = spec.compute_signing_root(slashing, slashing_domain) signed_slashing = spec.SignedCustodySlashing( message=slashing, @@ -103,22 +99,23 @@ def get_valid_custody_slashing(spec, state, attestation, shard_transition, inval return signed_slashing -def get_valid_chunk_challenge(spec, state, attestation, shard_transition): - shard = spec.compute_shard_from_committee_index(state, attestation.data.index, attestation.data.slot) +def get_valid_chunk_challenge(spec, state, attestation, shard_transition, data_index=None, chunk_index=None): 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 + data_index = len(shard_transition.shard_block_lengths) - 1 if not data_index else data_index - chunk_count = (shard_transition.shard_block_lengths[data_index] + spec.BYTES_PER_CUSTODY_CHUNK - 1) // spec.BYTES_PER_CUSTODY_CHUNK + chunk_count = (shard_transition.shard_block_lengths[data_index] + + spec.BYTES_PER_CUSTODY_CHUNK - 1) // spec.BYTES_PER_CUSTODY_CHUNK + chunk_index = chunk_count - 1 if not chunk_index else chunk_index return spec.CustodyChunkChallenge( responder_index=responder_index, attestation=attestation, - chunk_index=chunk_count - 1, + chunk_index=chunk_index, data_index=data_index, shard_transition=shard_transition, ) @@ -174,7 +171,8 @@ def get_custody_test_vector(bytelength): def get_shard_transition(spec, start_slot, block_lengths): - b = [ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(x)).get_backing().get_left().merkle_root() for x in block_lengths] + b = [ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(x)) + .get_backing().get_left().merkle_root() for x in block_lengths] shard_transition = spec.ShardTransition( start_slot=start_slot, shard_block_lengths=block_lengths, @@ -183,7 +181,3 @@ def get_shard_transition(spec, start_slot, block_lengths): proposer_signature_aggregate=spec.BLSSignature(), ) return shard_transition - - -def get_custody_merkle_root(data): - return None # get_merkle_tree(chunkify(data))[-1][0] diff --git a/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py index 061553ef9..b22a3ea69 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py @@ -1,3 +1,5 @@ +# TODO: What is this file for??? It seems to be broken! +# The phase0 attestations file already adds the custody bit blocks from eth2spec.utils.ssz.ssz_typing import Bitlist from eth2spec.utils import bls @@ -12,16 +14,14 @@ def get_valid_on_time_attestation(spec, state, index=None, signed=False, shard_t if index is None: index = 0 - attestation = phase0_attestations.get_valid_attestation(spec, state, state.slot, index, False, shard_transition_root=shard_transition_root) + attestation = phase0_attestations.get_valid_attestation(spec, state, state.slot, index, False) 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 index d409b468f..c71785518 100644 --- 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 @@ -1,15 +1,12 @@ 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, @@ -30,7 +27,7 @@ def run_chunk_challenge_processing(spec, state, custody_chunk_challenge, valid=T yield 'custody_chunk_challenge', custody_chunk_challenge if not valid: - expect_assertion_error(lambda: spec.custody_chunk_challenge(state, custody_chunk_challenge)) + expect_assertion_error(lambda: spec.process_chunk_challenge(state, custody_chunk_challenge)) yield 'post', None return @@ -74,12 +71,8 @@ def test_challenge_appended(spec, state): 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=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) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -92,6 +85,54 @@ def test_challenge_appended(spec, state): yield from run_chunk_challenge_processing(spec, state, challenge) +@with_all_phases_except(['phase0']) +@spec_state_test +def test_duplicate_challenge(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)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=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) + + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) + + _, _, _ = run_chunk_challenge_processing(spec, state, challenge) + + yield from run_chunk_challenge_processing(spec, state, challenge, valid=False) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_second_challenge(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)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=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) + + challenge0 = get_valid_chunk_challenge(spec, state, attestation, shard_transition, chunk_index=0) + + _, _, _ = run_chunk_challenge_processing(spec, state, challenge0) + + challenge1 = get_valid_chunk_challenge(spec, state, attestation, shard_transition, chunk_index=1) + + yield from run_chunk_challenge_processing(spec, state, challenge1) + + @with_all_phases_except(['phase0']) @spec_state_test def test_multiple_epochs_custody(spec, state): @@ -100,8 +141,8 @@ def test_multiple_epochs_custody(spec, state): 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=shard_transition) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -122,8 +163,8 @@ def test_many_epochs_custody(spec, state): 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=shard_transition) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -144,8 +185,8 @@ def test_off_chain_attestation(spec, state): 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=shard_transition) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) @@ -162,8 +203,8 @@ def test_custody_response(spec, state): 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=shard_transition) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -190,8 +231,8 @@ def test_custody_response_multiple_epochs(spec, state): 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=shard_transition) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -218,8 +259,8 @@ def test_custody_response_many_epochs(spec, state): 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=shard_transition) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) 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 index 706fd8f49..087e619f5 100644 --- 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 @@ -1,16 +1,12 @@ from eth2spec.test.helpers.custody import ( get_valid_custody_slashing, - get_custody_test_vector, - get_custody_merkle_root, get_shard_transition, ) from eth2spec.test.helpers.attestations import ( get_valid_on_time_attestation, ) -from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.utils.ssz.ssz_typing import ByteList -from eth2spec.test.helpers.state import next_epoch, get_balance, transition_to -from eth2spec.test.helpers.block import apply_empty_block +from eth2spec.test.helpers.state import get_balance, transition_to from eth2spec.test.context import ( with_all_phases_except, spec_state_test, @@ -48,7 +44,7 @@ def run_custody_slashing_processing(spec, state, custody_slashing, valid=True, c else: slashed_validator = state.validators[custody_slashing.message.whistleblower_index] assert get_balance(state, custody_slashing.message.whistleblower_index) < pre_slashed_balance - + assert slashed_validator.slashed assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH @@ -63,8 +59,8 @@ def test_custody_slashing(spec, state): 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=shard_transition, valid_custody_bits=False) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition, valid_custody_bits=False) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -72,8 +68,6 @@ def test_custody_slashing(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - data_index = 0 - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @@ -86,8 +80,8 @@ def test_incorrect_custody_slashing(spec, state): 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=shard_transition, valid_custody_bits=True) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition, valid_custody_bits=True) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -95,8 +89,6 @@ def test_incorrect_custody_slashing(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - data_index = 0 - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) yield from run_custody_slashing_processing(spec, state, slashing, correct=False) @@ -109,8 +101,8 @@ def test_multiple_epochs_custody(spec, state): 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=shard_transition, valid_custody_bits=False) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition, valid_custody_bits=False) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -118,8 +110,6 @@ def test_multiple_epochs_custody(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - data_index = 0 - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @@ -128,12 +118,12 @@ def test_multiple_epochs_custody(spec, state): @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) + 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=shard_transition, valid_custody_bits=False) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition, valid_custody_bits=False) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -141,8 +131,6 @@ def test_many_epochs_custody(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - data_index = 0 - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @@ -155,13 +143,11 @@ def test_off_chain_attestation(spec, state): 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=shard_transition, valid_custody_bits=False) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition, valid_custody_bits=False) transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - data_index = 0 - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @@ -174,8 +160,8 @@ def test_invalid_custody_slashing(spec, state): 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=shard_transition, valid_custody_bits=False) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition, valid_custody_bits=False) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -183,8 +169,6 @@ def test_invalid_custody_slashing(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - data_index = 0 - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) slashing.message.data = ByteList[spec.MAX_SHARD_BLOCK_SIZE]() From b82496fd11a0d2e8dd7a65700889e365beace13b Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 1 May 2020 00:19:25 +0100 Subject: [PATCH 10/45] Rename file --- ...pdates.py => test_process_custody_final_updates.py} | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) rename tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/{test_process_final_custody_updates.py => test_process_custody_final_updates.py} (96%) 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_custody_final_updates.py similarity index 96% rename from tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_final_custody_updates.py rename to tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_custody_final_updates.py index 566d795cf..396414f2a 100644 --- 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_custody_final_updates.py @@ -2,8 +2,6 @@ from eth2spec.test.helpers.custody import ( get_valid_chunk_challenge, get_valid_custody_chunk_response, get_valid_custody_key_reveal, - get_custody_test_vector, - get_custody_merkle_root, get_shard_transition ) from eth2spec.test.helpers.attestations import ( @@ -72,8 +70,8 @@ def test_validator_withdrawal_suspend_after_chunk_challenge(spec, state): 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=shard_transition) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -122,8 +120,8 @@ def test_validator_withdrawal_resume_after_chunk_challenge_response(spec, state) 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=shard_transition) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) From 964bf42335e9880fcc8eb90056ad53b387e316f3 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 1 May 2020 00:32:02 +0100 Subject: [PATCH 11/45] Fix type --- specs/phase1/custody-game.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 9f5a8909b..c81c6a17b 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -285,7 +285,7 @@ def process_chunk_challenge_response(state: BeaconState, 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) + increase_balance(state, proposer_index, Gwei(get_base_reward(state, proposer_index) // MINOR_REWARD_QUOTIENT)) ``` #### Custody key reveals From f4334d1522bb6f3a34fdb0fe612cf4b7c7b0b760 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 5 May 2020 15:28:12 +0800 Subject: [PATCH 12/45] Delete outdated helper --- .../test/helpers/phase1/attestations.py | 65 ------------------- 1 file changed, 65 deletions(-) delete mode 100644 tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py diff --git a/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py deleted file mode 100644 index b22a3ea69..000000000 --- a/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py +++ /dev/null @@ -1,65 +0,0 @@ -# TODO: What is this file for??? It seems to be broken! -# The phase0 attestations file already adds the custody bit blocks -from eth2spec.utils.ssz.ssz_typing import Bitlist -from eth2spec.utils import bls - -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, 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) - shard = spec.get_shard(state, attestation) - offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), state.slot + 1) - - for _ 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 sign_attestation(spec, state, attestation): - if not any(attestation.custody_bits_blocks): - phase0_attestations.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) From 3851a26a0fa1e8c860956ba4da893608a3126752 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 19 May 2020 07:33:07 -0600 Subject: [PATCH 13/45] add phase 1 custody objects to custody-game.md --- specs/phase1/beacon-chain.md | 99 +----------------------------------- specs/phase1/custody-game.md | 92 +++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 98 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 278efda28..27a9229dd 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -12,14 +12,6 @@ - [Custom types](#custom-types) - [Configuration](#configuration) - [Misc](#misc) -- [New containers](#new-containers) - - [`CustodyChunkChallenge`](#custodychunkchallenge) - - [`CustodyChunkChallengeRecord`](#custodychunkchallengerecord) - - [`CustodyChunkResponse`](#custodychunkresponse) - - [`CustodySlashing`](#custodyslashing) - - [`SignedCustodySlashing`](#signedcustodyslashing) - - [`CustodyKeyReveal`](#custodykeyreveal) - - [`EarlyDerivedSecretReveal`](#earlyderivedsecretreveal) - [Updated containers](#updated-containers) - [Extended `AttestationData`](#extended-attestationdata) - [Extended `Attestation`](#extended-attestation) @@ -31,7 +23,7 @@ - [Extended `BeaconBlock`](#extended-beaconblock) - [Extended `SignedBeaconBlock`](#extended-signedbeaconblock) - [Extended `BeaconState`](#extended-beaconstate) -- [New containers](#new-containers-1) +- [New containers](#new-containers) - [`ShardBlock`](#shardblock) - [`SignedShardBlock`](#signedshardblock) - [`ShardBlockHeader`](#shardblockheader) @@ -124,95 +116,6 @@ Configuration is not namespaced. Instead it is strictly an extension; | `BYTES_PER_CUSTODY_CHUNK` | `2**12` | bytes | | `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 - chunk_index: uint64 -``` - -#### `CustodyChunkResponse` - -```python -class CustodyChunkResponse(Container): - challenge_index: uint64 - chunk_index: uint64 - chunk: ByteVector[BYTES_PER_CUSTODY_CHUNK] - branch: Vector[Root, 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 The following containers have updated definitions in Phase 1. diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index c81c6a17b..96a7f5616 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -18,6 +18,13 @@ - [Signature domain types](#signature-domain-types) - [Data structures](#data-structures) - [New Beacon Chain operations](#new-beacon-chain-operations) + - [`CustodyChunkChallenge`](#custodychunkchallenge) + - [`CustodyChunkChallengeRecord`](#custodychunkchallengerecord) + - [`CustodyChunkResponse`](#custodychunkresponse) + - [`CustodySlashing`](#custodyslashing) + - [`SignedCustodySlashing`](#signedcustodyslashing) + - [`CustodyKeyReveal`](#custodykeyreveal) + - [`EarlyDerivedSecretReveal`](#earlyderivedsecretreveal) - [Helpers](#helpers) - [`replace_empty_or_append`](#replace_empty_or_append) - [`legendre_bit`](#legendre_bit) @@ -95,6 +102,91 @@ The following types are defined, mapping into `DomainType` (little endian): ### New Beacon Chain operations +#### `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 + chunk_index: uint64 +``` + +#### `CustodyChunkResponse` + +```python +class CustodyChunkResponse(Container): + challenge_index: uint64 + chunk_index: uint64 + chunk: ByteVector[BYTES_PER_CUSTODY_CHUNK] + branch: Vector[Root, 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 +``` + + ## Helpers ### `replace_empty_or_append` From 3f0e58a8ed52253d84af70a1e041d64648dbe19b Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 19 May 2020 07:50:05 -0600 Subject: [PATCH 14/45] add chunk challenge and response to block and operations --- configs/mainnet.yaml | 2 ++ configs/minimal.yaml | 2 ++ specs/phase1/beacon-chain.md | 4 +++- specs/phase1/custody-game.md | 7 +++---- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index f5f38de5e..6475d491e 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -214,6 +214,8 @@ MAX_REVEAL_LATENESS_DECREMENT: 128 # 2**8 (= 256) MAX_CUSTODY_KEY_REVEALS: 256 MAX_EARLY_DERIVED_SECRET_REVEALS: 1 +MAX_CUSTODY_CHUNK_CHALLENGES: 4 +MAX_CUSTODY_CHUNK_CHALLENGE_RESP: 16 MAX_CUSTODY_SLASHINGS: 1 # Reward and penalty quotients diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 1b5aac5f6..89e2a7eae 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -215,6 +215,8 @@ CUSTODY_PERIOD_TO_RANDAO_PADDING: 8 # 2**8 (= 256) MAX_CUSTODY_KEY_REVEALS: 256 MAX_EARLY_DERIVED_SECRET_REVEALS: 1 +MAX_CUSTODY_CHUNK_CHALLENGES: 2 +MAX_CUSTODY_CHUNK_CHALLENGE_RESP: 8 MAX_CUSTODY_SLASHINGS: 1 # Reward and penalty quotients diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 27a9229dd..e48c99c4a 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -215,9 +215,11 @@ class BeaconBlockBody(Container): deposits: List[Deposit, MAX_DEPOSITS] voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] # Custody game - custody_slashings: List[SignedCustodySlashing, MAX_CUSTODY_SLASHINGS] + chunk_challenges: List[CustodyChunkResponse, MAX_CUSTODY_CHUNK_CHALLENGES] + chunk_challenge_responses: List[CustodyChunkResponse, MAX_CUSTODY_CHUNK_CHALLENGE_RESPONSES] custody_key_reveals: List[CustodyKeyReveal, MAX_CUSTODY_KEY_REVEALS] early_derived_secret_reveals: List[EarlyDerivedSecretReveal, MAX_EARLY_DERIVED_SECRET_REVEALS] + custody_slashings: List[SignedCustodySlashing, MAX_CUSTODY_SLASHINGS] # Shards shard_transitions: Vector[ShardTransition, MAX_SHARDS] # Light clients diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 96a7f5616..08893ef53 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -81,6 +81,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_CHUNK_CHALLENGE_RESPONSES` | `2**4` (= 16) | | `MAX_CUSTODY_SLASHINGS` | `1` | ### Reward and penalty quotients @@ -297,6 +298,8 @@ def process_custody_game_operations(state: BeaconState, body: BeaconBlockBody) - for operation in operations: fn(state, operation) + for_ops(body.chunk_challenges, process_chunk_challenge) + for_ops(body.chunk_challenge_responses, process_chunk_challenge) for_ops(body.custody_key_reveals, process_custody_key_reveal) for_ops(body.early_derived_secret_reveals, process_early_derived_secret_reveal) for_ops(body.custody_slashings, process_custody_slashing) @@ -304,10 +307,6 @@ def process_custody_game_operations(state: BeaconState, body: BeaconBlockBody) - #### 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 From 7fc9dbf297e4900aa4345443722e3c184790a557 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 19 May 2020 07:52:26 -0600 Subject: [PATCH 15/45] clarify comment for ShardTransition.shard_data_roots --- specs/phase1/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index e48c99c4a..0f5e26a8c 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -352,7 +352,7 @@ class ShardTransition(Container): # Shard block lengths shard_block_lengths: List[uint64, MAX_SHARD_BLOCKS_PER_ATTESTATION] # Shard data roots - # The root is of ByteVector[MAX_] + # The root is of ByteList[MAX_SHARD_BLOCK_SIZE] shard_data_roots: List[Bytes32, MAX_SHARD_BLOCKS_PER_ATTESTATION] # Intermediate shard states shard_states: List[ShardState, MAX_SHARD_BLOCKS_PER_ATTESTATION] From 1623086088e6f0496566ab7d50d16a8c78cdebf0 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 19 May 2020 08:14:04 -0600 Subject: [PATCH 16/45] make get_validator_from_deposit for better code reuse across phase 0 and 1 --- specs/phase0/beacon-chain.md | 26 +++++++++----- specs/phase1/beacon-chain.md | 66 ++++++++++-------------------------- 2 files changed, 35 insertions(+), 57 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 3ef4081ce..ada599c9d 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1632,6 +1632,22 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: ##### Deposits +```python +def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: + amount = deposit.data.amount + effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) + + return Validator( + pubkey=deposit.data.pubkey, + withdrawal_credentials=deposit.data.withdrawal_credentials, + activation_eligibility_epoch=FAR_FUTURE_EPOCH, + activation_epoch=FAR_FUTURE_EPOCH, + exit_epoch=FAR_FUTURE_EPOCH, + withdrawable_epoch=FAR_FUTURE_EPOCH, + effective_balance=effective_balance, + ) +``` + ```python def process_deposit(state: BeaconState, deposit: Deposit) -> None: # Verify the Merkle branch @@ -1662,15 +1678,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: return # Add validator and balance entries - state.validators.append(Validator( - pubkey=pubkey, - withdrawal_credentials=deposit.data.withdrawal_credentials, - activation_eligibility_epoch=FAR_FUTURE_EPOCH, - activation_epoch=FAR_FUTURE_EPOCH, - exit_epoch=FAR_FUTURE_EPOCH, - withdrawable_epoch=FAR_FUTURE_EPOCH, - effective_balance=min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE), - )) + state.validators.append(get_validator_from_deposit(state, deposit)) state.balances.append(amount) else: # Increase balance by deposit amount diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 0f5e26a8c..32e9c925b 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -883,58 +883,28 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: state.previous_epoch_attestations.append(pending_attestation) ``` -##### New deposits +##### New default validator for deposits ```python -def process_deposit(state: BeaconState, deposit: Deposit) -> None: - # Verify the Merkle branch - assert is_valid_merkle_branch( - leaf=hash_tree_root(deposit.data), - branch=deposit.proof, - depth=DEPOSIT_CONTRACT_TREE_DEPTH + 1, # Add 1 for the List length mix-in - index=state.eth1_deposit_index, - root=state.eth1_data.deposit_root, +def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: + amount = deposit.data.amount + effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) + next_custody_secret_to_reveal = get_custody_period_for_validator( + ValidatorIndex(len(state.validators)), + get_current_epoch(state), ) - # Deposits must be processed in order - state.eth1_deposit_index += 1 - - pubkey = deposit.data.pubkey - amount = deposit.data.amount - validator_pubkeys = [v.pubkey for v in state.validators] - if pubkey not in validator_pubkeys: - # Verify the deposit signature (proof of possession) which is not checked by the deposit contract - deposit_message = DepositMessage( - pubkey=deposit.data.pubkey, - withdrawal_credentials=deposit.data.withdrawal_credentials, - amount=deposit.data.amount, - ) - domain = compute_domain(DOMAIN_DEPOSIT) # Fork-agnostic domain since deposits are valid across forks - signing_root = compute_signing_root(deposit_message, domain) - if not bls.Verify(pubkey, signing_root, deposit.data.signature): - return - - # Add validator and balance entries - # TODO: This function is duplicated from phase 1 just because the validator definition - # has changed and we need to initialize it properly. Is there a better solution for - # this? - state.validators.append(Validator( - pubkey=pubkey, - withdrawal_credentials=deposit.data.withdrawal_credentials, - activation_eligibility_epoch=FAR_FUTURE_EPOCH, - activation_epoch=FAR_FUTURE_EPOCH, - exit_epoch=FAR_FUTURE_EPOCH, - withdrawable_epoch=FAR_FUTURE_EPOCH, - effective_balance=min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE), - next_custody_secret_to_reveal=get_custody_period_for_validator(ValidatorIndex(len(state.validators)), - get_current_epoch(state)), - all_custody_secrets_revealed_epoch=FAR_FUTURE_EPOCH, - )) - state.balances.append(amount) - else: - # Increase balance by deposit amount - index = ValidatorIndex(validator_pubkeys.index(pubkey)) - increase_balance(state, index, amount) + return Validator( + pubkey=deposit.data.pubkey, + withdrawal_credentials=deposit.data.withdrawal_credentials, + activation_eligibility_epoch=FAR_FUTURE_EPOCH, + activation_epoch=FAR_FUTURE_EPOCH, + exit_epoch=FAR_FUTURE_EPOCH, + withdrawable_epoch=FAR_FUTURE_EPOCH, + effective_balance=effective_balance, + next_custody_secret_to_reveal=next_custody_secret_to_reveal, + all_custody_secrets_revealed_epoch=FAR_FUTURE_EPOCH, + ) ``` ##### New Attester slashing processing From 78947548e6b5e31e7601f3519fa801515c4e84a0 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 19 May 2020 08:31:01 -0600 Subject: [PATCH 17/45] add process_challenge_deadlines to process_epoch --- specs/phase1/beacon-chain.md | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 32e9c925b..9ca4b1e39 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -995,6 +995,7 @@ def process_epoch(state: BeaconState) -> None: process_rewards_and_penalties(state) process_registry_updates(state) process_reveal_deadlines(state) + process_challenge_deadlines(state) process_slashings(state) process_final_updates(state) process_custody_final_updates(state) From de03ebb143136caa1a01e2ba8b465aa2c6e224b1 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 19 May 2020 08:31:16 -0600 Subject: [PATCH 18/45] many custody game formatting cleanups --- specs/phase1/beacon-chain.md | 2 +- specs/phase1/custody-game.md | 67 ++++++++++++++++-------------------- 2 files changed, 31 insertions(+), 38 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 9ca4b1e39..d53113038 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -63,7 +63,7 @@ - [`process_crosslink_for_shard`](#process_crosslink_for_shard) - [`process_crosslinks`](#process_crosslinks) - [`process_attestation`](#process_attestation) - - [New deposits](#new-deposits) + - [New default validator for deposits](#new-default-validator-for-deposits) - [New Attester slashing processing](#new-attester-slashing-processing) - [Shard transition false positives](#shard-transition-false-positives) - [Light client processing](#light-client-processing) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 08893ef53..f4f45ce1e 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -71,7 +71,7 @@ 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 | | `CHUNK_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days | -| `MAX_CHUNK_CHALLENGE_DELAY` | `2**11` (= 16,384) | epochs | ~9 days | +| `MAX_CHUNK_CHALLENGE_DELAY` | `2**11` (= 2,048) | epochs | ~9 days | | `CUSTODY_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days | ### Max operations per block @@ -311,12 +311,13 @@ def process_custody_game_operations(state: BeaconState, body: BeaconBlockBody) - 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)) + # Verify it is not too late to challenge the attestation + max_attestation_challenge_epoch = challenge.attestation.data.target.epoch + MAX_CHUNK_CHALLENGE_DELAY + assert get_current_epoch(state) <= max_attestation_challenge_epoch + # Verify it is not too late to challenge the responder 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)) + if responder.exit_epoch < FAR_FUTURE_EPOCH: + assert get_current_epoch(state) <= responder.exit_epoch + MAX_CHUNK_CHALLENGE_DELAY # Verify responder is slashable assert is_slashable_validator(responder, get_current_epoch(state)) # Verify the responder participated in the attestation @@ -332,8 +333,8 @@ def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge 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 + shard_block_length = challenge.shard_transition.shard_block_lengths[challenge.data_index] + transition_chunks = (shard_block_length + BYTES_PER_CUSTODY_CHUNK - 1) // BYTES_PER_CUSTODY_CHUNK assert challenge.chunk_index < transition_chunks # Add new chunk challenge record new_record = CustodyChunkChallengeRecord( @@ -357,10 +358,13 @@ def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge 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) - + # Get matching challenge (if any) from records + matching_challenges = [ + record for record in state.custody_chunk_challenge_records + if record.challenge_index == response.challenge_index + ] + assert len(matching_challenges) > 0 + challenge = matching_challenges[0] # Verify chunk index assert response.chunk_index == challenge.chunk_index # Verify the chunk matches the crosslink data root @@ -391,12 +395,14 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> epoch_to_sign = get_randao_epoch_for_custody_period(revealer.next_custody_secret_to_reveal, reveal.revealer_index) custody_reveal_period = get_custody_period_for_validator(reveal.revealer_index, get_current_epoch(state)) - # Only past custody periods can be revealed, except after exiting the exit - # period can be revealed - assert (revealer.next_custody_secret_to_reveal < custody_reveal_period - or (revealer.exit_epoch <= get_current_epoch(state) and - revealer.next_custody_secret_to_reveal - <= get_custody_period_for_validator(reveal.revealer_index, revealer.exit_epoch - 1))) + # Only past custody periods can be revealed, except after exiting the exit period can be revealed + is_past_reveal = revealer.next_custody_secret_to_reveal < custody_reveal_period + is_exited = revealer.exit_epoch <= get_current_epoch(state) + is_exit_period_reveal = ( + revealer.next_custody_secret_to_reveal + == get_custody_period_for_validator(reveal.revealer_index, revealer.exit_epoch - 1) + ) + assert is_past_reveal or (is_exited and is_exit_period_reveal) # Revealed validator is active or exited, but not withdrawn assert is_slashable_validator(revealer, get_current_epoch(state)) @@ -407,9 +413,7 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> assert bls.Verify(revealer.pubkey, signing_root, reveal.reveal) # Process reveal - if (revealer.exit_epoch <= get_current_epoch(state) and - revealer.next_custody_secret_to_reveal - == get_custody_period_for_validator(reveal.revealer_index, revealer.exit_epoch - 1)): + if is_exited and is_exit_period_reveal: revealer.all_custody_secrets_revealed_epoch = get_current_epoch(state) revealer.next_custody_secret_to_reveal += 1 @@ -556,23 +560,16 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed ### Handling of reveal deadlines -Run `process_reveal_deadlines(state)` after `process_registry_updates(state)`: - ```python 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 \ - + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD): + deadline = validator.next_custody_secret_to_reveal + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD) + if get_custody_period_for_validator(ValidatorIndex(index), epoch) > deadline: slash_validator(state, ValidatorIndex(index)) ``` -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: @@ -583,8 +580,6 @@ def process_challenge_deadlines(state: BeaconState) -> None: ### Final updates -After `process_final_updates(state)`, additional updates are made for the custody game: - ```python def process_custody_final_updates(state: BeaconState) -> None: # Clean up exposed RANDAO key reveals @@ -592,13 +587,11 @@ def process_custody_final_updates(state: BeaconState) -> None: # Reset withdrawable epochs if challenge records are empty records = state.custody_chunk_challenge_records - validator_indices_in_records = set( - [record.responder_index for record in records] - ) + validator_indices_in_records = set([record.responder_index for record in records]) for index, validator in enumerate(state.validators): if validator.exit_epoch != FAR_FUTURE_EPOCH: - if (index in validator_indices_in_records - or validator.all_custody_secrets_revealed_epoch == FAR_FUTURE_EPOCH): + all_secrets_are_revealed = validator.all_custody_secrets_revealed_epoch == FAR_FUTURE_EPOCH + if index in validator_indices_in_records or all_secrets_are_revealed: # Delay withdrawable epochs if challenge records are not empty or not all # custody secrets revealed validator.withdrawable_epoch = FAR_FUTURE_EPOCH From e46d5effe474e77b3cbc40b43121ee41893d17e1 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Wed, 10 Jun 2020 17:20:42 +0100 Subject: [PATCH 19/45] Add test for slashing after failing to respond to custody chunk challenge --- configs/mainnet.yaml | 2 + configs/minimal.yaml | 2 + specs/phase1/beacon-chain.md | 2 +- .../test_process_challenge_deadlines.py | 76 +++++++++++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 6ba8085a5..2e15e84fd 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -207,6 +207,8 @@ EPOCHS_PER_CUSTODY_PERIOD: 2048 CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048 # 2**7 (= 128) epochs, ~14 hours MAX_REVEAL_LATENESS_DECREMENT: 128 +# 2**14 (= 16,384) epochs +CUSTODY_RESPONSE_DEADLINE: 16384 # Max operations # 2**8 (= 256) diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 87509a442..d99aa164a 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -208,6 +208,8 @@ EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096 EPOCHS_PER_CUSTODY_PERIOD: 8 # 2**11 (= 2,048) epochs CUSTODY_PERIOD_TO_RANDAO_PADDING: 8 +# 2**14 (= 16,384) epochs +CUSTODY_RESPONSE_DEADLINE: 32 # Max operations # 2**8 (= 256) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 9d55cde3e..e9ba4d88d 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -1137,7 +1137,7 @@ def process_phase_1_final_updates(state: BeaconState) -> None: #### Custody game updates -`process_reveal_deadlines` and `process_custody_final_updates` are defined in [the Custody Game spec](./custody-game.md), +`process_reveal_deadlines`, `process_challenge_deadlines` and `process_custody_final_updates` are defined in [the Custody Game spec](./custody-game.md), #### Online-tracking diff --git a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py new file mode 100644 index 000000000..2e60d167c --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py @@ -0,0 +1,76 @@ +from eth2spec.test.helpers.custody import ( + get_valid_chunk_challenge, + get_shard_transition, + get_valid_custody_key_reveal, +) +from eth2spec.test.helpers.attestations import ( + get_valid_on_time_attestation, +) +from eth2spec.test.helpers.state import next_epoch_via_block, transition_to +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_chunk_challenge import ( + run_chunk_challenge_processing, +) +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_chunk_challenge(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)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) + + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + validator_index = spec.get_beacon_committee( + state, + attestation.data.slot, + attestation.data.index + )[0] + + spec.initiate_validator_exit(state, validator_index) + assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) + + 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_via_block(spec, state) + while (state.validators[validator_index].next_custody_secret_to_reveal + <= spec.get_custody_period_for_validator( + 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_via_block(spec, state) + + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) + + _, _, _ = run_chunk_challenge_processing(spec, state, challenge) + + assert state.validators[validator_index].slashed == 0 + + transition_to(spec, state, state.slot + (spec.CUSTODY_RESPONSE_DEADLINE + + spec.EPOCHS_PER_CUSTODY_PERIOD) * spec.SLOTS_PER_EPOCH) + + yield from run_process_challenge_deadlines(spec, state) + + assert state.validators[validator_index].slashed == 1 From bcfaa1b635954dc9cdf47d02a73aa1b5dbb38eb9 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 12 Jun 2020 11:07:44 +0100 Subject: [PATCH 20/45] Fix tests --- Makefile | 2 +- .../test_process_chunk_challenge.py | 4 ++-- .../test_process_custody_key_reveal.py | 14 -------------- .../test_process_custody_slashing.py | 2 +- .../test_process_reveal_deadlines.py | 15 +++++++++------ 5 files changed, 13 insertions(+), 24 deletions(-) diff --git a/Makefile b/Makefile index 148daaa48..419500ebb 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,7 @@ codespell: lint: pyspec . venv/bin/activate; cd $(PY_SPEC_DIR); \ - flake8 --ignore=E252,W504,W503,E128 --max-line-length=120 ./eth2spec \ + flake8 --ignore=E252,W504,W503,E128,C901 --max-line-length=120 ./eth2spec \ && cd ./eth2spec && mypy --follow-imports=silent --warn-unused-ignores --ignore-missing-imports --check-untyped-defs --disallow-incomplete-defs --disallow-untyped-defs -p phase0 \ && mypy --follow-imports=silent --warn-unused-ignores --ignore-missing-imports --check-untyped-defs --disallow-incomplete-defs --disallow-untyped-defs -p phase1; 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 index c71785518..97fe0c222 100644 --- 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 @@ -158,7 +158,7 @@ def test_multiple_epochs_custody(spec, state): @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) + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 20) shard = 0 offset_slots = spec.get_offset_slots(state, shard) @@ -254,7 +254,7 @@ def test_custody_response_multiple_epochs(spec, state): @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) + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 20) shard = 0 offset_slots = spec.get_offset_slots(state, shard) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py index fd16a344c..cb96c97e1 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py @@ -87,17 +87,3 @@ def test_double_reveal(spec, state): _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal, False) - - -@with_all_phases_except([PHASE0]) -@spec_state_test -@always_bls -def test_max_decrement(spec, state): - state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH * 3 + 150 - custody_key_reveal = get_valid_custody_key_reveal(spec, state) - - _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) - - custody_key_reveal2 = get_valid_custody_key_reveal(spec, state) - - yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal2) 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 index 087e619f5..997e5ac92 100644 --- 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 @@ -118,7 +118,7 @@ def test_multiple_epochs_custody(spec, state): @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) + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 20) shard = 0 offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) 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 index 688b046c8..31d45aa41 100644 --- 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 @@ -1,7 +1,7 @@ from eth2spec.test.helpers.custody import ( get_valid_custody_key_reveal, ) -from eth2spec.test.helpers.state import next_epoch +from eth2spec.test.helpers.state import transition_to from eth2spec.test.context import ( with_all_phases_except, spec_state_test, @@ -19,9 +19,12 @@ def run_process_challenge_deadlines(spec, state): 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) + transition_to(spec, state, spec.get_randao_epoch_for_custody_period(0, 0) * spec.SLOTS_PER_EPOCH) + + transition_to(spec, state, state.slot + ((spec.CUSTODY_RESPONSE_DEADLINE) + * spec.SLOTS_PER_EPOCH)) + + state.validators[0].slashed = 0 yield from run_process_challenge_deadlines(spec, state) @@ -38,8 +41,8 @@ def test_validator_not_slashed_after_reveal(spec, state): assert state.validators[0].slashed == 0 - state.slot += spec.CHUNK_RESPONSE_DEADLINE * spec.SLOTS_PER_EPOCH - next_epoch(spec, state) + transition_to(spec, state, state.slot + ((spec.CUSTODY_RESPONSE_DEADLINE) + * spec.SLOTS_PER_EPOCH)) yield from run_process_challenge_deadlines(spec, state) From 7c6280aa8ef716262fc93a6fd4ba4bfb47a45636 Mon Sep 17 00:00:00 2001 From: dankrad Date: Fri, 12 Jun 2020 11:16:19 +0100 Subject: [PATCH 21/45] Update specs/phase1/custody-game.md Co-authored-by: Hsiao-Wei Wang --- specs/phase1/custody-game.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index d05476af3..3875f28e5 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -264,8 +264,12 @@ def compute_custody_bit(key: BLSSignature, data: ByteList[MAX_SHARD_BLOCK_SIZE]) secrets = get_custody_secrets(key) custody_atoms = get_custody_atoms(data) n = len(custody_atoms) - uhf = (sum(secrets[i % CUSTODY_SECRETS]**i * int.from_bytes(atom, "little") % CUSTODY_PRIME - for i, atom in enumerate(custody_atoms)) + secrets[n % CUSTODY_SECRETS]**n) % CUSTODY_PRIME + uhf = ( + sum( + secrets[i % CUSTODY_SECRETS]**i * int.from_bytes(atom, "little") % CUSTODY_PRIME + for i, atom in enumerate(custody_atoms) + ) + secrets[n % CUSTODY_SECRETS]**n + ) % CUSTODY_PRIME return legendre_bit(uhf + secrets[0], CUSTODY_PRIME) ``` From 31654d8bf459b0af62e1766129f9843a41d07149 Mon Sep 17 00:00:00 2001 From: dankrad Date: Fri, 12 Jun 2020 11:17:44 +0100 Subject: [PATCH 22/45] Update specs/phase1/custody-game.md Co-authored-by: Hsiao-Wei Wang --- specs/phase1/custody-game.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 3875f28e5..84780846b 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -252,7 +252,7 @@ def get_custody_secrets(key: BLSSignature) -> Sequence[int]: full_G2_element = bls.signature_to_G2(key) signature = full_G2_element[0].coeffs signature_bytes = b"".join(x.to_bytes(48, "little") for x in signature) - secrets = [int.from_bytes(signature_bytes[i:i + BYTES_PER_CUSTODY_ATOM], "little") + secrets = [int.from_bytes(signature_bytes[i:i + BYTES_PER_CUSTODY_ATOM], "little") for i in range(0, len(signature_bytes), 32)] return secrets ``` From d41b6a5775b42fb56588acb2cfda77c2ba34cafe Mon Sep 17 00:00:00 2001 From: dankrad Date: Fri, 12 Jun 2020 11:18:07 +0100 Subject: [PATCH 23/45] Update specs/phase1/custody-game.md Co-authored-by: Hsiao-Wei Wang --- specs/phase1/custody-game.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 84780846b..24d64b0ee 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -380,8 +380,8 @@ def process_chunk_challenge_response(state: BeaconState, root=challenge.data_root, ) # Clear the challenge - records = state.custody_chunk_challenge_records - records[records.index(challenge)] = CustodyChunkChallengeRecord() + index_in_records = state.custody_chunk_challenge_records.index(challenge) + state.custody_chunk_challenge_records[index_in_records] = CustodyChunkChallengeRecord() # Reward the proposer proposer_index = get_beacon_proposer_index(state) increase_balance(state, proposer_index, Gwei(get_base_reward(state, proposer_index) // MINOR_REWARD_QUOTIENT)) From 0e8bba2ce326ba15468d9d192fb727cac020d8db Mon Sep 17 00:00:00 2001 From: dankrad Date: Fri, 12 Jun 2020 11:28:30 +0100 Subject: [PATCH 24/45] Update specs/phase1/custody-game.md Co-authored-by: Hsiao-Wei Wang --- specs/phase1/custody-game.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 24d64b0ee..5ee38be8b 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -519,8 +519,10 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed shard_transition = custody_slashing.shard_transition assert hash_tree_root(shard_transition) == attestation.data.shard_transition_root # Verify that the provided data matches the shard-transition - assert custody_slashing.data.get_backing().get_left().merkle_root() \ + assert ( + custody_slashing.data.get_backing().get_left().merkle_root() == shard_transition.shard_data_roots[custody_slashing.data_index] + ) assert len(custody_slashing.data) == shard_transition.shard_block_lengths[custody_slashing.data_index] # Verify existence and participation of claimed malefactor From 7bf491d49dff596f66a5eaea395f3f85bb22a26f Mon Sep 17 00:00:00 2001 From: dankrad Date: Fri, 12 Jun 2020 11:28:49 +0100 Subject: [PATCH 25/45] Update specs/phase1/custody-game.md Co-authored-by: Hsiao-Wei Wang --- specs/phase1/custody-game.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 5ee38be8b..d4ce4640a 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -580,8 +580,8 @@ 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() + index_in_records = records.index(custody_chunk_challenge) + state.custody_chunk_challenge_records[index_in_records] = CustodyChunkChallengeRecord() ``` ### Final updates From 65c3417f90229c136f797a4592db3d56c7841197 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 12 Jun 2020 11:53:00 +0100 Subject: [PATCH 26/45] Fix replace_empty_or_append, remove assert False & test --- specs/phase1/custody-game.md | 13 +++++------ .../test_process_chunk_challenge.py | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index d4ce4640a..8185b6baa 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -193,14 +193,13 @@ class EarlyDerivedSecretReveal(Container): ### `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] == type(new_element)(): - assert False - list[i] = new_element +def replace_empty_or_append(l: List, new_element: Any) -> int: + for i in range(len(l)): + if l[i] == type(new_element)(): + l[i] = new_element return i - list.append(new_element) - return len(list) - 1 + l.append(new_element) + return len(l) - 1 ``` ### `legendre_bit` 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 index 97fe0c222..94b1c3b11 100644 --- 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 @@ -85,6 +85,29 @@ def test_challenge_appended(spec, state): yield from run_chunk_challenge_processing(spec, state, challenge) +@with_all_phases_except(['phase0']) +@spec_state_test +def test_challenge_empty_element_replaced(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)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=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) + + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) + + state.custody_chunk_challenge_records.append(spec.CustodyChunkChallengeRecord()) + + yield from run_chunk_challenge_processing(spec, state, challenge) + + @with_all_phases_except(['phase0']) @spec_state_test def test_duplicate_challenge(spec, state): From 59b35afcd92e87a4c7b309269fe68e993380521a Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 12 Jun 2020 12:04:30 +0100 Subject: [PATCH 27/45] Refactor universal hash function --- specs/phase1/custody-game.md | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 8185b6baa..1f3ad4378 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -256,19 +256,26 @@ def get_custody_secrets(key: BLSSignature) -> Sequence[int]: return secrets ``` +### `universal_hash_function` + +```python +def universal_hash_function(data_chunks: Sequence[bytes], secrets: Sequence[int]) -> int: + n = len(data_chunks) + return ( + sum( + secrets[i % CUSTODY_SECRETS]**i * int.from_bytes(atom, "little") % CUSTODY_PRIME + for i, atom in enumerate(data_chunks) + ) + secrets[n % CUSTODY_SECRETS]**n + ) % CUSTODY_PRIME +``` + ### `compute_custody_bit` ```python def compute_custody_bit(key: BLSSignature, data: ByteList[MAX_SHARD_BLOCK_SIZE]) -> bit: - secrets = get_custody_secrets(key) custody_atoms = get_custody_atoms(data) - n = len(custody_atoms) - uhf = ( - sum( - secrets[i % CUSTODY_SECRETS]**i * int.from_bytes(atom, "little") % CUSTODY_PRIME - for i, atom in enumerate(custody_atoms) - ) + secrets[n % CUSTODY_SECRETS]**n - ) % CUSTODY_PRIME + secrets = get_custody_secrets(key) + uhf = universal_hash_function(custody_atoms, secrets) return legendre_bit(uhf + secrets[0], CUSTODY_PRIME) ``` @@ -579,7 +586,7 @@ 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) - index_in_records = records.index(custody_chunk_challenge) + index_in_records = state.custody_chunk_challenge_records.index(custody_chunk_challenge) state.custody_chunk_challenge_records[index_in_records] = CustodyChunkChallengeRecord() ``` From 398fc833b8151e6ac808b4a0d0d37459583c9c61 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 12 Jun 2020 12:07:31 +0100 Subject: [PATCH 28/45] Fix TOC --- specs/phase1/custody-game.md | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 1f3ad4378..bab202cf1 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -30,6 +30,7 @@ - [`legendre_bit`](#legendre_bit) - [`get_custody_atoms`](#get_custody_atoms) - [`get_custody_secrets`](#get_custody_secrets) + - [`universal_hash_function`](#universal_hash_function) - [`compute_custody_bit`](#compute_custody_bit) - [`get_randao_epoch_for_custody_period`](#get_randao_epoch_for_custody_period) - [`get_custody_period_for_validator`](#get_custody_period_for_validator) From 04fb9926e819535d32d5c22ed8616c4a0b8a25e9 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 12 Jun 2020 17:16:08 +0100 Subject: [PATCH 29/45] Remove custody bits from phase 1 and tests --- specs/phase1/beacon-chain.md | 131 +----------------- specs/phase1/custody-game.md | 18 +-- specs/phase1/fork-choice.md | 34 ----- .../test/fork_choice/test_on_attestation.py | 7 +- .../eth2spec/test/helpers/attestations.py | 82 +---------- .../test/helpers/attester_slashings.py | 24 +--- .../test_process_attester_slashing.py | 7 +- .../test_process_attestation.py | 17 +-- 8 files changed, 27 insertions(+), 293 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 61e30e102..04c28f5ab 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -52,15 +52,12 @@ - [`get_shard_committee`](#get_shard_committee) - [`get_light_client_committee`](#get_light_client_committee) - [`get_shard_proposer_index`](#get_shard_proposer_index) - - [`get_indexed_attestation`](#get_indexed_attestation) - [`get_committee_count_delta`](#get_committee_count_delta) - [`get_start_shard`](#get_start_shard) - [`get_shard`](#get_shard) - [`get_latest_slot_for_shard`](#get_latest_slot_for_shard) - [`get_offset_slots`](#get_offset_slots) - [Predicates](#predicates) - - [`verify_attestation_custody`](#verify_attestation_custody) - - [Updated `is_valid_indexed_attestation`](#updated-is_valid_indexed_attestation) - [`is_on_time_attestation`](#is_on_time_attestation) - [`is_winning_attestation`](#is_winning_attestation) - [`optional_aggregate_verify`](#optional_aggregate_verify) @@ -77,7 +74,6 @@ - [`verify_empty_shard_transition`](#verify_empty_shard_transition) - [`process_shard_transitions`](#process_shard_transitions) - [New default validator for deposits](#new-default-validator-for-deposits) - - [New Attester slashing processing](#new-attester-slashing-processing) - [Light client processing](#light-client-processing) - [Epoch transition](#epoch-transition) - [Phase 1 final updates](#phase-1-final-updates) @@ -192,7 +188,6 @@ class AttestationData(Container): class Attestation(Container): aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] data: AttestationData - custody_bits_blocks: List[Bitlist[MAX_VALIDATORS_PER_COMMITTEE], MAX_SHARD_BLOCKS_PER_ATTESTATION] signature: BLSSignature ``` @@ -212,8 +207,9 @@ class PendingAttestation(Container): ```python class IndexedAttestation(Container): - committee: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] - attestation: Attestation + attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] + data: AttestationData + signature: BLSSignature ``` ### Extended `AttesterSlashing` @@ -599,17 +595,6 @@ def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard return committee[r % len(committee)] ``` -#### `get_indexed_attestation` - -```python -def get_indexed_attestation(beacon_state: BeaconState, attestation: Attestation) -> IndexedAttestation: - committee = get_beacon_committee(beacon_state, attestation.data.slot, attestation.data.index) - return IndexedAttestation( - committee=committee, - attestation=attestation, - ) -``` - #### `get_committee_count_delta` ```python @@ -679,65 +664,6 @@ def get_offset_slots(state: BeaconState, shard: Shard) -> Sequence[Slot]: ### Predicates -#### `verify_attestation_custody` - -```python -def verify_attestation_custody(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool: - """ - Check if ``indexed_attestation`` has valid signature against non-empty custody bits. - """ - attestation = indexed_attestation.attestation - aggregation_bits = attestation.aggregation_bits - domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch) - all_pubkeys = [] - all_signing_roots = [] - for block_index, custody_bits in enumerate(attestation.custody_bits_blocks): - assert len(custody_bits) == len(indexed_attestation.committee) - for participant, aggregation_bit, custody_bit in zip( - indexed_attestation.committee, aggregation_bits, custody_bits - ): - if aggregation_bit: - all_pubkeys.append(state.validators[participant].pubkey) - # Note: only 2N distinct message hashes - attestation_wrapper = AttestationCustodyBitWrapper( - attestation_data_root=hash_tree_root(attestation.data), - block_index=block_index, - bit=custody_bit, - ) - all_signing_roots.append(compute_signing_root(attestation_wrapper, domain)) - else: - assert not custody_bit - return bls.AggregateVerify(all_pubkeys, all_signing_roots, signature=attestation.signature) -``` - -#### Updated `is_valid_indexed_attestation` - -Note that this replaces the Phase 0 `is_valid_indexed_attestation`. - -```python -def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool: - """ - Check if ``indexed_attestation`` has valid indices and signature. - """ - # Verify aggregate signature - attestation = indexed_attestation.attestation - aggregation_bits = attestation.aggregation_bits - if not any(aggregation_bits) or len(aggregation_bits) != len(indexed_attestation.committee): - return False - - if len(attestation.custody_bits_blocks) == 0: - # fall back on phase0 behavior if there is no shard data. - domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch) - all_pubkeys = [] - for participant, aggregation_bit in zip(indexed_attestation.committee, aggregation_bits): - if aggregation_bit: - all_pubkeys.append(state.validators[participant].pubkey) - signing_root = compute_signing_root(indexed_attestation.attestation.data, domain) - return bls.FastAggregateVerify(all_pubkeys, signing_root, signature=attestation.signature) - else: - return verify_attestation_custody(state, indexed_attestation) -``` - #### `is_on_time_attestation` ```python @@ -855,16 +781,11 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None: else: assert attestation.data.source == state.previous_justified_checkpoint - # Type 1: on-time attestations, the custody bits should be non-empty. - if attestation.custody_bits_blocks != []: - # Ensure on-time attestation - assert is_on_time_attestation(state, attestation) - # Correct data root count - shard = get_shard(state, attestation) - assert len(attestation.custody_bits_blocks) == len(get_offset_slots(state, shard)) + # Type 1: on-time attestations + if is_on_time_attestation(state, attestation): # Correct parent block root assert data.beacon_block_root == get_block_root_at_slot(state, compute_previous_slot(state.slot)) - # Type 2: no shard transition, no custody bits + # Type 2: no shard transition else: # Ensure delayed attestation assert data.slot < compute_previous_slot(state.slot) @@ -1087,46 +1008,6 @@ def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validato ) ``` -##### New Attester slashing processing - -```python -def get_indices_from_committee( - committee: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE], - bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Sequence[ValidatorIndex]: - assert len(bits) == len(committee) - return [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 -``` - #### Light client processing ```python diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index bab202cf1..ca0e9c508 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -60,6 +60,7 @@ This document details the beacon chain additions and changes in Phase 1 of Ether | `CUSTODY_PRIME` | `2 ** 256 - 189` | - | | `CUSTODY_SECRETS` | `3` | - | | `BYTES_PER_CUSTODY_ATOM` | `32` | bytes | +| `CUSTODY_PROBABILITY_EXPONENT` | `10` | - | ## Configuration @@ -141,7 +142,6 @@ class CustodyChunkResponse(Container): ```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 @@ -277,7 +277,8 @@ def compute_custody_bit(key: BLSSignature, data: ByteList[MAX_SHARD_BLOCK_SIZE]) custody_atoms = get_custody_atoms(data) secrets = get_custody_secrets(key) uhf = universal_hash_function(custody_atoms, secrets) - return legendre_bit(uhf + secrets[0], CUSTODY_PRIME) + legendre_bits = [legendre_bit(uhf + secrets[0], CUSTODY_PRIME) for i in range(CUSTODY_PROBABILITY_EXPONENT)] + return all(legendre_bits) ``` ### `get_randao_epoch_for_custody_period` @@ -518,9 +519,6 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed # Verify the attestation assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) - # TODO: custody_slashing.data is not chunked like shard blocks yet, result is lots of padding. - # ??? What does this mean? - # TODO: can do a single combined merkle proof of data being attested. # Verify the shard transition is indeed attested by the attestation shard_transition = custody_slashing.shard_transition @@ -545,18 +543,14 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed signing_root = compute_signing_root(epoch_to_sign, domain) assert bls.Verify(malefactor.pubkey, signing_root, custody_slashing.malefactor_secret) - # Get the custody bit - custody_bits = attestation.custody_bits_blocks[custody_slashing.data_index] - committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index) - claimed_custody_bit = custody_bits[committee.index(custody_slashing.malefactor_index)] - # Compute the custody bit computed_custody_bit = compute_custody_bit(custody_slashing.malefactor_secret, custody_slashing.data) - + # Verify the claim - if claimed_custody_bit != computed_custody_bit: + if computed_custody_bit == 1: # Slash the malefactor, reward the other committee members slash_validator(state, custody_slashing.malefactor_index) + committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index) others_count = len(committee) - 1 whistleblower_reward = Gwei(malefactor.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT // others_count) for attester_index in attesters: diff --git a/specs/phase1/fork-choice.md b/specs/phase1/fork-choice.md index 3f9fbdbfb..f92640ebb 100644 --- a/specs/phase1/fork-choice.md +++ b/specs/phase1/fork-choice.md @@ -9,11 +9,9 @@ - [Introduction](#introduction) -- [Fork choice](#fork-choice) - [Helpers](#helpers) - [Extended `LatestMessage`](#extended-latestmessage) - [Updated `update_latest_messages`](#updated-update_latest_messages) - - [Handlers](#handlers) @@ -22,12 +20,6 @@ This document is the beacon chain fork choice spec for part of Ethereum 2.0 Phase 1. -## Fork choice - -Due to the changes in the structure of `IndexedAttestation` in Phase 1, `on_attestation` must be re-specified to handle this. The bulk of `on_attestation` has been moved out into a few helpers to reduce code duplication where possible. - -The rest of the fork choice remains stable. - ### Helpers #### Extended `LatestMessage` @@ -54,29 +46,3 @@ def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIn epoch=target.epoch, root=beacon_block_root, shard=shard, shard_root=attestation.data.shard_head_root ) ``` - -### Handlers - -```python -def on_attestation(store: Store, attestation: Attestation) -> None: - """ - Run ``on_attestation`` upon receiving a new ``attestation`` from either within a block or directly on the wire. - - An ``attestation`` that is asserted as invalid may be valid at a later time, - consider scheduling it for later processing in such case. - """ - validate_on_attestation(store, attestation) - store_target_checkpoint_state(store, attestation.data.target) - - # Get state at the `target` to fully validate attestation - target_state = store.checkpoint_states[attestation.data.target] - indexed_attestation = get_indexed_attestation(target_state, attestation) - assert is_valid_indexed_attestation(target_state, indexed_attestation) - - # Update latest messages for attesting indices - attesting_indices = [ - index for i, index in enumerate(indexed_attestation.committee) - if attestation.aggregation_bits[i] - ] - update_latest_messages(store, attesting_indices, attestation) -``` diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py index a5334c5c7..2c50544d8 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py @@ -16,18 +16,13 @@ def run_on_attestation(spec, state, store, attestation, valid=True): indexed_attestation = spec.get_indexed_attestation(state, attestation) spec.on_attestation(store, attestation) + sample_index = indexed_attestation.attesting_indices[0] if spec.fork == PHASE0: - sample_index = indexed_attestation.attesting_indices[0] latest_message = spec.LatestMessage( epoch=attestation.data.target.epoch, root=attestation.data.beacon_block_root, ) else: - attesting_indices = [ - index for i, index in enumerate(indexed_attestation.committee) - if attestation.aggregation_bits[i] - ] - sample_index = attesting_indices[0] latest_message = spec.LatestMessage( epoch=attestation.data.target.epoch, root=attestation.data.beacon_block_root, diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 17ef717bc..b9c91eaa0 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -2,14 +2,13 @@ from lru import LRU from typing import List -from eth2spec.test.context import expect_assertion_error, PHASE0, PHASE1 +from eth2spec.test.context import expect_assertion_error, PHASE1 from eth2spec.test.helpers.state import state_transition_and_sign_block, next_epoch, next_slot from eth2spec.test.helpers.block import build_empty_block_for_next_slot from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls from eth2spec.utils.ssz.ssz_typing import Bitlist -from eth2spec.test.helpers.custody import get_custody_test_vector def run_attestation_processing(spec, state, attestation, valid=True): @@ -98,35 +97,7 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None, on_t def convert_to_valid_on_time_attestation(spec, state, attestation, shard_transition, - signed=False, valid_custody_bits=None): - shard = spec.get_shard(state, attestation) - offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), state.slot + 1) - - if valid_custody_bits is not None: - beacon_committee = spec.get_beacon_committee( - state, - attestation.data.slot, - attestation.data.index, - ) - custody_secrets = [None for i in beacon_committee] - for i in range(len(beacon_committee)): - period = spec.get_custody_period_for_validator(beacon_committee[i], attestation.data.target.epoch) - epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, beacon_committee[i]) - domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch_to_sign) - signing_root = spec.compute_signing_root(spec.Epoch(epoch_to_sign), domain) - custody_secrets[i] = bls.Sign(privkeys[beacon_committee[i]], signing_root) - - for i in range(len(offset_slots)): - attestation.custody_bits_blocks.append( - Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits]) - ) - if valid_custody_bits is not None: - test_vector = get_custody_test_vector(shard_transition.shard_block_lengths[i]) - for j in range(len(attestation.custody_bits_blocks[i])): - if attestation.aggregation_bits[j]: - attestation.custody_bits_blocks[i][j] = \ - spec.compute_custody_bit(custody_secrets[j], test_vector) ^ (not valid_custody_bits) - + signed=False): if signed: sign_attestation(spec, state, attestation) @@ -134,7 +105,7 @@ def convert_to_valid_on_time_attestation(spec, state, attestation, shard_transit def get_valid_on_time_attestation(spec, state, slot=None, index=None, - shard_transition=None, valid_custody_bits=None, signed=False): + shard_transition=None, signed=False): ''' Construct on-time attestation for next slot ''' @@ -149,7 +120,6 @@ def get_valid_on_time_attestation(spec, state, slot=None, index=None, slot=slot, index=index, shard_transition=shard_transition, - valid_custody_bits=valid_custody_bits, signed=signed, on_time=True, ) @@ -174,7 +144,6 @@ def get_valid_attestation(spec, index=None, filter_participant_set=None, shard_transition=None, - valid_custody_bits=None, signed=False, on_time=True): # If filter_participant_set filters everything, the attestation has 0 participants, and cannot be signed. @@ -207,7 +176,6 @@ def get_valid_attestation(spec, attestation = convert_to_valid_on_time_attestation( spec, state, attestation, shard_transition, - valid_custody_bits=valid_custody_bits, signed=signed, ) @@ -230,43 +198,9 @@ def sign_aggregate_attestation(spec, state, attestation_data, participants: List def sign_indexed_attestation(spec, state, indexed_attestation): - if spec.fork == 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 - if any(indexed_attestation.attestation.custody_bits_blocks): - sign_on_time_attestation(spec, state, indexed_attestation.attestation) - else: - 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) + participants = indexed_attestation.attesting_indices + data = indexed_attestation.data + indexed_attestation.signature = sign_aggregate_attestation(spec, state, data, participants) def get_attestation_custody_signature(spec, state, attestation_data, block_index, bit, privkey): @@ -283,10 +217,6 @@ def get_attestation_custody_signature(spec, state, attestation_data, block_index 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, diff --git a/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py b/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py index e743ca8ff..43763895f 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py @@ -1,4 +1,3 @@ -from eth2spec.test.context import PHASE1 from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation, sign_indexed_attestation @@ -41,34 +40,19 @@ def get_indexed_attestation_participants(spec, indexed_att): """ Wrapper around index-attestation to return the list of participant indices, regardless of spec phase. """ - if spec.fork == PHASE1: - return list(spec.get_indices_from_committee( - indexed_att.committee, - indexed_att.attestation.aggregation_bits, - )) - else: - return list(indexed_att.attesting_indices) + return list(indexed_att.attesting_indices) def set_indexed_attestation_participants(spec, indexed_att, participants): """ Wrapper around index-attestation to return the list of participant indices, regardless of spec phase. """ - if spec.fork == PHASE1: - indexed_att.attestation.aggregation_bits = [bool(i in participants) for i in indexed_att.committee] - else: - indexed_att.attesting_indices = participants + indexed_att.attesting_indices = participants def get_attestation_1_data(spec, att_slashing): - if spec.fork == PHASE1: - return att_slashing.attestation_1.attestation.data - else: - return att_slashing.attestation_1.data + return att_slashing.attestation_1.data def get_attestation_2_data(spec, att_slashing): - if spec.fork == PHASE1: - return att_slashing.attestation_2.attestation.data - else: - return att_slashing.attestation_2.data + return att_slashing.attestation_2.data diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py b/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py index 11ead6033..063514498 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py @@ -1,5 +1,5 @@ from eth2spec.test.context import ( - PHASE0, PHASE1, + PHASE0, spec_state_test, expect_assertion_error, always_bls, with_all_phases, with_phases ) from eth2spec.test.helpers.attestations import sign_indexed_attestation @@ -162,10 +162,7 @@ def test_same_data(spec, state): indexed_att_1 = attester_slashing.attestation_1 att_2_data = get_attestation_2_data(spec, attester_slashing) - if spec.fork == PHASE1: - indexed_att_1.attestation.data = att_2_data - else: - indexed_att_1.data = att_2_data + indexed_att_1.data = att_2_data sign_indexed_attestation(spec, state, attester_slashing.attestation_1) yield from run_attester_slashing_processing(spec, state, attester_slashing, False) 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 ed4328327..34ff28412 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 @@ -25,22 +25,9 @@ def test_on_time_success(spec, state): @with_all_phases_except(['phase0']) @spec_state_test @always_bls -def test_on_time_empty_custody_bits_blocks(spec, state): +def test_late_success(spec, state): attestation = get_valid_late_attestation(spec, state, signed=True) - assert not any(attestation.custody_bits_blocks) - - 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']) -@spec_state_test -@always_bls -def test_late_with_custody_bits_blocks(spec, state): - attestation = get_valid_on_time_attestation(spec, state, signed=True) - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY + 1) - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation) From f857dbfac246b29bdc92bc80c2ab861f4de114bd Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 12 Jun 2020 22:47:45 +0100 Subject: [PATCH 30/45] Custody tests --- .../pyspec/eth2spec/test/helpers/custody.py | 46 +++++--- .../test_process_custody_slashing.py | 103 ++++++++++++++---- 2 files changed, 111 insertions(+), 38 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index 898cf7731..edd7ac19f 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -59,7 +59,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_custody_slashing(spec, state, attestation, shard_transition, invalid_custody_bit=False): +def get_valid_custody_slashing(spec, state, attestation, shard_transition, custody_secret, data, data_index=0): beacon_committee = spec.get_beacon_committee( state, attestation.data.slot, @@ -68,21 +68,10 @@ def get_valid_custody_slashing(spec, state, attestation, shard_transition, inval malefactor_index = beacon_committee[0] whistleblower_index = beacon_committee[-1] - epoch = spec.get_randao_epoch_for_custody_period(attestation.data.target.epoch, - malefactor_index) - - # Generate the responder key - domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch) - signing_root = spec.compute_signing_root(spec.Epoch(epoch), domain) - malefactor_key = bls.Sign(privkeys[malefactor_index], signing_root) - data_index = 0 - data = ByteList[spec.MAX_SHARD_BLOCK_SIZE]( - get_custody_test_vector(shard_transition.shard_block_lengths[data_index])) - slashing = spec.CustodySlashing( data_index=data_index, malefactor_index=malefactor_index, - malefactor_secret=malefactor_key, + malefactor_secret=custody_secret, whistleblower_index=whistleblower_index, shard_transition=shard_transition, attestation=attestation, @@ -165,9 +154,9 @@ def get_valid_custody_chunk_response(spec, state, chunk_challenge, block_length, ) -def get_custody_test_vector(bytelength): +def get_custody_test_vector(bytelength, offset=0): ints = bytelength // 4 + 1 - return (b"".join(i.to_bytes(4, "little") for i in range(ints)))[:bytelength] + return (b"".join((i + offset).to_bytes(4, "little") for i in range(ints)))[:bytelength] def get_shard_transition(spec, start_slot, block_lengths): @@ -181,3 +170,30 @@ def get_shard_transition(spec, start_slot, block_lengths): proposer_signature_aggregate=spec.BLSSignature(), ) return shard_transition + + +def get_custody_secret(spec, state, validator_index, epoch=None): + period = spec.get_custody_period_for_validator(validator_index, epoch if epoch is not None + else spec.get_current_epoch(state)) + epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, validator_index) + domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch_to_sign) + signing_root = spec.compute_signing_root(spec.Epoch(epoch_to_sign), domain) + return bls.Sign(privkeys[validator_index], signing_root) + + +def get_custody_slashable_test_vector(spec, custody_secret, length, slashable=True): + test_vector = get_custody_test_vector(length) + offset = 0 + while spec.compute_custody_bit(custody_secret, test_vector) != slashable: + offset += 1 + test_vector = test_vector = get_custody_test_vector(length, offset) + return test_vector + + +def get_custody_slashable_shard_transition(spec, start_slot, block_lengths, custody_secret, slashable=True): + shard_transition = get_shard_transition(spec, start_slot, block_lengths) + slashable_test_vector = get_custody_slashable_test_vector(spec, custody_secret, + block_lengths[0], slashable=slashable) + shard_transition.shard_data_roots[0] = ByteList[spec.MAX_SHARD_BLOCK_SIZE](slashable_test_vector) \ + .get_backing().get_left().merkle_root() + return shard_transition, slashable_test_vector 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 index 997e5ac92..c40aa1787 100644 --- 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 @@ -1,6 +1,7 @@ from eth2spec.test.helpers.custody import ( get_valid_custody_slashing, - get_shard_transition, + get_custody_secret, + get_custody_slashable_shard_transition, ) from eth2spec.test.helpers.attestations import ( get_valid_on_time_attestation, @@ -58,9 +59,18 @@ def test_custody_slashing(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)) + + validator_index = spec.get_beacon_committee( + state, + state.slot, + shard, + )[0] + custody_secret = get_custody_secret(spec, state, validator_index) + shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, + [2**15 // 3] * len(offset_slots), custody_secret) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition, valid_custody_bits=False) + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -68,8 +78,8 @@ def test_custody_slashing(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) - + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, + custody_secret, slashable_test_vector) yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @@ -79,9 +89,18 @@ def test_incorrect_custody_slashing(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)) + + validator_index = spec.get_beacon_committee( + state, + state.slot, + shard, + )[0] + custody_secret = get_custody_secret(spec, state, validator_index) + shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, + [2**15 // 3] * len(offset_slots), custody_secret, slashable=False) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition, valid_custody_bits=True) + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -89,7 +108,8 @@ def test_incorrect_custody_slashing(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) + slashing = get_valid_custody_slashing(spec, state, attestation, + shard_transition, custody_secret, slashable_test_vector) yield from run_custody_slashing_processing(spec, state, slashing, correct=False) @@ -100,9 +120,18 @@ 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)) + + validator_index = spec.get_beacon_committee( + state, + state.slot, + shard, + )[0] + custody_secret = get_custody_secret(spec, state, validator_index) + shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, + [2**15 // 3] * len(offset_slots), custody_secret) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition, valid_custody_bits=False) + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -110,8 +139,8 @@ def test_multiple_epochs_custody(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) - + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, + custody_secret, slashable_test_vector) yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @@ -121,9 +150,18 @@ def test_many_epochs_custody(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 20) shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + + validator_index = spec.get_beacon_committee( + state, + state.slot, + shard, + )[0] + custody_secret = get_custody_secret(spec, state, validator_index) + shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, + [2**15 // 3] * len(offset_slots), custody_secret) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition, valid_custody_bits=False) + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -131,8 +169,8 @@ def test_many_epochs_custody(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) - + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, + custody_secret, slashable_test_vector) yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @@ -142,14 +180,23 @@ 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)) + + validator_index = spec.get_beacon_committee( + state, + state.slot, + shard, + )[0] + custody_secret = get_custody_secret(spec, state, validator_index) + shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, + [2**15 // 3] * len(offset_slots), custody_secret) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition, valid_custody_bits=False) + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) - + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, + custody_secret, slashable_test_vector) yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @@ -159,9 +206,18 @@ def test_invalid_custody_slashing(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)) + + validator_index = spec.get_beacon_committee( + state, + state.slot, + shard, + )[0] + custody_secret = get_custody_secret(spec, state, validator_index) + shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, + [2**15 // 3] * len(offset_slots), custody_secret) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition, valid_custody_bits=False) + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -169,7 +225,8 @@ def test_invalid_custody_slashing(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, + custody_secret, slashable_test_vector) slashing.message.data = ByteList[spec.MAX_SHARD_BLOCK_SIZE]() From 42a9f1afdf12faaac235e775d5db7e86720aa1c9 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 12 Jun 2020 23:44:36 +0100 Subject: [PATCH 31/45] Fix Legendre bit computations --- specs/phase1/custody-game.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index ca0e9c508..5cf5da743 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -277,7 +277,7 @@ def compute_custody_bit(key: BLSSignature, data: ByteList[MAX_SHARD_BLOCK_SIZE]) custody_atoms = get_custody_atoms(data) secrets = get_custody_secrets(key) uhf = universal_hash_function(custody_atoms, secrets) - legendre_bits = [legendre_bit(uhf + secrets[0], CUSTODY_PRIME) for i in range(CUSTODY_PROBABILITY_EXPONENT)] + legendre_bits = [legendre_bit(uhf + secrets[0] + i, CUSTODY_PRIME) for i in range(CUSTODY_PROBABILITY_EXPONENT)] return all(legendre_bits) ``` From f6d7dac30c6d29c3b6252daf6255daf7c217e046 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Sat, 13 Jun 2020 15:15:37 +0100 Subject: [PATCH 32/45] Change to 2**14 epoch (73 day) custody periods as per #1888 --- configs/mainnet.yaml | 17 ++++++------ configs/minimal.yaml | 19 +++++++------ specs/phase1/custody-game.md | 11 ++++---- .../test_process_custody_slashing.py | 2 +- .../test_process_challenge_deadlines.py | 27 +++---------------- .../test_process_reveal_deadlines.py | 13 ++++----- 6 files changed, 37 insertions(+), 52 deletions(-) diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 2e15e84fd..d2ab3cab6 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -196,19 +196,20 @@ GASPRICE_ADJUSTMENT_COEFFICIENT: 8 # Phase 1: Custody Game # --------------------------------------------------------------- +# 1/1024 chance of custody bit 1 +CUSTODY_PROBABILITY_EXPONENT: 10 + # Time parameters # 2**1 (= 2) epochs, 12.8 minutes RANDAO_PENALTY_EPOCHS: 2 -# 2**14 (= 16,384) epochs ~73 days -EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 16384 -# 2**11 (= 2,048) epochs, ~9 days -EPOCHS_PER_CUSTODY_PERIOD: 2048 +# 2**15 (= 32,768) epochs, ~146 days +EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 32768 +# 2**14 (= 16,384) epochs ~73 days +EPOCHS_PER_CUSTODY_PERIOD: 16384 # 2**11 (= 2,048) epochs, ~9 days CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048 -# 2**7 (= 128) epochs, ~14 hours -MAX_REVEAL_LATENESS_DECREMENT: 128 -# 2**14 (= 16,384) epochs -CUSTODY_RESPONSE_DEADLINE: 16384 +# 2**15 (= 32,768) epochs, ~146 days +MAX_CHUNK_CHALLENGE_DELAY: 32768 # Max operations # 2**8 (= 256) diff --git a/configs/minimal.yaml b/configs/minimal.yaml index d99aa164a..3fe160bbe 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -199,17 +199,20 @@ GASPRICE_ADJUSTMENT_COEFFICIENT: 8 # Phase 1: Custody Game # --------------------------------------------------------------- +# 1/1024 chance of custody bit 1 [customized for faster testing] +CUSTODY_PROBABILITY_EXPONENT: 2 + # Time parameters # 2**1 (= 2) epochs 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: 8 -# 2**11 (= 2,048) epochs -CUSTODY_PERIOD_TO_RANDAO_PADDING: 8 -# 2**14 (= 16,384) epochs -CUSTODY_RESPONSE_DEADLINE: 32 +# 2**15 (= 32,768) epochs, ~146 days [customized for faster testing] +EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 128 +# 2**14 (= 16,384) epochs ~73 days [customized for faster testing] +EPOCHS_PER_CUSTODY_PERIOD: 64 +# 2**11 (= 2,048) epochs, ~9 days [customized for faster testing] +CUSTODY_PERIOD_TO_RANDAO_PADDING: 16 +# 2**15 (= 32,768) epochs, ~146 days +MAX_CHUNK_CHALLENGE_DELAY: 128 # Max operations # 2**8 (= 256) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 5cf5da743..30119f3e3 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -69,12 +69,11 @@ This document details the beacon chain additions and changes in Phase 1 of Ether | Name | Value | Unit | Duration | | - | - | :-: | :-: | | `RANDAO_PENALTY_EPOCHS` | `2**1` (= 2) | epochs | 12.8 minutes | -| `EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS` | `2**14` (= 16,384) | epochs | ~73 days | -| `EPOCHS_PER_CUSTODY_PERIOD` | `2**11` (= 2,048) | epochs | ~9 days | +| `EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS` | `2**15` (= 32,768) | epochs | ~146 days | +| `EPOCHS_PER_CUSTODY_PERIOD` | `2**14` (= 16,384) | epochs | ~73 days | | `CUSTODY_PERIOD_TO_RANDAO_PADDING` | `2**11` (= 2,048) | epochs | ~9 days | | `CHUNK_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days | -| `MAX_CHUNK_CHALLENGE_DELAY` | `2**11` (= 2,048) | epochs | ~9 days | -| `CUSTODY_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days | +| `MAX_CHUNK_CHALLENGE_DELAY` | `2**15` (= 32,768) | epochs | ~146 days | ### Max operations per block @@ -571,7 +570,7 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed def process_reveal_deadlines(state: BeaconState) -> None: epoch = get_current_epoch(state) for index, validator in enumerate(state.validators): - deadline = validator.next_custody_secret_to_reveal + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD) + deadline = validator.next_custody_secret_to_reveal + 1 if get_custody_period_for_validator(ValidatorIndex(index), epoch) > deadline: slash_validator(state, ValidatorIndex(index)) ``` @@ -579,7 +578,7 @@ def process_reveal_deadlines(state: BeaconState) -> None: ```python 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: + if get_current_epoch(state) > custody_chunk_challenge.inclusion_epoch + EPOCHS_PER_CUSTODY_PERIOD: slash_validator(state, custody_chunk_challenge.responder_index, custody_chunk_challenge.challenger_index) index_in_records = state.custody_chunk_challenge_records.index(custody_chunk_challenge) state.custody_chunk_challenge_records[index_in_records] = CustodyChunkChallengeRecord() 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 index c40aa1787..d1649484e 100644 --- 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 @@ -147,7 +147,7 @@ def test_multiple_epochs_custody(spec, state): @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 * 20) + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 10) shard = 0 offset_slots = spec.get_offset_slots(state, shard) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py index 2e60d167c..6590d8af9 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py @@ -1,12 +1,11 @@ from eth2spec.test.helpers.custody import ( get_valid_chunk_challenge, get_shard_transition, - get_valid_custody_key_reveal, ) from eth2spec.test.helpers.attestations import ( get_valid_on_time_attestation, ) -from eth2spec.test.helpers.state import next_epoch_via_block, transition_to +from eth2spec.test.helpers.state import transition_to from eth2spec.test.context import ( with_all_phases_except, spec_state_test, @@ -17,7 +16,6 @@ from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_ep from eth2spec.test.phase_1.block_processing.test_process_chunk_challenge import ( run_chunk_challenge_processing, ) -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): @@ -44,32 +42,15 @@ def test_validator_slashed_after_chunk_challenge(spec, state): attestation.data.index )[0] - spec.initiate_validator_exit(state, validator_index) - assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) - - 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_via_block(spec, state) - while (state.validators[validator_index].next_custody_secret_to_reveal - <= spec.get_custody_period_for_validator( - 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_via_block(spec, state) - challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) _, _, _ = run_chunk_challenge_processing(spec, state, challenge) assert state.validators[validator_index].slashed == 0 - transition_to(spec, state, state.slot + (spec.CUSTODY_RESPONSE_DEADLINE + - spec.EPOCHS_PER_CUSTODY_PERIOD) * spec.SLOTS_PER_EPOCH) + transition_to(spec, state, state.slot + spec.MAX_CHUNK_CHALLENGE_DELAY * spec.SLOTS_PER_EPOCH) + + state.validators[validator_index].slashed = 0 yield from run_process_challenge_deadlines(spec, state) 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 index 31d45aa41..9cc0069b9 100644 --- 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 @@ -18,11 +18,13 @@ def run_process_challenge_deadlines(spec, state): @spec_state_test def test_validator_slashed_after_reveal_deadline(spec, state): assert state.validators[0].slashed == 0 - transition_to(spec, state, spec.get_randao_epoch_for_custody_period(0, 0) * spec.SLOTS_PER_EPOCH) - transition_to(spec, state, state.slot + ((spec.CUSTODY_RESPONSE_DEADLINE) - * spec.SLOTS_PER_EPOCH)) + # Need to run at least one reveal so that not all validators are slashed (otherwise spec fails to find proposers) + custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=1) + _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) + + transition_to(spec, state, state.slot + spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH) state.validators[0].slashed = 0 @@ -34,15 +36,14 @@ def test_validator_slashed_after_reveal_deadline(spec, state): @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 + transition_to(spec, state, 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 - transition_to(spec, state, state.slot + ((spec.CUSTODY_RESPONSE_DEADLINE) - * spec.SLOTS_PER_EPOCH)) + transition_to(spec, state, state.slot + spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH) yield from run_process_challenge_deadlines(spec, state) From 8697b30eea5c1353c79919b1940ed158cc69de8a Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 15 Jun 2020 15:27:37 +0800 Subject: [PATCH 33/45] Fix configuration --- configs/mainnet/phase1.yaml | 8 ++++++++ configs/minimal/phase1.yaml | 14 +++++++++++--- specs/phase1/beacon-chain.md | 34 ++++++++++++++-------------------- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/configs/mainnet/phase1.yaml b/configs/mainnet/phase1.yaml index 399192113..c3a4f93b9 100644 --- a/configs/mainnet/phase1.yaml +++ b/configs/mainnet/phase1.yaml @@ -28,6 +28,10 @@ TARGET_SHARD_BLOCK_SIZE: 262144 SHARD_BLOCK_OFFSETS: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233] # len(SHARD_BLOCK_OFFSETS) MAX_SHARD_BLOCKS_PER_ATTESTATION: 12 +# 2**12 (= 4,096) +BYTES_PER_CUSTODY_CHUNK: 4096 +# ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK) +CUSTODY_RESPONSE_DEPTH: 8 # Gwei values # 2**14 (= 16,384) Gwei @@ -41,6 +45,10 @@ ONLINE_PERIOD: 8 # 2**8 (= 256) | epochs LIGHT_CLIENT_COMMITTEE_PERIOD: 256 +# Max operations per block +# 2**20 (= 1,048,576) +MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS: 1048576 + # Domain types DOMAIN_SHARD_PROPOSAL: 0x80000000 DOMAIN_SHARD_COMMITTEE: 0x81000000 diff --git a/configs/minimal/phase1.yaml b/configs/minimal/phase1.yaml index 68e60aa87..5e570d646 100644 --- a/configs/minimal/phase1.yaml +++ b/configs/minimal/phase1.yaml @@ -30,6 +30,10 @@ TARGET_SHARD_BLOCK_SIZE: 262144 SHARD_BLOCK_OFFSETS: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233] # len(SHARD_BLOCK_OFFSETS) MAX_SHARD_BLOCKS_PER_ATTESTATION: 12 +# 2**12 (= 4,096) +BYTES_PER_CUSTODY_CHUNK: 4096 +# ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK) +CUSTODY_RESPONSE_DEPTH: 8 # Gwei values # 2**14 (= 16,384) Gwei @@ -43,6 +47,10 @@ ONLINE_PERIOD: 8 # 2**8 (= 256) | epochs LIGHT_CLIENT_COMMITTEE_PERIOD: 256 +# Max operations per block +# 2**20 (= 1,048,576) +MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS: 1048576 + # Domain types DOMAIN_SHARD_PROPOSAL: 0x80000000 DOMAIN_SHARD_COMMITTEE: 0x81000000 @@ -58,11 +66,11 @@ DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000 RANDAO_PENALTY_EPOCHS: 2 # [customized] quicker for testing EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096 -# [customized] quickerquicker for testing +# [customized] quicker for testing EPOCHS_PER_CUSTODY_PERIOD: 8 -# [customized] quickerquicker for testing +# [customized] quicker for testing CUSTODY_PERIOD_TO_RANDAO_PADDING: 8 -# [customized] quickerquicker for testing +# [customized] quicker for testing CUSTODY_RESPONSE_DEADLINE: 32 # Max operations diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 61e30e102..dbfbde897 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -16,6 +16,7 @@ - [Gwei values](#gwei-values) - [Initial values](#initial-values) - [Time parameters](#time-parameters) + - [Max operations per block](#max-operations-per-block) - [Domain types](#domain-types) - [Updated containers](#updated-containers) - [Extended `AttestationData`](#extended-attestationdata) @@ -116,12 +117,14 @@ Configuration is not namespaced. Instead it is strictly an extension; ### Shard block configs -| Name | Value | -| - | - | -| `MAX_SHARD_BLOCK_SIZE` | `2**20` (= 1,048,576) | -| `TARGET_SHARD_BLOCK_SIZE` | `2**18` (= 262,144) | -| `SHARD_BLOCK_OFFSETS` | `[1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]` | -| `MAX_SHARD_BLOCKS_PER_ATTESTATION` | `len(SHARD_BLOCK_OFFSETS)` | +| Name | Value | Unit | +| - | - | - | +| `MAX_SHARD_BLOCK_SIZE` | `2**20` (= 1,048,576) | bytes | +| `TARGET_SHARD_BLOCK_SIZE` | `2**18` (= 262,144) | bytes | +| `SHARD_BLOCK_OFFSETS` | `[1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]` | - | +| `MAX_SHARD_BLOCKS_PER_ATTESTATION` | `len(SHARD_BLOCK_OFFSETS)` | - | +| `BYTES_PER_CUSTODY_CHUNK` | `2**12` (= 4,096) | bytes | +| `CUSTODY_RESPONSE_DEPTH` | `ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK)` | - | ### Gwei values @@ -142,20 +145,11 @@ Configuration is not namespaced. Instead it is strictly an extension; | - | - | :-: | :-: | | `ONLINE_PERIOD` | `OnlineEpochs(2**3)` (= 8) | online epochs | ~51 mins | | `LIGHT_CLIENT_COMMITTEE_PERIOD` | `Epoch(2**8)` (= 256) | epochs | ~27 hours | -| `MAX_SHARD_BLOCK_SIZE` | `2**20` (= 1,048,576) | bytes | - | -| `TARGET_SHARD_BLOCK_SIZE` | `2**18` (= 262,144) | bytes | - | -| `SHARD_BLOCK_OFFSETS` | `[1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]` | list of slot offsets | - | -| `MAX_SHARD_BLOCKS_PER_ATTESTATION` | `len(SHARD_BLOCK_OFFSETS)` | - | - | -| `MAX_GASPRICE` | `Gwei(2**14)` (= 16,384) | Gwei | | -| `MIN_GASPRICE` | `Gwei(2**3)` (= 8) | Gwei | | -| `GASPRICE_ADJUSTMENT_COEFFICIENT` | `2**3` (= 8) | | | -| `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 | | -| `CUSTODY_RESPONSE_DEPTH` | `ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK)` | - | - | -| `NO_SIGNATURE` | `BLSSignature(b'\x00' * 96)` | | | + +### Max operations per block +| Name | Value | +| - | - | +| `MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS` | `2**20` (= 1,048,576) | ### Domain types From 97d50b381bed78e17b3c2f7510182fa38d5de6c1 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 15 Jun 2020 08:11:15 -0600 Subject: [PATCH 34/45] minor fixes in config comments --- configs/mainnet.yaml | 2 +- configs/minimal.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index d2ab3cab6..60af0fbbc 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -204,7 +204,7 @@ CUSTODY_PROBABILITY_EXPONENT: 10 RANDAO_PENALTY_EPOCHS: 2 # 2**15 (= 32,768) epochs, ~146 days EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 32768 -# 2**14 (= 16,384) epochs ~73 days +# 2**14 (= 16,384) epochs ~73 days EPOCHS_PER_CUSTODY_PERIOD: 16384 # 2**11 (= 2,048) epochs, ~9 days CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048 diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 3fe160bbe..dedb04f59 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -199,19 +199,19 @@ GASPRICE_ADJUSTMENT_COEFFICIENT: 8 # Phase 1: Custody Game # --------------------------------------------------------------- -# 1/1024 chance of custody bit 1 [customized for faster testing] +# 1/4 chance of custody bit 1 [customized for faster testing] CUSTODY_PROBABILITY_EXPONENT: 2 # Time parameters # 2**1 (= 2) epochs RANDAO_PENALTY_EPOCHS: 2 -# 2**15 (= 32,768) epochs, ~146 days [customized for faster testing] +# 2**7 (= 128) epochs, ~13.7 hours [customized for faster testing] EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 128 -# 2**14 (= 16,384) epochs ~73 days [customized for faster testing] +# 2**6 (= 64) epochs ~6.8 hours [customized for faster testing] EPOCHS_PER_CUSTODY_PERIOD: 64 -# 2**11 (= 2,048) epochs, ~9 days [customized for faster testing] +# 2**4 (= 16) epochs, ~1.7 hours [customized for faster testing] CUSTODY_PERIOD_TO_RANDAO_PADDING: 16 -# 2**15 (= 32,768) epochs, ~146 days +# 2**7 (= 128) epochs, ~13.7 hours [customize for faster testing] MAX_CHUNK_CHALLENGE_DELAY: 128 # Max operations From 01a69288b6dfa94f28f51c525e6869e41eb19164 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 15 Jun 2020 15:33:05 -0600 Subject: [PATCH 35/45] custody 0.01 testing cleanup --- .../eth2spec/test/helpers/attestations.py | 18 +- .../pyspec/eth2spec/test/helpers/custody.py | 4 +- .../test_process_custody_slashing.py | 190 +++++------------- 3 files changed, 50 insertions(+), 162 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index b9c91eaa0..07e227022 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -96,16 +96,7 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None, on_t return attestation_data -def convert_to_valid_on_time_attestation(spec, state, attestation, shard_transition, - signed=False): - if signed: - sign_attestation(spec, state, attestation) - - return attestation - - -def get_valid_on_time_attestation(spec, state, slot=None, index=None, - shard_transition=None, signed=False): +def get_valid_on_time_attestation(spec, state, slot=None, index=None, shard_transition=None, signed=False): ''' Construct on-time attestation for next slot ''' @@ -172,13 +163,6 @@ def get_valid_attestation(spec, # fill the attestation with (optionally filtered) participants, and optionally sign it fill_aggregate_attestation(spec, state, attestation, signed=signed, filter_participant_set=filter_participant_set) - if spec.fork == PHASE1 and on_time: - attestation = convert_to_valid_on_time_attestation( - spec, state, attestation, - shard_transition, - signed=signed, - ) - return attestation diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index edd7ac19f..4f91e6dc5 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -194,6 +194,6 @@ def get_custody_slashable_shard_transition(spec, start_slot, block_lengths, cust shard_transition = get_shard_transition(spec, start_slot, block_lengths) slashable_test_vector = get_custody_slashable_test_vector(spec, custody_secret, block_lengths[0], slashable=slashable) - shard_transition.shard_data_roots[0] = ByteList[spec.MAX_SHARD_BLOCK_SIZE](slashable_test_vector) \ - .get_backing().get_left().merkle_root() + block_data = ByteList[spec.MAX_SHARD_BLOCK_SIZE](slashable_test_vector) + shard_transition.shard_data_roots[0] = block_data.get_backing().get_left().merkle_root() return shard_transition, slashable_test_vector 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 index d1649484e..ec0bac82d 100644 --- 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 @@ -53,21 +53,36 @@ def run_custody_slashing_processing(spec, state, custody_slashing, valid=True, c yield 'post', state -@with_all_phases_except(['phase0']) -@spec_state_test -def test_custody_slashing(spec, state): - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) +def run_standard_custody_slashing_test(spec, + state, + shard_lateness=None, + shard=None, + validator_index=None, + block_lengths=None, + slashing_message_data=None, + correct=True, + valid=True): + if shard_lateness is None: + shard_lateness = spec.SLOTS_PER_EPOCH + transition_to(spec, state, state.slot + shard_lateness) + + if shard is None: + shard = 0 + if validator_index is None: + validator_index = spec.get_beacon_committee(state, state.slot, shard)[0] + + offset_slots = spec.get_offset_slots(state, shard) + if block_lengths is None: + block_lengths = [2**15 // 3] * len(offset_slots) - validator_index = spec.get_beacon_committee( - state, - state.slot, - shard, - )[0] custody_secret = get_custody_secret(spec, state, validator_index) - shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, - [2**15 // 3] * len(offset_slots), custody_secret) + shard_transition, slashable_test_vector = get_custody_slashable_shard_transition( + spec, + state.slot, + block_lengths, + custody_secret, + slashable=correct, + ) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -80,154 +95,43 @@ def test_custody_slashing(spec, state): slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, custody_secret, slashable_test_vector) - yield from run_custody_slashing_processing(spec, state, slashing, correct=True) + + if slashing_message_data is not None: + slashing.message.data = slashing_message_data + + yield from run_custody_slashing_processing(spec, state, slashing, valid=valid, correct=correct) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_custody_slashing(spec, state): + yield from run_standard_custody_slashing_test(spec, state) @with_all_phases_except(['phase0']) @spec_state_test def test_incorrect_custody_slashing(spec, state): - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - - validator_index = spec.get_beacon_committee( - state, - state.slot, - shard, - )[0] - custody_secret = get_custody_secret(spec, state, validator_index) - shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, - [2**15 // 3] * len(offset_slots), custody_secret, slashable=False) - - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=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)) - - slashing = get_valid_custody_slashing(spec, state, attestation, - shard_transition, custody_secret, slashable_test_vector) - - yield from run_custody_slashing_processing(spec, state, slashing, correct=False) + yield from run_standard_custody_slashing_test(spec, state, correct=False) @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) - - validator_index = spec.get_beacon_committee( - state, - state.slot, - shard, - )[0] - custody_secret = get_custody_secret(spec, state, validator_index) - shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, - [2**15 // 3] * len(offset_slots), custody_secret) - - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=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)) - - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, - custody_secret, slashable_test_vector) - yield from run_custody_slashing_processing(spec, state, slashing, correct=True) + yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 3) @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 * 10) - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - - validator_index = spec.get_beacon_committee( - state, - state.slot, - shard, - )[0] - custody_secret = get_custody_secret(spec, state, validator_index) - shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, - [2**15 // 3] * len(offset_slots), custody_secret) - - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=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)) - - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, - custody_secret, slashable_test_vector) - yield from run_custody_slashing_processing(spec, state, slashing, correct=True) - - -@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) - - validator_index = spec.get_beacon_committee( - state, - state.slot, - shard, - )[0] - custody_secret = get_custody_secret(spec, state, validator_index) - shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, - [2**15 // 3] * len(offset_slots), custody_secret) - - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, - custody_secret, slashable_test_vector) - yield from run_custody_slashing_processing(spec, state, slashing, correct=True) + yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 10) @with_all_phases_except(['phase0']) @spec_state_test def test_invalid_custody_slashing(spec, state): - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - - validator_index = spec.get_beacon_committee( + yield from run_standard_custody_slashing_test( + spec, state, - state.slot, - shard, - )[0] - custody_secret = get_custody_secret(spec, state, validator_index) - shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, - [2**15 // 3] * len(offset_slots), custody_secret) - - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=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)) - - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, - custody_secret, slashable_test_vector) - - slashing.message.data = ByteList[spec.MAX_SHARD_BLOCK_SIZE]() - - yield from run_custody_slashing_processing(spec, state, slashing, valid=False) + slashing_message_data=ByteList[spec.MAX_SHARD_BLOCK_SIZE](), + valid=False, + ) From 113563176ae2e94ff01d473b310b012338bad79b Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Tue, 16 Jun 2020 14:36:12 +0100 Subject: [PATCH 36/45] Updated .gitignore --- .gitignore | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.gitignore b/.gitignore index bcd96f885..ecbeb9a3c 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,13 @@ tests/core/pyspec/test-reports tests/core/pyspec/eth2spec/test_results.xml *.egg-info + +# flake8 config +tox.ini + +# VS code files +.vscode +*.code-workspace + +# npm (for doctoc) +package-lock.json \ No newline at end of file From 2dee432536bf32df86e657464613f6df44ccffb6 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Tue, 16 Jun 2020 14:43:34 +0100 Subject: [PATCH 37/45] Refactor getting Merkle root of data part of ByteList --- tests/core/pyspec/eth2spec/test/helpers/custody.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index 4f91e6dc5..1993e566d 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -136,6 +136,12 @@ def build_proof(anchor, leaf_index): return list(reversed(proof)) +def get_block_data_merkle_root(data_as_bytelist): + # To get the Merkle root of the block data, we need the Merkle root without the length Mixing + # The below implements this in the Remerkleable framework + return data_as_bytelist.get_backing().get_left().merkle_root() + + 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) @@ -160,8 +166,8 @@ def get_custody_test_vector(bytelength, offset=0): def get_shard_transition(spec, start_slot, block_lengths): - b = [ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(x)) - .get_backing().get_left().merkle_root() for x in block_lengths] + b = [get_block_data_merkle_root(ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(x))) + for x in block_lengths] shard_transition = spec.ShardTransition( start_slot=start_slot, shard_block_lengths=block_lengths, @@ -195,5 +201,5 @@ def get_custody_slashable_shard_transition(spec, start_slot, block_lengths, cust slashable_test_vector = get_custody_slashable_test_vector(spec, custody_secret, block_lengths[0], slashable=slashable) block_data = ByteList[spec.MAX_SHARD_BLOCK_SIZE](slashable_test_vector) - shard_transition.shard_data_roots[0] = block_data.get_backing().get_left().merkle_root() + shard_transition.shard_data_roots[0] = get_block_data_merkle_root(block_data) return shard_transition, slashable_test_vector From a4c2950c4a8bfb28dad81b18db3526d72c9637ca Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Tue, 16 Jun 2020 14:48:00 +0100 Subject: [PATCH 38/45] Phase 1 validator guide stub. This is to start collecting important details with correct validator operation. --- specs/phase1/validator.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 specs/phase1/validator.md diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md new file mode 100644 index 000000000..310be3129 --- /dev/null +++ b/specs/phase1/validator.md @@ -0,0 +1,39 @@ +# Ethereum 2.0 Phase 1 -- Updates to honest validator + +**Notice**: This document is a work-in-progress for researchers and implementers. This is so far only a skeleton that describes non-obvious pitfalls so that they won't be forgotten when the full version of the document is prepared + +## Table of contents + + + + + + +- [Introduction](#introduction) +- [How to avoid slashing](#how-to-avoid-slashing) + - [Custody slashing](#custody-slashing) + + + + +## Introduction + +This is an update to the [Phase 0 -- Honest validator](../phase0/validator.md) honest validator guide. This will only describe the differences in phase 1. All behaviours in phase 0 remain valid + +## How to avoid slashing + +### Custody slashing + +To avoid custody slashings, the attester must never sign any shard transition for which the custody bit is one. The custody bit is computed using the custody secret: + +```python +def get_custody_secret(spec, state, validator_index, epoch=None): + period = spec.get_custody_period_for_validator(validator_index, epoch if epoch is not None + else spec.get_current_epoch(state)) + epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, validator_index) + domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch_to_sign) + signing_root = spec.compute_signing_root(spec.Epoch(epoch_to_sign), domain) + return bls.Sign(privkeys[validator_index], signing_root) +``` + +Note that the valid custody secret is always the one for the **attestation target epoch**, not to be confused with the epoch in which the shard block was generated. While they are the same most of the time, getting this wrong at custody epoch boundaries would result in a custody slashing. From 8186594dfecb74ae006a57cdd928195ffe1752b2 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Tue, 16 Jun 2020 14:57:06 +0100 Subject: [PATCH 39/45] Rename to get_sample_shard_transition --- .../pyspec/eth2spec/test/helpers/custody.py | 4 ++-- .../test_process_chunk_challenge.py | 22 +++++++++---------- .../test_process_challenge_deadlines.py | 4 ++-- .../test_process_custody_final_updates.py | 6 ++--- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index 1993e566d..f63a07099 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -165,7 +165,7 @@ def get_custody_test_vector(bytelength, offset=0): return (b"".join((i + offset).to_bytes(4, "little") for i in range(ints)))[:bytelength] -def get_shard_transition(spec, start_slot, block_lengths): +def get_sample_shard_transition(spec, start_slot, block_lengths): b = [get_block_data_merkle_root(ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(x))) for x in block_lengths] shard_transition = spec.ShardTransition( @@ -197,7 +197,7 @@ def get_custody_slashable_test_vector(spec, custody_secret, length, slashable=Tr def get_custody_slashable_shard_transition(spec, start_slot, block_lengths, custody_secret, slashable=True): - shard_transition = get_shard_transition(spec, start_slot, block_lengths) + shard_transition = get_sample_shard_transition(spec, start_slot, block_lengths) slashable_test_vector = get_custody_slashable_test_vector(spec, custody_secret, block_lengths[0], slashable=slashable) block_data = ByteList[spec.MAX_SHARD_BLOCK_SIZE](slashable_test_vector) 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 index 94b1c3b11..bdb4325fd 100644 --- 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 @@ -1,7 +1,7 @@ from eth2spec.test.helpers.custody import ( get_valid_chunk_challenge, get_valid_custody_chunk_response, - get_shard_transition, + get_sample_shard_transition, ) from eth2spec.test.helpers.attestations import ( get_valid_on_time_attestation, @@ -70,7 +70,7 @@ 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)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -91,7 +91,7 @@ def test_challenge_empty_element_replaced(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)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -114,7 +114,7 @@ def test_duplicate_challenge(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)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -137,7 +137,7 @@ def test_second_challenge(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)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -163,7 +163,7 @@ def test_multiple_epochs_custody(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -185,7 +185,7 @@ def test_many_epochs_custody(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -207,7 +207,7 @@ def test_off_chain_attestation(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -225,7 +225,7 @@ def test_custody_response(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -253,7 +253,7 @@ def test_custody_response_multiple_epochs(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -281,7 +281,7 @@ def test_custody_response_many_epochs(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py index 6590d8af9..f3675732a 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py @@ -1,6 +1,6 @@ from eth2spec.test.helpers.custody import ( get_valid_chunk_challenge, - get_shard_transition, + get_sample_shard_transition, ) from eth2spec.test.helpers.attestations import ( get_valid_on_time_attestation, @@ -28,7 +28,7 @@ def test_validator_slashed_after_chunk_challenge(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)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_custody_final_updates.py b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_custody_final_updates.py index b9afe03bf..6ca6c8c99 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_custody_final_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_custody_final_updates.py @@ -2,7 +2,7 @@ from eth2spec.test.helpers.custody import ( get_valid_chunk_challenge, get_valid_custody_chunk_response, get_valid_custody_key_reveal, - get_shard_transition + get_sample_shard_transition ) from eth2spec.test.helpers.attestations import ( get_valid_on_time_attestation, @@ -66,7 +66,7 @@ def test_validator_withdrawal_suspend_after_chunk_challenge(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)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -114,7 +114,7 @@ def test_validator_withdrawal_resume_after_chunk_challenge_response(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)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) From 4b8b3f2cbc62c49d8ab9d989164aa0fe079b1213 Mon Sep 17 00:00:00 2001 From: dankrad Date: Tue, 16 Jun 2020 15:05:01 +0100 Subject: [PATCH 40/45] Update specs/phase1/custody-game.md Co-authored-by: Hsiao-Wei Wang --- specs/phase1/custody-game.md | 1 - 1 file changed, 1 deletion(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 4cf047405..9b20dea55 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -368,7 +368,6 @@ def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge ```python def process_chunk_challenge_response(state: BeaconState, response: CustodyChunkResponse) -> None: - # Get matching challenge (if any) from records matching_challenges = [ record for record in state.custody_chunk_challenge_records From 4bc849bcc2c0deb4e1bba67264d75e614bf5e146 Mon Sep 17 00:00:00 2001 From: dankrad Date: Tue, 16 Jun 2020 15:05:12 +0100 Subject: [PATCH 41/45] Update specs/phase1/custody-game.md Co-authored-by: Hsiao-Wei Wang --- specs/phase1/custody-game.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 9b20dea55..8920becda 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -373,7 +373,7 @@ def process_chunk_challenge_response(state: BeaconState, record for record in state.custody_chunk_challenge_records if record.challenge_index == response.challenge_index ] - assert len(matching_challenges) > 0 + assert len(matching_challenges) == 1 challenge = matching_challenges[0] # Verify chunk index assert response.chunk_index == challenge.chunk_index From df1a9325347dee7991127653346498677e9eecdf Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Tue, 16 Jun 2020 15:16:20 +0100 Subject: [PATCH 42/45] Rename misleading variable all_secrets_are_revealed --- specs/phase1/custody-game.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 8920becda..94d739ba9 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -595,13 +595,13 @@ def process_custody_final_updates(state: BeaconState) -> None: validator_indices_in_records = set([record.responder_index for record in records]) for index, validator in enumerate(state.validators): if validator.exit_epoch != FAR_FUTURE_EPOCH: - all_secrets_are_revealed = validator.all_custody_secrets_revealed_epoch == FAR_FUTURE_EPOCH - if index in validator_indices_in_records or all_secrets_are_revealed: + not_all_secrets_are_revealed = validator.all_custody_secrets_revealed_epoch == FAR_FUTURE_EPOCH + if index in validator_indices_in_records or not_all_secrets_are_revealed: # Delay withdrawable epochs if challenge records are not empty or not all # custody secrets revealed validator.withdrawable_epoch = FAR_FUTURE_EPOCH else: - # Reset withdrawable epochs if challenge records are empty + # Reset withdrawable epochs if challenge records are empty and all secrets are revealed if validator.withdrawable_epoch == FAR_FUTURE_EPOCH: validator.withdrawable_epoch = Epoch(validator.all_custody_secrets_revealed_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY) From 5e5a951c6f85c2fd886f3707eb013b1e70fdb9eb Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 16 Jun 2020 23:49:41 +0800 Subject: [PATCH 43/45] Fix conflicts --- configs/mainnet/phase0.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/configs/mainnet/phase0.yaml b/configs/mainnet/phase0.yaml index 7677e939b..39cfddf77 100644 --- a/configs/mainnet/phase0.yaml +++ b/configs/mainnet/phase0.yaml @@ -151,9 +151,3 @@ DOMAIN_DEPOSIT: 0x03000000 DOMAIN_VOLUNTARY_EXIT: 0x04000000 DOMAIN_SELECTION_PROOF: 0x05000000 DOMAIN_AGGREGATE_AND_PROOF: 0x06000000 -<<<<<<< HEAD:configs/mainnet.yaml -# Phase 1 -DOMAIN_SHARD_PROPOSAL: 0x80000000 -DOMAIN_SHARD_COMMITTEE: 0x81000000 -DOMAIN_LIGHT_CLIENT: 0x82000000 -DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000 From 58935c19d6e8590cf69be6e6e6246e946668e044 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Tue, 16 Jun 2020 17:23:09 +0100 Subject: [PATCH 44/45] Move constant --- specs/phase1/beacon-chain.md | 5 ----- specs/phase1/custody-game.md | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index a3f9ec4e9..59e0bb6da 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -142,11 +142,6 @@ Configuration is not namespaced. Instead it is strictly an extension; | `ONLINE_PERIOD` | `OnlineEpochs(2**3)` (= 8) | online epochs | ~51 mins | | `LIGHT_CLIENT_COMMITTEE_PERIOD` | `Epoch(2**8)` (= 256) | epochs | ~27 hours | -### Max operations per block -| Name | Value | -| - | - | -| `MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS` | `2**20` (= 1,048,576) | - ### Domain types | Name | Value | diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 94d739ba9..3d4645068 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -79,6 +79,7 @@ This document details the beacon chain additions and changes in Phase 1 of Ether | Name | Value | | - | - | +| `MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS` | `2**20` (= 1,048,576) | | `MAX_CUSTODY_KEY_REVEALS` | `2**8` (= 256) | | `MAX_EARLY_DERIVED_SECRET_REVEALS` | `2**0` (= 1) | | `MAX_CUSTODY_CHUNK_CHALLENGES` | `2**2` (= 4) | From e6e694fad995affe9a79cd92cf6e81e0e4874617 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Tue, 16 Jun 2020 17:35:33 +0100 Subject: [PATCH 45/45] Fix toc --- specs/phase1/beacon-chain.md | 1 - 1 file changed, 1 deletion(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 59e0bb6da..3206a50de 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -16,7 +16,6 @@ - [Gwei values](#gwei-values) - [Initial values](#initial-values) - [Time parameters](#time-parameters) - - [Max operations per block](#max-operations-per-block) - [Domain types](#domain-types) - [Updated containers](#updated-containers) - [Extended `AttestationData`](#extended-attestationdata)