@lodestar/beacon-node 1.50.0-dev.cea8fba405 → 1.50.0-dev.db953a4f93

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 (64) 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/blocks/importBlock.d.ts.map +1 -1
  12. package/lib/chain/blocks/importBlock.js +4 -0
  13. package/lib/chain/blocks/importBlock.js.map +1 -1
  14. package/lib/chain/chain.js +3 -3
  15. package/lib/chain/chain.js.map +1 -1
  16. package/lib/chain/initState.d.ts +2 -9
  17. package/lib/chain/initState.d.ts.map +1 -1
  18. package/lib/chain/initState.js +5 -38
  19. package/lib/chain/initState.js.map +1 -1
  20. package/lib/chain/opPools/opPool.d.ts +5 -4
  21. package/lib/chain/opPools/opPool.d.ts.map +1 -1
  22. package/lib/chain/opPools/opPool.js +62 -18
  23. package/lib/chain/opPools/opPool.js.map +1 -1
  24. package/lib/chain/opPools/utils.d.ts +4 -1
  25. package/lib/chain/opPools/utils.d.ts.map +1 -1
  26. package/lib/chain/opPools/utils.js +11 -1
  27. package/lib/chain/opPools/utils.js.map +1 -1
  28. package/lib/chain/validation/attesterSlashing.d.ts +4 -4
  29. package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
  30. package/lib/chain/validation/attesterSlashing.js +3 -0
  31. package/lib/chain/validation/attesterSlashing.js.map +1 -1
  32. package/lib/chain/validation/proposerSlashing.d.ts +3 -3
  33. package/lib/chain/validation/proposerSlashing.d.ts.map +1 -1
  34. package/lib/chain/validation/proposerSlashing.js +3 -0
  35. package/lib/chain/validation/proposerSlashing.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/index.d.ts +1 -1
  41. package/lib/index.d.ts.map +1 -1
  42. package/lib/index.js +1 -1
  43. package/lib/index.js.map +1 -1
  44. package/lib/network/processor/gossipHandlers.js +4 -4
  45. package/lib/network/processor/gossipHandlers.js.map +1 -1
  46. package/lib/sync/unknownBlock.d.ts +9 -5
  47. package/lib/sync/unknownBlock.d.ts.map +1 -1
  48. package/lib/sync/unknownBlock.js +83 -42
  49. package/lib/sync/unknownBlock.js.map +1 -1
  50. package/package.json +15 -15
  51. package/src/api/impl/beacon/pool/index.ts +4 -4
  52. package/src/chain/archiveStore/archiveStore.ts +9 -3
  53. package/src/chain/archiveStore/utils/pruneHistory.ts +6 -4
  54. package/src/chain/blocks/importBlock.ts +4 -0
  55. package/src/chain/chain.ts +3 -3
  56. package/src/chain/initState.ts +8 -60
  57. package/src/chain/opPools/opPool.ts +91 -18
  58. package/src/chain/opPools/utils.ts +31 -3
  59. package/src/chain/validation/attesterSlashing.ts +7 -4
  60. package/src/chain/validation/proposerSlashing.ts +7 -4
  61. package/src/db/repositories/proposerSlashing.ts +6 -1
  62. package/src/index.ts +1 -1
  63. package/src/network/processor/gossipHandlers.ts +4 -4
  64. package/src/sync/unknownBlock.ts +94 -46
@@ -1065,12 +1065,12 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1065
1065
  const {serializedData} = gossipData;
1066
1066
  const {fork} = topic.boundary;
1067
1067
  const attesterSlashing = sszDeserialize(topic, serializedData);
1068
- await validateGossipAttesterSlashing(chain, attesterSlashing);
1068
+ const verifiedDomains = await validateGossipAttesterSlashing(chain, attesterSlashing);
1069
1069
 
1070
1070
  // Handler - deferred to next event loop so the validation result propagates first
1071
1071
  callInNextEventLoop(() => {
1072
1072
  try {
1073
- chain.opPool.insertAttesterSlashing(fork, attesterSlashing);
1073
+ chain.opPool.insertAttesterSlashing(fork, attesterSlashing, verifiedDomains);
1074
1074
  chain.forkChoice.onAttesterSlashing(attesterSlashing);
1075
1075
  chain.emitter.emit(routes.events.EventType.attesterSlashing, attesterSlashing);
1076
1076
  } catch (e) {
@@ -1089,12 +1089,12 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1089
1089
  }: GossipHandlerParamGeneric<GossipType.proposer_slashing>) => {
1090
1090
  const {serializedData} = gossipData;
1091
1091
  const proposerSlashing = sszDeserialize(topic, serializedData);
1092
- await validateGossipProposerSlashing(chain, proposerSlashing);
1092
+ const verifiedDomain = await validateGossipProposerSlashing(chain, proposerSlashing);
1093
1093
 
1094
1094
  // Handler - deferred to next event loop so the validation result propagates first
1095
1095
  callInNextEventLoop(() => {
1096
1096
  try {
1097
- chain.opPool.insertProposerSlashing(proposerSlashing);
1097
+ chain.opPool.insertProposerSlashing(proposerSlashing, verifiedDomain);
1098
1098
  chain.emitter.emit(routes.events.EventType.proposerSlashing, proposerSlashing);
1099
1099
  } catch (e) {
1100
1100
  logger.debug(
@@ -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