-
-
Notifications
You must be signed in to change notification settings - Fork 348
/
Copy pathprocessWithdrawalRequest.ts
99 lines (87 loc) · 3.76 KB
/
processWithdrawalRequest.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
import {
FAR_FUTURE_EPOCH,
FULL_EXIT_REQUEST_AMOUNT,
ForkSeq,
MIN_ACTIVATION_BALANCE,
PENDING_PARTIAL_WITHDRAWALS_LIMIT,
} from "@lodestar/params";
import {electra, phase0, ssz} from "@lodestar/types";
import {toHex} from "@lodestar/utils";
import {CachedBeaconStateElectra} from "../types.js";
import {hasCompoundingWithdrawalCredential, hasExecutionWithdrawalCredential} from "../util/electra.js";
import {computeExitEpochAndUpdateChurn} from "../util/epoch.js";
import {getPendingBalanceToWithdraw, isActiveValidator} from "../util/validator.js";
import {initiateValidatorExit} from "./initiateValidatorExit.js";
export function processWithdrawalRequest(
fork: ForkSeq,
state: CachedBeaconStateElectra,
withdrawalRequest: electra.WithdrawalRequest
): void {
const amount = Number(withdrawalRequest.amount);
const {pendingPartialWithdrawals, validators, epochCtx} = state;
// no need to use unfinalized pubkey cache from 6110 as validator won't be active anyway
const {pubkey2index, config} = epochCtx;
const isFullExitRequest = amount === FULL_EXIT_REQUEST_AMOUNT;
// If partial withdrawal queue is full, only full exits are processed
if (pendingPartialWithdrawals.length >= PENDING_PARTIAL_WITHDRAWALS_LIMIT && !isFullExitRequest) {
return;
}
// bail out if validator is not in beacon state
// note that we don't need to check for 6110 unfinalized vals as they won't be eligible for withdraw/exit anyway
const validatorIndex = pubkey2index.get(withdrawalRequest.validatorPubkey);
if (validatorIndex === null) {
return;
}
const validator = validators.get(validatorIndex);
if (!isValidatorEligibleForWithdrawOrExit(validator, withdrawalRequest.sourceAddress, state)) {
return;
}
// TODO Electra: Consider caching pendingPartialWithdrawals
const pendingBalanceToWithdraw = getPendingBalanceToWithdraw(state, validatorIndex);
const validatorBalance = state.balances.get(validatorIndex);
if (isFullExitRequest) {
// only exit validator if it has no pending withdrawals in the queue
if (pendingBalanceToWithdraw === 0) {
initiateValidatorExit(fork, state, validator);
}
return;
}
// partial withdrawal request
const hasSufficientEffectiveBalance = validator.effectiveBalance >= MIN_ACTIVATION_BALANCE;
const hasExcessBalance = validatorBalance > MIN_ACTIVATION_BALANCE + pendingBalanceToWithdraw;
// Only allow partial withdrawals with compounding withdrawal credentials
if (
hasCompoundingWithdrawalCredential(validator.withdrawalCredentials) &&
hasSufficientEffectiveBalance &&
hasExcessBalance
) {
const amountToWithdraw = BigInt(
Math.min(validatorBalance - MIN_ACTIVATION_BALANCE - pendingBalanceToWithdraw, amount)
);
const exitQueueEpoch = computeExitEpochAndUpdateChurn(state, amountToWithdraw);
const withdrawableEpoch = exitQueueEpoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY;
const pendingPartialWithdrawal = ssz.electra.PendingPartialWithdrawal.toViewDU({
validatorIndex,
amount: amountToWithdraw,
withdrawableEpoch,
});
state.pendingPartialWithdrawals.push(pendingPartialWithdrawal);
}
}
function isValidatorEligibleForWithdrawOrExit(
validator: phase0.Validator,
sourceAddress: Uint8Array,
state: CachedBeaconStateElectra
): boolean {
const {withdrawalCredentials} = validator;
const addressStr = toHex(withdrawalCredentials.subarray(12));
const sourceAddressStr = toHex(sourceAddress);
const {epoch: currentEpoch, config} = state.epochCtx;
return (
hasExecutionWithdrawalCredential(withdrawalCredentials) &&
addressStr === sourceAddressStr &&
isActiveValidator(validator, currentEpoch) &&
validator.exitEpoch === FAR_FUTURE_EPOCH &&
currentEpoch >= validator.activationEpoch + config.SHARD_COMMITTEE_PERIOD
);
}