@lodestar/state-transition 1.39.0-dev.84b481ddb5 → 1.39.0-dev.87ff5db949

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.
Files changed (113) hide show
  1. package/lib/block/isValidIndexedAttestation.d.ts +4 -5
  2. package/lib/block/isValidIndexedAttestation.d.ts.map +1 -1
  3. package/lib/block/isValidIndexedAttestation.js +9 -10
  4. package/lib/block/isValidIndexedAttestation.js.map +1 -1
  5. package/lib/block/processAttestationPhase0.d.ts.map +1 -1
  6. package/lib/block/processAttestationPhase0.js +1 -1
  7. package/lib/block/processAttestationPhase0.js.map +1 -1
  8. package/lib/block/processAttestationsAltair.js +1 -1
  9. package/lib/block/processAttestationsAltair.js.map +1 -1
  10. package/lib/block/processAttesterSlashing.d.ts +3 -2
  11. package/lib/block/processAttesterSlashing.d.ts.map +1 -1
  12. package/lib/block/processAttesterSlashing.js +3 -3
  13. package/lib/block/processAttesterSlashing.js.map +1 -1
  14. package/lib/block/processBlsToExecutionChange.d.ts +3 -1
  15. package/lib/block/processBlsToExecutionChange.d.ts.map +1 -1
  16. package/lib/block/processBlsToExecutionChange.js +7 -11
  17. package/lib/block/processBlsToExecutionChange.js.map +1 -1
  18. package/lib/block/processProposerSlashing.d.ts +5 -2
  19. package/lib/block/processProposerSlashing.d.ts.map +1 -1
  20. package/lib/block/processProposerSlashing.js +7 -5
  21. package/lib/block/processProposerSlashing.js.map +1 -1
  22. package/lib/block/processRandao.js +2 -2
  23. package/lib/block/processRandao.js.map +1 -1
  24. package/lib/block/processSyncCommittee.d.ts +2 -1
  25. package/lib/block/processSyncCommittee.d.ts.map +1 -1
  26. package/lib/block/processSyncCommittee.js +6 -4
  27. package/lib/block/processSyncCommittee.js.map +1 -1
  28. package/lib/block/processVoluntaryExit.js +1 -1
  29. package/lib/block/processVoluntaryExit.js.map +1 -1
  30. package/lib/block/processWithdrawals.d.ts +3 -3
  31. package/lib/block/processWithdrawals.d.ts.map +1 -1
  32. package/lib/block/processWithdrawals.js +152 -105
  33. package/lib/block/processWithdrawals.js.map +1 -1
  34. package/lib/index.d.ts +2 -1
  35. package/lib/index.d.ts.map +1 -1
  36. package/lib/index.js +2 -0
  37. package/lib/index.js.map +1 -1
  38. package/lib/rewards/attestationsRewards.d.ts +6 -0
  39. package/lib/rewards/attestationsRewards.d.ts.map +1 -0
  40. package/lib/rewards/attestationsRewards.js +113 -0
  41. package/lib/rewards/attestationsRewards.js.map +1 -0
  42. package/lib/rewards/blockRewards.d.ts +13 -0
  43. package/lib/rewards/blockRewards.d.ts.map +1 -0
  44. package/lib/rewards/blockRewards.js +95 -0
  45. package/lib/rewards/blockRewards.js.map +1 -0
  46. package/lib/rewards/index.d.ts +4 -0
  47. package/lib/rewards/index.d.ts.map +1 -0
  48. package/lib/rewards/index.js +4 -0
  49. package/lib/rewards/index.js.map +1 -0
  50. package/lib/rewards/syncCommitteeRewards.d.ts +6 -0
  51. package/lib/rewards/syncCommitteeRewards.d.ts.map +1 -0
  52. package/lib/rewards/syncCommitteeRewards.js +36 -0
  53. package/lib/rewards/syncCommitteeRewards.js.map +1 -0
  54. package/lib/signatureSets/attesterSlashings.d.ts +4 -5
  55. package/lib/signatureSets/attesterSlashings.d.ts.map +1 -1
  56. package/lib/signatureSets/attesterSlashings.js +10 -7
  57. package/lib/signatureSets/attesterSlashings.js.map +1 -1
  58. package/lib/signatureSets/blsToExecutionChange.d.ts +1 -2
  59. package/lib/signatureSets/blsToExecutionChange.d.ts.map +1 -1
  60. package/lib/signatureSets/blsToExecutionChange.js +2 -2
  61. package/lib/signatureSets/blsToExecutionChange.js.map +1 -1
  62. package/lib/signatureSets/index.d.ts +2 -2
  63. package/lib/signatureSets/index.d.ts.map +1 -1
  64. package/lib/signatureSets/index.js +10 -10
  65. package/lib/signatureSets/index.js.map +1 -1
  66. package/lib/signatureSets/indexedAttestation.d.ts +5 -6
  67. package/lib/signatureSets/indexedAttestation.d.ts.map +1 -1
  68. package/lib/signatureSets/indexedAttestation.js +12 -9
  69. package/lib/signatureSets/indexedAttestation.js.map +1 -1
  70. package/lib/signatureSets/proposer.d.ts +5 -6
  71. package/lib/signatureSets/proposer.d.ts.map +1 -1
  72. package/lib/signatureSets/proposer.js +12 -10
  73. package/lib/signatureSets/proposer.js.map +1 -1
  74. package/lib/signatureSets/proposerSlashings.d.ts +3 -4
  75. package/lib/signatureSets/proposerSlashings.d.ts.map +1 -1
  76. package/lib/signatureSets/proposerSlashings.js +7 -4
  77. package/lib/signatureSets/proposerSlashings.js.map +1 -1
  78. package/lib/signatureSets/randao.d.ts +2 -3
  79. package/lib/signatureSets/randao.d.ts.map +1 -1
  80. package/lib/signatureSets/randao.js +6 -4
  81. package/lib/signatureSets/randao.js.map +1 -1
  82. package/lib/signatureSets/voluntaryExits.d.ts +4 -5
  83. package/lib/signatureSets/voluntaryExits.d.ts.map +1 -1
  84. package/lib/signatureSets/voluntaryExits.js +10 -7
  85. package/lib/signatureSets/voluntaryExits.js.map +1 -1
  86. package/lib/stateTransition.d.ts.map +1 -1
  87. package/lib/stateTransition.js +1 -2
  88. package/lib/stateTransition.js.map +1 -1
  89. package/package.json +14 -11
  90. package/src/block/isValidIndexedAttestation.ts +19 -12
  91. package/src/block/processAttestationPhase0.ts +2 -1
  92. package/src/block/processAttestationsAltair.ts +1 -1
  93. package/src/block/processAttesterSlashing.ts +16 -4
  94. package/src/block/processBlsToExecutionChange.ts +13 -14
  95. package/src/block/processProposerSlashing.ts +21 -11
  96. package/src/block/processRandao.ts +2 -2
  97. package/src/block/processSyncCommittee.ts +7 -4
  98. package/src/block/processVoluntaryExit.ts +1 -1
  99. package/src/block/processWithdrawals.ts +230 -135
  100. package/src/index.ts +2 -2
  101. package/src/rewards/attestationsRewards.ts +200 -0
  102. package/src/rewards/blockRewards.ts +147 -0
  103. package/src/rewards/index.ts +3 -0
  104. package/src/rewards/syncCommitteeRewards.ts +59 -0
  105. package/src/signatureSets/attesterSlashings.ts +10 -9
  106. package/src/signatureSets/blsToExecutionChange.ts +2 -3
  107. package/src/signatureSets/index.ts +11 -11
  108. package/src/signatureSets/indexedAttestation.ts +12 -11
  109. package/src/signatureSets/proposer.ts +11 -11
  110. package/src/signatureSets/proposerSlashings.ts +7 -6
  111. package/src/signatureSets/randao.ts +4 -5
  112. package/src/signatureSets/voluntaryExits.ts +10 -9
  113. package/src/stateTransition.ts +1 -4
@@ -9,7 +9,7 @@ import {
9
9
  MIN_ACTIVATION_BALANCE,
10
10
  } from "@lodestar/params";
11
11
  import {ValidatorIndex, capella, ssz} from "@lodestar/types";
12
- import {MapDef, toRootHex} from "@lodestar/utils";
12
+ import {toRootHex} from "@lodestar/utils";
13
13
  import {CachedBeaconStateCapella, CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";
14
14
  import {isBuilderPaymentWithdrawable, isParentBlockFull} from "../util/gloas.ts";
15
15
  import {
@@ -30,13 +30,11 @@ export function processWithdrawals(
30
30
  return;
31
31
  }
32
32
 
33
- // processedPartialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002)
34
33
  // processedBuilderWithdrawalsCount is withdrawals coming from builder payment since gloas (EIP-7732)
35
- const {
36
- withdrawals: expectedWithdrawals,
37
- processedPartialWithdrawalsCount,
38
- processedBuilderWithdrawalsCount,
39
- } = getExpectedWithdrawals(fork, state);
34
+ // processedPartialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002)
35
+ // processedValidatorSweepCount is withdrawals coming from validator sweep
36
+ const {expectedWithdrawals, processedBuilderWithdrawalsCount, processedPartialWithdrawalsCount} =
37
+ getExpectedWithdrawals(fork, state);
40
38
  const numWithdrawals = expectedWithdrawals.length;
41
39
 
42
40
  // After gloas, withdrawals are verified later in processExecutionPayloadEnvelope
@@ -68,12 +66,10 @@ export function processWithdrawals(
68
66
  }
69
67
  }
70
68
 
71
- for (let i = 0; i < numWithdrawals; i++) {
72
- const withdrawal = expectedWithdrawals[i];
73
- decreaseBalance(state, withdrawal.validatorIndex, Number(withdrawal.amount));
74
- }
69
+ applyWithdrawals(state, expectedWithdrawals);
75
70
 
76
71
  if (fork >= ForkSeq.electra) {
72
+ // https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.0/specs/electra/beacon-chain.md#new-update_pending_partial_withdrawals
77
73
  const stateElectra = state as CachedBeaconStateElectra;
78
74
  stateElectra.pendingPartialWithdrawals = stateElectra.pendingPartialWithdrawals.sliceFrom(
79
75
  processedPartialWithdrawalsCount
@@ -97,13 +93,14 @@ export function processWithdrawals(
97
93
  ...remainingWithdrawals,
98
94
  ]);
99
95
  }
100
-
101
96
  // Update the nextWithdrawalIndex
97
+ // https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.0/specs/capella/beacon-chain.md#new-update_next_withdrawal_index
102
98
  const latestWithdrawal = expectedWithdrawals.at(-1);
103
99
  if (latestWithdrawal) {
104
100
  state.nextWithdrawalIndex = latestWithdrawal.index + 1;
105
101
  }
106
102
 
103
+ // https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.0/specs/capella/beacon-chain.md#new-update_next_withdrawal_validator_index
107
104
  // Update the nextWithdrawalValidatorIndex
108
105
  if (latestWithdrawal && expectedWithdrawals.length === MAX_WITHDRAWALS_PER_PAYLOAD) {
109
106
  // All slots filled, nextWithdrawalValidatorIndex should be validatorIndex having next turn
@@ -116,142 +113,163 @@ export function processWithdrawals(
116
113
  }
117
114
  }
118
115
 
119
- export function getExpectedWithdrawals(
120
- fork: ForkSeq,
121
- state: CachedBeaconStateCapella | CachedBeaconStateElectra | CachedBeaconStateGloas
122
- ): {
123
- withdrawals: capella.Withdrawal[];
124
- sampledValidators: number;
125
- processedPartialWithdrawalsCount: number;
126
- processedBuilderWithdrawalsCount: number;
127
- } {
128
- if (fork < ForkSeq.capella) {
129
- throw new Error(`getExpectedWithdrawals not supported at forkSeq=${fork} < ForkSeq.capella`);
130
- }
131
-
116
+ function getBuilderWithdrawals(
117
+ state: CachedBeaconStateGloas,
118
+ withdrawalIndex: number,
119
+ balanceAfterWithdrawals: Map<ValidatorIndex, number>
120
+ ): {builderWithdrawals: capella.Withdrawal[]; withdrawalIndex: number; processedCount: number} {
121
+ const builderWithdrawals: capella.Withdrawal[] = [];
132
122
  const epoch = state.epochCtx.epoch;
133
- let withdrawalIndex = state.nextWithdrawalIndex;
134
- const {validators, balances, nextWithdrawalValidatorIndex} = state;
135
-
136
- const withdrawals: capella.Withdrawal[] = [];
137
- const withdrawnBalances = new MapDef<ValidatorIndex, number>(() => 0);
138
- const isPostElectra = fork >= ForkSeq.electra;
139
- const isPostGloas = fork >= ForkSeq.gloas;
140
- // partialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002)
141
- let processedPartialWithdrawalsCount = 0;
142
- // builderWithdrawalsCount is withdrawals coming from builder payments since Gloas (EIP-7732)
143
- let processedBuilderWithdrawalsCount = 0;
144
-
145
- if (isPostGloas) {
146
- const stateGloas = state as CachedBeaconStateGloas;
147
-
148
- const allBuilderPendingWithdrawals =
149
- stateGloas.builderPendingWithdrawals.length <= MAX_WITHDRAWALS_PER_PAYLOAD
150
- ? stateGloas.builderPendingWithdrawals.getAllReadonly()
151
- : null;
123
+ const allBuilderPendingWithdrawals =
124
+ state.builderPendingWithdrawals.length <= MAX_WITHDRAWALS_PER_PAYLOAD
125
+ ? state.builderPendingWithdrawals.getAllReadonly()
126
+ : null;
127
+
128
+ let processedCount = 0;
129
+ for (let i = 0; i < state.builderPendingWithdrawals.length; i++) {
130
+ const withdrawal = allBuilderPendingWithdrawals
131
+ ? allBuilderPendingWithdrawals[i]
132
+ : state.builderPendingWithdrawals.getReadonly(i);
133
+
134
+ if (withdrawal.withdrawableEpoch > epoch || builderWithdrawals.length === MAX_WITHDRAWALS_PER_PAYLOAD) {
135
+ break;
136
+ }
152
137
 
153
- for (let i = 0; i < stateGloas.builderPendingWithdrawals.length; i++) {
154
- const withdrawal = allBuilderPendingWithdrawals
155
- ? allBuilderPendingWithdrawals[i]
156
- : stateGloas.builderPendingWithdrawals.getReadonly(i);
138
+ if (isBuilderPaymentWithdrawable(state, withdrawal)) {
139
+ const builderIndex = withdrawal.builderIndex;
140
+ const builder = state.validators.get(withdrawal.builderIndex);
157
141
 
158
- if (withdrawal.withdrawableEpoch > epoch || withdrawals.length + 1 === MAX_WITHDRAWALS_PER_PAYLOAD) {
159
- break;
142
+ let balance = balanceAfterWithdrawals.get(builderIndex);
143
+ if (balance === undefined) {
144
+ balance = state.balances.get(builderIndex);
145
+ balanceAfterWithdrawals.set(builderIndex, balance);
160
146
  }
161
147
 
162
- if (isBuilderPaymentWithdrawable(stateGloas, withdrawal)) {
163
- const totalWithdrawn = withdrawnBalances.getOrDefault(withdrawal.builderIndex);
164
- const balance = state.balances.get(withdrawal.builderIndex) - totalWithdrawn;
165
- const builder = state.validators.get(withdrawal.builderIndex);
166
-
167
- let withdrawableBalance = 0;
148
+ let withdrawableBalance = 0;
168
149
 
169
- if (builder.slashed) {
170
- withdrawableBalance = balance < withdrawal.amount ? balance : withdrawal.amount;
171
- } else if (balance > MIN_ACTIVATION_BALANCE) {
172
- withdrawableBalance =
173
- balance - MIN_ACTIVATION_BALANCE < withdrawal.amount ? balance - MIN_ACTIVATION_BALANCE : withdrawal.amount;
174
- }
150
+ if (builder.slashed) {
151
+ withdrawableBalance = balance < withdrawal.amount ? balance : withdrawal.amount;
152
+ } else if (balance > MIN_ACTIVATION_BALANCE) {
153
+ withdrawableBalance =
154
+ balance - MIN_ACTIVATION_BALANCE < withdrawal.amount ? balance - MIN_ACTIVATION_BALANCE : withdrawal.amount;
155
+ }
175
156
 
176
- if (withdrawableBalance > 0) {
177
- withdrawals.push({
178
- index: withdrawalIndex,
179
- validatorIndex: withdrawal.builderIndex,
180
- address: withdrawal.feeRecipient,
181
- amount: BigInt(withdrawableBalance),
182
- });
183
- withdrawalIndex++;
184
- withdrawnBalances.set(withdrawal.builderIndex, totalWithdrawn + withdrawableBalance);
185
- }
157
+ if (withdrawableBalance > 0) {
158
+ builderWithdrawals.push({
159
+ index: withdrawalIndex,
160
+ validatorIndex: withdrawal.builderIndex,
161
+ address: withdrawal.feeRecipient,
162
+ amount: BigInt(withdrawableBalance),
163
+ });
164
+ withdrawalIndex++;
165
+ balanceAfterWithdrawals.set(builderIndex, balance - withdrawableBalance);
186
166
  }
187
- processedBuilderWithdrawalsCount++;
188
167
  }
168
+ processedCount++;
189
169
  }
190
170
 
191
- if (isPostElectra) {
192
- // In pre-gloas, partialWithdrawalBound == MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP
193
- const partialWithdrawalBound = Math.min(
194
- withdrawals.length + MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP,
195
- MAX_WITHDRAWALS_PER_PAYLOAD - 1
196
- );
197
- const stateElectra = state as CachedBeaconStateElectra;
171
+ return {builderWithdrawals, withdrawalIndex, processedCount};
172
+ }
198
173
 
199
- // MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP = 8, PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728 so we should only call getAllReadonly() if it makes sense
200
- // pendingPartialWithdrawals comes from EIP-7002 smart contract where it takes fee so it's more likely than not validator is in correct condition to withdraw
201
- // also we may break early if withdrawableEpoch > epoch
202
- const allPendingPartialWithdrawals =
203
- stateElectra.pendingPartialWithdrawals.length <= partialWithdrawalBound
204
- ? stateElectra.pendingPartialWithdrawals.getAllReadonly()
205
- : null;
206
-
207
- // EIP-7002: Execution layer triggerable withdrawals
208
- for (let i = 0; i < stateElectra.pendingPartialWithdrawals.length; i++) {
209
- const withdrawal = allPendingPartialWithdrawals
210
- ? allPendingPartialWithdrawals[i]
211
- : stateElectra.pendingPartialWithdrawals.getReadonly(i);
212
- if (withdrawal.withdrawableEpoch > epoch || withdrawals.length === partialWithdrawalBound) {
213
- break;
214
- }
174
+ function getPendingPartialWithdrawals(
175
+ state: CachedBeaconStateElectra,
176
+ withdrawalIndex: number,
177
+ numPriorWithdrawal: number,
178
+ balanceAfterWithdrawals: Map<ValidatorIndex, number>
179
+ ): {pendingPartialWithdrawals: capella.Withdrawal[]; withdrawalIndex: number; processedCount: number} {
180
+ const epoch = state.epochCtx.epoch;
181
+ const pendingPartialWithdrawals: capella.Withdrawal[] = [];
182
+ const validators = state.validators;
183
+
184
+ // In pre-gloas, partialWithdrawalBound == MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP
185
+ const partialWithdrawalBound = Math.min(
186
+ numPriorWithdrawal + MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP,
187
+ MAX_WITHDRAWALS_PER_PAYLOAD - 1
188
+ );
189
+
190
+ // MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP = 8, PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728 so we should only call getAllReadonly() if it makes sense
191
+ // pendingPartialWithdrawals comes from EIP-7002 smart contract where it takes fee so it's more likely than not validator is in correct condition to withdraw
192
+ // also we may break early if withdrawableEpoch > epoch
193
+ const allPendingPartialWithdrawals =
194
+ state.pendingPartialWithdrawals.length <= partialWithdrawalBound
195
+ ? state.pendingPartialWithdrawals.getAllReadonly()
196
+ : null;
197
+
198
+ // EIP-7002: Execution layer triggerable withdrawals
199
+ let processedCount = 0;
200
+ for (let i = 0; i < state.pendingPartialWithdrawals.length; i++) {
201
+ const withdrawal = allPendingPartialWithdrawals
202
+ ? allPendingPartialWithdrawals[i]
203
+ : state.pendingPartialWithdrawals.getReadonly(i);
204
+ if (
205
+ withdrawal.withdrawableEpoch > epoch ||
206
+ pendingPartialWithdrawals.length + numPriorWithdrawal === partialWithdrawalBound
207
+ ) {
208
+ break;
209
+ }
215
210
 
216
- const validator = validators.getReadonly(withdrawal.validatorIndex);
217
- const totalWithdrawn = withdrawnBalances.getOrDefault(withdrawal.validatorIndex);
218
- const balance = state.balances.get(withdrawal.validatorIndex) - totalWithdrawn;
219
-
220
- if (
221
- validator.exitEpoch === FAR_FUTURE_EPOCH &&
222
- validator.effectiveBalance >= MIN_ACTIVATION_BALANCE &&
223
- balance > MIN_ACTIVATION_BALANCE
224
- ) {
225
- const balanceOverMinActivationBalance = BigInt(balance - MIN_ACTIVATION_BALANCE);
226
- const withdrawableBalance =
227
- balanceOverMinActivationBalance < withdrawal.amount ? balanceOverMinActivationBalance : withdrawal.amount;
228
- withdrawals.push({
229
- index: withdrawalIndex,
230
- validatorIndex: withdrawal.validatorIndex,
231
- address: validator.withdrawalCredentials.subarray(12),
232
- amount: withdrawableBalance,
233
- });
234
- withdrawalIndex++;
235
- withdrawnBalances.set(withdrawal.validatorIndex, totalWithdrawn + Number(withdrawableBalance));
236
- }
237
- processedPartialWithdrawalsCount++;
211
+ const validatorIndex = withdrawal.validatorIndex;
212
+ const validator = validators.getReadonly(validatorIndex);
213
+ let balance = balanceAfterWithdrawals.get(validatorIndex);
214
+ if (balance === undefined) {
215
+ balance = state.balances.get(validatorIndex);
216
+ balanceAfterWithdrawals.set(validatorIndex, balance);
238
217
  }
218
+
219
+ if (
220
+ validator.exitEpoch === FAR_FUTURE_EPOCH &&
221
+ validator.effectiveBalance >= MIN_ACTIVATION_BALANCE &&
222
+ balance > MIN_ACTIVATION_BALANCE
223
+ ) {
224
+ const balanceOverMinActivationBalance = BigInt(balance - MIN_ACTIVATION_BALANCE);
225
+ const withdrawableBalance =
226
+ balanceOverMinActivationBalance < withdrawal.amount ? balanceOverMinActivationBalance : withdrawal.amount;
227
+ pendingPartialWithdrawals.push({
228
+ index: withdrawalIndex,
229
+ validatorIndex,
230
+ address: validator.withdrawalCredentials.subarray(12),
231
+ amount: withdrawableBalance,
232
+ });
233
+ withdrawalIndex++;
234
+ balanceAfterWithdrawals.set(validatorIndex, balance - Number(withdrawableBalance));
235
+ }
236
+ processedCount++;
239
237
  }
240
238
 
241
- const withdrawalBound = Math.min(validators.length, MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP);
242
- let n = 0;
239
+ return {pendingPartialWithdrawals, withdrawalIndex, processedCount};
240
+ }
241
+
242
+ function getValidatorsSweepWithdrawals(
243
+ fork: ForkSeq,
244
+ state: CachedBeaconStateCapella | CachedBeaconStateElectra | CachedBeaconStateGloas,
245
+ withdrawalIndex: number,
246
+ numPriorWithdrawal: number,
247
+ balanceAfterWithdrawals: Map<ValidatorIndex, number>
248
+ ): {sweepWithdrawals: capella.Withdrawal[]; processedCount: number} {
249
+ const sweepWithdrawals: capella.Withdrawal[] = [];
250
+ const epoch = state.epochCtx.epoch;
251
+ const {validators, balances, nextWithdrawalValidatorIndex} = state;
252
+ const isPostElectra = fork >= ForkSeq.electra;
253
+
254
+ const validatorsLimit = Math.min(state.validators.length, MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP);
255
+ let processedCount = 0;
243
256
  // Just run a bounded loop max iterating over all withdrawals
244
257
  // however breaks out once we have MAX_WITHDRAWALS_PER_PAYLOAD
245
- for (n = 0; n < withdrawalBound; n++) {
258
+ for (let n = 0; n < validatorsLimit; n++) {
259
+ if (sweepWithdrawals.length + numPriorWithdrawal === MAX_WITHDRAWALS_PER_PAYLOAD) {
260
+ break;
261
+ }
262
+
246
263
  // Get next validator in turn
247
264
  const validatorIndex = (nextWithdrawalValidatorIndex + n) % validators.length;
248
265
 
249
266
  const validator = validators.getReadonly(validatorIndex);
250
- const withdrawnBalance = withdrawnBalances.getOrDefault(validatorIndex);
251
- const balance = isPostElectra
252
- ? // Deduct partially withdrawn balance already queued above
253
- balances.get(validatorIndex) - withdrawnBalance
254
- : balances.get(validatorIndex);
267
+ let balance = balanceAfterWithdrawals.get(validatorIndex);
268
+ if (balance === undefined) {
269
+ balance = balances.get(validatorIndex);
270
+ balanceAfterWithdrawals.set(validatorIndex, balance);
271
+ }
272
+
255
273
  const {withdrawableEpoch, withdrawalCredentials, effectiveBalance} = validator;
256
274
  const hasWithdrawableCredentials = isPostElectra
257
275
  ? hasExecutionWithdrawalCredential(withdrawalCredentials)
@@ -259,40 +277,117 @@ export function getExpectedWithdrawals(
259
277
  // early skip for balance = 0 as its now more likely that validator has exited/slashed with
260
278
  // balance zero than not have withdrawal credentials set
261
279
  if (balance === 0 || !hasWithdrawableCredentials) {
280
+ processedCount++;
262
281
  continue;
263
282
  }
264
283
 
265
284
  // capella full withdrawal
266
285
  if (withdrawableEpoch <= epoch) {
267
- withdrawals.push({
286
+ sweepWithdrawals.push({
268
287
  index: withdrawalIndex,
269
288
  validatorIndex,
270
289
  address: validator.withdrawalCredentials.subarray(12),
271
290
  amount: BigInt(balance),
272
291
  });
273
292
  withdrawalIndex++;
274
- withdrawnBalances.set(validatorIndex, withdrawnBalance + balance);
293
+ balanceAfterWithdrawals.set(validatorIndex, 0);
275
294
  } else if (
276
295
  effectiveBalance === (isPostElectra ? getMaxEffectiveBalance(withdrawalCredentials) : MAX_EFFECTIVE_BALANCE) &&
277
296
  balance > effectiveBalance
278
297
  ) {
279
298
  // capella partial withdrawal
280
299
  const partialAmount = balance - effectiveBalance;
281
- withdrawals.push({
300
+ sweepWithdrawals.push({
282
301
  index: withdrawalIndex,
283
302
  validatorIndex,
284
303
  address: validator.withdrawalCredentials.subarray(12),
285
304
  amount: BigInt(partialAmount),
286
305
  });
287
306
  withdrawalIndex++;
288
- withdrawnBalances.set(validatorIndex, withdrawnBalance + partialAmount);
307
+ balanceAfterWithdrawals.set(validatorIndex, balance - partialAmount);
289
308
  }
309
+ processedCount++;
310
+ }
290
311
 
291
- // Break if we have enough to pack the block
292
- if (withdrawals.length >= MAX_WITHDRAWALS_PER_PAYLOAD) {
293
- break;
294
- }
312
+ return {sweepWithdrawals, processedCount};
313
+ }
314
+
315
+ function applyWithdrawals(
316
+ state: CachedBeaconStateCapella | CachedBeaconStateElectra | CachedBeaconStateGloas,
317
+ withdrawals: capella.Withdrawal[]
318
+ ): void {
319
+ for (const withdrawal of withdrawals) {
320
+ decreaseBalance(state, withdrawal.validatorIndex, Number(withdrawal.amount));
295
321
  }
322
+ }
296
323
 
297
- return {withdrawals, sampledValidators: n, processedPartialWithdrawalsCount, processedBuilderWithdrawalsCount};
324
+ export function getExpectedWithdrawals(
325
+ fork: ForkSeq,
326
+ state: CachedBeaconStateCapella | CachedBeaconStateElectra | CachedBeaconStateGloas
327
+ ): {
328
+ expectedWithdrawals: capella.Withdrawal[];
329
+ processedBuilderWithdrawalsCount: number;
330
+ processedPartialWithdrawalsCount: number;
331
+ processedValidatorSweepCount: number;
332
+ } {
333
+ if (fork < ForkSeq.capella) {
334
+ throw new Error(`getExpectedWithdrawals not supported at forkSeq=${fork} < ForkSeq.capella`);
335
+ }
336
+
337
+ let withdrawalIndex = state.nextWithdrawalIndex;
338
+
339
+ const expectedWithdrawals: capella.Withdrawal[] = [];
340
+ // Map to track balances after applying withdrawals
341
+ // https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.0/specs/capella/beacon-chain.md#new-get_balance_after_withdrawals
342
+ const balanceAfterWithdrawals = new Map<ValidatorIndex, number>();
343
+ // partialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002)
344
+ let processedPartialWithdrawalsCount = 0;
345
+ // builderWithdrawalsCount is withdrawals coming from builder payments since Gloas (EIP-7732)
346
+ let processedBuilderWithdrawalsCount = 0;
347
+
348
+ if (fork >= ForkSeq.gloas) {
349
+ const {
350
+ builderWithdrawals,
351
+ withdrawalIndex: newWithdrawalIndex,
352
+ processedCount,
353
+ } = getBuilderWithdrawals(state as CachedBeaconStateGloas, withdrawalIndex, balanceAfterWithdrawals);
354
+
355
+ expectedWithdrawals.push(...builderWithdrawals);
356
+ withdrawalIndex = newWithdrawalIndex;
357
+ processedBuilderWithdrawalsCount = processedCount;
358
+ }
359
+
360
+ if (fork >= ForkSeq.electra) {
361
+ const {
362
+ pendingPartialWithdrawals,
363
+ withdrawalIndex: newWithdrawalIndex,
364
+ processedCount,
365
+ } = getPendingPartialWithdrawals(
366
+ state as CachedBeaconStateElectra,
367
+ withdrawalIndex,
368
+ expectedWithdrawals.length,
369
+ balanceAfterWithdrawals
370
+ );
371
+
372
+ expectedWithdrawals.push(...pendingPartialWithdrawals);
373
+ withdrawalIndex = newWithdrawalIndex;
374
+ processedPartialWithdrawalsCount = processedCount;
375
+ }
376
+
377
+ const {sweepWithdrawals, processedCount: processedValidatorSweepCount} = getValidatorsSweepWithdrawals(
378
+ fork,
379
+ state,
380
+ withdrawalIndex,
381
+ expectedWithdrawals.length,
382
+ balanceAfterWithdrawals
383
+ );
384
+
385
+ expectedWithdrawals.push(...sweepWithdrawals);
386
+
387
+ return {
388
+ expectedWithdrawals,
389
+ processedBuilderWithdrawalsCount,
390
+ processedPartialWithdrawalsCount,
391
+ processedValidatorSweepCount,
392
+ };
298
393
  }
package/src/index.ts CHANGED
@@ -27,8 +27,7 @@ export {
27
27
  createEmptyEpochCacheImmutableData,
28
28
  } from "./cache/epochCache.js";
29
29
  export {type EpochTransitionCache, beforeProcessEpoch} from "./cache/epochTransitionCache.js";
30
- // Aux data-structures
31
- export {type Index2PubkeyCache} from "./cache/pubkeyCache.js";
30
+ export {type Index2PubkeyCache, syncPubkeys} from "./cache/pubkeyCache.js";
32
31
  // Main state caches
33
32
  export {
34
33
  type BeaconStateCache,
@@ -41,6 +40,7 @@ export {
41
40
  export * from "./constants/index.js";
42
41
  export type {EpochTransitionStep} from "./epoch/index.js";
43
42
  export {type BeaconStateTransitionMetrics, getMetrics} from "./metrics.js";
43
+ export * from "./rewards/index.js";
44
44
  export * from "./signatureSets/index.js";
45
45
  export * from "./stateTransition.js";
46
46
  export type {