@lodestar/beacon-node 1.42.0-dev.4411584fd8 → 1.42.0-dev.5007d8c6d6

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 (132) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +35 -16
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/chain/blocks/blockInput/types.d.ts +3 -3
  5. package/lib/chain/blocks/blockInput/types.d.ts.map +1 -1
  6. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  7. package/lib/chain/blocks/importBlock.js +18 -2
  8. package/lib/chain/blocks/importBlock.js.map +1 -1
  9. package/lib/chain/blocks/importExecutionPayload.d.ts +48 -0
  10. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -0
  11. package/lib/chain/blocks/importExecutionPayload.js +159 -0
  12. package/lib/chain/blocks/importExecutionPayload.js.map +1 -0
  13. package/lib/chain/blocks/payloadEnvelopeInput/index.d.ts +3 -0
  14. package/lib/chain/blocks/payloadEnvelopeInput/index.d.ts.map +1 -0
  15. package/lib/chain/blocks/payloadEnvelopeInput/index.js +3 -0
  16. package/lib/chain/blocks/payloadEnvelopeInput/index.js.map +1 -0
  17. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +80 -0
  18. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -0
  19. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +248 -0
  20. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -0
  21. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +29 -0
  22. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -0
  23. package/lib/chain/blocks/payloadEnvelopeInput/types.js +11 -0
  24. package/lib/chain/blocks/payloadEnvelopeInput/types.js.map +1 -0
  25. package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts +15 -0
  26. package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts.map +1 -0
  27. package/lib/chain/blocks/payloadEnvelopeProcessor.js +46 -0
  28. package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -0
  29. package/lib/chain/blocks/types.d.ts +7 -0
  30. package/lib/chain/blocks/types.d.ts.map +1 -1
  31. package/lib/chain/blocks/writePayloadEnvelopeInputToDb.d.ts +12 -0
  32. package/lib/chain/blocks/writePayloadEnvelopeInputToDb.d.ts.map +1 -0
  33. package/lib/chain/blocks/writePayloadEnvelopeInputToDb.js +40 -0
  34. package/lib/chain/blocks/writePayloadEnvelopeInputToDb.js.map +1 -0
  35. package/lib/chain/chain.d.ts +7 -2
  36. package/lib/chain/chain.d.ts.map +1 -1
  37. package/lib/chain/chain.js +28 -3
  38. package/lib/chain/chain.js.map +1 -1
  39. package/lib/chain/errors/executionPayloadEnvelope.d.ts +12 -2
  40. package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
  41. package/lib/chain/errors/executionPayloadEnvelope.js +3 -1
  42. package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
  43. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  44. package/lib/chain/forkChoice/index.js +0 -10
  45. package/lib/chain/forkChoice/index.js.map +1 -1
  46. package/lib/chain/interface.d.ts +6 -3
  47. package/lib/chain/interface.d.ts.map +1 -1
  48. package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
  49. package/lib/chain/produceBlock/computeNewStateRoot.js +6 -1
  50. package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
  51. package/lib/chain/regen/interface.d.ts +2 -0
  52. package/lib/chain/regen/interface.d.ts.map +1 -1
  53. package/lib/chain/regen/interface.js +2 -0
  54. package/lib/chain/regen/interface.js.map +1 -1
  55. package/lib/chain/seenCache/index.d.ts +1 -1
  56. package/lib/chain/seenCache/index.d.ts.map +1 -1
  57. package/lib/chain/seenCache/index.js +1 -1
  58. package/lib/chain/seenCache/index.js.map +1 -1
  59. package/lib/chain/seenCache/seenGossipBlockInput.js +2 -2
  60. package/lib/chain/seenCache/seenGossipBlockInput.js.map +1 -1
  61. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +38 -0
  62. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -0
  63. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +76 -0
  64. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -0
  65. package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
  66. package/lib/chain/validation/executionPayloadEnvelope.js +30 -19
  67. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  68. package/lib/chain/validation/syncCommittee.d.ts +2 -2
  69. package/lib/chain/validation/syncCommittee.d.ts.map +1 -1
  70. package/lib/chain/validation/syncCommittee.js +12 -11
  71. package/lib/chain/validation/syncCommittee.js.map +1 -1
  72. package/lib/chain/validatorMonitor.d.ts +2 -1
  73. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  74. package/lib/chain/validatorMonitor.js +3 -0
  75. package/lib/chain/validatorMonitor.js.map +1 -1
  76. package/lib/metrics/metrics/lodestar.d.ts +28 -0
  77. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  78. package/lib/metrics/metrics/lodestar.js +74 -0
  79. package/lib/metrics/metrics/lodestar.js.map +1 -1
  80. package/lib/network/gossip/encoding.d.ts.map +1 -1
  81. package/lib/network/gossip/encoding.js +15 -0
  82. package/lib/network/gossip/encoding.js.map +1 -1
  83. package/lib/network/gossip/topic.d.ts +727 -0
  84. package/lib/network/gossip/topic.d.ts.map +1 -1
  85. package/lib/network/processor/extractSlotRootFns.d.ts.map +1 -1
  86. package/lib/network/processor/extractSlotRootFns.js +14 -4
  87. package/lib/network/processor/extractSlotRootFns.js.map +1 -1
  88. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  89. package/lib/network/processor/gossipHandlers.js +38 -8
  90. package/lib/network/processor/gossipHandlers.js.map +1 -1
  91. package/lib/sync/unknownBlock.d.ts +2 -8
  92. package/lib/sync/unknownBlock.d.ts.map +1 -1
  93. package/lib/sync/unknownBlock.js +7 -40
  94. package/lib/sync/unknownBlock.js.map +1 -1
  95. package/lib/util/sszBytes.d.ts +4 -1
  96. package/lib/util/sszBytes.d.ts.map +1 -1
  97. package/lib/util/sszBytes.js +69 -12
  98. package/lib/util/sszBytes.js.map +1 -1
  99. package/package.json +15 -15
  100. package/src/api/impl/beacon/blocks/index.ts +36 -17
  101. package/src/chain/blocks/blockInput/types.ts +3 -3
  102. package/src/chain/blocks/importBlock.ts +36 -2
  103. package/src/chain/blocks/importExecutionPayload.ts +241 -0
  104. package/src/chain/blocks/payloadEnvelopeInput/index.ts +2 -0
  105. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +336 -0
  106. package/src/chain/blocks/payloadEnvelopeInput/types.ts +33 -0
  107. package/src/chain/blocks/payloadEnvelopeProcessor.ts +61 -0
  108. package/src/chain/blocks/types.ts +8 -0
  109. package/src/chain/blocks/writePayloadEnvelopeInputToDb.ts +55 -0
  110. package/src/chain/chain.ts +37 -3
  111. package/src/chain/errors/executionPayloadEnvelope.ts +6 -2
  112. package/src/chain/forkChoice/index.ts +0 -10
  113. package/src/chain/interface.ts +6 -3
  114. package/src/chain/produceBlock/computeNewStateRoot.ts +6 -1
  115. package/src/chain/regen/interface.ts +2 -0
  116. package/src/chain/seenCache/index.ts +1 -1
  117. package/src/chain/seenCache/seenGossipBlockInput.ts +2 -2
  118. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +106 -0
  119. package/src/chain/validation/executionPayloadEnvelope.ts +38 -25
  120. package/src/chain/validation/syncCommittee.ts +15 -14
  121. package/src/chain/validatorMonitor.ts +10 -0
  122. package/src/metrics/metrics/lodestar.ts +77 -0
  123. package/src/network/gossip/encoding.ts +16 -0
  124. package/src/network/processor/extractSlotRootFns.ts +18 -5
  125. package/src/network/processor/gossipHandlers.ts +44 -7
  126. package/src/sync/unknownBlock.ts +9 -49
  127. package/src/util/sszBytes.ts +90 -10
  128. package/lib/chain/seenCache/seenExecutionPayloadEnvelope.d.ts +0 -15
  129. package/lib/chain/seenCache/seenExecutionPayloadEnvelope.d.ts.map +0 -1
  130. package/lib/chain/seenCache/seenExecutionPayloadEnvelope.js +0 -28
  131. package/lib/chain/seenCache/seenExecutionPayloadEnvelope.js.map +0 -1
  132. package/src/chain/seenCache/seenExecutionPayloadEnvelope.ts +0 -34
@@ -32,7 +32,7 @@ import {IArchiveStore} from "./archiveStore/interface.js";
32
32
  import {CheckpointBalancesCache} from "./balancesCache.js";
33
33
  import {BeaconProposerCache, ProposerPreparationData} from "./beaconProposerCache.js";
34
34
  import {IBlockInput} from "./blocks/blockInput/index.js";
35
- import {ImportBlockOpts} from "./blocks/types.js";
35
+ import {ImportBlockOpts, ImportPayloadOpts} from "./blocks/types.js";
36
36
  import {IBlsVerifier} from "./bls/index.js";
37
37
  import {ColumnReconstructionTracker} from "./ColumnReconstructionTracker.js";
38
38
  import {ChainEventEmitter} from "./emitter.js";
@@ -58,7 +58,6 @@ import {
58
58
  SeenBlockProposers,
59
59
  SeenContributionAndProof,
60
60
  SeenExecutionPayloadBids,
61
- SeenExecutionPayloadEnvelopes,
62
61
  SeenPayloadAttesters,
63
62
  SeenSyncCommitteeMessages,
64
63
  } from "./seenCache/index.js";
@@ -66,6 +65,7 @@ import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
66
65
  import {SeenAttestationDatas} from "./seenCache/seenAttestationData.js";
67
66
  import {SeenBlockAttesters} from "./seenCache/seenBlockAttesters.js";
68
67
  import {SeenBlockInput} from "./seenCache/seenGossipBlockInput.js";
68
+ import {PayloadEnvelopeInput, SeenPayloadEnvelopeInput} from "./seenCache/seenPayloadEnvelopeInput.js";
69
69
  import {ShufflingCache} from "./shufflingCache.js";
70
70
  import {ValidatorMonitor} from "./validatorMonitor.js";
71
71
 
@@ -128,13 +128,13 @@ export interface IBeaconChain {
128
128
  readonly seenAggregators: SeenAggregators;
129
129
  readonly seenPayloadAttesters: SeenPayloadAttesters;
130
130
  readonly seenAggregatedAttestations: SeenAggregatedAttestations;
131
- readonly seenExecutionPayloadEnvelopes: SeenExecutionPayloadEnvelopes;
132
131
  readonly seenExecutionPayloadBids: SeenExecutionPayloadBids;
133
132
  readonly seenBlockProposers: SeenBlockProposers;
134
133
  readonly seenSyncCommitteeMessages: SeenSyncCommitteeMessages;
135
134
  readonly seenContributionAndProof: SeenContributionAndProof;
136
135
  readonly seenAttestationDatas: SeenAttestationDatas;
137
136
  readonly seenBlockInputCache: SeenBlockInput;
137
+ readonly seenPayloadEnvelopeInputCache: SeenPayloadEnvelopeInput;
138
138
  // Seen cache for liveness checks
139
139
  readonly seenBlockAttesters: SeenBlockAttesters;
140
140
 
@@ -242,6 +242,9 @@ export interface IBeaconChain {
242
242
  /** Process a chain of blocks until complete */
243
243
  processChainSegment(blocks: IBlockInput[], opts?: ImportBlockOpts): Promise<void>;
244
244
 
245
+ /** Process execution payload envelope: verify, import to fork choice, and persist to DB */
246
+ processExecutionPayload(payloadInput: PayloadEnvelopeInput, opts?: ImportPayloadOpts): Promise<void>;
247
+
245
248
  getStatus(): Status;
246
249
 
247
250
  recomputeForkChoiceHead(caller: ForkchoiceCaller): ProtoBlock;
@@ -73,7 +73,12 @@ export function computeEnvelopeStateRoot(
73
73
  };
74
74
 
75
75
  const processEnvelopeTimer = metrics?.blockPayload.executionPayloadEnvelopeProcessingTime.startTimer();
76
- const postEnvelopeState = processExecutionPayloadEnvelope(postBlockState, signedEnvelope, false, {
76
+ const postEnvelopeState = processExecutionPayloadEnvelope(postBlockState, signedEnvelope, {
77
+ // Signature is zero-ed (G2_POINT_AT_INFINITY), skip verification
78
+ verifySignature: false,
79
+ // State root is being computed here, the envelope doesn't have it yet
80
+ verifyStateRoot: false,
81
+ // Preserve cache in source state, since the resulting state is not added to the state cache
77
82
  dontTransferCache: true,
78
83
  });
79
84
  processEnvelopeTimer?.();
@@ -9,8 +9,10 @@ export enum RegenCaller {
9
9
  processBlock = "processBlock",
10
10
  produceBlock = "produceBlock",
11
11
  validateGossipBlock = "validateGossipBlock",
12
+ validateGossipPayloadEnvelope = "validateGossipPayloadEnvelope",
12
13
  validateGossipBlob = "validateGossipBlob",
13
14
  validateGossipDataColumn = "validateGossipDataColumn",
15
+ validateGossipExecutionPayloadEnvelope = "validateGossipExecutionPayloadEnvelope",
14
16
  precomputeEpoch = "precomputeEpoch",
15
17
  predictProposerHead = "predictProposerHead",
16
18
  produceAttestationData = "produceAttestationData",
@@ -3,5 +3,5 @@ export {SeenBlockProposers} from "./seenBlockProposers.js";
3
3
  export {SeenSyncCommitteeMessages} from "./seenCommittee.js";
4
4
  export {SeenContributionAndProof} from "./seenCommitteeContribution.js";
5
5
  export {SeenExecutionPayloadBids} from "./seenExecutionPayloadBids.js";
6
- export {SeenExecutionPayloadEnvelopes} from "./seenExecutionPayloadEnvelope.js";
7
6
  export {SeenBlockInput} from "./seenGossipBlockInput.js";
7
+ export {PayloadEnvelopeInput, SeenPayloadEnvelopeInput} from "./seenPayloadEnvelopeInput.js";
@@ -180,7 +180,7 @@ export class SeenBlockInput {
180
180
  blockInput = this.blockInputs.get(parentRootHex ?? "");
181
181
  parentRootHex = blockInput?.parentRootHex;
182
182
  }
183
- this.logger?.debug(`BlockInputCache.prune deleted ${deletedCount} cached BlockInputs`);
183
+ this.logger?.debug("BlockInputCache.prune deleted cached BlockInputs", {deletedCount});
184
184
  this.pruneToMaxSize();
185
185
  }
186
186
 
@@ -193,7 +193,7 @@ export class SeenBlockInput {
193
193
  this.evictBlockInput(blockInput);
194
194
  }
195
195
  }
196
- this.logger?.debug(`BlockInputCache.onFinalized deleted ${deletedCount} cached BlockInputs`);
196
+ this.logger?.debug("BlockInputCache.onFinalized deleted cached BlockInputs", {deletedCount});
197
197
  this.pruneToMaxSize();
198
198
  };
199
199
 
@@ -0,0 +1,106 @@
1
+ import {CheckpointWithHex} from "@lodestar/fork-choice";
2
+ import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
3
+ import {RootHex} from "@lodestar/types";
4
+ import {Logger} from "@lodestar/utils";
5
+ import {Metrics} from "../../metrics/metrics.js";
6
+ import {SerializedCache} from "../../util/serializedCache.js";
7
+ import {CreateFromBlockProps, PayloadEnvelopeInput} from "../blocks/payloadEnvelopeInput/index.js";
8
+ import {ChainEvent, ChainEventEmitter} from "../emitter.js";
9
+
10
+ export type {PayloadEnvelopeInputState} from "../blocks/payloadEnvelopeInput/index.js";
11
+ export {PayloadEnvelopeInput} from "../blocks/payloadEnvelopeInput/index.js";
12
+
13
+ export type SeenPayloadEnvelopeInputModules = {
14
+ chainEvents: ChainEventEmitter;
15
+ signal: AbortSignal;
16
+ serializedCache: SerializedCache;
17
+ metrics: Metrics | null;
18
+ logger?: Logger;
19
+ };
20
+
21
+ /**
22
+ * Cache for tracking PayloadEnvelopeInput instances, keyed by beacon block root.
23
+ *
24
+ * Created during block import when a block is processed.
25
+ * Pruned on finalization and after payload is written to DB.
26
+ */
27
+ export class SeenPayloadEnvelopeInput {
28
+ private readonly chainEvents: ChainEventEmitter;
29
+ private readonly signal: AbortSignal;
30
+ private readonly serializedCache: SerializedCache;
31
+ private readonly metrics: Metrics | null;
32
+ private readonly logger?: Logger;
33
+ private payloadInputs = new Map<RootHex, PayloadEnvelopeInput>();
34
+
35
+ constructor({chainEvents, signal, serializedCache, metrics, logger}: SeenPayloadEnvelopeInputModules) {
36
+ this.chainEvents = chainEvents;
37
+ this.signal = signal;
38
+ this.serializedCache = serializedCache;
39
+ this.metrics = metrics;
40
+ this.logger = logger;
41
+
42
+ if (metrics) {
43
+ metrics.seenCache.payloadEnvelopeInput.count.addCollect(() => {
44
+ metrics.seenCache.payloadEnvelopeInput.count.set(this.payloadInputs.size);
45
+ metrics.seenCache.payloadEnvelopeInput.serializedObjectRefs.set(
46
+ Array.from(this.payloadInputs.values()).reduce(
47
+ (count, payloadInput) => count + payloadInput.getSerializedCacheKeys().length,
48
+ 0
49
+ )
50
+ );
51
+ });
52
+ }
53
+
54
+ this.chainEvents.on(ChainEvent.forkChoiceFinalized, this.onFinalized);
55
+ this.signal.addEventListener("abort", () => {
56
+ this.chainEvents.off(ChainEvent.forkChoiceFinalized, this.onFinalized);
57
+ });
58
+ }
59
+
60
+ private onFinalized = (checkpoint: CheckpointWithHex): void => {
61
+ // Prune all entries with slot < finalized slot
62
+ const finalizedSlot = computeStartSlotAtEpoch(checkpoint.epoch);
63
+ let deletedCount = 0;
64
+ for (const [, input] of this.payloadInputs) {
65
+ if (input.slot < finalizedSlot) {
66
+ this.evictPayloadInput(input);
67
+ deletedCount++;
68
+ }
69
+ }
70
+ this.logger?.debug("SeenPayloadEnvelopeInput.onFinalized deleted cached entries", {deletedCount});
71
+ };
72
+
73
+ add(props: CreateFromBlockProps): PayloadEnvelopeInput {
74
+ if (this.payloadInputs.has(props.blockRootHex)) {
75
+ throw new Error(`PayloadEnvelopeInput already exists for block ${props.blockRootHex}`);
76
+ }
77
+ const input = PayloadEnvelopeInput.createFromBlock(props);
78
+ this.payloadInputs.set(props.blockRootHex, input);
79
+ this.metrics?.seenCache.payloadEnvelopeInput.created.inc();
80
+ return input;
81
+ }
82
+
83
+ get(blockRootHex: RootHex): PayloadEnvelopeInput | undefined {
84
+ return this.payloadInputs.get(blockRootHex);
85
+ }
86
+
87
+ has(blockRootHex: RootHex): boolean {
88
+ return this.payloadInputs.has(blockRootHex);
89
+ }
90
+
91
+ prune(blockRootHex: RootHex): void {
92
+ const payloadInput = this.payloadInputs.get(blockRootHex);
93
+ if (payloadInput) {
94
+ this.evictPayloadInput(payloadInput);
95
+ }
96
+ }
97
+
98
+ size(): number {
99
+ return this.payloadInputs.size;
100
+ }
101
+
102
+ private evictPayloadInput(payloadInput: PayloadEnvelopeInput): void {
103
+ this.serializedCache.delete(payloadInput.getSerializedCacheKeys());
104
+ this.payloadInputs.delete(payloadInput.blockRootHex);
105
+ }
106
+ }
@@ -1,14 +1,15 @@
1
- import {PublicKey} from "@chainsafe/blst";
1
+ import {PayloadStatus} from "@lodestar/fork-choice";
2
2
  import {
3
+ BeaconStateView,
3
4
  CachedBeaconStateGloas,
4
5
  computeStartSlotAtEpoch,
5
- createSingleSignatureSetFromComponents,
6
- getExecutionPayloadEnvelopeSigningRoot,
6
+ getExecutionPayloadEnvelopeSignatureSet,
7
7
  } from "@lodestar/state-transition";
8
8
  import {gloas} from "@lodestar/types";
9
9
  import {toRootHex} from "@lodestar/utils";
10
10
  import {ExecutionPayloadEnvelopeError, ExecutionPayloadEnvelopeErrorCode, GossipAction} from "../errors/index.js";
11
11
  import {IBeaconChain} from "../index.js";
12
+ import {RegenCaller} from "../regen/index.js";
12
13
 
13
14
  export async function validateApiExecutionPayloadEnvelope(
14
15
  chain: IBeaconChain,
@@ -47,7 +48,9 @@ async function validateExecutionPayloadEnvelope(
47
48
 
48
49
  // [IGNORE] The node has not seen another valid
49
50
  // `SignedExecutionPayloadEnvelope` for this block root from this builder.
50
- if (chain.seenExecutionPayloadEnvelopes.isKnown(blockRootHex)) {
51
+ const envelopeBlock = chain.forkChoice.getBlockHex(blockRootHex, PayloadStatus.FULL);
52
+ const payloadInput = chain.seenPayloadEnvelopeInputCache.get(blockRootHex);
53
+ if (envelopeBlock || payloadInput?.hasPayloadEnvelope()) {
51
54
  throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, {
52
55
  code: ExecutionPayloadEnvelopeErrorCode.ENVELOPE_ALREADY_KNOWN,
53
56
  blockRoot: blockRootHex,
@@ -55,6 +58,14 @@ async function validateExecutionPayloadEnvelope(
55
58
  });
56
59
  }
57
60
 
61
+ if (!payloadInput) {
62
+ // PayloadEnvelopeInput should have been created during block import
63
+ throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, {
64
+ code: ExecutionPayloadEnvelopeErrorCode.PAYLOAD_ENVELOPE_INPUT_MISSING,
65
+ blockRoot: blockRootHex,
66
+ });
67
+ }
68
+
58
69
  // [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)`
59
70
  const finalizedCheckpoint = chain.forkChoice.getFinalizedCheckpoint();
60
71
  const finalizedSlot = computeStartSlotAtEpoch(finalizedCheckpoint.epoch);
@@ -79,45 +90,47 @@ async function validateExecutionPayloadEnvelope(
79
90
  });
80
91
  }
81
92
 
82
- if (block.builderIndex == null || block.blockHashFromBid == null) {
83
- // This indicates this block is a pre-gloas block which is wrong
84
- throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, {
85
- code: ExecutionPayloadEnvelopeErrorCode.CACHE_FAIL,
86
- blockRoot: blockRootHex,
87
- });
88
- }
89
-
90
93
  // [REJECT] `envelope.builder_index == bid.builder_index`
91
- if (envelope.builderIndex !== block.builderIndex) {
94
+ if (envelope.builderIndex !== payloadInput.getBuilderIndex()) {
92
95
  throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
93
96
  code: ExecutionPayloadEnvelopeErrorCode.BUILDER_INDEX_MISMATCH,
94
97
  envelopeBuilderIndex: envelope.builderIndex,
95
- bidBuilderIndex: block.builderIndex,
98
+ bidBuilderIndex: payloadInput.getBuilderIndex(),
96
99
  });
97
100
  }
98
101
 
99
102
  // [REJECT] `payload.block_hash == bid.block_hash`
100
- if (toRootHex(payload.blockHash) !== block.blockHashFromBid) {
103
+ if (toRootHex(payload.blockHash) !== payloadInput.getBlockHashHex()) {
101
104
  throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
102
105
  code: ExecutionPayloadEnvelopeErrorCode.BLOCK_HASH_MISMATCH,
103
106
  envelopeBlockHash: toRootHex(payload.blockHash),
104
- bidBlockHash: block.blockHashFromBid,
107
+ bidBlockHash: payloadInput.getBlockHashHex(),
105
108
  });
106
109
  }
107
110
 
108
- // [REJECT] `signed_execution_payload_envelope.signature` is valid with respect to the builder's public key.
109
- const state = chain.getHeadState() as CachedBeaconStateGloas;
110
- const signatureSet = createSingleSignatureSetFromComponents(
111
- PublicKey.fromBytes(state.builders.getReadonly(envelope.builderIndex).pubkey),
112
- getExecutionPayloadEnvelopeSigningRoot(chain.config, envelope),
113
- executionPayloadEnvelope.signature
111
+ // Get the post block state which is the pre-payload state to verify the builder's signature.
112
+ const blockState = await chain.regen
113
+ .getState(block.stateRoot, RegenCaller.validateGossipPayloadEnvelope)
114
+ .catch(() => {
115
+ throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, {
116
+ code: ExecutionPayloadEnvelopeErrorCode.UNKNOWN_BLOCK_STATE,
117
+ blockRoot: blockRootHex,
118
+ slot: envelope.slot,
119
+ });
120
+ });
121
+
122
+ const state = blockState as CachedBeaconStateGloas;
123
+ const signatureSet = getExecutionPayloadEnvelopeSignatureSet(
124
+ chain.config,
125
+ chain.pubkeyCache,
126
+ new BeaconStateView(state),
127
+ executionPayloadEnvelope,
128
+ payloadInput.proposerIndex
114
129
  );
115
130
 
116
- if (!(await chain.bls.verifySignatureSets([signatureSet]))) {
131
+ if (!(await chain.bls.verifySignatureSets([signatureSet], {verifyOnMainThread: true}))) {
117
132
  throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
118
133
  code: ExecutionPayloadEnvelopeErrorCode.INVALID_SIGNATURE,
119
134
  });
120
135
  }
121
-
122
- chain.seenExecutionPayloadEnvelopes.add(blockRootHex, envelope.slot);
123
136
  }
@@ -15,12 +15,12 @@ export async function validateGossipSyncCommittee(
15
15
  chain: IBeaconChain,
16
16
  syncCommittee: altair.SyncCommitteeMessage,
17
17
  subnet: SubnetID
18
- ): Promise<{indexInSubcommittee: IndexInSubcommittee}> {
18
+ ): Promise<{indicesInSubcommittee: IndexInSubcommittee[]}> {
19
19
  const {slot, validatorIndex, beaconBlockRoot} = syncCommittee;
20
20
  const messageRoot = toRootHex(beaconBlockRoot);
21
21
 
22
22
  const headState = chain.getHeadState();
23
- const indexInSubcommittee = validateGossipSyncCommitteeExceptSig(chain, headState, subnet, syncCommittee);
23
+ const indicesInSubcommittee = validateGossipSyncCommitteeExceptSig(chain, headState, subnet, syncCommittee);
24
24
 
25
25
  // [IGNORE] The signature's slot is for the current slot, i.e. sync_committee_signature.slot == current_slot.
26
26
  // > Checked in validateGossipSyncCommitteeExceptSig()
@@ -68,7 +68,7 @@ export async function validateGossipSyncCommittee(
68
68
  // Register this valid item as seen
69
69
  chain.seenSyncCommitteeMessages.add(slot, subnet, validatorIndex, messageRoot);
70
70
 
71
- return {indexInSubcommittee};
71
+ return {indicesInSubcommittee};
72
72
  }
73
73
 
74
74
  export async function validateApiSyncCommittee(
@@ -105,7 +105,7 @@ export function validateGossipSyncCommitteeExceptSig(
105
105
  headState: CachedBeaconStateAllForks,
106
106
  subnet: SubnetID,
107
107
  data: Pick<altair.SyncCommitteeMessage, "slot" | "validatorIndex">
108
- ): IndexInSubcommittee {
108
+ ): IndexInSubcommittee[] {
109
109
  const {slot, validatorIndex} = data;
110
110
  // [IGNORE] The signature's slot is for the current slot, i.e. sync_committee_signature.slot == current_slot.
111
111
  // (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance)
@@ -127,26 +127,27 @@ export function validateGossipSyncCommitteeExceptSig(
127
127
 
128
128
  // [REJECT] The subnet_id is valid for the given validator, i.e. subnet_id in compute_subnets_for_sync_committee(state, sync_committee_signature.validator_index).
129
129
  // Note this validation implies the validator is part of the broader current sync committee along with the correct subcommittee.
130
- const indexInSubcommittee = getIndexInSubcommittee(headState, subnet, data);
131
- if (indexInSubcommittee === null) {
130
+ const indicesInSubcommittee = getIndicesInSubcommittee(headState, subnet, data);
131
+ if (indicesInSubcommittee === null) {
132
132
  throw new SyncCommitteeError(GossipAction.REJECT, {
133
133
  code: SyncCommitteeErrorCode.VALIDATOR_NOT_IN_SYNC_COMMITTEE,
134
134
  validatorIndex,
135
135
  });
136
136
  }
137
137
 
138
- return indexInSubcommittee;
138
+ return indicesInSubcommittee;
139
139
  }
140
140
 
141
141
  /**
142
- * Returns the IndexInSubcommittee of the given `subnet`.
143
- * Returns `null` if not part of the sync committee or not part of the given `subnet`
142
+ * Returns all IndexInSubcommittee positions of the given `subnet`.
143
+ * Returns `null` if not part of the sync committee or not part of the given `subnet`.
144
+ * A validator may appear multiple times in the same subcommittee.
144
145
  */
145
- function getIndexInSubcommittee(
146
+ function getIndicesInSubcommittee(
146
147
  headState: CachedBeaconStateAllForks,
147
148
  subnet: SubnetID,
148
149
  data: Pick<altair.SyncCommitteeMessage, "slot" | "validatorIndex">
149
- ): IndexInSubcommittee | null {
150
+ ): IndexInSubcommittee[] | null {
150
151
  const syncCommittee = headState.epochCtx.getIndexedSyncCommittee(data.slot);
151
152
  const indexesInCommittee = syncCommittee.validatorIndexMap.get(data.validatorIndex);
152
153
  if (indexesInCommittee === undefined) {
@@ -154,12 +155,12 @@ function getIndexInSubcommittee(
154
155
  return null;
155
156
  }
156
157
 
158
+ const indices: IndexInSubcommittee[] = [];
157
159
  for (const indexInCommittee of indexesInCommittee) {
158
160
  if (Math.floor(indexInCommittee / SYNC_COMMITTEE_SUBNET_SIZE) === subnet) {
159
- return indexInCommittee % SYNC_COMMITTEE_SUBNET_SIZE;
161
+ indices.push(indexInCommittee % SYNC_COMMITTEE_SUBNET_SIZE);
160
162
  }
161
163
  }
162
164
 
163
- // Not part of this specific subnet
164
- return null;
165
+ return indices.length > 0 ? indices : null;
165
166
  }
@@ -23,6 +23,7 @@ import {
23
23
  ValidatorIndex,
24
24
  altair,
25
25
  deneb,
26
+ gloas,
26
27
  } from "@lodestar/types";
27
28
  import {LogData, LogHandler, LogLevel, Logger, MapDef, MapDefMax, prettyPrintIndices, toRootHex} from "@lodestar/utils";
28
29
  import {GENESIS_SLOT} from "../constants/constants.js";
@@ -61,6 +62,11 @@ export type ValidatorMonitor = {
61
62
  ): void;
62
63
  registerBeaconBlock(src: OpSource, delaySec: Seconds, block: BeaconBlock): void;
63
64
  registerBlobSidecar(src: OpSource, seenTimestampSec: Seconds, blob: deneb.BlobSidecar): void;
65
+ registerExecutionPayloadEnvelope(
66
+ src: OpSource,
67
+ delaySec: Seconds,
68
+ envelope: gloas.SignedExecutionPayloadEnvelope
69
+ ): void;
64
70
  registerImportedBlock(block: BeaconBlock, data: {proposerBalanceDelta: number}): void;
65
71
  onPoolSubmitUnaggregatedAttestation(
66
72
  seenTimestampSec: number,
@@ -450,6 +456,10 @@ export function createValidatorMonitor(
450
456
  //TODO: freetheblobs
451
457
  },
452
458
 
459
+ registerExecutionPayloadEnvelope(_src, _delaySec, _envelope) {
460
+ // TODO GLOAS: implement execution payload envelope monitoring
461
+ },
462
+
453
463
  registerImportedBlock(block, {proposerBalanceDelta}) {
454
464
  const validator = validators.get(block.proposerIndex);
455
465
  if (validator) {
@@ -3,6 +3,7 @@ import {NotReorgedReason} from "@lodestar/fork-choice";
3
3
  import {ArchiveStoreTask} from "../../chain/archiveStore/archiveStore.js";
4
4
  import {FrequencyStateArchiveStep} from "../../chain/archiveStore/strategies/frequencyStateArchiveStrategy.js";
5
5
  import {BlockInputSource} from "../../chain/blocks/blockInput/index.js";
6
+ import {PayloadEnvelopeInputSource} from "../../chain/blocks/payloadEnvelopeInput/index.js";
6
7
  import {JobQueueItemType} from "../../chain/bls/index.js";
7
8
  import {AttestationErrorCode, BlockErrorCode} from "../../chain/errors/index.js";
8
9
  import {
@@ -237,6 +238,56 @@ export function createLodestarMetrics(
237
238
  }),
238
239
  },
239
240
 
241
+ payloadEnvelopeProcessorQueue: {
242
+ length: register.gauge({
243
+ name: "lodestar_payload_envelope_processor_queue_length",
244
+ help: "Count of total payload envelope processor queue length",
245
+ }),
246
+ droppedJobs: register.gauge({
247
+ name: "lodestar_payload_envelope_processor_queue_dropped_jobs_total",
248
+ help: "Count of total payload envelope processor queue dropped jobs",
249
+ }),
250
+ jobTime: register.histogram({
251
+ name: "lodestar_payload_envelope_processor_queue_job_time_seconds",
252
+ help: "Time to process payload envelope processor queue job in seconds",
253
+ buckets: [0.01, 0.1, 1, 4, 12],
254
+ }),
255
+ jobWaitTime: register.histogram({
256
+ name: "lodestar_payload_envelope_processor_queue_job_wait_time_seconds",
257
+ help: "Time from job added to the payload envelope processor queue to starting in seconds",
258
+ buckets: [0.01, 0.1, 1, 4, 12],
259
+ }),
260
+ concurrency: register.gauge({
261
+ name: "lodestar_payload_envelope_processor_queue_concurrency",
262
+ help: "Current concurrency of payload envelope processor queue",
263
+ }),
264
+ },
265
+
266
+ unfinalizedPayloadEnvelopeWritesQueue: {
267
+ length: register.gauge({
268
+ name: "lodestar_unfinalized_payload_envelope_writes_queue_length",
269
+ help: "Count of total unfinalized payload envelope writes queue length",
270
+ }),
271
+ droppedJobs: register.gauge({
272
+ name: "lodestar_unfinalized_payload_envelope_writes_queue_dropped_jobs_total",
273
+ help: "Count of total unfinalized payload envelope writes queue dropped jobs",
274
+ }),
275
+ jobTime: register.histogram({
276
+ name: "lodestar_unfinalized_payload_envelope_writes_queue_job_time_seconds",
277
+ help: "Time to process unfinalized payload envelope writes queue job in seconds",
278
+ buckets: [0.01, 0.1, 1, 4, 12],
279
+ }),
280
+ jobWaitTime: register.histogram({
281
+ name: "lodestar_unfinalized_payload_envelope_writes_queue_job_wait_time_seconds",
282
+ help: "Time from job added to the unfinalized payload envelope writes queue to starting in seconds",
283
+ buckets: [0.01, 0.1, 1, 4, 12],
284
+ }),
285
+ concurrency: register.gauge({
286
+ name: "lodestar_unfinalized_payload_envelope_writes_queue_concurrency",
287
+ help: "Current concurrency of unfinalized payload envelope writes queue",
288
+ }),
289
+ },
290
+
240
291
  engineHttpProcessorQueue: {
241
292
  length: register.gauge({
242
293
  name: "lodestar_engine_http_processor_queue_length",
@@ -925,6 +976,18 @@ export function createLodestarMetrics(
925
976
  labelNames: ["reason"],
926
977
  }),
927
978
  },
979
+ importPayload: {
980
+ bySource: register.gauge<{source: PayloadEnvelopeInputSource}>({
981
+ name: "lodestar_import_payload_by_source_total",
982
+ help: "Total number of imported execution payload envelopes by source",
983
+ labelNames: ["source"],
984
+ }),
985
+ columnsBySource: register.gauge<{source: PayloadEnvelopeInputSource}>({
986
+ name: "lodestar_import_payload_columns_by_source_total",
987
+ help: "Total number of payload-attached columns (sampled columns for Gloas) by source",
988
+ labelNames: ["source"],
989
+ }),
990
+ },
928
991
  engineNotifyNewPayloadResult: register.gauge<{result: ExecutionPayloadStatus}>({
929
992
  name: "lodestar_execution_engine_notify_new_payload_result_total",
930
993
  help: "The total result of calling notifyNewPayload execution engine api",
@@ -1495,6 +1558,20 @@ export function createLodestarMetrics(
1495
1558
  help: "Number of BlockInputs created via a data column being seen first",
1496
1559
  }),
1497
1560
  },
1561
+ payloadEnvelopeInput: {
1562
+ count: register.gauge({
1563
+ name: "lodestar_seen_payload_envelope_input_cache_size",
1564
+ help: "Number of cached PayloadEnvelopeInputs",
1565
+ }),
1566
+ serializedObjectRefs: register.gauge({
1567
+ name: "lodestar_seen_payload_envelope_input_cache_serialized_object_refs",
1568
+ help: "Number of serialized-cache object refs retained by cached PayloadEnvelopeInputs",
1569
+ }),
1570
+ created: register.counter({
1571
+ name: "lodestar_seen_payload_envelope_input_cache_items_created_total",
1572
+ help: "Number of PayloadEnvelopeInputs created",
1573
+ }),
1574
+ },
1498
1575
  },
1499
1576
 
1500
1577
  processFinalizedCheckpoint: {
@@ -24,12 +24,28 @@ const decoder = new snappyWasm.Decoder();
24
24
  // Shared buffer to convert msgId to string
25
25
  const sharedMsgIdBuf = Buffer.alloc(20);
26
26
 
27
+ // Cache topic -> seed to avoid per-message allocations on the hot path.
28
+ // Topics are a fixed set per fork (changes only at fork boundaries).
29
+ const topicSeedCache = new Map<string, bigint>();
30
+
27
31
  /**
28
32
  * The function used to generate a gossipsub message id
29
33
  * We use the first 8 bytes of SHA256(data) for content addressing
30
34
  */
31
35
  export function fastMsgIdFn(rpcMsg: RPC.Message): string {
32
36
  if (rpcMsg.data) {
37
+ if (rpcMsg.topic) {
38
+ // Use topic-derived seed to prevent cross-topic deduplication of identical messages.
39
+ // SyncCommitteeMessages are published to multiple sync_committee_{subnet} topics with
40
+ // identical data, so hashing only the data incorrectly deduplicates across subnets.
41
+ // See https://github.com/ChainSafe/lodestar/issues/8294
42
+ let topicSeed = topicSeedCache.get(rpcMsg.topic);
43
+ if (topicSeed === undefined) {
44
+ topicSeed = xxhash.h64Raw(Buffer.from(rpcMsg.topic), h64Seed);
45
+ topicSeedCache.set(rpcMsg.topic, topicSeed);
46
+ }
47
+ return xxhash.h64Raw(rpcMsg.data, topicSeed).toString(16);
48
+ }
33
49
  return xxhash.h64Raw(rpcMsg.data, h64Seed).toString(16);
34
50
  }
35
51
  return "0000000000000000";
@@ -1,11 +1,14 @@
1
- import {ForkName} from "@lodestar/params";
1
+ import {ForkName, isForkPostGloas} from "@lodestar/params";
2
2
  import {SlotOptionalRoot, SlotRootHex} from "@lodestar/types";
3
3
  import {
4
+ getBeaconBlockRootFromDataColumnSidecarSerialized,
5
+ getBeaconBlockRootFromExecutionPayloadEnvelopeSerialized,
4
6
  getBlockRootFromBeaconAttestationSerialized,
5
7
  getBlockRootFromSignedAggregateAndProofSerialized,
6
8
  getSlotFromBeaconAttestationSerialized,
7
9
  getSlotFromBlobSidecarSerialized,
8
10
  getSlotFromDataColumnSidecarSerialized,
11
+ getSlotFromExecutionPayloadEnvelopeSerialized,
9
12
  getSlotFromSignedAggregateAndProofSerialized,
10
13
  getSlotFromSignedBeaconBlockSerialized,
11
14
  } from "../../util/sszBytes.js";
@@ -52,13 +55,23 @@ export function createExtractBlockSlotRootFns(): ExtractSlotRootFns {
52
55
  }
53
56
  return {slot};
54
57
  },
55
- [GossipType.data_column_sidecar]: (data: Uint8Array): SlotOptionalRoot | null => {
56
- const slot = getSlotFromDataColumnSidecarSerialized(data);
57
-
58
+ [GossipType.data_column_sidecar]: (data: Uint8Array, fork: ForkName): SlotOptionalRoot | null => {
59
+ const slot = getSlotFromDataColumnSidecarSerialized(data, fork);
58
60
  if (slot === null) {
59
61
  return null;
60
62
  }
61
- return {slot};
63
+
64
+ const root = isForkPostGloas(fork) ? getBeaconBlockRootFromDataColumnSidecarSerialized(data) : null;
65
+ return root !== null ? {slot, root} : {slot};
66
+ },
67
+ [GossipType.execution_payload]: (data: Uint8Array): SlotRootHex | null => {
68
+ const slot = getSlotFromExecutionPayloadEnvelopeSerialized(data);
69
+ const root = getBeaconBlockRootFromExecutionPayloadEnvelopeSerialized(data);
70
+
71
+ if (slot === null || root === null) {
72
+ return null;
73
+ }
74
+ return {slot, root};
62
75
  },
63
76
  };
64
77
  }