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)