From 79cdb88e66b0ec1c0a8e403391cd07428d5e99ee Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 16 Mar 2022 15:41:37 -0600 Subject: [PATCH 1/6] wip 00 to 01 cred change --- specs/capella/beacon-chain.md | 98 ++++++++++++++++++- .../test_process_bls_to_execution_change.py | 47 +++++++++ 2 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index e912f608e..bcc35381f 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -20,14 +20,20 @@ to validator withdrawals. Including: ## Custom types -## Constants - We define the following Python custom types for type hinting and readability: | Name | SSZ equivalent | Description | | - | - | - | | `WithdrawalIndex` | `uint64` | an index of a `Withdrawal`| +## Constants + +### Domain types + +| Name | Value | +| - | - | +| `DOMAIN_BLS_TO_EXECUTION_CHANGE` | `DomainType('0x0A000000')` | + ## Preset ### State list lengths @@ -36,6 +42,12 @@ We define the following Python custom types for type hinting and readability: | - | - | :-: | :-: | | `WITHDRAWALS_QUEUE_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | withdrawals enqueued in state| +### Max operations per block + +| Name | Value | +| - | - | +| `MAX_BLS_TO_EXECUTION_CHANGES` | `2**4` (= 16) | + ### Execution | Name | Value | Description | @@ -64,6 +76,25 @@ class Validator(Container): withdrawn_epoch: Epoch # [New in Capella] ``` +#### `BeaconBlockBody` + +```python +class BeaconBlockBody(Container): + randao_reveal: BLSSignature + eth1_data: Eth1Data # Eth1 data vote + graffiti: Bytes32 # Arbitrary data + # Operations + proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] + attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] + attestations: List[Attestation, MAX_ATTESTATIONS] + deposits: List[Deposit, MAX_DEPOSITS] + voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] + bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES] + sync_aggregate: SyncAggregate + # Execution + execution_payload: ExecutionPayload +``` + #### `BeaconState` ```python @@ -166,6 +197,23 @@ class Withdrawal(Container): amount: Gwei ``` +#### `BLSToExecutionChange` + +```python +class BLSToExecutionChange(Container): + validator_index: ValidatorIndex + from_bls_pubkey: BLSPubkey + to_execution_address: ExecutionAddress +``` + +#### `SignedBLSToExecutionChange` + +```python +class SignedBLSToExecutionChange(Container): + message: BLSToExecutionChange + signature: BLSSignature +``` + ## Helpers ### Beacon state mutators @@ -297,3 +345,49 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe withdrawals_root=hash_tree_root(payload.withdrawals), ) ``` + +#### Modified `process_operations` + +*Note*: The function `process_operations` is modified to process `BLSToExecutionChange` operations included in the block. + +```python +def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: + # Verify that outstanding deposits are processed up to the maximum number of deposits + assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index) + + def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: + for operation in operations: + fn(state, operation) + + for_ops(body.proposer_slashings, process_proposer_slashing) + for_ops(body.attester_slashings, process_attester_slashing) + for_ops(body.attestations, process_attestation) + for_ops(body.deposits, process_deposit) + for_ops(body.voluntary_exits, process_voluntary_exit) + for_ops(body.bls_to_execution_changes, process_bls_to_execution_change) +``` + +#### New `process_bls_to_execution_change` + +```python +def process_bls_to_execution_change(state: BeaconState, + signed_address_change: SignedBLSToExecutionChange) -> None: + address_change = signed_address_change.message + + assert address_change.validator_index < len(state.validators) + + validator = state.validators[address_change.validator_index] + + assert validator.withdrawal_credentials[:1] == BLS_WITHDRAWAL_PREFIX + assert validator.withdrawal_credentials[1:] == hash(address_change.from_bls_pubkey)[1:] + + domain = get_domain(state, DOMAIN_BLS_TO_EXECUTION_CHANGE) + signing_root = compute_signing_root(address_change, domain) + assert bls.Verify(address_change.from_bls_pubkey, signing_root, signed_address_change.signature) + + validator.withdrawal_credentials = ( + ETH1_ADDRESS_WITHDRAWAL_PREFIX + + 0x00 * 11 + + address_change.to_execution_address + ) +``` diff --git a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py new file mode 100644 index 000000000..1a7fbfa81 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py @@ -0,0 +1,47 @@ +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, +) + +from eth2spec.test.context import spec_state_test, expect_assertion_error, with_capella_and_later + +def run_bls_to_execution_change_processing(spec, state, address_change, valid=True): + """ + Run ``process_bls_to_execution_change``, yielding: + - pre-state ('pre') + - address-change ('address_change') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + # yield pre-state + yield 'pre', state + + yield 'address_change', address_change + + # If the address_change is invalid, processing is aborted, and there is no post-state. + if not valid: + expect_assertion_error(lambda: spec.process_bls_to_execution_change(state, attestation)) + yield 'post', None + return + + # process address change + spec.process_bls_to_execution_change(state, attestation) + + # Make sure the address change has been processed + assert state.withdrawal_credentials[:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX + assert state.withdrawal_credentials[1:12] == b'\x00' * 11 + assert state.withdrawal_credentials[12:] == address_change.to_execution_address + + # yield post-state + yield 'post', state + + +@with_capella_and_later +@spec_state_test +def test_success(spec, state): + address_change = spec.BLSToExecutionChange( + validator_index=0, + from_bls_pubkey=TEST, + to_execution_address=b'\x42' * 20, + ) + + yield from run_bls_to_execution_change_processing(spec, state, address_change) From 38496ba021e3c838434d2fbea561f6c2fe1b211f Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 22 Mar 2022 08:23:59 -0600 Subject: [PATCH 2/6] toc --- specs/capella/beacon-chain.md | 7 +++++++ specs/capella/validator.md | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index 6cff9a9c2..443051114 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -9,17 +9,22 @@ - [Introduction](#introduction) - [Custom types](#custom-types) - [Constants](#constants) + - [Domain types](#domain-types) - [Preset](#preset) - [State list lengths](#state-list-lengths) + - [Max operations per block](#max-operations-per-block) - [Execution](#execution) - [Configuration](#configuration) - [Containers](#containers) - [New containers](#new-containers) - [`Withdrawal`](#withdrawal) + - [`BLSToExecutionChange`](#blstoexecutionchange) + - [`SignedBLSToExecutionChange`](#signedblstoexecutionchange) - [Extended Containers](#extended-containers) - [`ExecutionPayload`](#executionpayload) - [`ExecutionPayloadHeader`](#executionpayloadheader) - [`Validator`](#validator) + - [`BeaconBlockBody`](#beaconblockbody) - [`BeaconState`](#beaconstate) - [Helpers](#helpers) - [Beacon state mutators](#beacon-state-mutators) @@ -32,6 +37,8 @@ - [Block processing](#block-processing) - [New `process_withdrawals`](#new-process_withdrawals) - [Modified `process_execution_payload`](#modified-process_execution_payload) + - [Modified `process_operations`](#modified-process_operations) + - [New `process_bls_to_execution_change`](#new-process_bls_to_execution_change) diff --git a/specs/capella/validator.md b/specs/capella/validator.md index 5e1f5fd0e..3caeaec9c 100644 --- a/specs/capella/validator.md +++ b/specs/capella/validator.md @@ -8,6 +8,17 @@ +- [Introduction](#introduction) +- [Prerequisites](#prerequisites) +- [Helpers](#helpers) +- [Protocols](#protocols) + - [`ExecutionEngine`](#executionengine) + - [`get_payload`](#get_payload) +- [Beacon chain responsibilities](#beacon-chain-responsibilities) + - [Block proposal](#block-proposal) + - [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody) + - [ExecutionPayload](#executionpayload) + From 2017b6126514bc0c873630d654e7951e8fb9c347 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 22 Mar 2022 13:37:17 -0600 Subject: [PATCH 3/6] test address_change --- specs/capella/beacon-chain.md | 2 +- .../test_process_bls_to_execution_change.py | 175 ++++++++++++++++-- .../pyspec/eth2spec/test/helpers/genesis.py | 7 +- 3 files changed, 164 insertions(+), 20 deletions(-) diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index 443051114..a749aec26 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -421,7 +421,7 @@ def process_bls_to_execution_change(state: BeaconState, validator.withdrawal_credentials = ( ETH1_ADDRESS_WITHDRAWAL_PREFIX - + 0x00 * 11 + + (0x00).to_bytes(11, 'little') + address_change.to_execution_address ) ``` diff --git a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py index 1a7fbfa81..01ed7b8f1 100644 --- a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py +++ b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py @@ -1,10 +1,10 @@ -from eth2spec.test.helpers.execution_payload import ( - build_empty_execution_payload, -) +from eth2spec.utils import bls +from eth2spec.test.helpers.keys import pubkeys, privkeys, pubkey_to_privkey from eth2spec.test.context import spec_state_test, expect_assertion_error, with_capella_and_later -def run_bls_to_execution_change_processing(spec, state, address_change, valid=True): + +def run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=True): """ Run ``process_bls_to_execution_change``, yielding: - pre-state ('pre') @@ -15,33 +15,176 @@ def run_bls_to_execution_change_processing(spec, state, address_change, valid=Tr # yield pre-state yield 'pre', state - yield 'address_change', address_change + yield 'address_change', signed_address_change # If the address_change is invalid, processing is aborted, and there is no post-state. if not valid: - expect_assertion_error(lambda: spec.process_bls_to_execution_change(state, attestation)) + expect_assertion_error(lambda: spec.process_bls_to_execution_change(state, signed_address_change)) yield 'post', None return # process address change - spec.process_bls_to_execution_change(state, attestation) + spec.process_bls_to_execution_change(state, signed_address_change) # Make sure the address change has been processed - assert state.withdrawal_credentials[:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX - assert state.withdrawal_credentials[1:12] == b'\x00' * 11 - assert state.withdrawal_credentials[12:] == address_change.to_execution_address + validator_index = signed_address_change.message.validator_index + validator = state.validators[validator_index] + assert validator.withdrawal_credentials[:1] == spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + assert validator.withdrawal_credentials[1:12] == b'\x00' * 11 + assert validator.withdrawal_credentials[12:] == signed_address_change.message.to_execution_address # yield post-state yield 'post', state -@with_capella_and_later -@spec_state_test -def test_success(spec, state): +def get_signed_address_change(spec, state, validator_index=None, withdrawal_pubkey=None): + if validator_index is None: + validator_index = 0 + + if withdrawal_pubkey is None: + key_index = -1 - validator_index + withdrawal_pubkey = pubkeys[key_index] + withdrawal_privkey = privkeys[key_index] + else: + withdrawal_privkey = pubkey_to_privkey[withdrawal_pubkey] + + domain = spec.get_domain(state, spec.DOMAIN_BLS_TO_EXECUTION_CHANGE) address_change = spec.BLSToExecutionChange( - validator_index=0, - from_bls_pubkey=TEST, + validator_index=validator_index, + from_bls_pubkey=withdrawal_pubkey, to_execution_address=b'\x42' * 20, ) - yield from run_bls_to_execution_change_processing(spec, state, address_change) + signing_root = spec.compute_signing_root(address_change, domain) + return spec.SignedBLSToExecutionChange( + message=address_change, + signature=bls.Sign(withdrawal_privkey, signing_root), + ) + + +@with_capella_and_later +@spec_state_test +def test_success(spec, state): + signed_address_change = get_signed_address_change(spec, state) + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change) + + +@with_capella_and_later +@spec_state_test +def test_success_not_activated(spec, state): + validator_index = 3 + validator = state.validators[validator_index] + validator.activation_eligibility_epoch += 4 + validator.activation_epoch = spec.FAR_FUTURE_EPOCH + + assert not spec.is_active_validator(validator, spec.get_current_epoch(state)) + + signed_address_change = get_signed_address_change(spec, state) + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change) + + assert not spec.is_fully_withdrawable_validator(state.validators[validator_index], spec.get_current_epoch(state)) + + +@with_capella_and_later +@spec_state_test +def test_success_in_activation_queue(spec, state): + validator_index = 3 + validator = state.validators[validator_index] + validator.activation_eligibility_epoch = spec.get_current_epoch(state) + validator.activation_epoch += 4 + + assert not spec.is_active_validator(validator, spec.get_current_epoch(state)) + + signed_address_change = get_signed_address_change(spec, state) + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change) + + assert not spec.is_fully_withdrawable_validator(state.validators[validator_index], spec.get_current_epoch(state)) + + +@with_capella_and_later +@spec_state_test +def test_success_in_exit_queue(spec, state): + validator_index = 3 + spec.initiate_validator_exit(state, validator_index) + + assert spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state)) + assert spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch + + signed_address_change = get_signed_address_change(spec, state, validator_index=validator_index) + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change) + + +@with_capella_and_later +@spec_state_test +def test_success_exited(spec, state): + validator_index = 4 + validator = state.validators[validator_index] + validator.exit_epoch = spec.get_current_epoch(state) + + assert not spec.is_active_validator(validator, spec.get_current_epoch(state)) + + signed_address_change = get_signed_address_change(spec, state, validator_index=validator_index) + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change) + + assert not spec.is_fully_withdrawable_validator(state.validators[validator_index], spec.get_current_epoch(state)) + + +@with_capella_and_later +@spec_state_test +def test_success_withdrawable(spec, state): + validator_index = 4 + validator = state.validators[validator_index] + validator.exit_epoch = spec.get_current_epoch(state) + validator.withdrawable_epoch = spec.get_current_epoch(state) + + assert not spec.is_active_validator(validator, spec.get_current_epoch(state)) + + signed_address_change = get_signed_address_change(spec, state, validator_index=validator_index) + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change) + + assert spec.is_fully_withdrawable_validator(state.validators[validator_index], spec.get_current_epoch(state)) + + +@with_capella_and_later +@spec_state_test +def test_fail_val_index_out_of_range(spec, state): + # Create for one validator beyond the validator list length + signed_address_change = get_signed_address_change(spec, state, validator_index=len(state.validators)) + + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_already_0x01(spec, state): + # Create for one validator beyond the validator list length + validator_index = len(state.validators) // 2 + validator = state.validators[validator_index] + validator.withdrawal_credentials = b'\x01' + b'\x00' * 11 + b'\x23' * 20 + signed_address_change = get_signed_address_change(spec, state, validator_index=validator_index) + + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_incorrect_from_bls_pubkey(spec, state): + # Create for one validator beyond the validator list length + validator_index = 2 + signed_address_change = get_signed_address_change( + spec, state, + validator_index=validator_index, + withdrawal_pubkey=pubkeys[0], + ) + + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_bad_signature(spec, state): + signed_address_change = get_signed_address_change(spec, state) + # Mutate sigature + signed_address_change.signature = spec.BLSSignature(b'\x42' * 96) + + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=False) diff --git a/tests/core/pyspec/eth2spec/test/helpers/genesis.py b/tests/core/pyspec/eth2spec/test/helpers/genesis.py index 1ca408598..83994c409 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/genesis.py +++ b/tests/core/pyspec/eth2spec/test/helpers/genesis.py @@ -6,11 +6,12 @@ from eth2spec.test.helpers.keys import pubkeys def build_mock_validator(spec, i: int, balance: int): - pubkey = pubkeys[i] + active_pubkey = pubkeys[i] + withdrawal_pubkey = pubkeys[-1 - i] # insecurely use pubkey as withdrawal key as well - withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:] + withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(withdrawal_pubkey)[1:] validator = spec.Validator( - pubkey=pubkeys[i], + pubkey=active_pubkey, withdrawal_credentials=withdrawal_credentials, activation_eligibility_epoch=spec.FAR_FUTURE_EPOCH, activation_epoch=spec.FAR_FUTURE_EPOCH, From 4ac4158b42162acd5b17a50fd10f63f3e263958f Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 23 Mar 2022 10:54:40 -0600 Subject: [PATCH 4/6] move bls chang operation to end of block body --- specs/capella/beacon-chain.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index a749aec26..ff93bec9c 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -197,10 +197,11 @@ class BeaconBlockBody(Container): attestations: List[Attestation, MAX_ATTESTATIONS] deposits: List[Deposit, MAX_DEPOSITS] voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] - bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES] sync_aggregate: SyncAggregate # Execution execution_payload: ExecutionPayload + # Capella operations + bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES] # [New in Capella] ``` #### `BeaconState` @@ -398,7 +399,7 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: for_ops(body.attestations, process_attestation) for_ops(body.deposits, process_deposit) for_ops(body.voluntary_exits, process_voluntary_exit) - for_ops(body.bls_to_execution_changes, process_bls_to_execution_change) + for_ops(body.bls_to_execution_changes, process_bls_to_execution_change) # [New in Capella] ``` #### New `process_bls_to_execution_change` From 6e369e4a5325a8e554857d9032835a30a0ceee25 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 23 Mar 2022 10:55:27 -0600 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: Hsiao-Wei Wang --- .../block_processing/test_process_bls_to_execution_change.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py index 01ed7b8f1..416544c55 100644 --- a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py +++ b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py @@ -182,6 +182,7 @@ def test_fail_incorrect_from_bls_pubkey(spec, state): @with_capella_and_later @spec_state_test +@always_bls def test_fail_bad_signature(spec, state): signed_address_change = get_signed_address_change(spec, state) # Mutate sigature From ee5f29cb75eb81413b62ef3774d4c231cc6f47db Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 24 Mar 2022 09:57:43 -0600 Subject: [PATCH 6/6] fix ci --- .../block_processing/test_process_bls_to_execution_change.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py index 416544c55..4b69e04a6 100644 --- a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py +++ b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py @@ -1,7 +1,7 @@ from eth2spec.utils import bls from eth2spec.test.helpers.keys import pubkeys, privkeys, pubkey_to_privkey -from eth2spec.test.context import spec_state_test, expect_assertion_error, with_capella_and_later +from eth2spec.test.context import spec_state_test, expect_assertion_error, with_capella_and_later, always_bls def run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=True):