@lodestar/state-transition 1.40.0-dev.3be9500fa9 → 1.40.0-dev.45b04262dc

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 (183) hide show
  1. package/lib/block/index.d.ts +1 -0
  2. package/lib/block/index.d.ts.map +1 -1
  3. package/lib/block/index.js +1 -0
  4. package/lib/block/index.js.map +1 -1
  5. package/lib/block/isValidIndexedAttestation.d.ts.map +1 -1
  6. package/lib/block/isValidIndexedAttestation.js +2 -2
  7. package/lib/block/isValidIndexedAttestation.js.map +1 -1
  8. package/lib/block/isValidIndexedPayloadAttestation.d.ts.map +1 -1
  9. package/lib/block/isValidIndexedPayloadAttestation.js +1 -1
  10. package/lib/block/isValidIndexedPayloadAttestation.js.map +1 -1
  11. package/lib/block/processAttestationsAltair.d.ts.map +1 -1
  12. package/lib/block/processAttestationsAltair.js +3 -4
  13. package/lib/block/processAttestationsAltair.js.map +1 -1
  14. package/lib/block/processBlockHeader.d.ts.map +1 -1
  15. package/lib/block/processBlockHeader.js +1 -2
  16. package/lib/block/processBlockHeader.js.map +1 -1
  17. package/lib/block/processBlsToExecutionChange.d.ts.map +1 -1
  18. package/lib/block/processBlsToExecutionChange.js +1 -2
  19. package/lib/block/processBlsToExecutionChange.js.map +1 -1
  20. package/lib/block/processConsolidationRequest.d.ts +1 -2
  21. package/lib/block/processConsolidationRequest.d.ts.map +1 -1
  22. package/lib/block/processConsolidationRequest.js +5 -4
  23. package/lib/block/processConsolidationRequest.js.map +1 -1
  24. package/lib/block/processDepositRequest.d.ts +8 -2
  25. package/lib/block/processDepositRequest.d.ts.map +1 -1
  26. package/lib/block/processDepositRequest.js +81 -8
  27. package/lib/block/processDepositRequest.js.map +1 -1
  28. package/lib/block/processExecutionPayload.d.ts.map +1 -1
  29. package/lib/block/processExecutionPayload.js +1 -2
  30. package/lib/block/processExecutionPayload.js.map +1 -1
  31. package/lib/block/processExecutionPayloadBid.d.ts.map +1 -1
  32. package/lib/block/processExecutionPayloadBid.js +17 -31
  33. package/lib/block/processExecutionPayloadBid.js.map +1 -1
  34. package/lib/block/processExecutionPayloadEnvelope.d.ts.map +1 -1
  35. package/lib/block/processExecutionPayloadEnvelope.js +27 -27
  36. package/lib/block/processExecutionPayloadEnvelope.js.map +1 -1
  37. package/lib/block/processOperations.js +2 -2
  38. package/lib/block/processOperations.js.map +1 -1
  39. package/lib/block/processPayloadAttestation.d.ts.map +1 -1
  40. package/lib/block/processPayloadAttestation.js +1 -1
  41. package/lib/block/processPayloadAttestation.js.map +1 -1
  42. package/lib/block/processProposerSlashing.js +2 -2
  43. package/lib/block/processProposerSlashing.js.map +1 -1
  44. package/lib/block/processSyncCommittee.d.ts +1 -2
  45. package/lib/block/processSyncCommittee.d.ts.map +1 -1
  46. package/lib/block/processSyncCommittee.js +6 -6
  47. package/lib/block/processSyncCommittee.js.map +1 -1
  48. package/lib/block/processVoluntaryExit.d.ts +1 -1
  49. package/lib/block/processVoluntaryExit.d.ts.map +1 -1
  50. package/lib/block/processVoluntaryExit.js +45 -3
  51. package/lib/block/processVoluntaryExit.js.map +1 -1
  52. package/lib/block/processWithdrawalRequest.js +1 -1
  53. package/lib/block/processWithdrawalRequest.js.map +1 -1
  54. package/lib/block/processWithdrawals.d.ts +1 -0
  55. package/lib/block/processWithdrawals.d.ts.map +1 -1
  56. package/lib/block/processWithdrawals.js +122 -68
  57. package/lib/block/processWithdrawals.js.map +1 -1
  58. package/lib/epoch/processBuilderPendingPayments.d.ts.map +1 -1
  59. package/lib/epoch/processBuilderPendingPayments.js +1 -4
  60. package/lib/epoch/processBuilderPendingPayments.js.map +1 -1
  61. package/lib/epoch/processPendingAttestations.d.ts.map +1 -1
  62. package/lib/epoch/processPendingAttestations.js +1 -1
  63. package/lib/epoch/processPendingAttestations.js.map +1 -1
  64. package/lib/signatureSets/attesterSlashings.d.ts +3 -4
  65. package/lib/signatureSets/attesterSlashings.d.ts.map +1 -1
  66. package/lib/signatureSets/attesterSlashings.js +6 -6
  67. package/lib/signatureSets/attesterSlashings.js.map +1 -1
  68. package/lib/signatureSets/blsToExecutionChange.d.ts +3 -3
  69. package/lib/signatureSets/blsToExecutionChange.d.ts.map +1 -1
  70. package/lib/signatureSets/blsToExecutionChange.js.map +1 -1
  71. package/lib/signatureSets/executionPayloadBid.d.ts +4 -0
  72. package/lib/signatureSets/executionPayloadBid.d.ts.map +1 -0
  73. package/lib/signatureSets/executionPayloadBid.js +8 -0
  74. package/lib/signatureSets/executionPayloadBid.js.map +1 -0
  75. package/lib/signatureSets/executionPayloadEnvelope.d.ts +4 -0
  76. package/lib/signatureSets/executionPayloadEnvelope.d.ts.map +1 -0
  77. package/lib/signatureSets/executionPayloadEnvelope.js +8 -0
  78. package/lib/signatureSets/executionPayloadEnvelope.js.map +1 -0
  79. package/lib/signatureSets/index.d.ts +3 -2
  80. package/lib/signatureSets/index.d.ts.map +1 -1
  81. package/lib/signatureSets/index.js +10 -8
  82. package/lib/signatureSets/index.js.map +1 -1
  83. package/lib/signatureSets/indexedAttestation.d.ts +3 -4
  84. package/lib/signatureSets/indexedAttestation.d.ts.map +1 -1
  85. package/lib/signatureSets/indexedAttestation.js +6 -6
  86. package/lib/signatureSets/indexedAttestation.js.map +1 -1
  87. package/lib/signatureSets/indexedPayloadAttestation.d.ts +5 -4
  88. package/lib/signatureSets/indexedPayloadAttestation.d.ts.map +1 -1
  89. package/lib/signatureSets/indexedPayloadAttestation.js +3 -3
  90. package/lib/signatureSets/indexedPayloadAttestation.js.map +1 -1
  91. package/lib/signatureSets/proposer.d.ts +3 -3
  92. package/lib/signatureSets/proposer.d.ts.map +1 -1
  93. package/lib/signatureSets/proposer.js +12 -12
  94. package/lib/signatureSets/proposer.js.map +1 -1
  95. package/lib/signatureSets/proposerSlashings.d.ts +2 -3
  96. package/lib/signatureSets/proposerSlashings.d.ts.map +1 -1
  97. package/lib/signatureSets/proposerSlashings.js +6 -6
  98. package/lib/signatureSets/proposerSlashings.js.map +1 -1
  99. package/lib/signatureSets/randao.d.ts +1 -1
  100. package/lib/signatureSets/randao.d.ts.map +1 -1
  101. package/lib/signatureSets/randao.js +4 -4
  102. package/lib/signatureSets/randao.js.map +1 -1
  103. package/lib/signatureSets/voluntaryExits.d.ts +2 -2
  104. package/lib/signatureSets/voluntaryExits.d.ts.map +1 -1
  105. package/lib/signatureSets/voluntaryExits.js +6 -6
  106. package/lib/signatureSets/voluntaryExits.js.map +1 -1
  107. package/lib/slot/index.d.ts.map +1 -1
  108. package/lib/slot/index.js +1 -1
  109. package/lib/slot/index.js.map +1 -1
  110. package/lib/util/electra.d.ts.map +1 -1
  111. package/lib/util/electra.js +1 -2
  112. package/lib/util/electra.js.map +1 -1
  113. package/lib/util/gloas.d.ts +46 -5
  114. package/lib/util/gloas.d.ts.map +1 -1
  115. package/lib/util/gloas.js +92 -6
  116. package/lib/util/gloas.js.map +1 -1
  117. package/lib/util/index.d.ts +1 -0
  118. package/lib/util/index.d.ts.map +1 -1
  119. package/lib/util/index.js +1 -0
  120. package/lib/util/index.js.map +1 -1
  121. package/lib/util/interop.js +1 -1
  122. package/lib/util/interop.js.map +1 -1
  123. package/lib/util/loadState/findModifiedInactivityScores.d.ts +1 -1
  124. package/lib/util/loadState/findModifiedInactivityScores.d.ts.map +1 -1
  125. package/lib/util/loadState/findModifiedInactivityScores.js +3 -2
  126. package/lib/util/loadState/findModifiedInactivityScores.js.map +1 -1
  127. package/lib/util/loadState/findModifiedValidators.d.ts +2 -2
  128. package/lib/util/loadState/findModifiedValidators.d.ts.map +1 -1
  129. package/lib/util/loadState/findModifiedValidators.js +4 -3
  130. package/lib/util/loadState/findModifiedValidators.js.map +1 -1
  131. package/lib/util/loadState/loadValidator.d.ts.map +1 -1
  132. package/lib/util/loadState/loadValidator.js +3 -2
  133. package/lib/util/loadState/loadValidator.js.map +1 -1
  134. package/lib/util/signatureSets.d.ts +38 -5
  135. package/lib/util/signatureSets.d.ts.map +1 -1
  136. package/lib/util/signatureSets.js +48 -6
  137. package/lib/util/signatureSets.js.map +1 -1
  138. package/lib/util/validator.d.ts +6 -1
  139. package/lib/util/validator.d.ts.map +1 -1
  140. package/lib/util/validator.js +26 -16
  141. package/lib/util/validator.js.map +1 -1
  142. package/package.json +8 -8
  143. package/src/block/index.ts +1 -0
  144. package/src/block/isValidIndexedAttestation.ts +3 -2
  145. package/src/block/isValidIndexedPayloadAttestation.ts +4 -1
  146. package/src/block/processAttestationsAltair.ts +3 -10
  147. package/src/block/processBlockHeader.ts +1 -2
  148. package/src/block/processBlsToExecutionChange.ts +1 -2
  149. package/src/block/processConsolidationRequest.ts +5 -5
  150. package/src/block/processDepositRequest.ts +101 -8
  151. package/src/block/processExecutionPayload.ts +1 -2
  152. package/src/block/processExecutionPayloadBid.ts +21 -44
  153. package/src/block/processExecutionPayloadEnvelope.ts +35 -32
  154. package/src/block/processOperations.ts +2 -2
  155. package/src/block/processPayloadAttestation.ts +1 -1
  156. package/src/block/processProposerSlashing.ts +2 -2
  157. package/src/block/processSyncCommittee.ts +4 -7
  158. package/src/block/processVoluntaryExit.ts +60 -5
  159. package/src/block/processWithdrawalRequest.ts +1 -1
  160. package/src/block/processWithdrawals.ts +169 -72
  161. package/src/epoch/processBuilderPendingPayments.ts +1 -5
  162. package/src/epoch/processPendingAttestations.ts +1 -1
  163. package/src/signatureSets/attesterSlashings.ts +3 -7
  164. package/src/signatureSets/blsToExecutionChange.ts +3 -3
  165. package/src/signatureSets/executionPayloadBid.ts +14 -0
  166. package/src/signatureSets/executionPayloadEnvelope.ts +13 -0
  167. package/src/signatureSets/index.ts +8 -9
  168. package/src/signatureSets/indexedAttestation.ts +2 -7
  169. package/src/signatureSets/indexedPayloadAttestation.ts +9 -7
  170. package/src/signatureSets/proposer.ts +8 -12
  171. package/src/signatureSets/proposerSlashings.ts +4 -7
  172. package/src/signatureSets/randao.ts +4 -8
  173. package/src/signatureSets/voluntaryExits.ts +5 -10
  174. package/src/slot/index.ts +1 -1
  175. package/src/util/electra.ts +1 -4
  176. package/src/util/gloas.ts +117 -11
  177. package/src/util/index.ts +1 -0
  178. package/src/util/interop.ts +1 -1
  179. package/src/util/loadState/findModifiedInactivityScores.ts +4 -2
  180. package/src/util/loadState/findModifiedValidators.ts +4 -3
  181. package/src/util/loadState/loadValidator.ts +3 -2
  182. package/src/util/signatureSets.ts +84 -8
  183. package/src/util/validator.ts +31 -16
@@ -1,8 +1,16 @@
1
+ import {PublicKey, Signature, verify} from "@chainsafe/blst";
1
2
  import {FAR_FUTURE_EPOCH, ForkSeq} from "@lodestar/params";
2
- import {phase0} from "@lodestar/types";
3
+ import {phase0, ssz} from "@lodestar/types";
3
4
  import {verifyVoluntaryExitSignature} from "../signatureSets/index.js";
4
- import {CachedBeaconStateAllForks, CachedBeaconStateElectra} from "../types.js";
5
- import {getPendingBalanceToWithdraw, isActiveValidator} from "../util/index.js";
5
+ import {CachedBeaconStateAllForks, CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";
6
+ import {
7
+ convertValidatorIndexToBuilderIndex,
8
+ getPendingBalanceToWithdrawForBuilder,
9
+ initiateBuilderExit,
10
+ isActiveBuilder,
11
+ isBuilderIndex,
12
+ } from "../util/gloas.js";
13
+ import {computeSigningRoot, getCurrentEpoch, getPendingBalanceToWithdraw, isActiveValidator} from "../util/index.js";
6
14
  import {initiateValidatorExit} from "./index.js";
7
15
 
8
16
  export enum VoluntaryExitValidity {
@@ -16,7 +24,7 @@ export enum VoluntaryExitValidity {
16
24
  }
17
25
 
18
26
  /**
19
- * Process a VoluntaryExit operation. Initiates the exit of a validator.
27
+ * Process a VoluntaryExit operation. Initiates the exit of a validator or builder.
20
28
  *
21
29
  * PERF: Work depends on number of VoluntaryExit per block. On regular networks the average is 0 / block.
22
30
  */
@@ -26,6 +34,53 @@ export function processVoluntaryExit(
26
34
  signedVoluntaryExit: phase0.SignedVoluntaryExit,
27
35
  verifySignature = true
28
36
  ): void {
37
+ const voluntaryExit = signedVoluntaryExit.message;
38
+ const currentEpoch = getCurrentEpoch(state);
39
+
40
+ // Exits must specify an epoch when they become valid; they are not valid before then
41
+ if (currentEpoch < voluntaryExit.epoch) {
42
+ throw Error(`Voluntary exit epoch ${voluntaryExit.epoch} is after current epoch ${currentEpoch}`);
43
+ }
44
+
45
+ // Check if this is a builder exit
46
+ if (fork >= ForkSeq.gloas && isBuilderIndex(voluntaryExit.validatorIndex)) {
47
+ const stateGloas = state as CachedBeaconStateGloas;
48
+ const builderIndex = convertValidatorIndexToBuilderIndex(voluntaryExit.validatorIndex);
49
+ const builder = stateGloas.builders.getReadonly(builderIndex);
50
+
51
+ // Verify the builder is active
52
+ if (!isActiveBuilder(builder, state.finalizedCheckpoint.epoch)) {
53
+ throw Error(`Builder ${builderIndex} is not active`);
54
+ }
55
+
56
+ // Only exit builder if it has no pending withdrawals in the queue
57
+ if (getPendingBalanceToWithdrawForBuilder(stateGloas, builderIndex) !== 0) {
58
+ throw Error(`Builder ${builderIndex} has pending withdrawals`);
59
+ }
60
+
61
+ // Verify signature
62
+ if (verifySignature) {
63
+ const domain = state.config.getDomainForVoluntaryExit(state.slot);
64
+ const signingRoot = computeSigningRoot(ssz.phase0.VoluntaryExit, voluntaryExit, domain);
65
+
66
+ try {
67
+ const publicKey = PublicKey.fromBytes(builder.pubkey);
68
+ const signature = Signature.fromBytes(signedVoluntaryExit.signature, true);
69
+
70
+ if (!verify(signingRoot, publicKey, signature)) {
71
+ throw Error("BLS verify failed");
72
+ }
73
+ } catch (e) {
74
+ throw Error(`Builder ${builderIndex} invalid exit signature reason=${(e as Error).message}`);
75
+ }
76
+ }
77
+
78
+ // Initiate builder exit
79
+ initiateBuilderExit(stateGloas, builderIndex);
80
+ return;
81
+ }
82
+
83
+ // Handle validator exit
29
84
  const validity = getVoluntaryExitValidity(fork, state, signedVoluntaryExit, verifySignature);
30
85
  if (validity !== VoluntaryExitValidity.valid) {
31
86
  throw Error(`Invalid voluntary exit at forkSeq=${fork} reason=${validity}`);
@@ -69,7 +124,7 @@ export function getVoluntaryExitValidity(
69
124
  // only exit validator if it has no pending withdrawals in the queue
70
125
  if (
71
126
  fork >= ForkSeq.electra &&
72
- getPendingBalanceToWithdraw(fork, state as CachedBeaconStateElectra, voluntaryExit.validatorIndex) !== 0
127
+ getPendingBalanceToWithdraw(state as CachedBeaconStateElectra, voluntaryExit.validatorIndex) !== 0
73
128
  ) {
74
129
  return VoluntaryExitValidity.pendingWithdrawals;
75
130
  }
@@ -42,7 +42,7 @@ export function processWithdrawalRequest(
42
42
  }
43
43
 
44
44
  // TODO Electra: Consider caching pendingPartialWithdrawals
45
- const pendingBalanceToWithdraw = getPendingBalanceToWithdraw(fork, state, validatorIndex);
45
+ const pendingBalanceToWithdraw = getPendingBalanceToWithdraw(state, validatorIndex);
46
46
  const validatorBalance = state.balances.get(validatorIndex);
47
47
 
48
48
  if (isFullExitRequest) {
@@ -1,23 +1,29 @@
1
- import {byteArrayEquals} from "@chainsafe/ssz";
2
1
  import {
3
2
  FAR_FUTURE_EPOCH,
4
3
  ForkSeq,
4
+ MAX_BUILDERS_PER_WITHDRAWALS_SWEEP,
5
5
  MAX_EFFECTIVE_BALANCE,
6
6
  MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP,
7
7
  MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP,
8
8
  MAX_WITHDRAWALS_PER_PAYLOAD,
9
9
  MIN_ACTIVATION_BALANCE,
10
10
  } from "@lodestar/params";
11
- import {ValidatorIndex, capella, ssz} from "@lodestar/types";
12
- import {toRootHex} from "@lodestar/utils";
11
+ import {BuilderIndex, ValidatorIndex, capella, ssz} from "@lodestar/types";
12
+ import {byteArrayEquals, toRootHex} from "@lodestar/utils";
13
13
  import {CachedBeaconStateCapella, CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";
14
- import {isBuilderPaymentWithdrawable, isParentBlockFull} from "../util/gloas.ts";
14
+ import {
15
+ convertBuilderIndexToValidatorIndex,
16
+ convertValidatorIndexToBuilderIndex,
17
+ isBuilderIndex,
18
+ isParentBlockFull,
19
+ } from "../util/gloas.ts";
15
20
  import {
16
21
  decreaseBalance,
17
22
  getMaxEffectiveBalance,
18
23
  hasEth1WithdrawalCredential,
19
24
  hasExecutionWithdrawalCredential,
20
25
  isCapellaPayloadHeader,
26
+ isPartiallyWithdrawableValidator,
21
27
  } from "../util/index.js";
22
28
 
23
29
  export function processWithdrawals(
@@ -32,9 +38,14 @@ export function processWithdrawals(
32
38
 
33
39
  // processedBuilderWithdrawalsCount is withdrawals coming from builder payment since gloas (EIP-7732)
34
40
  // processedPartialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002)
41
+ // processedBuildersSweepCount is withdrawals from builder sweep since gloas (EIP-7732)
35
42
  // processedValidatorSweepCount is withdrawals coming from validator sweep
36
- const {expectedWithdrawals, processedBuilderWithdrawalsCount, processedPartialWithdrawalsCount} =
37
- getExpectedWithdrawals(fork, state);
43
+ const {
44
+ expectedWithdrawals,
45
+ processedBuilderWithdrawalsCount,
46
+ processedPartialWithdrawalsCount,
47
+ processedBuildersSweepCount,
48
+ } = getExpectedWithdrawals(fork, state);
38
49
  const numWithdrawals = expectedWithdrawals.length;
39
50
 
40
51
  // After gloas, withdrawals are verified later in processExecutionPayloadEnvelope
@@ -78,20 +89,20 @@ export function processWithdrawals(
78
89
 
79
90
  if (fork >= ForkSeq.gloas) {
80
91
  const stateGloas = state as CachedBeaconStateGloas;
81
- stateGloas.latestWithdrawalsRoot = ssz.capella.Withdrawals.hashTreeRoot(expectedWithdrawals);
82
-
83
- const unprocessedWithdrawals = stateGloas.builderPendingWithdrawals
84
- .getAllReadonly()
85
- .slice(0, processedBuilderWithdrawalsCount)
86
- .filter((w) => !isBuilderPaymentWithdrawable(stateGloas, w));
87
- const remainingWithdrawals = stateGloas.builderPendingWithdrawals
88
- .sliceFrom(processedBuilderWithdrawalsCount)
89
- .getAllReadonly();
90
-
91
- stateGloas.builderPendingWithdrawals = ssz.gloas.BeaconState.fields.builderPendingWithdrawals.toViewDU([
92
- ...unprocessedWithdrawals,
93
- ...remainingWithdrawals,
94
- ]);
92
+
93
+ // Store expected withdrawals for verification
94
+ stateGloas.payloadExpectedWithdrawals = ssz.capella.Withdrawals.toViewDU(expectedWithdrawals);
95
+
96
+ // Update builder pending withdrawals queue
97
+ stateGloas.builderPendingWithdrawals = stateGloas.builderPendingWithdrawals.sliceFrom(
98
+ processedBuilderWithdrawalsCount
99
+ );
100
+
101
+ // Update next builder index for sweep
102
+ if (stateGloas.builders.length > 0) {
103
+ const nextIndex = stateGloas.nextWithdrawalBuilderIndex + processedBuildersSweepCount;
104
+ stateGloas.nextWithdrawalBuilderIndex = nextIndex % stateGloas.builders.length;
105
+ }
95
106
  }
96
107
  // Update the nextWithdrawalIndex
97
108
  // https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.0/specs/capella/beacon-chain.md#new-update_next_withdrawal_index
@@ -116,10 +127,14 @@ export function processWithdrawals(
116
127
  function getBuilderWithdrawals(
117
128
  state: CachedBeaconStateGloas,
118
129
  withdrawalIndex: number,
119
- balanceAfterWithdrawals: Map<ValidatorIndex, number>
130
+ priorWithdrawals: capella.Withdrawal[],
131
+ builderBalanceAfterWithdrawals: Map<number, number>
120
132
  ): {builderWithdrawals: capella.Withdrawal[]; withdrawalIndex: number; processedCount: number} {
133
+ const withdrawalsLimit = MAX_WITHDRAWALS_PER_PAYLOAD - 1;
134
+ if (priorWithdrawals.length > withdrawalsLimit) {
135
+ throw Error(`Prior withdrawals exceed limit: ${priorWithdrawals.length} > ${withdrawalsLimit}`);
136
+ }
121
137
  const builderWithdrawals: capella.Withdrawal[] = [];
122
- const epoch = state.epochCtx.epoch;
123
138
  const allBuilderPendingWithdrawals =
124
139
  state.builderPendingWithdrawals.length <= MAX_WITHDRAWALS_PER_PAYLOAD
125
140
  ? state.builderPendingWithdrawals.getAllReadonly()
@@ -127,55 +142,103 @@ function getBuilderWithdrawals(
127
142
 
128
143
  let processedCount = 0;
129
144
  for (let i = 0; i < state.builderPendingWithdrawals.length; i++) {
145
+ // Check combined length against limit
146
+ const allWithdrawals = priorWithdrawals.length + builderWithdrawals.length;
147
+ if (allWithdrawals >= withdrawalsLimit) {
148
+ break;
149
+ }
150
+
130
151
  const withdrawal = allBuilderPendingWithdrawals
131
152
  ? allBuilderPendingWithdrawals[i]
132
153
  : state.builderPendingWithdrawals.getReadonly(i);
133
154
 
134
- if (withdrawal.withdrawableEpoch > epoch || builderWithdrawals.length === MAX_WITHDRAWALS_PER_PAYLOAD) {
135
- break;
155
+ const builderIndex = withdrawal.builderIndex;
156
+
157
+ // Get builder balance (from builder.balance, not state.balances)
158
+ let balance = builderBalanceAfterWithdrawals.get(builderIndex);
159
+ if (balance === undefined) {
160
+ balance = state.builders.getReadonly(builderIndex).balance;
161
+ builderBalanceAfterWithdrawals.set(builderIndex, balance);
136
162
  }
137
163
 
138
- if (isBuilderPaymentWithdrawable(state, withdrawal)) {
139
- const builderIndex = withdrawal.builderIndex;
140
- const builder = state.validators.get(withdrawal.builderIndex);
164
+ // Use the withdrawal amount directly as specified in the spec
165
+ builderWithdrawals.push({
166
+ index: withdrawalIndex,
167
+ validatorIndex: convertBuilderIndexToValidatorIndex(builderIndex),
168
+ address: withdrawal.feeRecipient,
169
+ amount: BigInt(withdrawal.amount),
170
+ });
171
+ withdrawalIndex++;
172
+ builderBalanceAfterWithdrawals.set(builderIndex, balance - withdrawal.amount);
141
173
 
142
- let balance = balanceAfterWithdrawals.get(builderIndex);
143
- if (balance === undefined) {
144
- balance = state.balances.get(builderIndex);
145
- balanceAfterWithdrawals.set(builderIndex, balance);
146
- }
174
+ processedCount++;
175
+ }
147
176
 
148
- let withdrawableBalance = 0;
177
+ return {builderWithdrawals, withdrawalIndex, processedCount};
178
+ }
149
179
 
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
- }
180
+ function getBuildersSweepWithdrawals(
181
+ state: CachedBeaconStateGloas,
182
+ withdrawalIndex: number,
183
+ numPriorWithdrawal: number,
184
+ builderBalanceAfterWithdrawals: Map<number, number>
185
+ ): {buildersSweepWithdrawals: capella.Withdrawal[]; withdrawalIndex: number; processedCount: number} {
186
+ const withdrawalsLimit = MAX_WITHDRAWALS_PER_PAYLOAD - 1;
187
+ if (numPriorWithdrawal > withdrawalsLimit) {
188
+ throw Error(`Prior withdrawals exceed limit: ${numPriorWithdrawal} > ${withdrawalsLimit}`);
189
+ }
190
+ const buildersSweepWithdrawals: capella.Withdrawal[] = [];
191
+ const epoch = state.epochCtx.epoch;
192
+ const builders = state.builders;
156
193
 
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);
166
- }
194
+ // Return early if no builders
195
+ if (builders.length === 0) {
196
+ return {buildersSweepWithdrawals, withdrawalIndex, processedCount: 0};
197
+ }
198
+
199
+ const buildersLimit = Math.min(builders.length, MAX_BUILDERS_PER_WITHDRAWALS_SWEEP);
200
+ let processedCount = 0;
201
+
202
+ for (let n = 0; n < buildersLimit; n++) {
203
+ if (buildersSweepWithdrawals.length + numPriorWithdrawal >= withdrawalsLimit) {
204
+ break;
205
+ }
206
+
207
+ // Get next builder in turn
208
+ const builderIndex = (state.nextWithdrawalBuilderIndex + n) % builders.length;
209
+ const builder = builders.getReadonly(builderIndex);
210
+
211
+ // Get builder balance
212
+ let balance = builderBalanceAfterWithdrawals.get(builderIndex);
213
+ if (balance === undefined) {
214
+ balance = builder.balance;
215
+ builderBalanceAfterWithdrawals.set(builderIndex, balance);
216
+ }
217
+
218
+ // Check if builder is withdrawable and has balance
219
+ if (builder.withdrawableEpoch <= epoch && balance > 0) {
220
+ // Withdraw full balance to builder's execution address
221
+ buildersSweepWithdrawals.push({
222
+ index: withdrawalIndex,
223
+ validatorIndex: convertBuilderIndexToValidatorIndex(builderIndex),
224
+ address: builder.executionAddress,
225
+ amount: BigInt(balance),
226
+ });
227
+ withdrawalIndex++;
228
+ builderBalanceAfterWithdrawals.set(builderIndex, 0);
167
229
  }
230
+
168
231
  processedCount++;
169
232
  }
170
233
 
171
- return {builderWithdrawals, withdrawalIndex, processedCount};
234
+ return {buildersSweepWithdrawals, withdrawalIndex, processedCount};
172
235
  }
173
236
 
174
237
  function getPendingPartialWithdrawals(
175
238
  state: CachedBeaconStateElectra,
176
239
  withdrawalIndex: number,
177
240
  numPriorWithdrawal: number,
178
- balanceAfterWithdrawals: Map<ValidatorIndex, number>
241
+ validatorBalanceAfterWithdrawals: Map<ValidatorIndex, number>
179
242
  ): {pendingPartialWithdrawals: capella.Withdrawal[]; withdrawalIndex: number; processedCount: number} {
180
243
  const epoch = state.epochCtx.epoch;
181
244
  const pendingPartialWithdrawals: capella.Withdrawal[] = [];
@@ -203,17 +266,17 @@ function getPendingPartialWithdrawals(
203
266
  : state.pendingPartialWithdrawals.getReadonly(i);
204
267
  if (
205
268
  withdrawal.withdrawableEpoch > epoch ||
206
- pendingPartialWithdrawals.length + numPriorWithdrawal === partialWithdrawalBound
269
+ pendingPartialWithdrawals.length + numPriorWithdrawal >= partialWithdrawalBound
207
270
  ) {
208
271
  break;
209
272
  }
210
273
 
211
274
  const validatorIndex = withdrawal.validatorIndex;
212
275
  const validator = validators.getReadonly(validatorIndex);
213
- let balance = balanceAfterWithdrawals.get(validatorIndex);
276
+ let balance = validatorBalanceAfterWithdrawals.get(validatorIndex);
214
277
  if (balance === undefined) {
215
278
  balance = state.balances.get(validatorIndex);
216
- balanceAfterWithdrawals.set(validatorIndex, balance);
279
+ validatorBalanceAfterWithdrawals.set(validatorIndex, balance);
217
280
  }
218
281
 
219
282
  if (
@@ -231,7 +294,7 @@ function getPendingPartialWithdrawals(
231
294
  amount: withdrawableBalance,
232
295
  });
233
296
  withdrawalIndex++;
234
- balanceAfterWithdrawals.set(validatorIndex, balance - Number(withdrawableBalance));
297
+ validatorBalanceAfterWithdrawals.set(validatorIndex, balance - Number(withdrawableBalance));
235
298
  }
236
299
  processedCount++;
237
300
  }
@@ -244,7 +307,7 @@ function getValidatorsSweepWithdrawals(
244
307
  state: CachedBeaconStateCapella | CachedBeaconStateElectra | CachedBeaconStateGloas,
245
308
  withdrawalIndex: number,
246
309
  numPriorWithdrawal: number,
247
- balanceAfterWithdrawals: Map<ValidatorIndex, number>
310
+ validatorBalanceAfterWithdrawals: Map<ValidatorIndex, number>
248
311
  ): {sweepWithdrawals: capella.Withdrawal[]; processedCount: number} {
249
312
  const sweepWithdrawals: capella.Withdrawal[] = [];
250
313
  const epoch = state.epochCtx.epoch;
@@ -264,13 +327,13 @@ function getValidatorsSweepWithdrawals(
264
327
  const validatorIndex = (nextWithdrawalValidatorIndex + n) % validators.length;
265
328
 
266
329
  const validator = validators.getReadonly(validatorIndex);
267
- let balance = balanceAfterWithdrawals.get(validatorIndex);
330
+ let balance = validatorBalanceAfterWithdrawals.get(validatorIndex);
268
331
  if (balance === undefined) {
269
332
  balance = balances.get(validatorIndex);
270
- balanceAfterWithdrawals.set(validatorIndex, balance);
333
+ validatorBalanceAfterWithdrawals.set(validatorIndex, balance);
271
334
  }
272
335
 
273
- const {withdrawableEpoch, withdrawalCredentials, effectiveBalance} = validator;
336
+ const {withdrawableEpoch, withdrawalCredentials} = validator;
274
337
  const hasWithdrawableCredentials = isPostElectra
275
338
  ? hasExecutionWithdrawalCredential(withdrawalCredentials)
276
339
  : hasEth1WithdrawalCredential(withdrawalCredentials);
@@ -290,13 +353,11 @@ function getValidatorsSweepWithdrawals(
290
353
  amount: BigInt(balance),
291
354
  });
292
355
  withdrawalIndex++;
293
- balanceAfterWithdrawals.set(validatorIndex, 0);
294
- } else if (
295
- effectiveBalance === (isPostElectra ? getMaxEffectiveBalance(withdrawalCredentials) : MAX_EFFECTIVE_BALANCE) &&
296
- balance > effectiveBalance
297
- ) {
356
+ validatorBalanceAfterWithdrawals.set(validatorIndex, 0);
357
+ } else if (isPartiallyWithdrawableValidator(fork, validator, balance)) {
298
358
  // capella partial withdrawal
299
- const partialAmount = balance - effectiveBalance;
359
+ const maxEffectiveBalance = isPostElectra ? getMaxEffectiveBalance(withdrawalCredentials) : MAX_EFFECTIVE_BALANCE;
360
+ const partialAmount = balance - maxEffectiveBalance;
300
361
  sweepWithdrawals.push({
301
362
  index: withdrawalIndex,
302
363
  validatorIndex,
@@ -304,7 +365,7 @@ function getValidatorsSweepWithdrawals(
304
365
  amount: BigInt(partialAmount),
305
366
  });
306
367
  withdrawalIndex++;
307
- balanceAfterWithdrawals.set(validatorIndex, balance - partialAmount);
368
+ validatorBalanceAfterWithdrawals.set(validatorIndex, balance - partialAmount);
308
369
  }
309
370
  processedCount++;
310
371
  }
@@ -317,7 +378,16 @@ function applyWithdrawals(
317
378
  withdrawals: capella.Withdrawal[]
318
379
  ): void {
319
380
  for (const withdrawal of withdrawals) {
320
- decreaseBalance(state, withdrawal.validatorIndex, Number(withdrawal.amount));
381
+ if (isBuilderIndex(withdrawal.validatorIndex)) {
382
+ // Handle builder withdrawal
383
+ const builderIndex = convertValidatorIndexToBuilderIndex(withdrawal.validatorIndex);
384
+ const builder = (state as CachedBeaconStateGloas).builders.get(builderIndex);
385
+ const withdrawalAmount = Number(withdrawal.amount);
386
+ builder.balance -= Math.min(withdrawalAmount, builder.balance);
387
+ } else {
388
+ // Handle validator withdrawal
389
+ decreaseBalance(state, withdrawal.validatorIndex, Number(withdrawal.amount));
390
+ }
321
391
  }
322
392
  }
323
393
 
@@ -328,6 +398,7 @@ export function getExpectedWithdrawals(
328
398
  expectedWithdrawals: capella.Withdrawal[];
329
399
  processedBuilderWithdrawalsCount: number;
330
400
  processedPartialWithdrawalsCount: number;
401
+ processedBuildersSweepCount: number;
331
402
  processedValidatorSweepCount: number;
332
403
  } {
333
404
  if (fork < ForkSeq.capella) {
@@ -337,20 +408,28 @@ export function getExpectedWithdrawals(
337
408
  let withdrawalIndex = state.nextWithdrawalIndex;
338
409
 
339
410
  const expectedWithdrawals: capella.Withdrawal[] = [];
340
- // Map to track balances after applying withdrawals
411
+ // Separate maps to track balances after applying withdrawals
341
412
  // 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>();
413
+ const builderBalanceAfterWithdrawals = new Map<BuilderIndex, number>();
414
+ const validatorBalanceAfterWithdrawals = new Map<ValidatorIndex, number>();
343
415
  // partialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002)
344
416
  let processedPartialWithdrawalsCount = 0;
345
417
  // builderWithdrawalsCount is withdrawals coming from builder payments since Gloas (EIP-7732)
346
418
  let processedBuilderWithdrawalsCount = 0;
419
+ // buildersSweepCount is withdrawals from builder sweep since Gloas (EIP-7732)
420
+ let processedBuildersSweepCount = 0;
347
421
 
348
422
  if (fork >= ForkSeq.gloas) {
349
423
  const {
350
424
  builderWithdrawals,
351
425
  withdrawalIndex: newWithdrawalIndex,
352
426
  processedCount,
353
- } = getBuilderWithdrawals(state as CachedBeaconStateGloas, withdrawalIndex, balanceAfterWithdrawals);
427
+ } = getBuilderWithdrawals(
428
+ state as CachedBeaconStateGloas,
429
+ withdrawalIndex,
430
+ expectedWithdrawals,
431
+ builderBalanceAfterWithdrawals
432
+ );
354
433
 
355
434
  expectedWithdrawals.push(...builderWithdrawals);
356
435
  withdrawalIndex = newWithdrawalIndex;
@@ -366,7 +445,7 @@ export function getExpectedWithdrawals(
366
445
  state as CachedBeaconStateElectra,
367
446
  withdrawalIndex,
368
447
  expectedWithdrawals.length,
369
- balanceAfterWithdrawals
448
+ validatorBalanceAfterWithdrawals
370
449
  );
371
450
 
372
451
  expectedWithdrawals.push(...pendingPartialWithdrawals);
@@ -374,12 +453,29 @@ export function getExpectedWithdrawals(
374
453
  processedPartialWithdrawalsCount = processedCount;
375
454
  }
376
455
 
456
+ if (fork >= ForkSeq.gloas) {
457
+ const {
458
+ buildersSweepWithdrawals,
459
+ withdrawalIndex: newWithdrawalIndex,
460
+ processedCount,
461
+ } = getBuildersSweepWithdrawals(
462
+ state as CachedBeaconStateGloas,
463
+ withdrawalIndex,
464
+ expectedWithdrawals.length,
465
+ builderBalanceAfterWithdrawals
466
+ );
467
+
468
+ expectedWithdrawals.push(...buildersSweepWithdrawals);
469
+ withdrawalIndex = newWithdrawalIndex;
470
+ processedBuildersSweepCount = processedCount;
471
+ }
472
+
377
473
  const {sweepWithdrawals, processedCount: processedValidatorSweepCount} = getValidatorsSweepWithdrawals(
378
474
  fork,
379
475
  state,
380
476
  withdrawalIndex,
381
477
  expectedWithdrawals.length,
382
- balanceAfterWithdrawals
478
+ validatorBalanceAfterWithdrawals
383
479
  );
384
480
 
385
481
  expectedWithdrawals.push(...sweepWithdrawals);
@@ -388,6 +484,7 @@ export function getExpectedWithdrawals(
388
484
  expectedWithdrawals,
389
485
  processedBuilderWithdrawalsCount,
390
486
  processedPartialWithdrawalsCount,
487
+ processedBuildersSweepCount,
391
488
  processedValidatorSweepCount,
392
489
  };
393
490
  }
@@ -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 > quorum) {
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
  }
@@ -1,5 +1,5 @@
1
- import {byteArrayEquals} from "@chainsafe/ssz";
2
1
  import {Epoch, phase0} from "@lodestar/types";
2
+ import {byteArrayEquals} from "@lodestar/utils";
3
3
  import {CachedBeaconStatePhase0} from "../types.js";
4
4
  import {computeStartSlotAtEpoch, getBlockRootAtSlot} from "../util/index.js";
5
5
 
@@ -1,38 +1,34 @@
1
1
  import {BeaconConfig} from "@lodestar/config";
2
2
  import {DOMAIN_BEACON_ATTESTER} from "@lodestar/params";
3
3
  import {AttesterSlashing, IndexedAttestationBigint, SignedBeaconBlock, Slot, ssz} from "@lodestar/types";
4
- import {Index2PubkeyCache} from "../cache/pubkeyCache.js";
5
4
  import {ISignatureSet, SignatureSetType, computeSigningRoot, computeStartSlotAtEpoch} from "../util/index.js";
6
5
 
7
6
  /** Get signature sets from all AttesterSlashing objects in a block */
8
7
  export function getAttesterSlashingsSignatureSets(
9
8
  config: BeaconConfig,
10
- index2pubkey: Index2PubkeyCache,
11
9
  signedBlock: SignedBeaconBlock
12
10
  ): ISignatureSet[] {
13
11
  // the getDomain() api requires the state slot as 1st param, however it's the same to block.slot in state-transition
14
12
  // and the same epoch when we verify blocks in batch in beacon-node. So we can safely use block.slot here.
15
13
  const blockSlot = signedBlock.message.slot;
16
14
  return signedBlock.message.body.attesterSlashings.flatMap((attesterSlashing) =>
17
- getAttesterSlashingSignatureSets(config, index2pubkey, blockSlot, attesterSlashing)
15
+ getAttesterSlashingSignatureSets(config, blockSlot, attesterSlashing)
18
16
  );
19
17
  }
20
18
 
21
19
  /** Get signature sets from a single AttesterSlashing object */
22
20
  export function getAttesterSlashingSignatureSets(
23
21
  config: BeaconConfig,
24
- index2pubkey: Index2PubkeyCache,
25
22
  stateSlot: Slot,
26
23
  attesterSlashing: AttesterSlashing
27
24
  ): ISignatureSet[] {
28
25
  return [attesterSlashing.attestation1, attesterSlashing.attestation2].map((attestation) =>
29
- getIndexedAttestationBigintSignatureSet(config, index2pubkey, stateSlot, attestation)
26
+ getIndexedAttestationBigintSignatureSet(config, stateSlot, attestation)
30
27
  );
31
28
  }
32
29
 
33
30
  export function getIndexedAttestationBigintSignatureSet(
34
31
  config: BeaconConfig,
35
- index2pubkey: Index2PubkeyCache,
36
32
  stateSlot: Slot,
37
33
  indexedAttestation: IndexedAttestationBigint
38
34
  ): ISignatureSet {
@@ -41,7 +37,7 @@ export function getIndexedAttestationBigintSignatureSet(
41
37
 
42
38
  return {
43
39
  type: SignatureSetType.aggregate,
44
- pubkeys: indexedAttestation.attestingIndices.map((i) => index2pubkey[i]),
40
+ indices: indexedAttestation.attestingIndices.map((i) => Number(i)),
45
41
  signingRoot: computeSigningRoot(ssz.phase0.AttestationDataBigint, indexedAttestation.data, domain),
46
42
  signature: indexedAttestation.signature,
47
43
  };
@@ -2,7 +2,7 @@ import {PublicKey} from "@chainsafe/blst";
2
2
  import {BeaconConfig} from "@lodestar/config";
3
3
  import {DOMAIN_BLS_TO_EXECUTION_CHANGE, ForkName} from "@lodestar/params";
4
4
  import {capella, ssz} from "@lodestar/types";
5
- import {ISignatureSet, SignatureSetType, computeSigningRoot, verifySignatureSet} from "../util/index.js";
5
+ import {SignatureSetType, SingleSignatureSet, computeSigningRoot, verifySignatureSet} from "../util/index.js";
6
6
 
7
7
  export function verifyBlsToExecutionChangeSignature(
8
8
  config: BeaconConfig,
@@ -17,7 +17,7 @@ export function verifyBlsToExecutionChangeSignature(
17
17
  export function getBlsToExecutionChangeSignatureSet(
18
18
  config: BeaconConfig,
19
19
  signedBLSToExecutionChange: capella.SignedBLSToExecutionChange
20
- ): ISignatureSet {
20
+ ): SingleSignatureSet {
21
21
  // signatureFork for signing domain is fixed
22
22
  const signatureFork = ForkName.phase0;
23
23
  const domain = config.getDomainAtFork(signatureFork, DOMAIN_BLS_TO_EXECUTION_CHANGE);
@@ -35,7 +35,7 @@ export function getBlsToExecutionChangeSignatureSet(
35
35
  export function getBlsToExecutionChangeSignatureSets(
36
36
  config: BeaconConfig,
37
37
  signedBlock: capella.SignedBeaconBlock
38
- ): ISignatureSet[] {
38
+ ): SingleSignatureSet[] {
39
39
  return signedBlock.message.body.blsToExecutionChanges.map((blsToExecutionChange) =>
40
40
  getBlsToExecutionChangeSignatureSet(config, blsToExecutionChange)
41
41
  );
@@ -0,0 +1,14 @@
1
+ import {BeaconConfig} from "@lodestar/config";
2
+ import {DOMAIN_BEACON_BUILDER} from "@lodestar/params";
3
+ import {Slot, gloas, ssz} from "@lodestar/types";
4
+ import {computeSigningRoot} from "../util/index.js";
5
+
6
+ export function getExecutionPayloadBidSigningRoot(
7
+ config: BeaconConfig,
8
+ stateSlot: Slot,
9
+ bid: gloas.ExecutionPayloadBid
10
+ ): Uint8Array {
11
+ const domain = config.getDomain(stateSlot, DOMAIN_BEACON_BUILDER);
12
+
13
+ return computeSigningRoot(ssz.gloas.ExecutionPayloadBid, bid, domain);
14
+ }
@@ -0,0 +1,13 @@
1
+ import {BeaconConfig} from "@lodestar/config";
2
+ import {DOMAIN_BEACON_BUILDER} from "@lodestar/params";
3
+ import {gloas, ssz} from "@lodestar/types";
4
+ import {computeSigningRoot} from "../util/index.js";
5
+
6
+ export function getExecutionPayloadEnvelopeSigningRoot(
7
+ config: BeaconConfig,
8
+ envelope: gloas.ExecutionPayloadEnvelope
9
+ ): Uint8Array {
10
+ const domain = config.getDomain(envelope.slot, DOMAIN_BEACON_BUILDER);
11
+
12
+ return computeSigningRoot(ssz.gloas.ExecutionPayloadEnvelope, envelope, domain);
13
+ }