diff --git a/.circleci/config.yml b/.circleci/config.yml index fcdf483d5..157c56ca5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -176,7 +176,7 @@ jobs: - checkout - run: name: Check table of contents - command: sudo npm install -g doctoc@2 && make check_toc + command: sudo npm install -g doctoc@2.2.0 && make check_toc codespell: docker: - image: circleci/python:3.9 diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 41a80ab92..b27c90765 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -50,7 +50,7 @@ jobs: with: ref: ${{ github.event.inputs.commitRef || env.DEFAULT_BRANCH }} - name: Check table of contents - run: sudo npm install -g doctoc@2 && make check_toc + run: sudo npm install -g doctoc@2.2.0 && make check_toc codespell: runs-on: self-hosted diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 365bc1136..9206ab77d 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -53,6 +53,9 @@ DENEB_FORK_EPOCH: 18446744073709551615 # EIP6110 EIP6110_FORK_VERSION: 0x05000000 # temporary stub EIP6110_FORK_EPOCH: 18446744073709551615 +# WHISK +WHISK_FORK_VERSION: 0x06000000 # temporary stub +WHISK_FORK_EPOCH: 18446744073709551615 # Time parameters diff --git a/configs/minimal.yaml b/configs/minimal.yaml index b22a7165e..256a39d1c 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -52,6 +52,9 @@ DENEB_FORK_EPOCH: 18446744073709551615 # EIP6110 EIP6110_FORK_VERSION: 0x05000001 EIP6110_FORK_EPOCH: 18446744073709551615 +# WHISK +WHISK_FORK_VERSION: 0x06000001 +WHISK_FORK_EPOCH: 18446744073709551615 # Time parameters diff --git a/specs/_features/whisk/beacon-chain.md b/specs/_features/whisk/beacon-chain.md index bad818837..b10611a8d 100644 --- a/specs/_features/whisk/beacon-chain.md +++ b/specs/_features/whisk/beacon-chain.md @@ -15,7 +15,6 @@ - [Curdleproofs and opening proofs](#curdleproofs-and-opening-proofs) - [Epoch processing](#epoch-processing) - [`WhiskTracker`](#whisktracker) - - [`Validator`](#validator) - [`BeaconState`](#beaconstate) - [Block processing](#block-processing) - [Block header](#block-header) @@ -23,6 +22,7 @@ - [`BeaconBlockBody`](#beaconblockbody) - [Deposits](#deposits) - [`get_beacon_proposer_index`](#get_beacon_proposer_index) +- [Testing](#testing) @@ -124,23 +124,6 @@ class WhiskTracker(Container): k_r_G: BLSG1Point # k * r * G ``` -### `Validator` - -```python -class Validator(Container): - pubkey: BLSPubkey - withdrawal_credentials: Bytes32 # Commitment to pubkey for withdrawals - effective_balance: Gwei # Balance at stake - slashed: boolean - # Status epochs - activation_eligibility_epoch: Epoch # When criteria for activation were met - activation_epoch: Epoch - exit_epoch: Epoch - withdrawable_epoch: Epoch # When validator can withdraw funds - whisk_tracker: WhiskTracker # Whisk tracker (r * G, k * r * G) [New in Whisk] - whisk_k_commitment: BLSG1Point # Whisk k commitment k * BLS_G1_GENERATOR [New in Whisk] -``` - ### `BeaconState` ```python @@ -186,8 +169,11 @@ class BeaconState(Container): next_withdrawal_validator_index: ValidatorIndex # Deep history valid from Capella onwards historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT] + # Whisk whisk_candidate_trackers: Vector[WhiskTracker, WHISK_CANDIDATE_TRACKERS_COUNT] # [New in Whisk] whisk_proposer_trackers: Vector[WhiskTracker, WHISK_PROPOSER_TRACKERS_COUNT] # [New in Whisk] + whisk_trackers: List[WhiskTracker, VALIDATOR_REGISTRY_LIMIT] # [New in Whisk] + whisk_k_commitments: List[BLSG1Point, VALIDATOR_REGISTRY_LIMIT] # [New in Whisk] ``` ```python @@ -203,7 +189,7 @@ def select_whisk_trackers(state: BeaconState, epoch: Epoch) -> None: for i in range(WHISK_CANDIDATE_TRACKERS_COUNT): seed = hash(get_seed(state, epoch, DOMAIN_WHISK_CANDIDATE_SELECTION) + uint_to_bytes(i)) candidate_index = compute_proposer_index(state, active_validator_indices, seed) # sample by effective balance - state.whisk_candidate_trackers[i] = state.validators[candidate_index].whisk_tracker + state.whisk_candidate_trackers[i] = state.whisk_trackers[candidate_index] ``` ```python @@ -237,7 +223,7 @@ def process_epoch(state: BeaconState) -> None: ```python def process_whisk_opening_proof(state: BeaconState, block: BeaconBlock) -> None: tracker = state.whisk_proposer_trackers[state.slot % WHISK_PROPOSER_TRACKERS_COUNT] - k_commitment = state.validators[block.proposer_index].whisk_k_commitment + k_commitment = state.whisk_k_commitments[block.proposer_index] assert IsValidWhiskOpeningProof(tracker, k_commitment, block.body.whisk_opening_proof) ``` @@ -297,7 +283,7 @@ class BeaconBlockBody(capella.BeaconBlockBody): whisk_shuffle_proof_M_commitment: BLSG1Point # [New in Whisk] whisk_registration_proof: WhiskTrackerProof # [New in Whisk] whisk_tracker: WhiskTracker # [New in Whisk] - whisk_k_commitment: BLSG1Point # [New in Whisk] + whisk_k_commitment: BLSG1Point # k * BLS_G1_GENERATOR [New in Whisk] ``` ```python @@ -306,9 +292,10 @@ def get_shuffle_indices(randao_reveal: BLSSignature) -> Sequence[uint64]: Given a `randao_reveal` return the list of indices that got shuffled from the entire candidate set """ indices = [] - for i in WHISK_VALIDATORS_PER_SHUFFLE: + for i in range(0, WHISK_VALIDATORS_PER_SHUFFLE): # XXX ensure we are not suffering from modulo bias - shuffle_index = uint256(hash(randao_reveal + uint_to_bytes(i))) % WHISK_CANDIDATE_TRACKERS_COUNT + pre_image = randao_reveal + uint_to_bytes(uint64(i)) + shuffle_index = bytes_to_uint64(hash(pre_image)[0:8]) % WHISK_CANDIDATE_TRACKERS_COUNT indices.append(shuffle_index) return indices @@ -319,20 +306,23 @@ def process_shuffled_trackers(state: BeaconState, body: BeaconBlockBody) -> None # Check the shuffle proof shuffle_indices = get_shuffle_indices(body.randao_reveal) pre_shuffle_trackers = [state.whisk_candidate_trackers[i] for i in shuffle_indices] - post_shuffle_trackers = body.whisk_post_shuffle_trackers shuffle_epoch = get_current_epoch(state) % WHISK_EPOCHS_PER_SHUFFLING_PHASE if shuffle_epoch + WHISK_PROPOSER_SELECTION_GAP + 1 >= WHISK_EPOCHS_PER_SHUFFLING_PHASE: - # Require unchanged trackers during cooldown - assert pre_shuffle_trackers == post_shuffle_trackers + # Require trackers set to zero during cooldown + assert body.whisk_post_shuffle_trackers == Vector[WhiskTracker, WHISK_VALIDATORS_PER_SHUFFLE]() + assert body.whisk_shuffle_proof_M_commitment == BLSG1Point() + assert body.whisk_shuffle_proof == WhiskShuffleProof() + post_shuffle_trackers = pre_shuffle_trackers else: # Require shuffled trackers during shuffle assert IsValidWhiskShuffleProof( pre_shuffle_trackers, - post_shuffle_trackers, + body.whisk_post_shuffle_trackers, body.whisk_shuffle_proof_M_commitment, body.whisk_shuffle_proof, ) + post_shuffle_trackers = body.whisk_post_shuffle_trackers # Shuffle candidate trackers for i, shuffle_index in enumerate(shuffle_indices): @@ -341,7 +331,7 @@ def process_shuffled_trackers(state: BeaconState, body: BeaconBlockBody) -> None ```python def is_k_commitment_unique(state: BeaconState, k_commitment: BLSG1Point) -> bool: - return all([validator.whisk_k_commitment != k_commitment for validator in state.validators]) + return all([whisk_k_commitment != k_commitment for whisk_k_commitment in state.whisk_k_commitments]) ``` ```python @@ -382,49 +372,30 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: ### Deposits +```python +def get_initial_whisk_k(validator_index: ValidatorIndex, counter: int) -> BLSFieldElement: + # hash `validator_index || counter` + return BLSFieldElement(bytes_to_bls_field(hash(uint_to_bytes(validator_index) + uint_to_bytes(uint64(counter))))) +``` + ```python def get_unique_whisk_k(state: BeaconState, validator_index: ValidatorIndex) -> BLSFieldElement: counter = 0 while True: - # hash `validator_index || counter` - k = BLSFieldElement(bytes_to_bls_field(hash(uint_to_bytes(validator_index) + uint_to_bytes(uint64(counter))))) + k = get_initial_whisk_k(validator_index, counter) if is_k_commitment_unique(state, BLSG1ScalarMultiply(k, BLS_G1_GENERATOR)): return k # unique by trial and error counter += 1 ``` ```python -def get_initial_commitments(k: BLSFieldElement) -> Tuple[BLSG1Point, WhiskTracker]: - return ( - BLSG1ScalarMultiply(k, BLS_G1_GENERATOR), - WhiskTracker(r_G=BLS_G1_GENERATOR, k_r_G=BLSG1ScalarMultiply(k, BLS_G1_GENERATOR)) - ) +def get_k_commitment(k: BLSFieldElement) -> BLSG1Point: + return BLSG1ScalarMultiply(k, BLS_G1_GENERATOR) ``` ```python -def get_validator_from_deposit_whisk( - state: BeaconState, - pubkey: BLSPubkey, - withdrawal_credentials: Bytes32, - amount: uint64 -) -> Validator: - effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) - k = get_unique_whisk_k(state, ValidatorIndex(len(state.validators))) - whisk_k_commitment, whisk_tracker = get_initial_commitments(k) - - validator = Validator( - pubkey=pubkey, - withdrawal_credentials=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, - # Whisk fields - whisk_tracker=whisk_tracker, - whisk_k_commitment=whisk_k_commitment, - ) - return validator +def get_initial_tracker(k: BLSFieldElement) -> WhiskTracker: + return WhiskTracker(r_G=BLS_G1_GENERATOR, k_r_G=BLSG1ScalarMultiply(k, BLS_G1_GENERATOR)) ``` ```python @@ -446,13 +417,16 @@ def apply_deposit(state: BeaconState, # Initialize validator if the deposit signature is valid if bls.Verify(pubkey, signing_root, signature): index = get_index_for_new_validator(state) - validator = get_validator_from_deposit_whisk(state, pubkey, withdrawal_credentials, amount) + validator = get_validator_from_deposit(pubkey, withdrawal_credentials, amount) set_or_append_list(state.validators, index, validator) set_or_append_list(state.balances, index, amount) - # [New in Altair] set_or_append_list(state.previous_epoch_participation, index, ParticipationFlags(0b0000_0000)) set_or_append_list(state.current_epoch_participation, index, ParticipationFlags(0b0000_0000)) set_or_append_list(state.inactivity_scores, index, uint64(0)) + # [New in Whisk] + k = get_unique_whisk_k(state, ValidatorIndex(len(state.validators) - 1)) + state.whisk_trackers.append(get_initial_tracker(k)) + state.whisk_k_commitments.append(get_k_commitment(k)) else: # Increase balance by deposit amount index = ValidatorIndex(validator_pubkeys.index(pubkey)) @@ -469,3 +443,25 @@ def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: assert state.latest_block_header.slot == state.slot # sanity check `process_block_header` has been called return state.latest_block_header.proposer_index ``` + +## Testing + +*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure Whisk testing only. + +```python +def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, + eth1_timestamp: uint64, + deposits: Sequence[Deposit], + execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader() + ) -> BeaconState: + state_capella = capella.initialize_beacon_state_from_eth1( + eth1_block_hash, + eth1_timestamp, + deposits, + execution_payload_header, + ) + state = upgrade_to_whisk(state_capella) + state.fork.previous_version = WHISK_FORK_VERSION + state.fork.current_version = WHISK_FORK_VERSION + return state +``` diff --git a/specs/_features/whisk/fork.md b/specs/_features/whisk/fork.md index 189d8c5b3..f66026037 100644 --- a/specs/_features/whisk/fork.md +++ b/specs/_features/whisk/fork.md @@ -68,6 +68,11 @@ def whisk_proposer_selection(state: BeaconState, epoch: Epoch) -> None: ```python def upgrade_to_whisk(pre: bellatrix.BeaconState) -> BeaconState: + # Compute initial unsafe trackers for all validators + ks = [get_initial_whisk_k(ValidatorIndex(validator_index), 0) for validator_index in range(len(pre.validators))] + whisk_k_commitments = [get_k_commitment(k) for k in ks] + whisk_trackers = [get_initial_tracker(k) for k in ks] + epoch = bellatrix.get_current_epoch(pre) post = BeaconState( # Versioning @@ -104,27 +109,19 @@ def upgrade_to_whisk(pre: bellatrix.BeaconState) -> BeaconState: current_justified_checkpoint=pre.current_justified_checkpoint, finalized_checkpoint=pre.finalized_checkpoint, # Inactivity - inactivity_scores=pre.inactivity_Scores, + inactivity_scores=pre.inactivity_scores, + # Sync + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + # Execution-layer + latest_execution_payload_header=pre.latest_execution_payload_header, + # Whisk + whisk_proposer_trackers=[WhiskTracker() for _ in range(WHISK_PROPOSER_TRACKERS_COUNT)], # [New in Whisk] + whisk_candidate_trackers=[WhiskTracker() for _ in range(WHISK_CANDIDATE_TRACKERS_COUNT)], # [New in Whisk] + whisk_trackers=whisk_trackers, # [New in Whisk] + whisk_k_commitments=whisk_k_commitments, # [New in Whisk] ) - # Initialize all validators with predictable commitments - for val_index, pre_validator in enumerate(pre.validators): - whisk_commitment, whisk_tracker = get_initial_commitments(get_unique_whisk_k(post, ValidatorIndex(val_index))) - - post_validator = Validator( - pubkey=pre_validator.pubkey, - withdrawal_credentials=pre_validator.withdrawal_credentials, - effective_balance=pre_validator.effective_balance, - slashed=pre_validator.slashed, - activation_eligibility_epoch=pre_validator.activation_eligibility_epoch, - activation_epoch=pre_validator.activation_epoch, - exit_epoch=pre_validator.exit_epoch, - withdrawable_epoch=pre_validator.withdrawable_epoch, - whisk_commitment=whisk_commitment, - whisk_tracker=whisk_tracker, - ) - post.validators.append(post_validator) - # Do a candidate selection followed by a proposer selection so that we have proposers for the upcoming day # Use an old epoch when selecting candidates so that we don't get the same seed as in the next candidate selection whisk_candidate_selection(post, epoch - WHISK_PROPOSER_SELECTION_GAP - 1) diff --git a/specs/altair/beacon-chain.md b/specs/altair/beacon-chain.md index 8c3a8877e..1de39d6fc 100644 --- a/specs/altair/beacon-chain.md +++ b/specs/altair/beacon-chain.md @@ -45,7 +45,7 @@ - [Modified `slash_validator`](#modified-slash_validator) - [Block processing](#block-processing) - [Modified `process_attestation`](#modified-process_attestation) - - [Modified `apply_deposit`](#modified-apply_deposit) + - [Modified `add_validator_to_registry`](#modified-add_validator_to_registry) - [Sync aggregate processing](#sync-aggregate-processing) - [Epoch processing](#epoch-processing) - [Justification and finalization](#justification-and-finalization) @@ -508,40 +508,23 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: increase_balance(state, get_beacon_proposer_index(state), proposer_reward) ``` -#### Modified `apply_deposit` +#### Modified `add_validator_to_registry` -*Note*: The function `apply_deposit` is modified to initialize `inactivity_scores`, `previous_epoch_participation`, and `current_epoch_participation`. +*Note*: The function `add_validator_to_registry` is modified to initialize `inactivity_scores`, `previous_epoch_participation`, and `current_epoch_participation`. ```python -def apply_deposit(state: BeaconState, - pubkey: BLSPubkey, - withdrawal_credentials: Bytes32, - amount: uint64, - signature: BLSSignature) -> None: - validator_pubkeys = [validator.pubkey for validator 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=pubkey, - withdrawal_credentials=withdrawal_credentials, - amount=amount, - ) - domain = compute_domain(DOMAIN_DEPOSIT) # Fork-agnostic domain since deposits are valid across forks - signing_root = compute_signing_root(deposit_message, domain) - # Initialize validator if the deposit signature is valid - if bls.Verify(pubkey, signing_root, signature): - index = get_index_for_new_validator(state) - validator = get_validator_from_deposit(pubkey, withdrawal_credentials, amount) - set_or_append_list(state.validators, index, validator) - set_or_append_list(state.balances, index, amount) - # [New in Altair] - set_or_append_list(state.previous_epoch_participation, index, ParticipationFlags(0b0000_0000)) - set_or_append_list(state.current_epoch_participation, index, ParticipationFlags(0b0000_0000)) - set_or_append_list(state.inactivity_scores, index, uint64(0)) - else: - # Increase balance by deposit amount - index = ValidatorIndex(validator_pubkeys.index(pubkey)) - increase_balance(state, index, amount) +def add_validator_to_registry(state: BeaconState, + pubkey: BLSPubkey, + withdrawal_credentials: Bytes32, + amount: uint64) -> None: + index = get_index_for_new_validator(state) + validator = get_validator_from_deposit(pubkey, withdrawal_credentials, amount) + set_or_append_list(state.validators, index, validator) + set_or_append_list(state.balances, index, amount) + # [New in Altair] + set_or_append_list(state.previous_epoch_participation, index, ParticipationFlags(0b0000_0000)) + set_or_append_list(state.current_epoch_participation, index, ParticipationFlags(0b0000_0000)) + set_or_append_list(state.inactivity_scores, index, uint64(0)) ``` #### Sync aggregate processing diff --git a/specs/deneb/beacon-chain.md b/specs/deneb/beacon-chain.md index 148dc96d8..3189ee190 100644 --- a/specs/deneb/beacon-chain.md +++ b/specs/deneb/beacon-chain.md @@ -33,7 +33,8 @@ - [Modified `verify_and_notify_new_payload`](#modified-verify_and_notify_new_payload) - [Block processing](#block-processing) - [Execution payload](#execution-payload) - - [`process_execution_payload`](#process_execution_payload) + - [Modified `process_execution_payload`](#modified-process_execution_payload) + - [Modified `process_voluntary_exit`](#modified-process_voluntary_exit) - [Testing](#testing) @@ -42,7 +43,8 @@ ## Introduction Deneb is a consensus-layer upgrade containing a number of features. Including: -* [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844): Shard Blob Transactions scale data-availability of Ethereum in a simple, forwards-compatible manner. +* [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844): Shard Blob Transactions scale data-availability of Ethereum in a simple, forwards-compatible manner +* [EIP-7044](https://github.com/ethereum/EIPs/pull/7044): Perpetually Valid Signed Voluntary Exits ## Custom types @@ -221,7 +223,7 @@ def verify_and_notify_new_payload(self: ExecutionEngine, #### Execution payload -##### `process_execution_payload` +##### Modified `process_execution_payload` ```python def process_execution_payload(state: BeaconState, body: BeaconBlockBody, execution_engine: ExecutionEngine) -> None: @@ -266,6 +268,31 @@ def process_execution_payload(state: BeaconState, body: BeaconBlockBody, executi ) ``` +#### Modified `process_voluntary_exit` + +*Note*: The function `process_voluntary_exit` is modified to use the a fixed fork version -- `CAPELLA_FORK_VERSION` -- for EIP-7044 + +```python +def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVoluntaryExit) -> None: + voluntary_exit = signed_voluntary_exit.message + validator = state.validators[voluntary_exit.validator_index] + # Verify the validator is active + assert is_active_validator(validator, get_current_epoch(state)) + # Verify exit has not been initiated + assert validator.exit_epoch == FAR_FUTURE_EPOCH + # Exits must specify an epoch when they become valid; they are not valid before then + assert get_current_epoch(state) >= voluntary_exit.epoch + # Verify the validator has been active long enough + assert get_current_epoch(state) >= validator.activation_epoch + SHARD_COMMITTEE_PERIOD + # Verify signature + # [Modified in Deneb:EIP7044] + domain = compute_domain(DOMAIN_VOLUNTARY_EXIT, CAPELLA_FORK_VERSION, state.genesis_validators_root) + signing_root = compute_signing_root(voluntary_exit, domain) + assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature) + # Initiate exit + initiate_validator_exit(state, voluntary_exit.validator_index) +``` + ## Testing *Note*: The function `initialize_beacon_state_from_eth1` is modified for pure Deneb testing only. diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index b77e017ab..214c0b77e 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1849,6 +1849,15 @@ def get_validator_from_deposit(pubkey: BLSPubkey, withdrawal_credentials: Bytes3 ) ``` +```python +def add_validator_to_registry(state: BeaconState, + pubkey: BLSPubkey, + withdrawal_credentials: Bytes32, + amount: uint64) -> None: + state.validators.append(get_validator_from_deposit(pubkey, withdrawal_credentials, amount)) + state.balances.append(amount) +``` + ```python def apply_deposit(state: BeaconState, pubkey: BLSPubkey, @@ -1865,12 +1874,8 @@ def apply_deposit(state: BeaconState, ) 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, signature): - return - - # Add validator and balance entries - state.validators.append(get_validator_from_deposit(pubkey, withdrawal_credentials, amount)) - state.balances.append(amount) + if bls.Verify(pubkey, signing_root, signature): + add_validator_to_registry(state, pubkey, withdrawal_credentials, amount) else: # Increase balance by deposit amount index = ValidatorIndex(validator_pubkeys.index(pubkey)) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index c0d18b08f..bbb4c4d42 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -55,7 +55,7 @@ It consists of four main sections: - [ENR structure](#enr-structure) - [Attestation subnet bitfield](#attestation-subnet-bitfield) - [`eth2` field](#eth2-field) - - [Attestation subnet subcription](#attestation-subnet-subcription) + - [Attestation subnet subscription](#attestation-subnet-subscription) - [Design decision rationale](#design-decision-rationale) - [Transport](#transport-1) - [Why are we defining specific transports?](#why-are-we-defining-specific-transports) @@ -1002,7 +1002,7 @@ Clients MAY connect to peers with the same `fork_digest` but a different `next_f Unless `ENRForkID` is manually updated to matching prior to the earlier `next_fork_epoch` of the two clients, these connecting clients will be unable to successfully interact starting at the earlier `next_fork_epoch`. -### Attestation subnet subcription +### Attestation subnet subscription Because Phase 0 does not have shards and thus does not have Shard Committees, there is no stable backbone to the attestation subnets (`beacon_attestation_{subnet_id}`). To provide this stability, each beacon node should: diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/test_process_voluntary_exit.py b/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/test_process_voluntary_exit.py index f4fcaac68..ea3b57a97 100644 --- a/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/test_process_voluntary_exit.py +++ b/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/test_process_voluntary_exit.py @@ -2,6 +2,11 @@ from eth2spec.test.context import ( spec_state_test, always_bls, with_bellatrix_and_later, + with_phases, +) +from eth2spec.test.helpers.constants import ( + BELLATRIX, + CAPELLA, ) from eth2spec.test.helpers.keys import pubkey_to_privkey from eth2spec.test.helpers.state import ( @@ -12,8 +17,10 @@ from eth2spec.test.helpers.voluntary_exits import ( sign_voluntary_exit, ) +BELLATRIX_AND_CAPELLA = [BELLATRIX, CAPELLA] -def _run_voluntary_exit_processing_test( + +def run_voluntary_exit_processing_test( spec, state, fork_version, @@ -51,7 +58,7 @@ def _run_voluntary_exit_processing_test( @spec_state_test @always_bls def test_invalid_voluntary_exit_with_current_fork_version_is_before_fork_epoch(spec, state): - yield from _run_voluntary_exit_processing_test( + yield from run_voluntary_exit_processing_test( spec, state, fork_version=state.fork.current_version, @@ -60,11 +67,11 @@ def test_invalid_voluntary_exit_with_current_fork_version_is_before_fork_epoch(s ) -@with_bellatrix_and_later +@with_phases(BELLATRIX_AND_CAPELLA) @spec_state_test @always_bls def test_voluntary_exit_with_current_fork_version_not_is_before_fork_epoch(spec, state): - yield from _run_voluntary_exit_processing_test( + yield from run_voluntary_exit_processing_test( spec, state, fork_version=state.fork.current_version, @@ -72,13 +79,13 @@ def test_voluntary_exit_with_current_fork_version_not_is_before_fork_epoch(spec, ) -@with_bellatrix_and_later +@with_phases([BELLATRIX, CAPELLA]) @spec_state_test @always_bls def test_voluntary_exit_with_previous_fork_version_is_before_fork_epoch(spec, state): assert state.fork.previous_version != state.fork.current_version - yield from _run_voluntary_exit_processing_test( + yield from run_voluntary_exit_processing_test( spec, state, fork_version=state.fork.previous_version, @@ -86,13 +93,13 @@ def test_voluntary_exit_with_previous_fork_version_is_before_fork_epoch(spec, st ) -@with_bellatrix_and_later +@with_phases(BELLATRIX_AND_CAPELLA) @spec_state_test @always_bls def test_invalid_voluntary_exit_with_previous_fork_version_not_is_before_fork_epoch(spec, state): assert state.fork.previous_version != state.fork.current_version - yield from _run_voluntary_exit_processing_test( + yield from run_voluntary_exit_processing_test( spec, state, fork_version=state.fork.previous_version, @@ -107,7 +114,7 @@ def test_invalid_voluntary_exit_with_previous_fork_version_not_is_before_fork_ep def test_invalid_voluntary_exit_with_genesis_fork_version_is_before_fork_epoch(spec, state): assert spec.config.GENESIS_FORK_VERSION not in (state.fork.previous_version, state.fork.current_version) - yield from _run_voluntary_exit_processing_test( + yield from run_voluntary_exit_processing_test( spec, state, fork_version=spec.config.GENESIS_FORK_VERSION, @@ -122,7 +129,7 @@ def test_invalid_voluntary_exit_with_genesis_fork_version_is_before_fork_epoch(s def test_invalid_voluntary_exit_with_genesis_fork_version_not_is_before_fork_epoch(spec, state): assert spec.config.GENESIS_FORK_VERSION not in (state.fork.previous_version, state.fork.current_version) - yield from _run_voluntary_exit_processing_test( + yield from run_voluntary_exit_processing_test( spec, state, fork_version=spec.config.GENESIS_FORK_VERSION, diff --git a/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_voluntary_exit.py b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_voluntary_exit.py new file mode 100644 index 000000000..b01eaab0e --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_voluntary_exit.py @@ -0,0 +1,88 @@ +from eth2spec.test.context import ( + always_bls, + spec_state_test, + with_deneb_and_later, +) +from eth2spec.test.helpers.constants import ( + DENEB, +) +from eth2spec.test.bellatrix.block_processing.test_process_voluntary_exit import ( + run_voluntary_exit_processing_test, +) + + +@with_deneb_and_later +@spec_state_test +@always_bls +def test_invalid_voluntary_exit_with_current_fork_version_not_is_before_fork_epoch(spec, state): + """ + Since Deneb, the VoluntaryExit domain is fixed to `CAPELLA_FORK_VERSION` + """ + assert state.fork.current_version != spec.config.CAPELLA_FORK_VERSION + yield from run_voluntary_exit_processing_test( + spec, + state, + fork_version=state.fork.current_version, + is_before_fork_epoch=False, + valid=False, + ) + + +@with_deneb_and_later +@spec_state_test +@always_bls +def test_voluntary_exit_with_previous_fork_version_not_is_before_fork_epoch(spec, state): + """ + Since Deneb, the VoluntaryExit domain is fixed to `CAPELLA_FORK_VERSION` + + Note: This test is valid for ``spec.fork == DENEB`` and invalid for subsequent forks + """ + assert state.fork.previous_version != state.fork.current_version + + if spec.fork == DENEB: + assert state.fork.previous_version == spec.config.CAPELLA_FORK_VERSION + yield from run_voluntary_exit_processing_test( + spec, + state, + fork_version=state.fork.previous_version, + is_before_fork_epoch=False, + ) + else: + assert state.fork.previous_version != spec.config.CAPELLA_FORK_VERSION + yield from run_voluntary_exit_processing_test( + spec, + state, + fork_version=state.fork.previous_version, + is_before_fork_epoch=False, + valid=False, + ) + + +@with_deneb_and_later +@spec_state_test +@always_bls +def test_voluntary_exit_with_previous_fork_version_is_before_fork_epoch(spec, state): + """ + Since Deneb, the VoluntaryExit domain is fixed to `CAPELLA_FORK_VERSION` + + Note: This test is valid for ``spec.fork == DENEB`` and invalid for subsequent forks + """ + assert state.fork.previous_version != state.fork.current_version + + if spec.fork == DENEB: + assert state.fork.previous_version == spec.config.CAPELLA_FORK_VERSION + yield from run_voluntary_exit_processing_test( + spec, + state, + fork_version=state.fork.previous_version, + is_before_fork_epoch=True, + ) + else: + assert state.fork.previous_version != spec.config.CAPELLA_FORK_VERSION + yield from run_voluntary_exit_processing_test( + spec, + state, + fork_version=state.fork.previous_version, + is_before_fork_epoch=True, + valid=False, + ) diff --git a/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py b/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py index cac101dff..2e8139db6 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py +++ b/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py @@ -1,29 +1,31 @@ from random import Random from eth2spec.utils import bls from eth2spec.test.context import expect_assertion_error +from eth2spec.test.helpers.forks import is_post_deneb from eth2spec.test.helpers.keys import privkeys def prepare_signed_exits(spec, state, indices, fork_version=None): - if fork_version is None: - domain = spec.get_domain(state, spec.DOMAIN_VOLUNTARY_EXIT) - else: - domain = spec.compute_domain(spec.DOMAIN_VOLUNTARY_EXIT, fork_version, state.genesis_validators_root) - def create_signed_exit(index): - exit = spec.VoluntaryExit( + voluntary_exit = spec.VoluntaryExit( epoch=spec.get_current_epoch(state), validator_index=index, ) - signing_root = spec.compute_signing_root(exit, domain) - return spec.SignedVoluntaryExit(message=exit, signature=bls.Sign(privkeys[index], signing_root)) + return sign_voluntary_exit(spec, state, voluntary_exit, privkeys[index], fork_version=fork_version) return [create_signed_exit(index) for index in indices] def sign_voluntary_exit(spec, state, voluntary_exit, privkey, fork_version=None): if fork_version is None: - domain = spec.get_domain(state, spec.DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch) + if is_post_deneb(spec): + domain = spec.compute_domain( + spec.DOMAIN_VOLUNTARY_EXIT, + spec.config.CAPELLA_FORK_VERSION, + state.genesis_validators_root, + ) + else: + domain = spec.get_domain(state, spec.DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch) else: domain = spec.compute_domain(spec.DOMAIN_VOLUNTARY_EXIT, fork_version, state.genesis_validators_root) diff --git a/tests/generators/operations/main.py b/tests/generators/operations/main.py index 7aea6fc6f..053236c8d 100644 --- a/tests/generators/operations/main.py +++ b/tests/generators/operations/main.py @@ -39,6 +39,7 @@ if __name__ == "__main__": _new_deneb_mods = {key: 'eth2spec.test.deneb.block_processing.test_process_' + key for key in [ 'execution_payload', + 'voluntary_exit', ]} deneb_mods = combine_mods(_new_deneb_mods, capella_mods)