Skip to content

Commit 1674f64

Browse files
authored
Merge 0f612a4 into d48c3b8
2 parents d48c3b8 + 0f612a4 commit 1674f64

File tree

2 files changed

+2047
-0
lines changed

2 files changed

+2047
-0
lines changed

EIPS/eip-8070.md

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
---
2+
eip: 8070
3+
title: Prevent using consolidations as withdrawals
4+
description: Cancels consolidation if the max effective balance of the target validator will be exceeded, preventing the withdrawal of the unused balance
5+
author: Mikhail Kalinin (@mkalinin), Francesco D'Amato (@fradamt)
6+
discussions-to: <URL>
7+
status: Draft
8+
type: Standards Track
9+
category: Core
10+
created: 2025-10-29
11+
---
12+
13+
## Abstract
14+
15+
Cancels a consolidation request if the effective balance of the target validator would exceed the max effective balance after processing it, which would result in the excess balance being withdrawn. This is an unintended way to speed up withdrawals when the consolidation queue is faster than the exit queue.
16+
17+
## Motivation
18+
19+
The existing design of consolidation mechanism leaves an opportunity to use consolidation queue for exits which becomes appealing to be abused when there is an imbalance between exit and consolidation queues favoring the latter.
20+
21+
At the date of writing this EIP, the consolidation flaw is being heavily exploited. There are public write ups on how to speed up withdrawals by using this vulnerability.
22+
23+
Even though this is a UX rather than security issue, consolidation queue was never meant to be used for withdrawals, which makes the fix introduced by this EIP an important modification.
24+
25+
## Specification
26+
27+
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174).
28+
29+
Starting from the beginning of the epoch when this EIP is activated, Consensus Layer client **MUST** use modified `process_consolidation_request` function which code is outlined below.
30+
31+
### New `get_pending_balance_to_consolidate`
32+
33+
```python
34+
def get_pending_balance_to_consolidate(state: BeaconState, target_index: ValidatorIndex) -> Gwei:
35+
pending_balance_to_consolidate = Gwei(0)
36+
for pending_consolidation in state.pending_consolidations:
37+
if pending_consolidation.target_index == target_index:
38+
source_validator = state.validators[pending_consolidation.source_index]
39+
pending_balance_to_consolidate += source_validator.effective_balance
40+
return pending_balance_to_consolidate
41+
```
42+
43+
### Modified `process_consolidation_request`
44+
45+
*Note*: This function is extended with the check of the target's balance after consolidation and cancels consolidation request if the balance exceedes the max effective balance.
46+
47+
```python
48+
def process_consolidation_request(
49+
state: BeaconState, consolidation_request: ConsolidationRequest
50+
) -> None:
51+
if is_valid_switch_to_compounding_request(state, consolidation_request):
52+
validator_pubkeys = [v.pubkey for v in state.validators]
53+
request_source_pubkey = consolidation_request.source_pubkey
54+
source_index = ValidatorIndex(validator_pubkeys.index(request_source_pubkey))
55+
switch_to_compounding_validator(state, source_index)
56+
return
57+
58+
# Verify that source != target, so a consolidation cannot be used as an exit
59+
if consolidation_request.source_pubkey == consolidation_request.target_pubkey:
60+
return
61+
# If the pending consolidations queue is full, consolidation requests are ignored
62+
if len(state.pending_consolidations) == PENDING_CONSOLIDATIONS_LIMIT:
63+
return
64+
# If there is too little available consolidation churn limit, consolidation requests are ignored
65+
if get_consolidation_churn_limit(state) <= MIN_ACTIVATION_BALANCE:
66+
return
67+
68+
validator_pubkeys = [v.pubkey for v in state.validators]
69+
# Verify pubkeys exists
70+
request_source_pubkey = consolidation_request.source_pubkey
71+
request_target_pubkey = consolidation_request.target_pubkey
72+
if request_source_pubkey not in validator_pubkeys:
73+
return
74+
if request_target_pubkey not in validator_pubkeys:
75+
return
76+
source_index = ValidatorIndex(validator_pubkeys.index(request_source_pubkey))
77+
target_index = ValidatorIndex(validator_pubkeys.index(request_target_pubkey))
78+
source_validator = state.validators[source_index]
79+
target_validator = state.validators[target_index]
80+
81+
# Verify source withdrawal credentials
82+
has_correct_credential = has_execution_withdrawal_credential(source_validator)
83+
is_correct_source_address = (
84+
source_validator.withdrawal_credentials[12:] == consolidation_request.source_address
85+
)
86+
if not (has_correct_credential and is_correct_source_address):
87+
return
88+
89+
# Verify that target has compounding withdrawal credentials
90+
if not has_compounding_withdrawal_credential(target_validator):
91+
return
92+
93+
# Verify the source and the target are active
94+
current_epoch = get_current_epoch(state)
95+
if not is_active_validator(source_validator, current_epoch):
96+
return
97+
if not is_active_validator(target_validator, current_epoch):
98+
return
99+
# Verify exits for source and target have not been initiated
100+
if source_validator.exit_epoch != FAR_FUTURE_EPOCH:
101+
return
102+
if target_validator.exit_epoch != FAR_FUTURE_EPOCH:
103+
return
104+
# Verify the source has been active long enough
105+
if current_epoch < source_validator.activation_epoch + SHARD_COMMITTEE_PERIOD:
106+
return
107+
# Verify the source has no pending withdrawals in the queue
108+
if get_pending_balance_to_withdraw(state, source_index) > 0:
109+
return
110+
111+
# [New in EIPXXXX]
112+
# Verify that the consolidating balance will
113+
# end up as active balance, not as excess balance
114+
target_balance_after_consolidation = (
115+
get_pending_balance_to_consolidate(state, target_index)
116+
+ source_validator.effective_balance
117+
+ state.balances[target_index]
118+
)
119+
if target_balance_after_consolidation > get_max_effective_balance(target_validator):
120+
return
121+
122+
# Initiate source validator exit and append pending consolidation
123+
source_validator.exit_epoch = compute_consolidation_epoch_and_update_churn(
124+
state, source_validator.effective_balance
125+
)
126+
source_validator.withdrawable_epoch = Epoch(
127+
source_validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY
128+
)
129+
state.pending_consolidations.append(
130+
PendingConsolidation(source_index=source_index, target_index=target_index)
131+
)
132+
```
133+
134+
## Rationale
135+
136+
### Iterating over pending consolidaitons
137+
138+
The new design introduces an iteration over pending consolidations which increases complexity of consolidation processing.
139+
140+
This is done to handle the case when there are multiple consolidations with the same target and each of them doesn't exceed the max effective balance while all of them together does.
141+
142+
## Backwards Compatibility
143+
144+
This EIP introduces backwards-incompatible changes to the Consensus Layer and must be activated via scheduled network upgrade.
145+
146+
## Test Cases
147+
148+
* test_single_consolidation_request_at_max_eb
149+
* test_no_pending_consolidations_exceeding_max_eb
150+
* test_single_pending_consolidation_exceeding_max_eb
151+
* test_multiple_pending_consolidations_at_max_eb
152+
* test_multiple_pending_consolidations_exceeding_max_eb
153+
* test_exceeding_max_eb_with_the_target_balance_but_not_eb
154+
* test_exceeding_max_eb_with_the_source_eb_but_not_the_balance
155+
* test_multiple_pending_consolidations_exceeding_max_eb_with_the_source_eb_but_not_the_balance
156+
157+
All of the above test cases are implemented [here](../assets/eip-8070/test_process_consolidation_request.py).
158+
159+
## Security Considerations
160+
161+
When consolidation reuqest results in max effective balance exceed it is cancelled on the Consensus Layer,
162+
neither request fee nor transaction gast cost are refunded in this case.
163+
164+
## Copyright
165+
166+
Copyright and related rights waived via [CC0](../LICENSE.md).

0 commit comments

Comments
 (0)