Skip to content

Commit f9d9ae5

Browse files
ensi321twoeths
andauthored
feat: switch to compounding from consolidation requests (#7122)
* New compound switching logic * clean up * fix: remove newCompoundingValidators in EpochTransitionCache * Bump spec version * Address comment * address comment * Lint --------- Co-authored-by: Tuyen Nguyen <[email protected]>
1 parent fb40cf0 commit f9d9ae5

5 files changed

Lines changed: 79 additions & 39 deletions

File tree

packages/state-transition/src/block/processConsolidationRequest.ts

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,38 @@ import {FAR_FUTURE_EPOCH, MIN_ACTIVATION_BALANCE, PENDING_CONSOLIDATIONS_LIMIT}
33

44
import {CachedBeaconStateElectra} from "../types.js";
55
import {getConsolidationChurnLimit, isActiveValidator} from "../util/validator.js";
6-
import {hasExecutionWithdrawalCredential} from "../util/electra.js";
6+
import {hasExecutionWithdrawalCredential, switchToCompoundingValidator} from "../util/electra.js";
77
import {computeConsolidationEpochAndUpdateChurn} from "../util/epoch.js";
8+
import {hasEth1WithdrawalCredential} from "../util/capella.js";
89

10+
// TODO Electra: Clean up necessary as there is a lot of overlap with isValidSwitchToCompoundRequest
911
export function processConsolidationRequest(
1012
state: CachedBeaconStateElectra,
1113
consolidationRequest: electra.ConsolidationRequest
1214
): void {
13-
// If the pending consolidations queue is full, consolidation requests are ignored
14-
if (state.pendingConsolidations.length >= PENDING_CONSOLIDATIONS_LIMIT) {
15+
const {sourcePubkey, targetPubkey, sourceAddress} = consolidationRequest;
16+
const sourceIndex = state.epochCtx.getValidatorIndex(sourcePubkey);
17+
const targetIndex = state.epochCtx.getValidatorIndex(targetPubkey);
18+
19+
if (sourceIndex === null || targetIndex === null) {
1520
return;
1621
}
1722

18-
// If there is too little available consolidation churn limit, consolidation requests are ignored
19-
if (getConsolidationChurnLimit(state.epochCtx) <= MIN_ACTIVATION_BALANCE) {
23+
if (isValidSwitchToCompoundRequest(state, consolidationRequest)) {
24+
switchToCompoundingValidator(state, sourceIndex);
25+
// Early return since we have already switched validator to compounding
2026
return;
2127
}
2228

23-
const {sourcePubkey, targetPubkey} = consolidationRequest;
24-
const sourceIndex = state.epochCtx.getValidatorIndex(sourcePubkey);
25-
const targetIndex = state.epochCtx.getValidatorIndex(targetPubkey);
26-
27-
if (sourceIndex === null || targetIndex === null) {
29+
// If the pending consolidations queue is full, consolidation requests are ignored
30+
if (state.pendingConsolidations.length >= PENDING_CONSOLIDATIONS_LIMIT) {
2831
return;
2932
}
3033

34+
// If there is too little available consolidation churn limit, consolidation requests are ignored
35+
if (getConsolidationChurnLimit(state.epochCtx) <= MIN_ACTIVATION_BALANCE) {
36+
return;
37+
}
3138
// Verify that source != target, so a consolidation cannot be used as an exit.
3239
if (sourceIndex === targetIndex) {
3340
return;
@@ -46,7 +53,7 @@ export function processConsolidationRequest(
4653
return;
4754
}
4855

49-
if (Buffer.compare(sourceWithdrawalAddress, consolidationRequest.sourceAddress) !== 0) {
56+
if (Buffer.compare(sourceWithdrawalAddress, sourceAddress) !== 0) {
5057
return;
5158
}
5259

@@ -70,4 +77,55 @@ export function processConsolidationRequest(
7077
targetIndex,
7178
});
7279
state.pendingConsolidations.push(pendingConsolidation);
80+
81+
// Churn any target excess active balance of target and raise its max
82+
if (hasEth1WithdrawalCredential(targetValidator.withdrawalCredentials)) {
83+
switchToCompoundingValidator(state, targetIndex);
84+
}
85+
}
86+
87+
/**
88+
* Determine if we should set consolidation target validator to compounding credential
89+
*/
90+
function isValidSwitchToCompoundRequest(
91+
state: CachedBeaconStateElectra,
92+
consolidationRequest: electra.ConsolidationRequest
93+
): boolean {
94+
const {sourcePubkey, targetPubkey, sourceAddress} = consolidationRequest;
95+
const sourceIndex = state.epochCtx.getValidatorIndex(sourcePubkey);
96+
const targetIndex = state.epochCtx.getValidatorIndex(targetPubkey);
97+
98+
// Verify pubkey exists
99+
if (sourceIndex === null) {
100+
return false;
101+
}
102+
103+
// Switch to compounding requires source and target be equal
104+
if (sourceIndex !== targetIndex) {
105+
return false;
106+
}
107+
108+
const sourceValidator = state.validators.getReadonly(sourceIndex);
109+
const sourceWithdrawalAddress = sourceValidator.withdrawalCredentials.subarray(12);
110+
// Verify request has been authorized
111+
if (Buffer.compare(sourceWithdrawalAddress, sourceAddress) !== 0) {
112+
return false;
113+
}
114+
115+
// Verify source withdrawal credentials
116+
if (!hasEth1WithdrawalCredential(sourceValidator.withdrawalCredentials)) {
117+
return false;
118+
}
119+
120+
// Verify the source is active
121+
if (!isActiveValidator(sourceValidator, state.epochCtx.epoch)) {
122+
return false;
123+
}
124+
125+
// Verify exit for source has not been initiated
126+
if (sourceValidator.exitEpoch !== FAR_FUTURE_EPOCH) {
127+
return false;
128+
}
129+
130+
return true;
73131
}

packages/state-transition/src/cache/epochTransitionCache.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,6 @@ export interface EpochTransitionCache {
139139
*/
140140
validators: phase0.Validator[];
141141

142-
/**
143-
* This is for electra only
144-
* Validators that're switched to compounding during processPendingConsolidations(), not available in beforeProcessEpoch()
145-
*/
146-
newCompoundingValidators?: Set<ValidatorIndex>;
147-
148142
/**
149143
* balances array will be populated by processRewardsAndPenalties() and consumed by processEffectiveBalanceUpdates().
150144
* processRewardsAndPenalties() already has a regular Javascript array of balances.
@@ -518,8 +512,6 @@ export function beforeProcessEpoch(
518512
inclusionDelays,
519513
flags,
520514
validators,
521-
// will be assigned in processPendingConsolidations()
522-
newCompoundingValidators: undefined,
523515
// Will be assigned in processRewardsAndPenalties()
524516
balances: undefined,
525517
};

packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ export function processEffectiveBalanceUpdates(
4646
// so it's recycled here for performance.
4747
const balances = cache.balances ?? state.balances.getAll();
4848
const currentEpochValidators = cache.validators;
49-
const newCompoundingValidators = cache.newCompoundingValidators ?? new Set();
5049

5150
let numUpdate = 0;
5251
for (let i = 0, len = balances.length; i < len; i++) {
@@ -61,9 +60,9 @@ export function processEffectiveBalanceUpdates(
6160
effectiveBalanceLimit = MAX_EFFECTIVE_BALANCE;
6261
} else {
6362
// from electra, effectiveBalanceLimit is per validator
64-
const isCompoundingValidator =
65-
hasCompoundingWithdrawalCredential(currentEpochValidators[i].withdrawalCredentials) ||
66-
newCompoundingValidators.has(i);
63+
const isCompoundingValidator = hasCompoundingWithdrawalCredential(
64+
currentEpochValidators[i].withdrawalCredentials
65+
);
6766
effectiveBalanceLimit = isCompoundingValidator ? MAX_EFFECTIVE_BALANCE_ELECTRA : MIN_ACTIVATION_BALANCE;
6867
}
6968

packages/state-transition/src/epoch/processPendingConsolidations.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import {ValidatorIndex} from "@lodestar/types";
21
import {CachedBeaconStateElectra, EpochTransitionCache} from "../types.js";
32
import {decreaseBalance, increaseBalance} from "../util/balance.js";
43
import {getActiveBalance} from "../util/validator.js";
5-
import {switchToCompoundingValidator} from "../util/electra.js";
64

75
/**
86
* Starting from Electra:
@@ -22,7 +20,6 @@ export function processPendingConsolidations(state: CachedBeaconStateElectra, ca
2220
let nextPendingConsolidation = 0;
2321
const validators = state.validators;
2422
const cachedBalances = cache.balances;
25-
const newCompoundingValidators = new Set<ValidatorIndex>();
2623

2724
for (const pendingConsolidation of state.pendingConsolidations.getAllReadonly()) {
2825
const {sourceIndex, targetIndex} = pendingConsolidation;
@@ -36,9 +33,6 @@ export function processPendingConsolidations(state: CachedBeaconStateElectra, ca
3633
if (sourceValidator.withdrawableEpoch > nextEpoch) {
3734
break;
3835
}
39-
// Churn any target excess active balance of target and raise its max
40-
switchToCompoundingValidator(state, targetIndex);
41-
newCompoundingValidators.add(targetIndex);
4236
// Move active balance to target. Excess balance is withdrawable.
4337
const activeBalance = getActiveBalance(state, sourceIndex);
4438
decreaseBalance(state, sourceIndex, activeBalance);
@@ -51,6 +45,5 @@ export function processPendingConsolidations(state: CachedBeaconStateElectra, ca
5145
nextPendingConsolidation++;
5246
}
5347

54-
cache.newCompoundingValidators = newCompoundingValidators;
5548
state.pendingConsolidations = state.pendingConsolidations.sliceFrom(nextPendingConsolidation);
5649
}

packages/state-transition/src/util/electra.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,13 @@ export function hasExecutionWithdrawalCredential(withdrawalCredentials: Uint8Arr
1717
export function switchToCompoundingValidator(state: CachedBeaconStateElectra, index: ValidatorIndex): void {
1818
const validator = state.validators.get(index);
1919

20-
if (hasEth1WithdrawalCredential(validator.withdrawalCredentials)) {
21-
// directly modifying the byte leads to ssz missing the modification resulting into
22-
// wrong root compute, although slicing can be avoided but anyway this is not going
23-
// to be a hot path so its better to clean slice and avoid side effects
24-
const newWithdrawalCredentials = validator.withdrawalCredentials.slice();
25-
newWithdrawalCredentials[0] = COMPOUNDING_WITHDRAWAL_PREFIX;
26-
validator.withdrawalCredentials = newWithdrawalCredentials;
27-
queueExcessActiveBalance(state, index);
28-
}
20+
// directly modifying the byte leads to ssz missing the modification resulting into
21+
// wrong root compute, although slicing can be avoided but anyway this is not going
22+
// to be a hot path so its better to clean slice and avoid side effects
23+
const newWithdrawalCredentials = validator.withdrawalCredentials.slice();
24+
newWithdrawalCredentials[0] = COMPOUNDING_WITHDRAWAL_PREFIX;
25+
validator.withdrawalCredentials = newWithdrawalCredentials;
26+
queueExcessActiveBalance(state, index);
2927
}
3028

3129
export function queueExcessActiveBalance(state: CachedBeaconStateElectra, index: ValidatorIndex): void {

0 commit comments

Comments
 (0)