@lodestar/beacon-node 1.42.0-dev.8b81ee6c3e → 1.42.0-dev.972ec54bb9

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 (204) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +22 -2
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/pool/index.js +4 -0
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
  8. package/lib/api/impl/beacon/state/index.js +13 -10
  9. package/lib/api/impl/beacon/state/index.js.map +1 -1
  10. package/lib/api/impl/lodestar/index.d.ts.map +1 -1
  11. package/lib/api/impl/lodestar/index.js +4 -0
  12. package/lib/api/impl/lodestar/index.js.map +1 -1
  13. package/lib/api/impl/validator/index.d.ts.map +1 -1
  14. package/lib/api/impl/validator/index.js +9 -3
  15. package/lib/api/impl/validator/index.js.map +1 -1
  16. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  17. package/lib/chain/blocks/importBlock.js +6 -4
  18. package/lib/chain/blocks/importBlock.js.map +1 -1
  19. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  20. package/lib/chain/blocks/importExecutionPayload.js +14 -5
  21. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  22. package/lib/chain/blocks/index.js +1 -1
  23. package/lib/chain/blocks/index.js.map +1 -1
  24. package/lib/chain/blocks/types.d.ts +3 -3
  25. package/lib/chain/blocks/types.d.ts.map +1 -1
  26. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts.map +1 -1
  27. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +3 -2
  28. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
  29. package/lib/chain/blocks/verifyBlocksSignatures.d.ts.map +1 -1
  30. package/lib/chain/blocks/verifyBlocksSignatures.js +4 -2
  31. package/lib/chain/blocks/verifyBlocksSignatures.js.map +1 -1
  32. package/lib/chain/chain.d.ts +3 -1
  33. package/lib/chain/chain.d.ts.map +1 -1
  34. package/lib/chain/chain.js +26 -9
  35. package/lib/chain/chain.js.map +1 -1
  36. package/lib/chain/emitter.d.ts +27 -5
  37. package/lib/chain/emitter.d.ts.map +1 -1
  38. package/lib/chain/emitter.js +12 -3
  39. package/lib/chain/emitter.js.map +1 -1
  40. package/lib/chain/errors/blockError.d.ts +11 -1
  41. package/lib/chain/errors/blockError.d.ts.map +1 -1
  42. package/lib/chain/errors/blockError.js +4 -0
  43. package/lib/chain/errors/blockError.js.map +1 -1
  44. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  45. package/lib/chain/forkChoice/index.js +10 -8
  46. package/lib/chain/forkChoice/index.js.map +1 -1
  47. package/lib/chain/interface.d.ts +4 -1
  48. package/lib/chain/interface.d.ts.map +1 -1
  49. package/lib/chain/interface.js.map +1 -1
  50. package/lib/chain/lightClient/index.d.ts +2 -2
  51. package/lib/chain/lightClient/index.d.ts.map +1 -1
  52. package/lib/chain/lightClient/index.js +7 -0
  53. package/lib/chain/lightClient/index.js.map +1 -1
  54. package/lib/chain/opPools/aggregatedAttestationPool.d.ts.map +1 -1
  55. package/lib/chain/opPools/aggregatedAttestationPool.js +5 -2
  56. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  57. package/lib/chain/opPools/executionPayloadBidPool.d.ts +2 -2
  58. package/lib/chain/opPools/executionPayloadBidPool.d.ts.map +1 -1
  59. package/lib/chain/opPools/executionPayloadBidPool.js +2 -2
  60. package/lib/chain/opPools/executionPayloadBidPool.js.map +1 -1
  61. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  62. package/lib/chain/prepareNextSlot.js +7 -1
  63. package/lib/chain/prepareNextSlot.js.map +1 -1
  64. package/lib/chain/produceBlock/computeNewStateRoot.d.ts +3 -3
  65. package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
  66. package/lib/chain/produceBlock/computeNewStateRoot.js +8 -8
  67. package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
  68. package/lib/chain/produceBlock/produceBlockBody.d.ts +5 -5
  69. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  70. package/lib/chain/produceBlock/produceBlockBody.js +23 -4
  71. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  72. package/lib/chain/seenCache/seenGossipBlockInput.d.ts +1 -1
  73. package/lib/chain/seenCache/seenGossipBlockInput.d.ts.map +1 -1
  74. package/lib/chain/seenCache/seenGossipBlockInput.js +2 -2
  75. package/lib/chain/seenCache/seenGossipBlockInput.js.map +1 -1
  76. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +1 -1
  77. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  78. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +2 -2
  79. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  80. package/lib/chain/validation/block.d.ts.map +1 -1
  81. package/lib/chain/validation/block.js +27 -5
  82. package/lib/chain/validation/block.js.map +1 -1
  83. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  84. package/lib/chain/validation/executionPayloadBid.js +7 -4
  85. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  86. package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
  87. package/lib/chain/validation/executionPayloadEnvelope.js +6 -1
  88. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  89. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  90. package/lib/chain/validation/payloadAttestationMessage.js +4 -1
  91. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  92. package/lib/chain/validation/syncCommittee.d.ts.map +1 -1
  93. package/lib/chain/validation/syncCommittee.js +4 -0
  94. package/lib/chain/validation/syncCommittee.js.map +1 -1
  95. package/lib/chain/validation/syncCommitteeContributionAndProof.js +4 -1
  96. package/lib/chain/validation/syncCommitteeContributionAndProof.js.map +1 -1
  97. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  98. package/lib/chain/validatorMonitor.js +3 -3
  99. package/lib/chain/validatorMonitor.js.map +1 -1
  100. package/lib/metrics/metrics/lodestar.d.ts +20 -0
  101. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  102. package/lib/metrics/metrics/lodestar.js +33 -0
  103. package/lib/metrics/metrics/lodestar.js.map +1 -1
  104. package/lib/network/interface.d.ts +1 -0
  105. package/lib/network/interface.d.ts.map +1 -1
  106. package/lib/network/network.d.ts +1 -0
  107. package/lib/network/network.d.ts.map +1 -1
  108. package/lib/network/network.js +3 -0
  109. package/lib/network/network.js.map +1 -1
  110. package/lib/network/processor/extractSlotRootFns.d.ts +1 -1
  111. package/lib/network/processor/extractSlotRootFns.d.ts.map +1 -1
  112. package/lib/network/processor/extractSlotRootFns.js +25 -5
  113. package/lib/network/processor/extractSlotRootFns.js.map +1 -1
  114. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  115. package/lib/network/processor/gossipHandlers.js +42 -12
  116. package/lib/network/processor/gossipHandlers.js.map +1 -1
  117. package/lib/network/processor/index.d.ts +11 -1
  118. package/lib/network/processor/index.d.ts.map +1 -1
  119. package/lib/network/processor/index.js +234 -22
  120. package/lib/network/processor/index.js.map +1 -1
  121. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
  122. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +2 -1
  123. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  124. package/lib/network/reqresp/handlers/beaconBlocksByRoot.d.ts.map +1 -1
  125. package/lib/network/reqresp/handlers/beaconBlocksByRoot.js +2 -0
  126. package/lib/network/reqresp/handlers/beaconBlocksByRoot.js.map +1 -1
  127. package/lib/network/reqresp/handlers/blobSidecarsByRange.d.ts +2 -2
  128. package/lib/network/reqresp/handlers/blobSidecarsByRange.d.ts.map +1 -1
  129. package/lib/network/reqresp/handlers/blobSidecarsByRange.js +7 -3
  130. package/lib/network/reqresp/handlers/blobSidecarsByRange.js.map +1 -1
  131. package/lib/network/reqresp/handlers/blobSidecarsByRoot.d.ts.map +1 -1
  132. package/lib/network/reqresp/handlers/blobSidecarsByRoot.js +6 -0
  133. package/lib/network/reqresp/handlers/blobSidecarsByRoot.js.map +1 -1
  134. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts +2 -2
  135. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  136. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +7 -3
  137. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  138. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
  139. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +2 -1
  140. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
  141. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.d.ts.map +1 -1
  142. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js +3 -8
  143. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js.map +1 -1
  144. package/lib/node/nodejs.d.ts.map +1 -1
  145. package/lib/node/nodejs.js +4 -1
  146. package/lib/node/nodejs.js.map +1 -1
  147. package/lib/node/notifier.d.ts.map +1 -1
  148. package/lib/node/notifier.js +2 -2
  149. package/lib/node/notifier.js.map +1 -1
  150. package/lib/sync/unknownBlock.js +2 -2
  151. package/lib/sync/unknownBlock.js.map +1 -1
  152. package/lib/util/sszBytes.d.ts +25 -1
  153. package/lib/util/sszBytes.d.ts.map +1 -1
  154. package/lib/util/sszBytes.js +155 -1
  155. package/lib/util/sszBytes.js.map +1 -1
  156. package/package.json +15 -15
  157. package/src/api/impl/beacon/blocks/index.ts +31 -2
  158. package/src/api/impl/beacon/pool/index.ts +4 -0
  159. package/src/api/impl/beacon/state/index.ts +15 -15
  160. package/src/api/impl/lodestar/index.ts +4 -0
  161. package/src/api/impl/validator/index.ts +9 -2
  162. package/src/chain/blocks/importBlock.ts +11 -7
  163. package/src/chain/blocks/importExecutionPayload.ts +15 -5
  164. package/src/chain/blocks/index.ts +1 -1
  165. package/src/chain/blocks/types.ts +3 -3
  166. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +2 -1
  167. package/src/chain/blocks/verifyBlocksSignatures.ts +9 -2
  168. package/src/chain/chain.ts +35 -9
  169. package/src/chain/emitter.ts +23 -5
  170. package/src/chain/errors/blockError.ts +7 -1
  171. package/src/chain/forkChoice/index.ts +11 -8
  172. package/src/chain/interface.ts +7 -0
  173. package/src/chain/lightClient/index.ts +15 -3
  174. package/src/chain/opPools/aggregatedAttestationPool.ts +6 -1
  175. package/src/chain/opPools/executionPayloadBidPool.ts +3 -3
  176. package/src/chain/prepareNextSlot.ts +8 -0
  177. package/src/chain/produceBlock/computeNewStateRoot.ts +11 -10
  178. package/src/chain/produceBlock/produceBlockBody.ts +40 -10
  179. package/src/chain/seenCache/seenGossipBlockInput.ts +2 -2
  180. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +2 -2
  181. package/src/chain/validation/block.ts +30 -7
  182. package/src/chain/validation/executionPayloadBid.ts +7 -3
  183. package/src/chain/validation/executionPayloadEnvelope.ts +10 -1
  184. package/src/chain/validation/payloadAttestationMessage.ts +4 -0
  185. package/src/chain/validation/syncCommittee.ts +5 -1
  186. package/src/chain/validation/syncCommitteeContributionAndProof.ts +5 -1
  187. package/src/chain/validatorMonitor.ts +3 -2
  188. package/src/metrics/metrics/lodestar.ts +34 -0
  189. package/src/network/interface.ts +1 -0
  190. package/src/network/network.ts +4 -0
  191. package/src/network/processor/extractSlotRootFns.ts +32 -6
  192. package/src/network/processor/gossipHandlers.ts +49 -12
  193. package/src/network/processor/index.ts +304 -22
  194. package/src/network/reqresp/handlers/beaconBlocksByRange.ts +3 -1
  195. package/src/network/reqresp/handlers/beaconBlocksByRoot.ts +3 -0
  196. package/src/network/reqresp/handlers/blobSidecarsByRange.ts +15 -3
  197. package/src/network/reqresp/handlers/blobSidecarsByRoot.ts +11 -0
  198. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +19 -3
  199. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +3 -1
  200. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRoot.ts +3 -12
  201. package/src/node/nodejs.ts +5 -2
  202. package/src/node/notifier.ts +7 -2
  203. package/src/sync/unknownBlock.ts +3 -3
  204. package/src/util/sszBytes.ts +203 -1
@@ -14,7 +14,16 @@ import {
14
14
  isForkPostBellatrix,
15
15
  isForkPostGloas,
16
16
  } from "@lodestar/params";
17
- import {G2_POINT_AT_INFINITY, IBeaconStateView, computeTimeAtSlot} from "@lodestar/state-transition";
17
+ import {
18
+ G2_POINT_AT_INFINITY,
19
+ IBeaconStateView,
20
+ type IBeaconStateViewBellatrix,
21
+ computeTimeAtSlot,
22
+ isParentBlockFull,
23
+ isStatePostBellatrix,
24
+ isStatePostCapella,
25
+ isStatePostGloas,
26
+ } from "@lodestar/state-transition";
18
27
  import {
19
28
  BLSPubkey,
20
29
  BLSSignature,
@@ -103,11 +112,11 @@ export type ProduceFullGloas = {
103
112
  blobsBundle: BlobsBundle<ForkPostGloas>;
104
113
  cells: fulu.Cell[][];
105
114
  /**
106
- * Cached envelope state root computed during block production.
115
+ * Cached payload envelope state root computed during block production.
107
116
  * This is the state root after running `processExecutionPayloadEnvelope` on the
108
117
  * post-block state, and later used to construct the `ExecutionPayloadEnvelope`.
109
118
  */
110
- envelopeStateRoot: Root;
119
+ payloadEnvelopeStateRoot: Root;
111
120
  };
112
121
  export type ProduceFullFulu = {
113
122
  type: BlockType.Full;
@@ -191,6 +200,10 @@ export async function produceBlockBody<T extends BlockType>(
191
200
  this.logger.verbose("Producing beacon block body", logMeta);
192
201
 
193
202
  if (isForkPostGloas(fork)) {
203
+ if (!isStatePostGloas(currentState)) {
204
+ throw new Error("Expected Gloas state for Gloas block production");
205
+ }
206
+
194
207
  // TODO GLOAS: support non self-building here, the block type differentiation between
195
208
  // full and blinded no longer makes sense in gloas, it might be a good idea to move
196
209
  // this into a completely separate function and have pre/post gloas more separated
@@ -297,6 +310,10 @@ export async function produceBlockBody<T extends BlockType>(
297
310
  shouldOverrideBuilder,
298
311
  });
299
312
  } else if (isForkPostBellatrix(fork)) {
313
+ if (!isStatePostBellatrix(currentState)) {
314
+ throw new Error("Expected Bellatrix state for execution block production");
315
+ }
316
+
300
317
  const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice);
301
318
  const finalizedBlockHash = this.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
302
319
  const feeRecipient = requestedFeeRecipient ?? this.beaconProposerCache.getOrDefault(proposerIndex);
@@ -598,7 +615,7 @@ export async function prepareExecutionPayload(
598
615
  parentBlockRoot: Root,
599
616
  safeBlockHash: RootHex,
600
617
  finalizedBlockHash: RootHex,
601
- state: IBeaconStateView,
618
+ state: IBeaconStateViewBellatrix,
602
619
  suggestedFeeRecipient: string
603
620
  ): Promise<{prepType: PayloadPreparationType; payloadId: PayloadId}> {
604
621
  const parentHash = state.latestBlockHash;
@@ -666,7 +683,7 @@ async function prepareExecutionPayloadHeader(
666
683
  config: ChainForkConfig;
667
684
  },
668
685
  fork: ForkPostBellatrix,
669
- state: IBeaconStateView,
686
+ state: IBeaconStateViewBellatrix,
670
687
  proposerPubKey: BLSPubkey
671
688
  ): Promise<{
672
689
  header: ExecutionPayloadHeader;
@@ -693,7 +710,7 @@ export function getPayloadAttributesForSSE(
693
710
  prepareSlot,
694
711
  parentBlockRoot,
695
712
  feeRecipient,
696
- }: {prepareState: IBeaconStateView; prepareSlot: Slot; parentBlockRoot: Root; feeRecipient: string}
713
+ }: {prepareState: IBeaconStateViewBellatrix; prepareSlot: Slot; parentBlockRoot: Root; feeRecipient: string}
697
714
  ): SSEPayloadAttributes {
698
715
  const parentHash = prepareState.latestBlockHash;
699
716
  const payloadAttributes = preparePayloadAttributes(fork, chain, {
@@ -736,7 +753,7 @@ function preparePayloadAttributes(
736
753
  parentBlockRoot,
737
754
  feeRecipient,
738
755
  }: {
739
- prepareState: IBeaconStateView;
756
+ prepareState: IBeaconStateViewBellatrix;
740
757
  prepareSlot: Slot;
741
758
  parentBlockRoot: Root;
742
759
  feeRecipient: string;
@@ -751,9 +768,22 @@ function preparePayloadAttributes(
751
768
  };
752
769
 
753
770
  if (ForkSeq[fork] >= ForkSeq.capella) {
754
- // withdrawals logic is now fork aware as it changes on electra fork post capella
755
- (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
756
- prepareState.getExpectedWithdrawals().expectedWithdrawals;
771
+ if (!isStatePostCapella(prepareState)) {
772
+ throw new Error("Expected Capella state for withdrawals");
773
+ }
774
+
775
+ if (isStatePostGloas(prepareState) && !isParentBlockFull(prepareState)) {
776
+ // When the parent block is empty, state.payloadExpectedWithdrawals holds a batch
777
+ // already deducted from CL balances but never credited on the EL (the envelope
778
+ // was not delivered). The next payload must carry those same withdrawals to
779
+ // restore CL/EL consistency, otherwise validators permanently lose that balance.
780
+ (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
781
+ prepareState.payloadExpectedWithdrawals;
782
+ } else {
783
+ // withdrawals logic is now fork aware as it changes on electra fork post capella
784
+ (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
785
+ prepareState.getExpectedWithdrawals().expectedWithdrawals;
786
+ }
757
787
  }
758
788
 
759
789
  if (ForkSeq[fork] >= ForkSeq.deneb) {
@@ -149,8 +149,8 @@ export class SeenBlockInput {
149
149
  });
150
150
  }
151
151
 
152
- has(rootHex: RootHex): boolean {
153
- return this.blockInputs.has(rootHex);
152
+ hasBlock(rootHex: RootHex): boolean {
153
+ return this.blockInputs.get(rootHex)?.hasBlock() ?? false;
154
154
  }
155
155
 
156
156
  get(rootHex: RootHex): IBlockInput | undefined {
@@ -84,8 +84,8 @@ export class SeenPayloadEnvelopeInput {
84
84
  return this.payloadInputs.get(blockRootHex);
85
85
  }
86
86
 
87
- has(blockRootHex: RootHex): boolean {
88
- return this.payloadInputs.has(blockRootHex);
87
+ hasPayload(blockRootHex: RootHex): boolean {
88
+ return this.payloadInputs.get(blockRootHex)?.hasPayloadEnvelope() ?? false;
89
89
  }
90
90
 
91
91
  prune(blockRootHex: RootHex): void {
@@ -1,4 +1,5 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
+ import {ExecutionStatus} from "@lodestar/fork-choice";
2
3
  import {ForkName, isForkPostBellatrix, isForkPostDeneb, isForkPostGloas} from "@lodestar/params";
3
4
  import {
4
5
  computeEpochAtSlot,
@@ -6,6 +7,7 @@ import {
6
7
  computeTimeAtSlot,
7
8
  getBlockProposerSignatureSet,
8
9
  isExecutionBlockBodyType,
10
+ isStatePostBellatrix,
9
11
  } from "@lodestar/state-transition";
10
12
  import {SignedBeaconBlock, deneb, gloas, isGloasBeaconBlock} from "@lodestar/types";
11
13
  import {byteArrayEquals, sleep, toRootHex} from "@lodestar/utils";
@@ -70,12 +72,7 @@ export async function validateGossipBlock(
70
72
  // [REJECT] The current finalized_checkpoint is an ancestor of block -- i.e.
71
73
  // get_ancestor(store, block.parent_root, compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)) == store.finalized_checkpoint.root
72
74
  const parentRoot = toRootHex(block.parentRoot);
73
- const parentBlock = isGloasBeaconBlock(block)
74
- ? chain.forkChoice.getBlockHexAndBlockHash(
75
- parentRoot,
76
- toRootHex(block.body.signedExecutionPayloadBid.message.parentBlockHash)
77
- )
78
- : chain.forkChoice.getBlockHexDefaultStatus(parentRoot);
75
+ const parentBlock = chain.forkChoice.getBlockHexDefaultStatus(parentRoot);
79
76
  if (parentBlock === null) {
80
77
  // If fork choice does *not* consider the parent to be a descendant of the finalized block,
81
78
  // then there are two more cases:
@@ -90,6 +87,27 @@ export async function validateGossipBlock(
90
87
  throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_UNKNOWN, parentRoot});
91
88
  }
92
89
 
90
+ // [IGNORE] The block's parent (defined by `block.parent_root`) passes all validation
91
+ // (including execution node verification of the `block.body.execution_payload`)
92
+ if (isForkPostBellatrix(fork) && parentBlock.executionStatus === ExecutionStatus.Invalid) {
93
+ throw new BlockGossipError(GossipAction.IGNORE, {
94
+ code: BlockErrorCode.PARENT_EXECUTION_INVALID,
95
+ parentRoot,
96
+ });
97
+ }
98
+
99
+ // [IGNORE] The block's parent execution payload (defined by bid.parent_block_hash) has been seen
100
+ // (via gossip or non-gossip sources) (a client MAY queue blocks for processing once the parent payload is retrieved).
101
+ if (isGloasBeaconBlock(block)) {
102
+ const parentBlockHashHex = toRootHex(block.body.signedExecutionPayloadBid.message.parentBlockHash);
103
+ if (chain.forkChoice.getBlockHexAndBlockHash(parentRoot, parentBlockHashHex) === null) {
104
+ throw new BlockGossipError(GossipAction.IGNORE, {
105
+ code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN,
106
+ parentBlockHash: parentBlockHashHex,
107
+ });
108
+ }
109
+ }
110
+
93
111
  // [IGNORE] The attestation head block is too far behind the attestation slot, causing many skip slots.
94
112
  // This is deemed a DoS risk because we need to get the proposerShuffling. To get the shuffling we have
95
113
  // to do a bunch of epoch transitions, the longer the distance between the parent and block,
@@ -174,7 +192,7 @@ export async function validateGossipBlock(
174
192
  if (isForkPostBellatrix(fork) && !isForkPostGloas(fork)) {
175
193
  if (!isExecutionBlockBodyType(block.body)) throw Error("Not execution block body type");
176
194
  const executionPayload = block.body.executionPayload;
177
- if (blockState.isExecutionStateType && blockState.isExecutionEnabled(block)) {
195
+ if (isStatePostBellatrix(blockState) && blockState.isExecutionStateType && blockState.isExecutionEnabled(block)) {
178
196
  const expectedTimestamp = computeTimeAtSlot(config, blockSlot, chain.genesisTime);
179
197
  if (executionPayload.timestamp !== computeTimeAtSlot(config, blockSlot, chain.genesisTime)) {
180
198
  throw new BlockGossipError(GossipAction.REJECT, {
@@ -186,6 +204,11 @@ export async function validateGossipBlock(
186
204
  }
187
205
  }
188
206
 
207
+ // [REJECT] The proposer index is a valid validator index
208
+ if (proposerIndex >= blockState.validatorCount) {
209
+ throw new BlockGossipError(GossipAction.REJECT, {code: BlockErrorCode.UNKNOWN_PROPOSER, proposerIndex});
210
+ }
211
+
189
212
  // [REJECT] The proposer signature, signed_beacon_block.signature, is valid with respect to the proposer_index pubkey.
190
213
  if (!chain.seenBlockInputCache.isVerifiedProposerSignature(blockSlot, blockRoot, signedBlock.signature)) {
191
214
  const signatureSet = getBlockProposerSignatureSet(chain.config, signedBlock);
@@ -3,6 +3,7 @@ import {
3
3
  createSingleSignatureSetFromComponents,
4
4
  getExecutionPayloadBidSigningRoot,
5
5
  isActiveBuilder,
6
+ isStatePostGloas,
6
7
  } from "@lodestar/state-transition";
7
8
  import {gloas} from "@lodestar/types";
8
9
  import {toRootHex} from "@lodestar/utils";
@@ -32,6 +33,9 @@ async function validateExecutionPayloadBid(
32
33
  const parentBlockRootHex = toRootHex(bid.parentBlockRoot);
33
34
  const parentBlockHashHex = toRootHex(bid.parentBlockHash);
34
35
  const state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.validateGossipExecutionPayloadBid);
36
+ if (!isStatePostGloas(state)) {
37
+ throw new Error(`Expected gloas+ state for execution payload bid validation, got fork=${state.forkName}`);
38
+ }
35
39
 
36
40
  // [IGNORE] `bid.slot` is the current slot or the next slot.
37
41
  const currentSlot = chain.clock.currentSlot;
@@ -83,9 +87,9 @@ async function validateExecutionPayloadBid(
83
87
  });
84
88
  }
85
89
 
86
- // [IGNORE] this bid is the highest value bid seen for the corresponding slot
87
- // and the given parent block hash.
88
- const bestBid = chain.executionPayloadBidPool.getBestBid(parentBlockRootHex, parentBlockHashHex, bid.slot);
90
+ // [IGNORE] this bid is the highest value bid seen for the tuple
91
+ // `(bid.slot, bid.parent_block_hash, bid.parent_block_root)`.
92
+ const bestBid = chain.executionPayloadBidPool.getBestBid(bid.slot, parentBlockHashHex, parentBlockRootHex);
89
93
  if (bestBid !== null && bestBid.value >= bid.value) {
90
94
  throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
91
95
  code: ExecutionPayloadBidErrorCode.BID_TOO_LOW,
@@ -1,5 +1,9 @@
1
1
  import {PayloadStatus} from "@lodestar/fork-choice";
2
- import {computeStartSlotAtEpoch, getExecutionPayloadEnvelopeSignatureSet} from "@lodestar/state-transition";
2
+ import {
3
+ computeStartSlotAtEpoch,
4
+ getExecutionPayloadEnvelopeSignatureSet,
5
+ isStatePostGloas,
6
+ } from "@lodestar/state-transition";
3
7
  import {gloas} from "@lodestar/types";
4
8
  import {toRootHex} from "@lodestar/utils";
5
9
  import {ExecutionPayloadEnvelopeError, ExecutionPayloadEnvelopeErrorCode, GossipAction} from "../errors/index.js";
@@ -113,7 +117,12 @@ async function validateExecutionPayloadEnvelope(
113
117
  slot: envelope.slot,
114
118
  });
115
119
  });
120
+ if (!isStatePostGloas(blockState)) {
121
+ throw new Error(`Expected gloas+ state for execution payload envelope validation, got fork=${blockState.forkName}`);
122
+ }
116
123
 
124
+ // [REJECT] `signed_execution_payload_envelope.signature` is valid as verified
125
+ // by `verify_execution_payload_envelope_signature`.
117
126
  const signatureSet = getExecutionPayloadEnvelopeSignatureSet(
118
127
  chain.config,
119
128
  chain.pubkeyCache,
@@ -2,6 +2,7 @@ import {
2
2
  computeEpochAtSlot,
3
3
  createSingleSignatureSetFromComponents,
4
4
  getPayloadAttestationDataSigningRoot,
5
+ isStatePostGloas,
5
6
  } from "@lodestar/state-transition";
6
7
  import {RootHex, gloas, ssz} from "@lodestar/types";
7
8
  import {toRootHex} from "@lodestar/utils";
@@ -66,6 +67,9 @@ async function validatePayloadAttestationMessage(
66
67
  }
67
68
 
68
69
  const state = chain.getHeadState();
70
+ if (!isStatePostGloas(state)) {
71
+ throw new Error(`Expected gloas+ state for payload attestation validation, got fork=${state.forkName}`);
72
+ }
69
73
 
70
74
  // [REJECT] The message's block `data.beacon_block_root` passes validation.
71
75
  // TODO GLOAS: implement this. Technically if we cannot get proto block from fork choice,
@@ -1,5 +1,5 @@
1
1
  import {SYNC_COMMITTEE_SUBNET_COUNT, SYNC_COMMITTEE_SUBNET_SIZE} from "@lodestar/params";
2
- import {IBeaconStateView} from "@lodestar/state-transition";
2
+ import {IBeaconStateView, isStatePostAltair} from "@lodestar/state-transition";
3
3
  import {SubnetID, altair} from "@lodestar/types";
4
4
  import {toRootHex} from "@lodestar/utils";
5
5
  import {GossipAction, SyncCommitteeError, SyncCommitteeErrorCode} from "../errors/index.js";
@@ -148,6 +148,10 @@ function getIndicesInSubcommittee(
148
148
  subnet: SubnetID,
149
149
  data: Pick<altair.SyncCommitteeMessage, "slot" | "validatorIndex">
150
150
  ): IndexInSubcommittee[] | null {
151
+ if (!isStatePostAltair(headState)) {
152
+ return null;
153
+ }
154
+
151
155
  const syncCommittee = headState.getIndexedSyncCommittee(data.slot);
152
156
  const indexesInCommittee = syncCommittee.validatorIndexMap.get(data.validatorIndex);
153
157
  if (indexesInCommittee === undefined) {
@@ -1,5 +1,5 @@
1
1
  import {SYNC_COMMITTEE_SUBNET_SIZE} from "@lodestar/params";
2
- import {IBeaconStateView, isSyncCommitteeAggregator} from "@lodestar/state-transition";
2
+ import {IBeaconStateView, isStatePostAltair, isSyncCommitteeAggregator} from "@lodestar/state-transition";
3
3
  import {ValidatorIndex, altair} from "@lodestar/types";
4
4
  import {GossipAction, SyncCommitteeError, SyncCommitteeErrorCode} from "../errors/index.js";
5
5
  import {IBeaconChain} from "../interface.js";
@@ -107,6 +107,10 @@ function getContributionIndices(
107
107
  state: IBeaconStateView,
108
108
  contribution: altair.SyncCommitteeContribution
109
109
  ): ValidatorIndex[] {
110
+ if (!isStatePostAltair(state)) {
111
+ throw new Error("Expected Altair state for sync committee contribution");
112
+ }
113
+
110
114
  const startIndex = contribution.subcommitteeIndex * SYNC_COMMITTEE_SUBNET_SIZE;
111
115
 
112
116
  const syncCommittee = state.getIndexedSyncCommittee(contribution.slot);
@@ -1,5 +1,5 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {ForkSeq, MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH} from "@lodestar/params";
2
+ import {MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH} from "@lodestar/params";
3
3
  import {
4
4
  IBeaconStateView,
5
5
  ParticipationFlags,
@@ -7,6 +7,7 @@ import {
7
7
  computeStartSlotAtEpoch,
8
8
  computeTimeAtSlot,
9
9
  getCurrentSlot,
10
+ isStatePostAltair,
10
11
  parseAttesterFlags,
11
12
  parseParticipationFlags,
12
13
  } from "@lodestar/state-transition";
@@ -740,7 +741,7 @@ export function createValidatorMonitor(
740
741
 
741
742
  const rootCache = new RootHexCache(headState);
742
743
 
743
- if (config.getForkSeq(headState.slot) >= ForkSeq.altair) {
744
+ if (isStatePostAltair(headState)) {
744
745
  const prevEpochStartSlot = computeStartSlotAtEpoch(prevEpoch);
745
746
  const prevEpochTargetRoot = toRootHex(headState.getBlockRootAtSlot(prevEpochStartSlot));
746
747
 
@@ -1746,6 +1746,40 @@ export function createLodestarMetrics(
1746
1746
  }),
1747
1747
  },
1748
1748
 
1749
+ // some gossip messages need to wait for payload to be processed before they can be processed
1750
+ awaitingPayloadGossipMessages: {
1751
+ queue: register.gauge<{topic: GossipType}>({
1752
+ name: "lodestar_awaiting_payload_gossip_messages_total",
1753
+ help: "Total number of gossip messages waiting for payload to be processed",
1754
+ labelNames: ["topic"],
1755
+ }),
1756
+ countPerSlot: register.gauge({
1757
+ name: "lodestar_awaiting_payload_gossip_messages_per_slot_total",
1758
+ help: "Total number of gossip messages waiting for payload to be processed per slot",
1759
+ }),
1760
+ resolve: register.gauge<{topic: GossipType}>({
1761
+ name: "lodestar_awaiting_payload_gossip_messages_resolve_total",
1762
+ help: "Total number of gossip messages are reprocessed",
1763
+ labelNames: ["topic"],
1764
+ }),
1765
+ waitSecBeforeResolve: register.gauge<{topic: GossipType}>({
1766
+ name: "lodestar_awaiting_payload_gossip_messages_wait_time_resolve_seconds",
1767
+ help: "Time to wait for unknown payload in seconds",
1768
+ labelNames: ["topic"],
1769
+ }),
1770
+ // having 2 labels here is not great for performance, however it's rarely happening and having the reason label is important for debugging
1771
+ reject: register.gauge<{reason: ReprocessRejectReason; topic: GossipType}>({
1772
+ name: "lodestar_awaiting_payload_gossip_messages_reject_total",
1773
+ help: "Total number of gossip messages are rejected to reprocess",
1774
+ labelNames: ["reason", "topic"],
1775
+ }),
1776
+ waitSecBeforeReject: register.gauge<{reason: ReprocessRejectReason; topic: GossipType}>({
1777
+ name: "lodestar_awaiting_payload_gossip_messages_wait_time_reject_seconds",
1778
+ help: "Time to wait for unknown payload before being rejected",
1779
+ labelNames: ["reason", "topic"],
1780
+ }),
1781
+ },
1782
+
1749
1783
  lightclientServer: {
1750
1784
  onSyncAggregate: register.gauge<{event: string}>({
1751
1785
  name: "lodestar_lightclient_server_on_sync_aggregate_event_total",
@@ -74,6 +74,7 @@ export interface INetwork extends INetworkCorePublic {
74
74
  shouldAggregate(subnet: SubnetID, slot: Slot): boolean;
75
75
  reStatusPeers(peers: PeerIdStr[]): Promise<void>;
76
76
  searchUnknownBlock(slotRoot: SlotRootHex, source: BlockInputSource, peer?: PeerIdStr): void;
77
+ searchUnknownEnvelope(slotRoot: SlotRootHex, source: BlockInputSource, peer?: PeerIdStr): void;
77
78
  // ReqResp
78
79
  sendBeaconBlocksByRange(peerId: PeerIdStr, request: phase0.BeaconBlocksByRangeRequest): Promise<SignedBeaconBlock[]>;
79
80
  sendBeaconBlocksByRoot(peerId: PeerIdStr, request: BeaconBlocksByRootRequest): Promise<SignedBeaconBlock[]>;
@@ -285,6 +285,10 @@ export class Network implements INetwork {
285
285
  this.networkProcessor.searchUnknownBlock(slotRoot, source, peer);
286
286
  }
287
287
 
288
+ searchUnknownEnvelope(slotRoot: SlotRootHex, source: BlockInputSource, peer?: PeerIdStr): void {
289
+ this.networkProcessor.searchUnknownEnvelope(slotRoot, source, peer);
290
+ }
291
+
288
292
  async reportPeer(peer: PeerIdStr, action: PeerAction, actionName: string): Promise<void> {
289
293
  return this.core.reportPeer(peer, action, actionName);
290
294
  }
@@ -1,23 +1,25 @@
1
- import {ForkName, isForkPostGloas} from "@lodestar/params";
1
+ import {ForkName, ForkSeq} from "@lodestar/params";
2
2
  import {SlotOptionalRoot, SlotRootHex} from "@lodestar/types";
3
3
  import {
4
4
  getBeaconBlockRootFromDataColumnSidecarSerialized,
5
- getBeaconBlockRootFromExecutionPayloadEnvelopeSerialized,
6
5
  getBlockRootFromBeaconAttestationSerialized,
6
+ getBlockRootFromPayloadAttestationMessageSerialized,
7
7
  getBlockRootFromSignedAggregateAndProofSerialized,
8
8
  getSlotFromBeaconAttestationSerialized,
9
9
  getSlotFromBlobSidecarSerialized,
10
10
  getSlotFromDataColumnSidecarSerialized,
11
11
  getSlotFromExecutionPayloadEnvelopeSerialized,
12
+ getSlotFromPayloadAttestationMessageSerialized,
12
13
  getSlotFromSignedAggregateAndProofSerialized,
13
14
  getSlotFromSignedBeaconBlockSerialized,
15
+ getSlotFromSignedExecutionPayloadBidSerialized,
14
16
  } from "../../util/sszBytes.js";
15
17
  import {GossipType} from "../gossip/index.js";
16
18
  import {ExtractSlotRootFns} from "./types.js";
17
19
 
18
20
  /**
19
21
  * Extract the slot and block root of a gossip message form serialized data.
20
- * Not applicable for all topics.
22
+ * Only do it for messages that have a slot and block root, and we want to await the block if the block root is not known.
21
23
  */
22
24
  export function createExtractBlockSlotRootFns(): ExtractSlotRootFns {
23
25
  return {
@@ -57,21 +59,45 @@ export function createExtractBlockSlotRootFns(): ExtractSlotRootFns {
57
59
  },
58
60
  [GossipType.data_column_sidecar]: (data: Uint8Array, fork: ForkName): SlotOptionalRoot | null => {
59
61
  const slot = getSlotFromDataColumnSidecarSerialized(data, fork);
62
+
60
63
  if (slot === null) {
61
64
  return null;
62
65
  }
63
66
 
64
- const root = isForkPostGloas(fork) ? getBeaconBlockRootFromDataColumnSidecarSerialized(data) : null;
67
+ if (ForkSeq[fork] < ForkSeq.gloas) {
68
+ return {slot};
69
+ }
70
+
71
+ const root = getBeaconBlockRootFromDataColumnSidecarSerialized(data);
72
+ // null root means the message is invalid here and will be ignored in gossip handler later
73
+ // returning the slot here helps check the earliest permissable slot in the network processor
65
74
  return root !== null ? {slot, root} : {slot};
66
75
  },
67
- [GossipType.execution_payload]: (data: Uint8Array): SlotRootHex | null => {
76
+ [GossipType.execution_payload]: (data: Uint8Array): SlotOptionalRoot | null => {
68
77
  const slot = getSlotFromExecutionPayloadEnvelopeSerialized(data);
69
- const root = getBeaconBlockRootFromExecutionPayloadEnvelopeSerialized(data);
78
+ // Do not extract the root here; the network processor will extract it in the 2nd round to trigger block search without awaiting.
79
+ if (slot === null) {
80
+ return null;
81
+ }
82
+ return {slot};
83
+ },
84
+ [GossipType.payload_attestation_message]: (data: Uint8Array): SlotRootHex | null => {
85
+ const slot = getSlotFromPayloadAttestationMessageSerialized(data);
86
+ const root = getBlockRootFromPayloadAttestationMessageSerialized(data);
70
87
 
71
88
  if (slot === null || root === null) {
72
89
  return null;
73
90
  }
74
91
  return {slot, root};
75
92
  },
93
+ [GossipType.execution_payload_bid]: (data: Uint8Array): SlotOptionalRoot | null => {
94
+ const slot = getSlotFromSignedExecutionPayloadBidSerialized(data);
95
+
96
+ if (slot === null) {
97
+ return null;
98
+ }
99
+
100
+ return {slot};
101
+ },
76
102
  };
77
103
  }
@@ -169,16 +169,19 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
169
169
 
170
170
  logger.debug("Received gossip block", {...logCtx});
171
171
 
172
- let blockInput: IBlockInput | undefined;
172
+ // optimistically add gossip block to the seen cache
173
+ // if validation fails, we will NOT forward this gossip block to peers
174
+ // - if PARENT_UNKNOWN error, blockInput will then be queued inside BlockInputSync. If the gossip block is really invalid, it will be pruned there
175
+ // - if other validator errors, blockInput will stay in the seen cache and will be pruned on finalization
176
+ const blockInput = chain.seenBlockInputCache.getByBlock({
177
+ block: signedBlock,
178
+ blockRootHex,
179
+ source: BlockInputSource.gossip,
180
+ seenTimestampSec,
181
+ peerIdStr,
182
+ });
173
183
  try {
174
184
  await validateGossipBlock(config, chain, signedBlock, fork);
175
- blockInput = chain.seenBlockInputCache.getByBlock({
176
- block: signedBlock,
177
- blockRootHex,
178
- source: BlockInputSource.gossip,
179
- seenTimestampSec,
180
- peerIdStr,
181
- });
182
185
  const blockInputMeta = blockInput.getLogMeta();
183
186
 
184
187
  const recvToValidation = Date.now() / 1000 - seenTimestampSec;
@@ -194,9 +197,9 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
194
197
  return blockInput;
195
198
  } catch (e) {
196
199
  if (e instanceof BlockGossipError) {
200
+ logger.debug("Gossip block has error", {slot, root: blockShortHex, code: e.type.code});
197
201
  if (e.type.code === BlockErrorCode.PARENT_UNKNOWN && blockInput) {
198
- logger.debug("Gossip block has error", {slot, root: blockShortHex, code: e.type.code});
199
- chain.emitter.emit(ChainEvent.unknownParent, {
202
+ chain.emitter.emit(ChainEvent.blockUnknownParent, {
200
203
  blockInput,
201
204
  peer: peerIdStr,
202
205
  source: BlockInputSource.gossip,
@@ -1037,8 +1040,37 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1037
1040
  const {serializedData} = gossipData;
1038
1041
  const signedEnvelope = sszDeserialize(topic, serializedData);
1039
1042
  const envelope = signedEnvelope.message;
1040
- // TODO GLOAS: handle BLOCK_ROOT_UNKNOWN error to trigger sync
1041
- await validateGossipExecutionPayloadEnvelope(chain, signedEnvelope);
1043
+
1044
+ // TODO GLOAS: consider optimistically create PayloadEnvelopeInput here similar to how we do that for beacon_block
1045
+ // so that UnknownBlockSync can handle backward sync
1046
+ // the problem now is we cannot create a PayloadEnvelopeInput without the beacon block being known, we need at least the proposer index
1047
+ // we can achieve that by looking into the EpochCache
1048
+ try {
1049
+ await validateGossipExecutionPayloadEnvelope(chain, signedEnvelope);
1050
+ } catch (e) {
1051
+ if (e instanceof ExecutionPayloadEnvelopeError) {
1052
+ const {slot, beaconBlockRoot} = signedEnvelope.message;
1053
+ logger.debug("Gossip envelope has error", {slot, root: toRootHex(beaconBlockRoot), code: e.type.code});
1054
+ if (e.type.code === ExecutionPayloadEnvelopeErrorCode.BLOCK_ROOT_UNKNOWN) {
1055
+ // TODO GLOAS: UnknownBlockSync to handle this
1056
+ chain.emitter.emit(ChainEvent.envelopeUnknownBlock, {
1057
+ envelope: signedEnvelope,
1058
+ peer: peerIdStr,
1059
+ source: BlockInputSource.gossip,
1060
+ });
1061
+ }
1062
+
1063
+ if (e.action === GossipAction.REJECT) {
1064
+ chain.persistInvalidSszValue(
1065
+ ssz.gloas.SignedExecutionPayloadEnvelope,
1066
+ signedEnvelope,
1067
+ `gossip_reject_slot_${slot}`
1068
+ );
1069
+ }
1070
+ }
1071
+
1072
+ throw e;
1073
+ }
1042
1074
 
1043
1075
  const slot = envelope.slot;
1044
1076
  const delaySec = seenTimestampSec - computeTimeAtSlot(config, slot, chain.genesisTime);
@@ -1116,6 +1148,11 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1116
1148
  } catch (e) {
1117
1149
  logger.error("Error adding to executionPayloadBid pool", {}, e as Error);
1118
1150
  }
1151
+
1152
+ chain.emitter.emit(routes.events.EventType.executionPayloadBid, {
1153
+ version: config.getForkName(executionPayloadBid.message.slot),
1154
+ data: executionPayloadBid,
1155
+ });
1119
1156
  },
1120
1157
  };
1121
1158
  }