@lodestar/state-transition 1.37.0-rc.0 → 1.38.0-dev.1ddbe5d870

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 (182) 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/isValidIndexedAttestation.d.ts +3 -2
  6. package/lib/block/isValidIndexedAttestation.d.ts.map +1 -1
  7. package/lib/block/isValidIndexedAttestation.js +4 -4
  8. package/lib/block/isValidIndexedAttestation.js.map +1 -1
  9. package/lib/block/isValidIndexedPayloadAttestation.d.ts +4 -0
  10. package/lib/block/isValidIndexedPayloadAttestation.d.ts.map +1 -0
  11. package/lib/block/isValidIndexedPayloadAttestation.js +14 -0
  12. package/lib/block/isValidIndexedPayloadAttestation.js.map +1 -0
  13. package/lib/block/processAttestationPhase0.d.ts.map +1 -1
  14. package/lib/block/processAttestationPhase0.js +7 -2
  15. package/lib/block/processAttestationPhase0.js.map +1 -1
  16. package/lib/block/processAttestationsAltair.d.ts +3 -3
  17. package/lib/block/processAttestationsAltair.d.ts.map +1 -1
  18. package/lib/block/processAttestationsAltair.js +47 -5
  19. package/lib/block/processAttestationsAltair.js.map +1 -1
  20. package/lib/block/processAttesterSlashing.d.ts +2 -1
  21. package/lib/block/processAttesterSlashing.d.ts.map +1 -1
  22. package/lib/block/processAttesterSlashing.js +5 -4
  23. package/lib/block/processAttesterSlashing.js.map +1 -1
  24. package/lib/block/processConsolidationRequest.d.ts +3 -2
  25. package/lib/block/processConsolidationRequest.d.ts.map +1 -1
  26. package/lib/block/processConsolidationRequest.js +2 -2
  27. package/lib/block/processConsolidationRequest.js.map +1 -1
  28. package/lib/block/processDepositRequest.d.ts +2 -2
  29. package/lib/block/processDepositRequest.d.ts.map +1 -1
  30. package/lib/block/processDepositRequest.js.map +1 -1
  31. package/lib/block/processExecutionPayloadBid.d.ts +5 -0
  32. package/lib/block/processExecutionPayloadBid.d.ts.map +1 -0
  33. package/lib/block/processExecutionPayloadBid.js +89 -0
  34. package/lib/block/processExecutionPayloadBid.js.map +1 -0
  35. package/lib/block/processExecutionPayloadEnvelope.d.ts +4 -0
  36. package/lib/block/processExecutionPayloadEnvelope.d.ts.map +1 -0
  37. package/lib/block/processExecutionPayloadEnvelope.js +118 -0
  38. package/lib/block/processExecutionPayloadEnvelope.js.map +1 -0
  39. package/lib/block/processOperations.d.ts.map +1 -1
  40. package/lib/block/processOperations.js +8 -2
  41. package/lib/block/processOperations.js.map +1 -1
  42. package/lib/block/processPayloadAttestation.d.ts +4 -0
  43. package/lib/block/processPayloadAttestation.d.ts.map +1 -0
  44. package/lib/block/processPayloadAttestation.js +16 -0
  45. package/lib/block/processPayloadAttestation.js.map +1 -0
  46. package/lib/block/processProposerSlashing.d.ts.map +1 -1
  47. package/lib/block/processProposerSlashing.js +17 -2
  48. package/lib/block/processProposerSlashing.js.map +1 -1
  49. package/lib/block/processRandao.js +1 -1
  50. package/lib/block/processRandao.js.map +1 -1
  51. package/lib/block/processSyncCommittee.d.ts +2 -1
  52. package/lib/block/processSyncCommittee.d.ts.map +1 -1
  53. package/lib/block/processSyncCommittee.js +3 -4
  54. package/lib/block/processSyncCommittee.js.map +1 -1
  55. package/lib/block/processVoluntaryExit.js +2 -2
  56. package/lib/block/processVoluntaryExit.js.map +1 -1
  57. package/lib/block/processWithdrawalRequest.d.ts +2 -2
  58. package/lib/block/processWithdrawalRequest.d.ts.map +1 -1
  59. package/lib/block/processWithdrawalRequest.js +1 -1
  60. package/lib/block/processWithdrawalRequest.js.map +1 -1
  61. package/lib/block/processWithdrawals.d.ts +4 -3
  62. package/lib/block/processWithdrawals.d.ts.map +1 -1
  63. package/lib/block/processWithdrawals.js +89 -19
  64. package/lib/block/processWithdrawals.js.map +1 -1
  65. package/lib/cache/epochCache.d.ts +5 -1
  66. package/lib/cache/epochCache.d.ts.map +1 -1
  67. package/lib/cache/epochCache.js +34 -1
  68. package/lib/cache/epochCache.js.map +1 -1
  69. package/lib/epoch/index.d.ts +4 -2
  70. package/lib/epoch/index.d.ts.map +1 -1
  71. package/lib/epoch/index.js +10 -1
  72. package/lib/epoch/index.js.map +1 -1
  73. package/lib/epoch/processBuilderPendingPayments.d.ts +6 -0
  74. package/lib/epoch/processBuilderPendingPayments.d.ts.map +1 -0
  75. package/lib/epoch/processBuilderPendingPayments.js +28 -0
  76. package/lib/epoch/processBuilderPendingPayments.js.map +1 -0
  77. package/lib/index.d.ts +1 -1
  78. package/lib/index.d.ts.map +1 -1
  79. package/lib/index.js.map +1 -1
  80. package/lib/signatureSets/attesterSlashings.d.ts +4 -3
  81. package/lib/signatureSets/attesterSlashings.d.ts.map +1 -1
  82. package/lib/signatureSets/attesterSlashings.js +6 -6
  83. package/lib/signatureSets/attesterSlashings.js.map +1 -1
  84. package/lib/signatureSets/index.d.ts +4 -2
  85. package/lib/signatureSets/index.d.ts.map +1 -1
  86. package/lib/signatureSets/index.js +9 -8
  87. package/lib/signatureSets/index.js.map +1 -1
  88. package/lib/signatureSets/indexedAttestation.d.ts +4 -3
  89. package/lib/signatureSets/indexedAttestation.d.ts.map +1 -1
  90. package/lib/signatureSets/indexedAttestation.js +9 -7
  91. package/lib/signatureSets/indexedAttestation.js.map +1 -1
  92. package/lib/signatureSets/indexedPayloadAttestation.d.ts +6 -0
  93. package/lib/signatureSets/indexedPayloadAttestation.d.ts.map +1 -0
  94. package/lib/signatureSets/indexedPayloadAttestation.js +11 -0
  95. package/lib/signatureSets/indexedPayloadAttestation.js.map +1 -0
  96. package/lib/signatureSets/proposer.d.ts +5 -4
  97. package/lib/signatureSets/proposer.d.ts.map +1 -1
  98. package/lib/signatureSets/proposer.js +12 -12
  99. package/lib/signatureSets/proposer.js.map +1 -1
  100. package/lib/signatureSets/proposerSlashings.d.ts +3 -2
  101. package/lib/signatureSets/proposerSlashings.d.ts.map +1 -1
  102. package/lib/signatureSets/proposerSlashings.js +4 -5
  103. package/lib/signatureSets/proposerSlashings.js.map +1 -1
  104. package/lib/signatureSets/randao.d.ts +3 -2
  105. package/lib/signatureSets/randao.d.ts.map +1 -1
  106. package/lib/signatureSets/randao.js +4 -5
  107. package/lib/signatureSets/randao.js.map +1 -1
  108. package/lib/signatureSets/voluntaryExits.d.ts +4 -3
  109. package/lib/signatureSets/voluntaryExits.d.ts.map +1 -1
  110. package/lib/signatureSets/voluntaryExits.js +6 -7
  111. package/lib/signatureSets/voluntaryExits.js.map +1 -1
  112. package/lib/slot/index.d.ts +2 -1
  113. package/lib/slot/index.d.ts.map +1 -1
  114. package/lib/slot/index.js +6 -2
  115. package/lib/slot/index.js.map +1 -1
  116. package/lib/slot/upgradeStateToAltair.js +1 -1
  117. package/lib/slot/upgradeStateToAltair.js.map +1 -1
  118. package/lib/slot/upgradeStateToGloas.d.ts +0 -1
  119. package/lib/slot/upgradeStateToGloas.d.ts.map +1 -1
  120. package/lib/slot/upgradeStateToGloas.js +47 -5
  121. package/lib/slot/upgradeStateToGloas.js.map +1 -1
  122. package/lib/stateTransition.js +5 -4
  123. package/lib/stateTransition.js.map +1 -1
  124. package/lib/util/electra.d.ts +5 -5
  125. package/lib/util/electra.d.ts.map +1 -1
  126. package/lib/util/electra.js +2 -1
  127. package/lib/util/electra.js.map +1 -1
  128. package/lib/util/epoch.d.ts +3 -3
  129. package/lib/util/epoch.d.ts.map +1 -1
  130. package/lib/util/epoch.js.map +1 -1
  131. package/lib/util/gloas.d.ts +11 -0
  132. package/lib/util/gloas.d.ts.map +1 -0
  133. package/lib/util/gloas.js +35 -0
  134. package/lib/util/gloas.js.map +1 -0
  135. package/lib/util/seed.d.ts +5 -1
  136. package/lib/util/seed.d.ts.map +1 -1
  137. package/lib/util/seed.js +33 -1
  138. package/lib/util/seed.js.map +1 -1
  139. package/lib/util/validator.d.ts +2 -2
  140. package/lib/util/validator.d.ts.map +1 -1
  141. package/lib/util/validator.js +14 -1
  142. package/lib/util/validator.js.map +1 -1
  143. package/package.json +6 -6
  144. package/src/block/index.ts +35 -14
  145. package/src/block/isValidIndexedAttestation.ts +5 -2
  146. package/src/block/isValidIndexedPayloadAttestation.ts +23 -0
  147. package/src/block/processAttestationPhase0.ts +13 -2
  148. package/src/block/processAttestationsAltair.ts +63 -6
  149. package/src/block/processAttesterSlashing.ts +6 -3
  150. package/src/block/processConsolidationRequest.ts +6 -5
  151. package/src/block/processDepositRequest.ts +5 -2
  152. package/src/block/processExecutionPayloadBid.ts +120 -0
  153. package/src/block/processExecutionPayloadEnvelope.ts +181 -0
  154. package/src/block/processOperations.ts +16 -4
  155. package/src/block/processPayloadAttestation.ts +25 -0
  156. package/src/block/processProposerSlashing.ts +25 -4
  157. package/src/block/processRandao.ts +1 -1
  158. package/src/block/processSyncCommittee.ts +4 -3
  159. package/src/block/processVoluntaryExit.ts +2 -2
  160. package/src/block/processWithdrawalRequest.ts +4 -4
  161. package/src/block/processWithdrawals.ts +118 -27
  162. package/src/cache/epochCache.ts +58 -1
  163. package/src/epoch/index.ts +12 -0
  164. package/src/epoch/processBuilderPendingPayments.ts +31 -0
  165. package/src/index.ts +2 -0
  166. package/src/signatureSets/attesterSlashings.ts +7 -3
  167. package/src/signatureSets/index.ts +12 -7
  168. package/src/signatureSets/indexedAttestation.ts +20 -9
  169. package/src/signatureSets/indexedPayloadAttestation.ts +24 -0
  170. package/src/signatureSets/proposer.ts +13 -7
  171. package/src/signatureSets/proposerSlashings.ts +5 -3
  172. package/src/signatureSets/randao.ts +13 -5
  173. package/src/signatureSets/voluntaryExits.ts +7 -4
  174. package/src/slot/index.ts +11 -3
  175. package/src/slot/upgradeStateToAltair.ts +2 -1
  176. package/src/slot/upgradeStateToGloas.ts +49 -5
  177. package/src/stateTransition.ts +5 -5
  178. package/src/util/electra.ts +15 -6
  179. package/src/util/epoch.ts +6 -3
  180. package/src/util/gloas.ts +58 -0
  181. package/src/util/seed.ts +57 -1
  182. package/src/util/validator.ts +21 -2
@@ -0,0 +1,181 @@
1
+ import {PublicKey, Signature, verify} from "@chainsafe/blst";
2
+ import {byteArrayEquals} from "@chainsafe/ssz";
3
+ import {DOMAIN_BEACON_BUILDER, SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
4
+ import {gloas, ssz} from "@lodestar/types";
5
+ import {toHex, toRootHex} from "@lodestar/utils";
6
+ import {CachedBeaconStateGloas} from "../types.ts";
7
+ import {computeExitEpochAndUpdateChurn, computeSigningRoot, computeTimeAtSlot} from "../util/index.ts";
8
+ import {processConsolidationRequest} from "./processConsolidationRequest.ts";
9
+ import {processDepositRequest} from "./processDepositRequest.ts";
10
+ import {processWithdrawalRequest} from "./processWithdrawalRequest.ts";
11
+
12
+ // This function does not call execution engine to verify payload. Need to call it from other place
13
+ export function processExecutionPayloadEnvelope(
14
+ state: CachedBeaconStateGloas,
15
+ signedEnvelope: gloas.SignedExecutionPayloadEnvelope,
16
+ verify: boolean
17
+ ): void {
18
+ const envelope = signedEnvelope.message;
19
+ const payload = envelope.payload;
20
+ const fork = state.config.getForkSeq(envelope.slot);
21
+
22
+ if (verify) {
23
+ const builderIndex = envelope.builderIndex;
24
+ const pubkey = state.validators.getReadonly(builderIndex).pubkey;
25
+
26
+ if (!verifyExecutionPayloadEnvelopeSignature(state, pubkey, signedEnvelope)) {
27
+ throw new Error("Payload Envelope has invalid signature");
28
+ }
29
+ }
30
+
31
+ validateExecutionPayloadEnvelope(state, envelope);
32
+
33
+ const requests = envelope.executionRequests;
34
+
35
+ for (const deposit of requests.deposits) {
36
+ processDepositRequest(state, deposit);
37
+ }
38
+
39
+ for (const withdrawal of requests.withdrawals) {
40
+ processWithdrawalRequest(fork, state, withdrawal);
41
+ }
42
+
43
+ for (const consolidation of requests.consolidations) {
44
+ processConsolidationRequest(fork, state, consolidation);
45
+ }
46
+
47
+ // Queue the builder payment
48
+ const paymentIndex = SLOTS_PER_EPOCH + (state.slot % SLOTS_PER_EPOCH);
49
+ const payment = state.builderPendingPayments.get(paymentIndex).clone();
50
+ const amount = payment.withdrawal.amount;
51
+
52
+ if (amount > 0) {
53
+ const exitQueueEpoch = computeExitEpochAndUpdateChurn(state, BigInt(amount));
54
+
55
+ payment.withdrawal.withdrawableEpoch = exitQueueEpoch + state.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY;
56
+ state.builderPendingWithdrawals.push(payment.withdrawal);
57
+ }
58
+
59
+ state.builderPendingPayments.set(paymentIndex, ssz.gloas.BuilderPendingPayment.defaultViewDU());
60
+
61
+ // Cache the execution payload hash
62
+ state.executionPayloadAvailability.set(state.slot % SLOTS_PER_HISTORICAL_ROOT, true);
63
+ state.latestBlockHash = payload.blockHash;
64
+
65
+ if (verify && !byteArrayEquals(envelope.stateRoot, state.hashTreeRoot())) {
66
+ throw new Error(
67
+ `Envelope's state root does not match state envelope=${toRootHex(envelope.stateRoot)} state=${toRootHex(state.hashTreeRoot())}`
68
+ );
69
+ }
70
+ }
71
+
72
+ function validateExecutionPayloadEnvelope(
73
+ state: CachedBeaconStateGloas,
74
+ envelope: gloas.ExecutionPayloadEnvelope
75
+ ): void {
76
+ const payload = envelope.payload;
77
+
78
+ if (byteArrayEquals(state.latestBlockHeader.stateRoot, ssz.Root.defaultValue())) {
79
+ const previousStateRoot = state.hashTreeRoot();
80
+ state.latestBlockHeader.stateRoot = previousStateRoot;
81
+ }
82
+
83
+ // Verify consistency with the beacon block
84
+ if (!byteArrayEquals(envelope.beaconBlockRoot, state.latestBlockHeader.hashTreeRoot())) {
85
+ throw new Error(
86
+ `Envelope's block is not the latest block header envelope=${toRootHex(envelope.beaconBlockRoot)} latestBlockHeader=${toRootHex(state.latestBlockHeader.hashTreeRoot())}`
87
+ );
88
+ }
89
+
90
+ // Verify consistency with the beacon block
91
+ if (envelope.slot !== state.slot) {
92
+ throw new Error(`Slot mismatch between envelope and state envelope=${envelope.slot} state=${state.slot}`);
93
+ }
94
+
95
+ const committedBid = state.latestExecutionPayloadBid;
96
+ // Verify consistency with the committed bid
97
+ if (envelope.builderIndex !== committedBid.builderIndex) {
98
+ throw new Error(
99
+ `Builder index mismatch between envelope and committed bid envelope=${envelope.builderIndex} committedBid=${committedBid.builderIndex}`
100
+ );
101
+ }
102
+
103
+ // Verify consistency with the committed bid
104
+ const envelopeKzgRoot = ssz.deneb.BlobKzgCommitments.hashTreeRoot(envelope.blobKzgCommitments);
105
+ if (!byteArrayEquals(committedBid.blobKzgCommitmentsRoot, envelopeKzgRoot)) {
106
+ throw new Error(
107
+ `Kzg commitment root mismatch between envelope and committed bid envelope=${toRootHex(envelopeKzgRoot)} committedBid=${toRootHex(committedBid.blobKzgCommitmentsRoot)}`
108
+ );
109
+ }
110
+
111
+ // Verify the withdrawals root
112
+ const envelopeWithdrawalsRoot = ssz.capella.Withdrawals.hashTreeRoot(envelope.payload.withdrawals);
113
+ if (!byteArrayEquals(state.latestWithdrawalsRoot, envelopeWithdrawalsRoot)) {
114
+ throw new Error(
115
+ `Withdrawals root mismatch between envelope and latest withdrawals root envelope=${toRootHex(envelopeWithdrawalsRoot)} latestWithdrawalRoot=${toRootHex(state.latestWithdrawalsRoot)}`
116
+ );
117
+ }
118
+
119
+ // Verify the gas_limit
120
+ if (Number(committedBid.gasLimit) !== payload.gasLimit) {
121
+ throw new Error(
122
+ `Gas limit mismatch between envelope's payload and committed bid envelope=${payload.gasLimit} committedBid=${Number(committedBid.gasLimit)}`
123
+ );
124
+ }
125
+
126
+ // Verify the block hash
127
+ if (!byteArrayEquals(committedBid.blockHash, payload.blockHash)) {
128
+ throw new Error(
129
+ `Block hash mismatch between envelope's payload and committed bid envelope=${toRootHex(payload.blockHash)} committedBid=${toRootHex(committedBid.blockHash)}`
130
+ );
131
+ }
132
+
133
+ // Verify consistency of the parent hash with respect to the previous execution payload
134
+ if (!byteArrayEquals(payload.parentHash, state.latestBlockHash)) {
135
+ throw new Error(
136
+ `Parent hash mismatch between envelope's payload and state envelope=${toRootHex(payload.parentHash)} state=${toRootHex(state.latestBlockHash)}`
137
+ );
138
+ }
139
+
140
+ // Verify prev_randao matches committed bid
141
+ if (!byteArrayEquals(committedBid.prevRandao, payload.prevRandao)) {
142
+ throw new Error(
143
+ `Prev randao mismatch between committed bid and payload committedBid=${toHex(committedBid.prevRandao)} payload=${toHex(payload.prevRandao)}`
144
+ );
145
+ }
146
+
147
+ // Verify timestamp
148
+ if (payload.timestamp !== computeTimeAtSlot(state.config, state.slot, state.genesisTime)) {
149
+ throw new Error(
150
+ `Timestamp mismatch between envelope's payload and state envelope=${payload.timestamp} state=${computeTimeAtSlot(state.config, state.slot, state.genesisTime)}`
151
+ );
152
+ }
153
+
154
+ // Verify commitments are under limit
155
+ const maxBlobsPerBlock = state.config.getMaxBlobsPerBlock(state.epochCtx.epoch);
156
+ if (envelope.blobKzgCommitments.length > maxBlobsPerBlock) {
157
+ throw new Error(
158
+ `Kzg commitments exceed limit commitment.length=${envelope.blobKzgCommitments.length} limit=${maxBlobsPerBlock}`
159
+ );
160
+ }
161
+
162
+ // Skipped: Verify the execution payload is valid
163
+ }
164
+
165
+ function verifyExecutionPayloadEnvelopeSignature(
166
+ state: CachedBeaconStateGloas,
167
+ pubkey: Uint8Array,
168
+ signedEnvelope: gloas.SignedExecutionPayloadEnvelope
169
+ ): boolean {
170
+ const domain = state.config.getDomain(state.slot, DOMAIN_BEACON_BUILDER);
171
+ const signingRoot = computeSigningRoot(ssz.gloas.ExecutionPayloadEnvelope, signedEnvelope.message, domain);
172
+
173
+ try {
174
+ const publicKey = PublicKey.fromBytes(pubkey);
175
+ const signature = Signature.fromBytes(signedEnvelope.signature, true);
176
+
177
+ return verify(signingRoot, publicKey, signature);
178
+ } catch (_e) {
179
+ return false; // Catch all BLS errors: failed key validation, failed signature validation, invalid signature
180
+ }
181
+ }
@@ -1,7 +1,12 @@
1
1
  import {ForkSeq} from "@lodestar/params";
2
- import {BeaconBlockBody, capella, electra} from "@lodestar/types";
2
+ import {BeaconBlockBody, capella, electra, gloas} from "@lodestar/types";
3
3
  import {BeaconStateTransitionMetrics} from "../metrics.js";
4
- import {CachedBeaconStateAllForks, CachedBeaconStateCapella, CachedBeaconStateElectra} from "../types.js";
4
+ import {
5
+ CachedBeaconStateAllForks,
6
+ CachedBeaconStateCapella,
7
+ CachedBeaconStateElectra,
8
+ CachedBeaconStateGloas,
9
+ } from "../types.js";
5
10
  import {getEth1DepositCount} from "../util/deposit.js";
6
11
  import {processAttestations} from "./processAttestations.js";
7
12
  import {processAttesterSlashing} from "./processAttesterSlashing.js";
@@ -9,6 +14,7 @@ import {processBlsToExecutionChange} from "./processBlsToExecutionChange.js";
9
14
  import {processConsolidationRequest} from "./processConsolidationRequest.js";
10
15
  import {processDeposit} from "./processDeposit.js";
11
16
  import {processDepositRequest} from "./processDepositRequest.js";
17
+ import {processPayloadAttestation} from "./processPayloadAttestation.ts";
12
18
  import {processProposerSlashing} from "./processProposerSlashing.js";
13
19
  import {processVoluntaryExit} from "./processVoluntaryExit.js";
14
20
  import {processWithdrawalRequest} from "./processWithdrawalRequest.js";
@@ -64,7 +70,7 @@ export function processOperations(
64
70
  }
65
71
  }
66
72
 
67
- if (fork >= ForkSeq.electra) {
73
+ if (fork >= ForkSeq.electra && fork < ForkSeq.gloas) {
68
74
  const stateElectra = state as CachedBeaconStateElectra;
69
75
  const bodyElectra = body as electra.BeaconBlockBody;
70
76
 
@@ -77,7 +83,13 @@ export function processOperations(
77
83
  }
78
84
 
79
85
  for (const elConsolidationRequest of bodyElectra.executionRequests.consolidations) {
80
- processConsolidationRequest(stateElectra, elConsolidationRequest);
86
+ processConsolidationRequest(fork, stateElectra, elConsolidationRequest);
87
+ }
88
+ }
89
+
90
+ if (fork >= ForkSeq.gloas) {
91
+ for (const payloadAttestation of (body as gloas.BeaconBlockBody).payloadAttestations) {
92
+ processPayloadAttestation(state as CachedBeaconStateGloas, payloadAttestation);
81
93
  }
82
94
  }
83
95
  }
@@ -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
 
@@ -56,7 +77,7 @@ export function assertValidProposerSlashing(
56
77
 
57
78
  // verify signatures
58
79
  if (verifySignatures) {
59
- const signatureSets = getProposerSlashingSignatureSets(state, proposerSlashing);
80
+ const signatureSets = getProposerSlashingSignatureSets(state.epochCtx.index2pubkey, state, proposerSlashing);
60
81
  for (let i = 0; i < signatureSets.length; i++) {
61
82
  if (!verifySignatureSet(signatureSets[i])) {
62
83
  throw new Error(`ProposerSlashing header${i + 1} signature invalid`);
@@ -17,7 +17,7 @@ export function processRandao(state: CachedBeaconStateAllForks, block: BeaconBlo
17
17
  const randaoReveal = block.body.randaoReveal;
18
18
 
19
19
  // verify RANDAO reveal
20
- if (verifySignature && !verifyRandaoSignature(state, block)) {
20
+ if (verifySignature && !verifyRandaoSignature(epochCtx.index2pubkey, state, block)) {
21
21
  throw new Error("RANDAO reveal is an invalid signature");
22
22
  }
23
23
 
@@ -1,6 +1,7 @@
1
1
  import {byteArrayEquals} from "@chainsafe/ssz";
2
2
  import {DOMAIN_SYNC_COMMITTEE, SYNC_COMMITTEE_SIZE} from "@lodestar/params";
3
3
  import {altair, ssz} from "@lodestar/types";
4
+ import {Index2PubkeyCache} from "../cache/pubkeyCache.js";
4
5
  import {G2_POINT_AT_INFINITY} from "../constants/index.js";
5
6
  import {CachedBeaconStateAllForks} from "../types.js";
6
7
  import {
@@ -23,7 +24,7 @@ export function processSyncAggregate(
23
24
  if (verifySignatures) {
24
25
  // This is to conform to the spec - we want the signature to be verified
25
26
  const participantIndices = block.body.syncAggregate.syncCommitteeBits.intersectValues(committeeIndices);
26
- const signatureSet = getSyncCommitteeSignatureSet(state, block, participantIndices);
27
+ const signatureSet = getSyncCommitteeSignatureSet(state.epochCtx.index2pubkey, state, block, participantIndices);
27
28
  // When there's no participation we consider the signature valid and just ignore i
28
29
  if (signatureSet !== null && !verifySignatureSet(signatureSet)) {
29
30
  throw Error("Sync committee signature invalid");
@@ -63,12 +64,12 @@ export function processSyncAggregate(
63
64
  }
64
65
 
65
66
  export function getSyncCommitteeSignatureSet(
67
+ index2pubkey: Index2PubkeyCache,
66
68
  state: CachedBeaconStateAllForks,
67
69
  block: altair.BeaconBlock,
68
70
  /** Optional parameter to prevent computing it twice */
69
71
  participantIndices?: number[]
70
72
  ): ISignatureSet | null {
71
- const {epochCtx} = state;
72
73
  const {syncAggregate} = block.body;
73
74
  const signature = syncAggregate.syncCommitteeSignature;
74
75
 
@@ -110,7 +111,7 @@ export function getSyncCommitteeSignatureSet(
110
111
 
111
112
  return {
112
113
  type: SignatureSetType.aggregate,
113
- pubkeys: participantIndices.map((i) => epochCtx.index2pubkey[i]),
114
+ pubkeys: participantIndices.map((i) => index2pubkey[i]),
114
115
  signingRoot: computeSigningRoot(ssz.Root, rootSigned, domain),
115
116
  signature,
116
117
  };
@@ -69,12 +69,12 @@ 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
  }
76
76
 
77
- if (verifySignature && !verifyVoluntaryExitSignature(state, signedVoluntaryExit)) {
77
+ if (verifySignature && !verifyVoluntaryExitSignature(epochCtx.index2pubkey, state, signedVoluntaryExit)) {
78
78
  return VoluntaryExitValidity.invalidSignature;
79
79
  }
80
80
 
@@ -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
  }