@lodestar/beacon-node 1.50.0-dev.cc4428df4b → 1.50.0-dev.d86161f63c

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 (91) hide show
  1. package/lib/api/impl/beacon/pool/index.js +4 -4
  2. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  3. package/lib/chain/archiveStore/archiveStore.d.ts +1 -0
  4. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  5. package/lib/chain/archiveStore/archiveStore.js +6 -2
  6. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  7. package/lib/chain/archiveStore/utils/pruneHistory.d.ts +4 -1
  8. package/lib/chain/archiveStore/utils/pruneHistory.d.ts.map +1 -1
  9. package/lib/chain/archiveStore/utils/pruneHistory.js +4 -3
  10. package/lib/chain/archiveStore/utils/pruneHistory.js.map +1 -1
  11. package/lib/chain/chain.js +3 -3
  12. package/lib/chain/chain.js.map +1 -1
  13. package/lib/chain/initState.d.ts +2 -9
  14. package/lib/chain/initState.d.ts.map +1 -1
  15. package/lib/chain/initState.js +5 -38
  16. package/lib/chain/initState.js.map +1 -1
  17. package/lib/chain/opPools/opPool.d.ts +5 -4
  18. package/lib/chain/opPools/opPool.d.ts.map +1 -1
  19. package/lib/chain/opPools/opPool.js +62 -18
  20. package/lib/chain/opPools/opPool.js.map +1 -1
  21. package/lib/chain/opPools/utils.d.ts +4 -1
  22. package/lib/chain/opPools/utils.d.ts.map +1 -1
  23. package/lib/chain/opPools/utils.js +11 -1
  24. package/lib/chain/opPools/utils.js.map +1 -1
  25. package/lib/chain/validation/attesterSlashing.d.ts +4 -4
  26. package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
  27. package/lib/chain/validation/attesterSlashing.js +3 -0
  28. package/lib/chain/validation/attesterSlashing.js.map +1 -1
  29. package/lib/chain/validation/proposerSlashing.d.ts +3 -3
  30. package/lib/chain/validation/proposerSlashing.d.ts.map +1 -1
  31. package/lib/chain/validation/proposerSlashing.js +3 -0
  32. package/lib/chain/validation/proposerSlashing.js.map +1 -1
  33. package/lib/db/beacon.d.ts.map +1 -1
  34. package/lib/db/beacon.js +2 -0
  35. package/lib/db/beacon.js.map +1 -1
  36. package/lib/db/repositories/proposerSlashing.d.ts +1 -0
  37. package/lib/db/repositories/proposerSlashing.d.ts.map +1 -1
  38. package/lib/db/repositories/proposerSlashing.js +5 -1
  39. package/lib/db/repositories/proposerSlashing.js.map +1 -1
  40. package/lib/execution/engine/http.d.ts.map +1 -1
  41. package/lib/execution/engine/http.js +5 -4
  42. package/lib/execution/engine/http.js.map +1 -1
  43. package/lib/index.d.ts +1 -1
  44. package/lib/index.d.ts.map +1 -1
  45. package/lib/index.js +1 -1
  46. package/lib/index.js.map +1 -1
  47. package/lib/metrics/metrics/beacon.d.ts +1 -1
  48. package/lib/metrics/metrics/beacon.d.ts.map +1 -1
  49. package/lib/metrics/metrics/beacon.js.map +1 -1
  50. package/lib/network/metadata.d.ts +1 -1
  51. package/lib/network/metadata.d.ts.map +1 -1
  52. package/lib/network/metadata.js +17 -17
  53. package/lib/network/metadata.js.map +1 -1
  54. package/lib/network/peers/datastore.d.ts +9 -9
  55. package/lib/network/peers/datastore.d.ts.map +1 -1
  56. package/lib/network/peers/datastore.js +36 -36
  57. package/lib/network/peers/datastore.js.map +1 -1
  58. package/lib/network/processor/gossipHandlers.js +4 -4
  59. package/lib/network/processor/gossipHandlers.js.map +1 -1
  60. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.d.ts.map +1 -1
  61. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js +41 -26
  62. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js.map +1 -1
  63. package/lib/sync/unknownBlock.d.ts +9 -5
  64. package/lib/sync/unknownBlock.d.ts.map +1 -1
  65. package/lib/sync/unknownBlock.js +83 -42
  66. package/lib/sync/unknownBlock.js.map +1 -1
  67. package/lib/util/execution.d.ts +2 -0
  68. package/lib/util/execution.d.ts.map +1 -1
  69. package/lib/util/execution.js +6 -6
  70. package/lib/util/execution.js.map +1 -1
  71. package/package.json +15 -15
  72. package/src/api/impl/beacon/pool/index.ts +4 -4
  73. package/src/chain/archiveStore/archiveStore.ts +9 -3
  74. package/src/chain/archiveStore/utils/pruneHistory.ts +6 -4
  75. package/src/chain/chain.ts +3 -3
  76. package/src/chain/initState.ts +8 -60
  77. package/src/chain/opPools/opPool.ts +91 -18
  78. package/src/chain/opPools/utils.ts +31 -3
  79. package/src/chain/validation/attesterSlashing.ts +7 -4
  80. package/src/chain/validation/proposerSlashing.ts +7 -4
  81. package/src/db/beacon.ts +2 -0
  82. package/src/db/repositories/proposerSlashing.ts +6 -1
  83. package/src/execution/engine/http.ts +6 -4
  84. package/src/index.ts +1 -1
  85. package/src/metrics/metrics/beacon.ts +1 -1
  86. package/src/network/metadata.ts +17 -17
  87. package/src/network/peers/datastore.ts +36 -36
  88. package/src/network/processor/gossipHandlers.ts +4 -4
  89. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRoot.ts +42 -28
  90. package/src/sync/unknownBlock.ts +94 -46
  91. package/src/util/execution.ts +10 -6
@@ -15,7 +15,7 @@ import {ChainEvent, ChainEventData, IBeaconChain} from "../chain/index.js";
15
15
  import {validateGloasBlockDataColumnSidecars} from "../chain/validation/dataColumnSidecar.js";
16
16
  import {validateGossipExecutionPayloadEnvelope} from "../chain/validation/executionPayloadEnvelope.js";
17
17
  import {Metrics} from "../metrics/index.js";
18
- import {INetwork, NetworkEvent, NetworkEventData, prettyPrintPeerIdStr} from "../network/index.js";
18
+ import {INetwork, NetworkEvent, NetworkEventData, PeerAction, prettyPrintPeerIdStr} from "../network/index.js";
19
19
  import {PeerSyncMeta} from "../network/peers/peersData.js";
20
20
  import {MAX_PEERS_PER_ROOT} from "../network/processor/constants.js";
21
21
  import {ClockEvent} from "../util/clock.js";
@@ -442,6 +442,11 @@ export class BlockInputSync {
442
442
  };
443
443
 
444
444
  private addByRootHex = (rootHex: RootHex, peerIdStr?: PeerIdStr): boolean => {
445
+ if (this.knownBadBlocks.has(rootHex)) {
446
+ this.logger.debug("Ignoring known bad block root", {root: rootHex, peerIdStr: peerIdStr ?? "unknown peer"});
447
+ return false;
448
+ }
449
+
445
450
  let pendingBlock = this.pendingBlocks.get(rootHex);
446
451
  let added = false;
447
452
  if (!pendingBlock) {
@@ -477,6 +482,11 @@ export class BlockInputSync {
477
482
  };
478
483
 
479
484
  private addByBlockInput = (blockInput: IBlockInput, peerIdStr?: string): void => {
485
+ if (this.knownBadBlocks.has(blockInput.blockRootHex)) {
486
+ this.logger.debug("Ignoring known bad block", {slot: blockInput.slot, root: blockInput.blockRootHex, peerIdStr});
487
+ return;
488
+ }
489
+
480
490
  let pendingBlock = this.pendingBlocks.get(blockInput.blockRootHex);
481
491
  // if entry is missing or was added via rootHex and now we have more complete information overwrite
482
492
  // the existing information with the more complete cache entry
@@ -513,6 +523,15 @@ export class BlockInputSync {
513
523
  };
514
524
 
515
525
  private addByPayloadRootHex = (rootHex: RootHex, peerIdStr?: PeerIdStr, slot?: Slot): boolean => {
526
+ if (this.knownBadBlocks.has(rootHex)) {
527
+ this.logger.debug("Ignoring payload for known bad block root", {
528
+ slot: slot ?? "unknown",
529
+ root: rootHex,
530
+ peerIdStr,
531
+ });
532
+ return false;
533
+ }
534
+
516
535
  let pendingPayload = this.pendingPayloads.get(rootHex);
517
536
  let added = false;
518
537
  if (!pendingPayload) {
@@ -581,6 +600,15 @@ export class BlockInputSync {
581
600
  peerIdStr?: PeerIdStr,
582
601
  envelope?: gloas.SignedExecutionPayloadEnvelope
583
602
  ): void => {
603
+ if (this.knownBadBlocks.has(payloadInput.blockRootHex)) {
604
+ this.logger.debug("Ignoring payload input for known bad block root", {
605
+ slot: payloadInput.slot,
606
+ root: payloadInput.blockRootHex,
607
+ peerIdStr,
608
+ });
609
+ return;
610
+ }
611
+
584
612
  const pendingPayload = this.toPendingPayloadInput(
585
613
  payloadInput,
586
614
  this.pendingPayloads.get(payloadInput.blockRootHex),
@@ -654,6 +682,7 @@ export class BlockInputSync {
654
682
  return {kind: "ready"};
655
683
  }
656
684
 
685
+ // The parent's payload is known but does not match `parentBlockHashHex`
657
686
  if (this.chain.forkChoice.hasPayloadHexUnsafe(parentRootHex)) {
658
687
  return {kind: "invalidParentPayload", parentRootHex, parentBlockHashHex};
659
688
  }
@@ -1032,7 +1061,8 @@ export class BlockInputSync {
1032
1061
  }
1033
1062
  } else if (blockSlot <= finalizedSlot) {
1034
1063
  // the common ancestor of the downloading chain and canonical chain should be at least the finalized slot and
1035
- // we should found it through forkchoice. If not, we should penalize all peers sending us this block chain
1064
+ // we should found it through forkchoice. If not, drop the chain without penalizing peers because pending entries are
1065
+ // block retained on failed download, so finalization can pass the block while it is still pending
1036
1066
  // 0 - 1 - ... - n - finalizedSlot
1037
1067
  // \
1038
1068
  // parent 1 - parent 2 - ... - unknownParent block
@@ -1040,7 +1070,7 @@ export class BlockInputSync {
1040
1070
  ...logCtx2,
1041
1071
  finalizedSlot,
1042
1072
  });
1043
- this.removeAndDownScoreAllDescendants(block, DroppedItemReason.belowFinalized);
1073
+ this.removeAllDescendants(block, DroppedItemReason.belowFinalized);
1044
1074
  } else {
1045
1075
  this.onUnknownBlockRoot({rootHex: pending.blockInput.parentRootHex, source: BlockInputSource.byRoot});
1046
1076
  }
@@ -1172,15 +1202,18 @@ export class BlockInputSync {
1172
1202
  break;
1173
1203
 
1174
1204
  case BlockErrorCode.EXECUTION_ENGINE_INVALID:
1175
- // the peer served a bad block
1205
+ // Ban but do not downscore: optimistic-sync peers may forward and serve blocks whose payload is later
1206
+ // rejected by the execution engine. Per the spec, an INVALID execution payload SHOULD NOT cause a peer
1207
+ // to be down-scored or disconnected, only consensus-layer invalidity MAY.
1208
+ // https://github.com/ethereum/consensus-specs/blob/master/specs/bellatrix/p2p-interface.md#the-reqresp-domain
1176
1209
  this.logger.debug("Execution engine rejected block from unknown parent sync", errorData, res.err);
1177
- this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.elInvalid);
1210
+ this.removeAndBanAllDescendants(pendingBlock, DroppedItemReason.elInvalid);
1178
1211
  break;
1179
1212
 
1180
1213
  default:
1181
1214
  // Block is not correct with respect to our chain. Log error loudly
1182
1215
  this.logger.debug("Error processing block from unknown parent sync", errorData, res.err);
1183
- this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidBlock);
1216
+ this.removeAllDescendants(pendingBlock, DroppedItemReason.invalidBlock);
1184
1217
  }
1185
1218
  }
1186
1219
 
@@ -1478,11 +1511,11 @@ export class BlockInputSync {
1478
1511
  }
1479
1512
 
1480
1513
  const {peerId, client: peerClient} = peerMeta;
1481
- cacheItem.peerIdStrings.add(peerId);
1482
1514
 
1483
1515
  try {
1484
1516
  if (!envelope) {
1485
1517
  envelope = await this.fetchExecutionPayloadEnvelope(peerId, blockRoot, rootHex);
1518
+ cacheItem.peerIdStrings.add(peerId);
1486
1519
  const slotWasUnknown = typeof slot !== "number";
1487
1520
  slot = envelope.message.payload.slotNumber;
1488
1521
  if (slotWasUnknown) {
@@ -1524,6 +1557,8 @@ export class BlockInputSync {
1524
1557
  const missing = pendingPayload.payloadInput.getMissingSampledColumnMeta().missing;
1525
1558
  if (missing.length > 0) {
1526
1559
  const columnSidecars = await this.fetchPayloadColumns(peerMeta, pendingPayload.payloadInput, missing);
1560
+ // toPendingPayloadInput copied cacheItem's set, so add to the object that is carried forward
1561
+ pendingPayload.peerIdStrings.add(peerId);
1527
1562
  const seenTimestampSec = Date.now() / 1000;
1528
1563
  for (const columnSidecar of columnSidecars) {
1529
1564
  if (pendingPayload.payloadInput.hasColumn(columnSidecar.index)) {
@@ -1716,8 +1751,6 @@ export class BlockInputSync {
1716
1751
  }
1717
1752
  const {peerId, client: peerClient} = peerMeta;
1718
1753
 
1719
- cacheItem.peerIdStrings.add(peerId);
1720
-
1721
1754
  try {
1722
1755
  const downloadResult = await downloadByRoot({
1723
1756
  config: this.config,
@@ -1728,6 +1761,7 @@ export class BlockInputSync {
1728
1761
  cacheItem,
1729
1762
  });
1730
1763
  cacheItem = downloadResult.result;
1764
+ cacheItem.peerIdStrings.add(peerId);
1731
1765
  if (slot === undefined) {
1732
1766
  slot = cacheItem.blockInput.slot;
1733
1767
  // we were not able to observe the time into slot when starting the fetch, do it now
@@ -1843,40 +1877,6 @@ export class BlockInputSync {
1843
1877
  throw Error(message);
1844
1878
  }
1845
1879
 
1846
- /**
1847
- * Gets all descendant blocks of `block` recursively from `pendingBlocks`.
1848
- * Assumes that if a parent block does not exist or is not processable, all descendant blocks are bad too.
1849
- * Downscore all peers that have referenced any of this bad blocks. May report peers multiple times if they have
1850
- * referenced more than one bad block.
1851
- */
1852
- private removeAndDownScoreAllDescendants(block: BlockInputSyncCacheItem, headReason: DroppedItemReason): void {
1853
- // Get all blocks that are a descendant of this one
1854
- const badPendingBlocks = this.removeAllDescendants(block, headReason, DroppedItemReason.invalidParent);
1855
- // just console log and do not penalize on pending/bad blocks for debugging
1856
- // console.log("removeAndDownscoreAllDescendants", {block});
1857
-
1858
- for (const block of badPendingBlocks) {
1859
- //
1860
- // TODO(fulu): why is this commented out here?
1861
- //
1862
- // this.knownBadBlocks.add(block.blockRootHex);
1863
- // for (const peerIdStr of block.peerIdStrings) {
1864
- // // TODO: Refactor peerRpcScores to work with peerIdStr only
1865
- // this.network.reportPeer(peerIdStr, PeerAction.LowToleranceError, "BadBlockByRoot");
1866
- // }
1867
- this.logger.debug("ignored Banning unknown block", {
1868
- slot: getBlockInputSyncCacheItemSlot(block),
1869
- root: getBlockInputSyncCacheItemRootHex(block),
1870
- peerIdStrings: Array.from(block.peerIdStrings)
1871
- .map((id) => prettyPrintPeerIdStr(id))
1872
- .join(","),
1873
- });
1874
- }
1875
-
1876
- // Prune knownBadBlocks
1877
- pruneSetToMax(this.knownBadBlocks, MAX_KNOWN_BAD_BLOCKS);
1878
- }
1879
-
1880
1880
  // Once a parent payload is invalid, every descendant waiting on that payload lineage becomes unrecoverable too.
1881
1881
  private removePendingPayloadAndDescendants(rootHex: RootHex, headReason: DroppedItemReason): void {
1882
1882
  // Keep PayloadEnvelopeInput resident in the seen cache. importBlock() owns that object and
@@ -1903,13 +1903,61 @@ export class BlockInputSync {
1903
1903
  }
1904
1904
  }
1905
1905
 
1906
+ /**
1907
+ * Same as `removeAndBanAllDescendants`, and downscores every peer that referenced or served a banned item, once per
1908
+ * peer. Only call for consensus-layer invalidity, never for an INVALID execution payload:
1909
+ * https://github.com/ethereum/consensus-specs/blob/master/specs/bellatrix/p2p-interface.md#the-reqresp-domain
1910
+ */
1911
+ private removeAndDownScoreAllDescendants(block: BlockInputSyncCacheItem, headReason: DroppedItemReason): void {
1912
+ const peersToReport = new Set<PeerIdStr>();
1913
+ for (const bannedBlock of this.removeAndBanAllDescendants(block, headReason)) {
1914
+ for (const peerIdStr of bannedBlock.peerIdStrings) {
1915
+ peersToReport.add(peerIdStr);
1916
+ }
1917
+ }
1918
+
1919
+ for (const peerIdStr of peersToReport) {
1920
+ this.network.reportPeer(peerIdStr, PeerAction.LowToleranceError, headReason);
1921
+ }
1922
+ }
1923
+
1924
+ /**
1925
+ * Removes `block` and all its pending descendants and bans their roots in `knownBadBlocks`. Only call when the chain
1926
+ * is provably bad: a banned root is never searched for again. Returns the banned items.
1927
+ */
1928
+ private removeAndBanAllDescendants(
1929
+ block: BlockInputSyncCacheItem,
1930
+ headReason: DroppedItemReason
1931
+ ): BlockInputSyncCacheItem[] {
1932
+ const bannedBlocks: BlockInputSyncCacheItem[] = [];
1933
+ for (const badBlock of this.removeAllDescendants(block, headReason, DroppedItemReason.invalidParent)) {
1934
+ const rootHex = getBlockInputSyncCacheItemRootHex(badBlock);
1935
+ // a gossip import may race a failing sync path, never ban a root fork choice has imported
1936
+ if (this.chain.forkChoice.hasBlockHex(rootHex)) {
1937
+ continue;
1938
+ }
1939
+ this.knownBadBlocks.add(rootHex);
1940
+ bannedBlocks.push(badBlock);
1941
+ this.logger.debug("Banning unknown block", {
1942
+ slot: getBlockInputSyncCacheItemSlot(badBlock),
1943
+ root: rootHex,
1944
+ reason: headReason,
1945
+ peerIdStrings: Array.from(badBlock.peerIdStrings)
1946
+ .map((id) => prettyPrintPeerIdStr(id))
1947
+ .join(","),
1948
+ });
1949
+ }
1950
+
1951
+ pruneSetToMax(this.knownBadBlocks, MAX_KNOWN_BAD_BLOCKS);
1952
+ return bannedBlocks;
1953
+ }
1954
+
1906
1955
  private removeAllDescendants(
1907
1956
  block: BlockInputSyncCacheItem,
1908
1957
  headReason: DroppedItemReason,
1909
1958
  descendantReason: DroppedItemReason = DroppedItemReason.invalidParent
1910
1959
  ): BlockInputSyncCacheItem[] {
1911
1960
  const rootHex = getBlockInputSyncCacheItemRootHex(block);
1912
- const slot = getBlockInputSyncCacheItemSlot(block);
1913
1961
  // Get all blocks that are a descendant of this one (index 0 is the head item itself)
1914
1962
  const badPendingBlocks = [block, ...getAllDescendantBlocks(rootHex, this.pendingBlocks)];
1915
1963
 
@@ -1926,8 +1974,8 @@ export class BlockInputSync {
1926
1974
  // Keep PayloadEnvelopeInput resident in the seen cache for consistency with the
1927
1975
  // importBlock()-owned lifecycle.
1928
1976
  this.logger.debug("Removing bad/unknown/incomplete BlockInputSyncCacheItem", {
1929
- slot,
1930
- blockRoot: rootHex,
1977
+ slot: getBlockInputSyncCacheItemSlot(block),
1978
+ root: rootHex,
1931
1979
  });
1932
1980
  }
1933
1981
 
@@ -259,8 +259,8 @@ export async function getDataColumnSidecarsFromExecution(
259
259
  return DataColumnEngineResult.SuccessResolved;
260
260
  }
261
261
 
262
- /** engine_getPayloadBodiesByHashV2: ELs MUST support at least 32 hashes per request. */
263
- const MAX_BODIES_REQUEST = 32;
262
+ /** engine_getPayloadBodiesByHashV2: every EL must accept requests of up to 32 hashes, larger ones may fail with -38004 */
263
+ export const MAX_BODIES_PER_REQUEST = 32;
264
264
 
265
265
  type SlotEnvelopeBytes = {slot: Slot; envelopeBytes: Uint8Array};
266
266
 
@@ -299,7 +299,7 @@ export async function* reconstructExecutionPayloadEnvelopesByRange(
299
299
 
300
300
  for await (const {key, value: bytes} of archive.binaryEntriesStream({gte: startSlot, lt: endSlot})) {
301
301
  batch.push({slot: archive.decodeKey(key), ...decodeArchivedEnvelope(bytes)});
302
- if (batch.length === MAX_BODIES_REQUEST) {
302
+ if (batch.length === MAX_BODIES_PER_REQUEST) {
303
303
  yield* reconstructBatch(executionEngine, logger, metrics, batch);
304
304
  batch = [];
305
305
  }
@@ -359,9 +359,13 @@ export async function reconstructExecutionPayloadEnvelopes(
359
359
  headerEnvelopes: gloas.SignedExecutionPayloadHeaderEnvelope[]
360
360
  ): Promise<(gloas.SignedExecutionPayloadEnvelope | RebuildMiss)[]> {
361
361
  const out: (gloas.SignedExecutionPayloadEnvelope | RebuildMiss)[] = [];
362
- for (let i = 0; i < headerEnvelopes.length; i += MAX_BODIES_REQUEST) {
362
+ for (let i = 0; i < headerEnvelopes.length; i += MAX_BODIES_PER_REQUEST) {
363
363
  out.push(
364
- ...(await reconstructEnvelopesBatch(executionEngine, metrics, headerEnvelopes.slice(i, i + MAX_BODIES_REQUEST)))
364
+ ...(await reconstructEnvelopesBatch(
365
+ executionEngine,
366
+ metrics,
367
+ headerEnvelopes.slice(i, i + MAX_BODIES_PER_REQUEST)
368
+ ))
365
369
  );
366
370
  }
367
371
  return out;
@@ -409,7 +413,7 @@ async function reconstructEnvelopesBatch(
409
413
  withdrawals: body.withdrawals,
410
414
  blockAccessList: body.blockAccessList,
411
415
  });
412
- metrics?.payloadEnvelopeReconstruction.envelopes.inc({result: "ok"});
416
+ metrics?.payloadEnvelopeReconstruction.envelopes.inc({result: "success"});
413
417
  return envelope;
414
418
  } catch (e) {
415
419
  if (