mirror of
https://github.com/ethereum/consensus-specs.git
synced 2026-02-01 09:45:02 -05:00
@@ -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)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- /TOC -->
|
||||
@@ -47,14 +54,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
|
||||
@@ -63,6 +76,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 |
|
||||
@@ -84,6 +103,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
|
||||
```
|
||||
|
||||
### Extended Containers
|
||||
|
||||
#### `ExecutionPayload`
|
||||
@@ -148,6 +184,26 @@ class Validator(Container):
|
||||
fully_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]
|
||||
sync_aggregate: SyncAggregate
|
||||
# Execution
|
||||
execution_payload: ExecutionPayload
|
||||
# Capella operations
|
||||
bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES] # [New in Capella]
|
||||
```
|
||||
|
||||
#### `BeaconState`
|
||||
|
||||
```python
|
||||
@@ -324,3 +380,49 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe
|
||||
withdrawals_root=hash_tree_root(payload.withdrawals), # [New in Capella]
|
||||
)
|
||||
```
|
||||
|
||||
#### 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 in Capella]
|
||||
```
|
||||
|
||||
#### 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).to_bytes(11, 'little')
|
||||
+ address_change.to_execution_address
|
||||
)
|
||||
```
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
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, always_bls
|
||||
|
||||
|
||||
def run_bls_to_execution_change_processing(spec, state, signed_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', 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, signed_address_change))
|
||||
yield 'post', None
|
||||
return
|
||||
|
||||
# process address change
|
||||
spec.process_bls_to_execution_change(state, signed_address_change)
|
||||
|
||||
# Make sure the address change has been processed
|
||||
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
|
||||
|
||||
|
||||
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=validator_index,
|
||||
from_bls_pubkey=withdrawal_pubkey,
|
||||
to_execution_address=b'\x42' * 20,
|
||||
)
|
||||
|
||||
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
|
||||
@always_bls
|
||||
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)
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user