@lodestar/beacon-node 1.40.0-dev.4acd3ce568 → 1.40.0-dev.9e8478fc70

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 (136) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +8 -18
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/debug/index.d.ts +1 -1
  5. package/lib/api/impl/debug/index.d.ts.map +1 -1
  6. package/lib/api/impl/debug/index.js +3 -6
  7. package/lib/api/impl/debug/index.js.map +1 -1
  8. package/lib/api/impl/lodestar/index.d.ts.map +1 -1
  9. package/lib/api/impl/lodestar/index.js +5 -2
  10. package/lib/api/impl/lodestar/index.js.map +1 -1
  11. package/lib/api/impl/validator/index.d.ts.map +1 -1
  12. package/lib/api/impl/validator/index.js +9 -8
  13. package/lib/api/impl/validator/index.js.map +1 -1
  14. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
  15. package/lib/chain/archiveStore/utils/archiveBlocks.js +4 -0
  16. package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
  17. package/lib/chain/blocks/blockInput/blockInput.d.ts +2 -0
  18. package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
  19. package/lib/chain/blocks/blockInput/blockInput.js +6 -0
  20. package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
  21. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  22. package/lib/chain/blocks/importBlock.js +2 -6
  23. package/lib/chain/blocks/importBlock.js.map +1 -1
  24. package/lib/chain/blocks/index.d.ts.map +1 -1
  25. package/lib/chain/blocks/index.js +0 -14
  26. package/lib/chain/blocks/index.js.map +1 -1
  27. package/lib/chain/blocks/types.d.ts +0 -2
  28. package/lib/chain/blocks/types.d.ts.map +1 -1
  29. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  30. package/lib/chain/blocks/verifyBlock.js +0 -7
  31. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  32. package/lib/chain/blocks/writeBlockInputToDb.d.ts +1 -4
  33. package/lib/chain/blocks/writeBlockInputToDb.d.ts.map +1 -1
  34. package/lib/chain/blocks/writeBlockInputToDb.js +20 -28
  35. package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
  36. package/lib/chain/chain.d.ts +14 -2
  37. package/lib/chain/chain.d.ts.map +1 -1
  38. package/lib/chain/chain.js +158 -10
  39. package/lib/chain/chain.js.map +1 -1
  40. package/lib/chain/interface.d.ts +14 -1
  41. package/lib/chain/interface.d.ts.map +1 -1
  42. package/lib/chain/interface.js.map +1 -1
  43. package/lib/chain/prepareNextSlot.js +6 -4
  44. package/lib/chain/prepareNextSlot.js.map +1 -1
  45. package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -2
  46. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  47. package/lib/chain/produceBlock/produceBlockBody.js +5 -3
  48. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  49. package/lib/chain/regen/interface.d.ts +2 -8
  50. package/lib/chain/regen/interface.d.ts.map +1 -1
  51. package/lib/chain/regen/interface.js +0 -1
  52. package/lib/chain/regen/interface.js.map +1 -1
  53. package/lib/chain/regen/queued.d.ts +1 -2
  54. package/lib/chain/regen/queued.d.ts.map +1 -1
  55. package/lib/chain/regen/queued.js +2 -16
  56. package/lib/chain/regen/queued.js.map +1 -1
  57. package/lib/chain/regen/regen.d.ts +3 -7
  58. package/lib/chain/regen/regen.d.ts.map +1 -1
  59. package/lib/chain/regen/regen.js +3 -16
  60. package/lib/chain/regen/regen.js.map +1 -1
  61. package/lib/chain/validation/blobSidecar.js +1 -1
  62. package/lib/chain/validation/blobSidecar.js.map +1 -1
  63. package/lib/chain/validation/dataColumnSidecar.js +1 -1
  64. package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
  65. package/lib/chain/validatorMonitor.d.ts +2 -0
  66. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  67. package/lib/chain/validatorMonitor.js +42 -3
  68. package/lib/chain/validatorMonitor.js.map +1 -1
  69. package/lib/metrics/metrics/lodestar.d.ts +7 -0
  70. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  71. package/lib/metrics/metrics/lodestar.js +24 -0
  72. package/lib/metrics/metrics/lodestar.js.map +1 -1
  73. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  74. package/lib/network/processor/gossipHandlers.js +0 -3
  75. package/lib/network/processor/gossipHandlers.js.map +1 -1
  76. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
  77. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +2 -4
  78. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  79. package/lib/network/reqresp/handlers/beaconBlocksByRoot.d.ts +1 -2
  80. package/lib/network/reqresp/handlers/beaconBlocksByRoot.d.ts.map +1 -1
  81. package/lib/network/reqresp/handlers/beaconBlocksByRoot.js +5 -26
  82. package/lib/network/reqresp/handlers/beaconBlocksByRoot.js.map +1 -1
  83. package/lib/network/reqresp/handlers/blobSidecarsByRoot.d.ts +1 -2
  84. package/lib/network/reqresp/handlers/blobSidecarsByRoot.d.ts.map +1 -1
  85. package/lib/network/reqresp/handlers/blobSidecarsByRoot.js +5 -7
  86. package/lib/network/reqresp/handlers/blobSidecarsByRoot.js.map +1 -1
  87. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  88. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +1 -2
  89. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  90. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.d.ts.map +1 -1
  91. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +1 -5
  92. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
  93. package/lib/network/reqresp/handlers/index.js +2 -2
  94. package/lib/network/reqresp/handlers/index.js.map +1 -1
  95. package/lib/sync/range/chain.d.ts.map +1 -1
  96. package/lib/sync/range/chain.js +0 -1
  97. package/lib/sync/range/chain.js.map +1 -1
  98. package/lib/sync/range/range.d.ts.map +1 -1
  99. package/lib/sync/range/range.js +0 -3
  100. package/lib/sync/range/range.js.map +1 -1
  101. package/lib/sync/unknownBlock.d.ts.map +1 -1
  102. package/lib/sync/unknownBlock.js +0 -3
  103. package/lib/sync/unknownBlock.js.map +1 -1
  104. package/package.json +15 -15
  105. package/src/api/impl/beacon/blocks/index.ts +8 -18
  106. package/src/api/impl/debug/index.ts +2 -6
  107. package/src/api/impl/lodestar/index.ts +6 -3
  108. package/src/api/impl/validator/index.ts +12 -11
  109. package/src/chain/archiveStore/utils/archiveBlocks.ts +4 -0
  110. package/src/chain/blocks/blockInput/blockInput.ts +8 -0
  111. package/src/chain/blocks/importBlock.ts +2 -6
  112. package/src/chain/blocks/index.ts +0 -19
  113. package/src/chain/blocks/types.ts +0 -2
  114. package/src/chain/blocks/verifyBlock.ts +0 -8
  115. package/src/chain/blocks/writeBlockInputToDb.ts +24 -30
  116. package/src/chain/chain.ts +180 -20
  117. package/src/chain/interface.ts +16 -0
  118. package/src/chain/prepareNextSlot.ts +6 -6
  119. package/src/chain/produceBlock/produceBlockBody.ts +7 -5
  120. package/src/chain/regen/interface.ts +1 -12
  121. package/src/chain/regen/queued.ts +3 -23
  122. package/src/chain/regen/regen.ts +4 -24
  123. package/src/chain/validation/blobSidecar.ts +1 -1
  124. package/src/chain/validation/dataColumnSidecar.ts +1 -1
  125. package/src/chain/validatorMonitor.ts +52 -3
  126. package/src/metrics/metrics/lodestar.ts +25 -0
  127. package/src/network/processor/gossipHandlers.ts +0 -3
  128. package/src/network/reqresp/handlers/beaconBlocksByRange.ts +2 -4
  129. package/src/network/reqresp/handlers/beaconBlocksByRoot.ts +5 -32
  130. package/src/network/reqresp/handlers/blobSidecarsByRoot.ts +5 -9
  131. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +5 -2
  132. package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +1 -5
  133. package/src/network/reqresp/handlers/index.ts +2 -2
  134. package/src/sync/range/chain.ts +0 -1
  135. package/src/sync/range/range.ts +0 -3
  136. package/src/sync/unknownBlock.ts +0 -3
@@ -1,7 +1,7 @@
1
1
  import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map";
2
2
  import {routes} from "@lodestar/api";
3
3
  import {ApplicationMethods} from "@lodestar/api/server";
4
- import {ExecutionStatus} from "@lodestar/fork-choice";
4
+ import {ExecutionStatus, ProtoBlock} from "@lodestar/fork-choice";
5
5
  import {
6
6
  ForkName,
7
7
  ForkPostBellatrix,
@@ -413,10 +413,10 @@ export function getValidatorApi(
413
413
  // as of now fee recipient checks can not be performed because builder does not return bid recipient
414
414
  {
415
415
  commonBlockBodyPromise,
416
- parentBlockRoot,
416
+ parentBlock,
417
417
  }: Omit<routes.validator.ExtraProduceBlockOpts, "builderSelection"> & {
418
418
  commonBlockBodyPromise: Promise<CommonBlockBody>;
419
- parentBlockRoot: Root;
419
+ parentBlock: ProtoBlock;
420
420
  }
421
421
  ): Promise<ProduceBlindedBlockRes> {
422
422
  const version = config.getForkName(slot);
@@ -447,7 +447,7 @@ export function getValidatorApi(
447
447
  timer = metrics?.blockProductionTime.startTimer();
448
448
  const {block, executionPayloadValue, consensusBlockValue} = await chain.produceBlindedBlock({
449
449
  slot,
450
- parentBlockRoot,
450
+ parentBlock,
451
451
  randaoReveal,
452
452
  graffiti,
453
453
  commonBlockBodyPromise,
@@ -482,10 +482,10 @@ export function getValidatorApi(
482
482
  feeRecipient,
483
483
  strictFeeRecipientCheck,
484
484
  commonBlockBodyPromise,
485
- parentBlockRoot,
485
+ parentBlock,
486
486
  }: Omit<routes.validator.ExtraProduceBlockOpts, "builderSelection"> & {
487
487
  commonBlockBodyPromise: Promise<CommonBlockBody>;
488
- parentBlockRoot: Root;
488
+ parentBlock: ProtoBlock;
489
489
  }
490
490
  ): Promise<ProduceBlockContentsRes & {shouldOverrideBuilder?: boolean}> {
491
491
  const source = ProducedBlockSource.engine;
@@ -496,7 +496,7 @@ export function getValidatorApi(
496
496
  timer = metrics?.blockProductionTime.startTimer();
497
497
  const {block, executionPayloadValue, consensusBlockValue, shouldOverrideBuilder} = await chain.produceBlock({
498
498
  slot,
499
- parentBlockRoot,
499
+ parentBlock,
500
500
  randaoReveal,
501
501
  graffiti,
502
502
  feeRecipient,
@@ -569,7 +569,8 @@ export function getValidatorApi(
569
569
  notWhileSyncing();
570
570
  await waitForSlot(slot); // Must never request for a future slot > currentSlot
571
571
 
572
- const {blockRoot: parentBlockRootHex, slot: parentSlot} = chain.getProposerHead(slot);
572
+ const parentBlock = chain.getProposerHead(slot);
573
+ const {blockRoot: parentBlockRootHex, slot: parentSlot} = parentBlock;
573
574
  const parentBlockRoot = fromHex(parentBlockRootHex);
574
575
  notOnOutOfRangeData(parentBlockRoot);
575
576
  metrics?.blockProductionSlotDelta.set(slot - parentSlot);
@@ -638,7 +639,7 @@ export function getValidatorApi(
638
639
  // can't do fee recipient checks as builder bid doesn't return feeRecipient as of now
639
640
  strictFeeRecipientCheck: false,
640
641
  commonBlockBodyPromise,
641
- parentBlockRoot,
642
+ parentBlock,
642
643
  })
643
644
  : Promise.reject(new Error("Builder disabled"));
644
645
 
@@ -647,7 +648,7 @@ export function getValidatorApi(
647
648
  feeRecipient,
648
649
  strictFeeRecipientCheck,
649
650
  commonBlockBodyPromise,
650
- parentBlockRoot,
651
+ parentBlock,
651
652
  }).then((engineBlock) => {
652
653
  // Once the engine returns a block, in the event of either:
653
654
  // - suspected builder censorship
@@ -689,7 +690,7 @@ export function getValidatorApi(
689
690
  chain
690
691
  .produceCommonBlockBody({
691
692
  slot,
692
- parentBlockRoot,
693
+ parentBlock,
693
694
  randaoReveal,
694
695
  graffiti: graffitiBytes,
695
696
  })
@@ -238,6 +238,7 @@ async function migrateBlocksFromHotToColdDb(db: IBeaconDb, blocks: BlockRootSlot
238
238
  // load Buffer instead of SignedBeaconBlock to improve performance
239
239
  const canonicalBlockEntries: BlockArchiveBatchPutBinaryItem[] = await Promise.all(
240
240
  canonicalBlocks.map(async (block) => {
241
+ // Here we assume the blocks are already in the hot db
241
242
  const blockBuffer = await db.block.getBinary(block.root);
242
243
  if (!blockBuffer) {
243
244
  throw Error(`Block not found for slot ${block.slot} root ${toRootHex(block.root)}`);
@@ -294,6 +295,8 @@ async function migrateBlobSidecarsFromHotToColdDb(
294
295
  );
295
296
  })
296
297
  .map(async (block) => {
298
+ // Here we assume the blob sidecars are already in the hot db
299
+ // instead of checking first the block input cache
297
300
  const bytes = await db.blobSidecars.getBinary(block.root);
298
301
  if (!bytes) {
299
302
  throw Error(`No blobSidecars found for slot ${block.slot} root ${toRootHex(block.root)}`);
@@ -343,6 +346,7 @@ async function migrateDataColumnSidecarsFromHotToColdDb(
343
346
  continue;
344
347
  }
345
348
 
349
+ // Here we assume the data column sidecars are already in the hot db
346
350
  const dataColumnSidecarBytes = await fromAsync(db.dataColumnSidecar.valuesStreamBinary(block.root));
347
351
  // there could be 0 dataColumnSidecarBytes if block has no blob
348
352
  logger.verbose("migrateDataColumnSidecarsFromHotToColdDb", {
@@ -412,6 +412,10 @@ export class BlockInputBlobs extends AbstractBlockInput<ForkBlobsDA, deneb.BlobS
412
412
  return this.blobsCache.has(blobIndex);
413
413
  }
414
414
 
415
+ getBlob(blobIndex: BlobIndex): deneb.BlobSidecar | undefined {
416
+ return this.blobsCache.get(blobIndex)?.blobSidecar;
417
+ }
418
+
415
419
  addBlob(
416
420
  {blockRootHex, blobSidecar, source, peerIdStr, seenTimestampSec}: AddBlob,
417
421
  opts = {throwOnDuplicateAdd: true}
@@ -787,6 +791,10 @@ export class BlockInputColumns extends AbstractBlockInput<ForkColumnsDA, fulu.Da
787
791
  return this.columnsCache.has(columnIndex);
788
792
  }
789
793
 
794
+ getColumn(columnIndex: number): fulu.DataColumnSidecar | undefined {
795
+ return this.columnsCache.get(columnIndex)?.columnSidecar;
796
+ }
797
+
790
798
  getVersionedHashes(): VersionedHashes {
791
799
  return this.state.versionedHashes;
792
800
  }
@@ -34,7 +34,6 @@ import {toCheckpointHex} from "../stateCache/index.js";
34
34
  import {isBlockInputBlobs, isBlockInputColumns} from "./blockInput/blockInput.js";
35
35
  import {AttestationImportOpt, FullyVerifiedBlock, ImportBlockOpts} from "./types.js";
36
36
  import {getCheckpointFromState} from "./utils/checkpoint.js";
37
- import {writeBlockInputToDb} from "./writeBlockInputToDb.js";
38
37
 
39
38
  /**
40
39
  * Fork-choice allows to import attestations from current (0) or past (1) epoch.
@@ -91,11 +90,8 @@ export async function importBlock(
91
90
  throw Error("Unavailable block can not be imported in forkchoice");
92
91
  }
93
92
 
94
- // 1. Persist block to hot DB (pre-emptively)
95
- // If eagerPersistBlock = true we do that in verifyBlocksInEpoch to batch all I/O operations to save block time to head
96
- if (!opts.eagerPersistBlock) {
97
- await writeBlockInputToDb.call(this, [blockInput]);
98
- }
93
+ // 1. Persist block to hot DB (performed asynchronously to avoid blocking head selection)
94
+ void this.unfinalizedBlockWrites.push([blockInput]);
99
95
 
100
96
  // Without forcefully clearing this cache, we would rely on WeakMap to evict memory which is not reliable
101
97
  this.serializedCache.clear();
@@ -11,7 +11,6 @@ import {FullyVerifiedBlock, ImportBlockOpts} from "./types.js";
11
11
  import {assertLinearChainSegment} from "./utils/chainSegment.js";
12
12
  import {verifyBlocksInEpoch} from "./verifyBlock.js";
13
13
  import {verifyBlocksSanityChecks} from "./verifyBlocksSanityChecks.js";
14
- import {removeEagerlyPersistedBlockInputs} from "./writeBlockInputToDb.js";
15
14
 
16
15
  export {AttestationImportOpt, type ImportBlockOpts} from "./types.js";
17
16
 
@@ -143,24 +142,6 @@ export async function processBlocks(
143
142
  }
144
143
  }
145
144
 
146
- // Clean db if we don't have blocks in forkchoice but already persisted them to db
147
- //
148
- // NOTE: this function is awaited to ensure that DB size remains constant, otherwise an attacker may bloat the
149
- // disk with big malicious payloads. Our sequential block importer will wait for this promise before importing
150
- // another block. The removal call error is not propagated since that would halt the chain.
151
- //
152
- // LOG: Because the error is not propagated and there's a risk of db bloat, the error is logged at warn level
153
- // to alert the user of potential db bloat. This error _should_ never happen user must act and report to us
154
- if (opts.eagerPersistBlock) {
155
- await removeEagerlyPersistedBlockInputs.call(this, blocks).catch((e) => {
156
- this.logger.warn(
157
- "Error pruning eagerly imported block inputs, DB may grow in size if this error happens frequently",
158
- {slot: blocks.map((block) => block.getBlock().message.slot).join(",")},
159
- e
160
- );
161
- });
162
- }
163
-
164
145
  throw err;
165
146
  }
166
147
  }
@@ -78,8 +78,6 @@ export type ImportBlockOpts = {
78
78
  validBlobSidecars?: BlobSidecarValidation;
79
79
  /** Seen timestamp seconds */
80
80
  seenTimestampSec?: number;
81
- /** Set to true if persist block right at verification time */
82
- eagerPersistBlock?: boolean;
83
81
  };
84
82
 
85
83
  /**
@@ -21,7 +21,6 @@ import {verifyBlocksDataAvailability} from "./verifyBlocksDataAvailability.js";
21
21
  import {SegmentExecStatus, verifyBlocksExecutionPayload} from "./verifyBlocksExecutionPayloads.js";
22
22
  import {verifyBlocksSignatures} from "./verifyBlocksSignatures.js";
23
23
  import {verifyBlocksStateTransitionOnly} from "./verifyBlocksStateTransitionOnly.js";
24
- import {writeBlockInputToDb} from "./writeBlockInputToDb.js";
25
24
 
26
25
  /**
27
26
  * Verifies 1 or more blocks are fully valid; from a linear sequence of blocks.
@@ -156,13 +155,6 @@ export async function verifyBlocksInEpoch(
156
155
  opts
157
156
  )
158
157
  : Promise.resolve({verifySignaturesTime: Date.now()}),
159
-
160
- // ideally we want to only persist blocks after verifying them however the reality is there are
161
- // rarely invalid blocks we'll batch all I/O operation here to reduce the overhead if there's
162
- // an error, we'll remove blocks not in forkchoice
163
- opts.verifyOnly !== true && opts.eagerPersistBlock
164
- ? writeBlockInputToDb.call(this, blockInputs)
165
- : Promise.resolve(),
166
158
  ]);
167
159
 
168
160
  if (opts.verifyOnly !== true) {
@@ -98,35 +98,29 @@ export async function writeBlockInputToDb(this: BeaconChain, blocksInputs: IBloc
98
98
  }
99
99
  }
100
100
 
101
- /**
102
- * Prunes eagerly persisted block inputs only if not known to the fork-choice
103
- */
104
- export async function removeEagerlyPersistedBlockInputs(this: BeaconChain, blockInputs: IBlockInput[]): Promise<void> {
105
- const blockToRemove = [];
106
- const blobsToRemove = [];
107
- const dataColumnsToRemove = [];
108
-
109
- for (const blockInput of blockInputs) {
110
- const block = blockInput.getBlock();
111
- const slot = block.message.slot;
112
- const blockRoot = this.config.getForkTypes(slot).BeaconBlock.hashTreeRoot(block.message);
113
- const blockRootHex = toRootHex(blockRoot);
114
- if (!this.forkChoice.hasBlockHex(blockRootHex)) {
115
- blockToRemove.push(block);
116
-
117
- if (isBlockInputColumns(blockInput) && blockInput.getCustodyColumns().length > 0) {
118
- dataColumnsToRemove.push(blockRoot);
119
- } else if (isBlockInputBlobs(blockInput)) {
120
- const blobSidecars = blockInput.getBlobs();
121
- blobsToRemove.push({blockRoot, slot, blobSidecars});
101
+ export async function persistBlockInputs(this: BeaconChain, blockInputs: IBlockInput[]): Promise<void> {
102
+ await writeBlockInputToDb
103
+ .call(this, blockInputs)
104
+ .catch((e) => {
105
+ this.logger.debug(
106
+ "Error persisting block input in hot db",
107
+ {
108
+ count: blockInputs.length,
109
+ slot: blockInputs[0].slot,
110
+ root: blockInputs[0].blockRootHex,
111
+ },
112
+ e
113
+ );
114
+ })
115
+ .finally(() => {
116
+ for (const blockInput of blockInputs) {
117
+ this.seenBlockInputCache.prune(blockInput.blockRootHex);
122
118
  }
123
- }
124
- }
125
-
126
- await Promise.all([
127
- // TODO: Batch DB operations not with Promise.all but with level db ops
128
- this.db.block.batchRemove(blockToRemove),
129
- this.db.blobSidecars.batchRemove(blobsToRemove),
130
- this.db.dataColumnSidecar.deleteMany(dataColumnsToRemove),
131
- ]);
119
+ if (blockInputs.length === 1) {
120
+ this.logger.debug("Pruned block input", {
121
+ slot: blockInputs[0].slot,
122
+ root: blockInputs[0].blockRootHex,
123
+ });
124
+ }
125
+ });
132
126
  }
@@ -37,14 +37,19 @@ import {
37
37
  UintNum64,
38
38
  ValidatorIndex,
39
39
  Wei,
40
+ deneb,
41
+ fulu,
40
42
  isBlindedBeaconBlock,
41
43
  phase0,
42
44
  rewards,
45
+ ssz,
46
+ sszTypesFor,
43
47
  } from "@lodestar/types";
44
48
  import {Logger, fromHex, gweiToWei, isErrorAborted, pruneSetToMax, sleep, toRootHex} from "@lodestar/utils";
45
49
  import {ProcessShutdownCallback} from "@lodestar/validator";
46
50
  import {GENESIS_EPOCH, ZERO_HASH} from "../constants/index.js";
47
51
  import {IBeaconDb} from "../db/index.js";
52
+ import {BLOB_SIDECARS_IN_WRAPPER_INDEX} from "../db/repositories/blobSidecars.ts";
48
53
  import {BuilderStatus} from "../execution/builder/http.js";
49
54
  import {IExecutionBuilder, IExecutionEngine} from "../execution/index.js";
50
55
  import {Metrics} from "../metrics/index.js";
@@ -55,12 +60,15 @@ import {CustodyConfig, getValidatorsCustodyRequirement} from "../util/dataColumn
55
60
  import {callInNextEventLoop} from "../util/eventLoop.js";
56
61
  import {ensureDir, writeIfNotExist} from "../util/file.js";
57
62
  import {isOptimisticBlock} from "../util/forkChoice.js";
63
+ import {JobItemQueue} from "../util/queue/itemQueue.ts";
58
64
  import {SerializedCache} from "../util/serializedCache.js";
65
+ import {getSlotFromSignedBeaconBlockSerialized} from "../util/sszBytes.ts";
59
66
  import {ArchiveStore} from "./archiveStore/archiveStore.js";
60
67
  import {CheckpointBalancesCache} from "./balancesCache.js";
61
68
  import {BeaconProposerCache} from "./beaconProposerCache.js";
62
- import {IBlockInput} from "./blocks/blockInput/index.js";
69
+ import {IBlockInput, isBlockInputBlobs, isBlockInputColumns} from "./blocks/blockInput/index.js";
63
70
  import {BlockProcessor, ImportBlockOpts} from "./blocks/index.js";
71
+ import {persistBlockInputs} from "./blocks/writeBlockInputToDb.ts";
64
72
  import {BlsMultiThreadWorkerPool, BlsSingleThreadVerifier, IBlsVerifier} from "./bls/index.js";
65
73
  import {ColumnReconstructionTracker} from "./ColumnReconstructionTracker.js";
66
74
  import {ChainEvent, ChainEventEmitter} from "./emitter.js";
@@ -113,6 +121,11 @@ import {ValidatorMonitor} from "./validatorMonitor.js";
113
121
  */
114
122
  const DEFAULT_MAX_CACHED_PRODUCED_RESULTS = 4;
115
123
 
124
+ /**
125
+ * The maximum number of pending unfinalized block writes to the database before backpressure is applied.
126
+ */
127
+ const DEFAULT_MAX_PENDING_UNFINALIZED_BLOCK_WRITES = 32;
128
+
116
129
  export class BeaconChain implements IBeaconChain {
117
130
  readonly genesisTime: UintNum64;
118
131
  readonly genesisValidatorsRoot: Root;
@@ -136,6 +149,7 @@ export class BeaconChain implements IBeaconChain {
136
149
  readonly lightClientServer?: LightClientServer;
137
150
  readonly reprocessController: ReprocessController;
138
151
  readonly archiveStore: ArchiveStore;
152
+ readonly unfinalizedBlockWrites: JobItemQueue<[IBlockInput[]], void>;
139
153
 
140
154
  // Ops pool
141
155
  readonly attestationPool: AttestationPool;
@@ -405,6 +419,15 @@ export class BeaconChain implements IBeaconChain {
405
419
  signal
406
420
  );
407
421
 
422
+ this.unfinalizedBlockWrites = new JobItemQueue(
423
+ persistBlockInputs.bind(this),
424
+ {
425
+ maxLength: DEFAULT_MAX_PENDING_UNFINALIZED_BLOCK_WRITES,
426
+ signal,
427
+ },
428
+ metrics?.unfinalizedBlockWritesQueue
429
+ );
430
+
408
431
  // always run PrepareNextSlotScheduler except for fork_choice spec tests
409
432
  if (!opts?.disablePrepareNextSlot) {
410
433
  new PrepareNextSlotScheduler(this, this.config, metrics, this.logger, signal);
@@ -430,6 +453,12 @@ export class BeaconChain implements IBeaconChain {
430
453
  async close(): Promise<void> {
431
454
  await this.archiveStore.close();
432
455
  await this.bls.close();
456
+
457
+ // Since we don't persist unfinalized fork-choice,
458
+ // we can abort any ongoing unfinalized block writes.
459
+ // TODO: persist fork choice to disk and allow unfinalized block writes to complete.
460
+ this.unfinalizedBlockWrites.dropAllJobs();
461
+
433
462
  this.abortController.abort();
434
463
  }
435
464
 
@@ -501,7 +530,7 @@ export class BeaconChain implements IBeaconChain {
501
530
  // only use regen queue if necessary, it'll cache in checkpointStateCache if regen gets through epoch transition
502
531
  const head = this.forkChoice.getHead();
503
532
  const startSlot = computeStartSlotAtEpoch(epoch);
504
- return this.regen.getBlockSlotState(head.blockRoot, startSlot, {dontTransferCache: true}, regenCaller);
533
+ return this.regen.getBlockSlotState(head, startSlot, {dontTransferCache: true}, regenCaller);
505
534
  }
506
535
 
507
536
  async getStateBySlot(
@@ -519,12 +548,7 @@ export class BeaconChain implements IBeaconChain {
519
548
  if (opts?.allowRegen) {
520
549
  // Find closest canonical block to slot, then trigger regen
521
550
  const block = this.forkChoice.getCanonicalBlockClosestLteSlot(slot) ?? finalizedBlock;
522
- const state = await this.regen.getBlockSlotState(
523
- block.blockRoot,
524
- slot,
525
- {dontTransferCache: true},
526
- RegenCaller.restApi
527
- );
551
+ const state = await this.regen.getBlockSlotState(block, slot, {dontTransferCache: true}, RegenCaller.restApi);
528
552
  return {
529
553
  state,
530
554
  executionOptimistic: isOptimisticBlock(block),
@@ -652,6 +676,13 @@ export class BeaconChain implements IBeaconChain {
652
676
  // Unfinalized slot, attempt to find in fork-choice
653
677
  const block = this.forkChoice.getCanonicalBlockAtSlot(slot);
654
678
  if (block) {
679
+ // Block found in fork-choice.
680
+ // It may be in the block input cache, awaiting full DA reconstruction, check there first
681
+ // Otherwise (most likely), check the hot db
682
+ const blockInput = this.seenBlockInputCache.get(block.blockRoot);
683
+ if (blockInput?.hasBlock()) {
684
+ return {block: blockInput.getBlock(), executionOptimistic: isOptimisticBlock(block), finalized: false};
685
+ }
655
686
  const data = await this.db.block.get(fromHex(block.blockRoot));
656
687
  if (data) {
657
688
  return {block: data, executionOptimistic: isOptimisticBlock(block), finalized: false};
@@ -671,6 +702,13 @@ export class BeaconChain implements IBeaconChain {
671
702
  ): Promise<{block: SignedBeaconBlock; executionOptimistic: boolean; finalized: boolean} | null> {
672
703
  const block = this.forkChoice.getBlockHex(root);
673
704
  if (block) {
705
+ // Block found in fork-choice.
706
+ // It may be in the block input cache, awaiting full DA reconstruction, check there first
707
+ // Otherwise (most likely), check the hot db
708
+ const blockInput = this.seenBlockInputCache.get(block.blockRoot);
709
+ if (blockInput?.hasBlock()) {
710
+ return {block: blockInput.getBlock(), executionOptimistic: isOptimisticBlock(block), finalized: false};
711
+ }
674
712
  const data = await this.db.block.get(fromHex(root));
675
713
  if (data) {
676
714
  return {block: data, executionOptimistic: isOptimisticBlock(block), finalized: false};
@@ -683,10 +721,137 @@ export class BeaconChain implements IBeaconChain {
683
721
  return data && {block: data, executionOptimistic: false, finalized: true};
684
722
  }
685
723
 
724
+ async getSerializedBlockByRoot(
725
+ root: string
726
+ ): Promise<{block: Uint8Array; executionOptimistic: boolean; finalized: boolean; slot: Slot} | null> {
727
+ const block = this.forkChoice.getBlockHex(root);
728
+ if (block) {
729
+ // Block found in fork-choice.
730
+ // It may be in the block input cache, awaiting full DA reconstruction, check there first
731
+ // Otherwise (most likely), check the hot db
732
+ const blockInput = this.seenBlockInputCache.get(block.blockRoot);
733
+ if (blockInput?.hasBlock()) {
734
+ const signedBlock = blockInput.getBlock();
735
+ const serialized = this.serializedCache.get(signedBlock);
736
+ if (serialized) {
737
+ return {
738
+ block: serialized,
739
+ executionOptimistic: isOptimisticBlock(block),
740
+ finalized: false,
741
+ slot: blockInput.slot,
742
+ };
743
+ }
744
+ return {
745
+ block: sszTypesFor(blockInput.forkName).SignedBeaconBlock.serialize(signedBlock),
746
+ executionOptimistic: isOptimisticBlock(block),
747
+ finalized: false,
748
+ slot: blockInput.slot,
749
+ };
750
+ }
751
+ const data = await this.db.block.getBinary(fromHex(root));
752
+ if (data) {
753
+ const slot = getSlotFromSignedBeaconBlockSerialized(data);
754
+ if (slot === null) throw new Error(`Invalid block data stored in DB for root: ${root}`);
755
+ return {block: data, executionOptimistic: isOptimisticBlock(block), finalized: false, slot};
756
+ }
757
+ // If block is not found in hot db, try cold db since there could be an archive cycle happening
758
+ // TODO: Add a lock to the archiver to have deterministic behavior on where are blocks
759
+ }
760
+
761
+ const data = await this.db.blockArchive.getBinaryEntryByRoot(fromHex(root));
762
+ return data && {block: data.value, executionOptimistic: false, finalized: true, slot: data.key};
763
+ }
764
+
765
+ async getBlobSidecars(blockSlot: Slot, blockRootHex: string): Promise<deneb.BlobSidecars | null> {
766
+ const blockInput = this.seenBlockInputCache.get(blockRootHex);
767
+ if (blockInput) {
768
+ if (!isBlockInputBlobs(blockInput)) {
769
+ throw new Error(`Expected block input to have blobs: slot=${blockSlot} root=${blockRootHex}`);
770
+ }
771
+ if (!blockInput.hasAllData()) {
772
+ return null;
773
+ }
774
+ return blockInput.getBlobs();
775
+ }
776
+ const unfinalizedBlobSidecars = (await this.db.blobSidecars.get(fromHex(blockRootHex)))?.blobSidecars ?? null;
777
+ if (unfinalizedBlobSidecars) {
778
+ return unfinalizedBlobSidecars;
779
+ }
780
+ return (await this.db.blobSidecarsArchive.get(blockSlot))?.blobSidecars ?? null;
781
+ }
782
+
783
+ async getSerializedBlobSidecars(blockSlot: Slot, blockRootHex: string): Promise<Uint8Array | null> {
784
+ const blockInput = this.seenBlockInputCache.get(blockRootHex);
785
+ if (blockInput) {
786
+ if (!isBlockInputBlobs(blockInput)) {
787
+ throw new Error(`Expected block input to have blobs: slot=${blockSlot} root=${blockRootHex}`);
788
+ }
789
+ if (!blockInput.hasAllData()) {
790
+ return null;
791
+ }
792
+ return ssz.deneb.BlobSidecars.serialize(blockInput.getBlobs());
793
+ }
794
+ const unfinalizedBlobSidecarsWrapper = await this.db.blobSidecars.getBinary(fromHex(blockRootHex));
795
+ if (unfinalizedBlobSidecarsWrapper) {
796
+ return unfinalizedBlobSidecarsWrapper.slice(BLOB_SIDECARS_IN_WRAPPER_INDEX);
797
+ }
798
+ const finalizedBlobSidecarsWrapper = await this.db.blobSidecarsArchive.getBinary(blockSlot);
799
+ if (finalizedBlobSidecarsWrapper) {
800
+ return finalizedBlobSidecarsWrapper.slice(BLOB_SIDECARS_IN_WRAPPER_INDEX);
801
+ }
802
+ return null;
803
+ }
804
+
805
+ async getDataColumnSidecars(blockSlot: Slot, blockRootHex: string): Promise<fulu.DataColumnSidecars> {
806
+ const blockInput = this.seenBlockInputCache.get(blockRootHex);
807
+ if (blockInput) {
808
+ if (!isBlockInputColumns(blockInput)) {
809
+ throw new Error(`Expected block input to have columns: slot=${blockSlot} root=${blockRootHex}`);
810
+ }
811
+ return blockInput.getAllColumns();
812
+ }
813
+ const sidecarsUnfinalized = await this.db.dataColumnSidecar.values(fromHex(blockRootHex));
814
+ if (sidecarsUnfinalized.length > 0) {
815
+ return sidecarsUnfinalized;
816
+ }
817
+ const sidecarsFinalized = await this.db.dataColumnSidecarArchive.values(blockSlot);
818
+ return sidecarsFinalized;
819
+ }
820
+
821
+ async getSerializedDataColumnSidecars(
822
+ blockSlot: Slot,
823
+ blockRootHex: string,
824
+ indices: number[]
825
+ ): Promise<(Uint8Array | undefined)[]> {
826
+ const blockInput = this.seenBlockInputCache.get(blockRootHex);
827
+ if (blockInput) {
828
+ if (!isBlockInputColumns(blockInput)) {
829
+ throw new Error(`Expected block input to have columns: slot=${blockSlot} root=${blockRootHex}`);
830
+ }
831
+ return indices.map((index) => {
832
+ const sidecar = blockInput.getColumn(index);
833
+ if (!sidecar) {
834
+ return undefined;
835
+ }
836
+ const serialized = this.serializedCache.get(sidecar);
837
+ if (serialized) {
838
+ return serialized;
839
+ }
840
+ return ssz.fulu.DataColumnSidecar.serialize(sidecar);
841
+ });
842
+ }
843
+ const sidecarsUnfinalized = await this.db.dataColumnSidecar.getManyBinary(fromHex(blockRootHex), indices);
844
+ if (sidecarsUnfinalized.some((sidecar) => sidecar != null)) {
845
+ return sidecarsUnfinalized;
846
+ }
847
+ const sidecarsFinalized = await this.db.dataColumnSidecarArchive.getManyBinary(blockSlot, indices);
848
+ return sidecarsFinalized;
849
+ }
850
+
686
851
  async produceCommonBlockBody(blockAttributes: BlockAttributes): Promise<CommonBlockBody> {
687
- const {slot, parentBlockRoot} = blockAttributes;
852
+ const {slot, parentBlock} = blockAttributes;
688
853
  const state = await this.regen.getBlockSlotState(
689
- toRootHex(parentBlockRoot),
854
+ parentBlock,
690
855
  slot,
691
856
  {dontTransferCache: true},
692
857
  RegenCaller.produceBlock
@@ -723,7 +888,7 @@ export class BeaconChain implements IBeaconChain {
723
888
  slot,
724
889
  feeRecipient,
725
890
  commonBlockBodyPromise,
726
- parentBlockRoot,
891
+ parentBlock,
727
892
  }: BlockAttributes & {commonBlockBodyPromise: Promise<CommonBlockBody>}
728
893
  ): Promise<{
729
894
  block: AssembledBlockType<T>;
@@ -732,7 +897,7 @@ export class BeaconChain implements IBeaconChain {
732
897
  shouldOverrideBuilder?: boolean;
733
898
  }> {
734
899
  const state = await this.regen.getBlockSlotState(
735
- toRootHex(parentBlockRoot),
900
+ parentBlock,
736
901
  slot,
737
902
  {dontTransferCache: true},
738
903
  RegenCaller.produceBlock
@@ -749,7 +914,7 @@ export class BeaconChain implements IBeaconChain {
749
914
  graffiti,
750
915
  slot,
751
916
  feeRecipient,
752
- parentBlockRoot,
917
+ parentBlock,
753
918
  proposerIndex,
754
919
  proposerPubKey,
755
920
  commonBlockBodyPromise,
@@ -772,7 +937,7 @@ export class BeaconChain implements IBeaconChain {
772
937
  const block = {
773
938
  slot,
774
939
  proposerIndex,
775
- parentRoot: parentBlockRoot,
940
+ parentRoot: fromHex(parentBlock.blockRoot),
776
941
  stateRoot: ZERO_HASH,
777
942
  body,
778
943
  } as AssembledBlockType<T>;
@@ -968,12 +1133,7 @@ export class BeaconChain implements IBeaconChain {
968
1133
  // thanks to one epoch look ahead, we don't need to dial up to attEpoch
969
1134
  const targetSlot = computeStartSlotAtEpoch(attEpoch - 1);
970
1135
  this.metrics?.gossipAttestation.useHeadBlockStateDialedToTargetEpoch.inc({caller: regenCaller});
971
- state = await this.regen.getBlockSlotState(
972
- attHeadBlock.blockRoot,
973
- targetSlot,
974
- {dontTransferCache: true},
975
- regenCaller
976
- );
1136
+ state = await this.regen.getBlockSlotState(attHeadBlock, targetSlot, {dontTransferCache: true}, regenCaller);
977
1137
  } else if (blockEpoch > attEpoch) {
978
1138
  // should not happen, handled inside attestation verification code
979
1139
  throw Error(`Block epoch ${blockEpoch} is after attestation epoch ${attEpoch}`);
@@ -22,6 +22,8 @@ import {
22
22
  Wei,
23
23
  altair,
24
24
  capella,
25
+ deneb,
26
+ fulu,
25
27
  phase0,
26
28
  rewards,
27
29
  } from "@lodestar/types";
@@ -193,12 +195,26 @@ export interface IBeaconChain {
193
195
  getCanonicalBlockAtSlot(
194
196
  slot: Slot
195
197
  ): Promise<{block: SignedBeaconBlock; executionOptimistic: boolean; finalized: boolean} | null>;
198
+ /**
199
+ * Get local block by root, does not fetch from the network
200
+ */
201
+ getSerializedBlockByRoot(
202
+ root: RootHex
203
+ ): Promise<{block: Uint8Array; executionOptimistic: boolean; finalized: boolean; slot: Slot} | null>;
196
204
  /**
197
205
  * Get local block by root, does not fetch from the network
198
206
  */
199
207
  getBlockByRoot(
200
208
  root: RootHex
201
209
  ): Promise<{block: SignedBeaconBlock; executionOptimistic: boolean; finalized: boolean} | null>;
210
+ getBlobSidecars(blockSlot: Slot, blockRootHex: string): Promise<deneb.BlobSidecars | null>;
211
+ getSerializedBlobSidecars(blockSlot: Slot, blockRootHex: string): Promise<Uint8Array | null>;
212
+ getDataColumnSidecars(blockSlot: Slot, blockRootHex: string): Promise<fulu.DataColumnSidecars>;
213
+ getSerializedDataColumnSidecars(
214
+ blockSlot: Slot,
215
+ blockRootHex: string,
216
+ indices: number[]
217
+ ): Promise<(Uint8Array | undefined)[]>;
202
218
 
203
219
  produceCommonBlockBody(blockAttributes: BlockAttributes): Promise<CommonBlockBody>;
204
220
  produceBlock(blockAttributes: BlockAttributes & {commonBlockBodyPromise: Promise<CommonBlockBody>}): Promise<{