-
Notifications
You must be signed in to change notification settings - Fork 996
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
EL-triggered consolidations #3775
Changes from all commits
1292bd9
204b39d
74eaf57
8a6ca1c
e030f2c
901a249
c492d61
17c5148
7c4b32a
dc2a2bd
5998e74
c17f22f
6b69368
f0ef76a
1970b56
6a731e9
96db63e
ffebf88
143b9e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,8 +28,7 @@ | |
- [`PendingBalanceDeposit`](#pendingbalancedeposit) | ||
- [`PendingPartialWithdrawal`](#pendingpartialwithdrawal) | ||
- [`ExecutionLayerWithdrawalRequest`](#executionlayerwithdrawalrequest) | ||
- [`Consolidation`](#consolidation) | ||
- [`SignedConsolidation`](#signedconsolidation) | ||
- [`ExecutionLayerConsolidationRequest`](#executionlayerconsolidationrequest) | ||
- [`PendingConsolidation`](#pendingconsolidation) | ||
- [Modified Containers](#modified-containers) | ||
- [`AttesterSlashing`](#attesterslashing) | ||
|
@@ -94,8 +93,8 @@ | |
- [New `process_execution_layer_withdrawal_request`](#new-process_execution_layer_withdrawal_request) | ||
- [Deposit receipts](#deposit-receipts) | ||
- [New `process_deposit_receipt`](#new-process_deposit_receipt) | ||
- [Consolidations](#consolidations) | ||
- [New `process_consolidation`](#new-process_consolidation) | ||
- [Execution layer consolidation requests](#execution-layer-consolidation-requests) | ||
- [New `process_execution_layer_consolidation_request`](#new-process_execution_layer_consolidation_request) | ||
- [Testing](#testing) | ||
|
||
<!-- END doctoc generated TOC please keep comment here to allow auto update --> | ||
|
@@ -164,16 +163,16 @@ The following values are (non-configurable) constants used throughout the specif | |
|
||
| Name | Value | | ||
| - | - | | ||
| `MAX_CONSOLIDATIONS` | `uint64(1)` | | ||
| `MAX_ATTESTER_SLASHINGS_ELECTRA` | `2**0` (= 1) | *[New in Electra:EIP7549]* | | ||
| `MAX_ATTESTATIONS_ELECTRA` | `2**3` (= 8) | *[New in Electra:EIP7549]* | | ||
|
||
### Execution | ||
|
||
| Name | Value | Description | | ||
| - | - | - | | ||
| `MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD` | `uint64(2**13)` (= 8,192) | *[New in Electra:EIP6110]* Maximum number of deposit receipts allowed in each payload | | ||
| `MAX_ATTESTER_SLASHINGS_ELECTRA` | `2**0` (= 1) | *[New in Electra:EIP7549]* | | ||
| `MAX_ATTESTATIONS_ELECTRA` | `2**3` (= 8) | *[New in Electra:EIP7549]* | | ||
| `MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD` | `uint64(2**4)` (= 16)| *[New in Electra:EIP7002]* Maximum number of execution layer withdrawal requests in each payload | | ||
| `MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD` | `uint64(1)` (= 1) | *[New in Electra:EIP7002]* Maximum number of execution layer consolidation requests in each payload | | ||
|
||
### Withdrawals processing | ||
|
||
|
@@ -238,25 +237,15 @@ class ExecutionLayerWithdrawalRequest(Container): | |
amount: Gwei | ||
``` | ||
|
||
#### `Consolidation` | ||
|
||
*Note*: The container is new in EIP7251. | ||
|
||
```python | ||
class Consolidation(Container): | ||
source_index: ValidatorIndex | ||
target_index: ValidatorIndex | ||
epoch: Epoch | ||
``` | ||
|
||
#### `SignedConsolidation` | ||
#### `ExecutionLayerConsolidationRequest` | ||
|
||
*Note*: The container is new in EIP7251. | ||
|
||
```python | ||
class SignedConsolidation(Container): | ||
message: Consolidation | ||
signature: BLSSignature | ||
class ExecutionLayerConsolidationRequest(Container): | ||
source_address: ExecutionAddress | ||
source_pubkey: BLSPubkey | ||
target_pubkey: BLSPubkey | ||
``` | ||
|
||
#### `PendingConsolidation` | ||
|
@@ -319,7 +308,6 @@ class BeaconBlockBody(Container): | |
execution_payload: ExecutionPayload # [Modified in Electra:EIP6110:EIP7002] | ||
bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES] | ||
blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK] | ||
consolidations: List[SignedConsolidation, MAX_CONSOLIDATIONS] # [New in Electra:EIP7251] | ||
``` | ||
|
||
#### `ExecutionPayload` | ||
|
@@ -348,6 +336,8 @@ class ExecutionPayload(Container): | |
deposit_receipts: List[DepositReceipt, MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD] # [New in Electra:EIP6110] | ||
# [New in Electra:EIP7002:EIP7251] | ||
withdrawal_requests: List[ExecutionLayerWithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD] | ||
# [New in Electra:EIP7251] | ||
consolidation_requests: List[ExecutionLayerConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD] | ||
``` | ||
|
||
#### `ExecutionPayloadHeader` | ||
|
@@ -375,6 +365,7 @@ class ExecutionPayloadHeader(Container): | |
excess_blob_gas: uint64 | ||
deposit_receipts_root: Root # [New in Electra:EIP6110] | ||
withdrawal_requests_root: Root # [New in Electra:EIP7002:EIP7251] | ||
consolidation_requests_root: Root # [New in Electra:EIP7251] | ||
``` | ||
|
||
#### `BeaconState` | ||
|
@@ -1013,6 +1004,7 @@ def process_execution_payload(state: BeaconState, body: BeaconBlockBody, executi | |
excess_blob_gas=payload.excess_blob_gas, | ||
deposit_receipts_root=hash_tree_root(payload.deposit_receipts), # [New in Electra:EIP6110] | ||
withdrawal_requests_root=hash_tree_root(payload.withdrawal_requests), # [New in Electra:EIP7002:EIP7251] | ||
consolidation_requests_root=hash_tree_root(payload.consolidation_requests), # [New in Electra:EIP7251] | ||
) | ||
``` | ||
|
||
|
@@ -1042,10 +1034,11 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: | |
for_ops(body.deposits, process_deposit) # [Modified in Electra:EIP7251] | ||
for_ops(body.voluntary_exits, process_voluntary_exit) # [Modified in Electra:EIP7251] | ||
for_ops(body.bls_to_execution_changes, process_bls_to_execution_change) | ||
for_ops(body.execution_payload.deposit_receipts, process_deposit_receipt) # [New in Electra:EIP6110] | ||
# [New in Electra:EIP7002:EIP7251] | ||
for_ops(body.execution_payload.withdrawal_requests, process_execution_layer_withdrawal_request) | ||
for_ops(body.execution_payload.deposit_receipts, process_deposit_receipt) # [New in Electra:EIP6110] | ||
for_ops(body.consolidations, process_consolidation) # [New in Electra:EIP7251] | ||
# [New in Electra:EIP7251] | ||
for_ops(body.execution_payload.consolidation_requests, process_execution_layer_consolidation_request) | ||
``` | ||
|
||
##### Attestations | ||
|
@@ -1292,43 +1285,62 @@ def process_deposit_receipt(state: BeaconState, deposit_receipt: DepositReceipt) | |
) | ||
``` | ||
|
||
##### Consolidations | ||
##### Execution layer consolidation requests | ||
|
||
###### New `process_consolidation` | ||
###### New `process_execution_layer_consolidation_request` | ||
|
||
```python | ||
def process_consolidation(state: BeaconState, signed_consolidation: SignedConsolidation) -> None: | ||
# If the pending consolidations queue is full, no consolidations are allowed in the block | ||
assert len(state.pending_consolidations) < PENDING_CONSOLIDATIONS_LIMIT | ||
# If there is too little available consolidation churn limit, no consolidations are allowed in the block | ||
assert get_consolidation_churn_limit(state) > MIN_ACTIVATION_BALANCE | ||
consolidation = signed_consolidation.message | ||
def process_execution_layer_consolidation_request( | ||
state: BeaconState, | ||
execution_layer_consolidation_request: ExecutionLayerConsolidationRequest | ||
) -> None: | ||
# If the pending consolidations queue is full, consolidation requests are ignored | ||
if len(state.pending_consolidations) == PENDING_CONSOLIDATIONS_LIMIT: | ||
return | ||
# If there is too little available consolidation churn limit, consolidation requests are ignored | ||
if get_consolidation_churn_limit(state) <= MIN_ACTIVATION_BALANCE: | ||
return | ||
|
||
validator_pubkeys = [v.pubkey for v in state.validators] | ||
# Verify pubkeys exists | ||
request_source_pubkey = execution_layer_consolidation_request.source_pubkey | ||
request_target_pubkey = execution_layer_consolidation_request.target_pubkey | ||
if request_source_pubkey not in validator_pubkeys: | ||
return | ||
if request_target_pubkey not in validator_pubkeys: | ||
return | ||
source_index = ValidatorIndex(validator_pubkeys.index(request_source_pubkey)) | ||
target_index = ValidatorIndex(validator_pubkeys.index(request_target_pubkey)) | ||
source_validator = state.validators[source_index] | ||
target_validator = state.validators[target_index] | ||
|
||
# Verify that source != target, so a consolidation cannot be used as an exit. | ||
assert consolidation.source_index != consolidation.target_index | ||
if source_index == target_index: | ||
return | ||
Comment on lines
+1318
to
+1319
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would it be more straightforward to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't have a strong opinion here. Whatever we choose, we do use |
||
|
||
# Verify source withdrawal credentials | ||
has_correct_credential = has_execution_withdrawal_credential(source_validator) | ||
is_correct_source_address = ( | ||
source_validator.withdrawal_credentials[12:] == execution_layer_consolidation_request.source_address | ||
) | ||
if not (has_correct_credential and is_correct_source_address): | ||
return | ||
|
||
# Verify that target has execution withdrawal credentials | ||
if not has_execution_withdrawal_credential(target_validator): | ||
hwwhww marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return | ||
|
||
source_validator = state.validators[consolidation.source_index] | ||
target_validator = state.validators[consolidation.target_index] | ||
# Verify the source and the target are active | ||
current_epoch = get_current_epoch(state) | ||
assert is_active_validator(source_validator, current_epoch) | ||
assert is_active_validator(target_validator, current_epoch) | ||
if not is_active_validator(source_validator, current_epoch): | ||
return | ||
if not is_active_validator(target_validator, current_epoch): | ||
return | ||
# Verify exits for source and target have not been initiated | ||
assert source_validator.exit_epoch == FAR_FUTURE_EPOCH | ||
assert target_validator.exit_epoch == FAR_FUTURE_EPOCH | ||
# Consolidations must specify an epoch when they become valid; they are not valid before then | ||
assert current_epoch >= consolidation.epoch | ||
|
||
# Verify the source and the target have Execution layer withdrawal credentials | ||
assert has_execution_withdrawal_credential(source_validator) | ||
assert has_execution_withdrawal_credential(target_validator) | ||
# Verify the same withdrawal address | ||
assert source_validator.withdrawal_credentials[12:] == target_validator.withdrawal_credentials[12:] | ||
Comment on lines
-1324
to
-1325
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @hwwhww it is great to see that the consolidation of validators with different withdrawal addresses is possible now. Was there any reason why it was not allowed initially (why ever this constraint has existed?), but became possible by this PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was not allowed initially because initially consolidation was authorized by the validator’s pubkey which isn’t secure enough to change the owner (withdrawal creds) of the funds. In order to make this possible, a consolidation request system smart contract has been introduced on EL with the corresponding changes on CL to make it possible to sign requests with withdrawal creds which unlocked moving funds between validators with different creds There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Awesome. Appreciate the details 👍 |
||
|
||
# Verify consolidation is signed by the source and the target | ||
domain = compute_domain(DOMAIN_CONSOLIDATION, genesis_validators_root=state.genesis_validators_root) | ||
signing_root = compute_signing_root(consolidation, domain) | ||
pubkeys = [source_validator.pubkey, target_validator.pubkey] | ||
assert bls.FastAggregateVerify(pubkeys, signing_root, signed_consolidation.signature) | ||
if source_validator.exit_epoch != FAR_FUTURE_EPOCH: | ||
return | ||
if target_validator.exit_epoch != FAR_FUTURE_EPOCH: | ||
return | ||
|
||
# Initiate source validator exit and append pending consolidation | ||
source_validator.exit_epoch = compute_consolidation_epoch_and_update_churn( | ||
|
@@ -1338,8 +1350,8 @@ def process_consolidation(state: BeaconState, signed_consolidation: SignedConsol | |
source_validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY | ||
) | ||
state.pending_consolidations.append(PendingConsolidation( | ||
source_index=consolidation.source_index, | ||
target_index=consolidation.target_index | ||
source_index=source_index, | ||
target_index=target_index | ||
)) | ||
``` | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test case idea: have a validator get partial withdrawal and then process its consolidation at the same block.