@lodestar/beacon-node 1.43.0-dev.e341cdc614 → 1.43.0-dev.e5b13221e5

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 (72) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +1 -4
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/validator/index.d.ts.map +1 -1
  5. package/lib/api/impl/validator/index.js +0 -4
  6. package/lib/api/impl/validator/index.js.map +1 -1
  7. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  8. package/lib/chain/blocks/importExecutionPayload.js +9 -18
  9. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  10. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  11. package/lib/chain/forkChoice/index.js +6 -2
  12. package/lib/chain/forkChoice/index.js.map +1 -1
  13. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  14. package/lib/chain/prepareNextSlot.js +20 -10
  15. package/lib/chain/prepareNextSlot.js.map +1 -1
  16. package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -2
  17. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  18. package/lib/chain/produceBlock/produceBlockBody.js +29 -19
  19. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  20. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  21. package/lib/chain/stateCache/persistentCheckpointsCache.js +4 -1
  22. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  23. package/lib/chain/validation/executionPayloadEnvelope.js +8 -8
  24. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  25. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  26. package/lib/chain/validation/payloadAttestationMessage.js +4 -3
  27. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  28. package/lib/db/repositories/executionPayloadEnvelopeArchive.js +1 -1
  29. package/lib/db/repositories/executionPayloadEnvelopeArchive.js.map +1 -1
  30. package/lib/execution/engine/http.d.ts.map +1 -1
  31. package/lib/execution/engine/http.js +21 -14
  32. package/lib/execution/engine/http.js.map +1 -1
  33. package/lib/execution/engine/interface.d.ts +1 -0
  34. package/lib/execution/engine/interface.d.ts.map +1 -1
  35. package/lib/execution/engine/mock.d.ts.map +1 -1
  36. package/lib/execution/engine/mock.js +6 -0
  37. package/lib/execution/engine/mock.js.map +1 -1
  38. package/lib/execution/engine/types.d.ts +20 -0
  39. package/lib/execution/engine/types.d.ts.map +1 -1
  40. package/lib/execution/engine/types.js +18 -0
  41. package/lib/execution/engine/types.js.map +1 -1
  42. package/lib/network/gossip/topic.d.ts +750 -2
  43. package/lib/network/gossip/topic.d.ts.map +1 -1
  44. package/lib/network/network.js +1 -1
  45. package/lib/network/network.js.map +1 -1
  46. package/lib/network/processor/gossipHandlers.js +3 -3
  47. package/lib/network/processor/gossipHandlers.js.map +1 -1
  48. package/lib/node/nodejs.d.ts.map +1 -1
  49. package/lib/node/nodejs.js +4 -2
  50. package/lib/node/nodejs.js.map +1 -1
  51. package/lib/util/sszBytes.d.ts.map +1 -1
  52. package/lib/util/sszBytes.js +16 -3
  53. package/lib/util/sszBytes.js.map +1 -1
  54. package/package.json +16 -16
  55. package/src/api/impl/beacon/blocks/index.ts +1 -4
  56. package/src/api/impl/validator/index.ts +0 -4
  57. package/src/chain/blocks/importExecutionPayload.ts +9 -19
  58. package/src/chain/forkChoice/index.ts +6 -2
  59. package/src/chain/prepareNextSlot.ts +23 -10
  60. package/src/chain/produceBlock/produceBlockBody.ts +40 -14
  61. package/src/chain/stateCache/persistentCheckpointsCache.ts +4 -1
  62. package/src/chain/validation/executionPayloadEnvelope.ts +8 -8
  63. package/src/chain/validation/payloadAttestationMessage.ts +5 -3
  64. package/src/db/repositories/executionPayloadEnvelopeArchive.ts +1 -1
  65. package/src/execution/engine/http.ts +21 -14
  66. package/src/execution/engine/interface.ts +1 -0
  67. package/src/execution/engine/mock.ts +8 -1
  68. package/src/execution/engine/types.ts +41 -0
  69. package/src/network/network.ts +1 -1
  70. package/src/network/processor/gossipHandlers.ts +3 -3
  71. package/src/node/nodejs.ts +4 -2
  72. package/src/util/sszBytes.ts +21 -3
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "bugs": {
12
12
  "url": "https://github.com/ChainSafe/lodestar/issues"
13
13
  },
14
- "version": "1.43.0-dev.e341cdc614",
14
+ "version": "1.43.0-dev.e5b13221e5",
15
15
  "type": "module",
16
16
  "exports": {
17
17
  ".": {
@@ -118,7 +118,7 @@
118
118
  "@chainsafe/prometheus-gc-stats": "^1.0.0",
119
119
  "@chainsafe/pubkey-index-map": "^3.0.0",
120
120
  "@chainsafe/snappy-wasm": "^0.5.0",
121
- "@chainsafe/ssz": "^1.2.2",
121
+ "@chainsafe/ssz": "^1.4.0",
122
122
  "@chainsafe/threads": "^1.11.3",
123
123
  "@crate-crypto/node-eth-kzg": "0.9.1",
124
124
  "@fastify/bearer-auth": "^10.0.1",
@@ -135,18 +135,18 @@
135
135
  "@libp2p/peer-id": "^6.0.4",
136
136
  "@libp2p/prometheus-metrics": "^5.0.14",
137
137
  "@libp2p/tcp": "^11.0.13",
138
- "@lodestar/api": "^1.43.0-dev.e341cdc614",
139
- "@lodestar/config": "^1.43.0-dev.e341cdc614",
140
- "@lodestar/db": "^1.43.0-dev.e341cdc614",
141
- "@lodestar/fork-choice": "^1.43.0-dev.e341cdc614",
142
- "@lodestar/light-client": "^1.43.0-dev.e341cdc614",
143
- "@lodestar/logger": "^1.43.0-dev.e341cdc614",
144
- "@lodestar/params": "^1.43.0-dev.e341cdc614",
145
- "@lodestar/reqresp": "^1.43.0-dev.e341cdc614",
146
- "@lodestar/state-transition": "^1.43.0-dev.e341cdc614",
147
- "@lodestar/types": "^1.43.0-dev.e341cdc614",
148
- "@lodestar/utils": "^1.43.0-dev.e341cdc614",
149
- "@lodestar/validator": "^1.43.0-dev.e341cdc614",
138
+ "@lodestar/api": "^1.43.0-dev.e5b13221e5",
139
+ "@lodestar/config": "^1.43.0-dev.e5b13221e5",
140
+ "@lodestar/db": "^1.43.0-dev.e5b13221e5",
141
+ "@lodestar/fork-choice": "^1.43.0-dev.e5b13221e5",
142
+ "@lodestar/light-client": "^1.43.0-dev.e5b13221e5",
143
+ "@lodestar/logger": "^1.43.0-dev.e5b13221e5",
144
+ "@lodestar/params": "^1.43.0-dev.e5b13221e5",
145
+ "@lodestar/reqresp": "^1.43.0-dev.e5b13221e5",
146
+ "@lodestar/state-transition": "^1.43.0-dev.e5b13221e5",
147
+ "@lodestar/types": "^1.43.0-dev.e5b13221e5",
148
+ "@lodestar/utils": "^1.43.0-dev.e5b13221e5",
149
+ "@lodestar/validator": "^1.43.0-dev.e5b13221e5",
150
150
  "@multiformats/multiaddr": "^13.0.1",
151
151
  "datastore-core": "^11.0.2",
152
152
  "datastore-fs": "^11.0.2",
@@ -169,7 +169,7 @@
169
169
  "@libp2p/interface-internal": "^3.0.13",
170
170
  "@libp2p/logger": "^6.2.2",
171
171
  "@libp2p/utils": "^7.0.13",
172
- "@lodestar/spec-test-util": "^1.43.0-dev.e341cdc614",
172
+ "@lodestar/spec-test-util": "^1.43.0-dev.e5b13221e5",
173
173
  "@types/js-yaml": "^4.0.5",
174
174
  "@types/qs": "^6.9.7",
175
175
  "@types/tmp": "^0.2.3",
@@ -186,5 +186,5 @@
186
186
  "beacon",
187
187
  "blockchain"
188
188
  ],
189
- "gitHead": "befc1d1fc43563f7092cc69715eb472fdc5701b6"
189
+ "gitHead": "52a0608b862b2938272884c58061fa9da66b0b11"
190
190
  }
@@ -651,11 +651,10 @@ export function getBeaconBlockApi({
651
651
  async publishExecutionPayloadEnvelope({signedExecutionPayloadEnvelope}) {
652
652
  const seenTimestampSec = Date.now() / 1000;
653
653
  const envelope = signedExecutionPayloadEnvelope.message;
654
- const slot = envelope.slot;
654
+ const slot = envelope.payload.slotNumber;
655
655
  const fork = config.getForkName(slot);
656
656
  const blockRootHex = toRootHex(envelope.beaconBlockRoot);
657
657
  const blockHashHex = toRootHex(envelope.payload.blockHash);
658
- const stateRootHex = toRootHex(envelope.stateRoot);
659
658
 
660
659
  if (!isForkPostGloas(fork)) {
661
660
  throw new ApiError(400, `publishExecutionPayloadEnvelope not supported for pre-gloas fork=${fork}`);
@@ -740,7 +739,6 @@ export function getBeaconBlockApi({
740
739
  slot,
741
740
  blockRoot: blockRootHex,
742
741
  blockHash: blockHashHex,
743
- stateRoot: stateRootHex,
744
742
  builderIndex: envelope.builderIndex,
745
743
  isSelfBuild,
746
744
  dataColumns: dataColumnSidecars.length,
@@ -768,7 +766,6 @@ export function getBeaconBlockApi({
768
766
  builderIndex: envelope.builderIndex,
769
767
  blockHash: blockHashHex,
770
768
  blockRoot: blockRootHex,
771
- stateRoot: stateRootHex,
772
769
  });
773
770
 
774
771
  const sentPeersArr = await publishPromise;
@@ -1648,10 +1648,6 @@ export function getValidatorApi(
1648
1648
  executionRequests: executionRequests,
1649
1649
  builderIndex: BUILDER_INDEX_SELF_BUILD,
1650
1650
  beaconBlockRoot,
1651
- slot,
1652
- // TODO GLOAS: stateRoot is no longer computed during block production.
1653
- // This field will be removed when we implement defer payload processing
1654
- stateRoot: ZERO_HASH,
1655
1651
  };
1656
1652
 
1657
1653
  logger.info("Produced execution payload envelope", {
@@ -2,7 +2,7 @@ import {routes} from "@lodestar/api";
2
2
  import {ExecutionStatus, PayloadExecutionStatus} from "@lodestar/fork-choice";
3
3
  import {SLOTS_PER_EPOCH} from "@lodestar/params";
4
4
  import {getExecutionPayloadEnvelopeSignatureSet, isStatePostGloas} from "@lodestar/state-transition";
5
- import {byteArrayEquals, fromHex, toRootHex} from "@lodestar/utils";
5
+ import {fromHex, toRootHex} from "@lodestar/utils";
6
6
  import {ExecutionPayloadStatus} from "../../execution/index.js";
7
7
  import {isQueueErrorAborted} from "../../util/queue/index.js";
8
8
  import {BeaconChain} from "../chain.js";
@@ -92,15 +92,15 @@ export async function importExecutionPayload(
92
92
  const envelope = signedEnvelope.message;
93
93
  const blockRootHex = payloadInput.blockRootHex;
94
94
  const blockHashHex = payloadInput.getBlockHashHex();
95
- const fork = this.config.getForkName(envelope.slot);
95
+ const fork = this.config.getForkName(envelope.payload.slotNumber);
96
96
 
97
97
  // 1. Emit `execution_payload_available` event at the start of import. At this point the payload input
98
98
  // is already complete, so the payload and required data are available for payload attestation.
99
99
  // This event is only about availability, not validity of the execution payload, hence we can emit
100
100
  // it before getting a response from the execution client on whether the payload is valid or not.
101
- if (this.clock.currentSlot - envelope.slot < EVENTSTREAM_EMIT_RECENT_EXECUTION_PAYLOAD_SLOTS) {
101
+ if (this.clock.currentSlot - envelope.payload.slotNumber < EVENTSTREAM_EMIT_RECENT_EXECUTION_PAYLOAD_SLOTS) {
102
102
  this.emitter.emit(routes.events.EventType.executionPayloadAvailable, {
103
- slot: envelope.slot,
103
+ slot: envelope.payload.slotNumber,
104
104
  blockRoot: blockRootHex,
105
105
  });
106
106
  }
@@ -213,22 +213,16 @@ export async function importExecutionPayload(
213
213
  });
214
214
  }
215
215
 
216
- // 5c. Verify envelope state root matches post-state
216
+ // 5c. Compute post-payload state root
217
217
  const postPayloadState = postPayloadResult.postPayloadState;
218
218
  const postPayloadStateRoot = postPayloadState.hashTreeRoot();
219
- if (!byteArrayEquals(envelope.stateRoot, postPayloadStateRoot)) {
220
- throw new PayloadError({
221
- code: PayloadErrorCode.STATE_TRANSITION_ERROR,
222
- message: `Envelope state root mismatch expected=${toRootHex(envelope.stateRoot)} actual=${toRootHex(postPayloadStateRoot)}`,
223
- });
224
- }
225
219
 
226
220
  // 6. Persist payload envelope to hot DB (performed asynchronously to avoid blocking)
227
221
  this.unfinalizedPayloadEnvelopeWrites.push(payloadInput).catch((e) => {
228
222
  if (!isQueueErrorAborted(e)) {
229
223
  this.logger.error(
230
224
  "Error pushing payload envelope to unfinalized write queue",
231
- {slot: envelope.slot, blockRoot: blockRootHex},
225
+ {slot: envelope.payload.slotNumber, blockRoot: blockRootHex},
232
226
  e as Error
233
227
  );
234
228
  }
@@ -256,26 +250,22 @@ export async function importExecutionPayload(
256
250
  this.metrics?.importPayload.columnsBySource.inc({source});
257
251
  }
258
252
 
259
- const stateRootHex = toRootHex(envelope.stateRoot);
260
-
261
253
  // 10. Emit event after payload is fully verified and imported to fork choice, only for recent enough payloads
262
- if (this.clock.currentSlot - envelope.slot < EVENTSTREAM_EMIT_RECENT_EXECUTION_PAYLOAD_SLOTS) {
254
+ if (this.clock.currentSlot - envelope.payload.slotNumber < EVENTSTREAM_EMIT_RECENT_EXECUTION_PAYLOAD_SLOTS) {
263
255
  this.emitter.emit(routes.events.EventType.executionPayload, {
264
- slot: envelope.slot,
256
+ slot: envelope.payload.slotNumber,
265
257
  builderIndex: envelope.builderIndex,
266
258
  blockHash: blockHashHex,
267
259
  blockRoot: blockRootHex,
268
- stateRoot: stateRootHex,
269
260
  // TODO GLOAS: revisit once we support optimistic import
270
261
  executionOptimistic: false,
271
262
  });
272
263
  }
273
264
 
274
265
  this.logger.verbose("Execution payload imported", {
275
- slot: envelope.slot,
266
+ slot: envelope.payload.slotNumber,
276
267
  builderIndex: envelope.builderIndex,
277
268
  blockRoot: blockRootHex,
278
269
  blockHash: blockHashHex,
279
- stateRoot: stateRootHex,
280
270
  });
281
271
  }
@@ -150,7 +150,9 @@ export function initializeForkChoiceFromFinalizedState(
150
150
 
151
151
  ...(isStatePostBellatrix(state) && state.isExecutionStateType && state.isMergeTransitionComplete
152
152
  ? {
153
- executionPayloadBlockHash: toRootHex(state.latestBlockHash),
153
+ executionPayloadBlockHash: isStatePostGloas(state)
154
+ ? toRootHex(state.latestBlockHash)
155
+ : toRootHex(state.latestExecutionPayloadHeader.blockHash),
154
156
  // TODO GLOAS: executionPayloadNumber is not tracked in BeaconState post-gloas (EIP-7732 removed
155
157
  // latestExecutionPayloadHeader). Using 0 as unavailable fallback until a solution is found.
156
158
  executionPayloadNumber: isStatePostGloas(state) ? 0 : state.payloadBlockNumber,
@@ -247,7 +249,9 @@ export function initializeForkChoiceFromUnfinalizedState(
247
249
  unfinalizedState.isExecutionStateType &&
248
250
  unfinalizedState.isMergeTransitionComplete
249
251
  ? {
250
- executionPayloadBlockHash: toRootHex(unfinalizedState.latestBlockHash),
252
+ executionPayloadBlockHash: isStatePostGloas(unfinalizedState)
253
+ ? toRootHex(unfinalizedState.latestBlockHash)
254
+ : toRootHex(unfinalizedState.latestExecutionPayloadHeader.blockHash),
251
255
  // TODO GLOAS: executionPayloadNumber is not tracked in BeaconState post-gloas (EIP-7732 removed
252
256
  // latestExecutionPayloadHeader). Using 0 as unavailable fallback until a solution is found.
253
257
  executionPayloadNumber: isStatePostGloas(unfinalizedState) ? 0 : unfinalizedState.payloadBlockNumber,
@@ -8,8 +8,9 @@ import {
8
8
  computeEpochAtSlot,
9
9
  computeTimeAtSlot,
10
10
  isStatePostBellatrix,
11
+ isStatePostGloas,
11
12
  } from "@lodestar/state-transition";
12
- import {Slot} from "@lodestar/types";
13
+ import {Bytes32, Slot} from "@lodestar/types";
13
14
  import {Logger, fromHex, isErrorAborted, sleep} from "@lodestar/utils";
14
15
  import {GENESIS_SLOT, ZERO_HASH_HEX} from "../constants/constants.js";
15
16
  import {BuilderStatus} from "../execution/builder/http.js";
@@ -81,6 +82,8 @@ export class PrepareNextSlotScheduler {
81
82
  // calling updateHead() here before we produce a block to reduce reorg possibility
82
83
  const headBlock = this.chain.recomputeForkChoiceHead(ForkchoiceCaller.prepareNextSlot);
83
84
  const {slot: headSlot, blockRoot: headRoot} = headBlock;
85
+ // may be updated below if we predict a proposer-boost-reorg
86
+ let updatedHeadRoot = headRoot;
84
87
 
85
88
  // PS: previously this was comparing slots, but that gave no leway on the skipped
86
89
  // slots on epoch bounday. Making it more fluid.
@@ -123,7 +126,6 @@ export class PrepareNextSlotScheduler {
123
126
  const proposerIndex = prepareState.getBeaconProposer(prepareSlot);
124
127
  const feeRecipient = this.chain.beaconProposerCache.get(proposerIndex);
125
128
  let updatedPrepareState = prepareState;
126
- let updatedHeadRoot = headRoot;
127
129
 
128
130
  if (feeRecipient) {
129
131
  // If we are proposing next slot, we need to predict if we can proposer-boost-reorg or not
@@ -156,25 +158,39 @@ export class PrepareNextSlotScheduler {
156
158
  this.logger.error("Builder disabled as the check status api failed", {prepareSlot}, e as Error);
157
159
  });
158
160
  }
161
+ }
162
+
163
+ if (!isStatePostBellatrix(updatedPrepareState)) {
164
+ throw new Error("Expected Bellatrix state for payload attributes");
165
+ }
166
+
167
+ let parentBlockHash: Bytes32;
168
+ if (isStatePostGloas(updatedPrepareState)) {
169
+ parentBlockHash = this.chain.forkChoice.shouldExtendPayload(updatedHeadRoot)
170
+ ? updatedPrepareState.latestExecutionPayloadBid.blockHash
171
+ : updatedPrepareState.latestExecutionPayloadBid.parentBlockHash;
172
+ } else {
173
+ parentBlockHash = updatedPrepareState.latestExecutionPayloadHeader.blockHash;
174
+ }
159
175
 
176
+ if (feeRecipient) {
160
177
  const preparationTime =
161
178
  computeTimeAtSlot(this.config, prepareSlot, this.chain.genesisTime) - Date.now() / 1000;
162
179
  this.metrics?.blockPayload.payloadAdvancePrepTime.observe(preparationTime);
163
- if (!isStatePostBellatrix(updatedPrepareState)) {
164
- throw new Error("Expected Bellatrix state for payload preparation");
165
- }
166
180
 
167
181
  const safeBlockHash = getSafeExecutionBlockHash(this.chain.forkChoice);
168
182
  const finalizedBlockHash =
169
183
  this.chain.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
184
+
170
185
  // awaiting here instead of throwing an async call because there is no other task
171
- // left for scheduler and this gives nice sematics to catch and log errors in the
186
+ // left for scheduler and this gives nice semantics to catch and log errors in the
172
187
  // try/catch wrapper here.
173
188
  await prepareExecutionPayload(
174
189
  this.chain,
175
190
  this.logger,
176
191
  fork as ForkPostBellatrix, // State is of execution type
177
192
  fromHex(updatedHeadRoot),
193
+ parentBlockHash,
178
194
  safeBlockHash,
179
195
  finalizedBlockHash,
180
196
  updatedPrepareState,
@@ -187,10 +203,6 @@ export class PrepareNextSlotScheduler {
187
203
  });
188
204
  }
189
205
 
190
- if (!isStatePostBellatrix(updatedPrepareState)) {
191
- throw new Error("Expected Bellatrix state for payload attributes");
192
- }
193
-
194
206
  this.computeStateHashTreeRoot(updatedPrepareState, isEpochTransition);
195
207
 
196
208
  // If emitPayloadAttributes is true emit a SSE payloadAttributes event
@@ -202,6 +214,7 @@ export class PrepareNextSlotScheduler {
202
214
  prepareState: updatedPrepareState,
203
215
  prepareSlot,
204
216
  parentBlockRoot: fromHex(headRoot),
217
+ parentBlockHash,
205
218
  // The likely consumers of this API are builders and will anyway ignore the
206
219
  // feeRecipient, so just pass zero hash for now till a real use case arises
207
220
  feeRecipient: "0x0000000000000000000000000000000000000000000000000000000000000000",
@@ -19,7 +19,6 @@ import {
19
19
  IBeaconStateView,
20
20
  type IBeaconStateViewBellatrix,
21
21
  computeTimeAtSlot,
22
- isParentBlockFull,
23
22
  isStatePostBellatrix,
24
23
  isStatePostCapella,
25
24
  isStatePostGloas,
@@ -47,8 +46,9 @@ import {
47
46
  electra,
48
47
  fulu,
49
48
  gloas,
49
+ ssz,
50
50
  } from "@lodestar/types";
51
- import {Logger, fromHex, sleep, toHex, toPubkeyHex, toRootHex} from "@lodestar/utils";
51
+ import {Logger, byteArrayEquals, fromHex, sleep, toHex, toPubkeyHex, toRootHex} from "@lodestar/utils";
52
52
  import {ZERO_HASH_HEX} from "../../constants/index.js";
53
53
  import {numToQuantity} from "../../execution/engine/utils.js";
54
54
  import {
@@ -214,11 +214,15 @@ export async function produceBlockBody<T extends BlockType>(
214
214
  });
215
215
 
216
216
  // Get execution payload from EL
217
+ const parentBlockHash = this.forkChoice.shouldExtendPayload(toRootHex(parentBlockRoot))
218
+ ? currentState.latestExecutionPayloadBid.blockHash
219
+ : currentState.latestExecutionPayloadBid.parentBlockHash;
217
220
  const prepareRes = await prepareExecutionPayload(
218
221
  this,
219
222
  this.logger,
220
223
  fork,
221
224
  parentBlockRoot,
225
+ parentBlockHash,
222
226
  safeBlockHash,
223
227
  finalizedBlockHash ?? ZERO_HASH_HEX,
224
228
  currentState,
@@ -255,8 +259,8 @@ export async function produceBlockBody<T extends BlockType>(
255
259
 
256
260
  // Create self-build execution payload bid
257
261
  const bid: gloas.ExecutionPayloadBid = {
258
- parentBlockHash: currentState.latestBlockHash,
259
- parentBlockRoot: parentBlockRoot,
262
+ parentBlockHash,
263
+ parentBlockRoot,
260
264
  blockHash: executionPayload.blockHash,
261
265
  prevRandao: currentState.getRandaoMix(currentState.epoch),
262
266
  feeRecipient: executionPayload.feeRecipient,
@@ -266,6 +270,7 @@ export async function produceBlockBody<T extends BlockType>(
266
270
  value: 0,
267
271
  executionPayment: 0,
268
272
  blobKzgCommitments: blobsBundle.commitments,
273
+ executionRequestsRoot: ssz.electra.ExecutionRequests.hashTreeRoot(executionRequests),
269
274
  };
270
275
  const signedBid: gloas.SignedExecutionPayloadBid = {
271
276
  message: bid,
@@ -334,6 +339,7 @@ export async function produceBlockBody<T extends BlockType>(
334
339
  this.logger,
335
340
  fork,
336
341
  parentBlockRoot,
342
+ currentState.latestExecutionPayloadHeader.blockHash,
337
343
  safeBlockHash,
338
344
  finalizedBlockHash ?? ZERO_HASH_HEX,
339
345
  currentState,
@@ -442,6 +448,7 @@ export async function produceBlockBody<T extends BlockType>(
442
448
  this.logger,
443
449
  fork,
444
450
  parentBlockRoot,
451
+ currentState.latestExecutionPayloadHeader.blockHash,
445
452
  safeBlockHash,
446
453
  finalizedBlockHash ?? ZERO_HASH_HEX,
447
454
  currentState,
@@ -607,17 +614,17 @@ export async function prepareExecutionPayload(
607
614
  logger: Logger,
608
615
  fork: ForkPostBellatrix,
609
616
  parentBlockRoot: Root,
617
+ parentBlockHash: Bytes32,
610
618
  safeBlockHash: RootHex,
611
619
  finalizedBlockHash: RootHex,
612
620
  state: IBeaconStateViewBellatrix,
613
621
  suggestedFeeRecipient: string
614
622
  ): Promise<{prepType: PayloadPreparationType; payloadId: PayloadId}> {
615
- const parentHash = state.latestBlockHash;
616
623
  const timestamp = computeTimeAtSlot(chain.config, state.slot, state.genesisTime);
617
624
  const prevRandao = state.getRandaoMix(state.epoch);
618
625
 
619
626
  const payloadIdCached = chain.executionEngine.payloadIdCache.get({
620
- headBlockHash: toRootHex(parentHash),
627
+ headBlockHash: toRootHex(parentBlockHash),
621
628
  finalizedBlockHash,
622
629
  timestamp: numToQuantity(timestamp),
623
630
  prevRandao: toHex(prevRandao),
@@ -646,12 +653,13 @@ export async function prepareExecutionPayload(
646
653
  prepareState: state,
647
654
  prepareSlot: state.slot,
648
655
  parentBlockRoot,
656
+ parentBlockHash,
649
657
  feeRecipient: suggestedFeeRecipient,
650
658
  });
651
659
 
652
660
  payloadId = await chain.executionEngine.notifyForkchoiceUpdate(
653
661
  fork,
654
- toRootHex(parentHash),
662
+ toRootHex(parentBlockHash),
655
663
  safeBlockHash,
656
664
  finalizedBlockHash,
657
665
  attributes
@@ -703,20 +711,30 @@ export function getPayloadAttributesForSSE(
703
711
  prepareState,
704
712
  prepareSlot,
705
713
  parentBlockRoot,
714
+ parentBlockHash,
706
715
  feeRecipient,
707
- }: {prepareState: IBeaconStateViewBellatrix; prepareSlot: Slot; parentBlockRoot: Root; feeRecipient: string}
716
+ }: {
717
+ prepareState: IBeaconStateViewBellatrix;
718
+ prepareSlot: Slot;
719
+ parentBlockRoot: Root;
720
+ parentBlockHash: Bytes32;
721
+ feeRecipient: string;
722
+ }
708
723
  ): SSEPayloadAttributes {
709
- const parentHash = prepareState.latestBlockHash;
710
724
  const payloadAttributes = preparePayloadAttributes(fork, chain, {
711
725
  prepareState,
712
726
  prepareSlot,
713
727
  parentBlockRoot,
728
+ parentBlockHash,
714
729
  feeRecipient,
715
730
  });
716
731
 
717
732
  let parentBlockNumber: number;
718
733
  if (isForkPostGloas(fork)) {
719
- const parentBlock = chain.forkChoice.getBlockHexAndBlockHash(toRootHex(parentBlockRoot), toRootHex(parentHash));
734
+ const parentBlock = chain.forkChoice.getBlockHexAndBlockHash(
735
+ toRootHex(parentBlockRoot),
736
+ toRootHex(parentBlockHash)
737
+ );
720
738
  if (parentBlock?.executionPayloadBlockHash == null) {
721
739
  throw Error(`Parent block not found in fork choice root=${toRootHex(parentBlockRoot)}`);
722
740
  }
@@ -730,7 +748,7 @@ export function getPayloadAttributesForSSE(
730
748
  proposalSlot: prepareSlot,
731
749
  parentBlockNumber,
732
750
  parentBlockRoot,
733
- parentBlockHash: parentHash,
751
+ parentBlockHash,
734
752
  payloadAttributes,
735
753
  };
736
754
  return ssePayloadAttributes;
@@ -745,11 +763,13 @@ function preparePayloadAttributes(
745
763
  prepareState,
746
764
  prepareSlot,
747
765
  parentBlockRoot,
766
+ parentBlockHash,
748
767
  feeRecipient,
749
768
  }: {
750
769
  prepareState: IBeaconStateViewBellatrix;
751
770
  prepareSlot: Slot;
752
771
  parentBlockRoot: Root;
772
+ parentBlockHash: Bytes32;
753
773
  feeRecipient: string;
754
774
  }
755
775
  ): SSEPayloadAttributes["payloadAttributes"] {
@@ -766,13 +786,15 @@ function preparePayloadAttributes(
766
786
  throw new Error("Expected Capella state for withdrawals");
767
787
  }
768
788
 
769
- if (isStatePostGloas(prepareState) && !isParentBlockFull(prepareState)) {
789
+ if (isStatePostGloas(prepareState)) {
790
+ const isExtendingPayload = byteArrayEquals(parentBlockHash, prepareState.latestExecutionPayloadBid.blockHash);
770
791
  // When the parent block is empty, state.payloadExpectedWithdrawals holds a batch
771
792
  // already deducted from CL balances but never credited on the EL (the envelope
772
793
  // was not delivered). The next payload must carry those same withdrawals to
773
794
  // restore CL/EL consistency, otherwise validators permanently lose that balance.
774
- (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
775
- prepareState.payloadExpectedWithdrawals;
795
+ (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals = isExtendingPayload
796
+ ? prepareState.getExpectedWithdrawals().expectedWithdrawals
797
+ : prepareState.payloadExpectedWithdrawals;
776
798
  } else {
777
799
  // withdrawals logic is now fork aware as it changes on electra fork post capella
778
800
  (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
@@ -784,6 +806,10 @@ function preparePayloadAttributes(
784
806
  (payloadAttributes as deneb.SSEPayloadAttributes["payloadAttributes"]).parentBeaconBlockRoot = parentBlockRoot;
785
807
  }
786
808
 
809
+ if (ForkSeq[fork] >= ForkSeq.gloas) {
810
+ (payloadAttributes as gloas.SSEPayloadAttributes["payloadAttributes"]).slotNumber = prepareSlot;
811
+ }
812
+
787
813
  return payloadAttributes;
788
814
  }
789
815
 
@@ -226,7 +226,10 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
226
226
  }
227
227
  sszTimer?.();
228
228
  const timer = this.metrics?.cpStateCache.stateReloadDuration.startTimer();
229
- const newCachedState = seedState.loadOtherState(stateBytes, validatorsBytes);
229
+ // preload validators and balances for faster state transition
230
+ const newCachedState = seedState.loadOtherState(stateBytes, validatorsBytes, {
231
+ preloadValidatorsAndBalances: true,
232
+ });
230
233
  // hashTreeRoot() calls the commit() inside
231
234
  // there is no modification inside the state, it's just that we want to compute and cache all roots
232
235
  const stateRoot = toRootHex(newCachedState.hashTreeRoot());
@@ -53,7 +53,7 @@ async function validateExecutionPayloadEnvelope(
53
53
  throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, {
54
54
  code: ExecutionPayloadEnvelopeErrorCode.ENVELOPE_ALREADY_KNOWN,
55
55
  blockRoot: blockRootHex,
56
- slot: envelope.slot,
56
+ slot: payload.slotNumber,
57
57
  });
58
58
  }
59
59
 
@@ -65,13 +65,13 @@ async function validateExecutionPayloadEnvelope(
65
65
  });
66
66
  }
67
67
 
68
- // [IGNORE] The envelope is from a slot greater than or equal to the latest finalized slot -- i.e. validate that `envelope.slot >= compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)`
68
+ // [IGNORE] The envelope is from a slot greater than or equal to the latest finalized slot -- i.e. validate that `payload.slotNumber >= compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)`
69
69
  const finalizedCheckpoint = chain.forkChoice.getFinalizedCheckpoint();
70
70
  const finalizedSlot = computeStartSlotAtEpoch(finalizedCheckpoint.epoch);
71
- if (envelope.slot < finalizedSlot) {
71
+ if (payload.slotNumber < finalizedSlot) {
72
72
  throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, {
73
73
  code: ExecutionPayloadEnvelopeErrorCode.BELONG_TO_FINALIZED_BLOCK,
74
- envelopeSlot: envelope.slot,
74
+ envelopeSlot: payload.slotNumber,
75
75
  finalizedSlot,
76
76
  });
77
77
  }
@@ -80,11 +80,11 @@ async function validateExecutionPayloadEnvelope(
80
80
  // TODO GLOAS: implement this. Technically if we cannot get proto block from fork choice,
81
81
  // it is possible that the block didn't pass the validation
82
82
 
83
- // [REJECT] `block.slot` equals `envelope.slot`.
84
- if (block.slot !== envelope.slot) {
83
+ // [REJECT] `block.slot` equals `payload.slotNumber`.
84
+ if (block.slot !== payload.slotNumber) {
85
85
  throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
86
86
  code: ExecutionPayloadEnvelopeErrorCode.SLOT_MISMATCH,
87
- envelopeSlot: envelope.slot,
87
+ envelopeSlot: payload.slotNumber,
88
88
  blockSlot: block.slot,
89
89
  });
90
90
  }
@@ -114,7 +114,7 @@ async function validateExecutionPayloadEnvelope(
114
114
  throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, {
115
115
  code: ExecutionPayloadEnvelopeErrorCode.UNKNOWN_BLOCK_STATE,
116
116
  blockRoot: blockRootHex,
117
- slot: envelope.slot,
117
+ slot: payload.slotNumber,
118
118
  });
119
119
  });
120
120
  if (!isStatePostGloas(blockState)) {
@@ -18,7 +18,8 @@ export async function validateApiPayloadAttestationMessage(
18
18
  chain: IBeaconChain,
19
19
  payloadAttestationMessage: gloas.PayloadAttestationMessage
20
20
  ): Promise<PayloadAttestationValidationResult> {
21
- return validatePayloadAttestationMessage(chain, payloadAttestationMessage);
21
+ const prioritizeBls = true;
22
+ return validatePayloadAttestationMessage(chain, payloadAttestationMessage, prioritizeBls);
22
23
  }
23
24
 
24
25
  export async function validateGossipPayloadAttestationMessage(
@@ -30,7 +31,8 @@ export async function validateGossipPayloadAttestationMessage(
30
31
 
31
32
  async function validatePayloadAttestationMessage(
32
33
  chain: IBeaconChain,
33
- payloadAttestationMessage: gloas.PayloadAttestationMessage
34
+ payloadAttestationMessage: gloas.PayloadAttestationMessage,
35
+ prioritizeBls = false
34
36
  ): Promise<PayloadAttestationValidationResult> {
35
37
  const {data, validatorIndex} = payloadAttestationMessage;
36
38
  const epoch = computeEpochAtSlot(data.slot);
@@ -102,7 +104,7 @@ async function validatePayloadAttestationMessage(
102
104
  payloadAttestationMessage.signature
103
105
  );
104
106
 
105
- if (!(await chain.bls.verifySignatureSets([signatureSet]))) {
107
+ if (!(await chain.bls.verifySignatureSets([signatureSet], {batchable: true, priority: prioritizeBls}))) {
106
108
  throw new PayloadAttestationError(GossipAction.REJECT, {
107
109
  code: PayloadAttestationErrorCode.INVALID_SIGNATURE,
108
110
  });
@@ -19,7 +19,7 @@ export class ExecutionPayloadEnvelopeArchiveRepository extends Repository<Slot,
19
19
  * Id is the slot from the envelope
20
20
  */
21
21
  getId(value: gloas.SignedExecutionPayloadEnvelope): Slot {
22
- return value.message.slot;
22
+ return value.message.payload.slotNumber;
23
23
  }
24
24
 
25
25
  encodeKey(id: Slot): Uint8Array {
@@ -216,13 +216,15 @@ export class ExecutionEngineHttp implements IExecutionEngine {
216
216
  executionRequests?: ExecutionRequests
217
217
  ): Promise<ExecutePayloadResponse> {
218
218
  const method =
219
- ForkSeq[fork] >= ForkSeq.electra
220
- ? "engine_newPayloadV4"
221
- : ForkSeq[fork] >= ForkSeq.deneb
222
- ? "engine_newPayloadV3"
223
- : ForkSeq[fork] >= ForkSeq.capella
224
- ? "engine_newPayloadV2"
225
- : "engine_newPayloadV1";
219
+ ForkSeq[fork] >= ForkSeq.gloas
220
+ ? "engine_newPayloadV5"
221
+ : ForkSeq[fork] >= ForkSeq.electra
222
+ ? "engine_newPayloadV4"
223
+ : ForkSeq[fork] >= ForkSeq.deneb
224
+ ? "engine_newPayloadV3"
225
+ : ForkSeq[fork] >= ForkSeq.capella
226
+ ? "engine_newPayloadV2"
227
+ : "engine_newPayloadV1";
226
228
 
227
229
  const serializedExecutionPayload = serializeExecutionPayload(fork, executionPayload);
228
230
 
@@ -244,7 +246,7 @@ export class ExecutionEngineHttp implements IExecutionEngine {
244
246
  }
245
247
  const serializedExecutionRequests = serializeExecutionRequests(executionRequests);
246
248
  engineRequest = {
247
- method: "engine_newPayloadV4",
249
+ method: ForkSeq[fork] >= ForkSeq.gloas ? "engine_newPayloadV5" : "engine_newPayloadV4",
248
250
  params: [
249
251
  serializedExecutionPayload,
250
252
  serializedVersionedHashes,
@@ -348,11 +350,13 @@ export class ExecutionEngineHttp implements IExecutionEngine {
348
350
  // Once on capella, should this need to be permanently switched to v2 when payload attrs
349
351
  // not provided
350
352
  const method =
351
- ForkSeq[fork] >= ForkSeq.deneb
352
- ? "engine_forkchoiceUpdatedV3"
353
- : ForkSeq[fork] >= ForkSeq.capella
354
- ? "engine_forkchoiceUpdatedV2"
355
- : "engine_forkchoiceUpdatedV1";
353
+ ForkSeq[fork] >= ForkSeq.gloas
354
+ ? "engine_forkchoiceUpdatedV4"
355
+ : ForkSeq[fork] >= ForkSeq.deneb
356
+ ? "engine_forkchoiceUpdatedV3"
357
+ : ForkSeq[fork] >= ForkSeq.capella
358
+ ? "engine_forkchoiceUpdatedV2"
359
+ : "engine_forkchoiceUpdatedV1";
356
360
  const payloadAttributesRpc = payloadAttributes ? serializePayloadAttributes(payloadAttributes) : undefined;
357
361
  // If we are just fcUing and not asking execution for payload, retry is not required
358
362
  // and we can move on, as the next fcU will be issued soon on the new slot
@@ -438,9 +442,12 @@ export class ExecutionEngineHttp implements IExecutionEngine {
438
442
  case ForkName.electra:
439
443
  method = "engine_getPayloadV4";
440
444
  break;
441
- default:
445
+ case ForkName.fulu:
442
446
  method = "engine_getPayloadV5";
443
447
  break;
448
+ default:
449
+ method = "engine_getPayloadV6";
450
+ break;
444
451
  }
445
452
  const payloadResponse = await this.rpc.fetchWithRetries<
446
453
  EngineApiRpcReturnTypes[typeof method],
@@ -87,6 +87,7 @@ export type PayloadAttributes = {
87
87
  suggestedFeeRecipient: string;
88
88
  withdrawals?: capella.Withdrawal[];
89
89
  parentBeaconBlockRoot?: Uint8Array;
90
+ slotNumber?: number; // EIP-7843
90
91
  };
91
92
 
92
93
  export type VersionedHashes = Uint8Array[];