diff --git a/specs/deneb/beacon-chain.md b/specs/deneb/beacon-chain.md index 148dc96d8f..3189ee1906 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/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 f4fcaac689..ea3b57a97a 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 @@ 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 @@ 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 0000000000..711d27eb92 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_voluntary_exit.py @@ -0,0 +1,89 @@ +from eth2spec.test.context import ( + always_bls, + spec_state_test, + with_phases, + 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 cac101dff1..2e8139db64 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 7aea6fc6f4..053236c8d7 100644 --- a/tests/generators/operations/main.py +++ b/tests/generators/operations/main.py @@ -39,6 +39,7 @@ _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)