@lodestar/state-transition 1.40.0-dev.0ae7a89ead → 1.40.0-dev.4acd3ce568
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/block/index.d.ts +1 -0
- package/lib/block/index.d.ts.map +1 -1
- package/lib/block/index.js +1 -0
- package/lib/block/index.js.map +1 -1
- package/lib/block/processDepositRequest.d.ts +8 -2
- package/lib/block/processDepositRequest.d.ts.map +1 -1
- package/lib/block/processDepositRequest.js +81 -8
- package/lib/block/processDepositRequest.js.map +1 -1
- package/lib/block/processExecutionPayloadBid.d.ts.map +1 -1
- package/lib/block/processExecutionPayloadBid.js +14 -27
- package/lib/block/processExecutionPayloadBid.js.map +1 -1
- package/lib/block/processExecutionPayloadEnvelope.d.ts.map +1 -1
- package/lib/block/processExecutionPayloadEnvelope.js +25 -24
- package/lib/block/processExecutionPayloadEnvelope.js.map +1 -1
- package/lib/block/processOperations.js +1 -1
- package/lib/block/processOperations.js.map +1 -1
- package/lib/block/processVoluntaryExit.d.ts +1 -1
- package/lib/block/processVoluntaryExit.d.ts.map +1 -1
- package/lib/block/processVoluntaryExit.js +44 -2
- package/lib/block/processVoluntaryExit.js.map +1 -1
- package/lib/block/processWithdrawals.d.ts +1 -0
- package/lib/block/processWithdrawals.d.ts.map +1 -1
- package/lib/block/processWithdrawals.js +115 -66
- package/lib/block/processWithdrawals.js.map +1 -1
- package/lib/epoch/processBuilderPendingPayments.d.ts.map +1 -1
- package/lib/epoch/processBuilderPendingPayments.js +1 -4
- package/lib/epoch/processBuilderPendingPayments.js.map +1 -1
- package/lib/util/electra.d.ts.map +1 -1
- package/lib/util/electra.js +1 -2
- package/lib/util/electra.js.map +1 -1
- package/lib/util/gloas.d.ts +43 -3
- package/lib/util/gloas.d.ts.map +1 -1
- package/lib/util/gloas.js +93 -5
- package/lib/util/gloas.js.map +1 -1
- package/lib/util/validator.d.ts +5 -0
- package/lib/util/validator.d.ts.map +1 -1
- package/lib/util/validator.js +25 -2
- package/lib/util/validator.js.map +1 -1
- package/package.json +7 -7
- package/src/block/index.ts +1 -0
- package/src/block/processDepositRequest.ts +101 -8
- package/src/block/processExecutionPayloadBid.ts +18 -40
- package/src/block/processExecutionPayloadEnvelope.ts +33 -29
- package/src/block/processOperations.ts +1 -1
- package/src/block/processVoluntaryExit.ts +59 -4
- package/src/block/processWithdrawals.ts +162 -70
- package/src/epoch/processBuilderPendingPayments.ts +1 -5
- package/src/util/electra.ts +1 -4
- package/src/util/gloas.ts +109 -8
- package/src/util/validator.ts +31 -1
|
@@ -2,22 +2,29 @@ import {byteArrayEquals} from "@chainsafe/ssz";
|
|
|
2
2
|
import {
|
|
3
3
|
FAR_FUTURE_EPOCH,
|
|
4
4
|
ForkSeq,
|
|
5
|
+
MAX_BUILDERS_PER_WITHDRAWALS_SWEEP,
|
|
5
6
|
MAX_EFFECTIVE_BALANCE,
|
|
6
7
|
MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP,
|
|
7
8
|
MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP,
|
|
8
9
|
MAX_WITHDRAWALS_PER_PAYLOAD,
|
|
9
10
|
MIN_ACTIVATION_BALANCE,
|
|
10
11
|
} from "@lodestar/params";
|
|
11
|
-
import {ValidatorIndex, capella, ssz} from "@lodestar/types";
|
|
12
|
+
import {BuilderIndex, ValidatorIndex, capella, ssz} from "@lodestar/types";
|
|
12
13
|
import {toRootHex} from "@lodestar/utils";
|
|
13
14
|
import {CachedBeaconStateCapella, CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";
|
|
14
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
convertBuilderIndexToValidatorIndex,
|
|
17
|
+
convertValidatorIndexToBuilderIndex,
|
|
18
|
+
isBuilderIndex,
|
|
19
|
+
isParentBlockFull,
|
|
20
|
+
} from "../util/gloas.ts";
|
|
15
21
|
import {
|
|
16
22
|
decreaseBalance,
|
|
17
23
|
getMaxEffectiveBalance,
|
|
18
24
|
hasEth1WithdrawalCredential,
|
|
19
25
|
hasExecutionWithdrawalCredential,
|
|
20
26
|
isCapellaPayloadHeader,
|
|
27
|
+
isPartiallyWithdrawableValidator,
|
|
21
28
|
} from "../util/index.js";
|
|
22
29
|
|
|
23
30
|
export function processWithdrawals(
|
|
@@ -32,9 +39,14 @@ export function processWithdrawals(
|
|
|
32
39
|
|
|
33
40
|
// processedBuilderWithdrawalsCount is withdrawals coming from builder payment since gloas (EIP-7732)
|
|
34
41
|
// processedPartialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002)
|
|
42
|
+
// processedBuildersSweepCount is withdrawals from builder sweep since gloas (EIP-7732)
|
|
35
43
|
// processedValidatorSweepCount is withdrawals coming from validator sweep
|
|
36
|
-
const {
|
|
37
|
-
|
|
44
|
+
const {
|
|
45
|
+
expectedWithdrawals,
|
|
46
|
+
processedBuilderWithdrawalsCount,
|
|
47
|
+
processedPartialWithdrawalsCount,
|
|
48
|
+
processedBuildersSweepCount,
|
|
49
|
+
} = getExpectedWithdrawals(fork, state);
|
|
38
50
|
const numWithdrawals = expectedWithdrawals.length;
|
|
39
51
|
|
|
40
52
|
// After gloas, withdrawals are verified later in processExecutionPayloadEnvelope
|
|
@@ -78,20 +90,20 @@ export function processWithdrawals(
|
|
|
78
90
|
|
|
79
91
|
if (fork >= ForkSeq.gloas) {
|
|
80
92
|
const stateGloas = state as CachedBeaconStateGloas;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
stateGloas.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
|
|
94
|
+
// Store expected withdrawals for verification
|
|
95
|
+
stateGloas.payloadExpectedWithdrawals = ssz.capella.Withdrawals.toViewDU(expectedWithdrawals);
|
|
96
|
+
|
|
97
|
+
// Update builder pending withdrawals queue
|
|
98
|
+
stateGloas.builderPendingWithdrawals = stateGloas.builderPendingWithdrawals.sliceFrom(
|
|
99
|
+
processedBuilderWithdrawalsCount
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Update next builder index for sweep
|
|
103
|
+
if (stateGloas.builders.length > 0) {
|
|
104
|
+
const nextIndex = stateGloas.nextWithdrawalBuilderIndex + processedBuildersSweepCount;
|
|
105
|
+
stateGloas.nextWithdrawalBuilderIndex = nextIndex % stateGloas.builders.length;
|
|
106
|
+
}
|
|
95
107
|
}
|
|
96
108
|
// Update the nextWithdrawalIndex
|
|
97
109
|
// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.0/specs/capella/beacon-chain.md#new-update_next_withdrawal_index
|
|
@@ -116,10 +128,11 @@ export function processWithdrawals(
|
|
|
116
128
|
function getBuilderWithdrawals(
|
|
117
129
|
state: CachedBeaconStateGloas,
|
|
118
130
|
withdrawalIndex: number,
|
|
119
|
-
|
|
131
|
+
priorWithdrawals: capella.Withdrawal[],
|
|
132
|
+
builderBalanceAfterWithdrawals: Map<number, number>
|
|
120
133
|
): {builderWithdrawals: capella.Withdrawal[]; withdrawalIndex: number; processedCount: number} {
|
|
134
|
+
const withdrawalsLimit = MAX_WITHDRAWALS_PER_PAYLOAD - 1;
|
|
121
135
|
const builderWithdrawals: capella.Withdrawal[] = [];
|
|
122
|
-
const epoch = state.epochCtx.epoch;
|
|
123
136
|
const allBuilderPendingWithdrawals =
|
|
124
137
|
state.builderPendingWithdrawals.length <= MAX_WITHDRAWALS_PER_PAYLOAD
|
|
125
138
|
? state.builderPendingWithdrawals.getAllReadonly()
|
|
@@ -127,55 +140,100 @@ function getBuilderWithdrawals(
|
|
|
127
140
|
|
|
128
141
|
let processedCount = 0;
|
|
129
142
|
for (let i = 0; i < state.builderPendingWithdrawals.length; i++) {
|
|
143
|
+
// Check combined length against limit
|
|
144
|
+
const allWithdrawals = priorWithdrawals.length + builderWithdrawals.length;
|
|
145
|
+
if (allWithdrawals >= withdrawalsLimit) {
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
|
|
130
149
|
const withdrawal = allBuilderPendingWithdrawals
|
|
131
150
|
? allBuilderPendingWithdrawals[i]
|
|
132
151
|
: state.builderPendingWithdrawals.getReadonly(i);
|
|
133
152
|
|
|
134
|
-
|
|
135
|
-
|
|
153
|
+
const builderIndex = withdrawal.builderIndex;
|
|
154
|
+
|
|
155
|
+
// Get builder balance (from builder.balance, not state.balances)
|
|
156
|
+
let balance = builderBalanceAfterWithdrawals.get(builderIndex);
|
|
157
|
+
if (balance === undefined) {
|
|
158
|
+
balance = state.builders.getReadonly(builderIndex).balance;
|
|
159
|
+
builderBalanceAfterWithdrawals.set(builderIndex, balance);
|
|
136
160
|
}
|
|
137
161
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
162
|
+
// Use the withdrawal amount directly as specified in the spec
|
|
163
|
+
builderWithdrawals.push({
|
|
164
|
+
index: withdrawalIndex,
|
|
165
|
+
validatorIndex: convertBuilderIndexToValidatorIndex(builderIndex),
|
|
166
|
+
address: withdrawal.feeRecipient,
|
|
167
|
+
amount: BigInt(withdrawal.amount),
|
|
168
|
+
});
|
|
169
|
+
withdrawalIndex++;
|
|
170
|
+
builderBalanceAfterWithdrawals.set(builderIndex, balance - withdrawal.amount);
|
|
141
171
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
balance = state.balances.get(builderIndex);
|
|
145
|
-
balanceAfterWithdrawals.set(builderIndex, balance);
|
|
146
|
-
}
|
|
172
|
+
processedCount++;
|
|
173
|
+
}
|
|
147
174
|
|
|
148
|
-
|
|
175
|
+
return {builderWithdrawals, withdrawalIndex, processedCount};
|
|
176
|
+
}
|
|
149
177
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
178
|
+
function getBuildersSweepWithdrawals(
|
|
179
|
+
state: CachedBeaconStateGloas,
|
|
180
|
+
withdrawalIndex: number,
|
|
181
|
+
numPriorWithdrawal: number,
|
|
182
|
+
builderBalanceAfterWithdrawals: Map<number, number>
|
|
183
|
+
): {buildersSweepWithdrawals: capella.Withdrawal[]; withdrawalIndex: number; processedCount: number} {
|
|
184
|
+
const withdrawalsLimit = MAX_WITHDRAWALS_PER_PAYLOAD - 1;
|
|
185
|
+
const buildersSweepWithdrawals: capella.Withdrawal[] = [];
|
|
186
|
+
const epoch = state.epochCtx.epoch;
|
|
187
|
+
const builders = state.builders;
|
|
156
188
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
189
|
+
// Return early if no builders
|
|
190
|
+
if (builders.length === 0) {
|
|
191
|
+
return {buildersSweepWithdrawals, withdrawalIndex, processedCount: 0};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const buildersLimit = Math.min(builders.length, MAX_BUILDERS_PER_WITHDRAWALS_SWEEP);
|
|
195
|
+
let processedCount = 0;
|
|
196
|
+
|
|
197
|
+
for (let n = 0; n < buildersLimit; n++) {
|
|
198
|
+
if (buildersSweepWithdrawals.length + numPriorWithdrawal >= withdrawalsLimit) {
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Get next builder in turn
|
|
203
|
+
const builderIndex = (state.nextWithdrawalBuilderIndex + n) % builders.length;
|
|
204
|
+
const builder = builders.getReadonly(builderIndex);
|
|
205
|
+
|
|
206
|
+
// Get builder balance
|
|
207
|
+
let balance = builderBalanceAfterWithdrawals.get(builderIndex);
|
|
208
|
+
if (balance === undefined) {
|
|
209
|
+
balance = builder.balance;
|
|
210
|
+
builderBalanceAfterWithdrawals.set(builderIndex, balance);
|
|
167
211
|
}
|
|
212
|
+
|
|
213
|
+
// Check if builder is withdrawable and has balance
|
|
214
|
+
if (builder.withdrawableEpoch <= epoch && balance > 0) {
|
|
215
|
+
// Withdraw full balance to builder's execution address
|
|
216
|
+
buildersSweepWithdrawals.push({
|
|
217
|
+
index: withdrawalIndex,
|
|
218
|
+
validatorIndex: convertBuilderIndexToValidatorIndex(builderIndex),
|
|
219
|
+
address: builder.executionAddress,
|
|
220
|
+
amount: BigInt(balance),
|
|
221
|
+
});
|
|
222
|
+
withdrawalIndex++;
|
|
223
|
+
builderBalanceAfterWithdrawals.set(builderIndex, 0);
|
|
224
|
+
}
|
|
225
|
+
|
|
168
226
|
processedCount++;
|
|
169
227
|
}
|
|
170
228
|
|
|
171
|
-
return {
|
|
229
|
+
return {buildersSweepWithdrawals, withdrawalIndex, processedCount};
|
|
172
230
|
}
|
|
173
231
|
|
|
174
232
|
function getPendingPartialWithdrawals(
|
|
175
233
|
state: CachedBeaconStateElectra,
|
|
176
234
|
withdrawalIndex: number,
|
|
177
235
|
numPriorWithdrawal: number,
|
|
178
|
-
|
|
236
|
+
validatorBalanceAfterWithdrawals: Map<ValidatorIndex, number>
|
|
179
237
|
): {pendingPartialWithdrawals: capella.Withdrawal[]; withdrawalIndex: number; processedCount: number} {
|
|
180
238
|
const epoch = state.epochCtx.epoch;
|
|
181
239
|
const pendingPartialWithdrawals: capella.Withdrawal[] = [];
|
|
@@ -203,17 +261,17 @@ function getPendingPartialWithdrawals(
|
|
|
203
261
|
: state.pendingPartialWithdrawals.getReadonly(i);
|
|
204
262
|
if (
|
|
205
263
|
withdrawal.withdrawableEpoch > epoch ||
|
|
206
|
-
pendingPartialWithdrawals.length + numPriorWithdrawal
|
|
264
|
+
pendingPartialWithdrawals.length + numPriorWithdrawal >= partialWithdrawalBound
|
|
207
265
|
) {
|
|
208
266
|
break;
|
|
209
267
|
}
|
|
210
268
|
|
|
211
269
|
const validatorIndex = withdrawal.validatorIndex;
|
|
212
270
|
const validator = validators.getReadonly(validatorIndex);
|
|
213
|
-
let balance =
|
|
271
|
+
let balance = validatorBalanceAfterWithdrawals.get(validatorIndex);
|
|
214
272
|
if (balance === undefined) {
|
|
215
273
|
balance = state.balances.get(validatorIndex);
|
|
216
|
-
|
|
274
|
+
validatorBalanceAfterWithdrawals.set(validatorIndex, balance);
|
|
217
275
|
}
|
|
218
276
|
|
|
219
277
|
if (
|
|
@@ -231,7 +289,7 @@ function getPendingPartialWithdrawals(
|
|
|
231
289
|
amount: withdrawableBalance,
|
|
232
290
|
});
|
|
233
291
|
withdrawalIndex++;
|
|
234
|
-
|
|
292
|
+
validatorBalanceAfterWithdrawals.set(validatorIndex, balance - Number(withdrawableBalance));
|
|
235
293
|
}
|
|
236
294
|
processedCount++;
|
|
237
295
|
}
|
|
@@ -244,7 +302,7 @@ function getValidatorsSweepWithdrawals(
|
|
|
244
302
|
state: CachedBeaconStateCapella | CachedBeaconStateElectra | CachedBeaconStateGloas,
|
|
245
303
|
withdrawalIndex: number,
|
|
246
304
|
numPriorWithdrawal: number,
|
|
247
|
-
|
|
305
|
+
validatorBalanceAfterWithdrawals: Map<ValidatorIndex, number>
|
|
248
306
|
): {sweepWithdrawals: capella.Withdrawal[]; processedCount: number} {
|
|
249
307
|
const sweepWithdrawals: capella.Withdrawal[] = [];
|
|
250
308
|
const epoch = state.epochCtx.epoch;
|
|
@@ -264,13 +322,13 @@ function getValidatorsSweepWithdrawals(
|
|
|
264
322
|
const validatorIndex = (nextWithdrawalValidatorIndex + n) % validators.length;
|
|
265
323
|
|
|
266
324
|
const validator = validators.getReadonly(validatorIndex);
|
|
267
|
-
let balance =
|
|
325
|
+
let balance = validatorBalanceAfterWithdrawals.get(validatorIndex);
|
|
268
326
|
if (balance === undefined) {
|
|
269
327
|
balance = balances.get(validatorIndex);
|
|
270
|
-
|
|
328
|
+
validatorBalanceAfterWithdrawals.set(validatorIndex, balance);
|
|
271
329
|
}
|
|
272
330
|
|
|
273
|
-
const {withdrawableEpoch, withdrawalCredentials
|
|
331
|
+
const {withdrawableEpoch, withdrawalCredentials} = validator;
|
|
274
332
|
const hasWithdrawableCredentials = isPostElectra
|
|
275
333
|
? hasExecutionWithdrawalCredential(withdrawalCredentials)
|
|
276
334
|
: hasEth1WithdrawalCredential(withdrawalCredentials);
|
|
@@ -290,13 +348,11 @@ function getValidatorsSweepWithdrawals(
|
|
|
290
348
|
amount: BigInt(balance),
|
|
291
349
|
});
|
|
292
350
|
withdrawalIndex++;
|
|
293
|
-
|
|
294
|
-
} else if (
|
|
295
|
-
effectiveBalance === (isPostElectra ? getMaxEffectiveBalance(withdrawalCredentials) : MAX_EFFECTIVE_BALANCE) &&
|
|
296
|
-
balance > effectiveBalance
|
|
297
|
-
) {
|
|
351
|
+
validatorBalanceAfterWithdrawals.set(validatorIndex, 0);
|
|
352
|
+
} else if (isPartiallyWithdrawableValidator(fork, validator, balance)) {
|
|
298
353
|
// capella partial withdrawal
|
|
299
|
-
const
|
|
354
|
+
const maxEffectiveBalance = isPostElectra ? getMaxEffectiveBalance(withdrawalCredentials) : MAX_EFFECTIVE_BALANCE;
|
|
355
|
+
const partialAmount = balance - maxEffectiveBalance;
|
|
300
356
|
sweepWithdrawals.push({
|
|
301
357
|
index: withdrawalIndex,
|
|
302
358
|
validatorIndex,
|
|
@@ -304,7 +360,7 @@ function getValidatorsSweepWithdrawals(
|
|
|
304
360
|
amount: BigInt(partialAmount),
|
|
305
361
|
});
|
|
306
362
|
withdrawalIndex++;
|
|
307
|
-
|
|
363
|
+
validatorBalanceAfterWithdrawals.set(validatorIndex, balance - partialAmount);
|
|
308
364
|
}
|
|
309
365
|
processedCount++;
|
|
310
366
|
}
|
|
@@ -317,7 +373,16 @@ function applyWithdrawals(
|
|
|
317
373
|
withdrawals: capella.Withdrawal[]
|
|
318
374
|
): void {
|
|
319
375
|
for (const withdrawal of withdrawals) {
|
|
320
|
-
|
|
376
|
+
if (isBuilderIndex(withdrawal.validatorIndex)) {
|
|
377
|
+
// Handle builder withdrawal
|
|
378
|
+
const builderIndex = convertValidatorIndexToBuilderIndex(withdrawal.validatorIndex);
|
|
379
|
+
const builder = (state as CachedBeaconStateGloas).builders.get(builderIndex);
|
|
380
|
+
const withdrawalAmount = Number(withdrawal.amount);
|
|
381
|
+
builder.balance -= Math.min(withdrawalAmount, builder.balance);
|
|
382
|
+
} else {
|
|
383
|
+
// Handle validator withdrawal
|
|
384
|
+
decreaseBalance(state, withdrawal.validatorIndex, Number(withdrawal.amount));
|
|
385
|
+
}
|
|
321
386
|
}
|
|
322
387
|
}
|
|
323
388
|
|
|
@@ -328,6 +393,7 @@ export function getExpectedWithdrawals(
|
|
|
328
393
|
expectedWithdrawals: capella.Withdrawal[];
|
|
329
394
|
processedBuilderWithdrawalsCount: number;
|
|
330
395
|
processedPartialWithdrawalsCount: number;
|
|
396
|
+
processedBuildersSweepCount: number;
|
|
331
397
|
processedValidatorSweepCount: number;
|
|
332
398
|
} {
|
|
333
399
|
if (fork < ForkSeq.capella) {
|
|
@@ -337,20 +403,28 @@ export function getExpectedWithdrawals(
|
|
|
337
403
|
let withdrawalIndex = state.nextWithdrawalIndex;
|
|
338
404
|
|
|
339
405
|
const expectedWithdrawals: capella.Withdrawal[] = [];
|
|
340
|
-
//
|
|
406
|
+
// Separate maps to track balances after applying withdrawals
|
|
341
407
|
// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.0/specs/capella/beacon-chain.md#new-get_balance_after_withdrawals
|
|
342
|
-
const
|
|
408
|
+
const builderBalanceAfterWithdrawals = new Map<BuilderIndex, number>();
|
|
409
|
+
const validatorBalanceAfterWithdrawals = new Map<ValidatorIndex, number>();
|
|
343
410
|
// partialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002)
|
|
344
411
|
let processedPartialWithdrawalsCount = 0;
|
|
345
412
|
// builderWithdrawalsCount is withdrawals coming from builder payments since Gloas (EIP-7732)
|
|
346
413
|
let processedBuilderWithdrawalsCount = 0;
|
|
414
|
+
// buildersSweepCount is withdrawals from builder sweep since Gloas (EIP-7732)
|
|
415
|
+
let processedBuildersSweepCount = 0;
|
|
347
416
|
|
|
348
417
|
if (fork >= ForkSeq.gloas) {
|
|
349
418
|
const {
|
|
350
419
|
builderWithdrawals,
|
|
351
420
|
withdrawalIndex: newWithdrawalIndex,
|
|
352
421
|
processedCount,
|
|
353
|
-
} = getBuilderWithdrawals(
|
|
422
|
+
} = getBuilderWithdrawals(
|
|
423
|
+
state as CachedBeaconStateGloas,
|
|
424
|
+
withdrawalIndex,
|
|
425
|
+
expectedWithdrawals,
|
|
426
|
+
builderBalanceAfterWithdrawals
|
|
427
|
+
);
|
|
354
428
|
|
|
355
429
|
expectedWithdrawals.push(...builderWithdrawals);
|
|
356
430
|
withdrawalIndex = newWithdrawalIndex;
|
|
@@ -366,7 +440,7 @@ export function getExpectedWithdrawals(
|
|
|
366
440
|
state as CachedBeaconStateElectra,
|
|
367
441
|
withdrawalIndex,
|
|
368
442
|
expectedWithdrawals.length,
|
|
369
|
-
|
|
443
|
+
validatorBalanceAfterWithdrawals
|
|
370
444
|
);
|
|
371
445
|
|
|
372
446
|
expectedWithdrawals.push(...pendingPartialWithdrawals);
|
|
@@ -374,12 +448,29 @@ export function getExpectedWithdrawals(
|
|
|
374
448
|
processedPartialWithdrawalsCount = processedCount;
|
|
375
449
|
}
|
|
376
450
|
|
|
451
|
+
if (fork >= ForkSeq.gloas) {
|
|
452
|
+
const {
|
|
453
|
+
buildersSweepWithdrawals,
|
|
454
|
+
withdrawalIndex: newWithdrawalIndex,
|
|
455
|
+
processedCount,
|
|
456
|
+
} = getBuildersSweepWithdrawals(
|
|
457
|
+
state as CachedBeaconStateGloas,
|
|
458
|
+
withdrawalIndex,
|
|
459
|
+
expectedWithdrawals.length,
|
|
460
|
+
builderBalanceAfterWithdrawals
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
expectedWithdrawals.push(...buildersSweepWithdrawals);
|
|
464
|
+
withdrawalIndex = newWithdrawalIndex;
|
|
465
|
+
processedBuildersSweepCount = processedCount;
|
|
466
|
+
}
|
|
467
|
+
|
|
377
468
|
const {sweepWithdrawals, processedCount: processedValidatorSweepCount} = getValidatorsSweepWithdrawals(
|
|
378
469
|
fork,
|
|
379
470
|
state,
|
|
380
471
|
withdrawalIndex,
|
|
381
472
|
expectedWithdrawals.length,
|
|
382
|
-
|
|
473
|
+
validatorBalanceAfterWithdrawals
|
|
383
474
|
);
|
|
384
475
|
|
|
385
476
|
expectedWithdrawals.push(...sweepWithdrawals);
|
|
@@ -388,6 +479,7 @@ export function getExpectedWithdrawals(
|
|
|
388
479
|
expectedWithdrawals,
|
|
389
480
|
processedBuilderWithdrawalsCount,
|
|
390
481
|
processedPartialWithdrawalsCount,
|
|
482
|
+
processedBuildersSweepCount,
|
|
391
483
|
processedValidatorSweepCount,
|
|
392
484
|
};
|
|
393
485
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {SLOTS_PER_EPOCH} from "@lodestar/params";
|
|
2
2
|
import {ssz} from "@lodestar/types";
|
|
3
3
|
import {CachedBeaconStateGloas} from "../types.ts";
|
|
4
|
-
import {computeExitEpochAndUpdateChurn} from "../util/epoch.ts";
|
|
5
4
|
import {getBuilderPaymentQuorumThreshold} from "../util/gloas.ts";
|
|
6
5
|
|
|
7
6
|
/**
|
|
@@ -12,10 +11,7 @@ export function processBuilderPendingPayments(state: CachedBeaconStateGloas): vo
|
|
|
12
11
|
|
|
13
12
|
for (let i = 0; i < SLOTS_PER_EPOCH; i++) {
|
|
14
13
|
const payment = state.builderPendingPayments.get(i);
|
|
15
|
-
if (payment.weight
|
|
16
|
-
const exitQueueEpoch = computeExitEpochAndUpdateChurn(state, BigInt(payment.withdrawal.amount));
|
|
17
|
-
payment.withdrawal.withdrawableEpoch = exitQueueEpoch + state.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY;
|
|
18
|
-
|
|
14
|
+
if (payment.weight >= quorum) {
|
|
19
15
|
state.builderPendingWithdrawals.push(payment.withdrawal);
|
|
20
16
|
}
|
|
21
17
|
}
|
package/src/util/electra.ts
CHANGED
|
@@ -3,12 +3,9 @@ import {ValidatorIndex, ssz} from "@lodestar/types";
|
|
|
3
3
|
import {G2_POINT_AT_INFINITY} from "../constants/constants.js";
|
|
4
4
|
import {CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";
|
|
5
5
|
import {hasEth1WithdrawalCredential} from "./capella.js";
|
|
6
|
-
import {hasBuilderWithdrawalCredential} from "./gloas.ts";
|
|
7
6
|
|
|
8
7
|
export function hasCompoundingWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean {
|
|
9
|
-
return
|
|
10
|
-
withdrawalCredentials[0] === COMPOUNDING_WITHDRAWAL_PREFIX || hasBuilderWithdrawalCredential(withdrawalCredentials)
|
|
11
|
-
);
|
|
8
|
+
return withdrawalCredentials[0] === COMPOUNDING_WITHDRAWAL_PREFIX;
|
|
12
9
|
}
|
|
13
10
|
|
|
14
11
|
export function hasExecutionWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean {
|
package/src/util/gloas.ts
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import {byteArrayEquals} from "@chainsafe/ssz";
|
|
2
2
|
import {
|
|
3
|
+
BUILDER_INDEX_FLAG,
|
|
3
4
|
BUILDER_PAYMENT_THRESHOLD_DENOMINATOR,
|
|
4
5
|
BUILDER_PAYMENT_THRESHOLD_NUMERATOR,
|
|
5
6
|
BUILDER_WITHDRAWAL_PREFIX,
|
|
6
7
|
EFFECTIVE_BALANCE_INCREMENT,
|
|
8
|
+
FAR_FUTURE_EPOCH,
|
|
9
|
+
MIN_DEPOSIT_AMOUNT,
|
|
7
10
|
SLOTS_PER_EPOCH,
|
|
8
11
|
} from "@lodestar/params";
|
|
9
|
-
import {gloas} from "@lodestar/types";
|
|
10
12
|
import {AttestationData} from "@lodestar/types/phase0";
|
|
11
13
|
import {CachedBeaconStateGloas} from "../types.ts";
|
|
12
14
|
import {getBlockRootAtSlot} from "./blockRoot.ts";
|
|
13
15
|
import {computeEpochAtSlot} from "./epoch.ts";
|
|
14
16
|
import {RootCache} from "./rootCache.ts";
|
|
15
17
|
|
|
16
|
-
export function
|
|
18
|
+
export function isBuilderWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean {
|
|
17
19
|
return withdrawalCredentials[0] === BUILDER_WITHDRAWAL_PREFIX;
|
|
18
20
|
}
|
|
19
21
|
|
|
@@ -25,14 +27,113 @@ export function getBuilderPaymentQuorumThreshold(state: CachedBeaconStateGloas):
|
|
|
25
27
|
return Math.floor(quorum / BUILDER_PAYMENT_THRESHOLD_DENOMINATOR);
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Check if a validator index represents a builder (has the builder flag set).
|
|
32
|
+
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/beacon-chain.md#new-is_builder_index
|
|
33
|
+
*/
|
|
34
|
+
export function isBuilderIndex(validatorIndex: number): boolean {
|
|
35
|
+
return (validatorIndex & BUILDER_INDEX_FLAG) !== 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Convert a builder index to a flagged validator index for use in Withdrawal containers.
|
|
40
|
+
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/beacon-chain.md#new-convert_builder_index_to_validator_index
|
|
41
|
+
*/
|
|
42
|
+
export function convertBuilderIndexToValidatorIndex(builderIndex: number): number {
|
|
43
|
+
return builderIndex | BUILDER_INDEX_FLAG;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Convert a flagged validator index back to a builder index.
|
|
48
|
+
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/beacon-chain.md#new-convert_validator_index_to_builder_index
|
|
49
|
+
*/
|
|
50
|
+
export function convertValidatorIndexToBuilderIndex(validatorIndex: number): number {
|
|
51
|
+
return validatorIndex & ~BUILDER_INDEX_FLAG;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Check if a builder is active (deposited and not yet withdrawable).
|
|
56
|
+
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/beacon-chain.md#isactivebuilder
|
|
57
|
+
*/
|
|
58
|
+
export function isActiveBuilder(state: CachedBeaconStateGloas, builderIndex: number): boolean {
|
|
59
|
+
const builder = state.builders.getReadonly(builderIndex);
|
|
60
|
+
const finalizedEpoch = state.finalizedCheckpoint.epoch;
|
|
61
|
+
|
|
62
|
+
return builder.depositEpoch < finalizedEpoch && builder.withdrawableEpoch === FAR_FUTURE_EPOCH;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get the total pending balance to withdraw for a builder (from withdrawals + payments).
|
|
67
|
+
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/beacon-chain.md#new-get_pending_balance_to_withdraw_for_builder
|
|
68
|
+
*/
|
|
69
|
+
export function getPendingBalanceToWithdrawForBuilder(state: CachedBeaconStateGloas, builderIndex: number): number {
|
|
70
|
+
let pendingBalance = 0;
|
|
71
|
+
|
|
72
|
+
// Sum pending withdrawals
|
|
73
|
+
for (let i = 0; i < state.builderPendingWithdrawals.length; i++) {
|
|
74
|
+
const withdrawal = state.builderPendingWithdrawals.getReadonly(i);
|
|
75
|
+
if (withdrawal.builderIndex === builderIndex) {
|
|
76
|
+
pendingBalance += withdrawal.amount;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Sum pending payments
|
|
81
|
+
for (let i = 0; i < state.builderPendingPayments.length; i++) {
|
|
82
|
+
const payment = state.builderPendingPayments.getReadonly(i);
|
|
83
|
+
if (payment.withdrawal.builderIndex === builderIndex) {
|
|
84
|
+
pendingBalance += payment.withdrawal.amount;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return pendingBalance;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Check if a builder has sufficient balance to cover a bid amount.
|
|
93
|
+
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/beacon-chain.md#new-can_builder_cover_bid
|
|
94
|
+
*/
|
|
95
|
+
export function canBuilderCoverBid(state: CachedBeaconStateGloas, builderIndex: number, bidAmount: number): boolean {
|
|
96
|
+
const builder = state.builders.getReadonly(builderIndex);
|
|
97
|
+
const pendingBalance = getPendingBalanceToWithdrawForBuilder(state, builderIndex);
|
|
98
|
+
const minBalance = MIN_DEPOSIT_AMOUNT + pendingBalance;
|
|
99
|
+
|
|
100
|
+
if (builder.balance < minBalance) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return builder.balance - minBalance >= bidAmount;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Initiate a builder exit by setting their withdrawable epoch.
|
|
109
|
+
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/beacon-chain.md#new-initiate_builder_exit
|
|
110
|
+
*/
|
|
111
|
+
export function initiateBuilderExit(state: CachedBeaconStateGloas, builderIndex: number): void {
|
|
112
|
+
const builder = state.builders.get(builderIndex);
|
|
113
|
+
|
|
114
|
+
// Return if builder already initiated exit
|
|
115
|
+
if (builder.withdrawableEpoch !== FAR_FUTURE_EPOCH) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Set builder exit epoch
|
|
33
120
|
const currentEpoch = computeEpochAtSlot(state.slot);
|
|
121
|
+
builder.withdrawableEpoch = currentEpoch + state.config.MIN_BUILDER_WITHDRAWABILITY_DELAY;
|
|
122
|
+
}
|
|
34
123
|
|
|
35
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Find the index of a builder by their public key.
|
|
126
|
+
* Returns null if not found.
|
|
127
|
+
*
|
|
128
|
+
* May consider builder pubkey cache if performance becomes an issue.
|
|
129
|
+
*/
|
|
130
|
+
export function findBuilderIndexByPubkey(state: CachedBeaconStateGloas, pubkey: Uint8Array): number | null {
|
|
131
|
+
for (let i = 0; i < state.builders.length; i++) {
|
|
132
|
+
if (byteArrayEquals(state.builders.getReadonly(i).pubkey, pubkey)) {
|
|
133
|
+
return i;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
36
137
|
}
|
|
37
138
|
|
|
38
139
|
export function isAttestationSameSlot(state: CachedBeaconStateGloas, data: AttestationData): boolean {
|
package/src/util/validator.ts
CHANGED
|
@@ -2,13 +2,15 @@ import {ChainForkConfig} from "@lodestar/config";
|
|
|
2
2
|
import {
|
|
3
3
|
EFFECTIVE_BALANCE_INCREMENT,
|
|
4
4
|
ForkSeq,
|
|
5
|
+
MAX_EFFECTIVE_BALANCE,
|
|
5
6
|
MAX_EFFECTIVE_BALANCE_ELECTRA,
|
|
6
7
|
MIN_ACTIVATION_BALANCE,
|
|
7
8
|
} from "@lodestar/params";
|
|
8
9
|
import {Epoch, ValidatorIndex, phase0} from "@lodestar/types";
|
|
9
10
|
import {intDiv} from "@lodestar/utils";
|
|
10
11
|
import {BeaconStateAllForks, CachedBeaconStateElectra, CachedBeaconStateGloas, EpochCache} from "../types.js";
|
|
11
|
-
import {
|
|
12
|
+
import {hasEth1WithdrawalCredential} from "./capella.js";
|
|
13
|
+
import {hasCompoundingWithdrawalCredential, hasExecutionWithdrawalCredential} from "./electra.js";
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Check if [[validator]] is active
|
|
@@ -94,6 +96,34 @@ export function getMaxEffectiveBalance(withdrawalCredentials: Uint8Array): numbe
|
|
|
94
96
|
return MIN_ACTIVATION_BALANCE;
|
|
95
97
|
}
|
|
96
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Check if validator is partially withdrawable.
|
|
101
|
+
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/electra/beacon-chain.md#modified-is_partially_withdrawable_validator
|
|
102
|
+
*/
|
|
103
|
+
export function isPartiallyWithdrawableValidator(fork: ForkSeq, validator: phase0.Validator, balance: number): boolean {
|
|
104
|
+
const isPostElectra = fork >= ForkSeq.electra;
|
|
105
|
+
|
|
106
|
+
// Check withdrawal credentials
|
|
107
|
+
const hasWithdrawableCredentials = isPostElectra
|
|
108
|
+
? hasExecutionWithdrawalCredential(validator.withdrawalCredentials)
|
|
109
|
+
: hasEth1WithdrawalCredential(validator.withdrawalCredentials);
|
|
110
|
+
|
|
111
|
+
if (!hasWithdrawableCredentials) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Get max effective balance based on fork
|
|
116
|
+
const maxEffectiveBalance = isPostElectra
|
|
117
|
+
? getMaxEffectiveBalance(validator.withdrawalCredentials)
|
|
118
|
+
: MAX_EFFECTIVE_BALANCE;
|
|
119
|
+
|
|
120
|
+
// Check if at max effective balance and has excess balance
|
|
121
|
+
const hasMaxEffectiveBalance = validator.effectiveBalance === maxEffectiveBalance;
|
|
122
|
+
const hasExcessBalance = balance > maxEffectiveBalance;
|
|
123
|
+
|
|
124
|
+
return hasMaxEffectiveBalance && hasExcessBalance;
|
|
125
|
+
}
|
|
126
|
+
|
|
97
127
|
export function getPendingBalanceToWithdraw(
|
|
98
128
|
fork: ForkSeq,
|
|
99
129
|
state: CachedBeaconStateElectra | CachedBeaconStateGloas,
|