@lodestar/state-transition 1.38.0-dev.bc1fed4d3d → 1.38.0-dev.f231d7e8ff

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 (134) hide show
  1. package/lib/block/index.d.ts +4 -1
  2. package/lib/block/index.d.ts.map +1 -1
  3. package/lib/block/index.js +19 -8
  4. package/lib/block/index.js.map +1 -1
  5. package/lib/block/isValidIndexedPayloadAttestation.d.ts +4 -0
  6. package/lib/block/isValidIndexedPayloadAttestation.d.ts.map +1 -0
  7. package/lib/block/isValidIndexedPayloadAttestation.js +14 -0
  8. package/lib/block/isValidIndexedPayloadAttestation.js.map +1 -0
  9. package/lib/block/processAttestationPhase0.d.ts.map +1 -1
  10. package/lib/block/processAttestationPhase0.js +6 -1
  11. package/lib/block/processAttestationPhase0.js.map +1 -1
  12. package/lib/block/processAttestationsAltair.d.ts +3 -3
  13. package/lib/block/processAttestationsAltair.d.ts.map +1 -1
  14. package/lib/block/processAttestationsAltair.js +46 -4
  15. package/lib/block/processAttestationsAltair.js.map +1 -1
  16. package/lib/block/processConsolidationRequest.d.ts +3 -2
  17. package/lib/block/processConsolidationRequest.d.ts.map +1 -1
  18. package/lib/block/processConsolidationRequest.js +2 -2
  19. package/lib/block/processConsolidationRequest.js.map +1 -1
  20. package/lib/block/processDepositRequest.d.ts +2 -2
  21. package/lib/block/processDepositRequest.d.ts.map +1 -1
  22. package/lib/block/processDepositRequest.js.map +1 -1
  23. package/lib/block/processExecutionPayloadBid.d.ts +5 -0
  24. package/lib/block/processExecutionPayloadBid.d.ts.map +1 -0
  25. package/lib/block/processExecutionPayloadBid.js +89 -0
  26. package/lib/block/processExecutionPayloadBid.js.map +1 -0
  27. package/lib/block/processExecutionPayloadEnvelope.d.ts +4 -0
  28. package/lib/block/processExecutionPayloadEnvelope.d.ts.map +1 -0
  29. package/lib/block/processExecutionPayloadEnvelope.js +118 -0
  30. package/lib/block/processExecutionPayloadEnvelope.js.map +1 -0
  31. package/lib/block/processOperations.d.ts.map +1 -1
  32. package/lib/block/processOperations.js +8 -2
  33. package/lib/block/processOperations.js.map +1 -1
  34. package/lib/block/processPayloadAttestation.d.ts +4 -0
  35. package/lib/block/processPayloadAttestation.d.ts.map +1 -0
  36. package/lib/block/processPayloadAttestation.js +16 -0
  37. package/lib/block/processPayloadAttestation.js.map +1 -0
  38. package/lib/block/processProposerSlashing.d.ts.map +1 -1
  39. package/lib/block/processProposerSlashing.js +16 -1
  40. package/lib/block/processProposerSlashing.js.map +1 -1
  41. package/lib/block/processVoluntaryExit.js +1 -1
  42. package/lib/block/processVoluntaryExit.js.map +1 -1
  43. package/lib/block/processWithdrawalRequest.d.ts +2 -2
  44. package/lib/block/processWithdrawalRequest.d.ts.map +1 -1
  45. package/lib/block/processWithdrawalRequest.js +1 -1
  46. package/lib/block/processWithdrawalRequest.js.map +1 -1
  47. package/lib/block/processWithdrawals.d.ts +4 -3
  48. package/lib/block/processWithdrawals.d.ts.map +1 -1
  49. package/lib/block/processWithdrawals.js +89 -19
  50. package/lib/block/processWithdrawals.js.map +1 -1
  51. package/lib/cache/epochCache.d.ts +5 -1
  52. package/lib/cache/epochCache.d.ts.map +1 -1
  53. package/lib/cache/epochCache.js +34 -1
  54. package/lib/cache/epochCache.js.map +1 -1
  55. package/lib/epoch/index.d.ts +4 -2
  56. package/lib/epoch/index.d.ts.map +1 -1
  57. package/lib/epoch/index.js +10 -1
  58. package/lib/epoch/index.js.map +1 -1
  59. package/lib/epoch/processBuilderPendingPayments.d.ts +6 -0
  60. package/lib/epoch/processBuilderPendingPayments.d.ts.map +1 -0
  61. package/lib/epoch/processBuilderPendingPayments.js +28 -0
  62. package/lib/epoch/processBuilderPendingPayments.js.map +1 -0
  63. package/lib/index.d.ts +1 -1
  64. package/lib/index.d.ts.map +1 -1
  65. package/lib/index.js.map +1 -1
  66. package/lib/signatureSets/index.d.ts +1 -0
  67. package/lib/signatureSets/index.d.ts.map +1 -1
  68. package/lib/signatureSets/index.js +1 -0
  69. package/lib/signatureSets/index.js.map +1 -1
  70. package/lib/signatureSets/indexedPayloadAttestation.d.ts +6 -0
  71. package/lib/signatureSets/indexedPayloadAttestation.d.ts.map +1 -0
  72. package/lib/signatureSets/indexedPayloadAttestation.js +11 -0
  73. package/lib/signatureSets/indexedPayloadAttestation.js.map +1 -0
  74. package/lib/slot/index.d.ts +2 -1
  75. package/lib/slot/index.d.ts.map +1 -1
  76. package/lib/slot/index.js +6 -2
  77. package/lib/slot/index.js.map +1 -1
  78. package/lib/slot/upgradeStateToAltair.js +1 -1
  79. package/lib/slot/upgradeStateToAltair.js.map +1 -1
  80. package/lib/slot/upgradeStateToGloas.d.ts +0 -1
  81. package/lib/slot/upgradeStateToGloas.d.ts.map +1 -1
  82. package/lib/slot/upgradeStateToGloas.js +47 -5
  83. package/lib/slot/upgradeStateToGloas.js.map +1 -1
  84. package/lib/stateTransition.js +4 -3
  85. package/lib/stateTransition.js.map +1 -1
  86. package/lib/util/electra.d.ts +5 -5
  87. package/lib/util/electra.d.ts.map +1 -1
  88. package/lib/util/electra.js +2 -1
  89. package/lib/util/electra.js.map +1 -1
  90. package/lib/util/epoch.d.ts +3 -3
  91. package/lib/util/epoch.d.ts.map +1 -1
  92. package/lib/util/epoch.js.map +1 -1
  93. package/lib/util/gloas.d.ts +11 -0
  94. package/lib/util/gloas.d.ts.map +1 -0
  95. package/lib/util/gloas.js +35 -0
  96. package/lib/util/gloas.js.map +1 -0
  97. package/lib/util/seed.d.ts +5 -1
  98. package/lib/util/seed.d.ts.map +1 -1
  99. package/lib/util/seed.js +33 -1
  100. package/lib/util/seed.js.map +1 -1
  101. package/lib/util/validator.d.ts +2 -2
  102. package/lib/util/validator.d.ts.map +1 -1
  103. package/lib/util/validator.js +14 -1
  104. package/lib/util/validator.js.map +1 -1
  105. package/package.json +6 -6
  106. package/src/block/index.ts +35 -14
  107. package/src/block/isValidIndexedPayloadAttestation.ts +23 -0
  108. package/src/block/processAttestationPhase0.ts +5 -1
  109. package/src/block/processAttestationsAltair.ts +62 -5
  110. package/src/block/processConsolidationRequest.ts +6 -5
  111. package/src/block/processDepositRequest.ts +5 -2
  112. package/src/block/processExecutionPayloadBid.ts +120 -0
  113. package/src/block/processExecutionPayloadEnvelope.ts +181 -0
  114. package/src/block/processOperations.ts +16 -4
  115. package/src/block/processPayloadAttestation.ts +25 -0
  116. package/src/block/processProposerSlashing.ts +24 -3
  117. package/src/block/processVoluntaryExit.ts +1 -1
  118. package/src/block/processWithdrawalRequest.ts +4 -4
  119. package/src/block/processWithdrawals.ts +118 -27
  120. package/src/cache/epochCache.ts +58 -1
  121. package/src/epoch/index.ts +12 -0
  122. package/src/epoch/processBuilderPendingPayments.ts +31 -0
  123. package/src/index.ts +2 -0
  124. package/src/signatureSets/index.ts +1 -0
  125. package/src/signatureSets/indexedPayloadAttestation.ts +24 -0
  126. package/src/slot/index.ts +11 -3
  127. package/src/slot/upgradeStateToAltair.ts +2 -1
  128. package/src/slot/upgradeStateToGloas.ts +49 -5
  129. package/src/stateTransition.ts +4 -4
  130. package/src/util/electra.ts +15 -6
  131. package/src/util/epoch.ts +6 -3
  132. package/src/util/gloas.ts +58 -0
  133. package/src/util/seed.ts +57 -1
  134. package/src/util/validator.ts +21 -2
@@ -0,0 +1,25 @@
1
+ import {byteArrayEquals} from "@chainsafe/ssz";
2
+ import {gloas} from "@lodestar/types";
3
+ import {CachedBeaconStateGloas} from "../types.ts";
4
+ import {isValidIndexedPayloadAttestation} from "./isValidIndexedPayloadAttestation.ts";
5
+
6
+ export function processPayloadAttestation(
7
+ state: CachedBeaconStateGloas,
8
+ payloadAttestation: gloas.PayloadAttestation
9
+ ): void {
10
+ const data = payloadAttestation.data;
11
+
12
+ if (!byteArrayEquals(data.beaconBlockRoot, state.latestBlockHeader.parentRoot)) {
13
+ throw Error("Payload attestation is referring to the wrong block");
14
+ }
15
+
16
+ if (data.slot + 1 !== state.slot) {
17
+ throw Error("Payload attestation is not from previous slot");
18
+ }
19
+
20
+ const indexedPayloadAttestation = state.epochCtx.getIndexedPayloadAttestation(data.slot, payloadAttestation);
21
+
22
+ if (!isValidIndexedPayloadAttestation(state, indexedPayloadAttestation, true)) {
23
+ throw Error("Invalid payload attestation");
24
+ }
25
+ }
@@ -1,8 +1,8 @@
1
- import {ForkSeq} from "@lodestar/params";
1
+ import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params";
2
2
  import {phase0, ssz} from "@lodestar/types";
3
3
  import {getProposerSlashingSignatureSets} from "../signatureSets/index.js";
4
- import {CachedBeaconStateAllForks} from "../types.js";
5
- import {isSlashableValidator} from "../util/index.js";
4
+ import {CachedBeaconStateAllForks, CachedBeaconStateGloas} from "../types.js";
5
+ import {computeEpochAtSlot, isSlashableValidator} from "../util/index.js";
6
6
  import {verifySignatureSet} from "../util/signatureSets.js";
7
7
  import {slashValidator} from "./slashValidator.js";
8
8
 
@@ -20,6 +20,27 @@ export function processProposerSlashing(
20
20
  ): void {
21
21
  assertValidProposerSlashing(state, proposerSlashing, verifySignatures);
22
22
 
23
+ if (fork >= ForkSeq.gloas) {
24
+ const slot = Number(proposerSlashing.signedHeader1.message.slot);
25
+ const proposalEpoch = computeEpochAtSlot(slot);
26
+ const currentEpoch = state.epochCtx.epoch;
27
+ const previousEpoch = currentEpoch - 1;
28
+
29
+ const paymentIndex =
30
+ proposalEpoch === currentEpoch
31
+ ? SLOTS_PER_EPOCH + (slot % SLOTS_PER_EPOCH)
32
+ : proposalEpoch === previousEpoch
33
+ ? slot % SLOTS_PER_EPOCH
34
+ : undefined;
35
+
36
+ if (paymentIndex !== undefined) {
37
+ (state as CachedBeaconStateGloas).builderPendingPayments.set(
38
+ paymentIndex,
39
+ ssz.gloas.BuilderPendingPayment.defaultViewDU()
40
+ );
41
+ }
42
+ }
43
+
23
44
  slashValidator(fork, state, proposerSlashing.signedHeader1.message.proposerIndex);
24
45
  }
25
46
 
@@ -69,7 +69,7 @@ export function getVoluntaryExitValidity(
69
69
  // only exit validator if it has no pending withdrawals in the queue
70
70
  if (
71
71
  fork >= ForkSeq.electra &&
72
- getPendingBalanceToWithdraw(state as CachedBeaconStateElectra, voluntaryExit.validatorIndex) !== 0
72
+ getPendingBalanceToWithdraw(fork, state as CachedBeaconStateElectra, voluntaryExit.validatorIndex) !== 0
73
73
  ) {
74
74
  return VoluntaryExitValidity.pendingWithdrawals;
75
75
  }
@@ -7,7 +7,7 @@ import {
7
7
  } from "@lodestar/params";
8
8
  import {electra, phase0, ssz} from "@lodestar/types";
9
9
  import {toHex} from "@lodestar/utils";
10
- import {CachedBeaconStateElectra} from "../types.js";
10
+ import {CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";
11
11
  import {hasCompoundingWithdrawalCredential, hasExecutionWithdrawalCredential} from "../util/electra.js";
12
12
  import {computeExitEpochAndUpdateChurn} from "../util/epoch.js";
13
13
  import {getPendingBalanceToWithdraw, isActiveValidator} from "../util/validator.js";
@@ -15,7 +15,7 @@ import {initiateValidatorExit} from "./initiateValidatorExit.js";
15
15
 
16
16
  export function processWithdrawalRequest(
17
17
  fork: ForkSeq,
18
- state: CachedBeaconStateElectra,
18
+ state: CachedBeaconStateElectra | CachedBeaconStateGloas,
19
19
  withdrawalRequest: electra.WithdrawalRequest
20
20
  ): void {
21
21
  const amount = Number(withdrawalRequest.amount);
@@ -42,7 +42,7 @@ export function processWithdrawalRequest(
42
42
  }
43
43
 
44
44
  // TODO Electra: Consider caching pendingPartialWithdrawals
45
- const pendingBalanceToWithdraw = getPendingBalanceToWithdraw(state, validatorIndex);
45
+ const pendingBalanceToWithdraw = getPendingBalanceToWithdraw(fork, state, validatorIndex);
46
46
  const validatorBalance = state.balances.get(validatorIndex);
47
47
 
48
48
  if (isFullExitRequest) {
@@ -81,7 +81,7 @@ export function processWithdrawalRequest(
81
81
  function isValidatorEligibleForWithdrawOrExit(
82
82
  validator: phase0.Validator,
83
83
  sourceAddress: Uint8Array,
84
- state: CachedBeaconStateElectra
84
+ state: CachedBeaconStateElectra | CachedBeaconStateGloas
85
85
  ): boolean {
86
86
  const {withdrawalCredentials} = validator;
87
87
  const addressStr = toHex(withdrawalCredentials.subarray(12));
@@ -10,7 +10,8 @@ import {
10
10
  } from "@lodestar/params";
11
11
  import {ValidatorIndex, capella, ssz} from "@lodestar/types";
12
12
  import {MapDef, toRootHex} from "@lodestar/utils";
13
- import {CachedBeaconStateCapella, CachedBeaconStateElectra} from "../types.js";
13
+ import {CachedBeaconStateCapella, CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";
14
+ import {isBuilderPaymentWithdrawable, isParentBlockFull} from "../util/gloas.ts";
14
15
  import {
15
16
  decreaseBalance,
16
17
  getMaxEffectiveBalance,
@@ -21,31 +22,48 @@ import {
21
22
 
22
23
  export function processWithdrawals(
23
24
  fork: ForkSeq,
24
- state: CachedBeaconStateCapella | CachedBeaconStateElectra,
25
- payload: capella.FullOrBlindedExecutionPayload
25
+ state: CachedBeaconStateCapella | CachedBeaconStateElectra | CachedBeaconStateGloas,
26
+ payload?: capella.FullOrBlindedExecutionPayload
26
27
  ): void {
28
+ // Return early if the parent block is empty
29
+ if (fork >= ForkSeq.gloas && !isParentBlockFull(state as CachedBeaconStateGloas)) {
30
+ return;
31
+ }
32
+
27
33
  // processedPartialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002)
28
- const {withdrawals: expectedWithdrawals, processedPartialWithdrawalsCount} = getExpectedWithdrawals(fork, state);
34
+ // processedBuilderWithdrawalsCount is withdrawals coming from builder payment since gloas (EIP-7732)
35
+ const {
36
+ withdrawals: expectedWithdrawals,
37
+ processedPartialWithdrawalsCount,
38
+ processedBuilderWithdrawalsCount,
39
+ } = getExpectedWithdrawals(fork, state);
29
40
  const numWithdrawals = expectedWithdrawals.length;
30
41
 
31
- if (isCapellaPayloadHeader(payload)) {
32
- const expectedWithdrawalsRoot = ssz.capella.Withdrawals.hashTreeRoot(expectedWithdrawals);
33
- const actualWithdrawalsRoot = payload.withdrawalsRoot;
34
- if (!byteArrayEquals(expectedWithdrawalsRoot, actualWithdrawalsRoot)) {
35
- throw Error(
36
- `Invalid withdrawalsRoot of executionPayloadHeader, expected=${toRootHex(
37
- expectedWithdrawalsRoot
38
- )}, actual=${toRootHex(actualWithdrawalsRoot)}`
39
- );
40
- }
41
- } else {
42
- if (expectedWithdrawals.length !== payload.withdrawals.length) {
43
- throw Error(`Invalid withdrawals length expected=${numWithdrawals} actual=${payload.withdrawals.length}`);
42
+ // After gloas, withdrawals are verified later in processExecutionPayloadEnvelope
43
+ if (fork < ForkSeq.gloas) {
44
+ if (payload === undefined) {
45
+ throw Error("payload is required for pre-gloas processWithdrawals");
44
46
  }
45
- for (let i = 0; i < numWithdrawals; i++) {
46
- const withdrawal = expectedWithdrawals[i];
47
- if (!ssz.capella.Withdrawal.equals(withdrawal, payload.withdrawals[i])) {
48
- throw Error(`Withdrawal mismatch at index=${i}`);
47
+
48
+ if (isCapellaPayloadHeader(payload)) {
49
+ const expectedWithdrawalsRoot = ssz.capella.Withdrawals.hashTreeRoot(expectedWithdrawals);
50
+ const actualWithdrawalsRoot = payload.withdrawalsRoot;
51
+ if (!byteArrayEquals(expectedWithdrawalsRoot, actualWithdrawalsRoot)) {
52
+ throw Error(
53
+ `Invalid withdrawalsRoot of executionPayloadHeader, expected=${toRootHex(
54
+ expectedWithdrawalsRoot
55
+ )}, actual=${toRootHex(actualWithdrawalsRoot)}`
56
+ );
57
+ }
58
+ } else {
59
+ if (expectedWithdrawals.length !== payload.withdrawals.length) {
60
+ throw Error(`Invalid withdrawals length expected=${numWithdrawals} actual=${payload.withdrawals.length}`);
61
+ }
62
+ for (let i = 0; i < numWithdrawals; i++) {
63
+ const withdrawal = expectedWithdrawals[i];
64
+ if (!ssz.capella.Withdrawal.equals(withdrawal, payload.withdrawals[i])) {
65
+ throw Error(`Withdrawal mismatch at index=${i}`);
66
+ }
49
67
  }
50
68
  }
51
69
  }
@@ -62,6 +80,24 @@ export function processWithdrawals(
62
80
  );
63
81
  }
64
82
 
83
+ if (fork >= ForkSeq.gloas) {
84
+ const stateGloas = state as CachedBeaconStateGloas;
85
+ stateGloas.latestWithdrawalsRoot = ssz.capella.Withdrawals.hashTreeRoot(expectedWithdrawals);
86
+
87
+ const unprocessedWithdrawals = stateGloas.builderPendingWithdrawals
88
+ .getAllReadonly()
89
+ .slice(0, processedBuilderWithdrawalsCount)
90
+ .filter((w) => !isBuilderPaymentWithdrawable(stateGloas, w));
91
+ const remainingWithdrawals = stateGloas.builderPendingWithdrawals
92
+ .sliceFrom(processedBuilderWithdrawalsCount)
93
+ .getAllReadonly();
94
+
95
+ stateGloas.builderPendingWithdrawals = ssz.gloas.BeaconState.fields.builderPendingWithdrawals.toViewDU([
96
+ ...unprocessedWithdrawals,
97
+ ...remainingWithdrawals,
98
+ ]);
99
+ }
100
+
65
101
  // Update the nextWithdrawalIndex
66
102
  const latestWithdrawal = expectedWithdrawals.at(-1);
67
103
  if (latestWithdrawal) {
@@ -82,11 +118,12 @@ export function processWithdrawals(
82
118
 
83
119
  export function getExpectedWithdrawals(
84
120
  fork: ForkSeq,
85
- state: CachedBeaconStateCapella | CachedBeaconStateElectra
121
+ state: CachedBeaconStateCapella | CachedBeaconStateElectra | CachedBeaconStateGloas
86
122
  ): {
87
123
  withdrawals: capella.Withdrawal[];
88
124
  sampledValidators: number;
89
125
  processedPartialWithdrawalsCount: number;
126
+ processedBuilderWithdrawalsCount: number;
90
127
  } {
91
128
  if (fork < ForkSeq.capella) {
92
129
  throw new Error(`getExpectedWithdrawals not supported at forkSeq=${fork} < ForkSeq.capella`);
@@ -99,17 +136,71 @@ export function getExpectedWithdrawals(
99
136
  const withdrawals: capella.Withdrawal[] = [];
100
137
  const withdrawnBalances = new MapDef<ValidatorIndex, number>(() => 0);
101
138
  const isPostElectra = fork >= ForkSeq.electra;
139
+ const isPostGloas = fork >= ForkSeq.gloas;
102
140
  // partialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002)
103
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;
152
+
153
+ for (let i = 0; i < stateGloas.builderPendingWithdrawals.length; i++) {
154
+ const withdrawal = allBuilderPendingWithdrawals
155
+ ? allBuilderPendingWithdrawals[i]
156
+ : stateGloas.builderPendingWithdrawals.getReadonly(i);
157
+
158
+ if (withdrawal.withdrawableEpoch > epoch || withdrawals.length + 1 === MAX_WITHDRAWALS_PER_PAYLOAD) {
159
+ break;
160
+ }
161
+
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;
168
+
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
+ }
175
+
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
+ }
186
+ }
187
+ processedBuilderWithdrawalsCount++;
188
+ }
189
+ }
104
190
 
105
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
+ );
106
197
  const stateElectra = state as CachedBeaconStateElectra;
107
198
 
108
199
  // MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP = 8, PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728 so we should only call getAllReadonly() if it makes sense
109
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
110
201
  // also we may break early if withdrawableEpoch > epoch
111
202
  const allPendingPartialWithdrawals =
112
- stateElectra.pendingPartialWithdrawals.length <= MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP
203
+ stateElectra.pendingPartialWithdrawals.length <= partialWithdrawalBound
113
204
  ? stateElectra.pendingPartialWithdrawals.getAllReadonly()
114
205
  : null;
115
206
 
@@ -118,7 +209,7 @@ export function getExpectedWithdrawals(
118
209
  const withdrawal = allPendingPartialWithdrawals
119
210
  ? allPendingPartialWithdrawals[i]
120
211
  : stateElectra.pendingPartialWithdrawals.getReadonly(i);
121
- if (withdrawal.withdrawableEpoch > epoch || withdrawals.length === MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP) {
212
+ if (withdrawal.withdrawableEpoch > epoch || withdrawals.length === partialWithdrawalBound) {
122
213
  break;
123
214
  }
124
215
 
@@ -147,11 +238,11 @@ export function getExpectedWithdrawals(
147
238
  }
148
239
  }
149
240
 
150
- const bound = Math.min(validators.length, MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP);
241
+ const withdrawalBound = Math.min(validators.length, MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP);
151
242
  let n = 0;
152
243
  // Just run a bounded loop max iterating over all withdrawals
153
244
  // however breaks out once we have MAX_WITHDRAWALS_PER_PAYLOAD
154
- for (n = 0; n < bound; n++) {
245
+ for (n = 0; n < withdrawalBound; n++) {
155
246
  // Get next validator in turn
156
247
  const validatorIndex = (nextWithdrawalValidatorIndex + n) % validators.length;
157
248
 
@@ -203,5 +294,5 @@ export function getExpectedWithdrawals(
203
294
  }
204
295
  }
205
296
 
206
- return {withdrawals, sampledValidators: n, processedPartialWithdrawalsCount};
297
+ return {withdrawals, sampledValidators: n, processedPartialWithdrawalsCount, processedBuilderWithdrawalsCount};
207
298
  }
@@ -24,6 +24,7 @@ import {
24
24
  SyncPeriod,
25
25
  ValidatorIndex,
26
26
  electra,
27
+ gloas,
27
28
  phase0,
28
29
  } from "@lodestar/types";
29
30
  import {LodestarError} from "@lodestar/utils";
@@ -46,6 +47,7 @@ import {
46
47
  getSeed,
47
48
  isActiveValidator,
48
49
  isAggregatorFromCommitteeLength,
50
+ naiveGetPayloadTimlinessCommitteeIndices,
49
51
  } from "../util/index.js";
50
52
  import {computeBaseRewardPerIncrement, computeSyncParticipantReward} from "../util/syncCommittee.js";
51
53
  import {sumTargetUnslashedBalanceIncrements} from "../util/targetUnslashedBalance.js";
@@ -59,7 +61,7 @@ import {
59
61
  computeSyncCommitteeCache,
60
62
  getSyncCommitteeCache,
61
63
  } from "./syncCommitteeCache.js";
62
- import {BeaconStateAllForks, BeaconStateAltair} from "./types.js";
64
+ import {BeaconStateAllForks, BeaconStateAltair, BeaconStateGloas} from "./types.js";
63
65
 
64
66
  /** `= PROPOSER_WEIGHT / (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)` */
65
67
  export const PROPOSER_WEIGHT_FACTOR = PROPOSER_WEIGHT / (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT);
@@ -238,6 +240,10 @@ export class EpochCache {
238
240
  /** TODO: Indexed SyncCommitteeCache */
239
241
  nextSyncCommitteeIndexed: SyncCommitteeCache;
240
242
 
243
+ // TODO GLOAS: See if we need to cached PTC for prev/next epoch
244
+ // PTC for current epoch
245
+ payloadTimelinessCommittee: ValidatorIndex[][];
246
+
241
247
  // TODO: Helper stats
242
248
  syncPeriod: SyncPeriod;
243
249
 
@@ -276,6 +282,7 @@ export class EpochCache {
276
282
  previousTargetUnslashedBalanceIncrements: number;
277
283
  currentSyncCommitteeIndexed: SyncCommitteeCache;
278
284
  nextSyncCommitteeIndexed: SyncCommitteeCache;
285
+ payloadTimelinessCommittee: ValidatorIndex[][];
279
286
  epoch: Epoch;
280
287
  syncPeriod: SyncPeriod;
281
288
  }) {
@@ -307,6 +314,7 @@ export class EpochCache {
307
314
  this.previousTargetUnslashedBalanceIncrements = data.previousTargetUnslashedBalanceIncrements;
308
315
  this.currentSyncCommitteeIndexed = data.currentSyncCommitteeIndexed;
309
316
  this.nextSyncCommitteeIndexed = data.nextSyncCommitteeIndexed;
317
+ this.payloadTimelinessCommittee = data.payloadTimelinessCommittee;
310
318
  this.epoch = data.epoch;
311
319
  this.syncPeriod = data.syncPeriod;
312
320
  }
@@ -485,6 +493,17 @@ export class EpochCache {
485
493
  nextSyncCommitteeIndexed = new SyncCommitteeCacheEmpty();
486
494
  }
487
495
 
496
+ // Compute PTC for this epoch
497
+ let payloadTimelinessCommittee: ValidatorIndex[][] = [];
498
+ if (currentEpoch >= config.GLOAS_FORK_EPOCH) {
499
+ payloadTimelinessCommittee = naiveGetPayloadTimlinessCommitteeIndices(
500
+ state as BeaconStateGloas,
501
+ currentShuffling,
502
+ effectiveBalanceIncrements,
503
+ currentEpoch
504
+ );
505
+ }
506
+
488
507
  // Precompute churnLimit for efficient initiateValidatorExit() during block proposing MUST be recompute everytime the
489
508
  // active validator indices set changes in size. Validators change active status only when:
490
509
  // - validator.activation_epoch is set. Only changes in process_registry_updates() if validator can be activated. If
@@ -559,6 +578,7 @@ export class EpochCache {
559
578
  currentTargetUnslashedBalanceIncrements,
560
579
  currentSyncCommitteeIndexed,
561
580
  nextSyncCommitteeIndexed,
581
+ payloadTimelinessCommittee: payloadTimelinessCommittee,
562
582
  epoch: currentEpoch,
563
583
  syncPeriod: computeSyncPeriodAtEpoch(currentEpoch),
564
584
  });
@@ -605,6 +625,7 @@ export class EpochCache {
605
625
  currentTargetUnslashedBalanceIncrements: this.currentTargetUnslashedBalanceIncrements,
606
626
  currentSyncCommitteeIndexed: this.currentSyncCommitteeIndexed,
607
627
  nextSyncCommitteeIndexed: this.nextSyncCommitteeIndexed,
628
+ payloadTimelinessCommittee: this.payloadTimelinessCommittee,
608
629
  epoch: this.epoch,
609
630
  syncPeriod: this.syncPeriod,
610
631
  });
@@ -750,6 +771,14 @@ export class EpochCache {
750
771
  const epochAfterUpcoming = upcomingEpoch + 1;
751
772
 
752
773
  this.proposersPrevEpoch = this.proposers;
774
+ if (upcomingEpoch >= this.config.GLOAS_FORK_EPOCH) {
775
+ this.payloadTimelinessCommittee = naiveGetPayloadTimlinessCommitteeIndices(
776
+ state as BeaconStateGloas,
777
+ this.currentShuffling,
778
+ this.effectiveBalanceIncrements,
779
+ upcomingEpoch
780
+ );
781
+ }
753
782
  if (upcomingEpoch >= this.config.FULU_FORK_EPOCH) {
754
783
  // Populate proposer cache with lookahead from state
755
784
  const proposerLookahead = (state as CachedBeaconStateFulu).proposerLookahead.getAll();
@@ -1151,6 +1180,34 @@ export class EpochCache {
1151
1180
  isPostElectra(): boolean {
1152
1181
  return this.epoch >= this.config.ELECTRA_FORK_EPOCH;
1153
1182
  }
1183
+
1184
+ getPayloadTimelinessCommittee(slot: Slot): ValidatorIndex[] {
1185
+ const epoch = computeEpochAtSlot(slot);
1186
+
1187
+ if (epoch < this.config.GLOAS_FORK_EPOCH) {
1188
+ throw new Error("Payload Timeliness Committee is not available before gloas fork");
1189
+ }
1190
+
1191
+ if (epoch === this.epoch) {
1192
+ return this.payloadTimelinessCommittee[slot % SLOTS_PER_EPOCH];
1193
+ }
1194
+
1195
+ throw new Error(`Payload Timeliness Committee is not available for slot=${slot}`);
1196
+ }
1197
+
1198
+ getIndexedPayloadAttestation(
1199
+ slot: Slot,
1200
+ payloadAttestation: gloas.PayloadAttestation
1201
+ ): gloas.IndexedPayloadAttestation {
1202
+ const payloadTimelinessCommittee = this.getPayloadTimelinessCommittee(slot);
1203
+ const attestingIndices = payloadAttestation.aggregationBits.intersectValues(payloadTimelinessCommittee);
1204
+
1205
+ return {
1206
+ attestingIndices: attestingIndices.sort((a, b) => a - b),
1207
+ data: payloadAttestation.data,
1208
+ signature: payloadAttestation.signature,
1209
+ };
1210
+ }
1154
1211
  }
1155
1212
 
1156
1213
  function getEffectiveBalanceIncrementsByteLen(validatorCount: number): number {
@@ -12,9 +12,11 @@ import {
12
12
  CachedBeaconStateCapella,
13
13
  CachedBeaconStateElectra,
14
14
  CachedBeaconStateFulu,
15
+ CachedBeaconStateGloas,
15
16
  CachedBeaconStatePhase0,
16
17
  EpochTransitionCache,
17
18
  } from "../types.js";
19
+ import {processBuilderPendingPayments} from "./processBuilderPendingPayments.ts";
18
20
  import {processEffectiveBalanceUpdates} from "./processEffectiveBalanceUpdates.js";
19
21
  import {processEth1DataReset} from "./processEth1DataReset.js";
20
22
  import {processHistoricalRootsUpdate} from "./processHistoricalRootsUpdate.js";
@@ -53,6 +55,7 @@ export {
53
55
  processPendingDeposits,
54
56
  processPendingConsolidations,
55
57
  processProposerLookahead,
58
+ processBuilderPendingPayments,
56
59
  };
57
60
 
58
61
  export {computeUnrealizedCheckpoints} from "./computeUnrealizedCheckpoints.js";
@@ -78,6 +81,7 @@ export enum EpochTransitionStep {
78
81
  processPendingDeposits = "processPendingDeposits",
79
82
  processPendingConsolidations = "processPendingConsolidations",
80
83
  processProposerLookahead = "processProposerLookahead",
84
+ processBuilderPendingPayments = "processBuilderPendingPayments",
81
85
  }
82
86
 
83
87
  export function processEpoch(
@@ -154,6 +158,14 @@ export function processEpoch(
154
158
  }
155
159
  }
156
160
 
161
+ if (fork >= ForkSeq.gloas) {
162
+ const timer = metrics?.epochTransitionStepTime.startTimer({
163
+ step: EpochTransitionStep.processBuilderPendingPayments,
164
+ });
165
+ processBuilderPendingPayments(state as CachedBeaconStateGloas);
166
+ timer?.();
167
+ }
168
+
157
169
  {
158
170
  const timer = metrics?.epochTransitionStepTime.startTimer({
159
171
  step: EpochTransitionStep.processEffectiveBalanceUpdates,
@@ -0,0 +1,31 @@
1
+ import {SLOTS_PER_EPOCH} from "@lodestar/params";
2
+ import {ssz} from "@lodestar/types";
3
+ import {CachedBeaconStateGloas} from "../types.ts";
4
+ import {computeExitEpochAndUpdateChurn} from "../util/epoch.ts";
5
+ import {getBuilderPaymentQuorumThreshold} from "../util/gloas.ts";
6
+
7
+ /**
8
+ * Processes the builder pending payments from the previous epoch.
9
+ */
10
+ export function processBuilderPendingPayments(state: CachedBeaconStateGloas): void {
11
+ const quorum = getBuilderPaymentQuorumThreshold(state);
12
+
13
+ for (let i = 0; i < SLOTS_PER_EPOCH; i++) {
14
+ 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
+
19
+ state.builderPendingWithdrawals.push(payment.withdrawal);
20
+ }
21
+ }
22
+
23
+ // TODO GLOAS: Optimize this
24
+ for (let i = 0; i < state.builderPendingPayments.length; i++) {
25
+ if (i < SLOTS_PER_EPOCH) {
26
+ state.builderPendingPayments.set(i, state.builderPendingPayments.get(i + SLOTS_PER_EPOCH).clone());
27
+ } else {
28
+ state.builderPendingPayments.set(i, ssz.gloas.BuilderPendingPayment.defaultViewDU());
29
+ }
30
+ }
31
+ }
package/src/index.ts CHANGED
@@ -52,6 +52,7 @@ export type {
52
52
  BeaconStateElectra,
53
53
  BeaconStateExecutions,
54
54
  BeaconStateFulu,
55
+ BeaconStateGloas,
55
56
  // Non-cached states
56
57
  BeaconStatePhase0,
57
58
  CachedBeaconStateAllForks,
@@ -62,6 +63,7 @@ export type {
62
63
  CachedBeaconStateElectra,
63
64
  CachedBeaconStateExecutions,
64
65
  CachedBeaconStateFulu,
66
+ CachedBeaconStateGloas,
65
67
  CachedBeaconStatePhase0,
66
68
  } from "./types.js";
67
69
  export * from "./util/index.js";
@@ -14,6 +14,7 @@ import {getVoluntaryExitsSignatureSets} from "./voluntaryExits.js";
14
14
  export * from "./attesterSlashings.js";
15
15
  export * from "./blsToExecutionChange.js";
16
16
  export * from "./indexedAttestation.js";
17
+ export * from "./indexedPayloadAttestation.ts";
17
18
  export * from "./proposer.js";
18
19
  export * from "./proposerSlashings.js";
19
20
  export * from "./randao.js";
@@ -0,0 +1,24 @@
1
+ import {DOMAIN_PTC_ATTESTER} from "@lodestar/params";
2
+ import {gloas, ssz} from "@lodestar/types";
3
+ import {CachedBeaconStateGloas} from "../types.ts";
4
+ import {ISignatureSet, computeSigningRoot, createAggregateSignatureSetFromComponents} from "../util/index.ts";
5
+
6
+ export function getIndexedPayloadAttestationSignatureSet(
7
+ state: CachedBeaconStateGloas,
8
+ indexedPayloadAttestation: gloas.IndexedPayloadAttestation
9
+ ): ISignatureSet {
10
+ return createAggregateSignatureSetFromComponents(
11
+ indexedPayloadAttestation.attestingIndices.map((i) => state.epochCtx.index2pubkey[i]),
12
+ getPayloadAttestationDataSigningRoot(state, indexedPayloadAttestation.data),
13
+ indexedPayloadAttestation.signature
14
+ );
15
+ }
16
+
17
+ export function getPayloadAttestationDataSigningRoot(
18
+ state: CachedBeaconStateGloas,
19
+ data: gloas.PayloadAttestationData
20
+ ): Uint8Array {
21
+ const domain = state.config.getDomain(state.slot, DOMAIN_PTC_ATTESTER);
22
+
23
+ return computeSigningRoot(ssz.gloas.PayloadAttestationData, data, domain);
24
+ }
package/src/slot/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import {byteArrayEquals} from "@chainsafe/ssz";
2
- import {SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
2
+ import {ForkSeq, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
3
3
  import {ZERO_HASH} from "../constants/index.js";
4
- import {CachedBeaconStateAllForks} from "../types.js";
4
+ import {CachedBeaconStateAllForks, CachedBeaconStateGloas} from "../types.js";
5
5
 
6
6
  export {upgradeStateToAltair} from "./upgradeStateToAltair.js";
7
7
  export {upgradeStateToBellatrix} from "./upgradeStateToBellatrix.js";
@@ -14,7 +14,7 @@ export {upgradeStateToGloas} from "./upgradeStateToGloas.js";
14
14
  /**
15
15
  * Dial state to next slot. Common for all forks
16
16
  */
17
- export function processSlot(state: CachedBeaconStateAllForks): void {
17
+ export function processSlot(fork: ForkSeq, state: CachedBeaconStateAllForks): void {
18
18
  // Cache state root
19
19
  // Note: .hashTreeRoot() automatically commits() pending changes
20
20
  const previousStateRoot = state.hashTreeRoot();
@@ -29,4 +29,12 @@ export function processSlot(state: CachedBeaconStateAllForks): void {
29
29
  // Note: .hashTreeRoot() automatically commits() pending changes
30
30
  const previousBlockRoot = state.latestBlockHeader.hashTreeRoot();
31
31
  state.blockRoots.set(state.slot % SLOTS_PER_HISTORICAL_ROOT, previousBlockRoot);
32
+
33
+ if (fork >= ForkSeq.gloas) {
34
+ // Unset the next payload availability
35
+ (state as CachedBeaconStateGloas).executionPayloadAvailability.set(
36
+ (state.slot + 1) % SLOTS_PER_HISTORICAL_ROOT,
37
+ false
38
+ );
39
+ }
32
40
  }
@@ -135,7 +135,8 @@ function translateParticipation(
135
135
  data,
136
136
  attestation.inclusionDelay,
137
137
  epochCtx.epoch,
138
- rootCache
138
+ rootCache,
139
+ null
139
140
  );
140
141
 
141
142
  const committeeIndices = epochCtx.getBeaconCommittee(data.slot, data.index);