@lodestar/beacon-node 1.45.0-dev.3100972235 → 1.45.0-dev.51a1c44b27

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.
@@ -165,6 +165,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
165
165
  // tracked in https://github.com/ChainSafe/lodestar/issues/7957
166
166
 
167
167
  const logCtx = {
168
+ slot,
168
169
  currentSlot: chain.clock.currentSlot,
169
170
  peerId: peerIdStr,
170
171
  delaySec,
@@ -184,20 +185,24 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
184
185
  seenTimestampSec,
185
186
  peerIdStr,
186
187
  });
188
+
189
+ // Optimistically seed the payload-envelope cache too, mirroring seenBlockInputCache above.
190
+ // This ensures we have PayloadEnvelopeInput, even through "PARENT_UNKNOWN" error
191
+ // see https://github.com/ChainSafe/lodestar/issues/9475
192
+ if (isForkPostGloas(fork)) {
193
+ chain.seenPayloadEnvelopeInputCache.add({
194
+ blockRootHex,
195
+ block: signedBlock as SignedBeaconBlock<ForkPostGloas>,
196
+ forkName: fork,
197
+ sampledColumns: chain.custodyConfig.sampledColumns,
198
+ custodyColumns: chain.custodyConfig.custodyColumns,
199
+ timeCreatedSec: seenTimestampSec,
200
+ });
201
+ }
202
+
187
203
  try {
188
204
  const {skippedSlots} = await validateGossipBlock(config, chain, signedBlock, fork);
189
205
 
190
- if (isForkPostGloas(fork)) {
191
- chain.seenPayloadEnvelopeInputCache.add({
192
- blockRootHex,
193
- block: signedBlock as SignedBeaconBlock<ForkPostGloas>,
194
- forkName: fork,
195
- sampledColumns: chain.custodyConfig.sampledColumns,
196
- custodyColumns: chain.custodyConfig.custodyColumns,
197
- timeCreatedSec: seenTimestampSec,
198
- });
199
- }
200
-
201
206
  const blockInputMeta = blockInput.getLogMeta();
202
207
 
203
208
  const recvToValidation = Date.now() / 1000 - seenTimestampSec;
@@ -234,12 +239,22 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
234
239
  throw e;
235
240
  }
236
241
 
237
- if (e.action === GossipAction.REJECT) {
238
- chain.persistInvalidSszValue(forkTypes.SignedBeaconBlock, signedBlock, `gossip_reject_slot_${slot}`);
242
+ // IGNORE means the block is acceptable (e.g. FUTURE_SLOT, ALREADY_KNOWN), just not propagated.
243
+ // Keep the optimistically-added cache entries; they are pruned on finalization. Only REJECT
244
+ // (provably invalid) and unexpected errors prune below.
245
+ if (e.action === GossipAction.IGNORE) {
246
+ throw e;
239
247
  }
248
+
249
+ chain.persistInvalidSszValue(forkTypes.SignedBeaconBlock, signedBlock, `gossip_reject_slot_${slot}`);
240
250
  }
241
251
 
252
+ // REJECT or unexpected (non-BlockGossipError) error: drop the optimistically-added entries from
253
+ // both caches, keeping them consistent.
242
254
  chain.seenBlockInputCache.prune(blockRootHex);
255
+ if (isForkPostGloas(fork)) {
256
+ chain.seenPayloadEnvelopeInputCache.prune(blockRootHex);
257
+ }
243
258
  throw e;
244
259
  }
245
260
  }
@@ -1127,9 +1142,9 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1127
1142
  const delaySec = chain.clock.secFromSlot(slot, seenTimestampSec);
1128
1143
 
1129
1144
  logger.debug("Received gossip payload envelope", {
1145
+ slot,
1130
1146
  currentSlot: chain.clock.currentSlot,
1131
1147
  peerId: peerIdStr,
1132
- slot,
1133
1148
  blockRoot: toRootHex(envelope.beaconBlockRoot),
1134
1149
  delaySec,
1135
1150
  });
@@ -142,6 +142,7 @@ export function cacheByRangeResponses({
142
142
  const blockRootHex = toRootHex(blockRoot);
143
143
 
144
144
  const existing = updatedBatchBlocks.get(block.message.slot);
145
+ let blockInput: IBlockInput;
145
146
  if (existing) {
146
147
  // In practice this code block shouldn't be reached because we shouldn't be refetching a block we already have, see Batch#getRequests.
147
148
  // Will throw if root hex does not match (meaning we are following the wrong chain)
@@ -155,8 +156,9 @@ export function cacheByRangeResponses({
155
156
  },
156
157
  {throwOnDuplicateAdd: false}
157
158
  );
159
+ blockInput = existing;
158
160
  } else {
159
- const blockInput = cache.getByBlock({
161
+ blockInput = cache.getByBlock({
160
162
  block,
161
163
  blockRootHex,
162
164
  source,
@@ -165,6 +167,21 @@ export function cacheByRangeResponses({
165
167
  });
166
168
  updatedBatchBlocks.set(blockInput.slot, blockInput);
167
169
  }
170
+
171
+ // Seed seenPayloadEnvelopeInputCache the instant we have the block, before the blob loop (or any
172
+ // later step) can throw and abort the batch — otherwise a gloas block would sit in
173
+ // seenBlockInputCache but unseeded here, and payload-by-root sync would later throw "Missing
174
+ // PayloadEnvelopeInput for known block" (see issue #9306). add() is idempotent.
175
+ if (isForkPostGloas(blockInput.forkName)) {
176
+ seenPayloadEnvelopeInputCache.add({
177
+ blockRootHex: blockInput.blockRootHex,
178
+ block: blockInput.getBlock() as SignedBeaconBlock<ForkPostGloas>,
179
+ forkName: blockInput.forkName,
180
+ sampledColumns: custodyConfig.sampledColumns,
181
+ custodyColumns: custodyConfig.custodyColumns,
182
+ timeCreatedSec: seenTimestampSec,
183
+ });
184
+ }
168
185
  }
169
186
 
170
187
  for (const {blockRoot, blobSidecars} of responses.validatedBlobSidecars ?? []) {
@@ -205,22 +222,6 @@ export function cacheByRangeResponses({
205
222
  }
206
223
  }
207
224
 
208
- // Seed seenPayloadEnvelopeInputCache for every gloas block in the batch, regardless of whether
209
- // the peer returned its envelope. Without this, a block returned without its envelope would be
210
- // imported with no cache entry, and later payload-by-root sync would throw
211
- // "Missing PayloadEnvelopeInput for known block" (see issue #9306).
212
- for (const blockInput of updatedBatchBlocks.values()) {
213
- if (!blockInput.hasBlock() || !isForkPostGloas(blockInput.forkName)) continue;
214
- seenPayloadEnvelopeInputCache.add({
215
- blockRootHex: blockInput.blockRootHex,
216
- block: blockInput.getBlock() as SignedBeaconBlock<ForkPostGloas>,
217
- forkName: blockInput.forkName,
218
- sampledColumns: custodyConfig.sampledColumns,
219
- custodyColumns: custodyConfig.custodyColumns,
220
- timeCreatedSec: seenTimestampSec,
221
- });
222
- }
223
-
224
225
  let payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null =
225
226
  existingPayloadEnvelopes !== null ? new Map(existingPayloadEnvelopes) : null;
226
227
  if (downloadedPayloadEnvelopes !== null) {
@@ -229,8 +230,8 @@ export function cacheByRangeResponses({
229
230
  const envelopeBlockRootHex = toRootHex(envelope.message.beaconBlockRoot);
230
231
  const payloadInput = seenPayloadEnvelopeInputCache.get(envelopeBlockRootHex);
231
232
  if (payloadInput === undefined) {
232
- // Unreachable given the loop above seeded an entry for every gloas block in the batch.
233
- // for the parent block, it's populated at BeaconChain init
233
+ // Unreachable given the validatedBlocks loop above seeded an entry for every gloas block in
234
+ // the batch. for the parent block, it's populated at BeaconChain init
234
235
  throw new Error(`Missing PayloadEnvelopeInput for block ${envelopeBlockRootHex}`);
235
236
  }
236
237