@lodestar/beacon-node 1.43.0-dev.a6d8600051 → 1.43.0-dev.ac258a8def

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 (220) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +10 -0
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/debug/index.d.ts.map +1 -1
  5. package/lib/api/impl/debug/index.js +0 -1
  6. package/lib/api/impl/debug/index.js.map +1 -1
  7. package/lib/api/impl/validator/index.d.ts.map +1 -1
  8. package/lib/api/impl/validator/index.js +2 -1
  9. package/lib/api/impl/validator/index.js.map +1 -1
  10. package/lib/chain/blocks/blockInput/blockInput.d.ts +3 -0
  11. package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
  12. package/lib/chain/blocks/blockInput/blockInput.js +4 -1
  13. package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
  14. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  15. package/lib/chain/blocks/importBlock.js +16 -29
  16. package/lib/chain/blocks/importBlock.js.map +1 -1
  17. package/lib/chain/blocks/importExecutionPayload.d.ts +4 -0
  18. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  19. package/lib/chain/blocks/importExecutionPayload.js +15 -6
  20. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  21. package/lib/chain/blocks/index.d.ts.map +1 -1
  22. package/lib/chain/blocks/index.js +29 -17
  23. package/lib/chain/blocks/index.js.map +1 -1
  24. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +11 -1
  25. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  26. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +24 -1
  27. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  28. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +16 -0
  29. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  30. package/lib/chain/blocks/types.d.ts +2 -1
  31. package/lib/chain/blocks/types.d.ts.map +1 -1
  32. package/lib/chain/blocks/utils/chainSegment.d.ts +1 -3
  33. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  34. package/lib/chain/blocks/utils/chainSegment.js +34 -23
  35. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  36. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  37. package/lib/chain/blocks/verifyBlock.js +5 -6
  38. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  39. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts +0 -4
  40. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts.map +1 -1
  41. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +5 -2
  42. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
  43. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts +2 -1
  44. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
  45. package/lib/chain/blocks/verifyBlocksSanityChecks.js +16 -7
  46. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  47. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
  48. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  49. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +10 -6
  50. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  51. package/lib/chain/chain.d.ts.map +1 -1
  52. package/lib/chain/chain.js +25 -10
  53. package/lib/chain/chain.js.map +1 -1
  54. package/lib/chain/emitter.d.ts +0 -11
  55. package/lib/chain/emitter.d.ts.map +1 -1
  56. package/lib/chain/emitter.js +0 -4
  57. package/lib/chain/emitter.js.map +1 -1
  58. package/lib/chain/errors/proposerPreferences.d.ts +8 -1
  59. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
  60. package/lib/chain/errors/proposerPreferences.js +1 -0
  61. package/lib/chain/errors/proposerPreferences.js.map +1 -1
  62. package/lib/chain/initState.d.ts.map +1 -1
  63. package/lib/chain/initState.js +6 -1
  64. package/lib/chain/initState.js.map +1 -1
  65. package/lib/chain/prepareNextSlot.js +1 -1
  66. package/lib/chain/prepareNextSlot.js.map +1 -1
  67. package/lib/chain/produceBlock/produceBlockBody.d.ts +1 -0
  68. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  69. package/lib/chain/produceBlock/produceBlockBody.js +2 -7
  70. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  71. package/lib/chain/regen/queued.d.ts.map +1 -1
  72. package/lib/chain/regen/queued.js +1 -4
  73. package/lib/chain/regen/queued.js.map +1 -1
  74. package/lib/chain/regen/regen.d.ts.map +1 -1
  75. package/lib/chain/regen/regen.js +1 -4
  76. package/lib/chain/regen/regen.js.map +1 -1
  77. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +14 -10
  78. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  79. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +63 -19
  80. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  81. package/lib/chain/seenCache/seenProposerPreferences.d.ts +8 -7
  82. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -1
  83. package/lib/chain/seenCache/seenProposerPreferences.js +11 -10
  84. package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -1
  85. package/lib/chain/validation/executionPayloadBid.js +11 -8
  86. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  87. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  88. package/lib/chain/validation/proposerPreferences.js +39 -17
  89. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  90. package/lib/network/gossip/topic.d.ts +2 -0
  91. package/lib/network/gossip/topic.d.ts.map +1 -1
  92. package/lib/network/interface.d.ts +1 -0
  93. package/lib/network/interface.d.ts.map +1 -1
  94. package/lib/network/network.d.ts +1 -0
  95. package/lib/network/network.d.ts.map +1 -1
  96. package/lib/network/network.js +3 -0
  97. package/lib/network/network.js.map +1 -1
  98. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  99. package/lib/network/processor/gossipHandlers.js +24 -7
  100. package/lib/network/processor/gossipHandlers.js.map +1 -1
  101. package/lib/network/processor/index.js +5 -5
  102. package/lib/network/processor/index.js.map +1 -1
  103. package/lib/network/reqresp/ReqRespBeaconNode.d.ts.map +1 -1
  104. package/lib/network/reqresp/ReqRespBeaconNode.js +1 -1
  105. package/lib/network/reqresp/ReqRespBeaconNode.js.map +1 -1
  106. package/lib/network/reqresp/handlers/beaconBlocksByHead.d.ts +9 -0
  107. package/lib/network/reqresp/handlers/beaconBlocksByHead.d.ts.map +1 -0
  108. package/lib/network/reqresp/handlers/beaconBlocksByHead.js +61 -0
  109. package/lib/network/reqresp/handlers/beaconBlocksByHead.js.map +1 -0
  110. package/lib/network/reqresp/handlers/index.d.ts.map +1 -1
  111. package/lib/network/reqresp/handlers/index.js +5 -0
  112. package/lib/network/reqresp/handlers/index.js.map +1 -1
  113. package/lib/network/reqresp/interface.d.ts +1 -1
  114. package/lib/network/reqresp/interface.js +1 -1
  115. package/lib/network/reqresp/protocols.d.ts +1 -0
  116. package/lib/network/reqresp/protocols.d.ts.map +1 -1
  117. package/lib/network/reqresp/protocols.js +5 -0
  118. package/lib/network/reqresp/protocols.js.map +1 -1
  119. package/lib/network/reqresp/rateLimit.d.ts.map +1 -1
  120. package/lib/network/reqresp/rateLimit.js +4 -0
  121. package/lib/network/reqresp/rateLimit.js.map +1 -1
  122. package/lib/network/reqresp/score.d.ts.map +1 -1
  123. package/lib/network/reqresp/score.js +1 -0
  124. package/lib/network/reqresp/score.js.map +1 -1
  125. package/lib/network/reqresp/types.d.ts +3 -0
  126. package/lib/network/reqresp/types.d.ts.map +1 -1
  127. package/lib/network/reqresp/types.js +3 -0
  128. package/lib/network/reqresp/types.js.map +1 -1
  129. package/lib/node/nodejs.js +2 -2
  130. package/lib/node/nodejs.js.map +1 -1
  131. package/lib/node/notifier.js +1 -7
  132. package/lib/node/notifier.js.map +1 -1
  133. package/lib/sync/constants.d.ts +7 -1
  134. package/lib/sync/constants.d.ts.map +1 -1
  135. package/lib/sync/constants.js +7 -4
  136. package/lib/sync/constants.js.map +1 -1
  137. package/lib/sync/range/batch.d.ts +23 -3
  138. package/lib/sync/range/batch.d.ts.map +1 -1
  139. package/lib/sync/range/batch.js +191 -36
  140. package/lib/sync/range/batch.js.map +1 -1
  141. package/lib/sync/range/chain.d.ts +16 -2
  142. package/lib/sync/range/chain.d.ts.map +1 -1
  143. package/lib/sync/range/chain.js +99 -8
  144. package/lib/sync/range/chain.js.map +1 -1
  145. package/lib/sync/range/range.d.ts.map +1 -1
  146. package/lib/sync/range/range.js +54 -5
  147. package/lib/sync/range/range.js.map +1 -1
  148. package/lib/sync/sync.d.ts.map +1 -1
  149. package/lib/sync/sync.js +13 -0
  150. package/lib/sync/sync.js.map +1 -1
  151. package/lib/sync/unknownBlock.d.ts +7 -2
  152. package/lib/sync/unknownBlock.d.ts.map +1 -1
  153. package/lib/sync/unknownBlock.js +127 -57
  154. package/lib/sync/unknownBlock.js.map +1 -1
  155. package/lib/sync/utils/downloadByRange.d.ts +36 -9
  156. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  157. package/lib/sync/utils/downloadByRange.js +106 -42
  158. package/lib/sync/utils/downloadByRange.js.map +1 -1
  159. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  160. package/lib/sync/utils/downloadByRoot.js +10 -0
  161. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  162. package/lib/sync/utils/rateLimit.d.ts +2 -0
  163. package/lib/sync/utils/rateLimit.d.ts.map +1 -0
  164. package/lib/sync/utils/rateLimit.js +15 -0
  165. package/lib/sync/utils/rateLimit.js.map +1 -0
  166. package/lib/util/sszBytes.d.ts.map +1 -1
  167. package/lib/util/sszBytes.js +8 -6
  168. package/lib/util/sszBytes.js.map +1 -1
  169. package/package.json +16 -16
  170. package/src/api/impl/beacon/blocks/index.ts +13 -0
  171. package/src/api/impl/debug/index.ts +0 -1
  172. package/src/api/impl/validator/index.ts +2 -1
  173. package/src/chain/blocks/blockInput/blockInput.ts +4 -1
  174. package/src/chain/blocks/importBlock.ts +16 -49
  175. package/src/chain/blocks/importExecutionPayload.ts +21 -11
  176. package/src/chain/blocks/index.ts +18 -9
  177. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +32 -2
  178. package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -0
  179. package/src/chain/blocks/types.ts +2 -1
  180. package/src/chain/blocks/utils/chainSegment.ts +35 -24
  181. package/src/chain/blocks/verifyBlock.ts +7 -5
  182. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +6 -4
  183. package/src/chain/blocks/verifyBlocksSanityChecks.ts +16 -6
  184. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +14 -6
  185. package/src/chain/chain.ts +29 -9
  186. package/src/chain/emitter.ts +0 -11
  187. package/src/chain/errors/proposerPreferences.ts +9 -1
  188. package/src/chain/initState.ts +9 -1
  189. package/src/chain/prepareNextSlot.ts +1 -1
  190. package/src/chain/produceBlock/produceBlockBody.ts +3 -7
  191. package/src/chain/regen/queued.ts +2 -7
  192. package/src/chain/regen/regen.ts +2 -7
  193. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +78 -22
  194. package/src/chain/seenCache/seenProposerPreferences.ts +14 -11
  195. package/src/chain/validation/executionPayloadBid.ts +11 -8
  196. package/src/chain/validation/proposerPreferences.ts +37 -18
  197. package/src/network/interface.ts +1 -0
  198. package/src/network/network.ts +12 -0
  199. package/src/network/processor/gossipHandlers.ts +30 -7
  200. package/src/network/processor/index.ts +5 -5
  201. package/src/network/reqresp/ReqRespBeaconNode.ts +1 -0
  202. package/src/network/reqresp/handlers/beaconBlocksByHead.ts +91 -0
  203. package/src/network/reqresp/handlers/index.ts +5 -0
  204. package/src/network/reqresp/interface.ts +1 -1
  205. package/src/network/reqresp/protocols.ts +6 -0
  206. package/src/network/reqresp/rateLimit.ts +4 -0
  207. package/src/network/reqresp/score.ts +1 -0
  208. package/src/network/reqresp/types.ts +5 -0
  209. package/src/node/nodejs.ts +2 -2
  210. package/src/node/notifier.ts +1 -8
  211. package/src/sync/constants.ts +8 -4
  212. package/src/sync/range/batch.ts +240 -42
  213. package/src/sync/range/chain.ts +118 -9
  214. package/src/sync/range/range.ts +68 -5
  215. package/src/sync/sync.ts +13 -1
  216. package/src/sync/unknownBlock.ts +157 -60
  217. package/src/sync/utils/downloadByRange.ts +174 -44
  218. package/src/sync/utils/downloadByRoot.ts +12 -0
  219. package/src/sync/utils/rateLimit.ts +16 -0
  220. package/src/util/sszBytes.ts +8 -6
@@ -17,8 +17,6 @@ export type ChainSegmentResult = {warnings: OrphanedPayloadEnvelope[] | null};
17
17
  * Assert this chain segment of blocks is linear with slot numbers and hashes,
18
18
  * and that the provided envelopes are consistent with their respective blocks.
19
19
  *
20
- * Must be called after verifyBlocksSanityChecks so that parentBlock (from forkchoice)
21
- * is available to seed the execution hash chain.
22
20
  *
23
21
  * For each block:
24
22
  * - Verifies parent root + slot linearity
@@ -31,7 +29,7 @@ export function assertLinearChainSegment(
31
29
  config: ChainForkConfig,
32
30
  blocks: IBlockInput[],
33
31
  payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null,
34
- parentBlock: ProtoBlock
32
+ parentBlock: ProtoBlock | null
35
33
  ): ChainSegmentResult {
36
34
  const warnings: OrphanedPayloadEnvelope[] = [];
37
35
 
@@ -39,8 +37,17 @@ export function assertLinearChainSegment(
39
37
  // Starts from the known forkchoice parent's execution hash.
40
38
  // - FULL variant (envelope present for slot): advances to envelope.payload.blockHash
41
39
  // - EMPTY variant (no envelope for slot): execution hash is unchanged
42
- // null only for pre-merge parents, which cannot precede gloas blocks.
43
- let currentExecHash: string | null = parentBlock.executionPayloadBlockHash;
40
+ let currentExecHash: string | null = parentBlock?.executionPayloadBlockHash ?? null;
41
+ // Checkpoint sync first batch: parent is the anchor PENDING whose executionPayloadBlockHash
42
+ // is the inherited parentBlockHash semantic (= grandparent's payload), not its own payload.
43
+ // If parent's own payload envelope arrives in this batch, advance currentExecHash to that
44
+ // payload's blockHash so the segment validation sees the true EL chain head.
45
+ if (parentBlock !== null) {
46
+ const parentPayloadInput = payloadEnvelopes?.get(parentBlock.slot);
47
+ if (parentPayloadInput?.hasPayloadEnvelope()) {
48
+ currentExecHash = parentPayloadInput.getBlockHashHex();
49
+ }
50
+ }
44
51
  // Track the execution hash before the last FULL advancement so we can recover
45
52
  // if the next block reveals that envelope was orphaned.
46
53
  let prevExecHash: string | null = currentExecHash;
@@ -68,27 +75,31 @@ export function assertLinearChainSegment(
68
75
  }
69
76
  }
70
77
 
71
- if (isGloasBeaconBlock(block.message) && currentExecHash !== null) {
72
- // Verify the bid's parentBlockHash matches the tracked execution hash.
73
- // This ensures the block was built on the correct FULL or EMPTY variant of its parent.
74
- const bidParentHash = toRootHex(block.message.body.signedExecutionPayloadBid.message.parentBlockHash);
75
- if (bidParentHash !== currentExecHash) {
76
- // Maybe the previous slot's FULL envelope was orphaned try falling back.
77
- // If even prevExecHash doesn't match, the segment is non-linear.
78
- if (bidParentHash !== prevExecHash) {
79
- throw new BlockError(block, {
80
- code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN,
81
- parentRoot: toRootHex(block.message.parentRoot),
82
- parentBlockHash: bidParentHash,
83
- });
84
- }
85
- if (lastFullSlot !== null && payloadEnvelopes !== null) {
86
- const orphanedInput = payloadEnvelopes.get(lastFullSlot);
87
- if (orphanedInput != null) {
88
- warnings.push({slot: lastFullSlot, payloadEnvelopeInput: orphanedInput});
78
+ if (isGloasBeaconBlock(block.message)) {
79
+ // Bid check fires only when we have a seeded execution hash. With parentBlock=null the
80
+ // chain is seeded mid-segment by the first FULL envelope.
81
+ if (currentExecHash !== null) {
82
+ // Verify the bid's parentBlockHash matches the tracked execution hash.
83
+ // This ensures the block was built on the correct FULL or EMPTY variant of its parent.
84
+ const bidParentHash = toRootHex(block.message.body.signedExecutionPayloadBid.message.parentBlockHash);
85
+ if (bidParentHash !== currentExecHash) {
86
+ // Maybe the previous slot's FULL envelope was orphaned — try falling back.
87
+ // If even prevExecHash doesn't match, the segment is non-linear.
88
+ if (bidParentHash !== prevExecHash) {
89
+ throw new BlockError(block, {
90
+ code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN,
91
+ parentRoot: toRootHex(block.message.parentRoot),
92
+ parentBlockHash: bidParentHash,
93
+ });
94
+ }
95
+ if (lastFullSlot !== null && payloadEnvelopes !== null) {
96
+ const orphanedInput = payloadEnvelopes.get(lastFullSlot);
97
+ if (orphanedInput != null) {
98
+ warnings.push({slot: lastFullSlot, payloadEnvelopeInput: orphanedInput});
99
+ }
89
100
  }
101
+ currentExecHash = prevExecHash;
90
102
  }
91
- currentExecHash = prevExecHash;
92
103
  }
93
104
 
94
105
  const payloadInput = payloadEnvelopes?.get(slot) ?? null;
@@ -125,15 +125,17 @@ export async function verifyBlocksInEpoch(
125
125
  }> =
126
126
  fork >= ForkSeq.gloas
127
127
  ? (async () => {
128
- const payloadInputsForDa: PayloadEnvelopeInput[] = [];
129
- for (const input of blockInputs) {
130
- const pi = payloadEnvelopes?.get(input.slot);
131
- if (pi !== undefined) payloadInputsForDa.push(pi);
132
- }
128
+ // Validate DA for ALL payloads in the Map, not just those paired with blockInputs.
129
+ // A checkpoint-sync batch may include a payload for a slot whose block was filtered
130
+ // out of relevantBlocks (e.g., the anchor at the finalized slot); that payload still
131
+ // needs DA validation so it can be imported in processBlocks.
132
+ const payloadInputsForDa: PayloadEnvelopeInput[] =
133
+ payloadEnvelopes !== null ? Array.from(payloadEnvelopes.values()) : [];
133
134
  const {dataAvailabilityStatuses, availableTime} = await verifyPayloadsDataAvailability(
134
135
  payloadInputsForDa,
135
136
  abortController.signal
136
137
  );
138
+
137
139
  const payloadDAStatuses = new Map<Slot, DataAvailabilityStatus>();
138
140
  for (let i = 0; i < payloadInputsForDa.length; i++) {
139
141
  payloadDAStatuses.set(payloadInputsForDa[i].slot, dataAvailabilityStatuses[i]);
@@ -46,8 +46,7 @@ type VerifyBlockExecutionResponse =
46
46
  | VerifyExecutionErrorResponse
47
47
  | {executionStatus: ExecutionStatus.Valid; lvhResponse: LVHValidResponse; execError: null}
48
48
  | {executionStatus: ExecutionStatus.Syncing; lvhResponse?: LVHValidResponse; execError: null}
49
- | {executionStatus: ExecutionStatus.PreMerge; lvhResponse: undefined; execError: null}
50
- | {executionStatus: ExecutionStatus.PayloadSeparated; lvhResponse: undefined; execError: null};
49
+ | {executionStatus: ExecutionStatus.PreMerge; lvhResponse: undefined; execError: null};
51
50
 
52
51
  /**
53
52
  * Verifies 1 or more execution payloads from a linear sequence of blocks.
@@ -145,9 +144,10 @@ export async function verifyBlockExecutionPayload(
145
144
  ): Promise<VerifyBlockExecutionResponse> {
146
145
  const block = blockInput.getBlock();
147
146
 
148
- // Gloas block doesn't have execution payload. Return right away
147
+ // Gloas block doesn't have execution payload. Return Syncing as a placeholder; the actual
148
+ // status for gloas PENDING/EMPTY is derived from parent's chain in importBlock.
149
149
  if (isBlockInputNoData(blockInput)) {
150
- return {executionStatus: ExecutionStatus.PayloadSeparated, lvhResponse: undefined, execError: null};
150
+ return {executionStatus: ExecutionStatus.Syncing, lvhResponse: undefined, execError: null};
151
151
  }
152
152
 
153
153
  /** Not null if execution is enabled */
@@ -198,6 +198,7 @@ export async function verifyBlockExecutionPayload(
198
198
  executionStatus,
199
199
  latestValidExecHash: execResult.latestValidHash,
200
200
  invalidateFromParentBlockRoot: blockInput.parentRootHex,
201
+ invalidateFromParentBlockHash: toRootHex(executionPayloadEnabled.parentHash),
201
202
  };
202
203
  const execError = new BlockError(block, {
203
204
  code: BlockErrorCode.EXECUTION_ENGINE_ERROR,
@@ -281,6 +282,7 @@ function getSegmentErrorResponse(
281
282
  executionStatus: ExecutionStatus.Invalid,
282
283
  latestValidExecHash: lvhResponse.latestValidExecHash,
283
284
  invalidateFromParentBlockRoot: parentBlock.blockRoot,
285
+ invalidateFromParentBlockHash: parentBlock.executionPayloadBlockHash,
284
286
  };
285
287
  }
286
288
  }
@@ -7,6 +7,7 @@ import {IClock} from "../../util/clock.js";
7
7
  import {BlockError, BlockErrorCode} from "../errors/index.js";
8
8
  import {IChainOptions} from "../options.js";
9
9
  import {IBlockInput} from "./blockInput/types.js";
10
+ import {PayloadEnvelopeInput} from "./payloadEnvelopeInput/payloadEnvelopeInput.js";
10
11
  import {ImportBlockOpts} from "./types.js";
11
12
 
12
13
  /**
@@ -30,6 +31,7 @@ export function verifyBlocksSanityChecks(
30
31
  blacklistedBlocks: Map<RootHex, Slot | null>;
31
32
  },
32
33
  blocks: IBlockInput[],
34
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null,
33
35
  opts: ImportBlockOpts
34
36
  ): {
35
37
  relevantBlocks: IBlockInput[];
@@ -100,13 +102,21 @@ export function verifyBlocksSanityChecks(
100
102
  const parentBlockHash = toRootHex(block.message.body.signedExecutionPayloadBid.message.parentBlockHash);
101
103
  const parentBlockWithPayload = chain.forkChoice.getBlockHexAndBlockHash(parentRoot, parentBlockHash);
102
104
  if (!parentBlockWithPayload) {
103
- throw new BlockError(block, {
104
- code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN,
105
- parentRoot,
106
- parentBlockHash,
107
- });
105
+ // Checkpoint sync: parent's FULL variant may not be in fork-choice yet because the
106
+ // anchor block is initialized with PENDING+EMPTY only. The parent's payload arrives
107
+ // in the same batch via payloadEnvelopes and will be imported by processBlocks. If
108
+ // a matching payload is in the Map, accept the parent as known.
109
+ const parentPayloadInput = payloadEnvelopes?.get(parentBlockDefaultStatus.slot);
110
+ if (parentPayloadInput?.getBlockHashHex() !== parentBlockHash) {
111
+ throw new BlockError(block, {
112
+ code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN,
113
+ parentRoot,
114
+ parentBlockHash,
115
+ });
116
+ }
117
+ } else {
118
+ parentBlock = parentBlockWithPayload;
108
119
  }
109
- parentBlock = parentBlockWithPayload;
110
120
  }
111
121
  // Parent is known to the fork-choice
112
122
  parentBlockSlot = parentBlock.slot;
@@ -20,7 +20,7 @@ export type VerifyExecutionPayloadEnvelopeOpts = {
20
20
  * performed outside this function, see `verifyExecutionPayloadEnvelopeSignature` and
21
21
  * `importExecutionPayload` which run both in parallel with this check.
22
22
  *
23
- * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.5/specs/gloas/fork-choice.md#new-verify_execution_payload_envelope
23
+ * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.6/specs/gloas/fork-choice.md#new-verify_execution_payload_envelope
24
24
  */
25
25
  export function verifyExecutionPayloadEnvelope(
26
26
  config: BeaconConfig,
@@ -43,6 +43,11 @@ export function verifyExecutionPayloadEnvelope(
43
43
  `Envelope's block is not the latest block header envelope=${toRootHex(envelope.beaconBlockRoot)} latestBlockHeader=${toRootHex(headerRoot)}`
44
44
  );
45
45
  }
46
+ if (!byteArrayEquals(envelope.parentBeaconBlockRoot, state.latestBlockHeader.parentRoot)) {
47
+ throw new Error(
48
+ `Envelope's parent_beacon_block_root mismatch envelope=${toRootHex(envelope.parentBeaconBlockRoot)} state=${toRootHex(state.latestBlockHeader.parentRoot)}`
49
+ );
50
+ }
46
51
 
47
52
  // Verify consistency with the committed bid
48
53
  const bid = state.latestExecutionPayloadBid;
@@ -77,16 +82,19 @@ export function verifyExecutionPayloadEnvelope(
77
82
  }
78
83
  }
79
84
 
80
- // Verify the execution payload is valid
81
- if (payload.slotNumber !== state.slot) {
82
- throw new Error(`Slot mismatch between payload and state payload=${payload.slotNumber} state=${state.slot}`);
85
+ // should not use state.slot, it does not work for skipped slot checkpoint sync
86
+ const blockSlot = state.latestBlockHeader.slot;
87
+ if (payload.slotNumber !== blockSlot) {
88
+ throw new Error(
89
+ `Slot mismatch between payload and latest block header payload=${payload.slotNumber} latestBlockHeader=${blockSlot}`
90
+ );
83
91
  }
84
92
  if (!byteArrayEquals(payload.parentHash, state.latestBlockHash)) {
85
93
  throw new Error(
86
94
  `Parent hash mismatch between payload and state payload=${toRootHex(payload.parentHash)} state=${toRootHex(state.latestBlockHash)}`
87
95
  );
88
96
  }
89
- const expectedTimestamp = computeTimeAtSlot(config, state.slot, state.genesisTime);
97
+ const expectedTimestamp = computeTimeAtSlot(config, blockSlot, state.genesisTime);
90
98
  if (payload.timestamp !== expectedTimestamp) {
91
99
  throw new Error(
92
100
  `Timestamp mismatch between payload and state payload=${payload.timestamp} state=${expectedTimestamp}`
@@ -108,7 +116,7 @@ export function verifyExecutionPayloadEnvelope(
108
116
  /**
109
117
  * Verify the BLS signature of an execution payload envelope.
110
118
  *
111
- * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.5/specs/gloas/fork-choice.md#new-verify_execution_payload_envelope_signature
119
+ * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.6/specs/gloas/fork-choice.md#new-verify_execution_payload_envelope_signature
112
120
  */
113
121
  export async function verifyExecutionPayloadEnvelopeSignature(
114
122
  config: BeaconConfig,
@@ -23,6 +23,7 @@ import {
23
23
  getEffectiveBalancesFromStateBytes,
24
24
  isStatePostAltair,
25
25
  isStatePostElectra,
26
+ isStatePostGloas,
26
27
  } from "@lodestar/state-transition";
27
28
  import {
28
29
  BeaconBlock,
@@ -335,15 +336,6 @@ export class BeaconChain implements IBeaconChain {
335
336
  metrics,
336
337
  logger,
337
338
  });
338
- this.seenPayloadEnvelopeInputCache = new SeenPayloadEnvelopeInput({
339
- config,
340
- clock,
341
- chainEvents: emitter,
342
- signal,
343
- serializedCache: this.serializedCache,
344
- metrics,
345
- logger,
346
- });
347
339
 
348
340
  this._earliestAvailableSlot = anchorState.slot;
349
341
 
@@ -399,6 +391,7 @@ export class BeaconChain implements IBeaconChain {
399
391
  metrics,
400
392
  logger
401
393
  );
394
+
402
395
  const regen = new QueuedStateRegenerator({
403
396
  config,
404
397
  forkChoice,
@@ -423,6 +416,33 @@ export class BeaconChain implements IBeaconChain {
423
416
  this.payloadEnvelopeProcessor = new PayloadEnvelopeProcessor(this, metrics, signal);
424
417
 
425
418
  this.forkChoice = forkChoice;
419
+
420
+ this.seenPayloadEnvelopeInputCache = new SeenPayloadEnvelopeInput({
421
+ config,
422
+ clock,
423
+ forkChoice,
424
+ chainEvents: emitter,
425
+ signal,
426
+ serializedCache: this.serializedCache,
427
+ metrics,
428
+ logger,
429
+ });
430
+
431
+ const anchorBlockSlot = anchorState.latestBlockHeader.slot;
432
+ if (isStatePostGloas(anchorState) && anchorBlockSlot > 0) {
433
+ const anchorBid = anchorState.latestExecutionPayloadBid;
434
+ this.seenPayloadEnvelopeInputCache.addFromBid({
435
+ blockRootHex: toRootHex(checkpoint.root),
436
+ slot: anchorBlockSlot,
437
+ forkName: anchorState.forkName,
438
+ proposerIndex: anchorState.latestBlockHeader.proposerIndex,
439
+ bid: anchorBid,
440
+ sampledColumns: this.custodyConfig.sampledColumns,
441
+ custodyColumns: this.custodyConfig.custodyColumns,
442
+ timeCreatedSec: Math.floor(Date.now() / 1000),
443
+ });
444
+ }
445
+
426
446
  this.clock = clock;
427
447
  this.regen = regen;
428
448
  this.bls = bls;
@@ -4,7 +4,6 @@ import {routes} from "@lodestar/api";
4
4
  import {CheckpointWithHex} from "@lodestar/fork-choice";
5
5
  import {IBeaconStateView} from "@lodestar/state-transition";
6
6
  import {DataColumnSidecar, RootHex, deneb, phase0} from "@lodestar/types";
7
- import {SignedExecutionPayloadEnvelope} from "@lodestar/types/gloas";
8
7
  import {PeerIdStr} from "../util/peerId.js";
9
8
  import {BlockInputSource, IBlockInput} from "./blocks/blockInput/types.js";
10
9
  import {PayloadEnvelopeInput} from "./blocks/payloadEnvelopeInput/payloadEnvelopeInput.js";
@@ -60,10 +59,6 @@ export enum ChainEvent {
60
59
  * Post-gloas, missing parent could be a SignedBeaconBlock and/or a SignedExecutionPayloadEnvelope
61
60
  */
62
61
  blockUnknownParent = "blockUnknownParent",
63
- /**
64
- * Trigger BlockInputSync to find a SignedBeaconBlock given a SignedExecutionPayloadEnvelop received
65
- */
66
- envelopeUnknownBlock = "envelopeUnknownBlock",
67
62
  /**
68
63
  * Trigger BlockInputSync to find a SignedBeaconBlock with specified block root.
69
64
  */
@@ -92,11 +87,6 @@ type ApiEvents = {[K in routes.events.EventType]: (data: routes.events.EventData
92
87
 
93
88
  export type ChainEventData = {
94
89
  [ChainEvent.blockUnknownParent]: {blockInput: IBlockInput; peer: PeerIdStr; source: BlockInputSource};
95
- [ChainEvent.envelopeUnknownBlock]: {
96
- envelope: SignedExecutionPayloadEnvelope;
97
- peer?: PeerIdStr;
98
- source: BlockInputSource;
99
- };
100
90
  [ChainEvent.unknownBlockRoot]: {rootHex: RootHex; peer?: PeerIdStr; source: BlockInputSource};
101
91
  [ChainEvent.incompleteBlockInput]: {blockInput: IBlockInput; peer: PeerIdStr; source: BlockInputSource};
102
92
  [ChainEvent.incompletePayloadEnvelope]: {
@@ -124,7 +114,6 @@ export type IChainEvents = ApiEvents & {
124
114
  // Sync events that are chain->chain. Initiated from network requests but do not cross the network
125
115
  // barrier so are considered ChainEvent(s).
126
116
  [ChainEvent.blockUnknownParent]: (data: ChainEventData[ChainEvent.blockUnknownParent]) => void;
127
- [ChainEvent.envelopeUnknownBlock]: (data: ChainEventData[ChainEvent.envelopeUnknownBlock]) => void;
128
117
  [ChainEvent.unknownBlockRoot]: (data: ChainEventData[ChainEvent.unknownBlockRoot]) => void;
129
118
  [ChainEvent.incompleteBlockInput]: (data: ChainEventData[ChainEvent.incompleteBlockInput]) => void;
130
119
  [ChainEvent.incompletePayloadEnvelope]: (data: ChainEventData[ChainEvent.incompletePayloadEnvelope]) => void;
@@ -1,9 +1,10 @@
1
- import {Slot, ValidatorIndex} from "@lodestar/types";
1
+ import {RootHex, Slot, ValidatorIndex} from "@lodestar/types";
2
2
  import {GossipActionError} from "./gossipValidation.js";
3
3
 
4
4
  export enum ProposerPreferencesErrorCode {
5
5
  INVALID_EPOCH = "PROPOSER_PREFERENCES_ERROR_INVALID_EPOCH",
6
6
  PROPOSAL_SLOT_PASSED = "PROPOSER_PREFERENCES_ERROR_PROPOSAL_SLOT_PASSED",
7
+ UNKNOWN_DEPENDENT_ROOT = "PROPOSER_PREFERENCES_ERROR_UNKNOWN_DEPENDENT_ROOT",
7
8
  INVALID_PROPOSER = "PROPOSER_PREFERENCES_ERROR_INVALID_PROPOSER",
8
9
  ALREADY_KNOWN = "PROPOSER_PREFERENCES_ERROR_ALREADY_KNOWN",
9
10
  INVALID_SIGNATURE = "PROPOSER_PREFERENCES_ERROR_INVALID_SIGNATURE",
@@ -20,15 +21,22 @@ export type ProposerPreferencesErrorType =
20
21
  proposalSlot: Slot;
21
22
  currentSlot: Slot;
22
23
  }
24
+ | {
25
+ code: ProposerPreferencesErrorCode.UNKNOWN_DEPENDENT_ROOT;
26
+ proposalSlot: Slot;
27
+ dependentRoot: RootHex;
28
+ }
23
29
  | {
24
30
  code: ProposerPreferencesErrorCode.INVALID_PROPOSER;
25
31
  proposalSlot: Slot;
26
32
  validatorIndex: ValidatorIndex;
33
+ dependentRoot: RootHex;
27
34
  }
28
35
  | {
29
36
  code: ProposerPreferencesErrorCode.ALREADY_KNOWN;
30
37
  proposalSlot: Slot;
31
38
  validatorIndex: ValidatorIndex;
39
+ dependentRoot: RootHex;
32
40
  }
33
41
  | {
34
42
  code: ProposerPreferencesErrorCode.INVALID_SIGNATURE;
@@ -1,7 +1,8 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {ZERO_HASH} from "@lodestar/params";
2
+ import {ForkPostGloas, ForkSeq, ZERO_HASH} from "@lodestar/params";
3
3
  import {
4
4
  BeaconStateAllForks,
5
+ BeaconStateGloas,
5
6
  IBeaconStateView,
6
7
  computeEpochAtSlot,
7
8
  computeStartSlotAtEpoch,
@@ -52,6 +53,13 @@ export function createGenesisBlock(config: ChainForkConfig, genesisState: Beacon
52
53
  const genesisBlock = types.SignedBeaconBlock.defaultValue();
53
54
  const stateRoot = genesisState.hashTreeRoot();
54
55
  genesisBlock.message.stateRoot = stateRoot;
56
+
57
+ if (config.getForkSeq(GENESIS_SLOT) >= ForkSeq.gloas) {
58
+ const gloasBlock = genesisBlock as SignedBeaconBlock<ForkPostGloas>;
59
+ const gloasState = genesisState as BeaconStateGloas;
60
+ gloasBlock.message.body.signedExecutionPayloadBid.message = gloasState.latestExecutionPayloadBid.toValue();
61
+ }
62
+
55
63
  return genesisBlock;
56
64
  }
57
65
 
@@ -222,7 +222,7 @@ export class PrepareNextSlotScheduler {
222
222
  // and head.parent (proposer-boost-reorg fallback). Anything older is evicted.
223
223
  const updatedHeadParent = this.chain.forkChoice.getBlockHexDefaultStatus(updatedHead.parentRoot);
224
224
  if (updatedHeadParent) {
225
- this.chain.seenPayloadEnvelopeInputCache.pruneBelow(updatedHeadParent.slot);
225
+ this.chain.seenPayloadEnvelopeInputCache.pruneBelowParent(updatedHeadParent);
226
226
  }
227
227
  }
228
228
 
@@ -49,7 +49,7 @@ import {
49
49
  ssz,
50
50
  } from "@lodestar/types";
51
51
  import {Logger, byteArrayEquals, fromHex, sleep, toHex, toPubkeyHex, toRootHex} from "@lodestar/utils";
52
- import {ZERO_HASH, ZERO_HASH_HEX} from "../../constants/index.js";
52
+ import {ZERO_HASH_HEX} from "../../constants/index.js";
53
53
  import {numToQuantity} from "../../execution/engine/utils.js";
54
54
  import {
55
55
  IExecutionBuilder,
@@ -111,6 +111,7 @@ export type ProduceFullGloas = {
111
111
  executionRequests: electra.ExecutionRequests;
112
112
  blobsBundle: BlobsBundle<ForkPostGloas>;
113
113
  cells: fulu.Cell[][];
114
+ parentBlockRoot: Root;
114
115
  };
115
116
  export type ProduceFullFulu = {
116
117
  type: BlockType.Full;
@@ -225,12 +226,6 @@ export async function produceBlockBody<T extends BlockType>(
225
226
  stateAfterParentPayload = currentState.withParentPayloadApplied(parentExecutionRequests);
226
227
  } else {
227
228
  parentBlockHash = currentState.latestExecutionPayloadBid.parentBlockHash;
228
- // At gloas genesis the committed bid has no prior EL block to reference
229
- // (`bid.parentBlockHash` is zero). Fall back to `bid.blockHash` (= eth1 genesis hash) so the
230
- // FCU to the EL carries a valid head. Post-genesis bids always reference a non-zero parent.
231
- if (byteArrayEquals(parentBlockHash, ZERO_HASH)) {
232
- parentBlockHash = currentState.latestExecutionPayloadBid.blockHash;
233
- }
234
229
  parentExecutionRequests = ssz.electra.ExecutionRequests.defaultValue();
235
230
  }
236
231
  const prepareRes = await prepareExecutionPayload(
@@ -317,6 +312,7 @@ export async function produceBlockBody<T extends BlockType>(
317
312
  gloasResult.executionRequests = executionRequests;
318
313
  gloasResult.blobsBundle = blobsBundle;
319
314
  gloasResult.cells = cells;
315
+ gloasResult.parentBlockRoot = fromHex(parentBlock.blockRoot);
320
316
 
321
317
  const fetchedTime = Date.now() / 1000 - computeTimeAtSlot(this.config, blockSlot, this.genesisTime);
322
318
  this.metrics?.blockPayload.payloadFetchedTime.observe({prepType}, fetchedTime);
@@ -1,7 +1,7 @@
1
1
  import {routes} from "@lodestar/api";
2
2
  import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
3
3
  import {IBeaconStateView, computeEpochAtSlot} from "@lodestar/state-transition";
4
- import {BeaconBlock, Epoch, RootHex, Slot, isGloasBeaconBlock, phase0} from "@lodestar/types";
4
+ import {BeaconBlock, Epoch, RootHex, Slot, phase0} from "@lodestar/types";
5
5
  import {Logger, toRootHex} from "@lodestar/utils";
6
6
  import {Metrics} from "../../metrics/index.js";
7
7
  import {JobItemQueue} from "../../util/queue/index.js";
@@ -88,12 +88,7 @@ export class QueuedStateRegenerator implements IStateRegenerator {
88
88
  */
89
89
  getPreStateSync(block: BeaconBlock): IBeaconStateView | null {
90
90
  const parentRoot = toRootHex(block.parentRoot);
91
- const parentBlock = isGloasBeaconBlock(block)
92
- ? this.forkChoice.getBlockHexAndBlockHash(
93
- parentRoot,
94
- toRootHex(block.body.signedExecutionPayloadBid.message.parentBlockHash)
95
- )
96
- : this.forkChoice.getBlockHexDefaultStatus(parentRoot);
91
+ const parentBlock = this.forkChoice.getBlockHexDefaultStatus(parentRoot);
97
92
  if (!parentBlock) {
98
93
  throw new RegenError({
99
94
  code: RegenErrorCode.BLOCK_NOT_IN_FORKCHOICE,
@@ -9,7 +9,7 @@ import {
9
9
  computeEpochAtSlot,
10
10
  computeStartSlotAtEpoch,
11
11
  } from "@lodestar/state-transition";
12
- import {BeaconBlock, RootHex, SignedBeaconBlock, Slot, isGloasBeaconBlock} from "@lodestar/types";
12
+ import {BeaconBlock, RootHex, SignedBeaconBlock, Slot} from "@lodestar/types";
13
13
  import {Logger, fromHex, toRootHex} from "@lodestar/utils";
14
14
  import {IBeaconDb} from "../../db/index.js";
15
15
  import {Metrics} from "../../metrics/index.js";
@@ -57,12 +57,7 @@ export class StateRegenerator implements IStateRegeneratorInternal {
57
57
  regenCaller: RegenCaller
58
58
  ): Promise<IBeaconStateView> {
59
59
  const parentRoot = toRootHex(block.parentRoot);
60
- const parentBlock = isGloasBeaconBlock(block)
61
- ? this.modules.forkChoice.getBlockHexAndBlockHash(
62
- parentRoot,
63
- toRootHex(block.body.signedExecutionPayloadBid.message.parentBlockHash)
64
- )
65
- : this.modules.forkChoice.getBlockHexDefaultStatus(parentRoot);
60
+ const parentBlock = this.modules.forkChoice.getBlockHexDefaultStatus(parentRoot);
66
61
  if (!parentBlock) {
67
62
  throw new RegenError({
68
63
  code: RegenErrorCode.BLOCK_NOT_IN_FORKCHOICE,