-
-
Notifications
You must be signed in to change notification settings - Fork 348
/
Copy pathprocessPendingDeposits.ts
126 lines (113 loc) Β· 5.21 KB
/
processPendingDeposits.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import {FAR_FUTURE_EPOCH, ForkSeq, GENESIS_SLOT, MAX_PENDING_DEPOSITS_PER_EPOCH} from "@lodestar/params";
import {PendingDeposit} from "@lodestar/types/lib/electra/types.js";
import {addValidatorToRegistry, isValidDepositSignature} from "../block/processDeposit.js";
import {CachedBeaconStateElectra, EpochTransitionCache} from "../types.js";
import {increaseBalance} from "../util/balance.js";
import {hasCompoundingWithdrawalCredential, isValidatorKnown} from "../util/electra.js";
import {computeStartSlotAtEpoch} from "../util/epoch.js";
import {getActivationExitChurnLimit} from "../util/validator.js";
/**
* Starting from Electra:
* Process pending balance deposits from state subject to churn limit and depsoitBalanceToConsume.
* For each eligible `deposit`, call `increaseBalance()`.
* Remove the processed deposits from `state.pendingDeposits`.
* Update `state.depositBalanceToConsume` for the next epoch
*
* TODO Electra: Update ssz library to support batch push to `pendingDeposits`
*/
export function processPendingDeposits(state: CachedBeaconStateElectra, cache: EpochTransitionCache): void {
const nextEpoch = state.epochCtx.epoch + 1;
const availableForProcessing = state.depositBalanceToConsume + BigInt(getActivationExitChurnLimit(state.epochCtx));
let processedAmount = 0;
let nextDepositIndex = 0;
const depositsToPostpone = [];
let isChurnLimitReached = false;
const finalizedSlot = computeStartSlotAtEpoch(state.finalizedCheckpoint.epoch);
for (const deposit of state.pendingDeposits.getAllReadonly()) {
// Do not process deposit requests if Eth1 bridge deposits are not yet applied.
if (
// Is deposit request
deposit.slot > GENESIS_SLOT &&
// There are pending Eth1 bridge deposits
state.eth1DepositIndex < state.depositRequestsStartIndex
) {
break;
}
// Check if deposit has been finalized, otherwise, stop processing.
if (deposit.slot > finalizedSlot) {
break;
}
// Check if number of processed deposits has not reached the limit, otherwise, stop processing.
if (nextDepositIndex >= MAX_PENDING_DEPOSITS_PER_EPOCH) {
break;
}
// Read validator state
let isValidatorExited = false;
let isValidatorWithdrawn = false;
const validatorIndex = state.epochCtx.getValidatorIndex(deposit.pubkey);
if (isValidatorKnown(state, validatorIndex)) {
const validator = state.validators.getReadonly(validatorIndex);
isValidatorExited = validator.exitEpoch < FAR_FUTURE_EPOCH;
isValidatorWithdrawn = validator.withdrawableEpoch < nextEpoch;
}
if (isValidatorWithdrawn) {
// Deposited balance will never become active. Increase balance but do not consume churn
applyPendingDeposit(state, deposit, cache);
} else if (isValidatorExited) {
// Validator is exiting, postpone the deposit until after withdrawable epoch
depositsToPostpone.push(deposit);
} else {
// Check if deposit fits in the churn, otherwise, do no more deposit processing in this epoch.
isChurnLimitReached = processedAmount + deposit.amount > availableForProcessing;
if (isChurnLimitReached) {
break;
}
// Consume churn and apply deposit.
processedAmount += deposit.amount;
applyPendingDeposit(state, deposit, cache);
}
// Regardless of how the deposit was handled, we move on in the queue.
nextDepositIndex++;
}
const remainingPendingDeposits = state.pendingDeposits.sliceFrom(nextDepositIndex);
state.pendingDeposits = remainingPendingDeposits;
// TODO Electra: add a function in ListCompositeTreeView to support batch push operation
for (const deposit of depositsToPostpone) {
state.pendingDeposits.push(deposit);
}
// Accumulate churn only if the churn limit has been hit.
if (isChurnLimitReached) {
state.depositBalanceToConsume = availableForProcessing - BigInt(processedAmount);
} else {
state.depositBalanceToConsume = 0n;
}
}
function applyPendingDeposit(
state: CachedBeaconStateElectra,
deposit: PendingDeposit,
cache: EpochTransitionCache
): void {
const validatorIndex = state.epochCtx.getValidatorIndex(deposit.pubkey);
const {pubkey, withdrawalCredentials, amount, signature} = deposit;
const cachedBalances = cache.balances;
if (!isValidatorKnown(state, validatorIndex)) {
// Verify the deposit signature (proof of possession) which is not checked by the deposit contract
if (isValidDepositSignature(state.config, pubkey, withdrawalCredentials, amount, signature)) {
addValidatorToRegistry(ForkSeq.electra, state, pubkey, withdrawalCredentials, amount);
const newValidatorIndex = state.validators.length - 1;
cache.isCompoundingValidatorArr[newValidatorIndex] = hasCompoundingWithdrawalCredential(withdrawalCredentials);
// set balance, so that the next deposit of same pubkey will increase the balance correctly
// this is to fix the double deposit issue found in mekong
// see https://github.com/ChainSafe/lodestar/pull/7255
if (cachedBalances) {
cachedBalances[newValidatorIndex] = amount;
}
}
} else {
// Increase balance
increaseBalance(state, validatorIndex, amount);
if (cachedBalances) {
cachedBalances[validatorIndex] += amount;
}
}
}