@lodestar/beacon-node 1.39.0 → 1.40.0-dev.3715a82f3f

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 (171) hide show
  1. package/lib/api/impl/beacon/state/utils.d.ts +2 -7
  2. package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
  3. package/lib/api/impl/beacon/state/utils.js +0 -12
  4. package/lib/api/impl/beacon/state/utils.js.map +1 -1
  5. package/lib/api/impl/config/constants.d.ts +3 -0
  6. package/lib/api/impl/config/constants.d.ts.map +1 -1
  7. package/lib/api/impl/config/constants.js +5 -1
  8. package/lib/api/impl/config/constants.js.map +1 -1
  9. package/lib/api/impl/lodestar/index.js +1 -1
  10. package/lib/api/impl/lodestar/index.js.map +1 -1
  11. package/lib/api/impl/proof/index.d.ts.map +1 -1
  12. package/lib/api/impl/proof/index.js +1 -2
  13. package/lib/api/impl/proof/index.js.map +1 -1
  14. package/lib/api/impl/validator/index.d.ts.map +1 -1
  15. package/lib/api/impl/validator/index.js +1 -3
  16. package/lib/api/impl/validator/index.js.map +1 -1
  17. package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts.map +1 -1
  18. package/lib/chain/archiveStore/historicalState/getHistoricalState.js +5 -3
  19. package/lib/chain/archiveStore/historicalState/getHistoricalState.js.map +1 -1
  20. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
  21. package/lib/chain/archiveStore/utils/archiveBlocks.js +19 -14
  22. package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
  23. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  24. package/lib/chain/blocks/importBlock.js +12 -3
  25. package/lib/chain/blocks/importBlock.js.map +1 -1
  26. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  27. package/lib/chain/blocks/verifyBlock.js +8 -1
  28. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  29. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
  30. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +1 -0
  31. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
  32. package/lib/chain/chain.d.ts +3 -2
  33. package/lib/chain/chain.d.ts.map +1 -1
  34. package/lib/chain/chain.js +16 -6
  35. package/lib/chain/chain.js.map +1 -1
  36. package/lib/chain/initState.d.ts +1 -1
  37. package/lib/chain/initState.d.ts.map +1 -1
  38. package/lib/chain/initState.js +5 -3
  39. package/lib/chain/initState.js.map +1 -1
  40. package/lib/chain/interface.d.ts +2 -2
  41. package/lib/chain/interface.d.ts.map +1 -1
  42. package/lib/chain/lightClient/proofs.d.ts.map +1 -1
  43. package/lib/chain/lightClient/proofs.js +0 -2
  44. package/lib/chain/lightClient/proofs.js.map +1 -1
  45. package/lib/chain/opPools/aggregatedAttestationPool.d.ts +5 -9
  46. package/lib/chain/opPools/aggregatedAttestationPool.d.ts.map +1 -1
  47. package/lib/chain/opPools/aggregatedAttestationPool.js +12 -141
  48. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  49. package/lib/chain/opPools/opPool.js +5 -8
  50. package/lib/chain/opPools/opPool.js.map +1 -1
  51. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  52. package/lib/chain/prepareNextSlot.js +4 -7
  53. package/lib/chain/prepareNextSlot.js.map +1 -1
  54. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  55. package/lib/chain/produceBlock/produceBlockBody.js +1 -1
  56. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  57. package/lib/chain/regen/interface.d.ts +1 -5
  58. package/lib/chain/regen/interface.d.ts.map +1 -1
  59. package/lib/chain/regen/queued.d.ts +4 -7
  60. package/lib/chain/regen/queued.d.ts.map +1 -1
  61. package/lib/chain/regen/queued.js +15 -25
  62. package/lib/chain/regen/queued.js.map +1 -1
  63. package/lib/chain/regen/regen.d.ts +1 -1
  64. package/lib/chain/regen/regen.d.ts.map +1 -1
  65. package/lib/chain/regen/regen.js +13 -17
  66. package/lib/chain/regen/regen.js.map +1 -1
  67. package/lib/chain/shufflingCache.d.ts +16 -11
  68. package/lib/chain/shufflingCache.d.ts.map +1 -1
  69. package/lib/chain/shufflingCache.js +47 -41
  70. package/lib/chain/shufflingCache.js.map +1 -1
  71. package/lib/chain/stateCache/blockStateCacheImpl.d.ts +1 -2
  72. package/lib/chain/stateCache/blockStateCacheImpl.d.ts.map +1 -1
  73. package/lib/chain/stateCache/blockStateCacheImpl.js +2 -2
  74. package/lib/chain/stateCache/blockStateCacheImpl.js.map +1 -1
  75. package/lib/chain/stateCache/fifoBlockStateCache.d.ts +1 -2
  76. package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
  77. package/lib/chain/stateCache/fifoBlockStateCache.js +4 -4
  78. package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
  79. package/lib/chain/stateCache/inMemoryCheckpointsCache.d.ts +4 -5
  80. package/lib/chain/stateCache/inMemoryCheckpointsCache.d.ts.map +1 -1
  81. package/lib/chain/stateCache/inMemoryCheckpointsCache.js +9 -10
  82. package/lib/chain/stateCache/inMemoryCheckpointsCache.js.map +1 -1
  83. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +5 -6
  84. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  85. package/lib/chain/stateCache/persistentCheckpointsCache.js +17 -17
  86. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  87. package/lib/chain/stateCache/types.d.ts +5 -6
  88. package/lib/chain/stateCache/types.d.ts.map +1 -1
  89. package/lib/chain/stateCache/types.js.map +1 -1
  90. package/lib/chain/validation/attestation.d.ts.map +1 -1
  91. package/lib/chain/validation/attestation.js +2 -2
  92. package/lib/chain/validation/attestation.js.map +1 -1
  93. package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
  94. package/lib/chain/validation/attesterSlashing.js +1 -1
  95. package/lib/chain/validation/attesterSlashing.js.map +1 -1
  96. package/lib/chain/validation/block.d.ts.map +1 -1
  97. package/lib/chain/validation/block.js +2 -0
  98. package/lib/chain/validation/block.js.map +1 -1
  99. package/lib/chain/validation/blsToExecutionChange.d.ts.map +1 -1
  100. package/lib/chain/validation/blsToExecutionChange.js +9 -2
  101. package/lib/chain/validation/blsToExecutionChange.js.map +1 -1
  102. package/lib/chain/validation/proposerSlashing.js +2 -1
  103. package/lib/chain/validation/proposerSlashing.js.map +1 -1
  104. package/lib/db/repositories/checkpointState.d.ts +2 -6
  105. package/lib/db/repositories/checkpointState.d.ts.map +1 -1
  106. package/lib/db/repositories/checkpointState.js +3 -15
  107. package/lib/db/repositories/checkpointState.js.map +1 -1
  108. package/lib/db/repositories/stateArchive.d.ts +9 -9
  109. package/lib/db/repositories/stateArchive.d.ts.map +1 -1
  110. package/lib/db/repositories/stateArchive.js +6 -21
  111. package/lib/db/repositories/stateArchive.js.map +1 -1
  112. package/lib/execution/engine/mock.d.ts +9 -6
  113. package/lib/execution/engine/mock.d.ts.map +1 -1
  114. package/lib/execution/engine/mock.js +34 -7
  115. package/lib/execution/engine/mock.js.map +1 -1
  116. package/lib/index.d.ts +1 -1
  117. package/lib/index.d.ts.map +1 -1
  118. package/lib/index.js +1 -1
  119. package/lib/index.js.map +1 -1
  120. package/lib/metrics/metrics/lodestar.d.ts +1 -6
  121. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  122. package/lib/metrics/metrics/lodestar.js +3 -17
  123. package/lib/metrics/metrics/lodestar.js.map +1 -1
  124. package/lib/network/reqresp/utils/dataColumnResponseValidation.js +1 -1
  125. package/lib/network/reqresp/utils/dataColumnResponseValidation.js.map +1 -1
  126. package/lib/node/nodejs.d.ts.map +1 -1
  127. package/lib/node/nodejs.js +17 -2
  128. package/lib/node/nodejs.js.map +1 -1
  129. package/lib/util/sszBytes.js +1 -1
  130. package/lib/util/sszBytes.js.map +1 -1
  131. package/package.json +15 -15
  132. package/src/api/impl/beacon/state/utils.ts +2 -22
  133. package/src/api/impl/config/constants.ts +8 -0
  134. package/src/api/impl/lodestar/index.ts +1 -1
  135. package/src/api/impl/proof/index.ts +1 -2
  136. package/src/api/impl/validator/index.ts +1 -3
  137. package/src/chain/archiveStore/historicalState/getHistoricalState.ts +5 -3
  138. package/src/chain/archiveStore/utils/archiveBlocks.ts +21 -14
  139. package/src/chain/blocks/importBlock.ts +13 -3
  140. package/src/chain/blocks/verifyBlock.ts +9 -3
  141. package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +1 -0
  142. package/src/chain/chain.ts +20 -9
  143. package/src/chain/initState.ts +5 -3
  144. package/src/chain/interface.ts +2 -2
  145. package/src/chain/lightClient/proofs.ts +0 -2
  146. package/src/chain/opPools/aggregatedAttestationPool.ts +19 -191
  147. package/src/chain/opPools/opPool.ts +5 -7
  148. package/src/chain/prepareNextSlot.ts +2 -6
  149. package/src/chain/produceBlock/produceBlockBody.ts +6 -1
  150. package/src/chain/regen/interface.ts +1 -5
  151. package/src/chain/regen/queued.ts +15 -34
  152. package/src/chain/regen/regen.ts +12 -18
  153. package/src/chain/shufflingCache.ts +67 -50
  154. package/src/chain/stateCache/blockStateCacheImpl.ts +2 -3
  155. package/src/chain/stateCache/fifoBlockStateCache.ts +4 -5
  156. package/src/chain/stateCache/inMemoryCheckpointsCache.ts +9 -15
  157. package/src/chain/stateCache/persistentCheckpointsCache.ts +17 -25
  158. package/src/chain/stateCache/types.ts +5 -10
  159. package/src/chain/validation/attestation.ts +3 -3
  160. package/src/chain/validation/attesterSlashing.ts +8 -1
  161. package/src/chain/validation/block.ts +3 -0
  162. package/src/chain/validation/blsToExecutionChange.ts +9 -7
  163. package/src/chain/validation/proposerSlashing.ts +2 -1
  164. package/src/db/repositories/checkpointState.ts +3 -19
  165. package/src/db/repositories/stateArchive.ts +13 -27
  166. package/src/execution/engine/mock.ts +40 -13
  167. package/src/index.ts +1 -1
  168. package/src/metrics/metrics/lodestar.ts +3 -17
  169. package/src/network/reqresp/utils/dataColumnResponseValidation.ts +1 -1
  170. package/src/node/nodejs.ts +18 -3
  171. package/src/util/sszBytes.ts +1 -1
@@ -73,9 +73,12 @@ export async function archiveBlocks(
73
73
  root: fromHex(block.blockRoot),
74
74
  }));
75
75
 
76
+ const logCtx = {currentEpoch, finalizedEpoch: finalizedCheckpoint.epoch, finalizedRoot: finalizedCheckpoint.rootHex};
77
+
76
78
  if (finalizedCanonicalBlockRoots.length > 0) {
77
79
  await migrateBlocksFromHotToColdDb(db, finalizedCanonicalBlockRoots);
78
80
  logger.verbose("Migrated blocks from hot DB to cold DB", {
81
+ ...logCtx,
79
82
  fromSlot: finalizedCanonicalBlockRoots[0].slot,
80
83
  toSlot: finalizedCanonicalBlockRoots.at(-1)?.slot,
81
84
  size: finalizedCanonicalBlockRoots.length,
@@ -88,7 +91,7 @@ export async function archiveBlocks(
88
91
  finalizedCanonicalBlockRoots,
89
92
  currentEpoch
90
93
  );
91
- logger.verbose("Migrated blobSidecars from hot DB to cold DB", {migratedEntries});
94
+ logger.verbose("Migrated blobSidecars from hot DB to cold DB", {...logCtx, migratedEntries});
92
95
  }
93
96
 
94
97
  if (finalizedPostFulu) {
@@ -99,7 +102,7 @@ export async function archiveBlocks(
99
102
  finalizedCanonicalBlockRoots,
100
103
  currentEpoch
101
104
  );
102
- logger.verbose("Migrated dataColumnSidecars from hot DB to cold DB", {migratedEntries});
105
+ logger.verbose("Migrated dataColumnSidecars from hot DB to cold DB", {...logCtx, migratedEntries});
103
106
  }
104
107
  }
105
108
 
@@ -114,14 +117,14 @@ export async function archiveBlocks(
114
117
  nonCanonicalBlockRoots.map(async (root, index) => {
115
118
  const block = finalizedNonCanonicalBlocks[index];
116
119
  const blockBytes = await db.block.getBinary(root);
117
- const logCtx = {slot: block.slot, root: block.blockRoot};
120
+ const blockLogCtx = {slot: block.slot, root: block.blockRoot};
118
121
  if (blockBytes) {
119
122
  await persistOrphanedBlock(block.slot, block.blockRoot, blockBytes, {
120
123
  persistOrphanedBlocksDir: persistOrphanedBlocksDir ?? "orphaned_blocks",
121
124
  });
122
- logger.verbose("Persisted orphaned block", logCtx);
125
+ logger.verbose("Persisted orphaned block", {...logCtx, ...blockLogCtx});
123
126
  } else {
124
- logger.warn("Tried to persist orphaned block but no block found", logCtx);
127
+ logger.warn("Tried to persist orphaned block but no block found", {...logCtx, ...blockLogCtx});
125
128
  }
126
129
  })
127
130
  );
@@ -129,17 +132,18 @@ export async function archiveBlocks(
129
132
 
130
133
  await db.block.batchDelete(nonCanonicalBlockRoots);
131
134
  logger.verbose("Deleted non canonical blocks from hot DB", {
135
+ ...logCtx,
132
136
  slots: finalizedNonCanonicalBlocks.map((summary) => summary.slot).join(","),
133
137
  });
134
138
 
135
139
  if (finalizedPostDeneb) {
136
140
  await db.blobSidecars.batchDelete(nonCanonicalBlockRoots);
137
- logger.verbose("Deleted non canonical blobSidecars from hot DB");
141
+ logger.verbose("Deleted non canonical blobSidecars from hot DB", logCtx);
138
142
  }
139
143
 
140
144
  if (finalizedPostFulu) {
141
145
  await db.dataColumnSidecar.deleteMany(nonCanonicalBlockRoots);
142
- logger.verbose("Deleted non canonical dataColumnSidecars from hot DB");
146
+ logger.verbose("Deleted non canonical dataColumnSidecars from hot DB", logCtx);
143
147
  }
144
148
  }
145
149
 
@@ -154,13 +158,13 @@ export async function archiveBlocks(
154
158
  const slotsToDelete = await db.blobSidecarsArchive.keys({lt: computeStartSlotAtEpoch(blobSidecarsMinEpoch)});
155
159
  if (slotsToDelete.length > 0) {
156
160
  await db.blobSidecarsArchive.batchDelete(slotsToDelete);
157
- logger.verbose(`blobSidecars prune: batchDelete range ${slotsToDelete[0]}..${slotsToDelete.at(-1)}`);
161
+ logger.verbose(`blobSidecars prune: batchDelete range ${slotsToDelete[0]}..${slotsToDelete.at(-1)}`, logCtx);
158
162
  } else {
159
- logger.verbose(`blobSidecars prune: no entries before epoch ${blobSidecarsMinEpoch}`);
163
+ logger.verbose(`blobSidecars prune: no entries before epoch ${blobSidecarsMinEpoch}`, logCtx);
160
164
  }
161
165
  }
162
166
  } else {
163
- logger.verbose("blobSidecars pruning skipped: archiveDataEpochs set to Infinity");
167
+ logger.verbose("blobSidecars pruning skipped: archiveDataEpochs set to Infinity", logCtx);
164
168
  }
165
169
  }
166
170
 
@@ -184,20 +188,22 @@ export async function archiveBlocks(
184
188
  if (slotsToDelete.length > 0) {
185
189
  await db.dataColumnSidecarArchive.deleteMany(slotsToDelete);
186
190
  logger.verbose("dataColumnSidecars prune", {
191
+ ...logCtx,
187
192
  slotRange: prettyPrintIndices(slotsToDelete),
188
193
  numOfSlots: slotsToDelete.length,
189
194
  totalNumOfSidecars: prefixedKeys.length,
190
195
  });
191
196
  } else {
192
- logger.verbose(`dataColumnSidecars prune: no entries before epoch ${dataColumnSidecarsMinEpoch}`);
197
+ logger.verbose(`dataColumnSidecars prune: no entries before epoch ${dataColumnSidecarsMinEpoch}`, logCtx);
193
198
  }
194
199
  } else {
195
200
  logger.verbose(
196
- `dataColumnSidecars pruning skipped: ${dataColumnSidecarsMinEpoch} is before fulu fork epoch ${config.FULU_FORK_EPOCH}`
201
+ `dataColumnSidecars pruning skipped: ${dataColumnSidecarsMinEpoch} is before fulu fork epoch ${config.FULU_FORK_EPOCH}`,
202
+ logCtx
197
203
  );
198
204
  }
199
205
  } else {
200
- logger.verbose("dataColumnSidecars pruning skipped: archiveDataEpochs set to Infinity");
206
+ logger.verbose("dataColumnSidecars pruning skipped: archiveDataEpochs set to Infinity", logCtx);
201
207
  }
202
208
  }
203
209
 
@@ -213,8 +219,8 @@ export async function archiveBlocks(
213
219
  }
214
220
 
215
221
  logger.verbose("Archiving of finalized blocks complete", {
222
+ ...logCtx,
216
223
  totalArchived: finalizedCanonicalBlocks.length,
217
- finalizedEpoch: finalizedCheckpoint.epoch,
218
224
  });
219
225
  }
220
226
 
@@ -340,6 +346,7 @@ async function migrateDataColumnSidecarsFromHotToColdDb(
340
346
  const dataColumnSidecarBytes = await fromAsync(db.dataColumnSidecar.valuesStreamBinary(block.root));
341
347
  // there could be 0 dataColumnSidecarBytes if block has no blob
342
348
  logger.verbose("migrateDataColumnSidecarsFromHotToColdDb", {
349
+ currentEpoch,
343
350
  slot: block.slot,
344
351
  root: toRootHex(block.root),
345
352
  numSidecars: dataColumnSidecarBytes.length,
@@ -418,13 +418,20 @@ export async function importBlock(
418
418
  this.logger.verbose("After importBlock caching postState without SSZ cache", {slot: postState.slot});
419
419
  }
420
420
 
421
+ // Cache shufflings when crossing an epoch boundary
422
+ const parentEpoch = computeEpochAtSlot(parentBlockSlot);
423
+ if (parentEpoch < blockEpoch) {
424
+ this.shufflingCache.processState(postState);
425
+ this.logger.verbose("Processed shuffling for next epoch", {parentEpoch, blockEpoch, slot: blockSlot});
426
+ }
427
+
421
428
  if (blockSlot % SLOTS_PER_EPOCH === 0) {
422
429
  // Cache state to preserve epoch transition work
423
430
  const checkpointState = postState;
424
431
  const cp = getCheckpointFromState(checkpointState);
425
432
  this.regen.addCheckpointState(cp, checkpointState);
426
- // consumers should not mutate or get the transfered cache
427
- this.emitter.emit(ChainEvent.checkpoint, cp, checkpointState.clone(true));
433
+ // consumers should not mutate state ever
434
+ this.emitter.emit(ChainEvent.checkpoint, cp, checkpointState);
428
435
 
429
436
  // Note: in-lined code from previos handler of ChainEvent.checkpoint
430
437
  this.logger.verbose("Checkpoint processed", toCheckpointHex(cp));
@@ -584,7 +591,10 @@ export function addAttestationPostElectra(
584
591
  true
585
592
  );
586
593
  } else {
587
- const committees = epochCtx.getBeaconCommittees(attestation.data.slot, committeeIndices);
594
+ const attSlot = attestation.data.slot;
595
+ const attEpoch = computeEpochAtSlot(attSlot);
596
+ const decisionRoot = epochCtx.getShufflingDecisionRoot(attEpoch);
597
+ const committees = this.shufflingCache.getBeaconCommittees(attEpoch, decisionRoot, attSlot, committeeIndices);
588
598
  const aggregationBools = attestation.aggregationBits.toBoolArray();
589
599
  let offset = 0;
590
600
  for (let i = 0; i < committees.length; i++) {
@@ -75,6 +75,10 @@ export async function verifyBlocksInEpoch(
75
75
  throw new BlockError(block0, {code: BlockErrorCode.PRESTATE_MISSING, error: e as Error});
76
76
  });
77
77
 
78
+ // in forky condition, make sure to populate ShufflingCache with regened state
79
+ // otherwise it may fail to get indexed attestations from shuffling cache later
80
+ this.shufflingCache.processState(preState0);
81
+
78
82
  if (!isStateValidatorsNodesPopulated(preState0)) {
79
83
  this.logger.verbose("verifyBlocksInEpoch preState0 SSZ cache stats", {
80
84
  slot: preState0.slot,
@@ -105,9 +109,11 @@ export async function verifyBlocksInEpoch(
105
109
  // Store indexed attestations for each block to avoid recomputing them during import
106
110
  const indexedAttestationsByBlock: IndexedAttestation[][] = [];
107
111
  for (const [i, block] of blocks.entries()) {
108
- indexedAttestationsByBlock[i] = block.message.body.attestations.map((attestation) =>
109
- preState0.epochCtx.getIndexedAttestation(fork, attestation)
110
- );
112
+ indexedAttestationsByBlock[i] = block.message.body.attestations.map((attestation) => {
113
+ const attEpoch = computeEpochAtSlot(attestation.data.slot);
114
+ const decisionRoot = preState0.epochCtx.getShufflingDecisionRoot(attEpoch);
115
+ return this.shufflingCache.getIndexedAttestation(attEpoch, decisionRoot, fork, attestation);
116
+ });
111
117
  }
112
118
 
113
119
  // batch all I/O operations to reduce overhead
@@ -59,6 +59,7 @@ export async function verifyBlocksStateTransitionOnly(
59
59
  // if block is trusted don't verify proposer or op signature
60
60
  verifyProposer: !useBlsBatchVerify && !validSignatures && !validProposerSignature,
61
61
  verifySignatures: !useBlsBatchVerify && !validSignatures,
62
+ dontTransferCache: false,
62
63
  },
63
64
  {metrics, validatorMonitor}
64
65
  );
@@ -52,6 +52,7 @@ import {computeNodeIdFromPrivateKey} from "../network/subnets/interface.js";
52
52
  import {BufferPool} from "../util/bufferPool.js";
53
53
  import {Clock, ClockEvent, IClock} from "../util/clock.js";
54
54
  import {CustodyConfig, getValidatorsCustodyRequirement} from "../util/dataColumns.js";
55
+ import {callInNextEventLoop} from "../util/eventLoop.js";
55
56
  import {ensureDir, writeIfNotExist} from "../util/file.js";
56
57
  import {isOptimisticBlock} from "../util/forkChoice.js";
57
58
  import {SerializedCache} from "../util/serializedCache.js";
@@ -291,7 +292,8 @@ export class BeaconChain implements IBeaconChain {
291
292
  });
292
293
 
293
294
  this._earliestAvailableSlot = anchorState.slot;
294
- this.shufflingCache = anchorState.epochCtx.shufflingCache = new ShufflingCache(metrics, logger, this.opts, [
295
+
296
+ this.shufflingCache = new ShufflingCache(metrics, logger, this.opts, [
295
297
  {
296
298
  shuffling: anchorState.epochCtx.previousShuffling,
297
299
  decisionRoot: anchorState.epochCtx.previousDecisionRoot,
@@ -417,6 +419,7 @@ export class BeaconChain implements IBeaconChain {
417
419
  clock.addListener(ClockEvent.epoch, this.onClockEpoch.bind(this));
418
420
  emitter.addListener(ChainEvent.forkChoiceFinalized, this.onForkChoiceFinalized.bind(this));
419
421
  emitter.addListener(ChainEvent.forkChoiceJustified, this.onForkChoiceJustified.bind(this));
422
+ emitter.addListener(ChainEvent.checkpoint, this.onCheckpoint.bind(this));
420
423
  }
421
424
 
422
425
  async init(): Promise<void> {
@@ -504,7 +507,7 @@ export class BeaconChain implements IBeaconChain {
504
507
  async getStateBySlot(
505
508
  slot: Slot,
506
509
  opts?: StateGetOpts
507
- ): Promise<{state: BeaconStateAllForks; executionOptimistic: boolean; finalized: boolean} | null> {
510
+ ): Promise<{state: CachedBeaconStateAllForks; executionOptimistic: boolean; finalized: boolean} | null> {
508
511
  const finalizedBlock = this.forkChoice.getFinalizedBlock();
509
512
 
510
513
  if (slot < finalizedBlock.slot) {
@@ -559,7 +562,7 @@ export class BeaconChain implements IBeaconChain {
559
562
  async getStateByStateRoot(
560
563
  stateRoot: RootHex,
561
564
  opts?: StateGetOpts
562
- ): Promise<{state: BeaconStateAllForks; executionOptimistic: boolean; finalized: boolean} | null> {
565
+ ): Promise<{state: CachedBeaconStateAllForks | Uint8Array; executionOptimistic: boolean; finalized: boolean} | null> {
563
566
  if (opts?.allowRegen) {
564
567
  const state = await this.regen.getState(stateRoot, RegenCaller.restApi);
565
568
  const block = this.forkChoice.getBlock(state.latestBlockHeader.hashTreeRoot());
@@ -587,7 +590,8 @@ export class BeaconChain implements IBeaconChain {
587
590
  };
588
591
  }
589
592
 
590
- const data = await this.db.stateArchive.getByRoot(fromHex(stateRoot));
593
+ // this is mostly useful for a node with `--chain.archiveStateEpochFrequency 1`
594
+ const data = await this.db.stateArchive.getBinaryByRoot(fromHex(stateRoot));
591
595
  return data && {state: data, executionOptimistic: false, finalized: true};
592
596
  }
593
597
 
@@ -980,8 +984,8 @@ export class BeaconChain implements IBeaconChain {
980
984
  this.metrics?.gossipAttestation.useHeadBlockState.inc({caller: regenCaller});
981
985
  state = await this.regen.getState(attHeadBlock.stateRoot, regenCaller);
982
986
  }
983
-
984
- // should always be the current epoch of the active context so no need to await a result from the ShufflingCache
987
+ // resolve the promise to unblock other calls of the same epoch and dependent root
988
+ this.shufflingCache.processState(state);
985
989
  return state.epochCtx.getShufflingAtEpoch(attEpoch);
986
990
  }
987
991
 
@@ -1165,6 +1169,13 @@ export class BeaconChain implements IBeaconChain {
1165
1169
  this.logger.verbose("Fork choice justified", {epoch: cp.epoch, root: cp.rootHex});
1166
1170
  }
1167
1171
 
1172
+ private onCheckpoint(this: BeaconChain, _checkpoint: phase0.Checkpoint, state: CachedBeaconStateAllForks): void {
1173
+ // Defer to not block other checkpoint event handlers, which can cause lightclient update delays
1174
+ callInNextEventLoop(() => {
1175
+ this.shufflingCache.processState(state);
1176
+ });
1177
+ }
1178
+
1168
1179
  private async onForkChoiceFinalized(this: BeaconChain, cp: CheckpointWithHex): Promise<void> {
1169
1180
  this.logger.verbose("Fork choice finalized", {epoch: cp.epoch, root: cp.rootHex});
1170
1181
  this.seenBlockProposers.prune(computeStartSlotAtEpoch(cp.epoch));
@@ -1295,9 +1306,9 @@ export class BeaconChain implements IBeaconChain {
1295
1306
 
1296
1307
  preState = processSlots(preState, block.slot); // Dial preState's slot to block.slot
1297
1308
 
1298
- const postState = this.regen.getStateSync(toRootHex(block.stateRoot)) ?? undefined;
1309
+ const proposerRewards = this.regen.getStateSync(toRootHex(block.stateRoot))?.proposerRewards ?? undefined;
1299
1310
 
1300
- return computeBlockRewards(this.config, block, preState.clone(), postState?.clone());
1311
+ return computeBlockRewards(this.config, block, preState, proposerRewards);
1301
1312
  }
1302
1313
 
1303
1314
  async getAttestationsRewards(
@@ -1338,6 +1349,6 @@ export class BeaconChain implements IBeaconChain {
1338
1349
 
1339
1350
  preState = processSlots(preState, block.slot); // Dial preState's slot to block.slot
1340
1351
 
1341
- return computeSyncCommitteeRewards(this.config, this.index2pubkey, block, preState.clone(), validatorIds);
1352
+ return computeSyncCommitteeRewards(this.config, this.index2pubkey, block, preState, validatorIds);
1342
1353
  }
1343
1354
  }
@@ -6,6 +6,7 @@ import {Logger, toHex, toRootHex} from "@lodestar/utils";
6
6
  import {GENESIS_SLOT} from "../constants/index.js";
7
7
  import {IBeaconDb} from "../db/index.js";
8
8
  import {Metrics} from "../metrics/index.js";
9
+ import {getStateTypeFromBytes} from "../util/multifork.js";
9
10
 
10
11
  export async function persistAnchorState(
11
12
  config: ChainForkConfig,
@@ -53,14 +54,15 @@ export function createGenesisBlock(config: ChainForkConfig, genesisState: Beacon
53
54
  * Restore the latest beacon state from db
54
55
  */
55
56
  export async function initStateFromDb(
56
- _config: ChainForkConfig,
57
+ config: ChainForkConfig,
57
58
  db: IBeaconDb,
58
59
  logger: Logger
59
60
  ): Promise<BeaconStateAllForks> {
60
- const state = await db.stateArchive.lastValue();
61
- if (!state) {
61
+ const stateBytes = await db.stateArchive.lastBinary();
62
+ if (stateBytes == null) {
62
63
  throw new Error("No state exists in database");
63
64
  }
65
+ const state = getStateTypeFromBytes(config, stateBytes).deserializeToViewDU(stateBytes);
64
66
 
65
67
  logger.info("Initializing beacon state from db", {
66
68
  slot: state.slot,
@@ -168,12 +168,12 @@ export interface IBeaconChain {
168
168
  getStateBySlot(
169
169
  slot: Slot,
170
170
  opts?: StateGetOpts
171
- ): Promise<{state: BeaconStateAllForks; executionOptimistic: boolean; finalized: boolean} | null>;
171
+ ): Promise<{state: CachedBeaconStateAllForks; executionOptimistic: boolean; finalized: boolean} | null>;
172
172
  /** Returns a local state by state root */
173
173
  getStateByStateRoot(
174
174
  stateRoot: RootHex,
175
175
  opts?: StateGetOpts
176
- ): Promise<{state: BeaconStateAllForks; executionOptimistic: boolean; finalized: boolean} | null>;
176
+ ): Promise<{state: CachedBeaconStateAllForks | Uint8Array; executionOptimistic: boolean; finalized: boolean} | null>;
177
177
  /** Return serialized bytes of a persisted checkpoint state */
178
178
  getPersistedCheckpointState(checkpoint?: phase0.Checkpoint): Promise<Uint8Array | null>;
179
179
  /** Returns a cached state by checkpoint */
@@ -12,7 +12,6 @@ import {BeaconBlockBody, SSZTypesFor, ssz} from "@lodestar/types";
12
12
  import {SyncCommitteeWitness} from "./types.js";
13
13
 
14
14
  export function getSyncCommitteesWitness(fork: ForkName, state: BeaconStateAllForks): SyncCommitteeWitness {
15
- state.commit();
16
15
  const n1 = state.node;
17
16
  let witness: Uint8Array[];
18
17
  let currentSyncCommitteeRoot: Uint8Array;
@@ -71,7 +70,6 @@ export function getCurrentSyncCommitteeBranch(syncCommitteesWitness: SyncCommitt
71
70
  }
72
71
 
73
72
  export function getFinalizedRootProof(state: CachedBeaconStateAllForks): Uint8Array[] {
74
- state.commit();
75
73
  const finalizedRootGindex = state.epochCtx.isPostElectra() ? FINALIZED_ROOT_GINDEX_ELECTRA : FINALIZED_ROOT_GINDEX;
76
74
  return new Tree(state.node).getSingleProof(BigInt(finalizedRootGindex));
77
75
  }
@@ -5,7 +5,6 @@ import {IForkChoice} from "@lodestar/fork-choice";
5
5
  import {
6
6
  ForkName,
7
7
  ForkSeq,
8
- MAX_ATTESTATIONS,
9
8
  MAX_ATTESTATIONS_ELECTRA,
10
9
  MAX_COMMITTEES_PER_SLOT,
11
10
  MIN_ATTESTATION_INCLUSION_DELAY,
@@ -23,7 +22,6 @@ import {
23
22
  CachedBeaconStateAllForks,
24
23
  CachedBeaconStateAltair,
25
24
  CachedBeaconStateGloas,
26
- CachedBeaconStatePhase0,
27
25
  EffectiveBalanceIncrements,
28
26
  RootCache,
29
27
  computeEpochAtSlot,
@@ -32,21 +30,12 @@ import {
32
30
  getAttestationParticipationStatus,
33
31
  getBlockRootAtSlot,
34
32
  } from "@lodestar/state-transition";
35
- import {
36
- Attestation,
37
- Epoch,
38
- RootHex,
39
- Slot,
40
- ValidatorIndex,
41
- electra,
42
- isElectraAttestation,
43
- phase0,
44
- ssz,
45
- } from "@lodestar/types";
33
+ import {Attestation, Epoch, RootHex, Slot, electra, isElectraAttestation, phase0, ssz} from "@lodestar/types";
46
34
  import {MapDef, assert, toRootHex} from "@lodestar/utils";
47
35
  import {Metrics} from "../../metrics/metrics.js";
48
36
  import {IntersectResult, intersectUint8Arrays} from "../../util/bitArray.js";
49
37
  import {getShufflingDependentRoot} from "../../util/dependentRoot.js";
38
+ import {ShufflingCache} from "../shufflingCache.js";
50
39
  import {InsertOutcome} from "./types.js";
51
40
  import {pruneBySlot, signatureFromBytesNoCheck} from "./utils.js";
52
41
 
@@ -54,8 +43,6 @@ type DataRootHex = string;
54
43
 
55
44
  type CommitteeIndex = number;
56
45
 
57
- // for pre-electra
58
- type AttestationWithScore = {attestation: Attestation; score: number};
59
46
  /**
60
47
  * for electra, this is to consolidate aggregated attestations of the same attestation data into a single attestation to be included in block
61
48
  * note that this is local definition in this file and it's NOT validator consolidation
@@ -110,15 +97,6 @@ const MAX_RETAINED_ATTESTATIONS_PER_GROUP = 4;
110
97
  */
111
98
  const MAX_RETAINED_ATTESTATIONS_PER_GROUP_ELECTRA = 8;
112
99
 
113
- /**
114
- * Pre-electra, each slot has 64 committees, and each block has 128 attestations max so in average
115
- * we get 2 attestation per groups.
116
- * Starting from Jan 2024, we have a performance issue getting attestations for a block. Based on the
117
- * fact that lot of groups will have only 1 full participation attestation, increase this number
118
- * a bit higher than average. This also help decrease number of slots to search for attestations.
119
- */
120
- const MAX_ATTESTATIONS_PER_GROUP = 3;
121
-
122
100
  /**
123
101
  * For electra, there is on chain aggregation of attestations across committees, so we can just pick up to 8
124
102
  * attestations per group, sort by scores to get first 8.
@@ -230,123 +208,18 @@ export class AggregatedAttestationPool {
230
208
  this.lowestPermissibleSlot = Math.max(clockSlot - slotsToRetain, 0);
231
209
  }
232
210
 
233
- getAttestationsForBlock(fork: ForkName, forkChoice: IForkChoice, state: CachedBeaconStateAllForks): Attestation[] {
234
- const forkSeq = ForkSeq[fork];
235
- return forkSeq >= ForkSeq.electra
236
- ? this.getAttestationsForBlockElectra(fork, forkChoice, state)
237
- : this.getAttestationsForBlockPreElectra(fork, forkChoice, state);
238
- }
239
-
240
- /**
241
- * Get attestations to be included in a block pre-electra. Returns up to $MAX_ATTESTATIONS items
242
- */
243
- getAttestationsForBlockPreElectra(
211
+ getAttestationsForBlock(
244
212
  fork: ForkName,
245
213
  forkChoice: IForkChoice,
214
+ shufflingCache: ShufflingCache,
246
215
  state: CachedBeaconStateAllForks
247
- ): phase0.Attestation[] {
248
- const stateSlot = state.slot;
249
- const stateEpoch = state.epochCtx.epoch;
250
- const statePrevEpoch = stateEpoch - 1;
251
-
252
- const notSeenValidatorsFn = getNotSeenValidatorsFn(this.config, state);
253
- const validateAttestationDataFn = getValidateAttestationDataFn(forkChoice, state);
254
-
255
- const attestationsByScore: AttestationWithScore[] = [];
256
-
257
- const slots = Array.from(this.attestationGroupByIndexByDataHexBySlot.keys()).sort((a, b) => b - a);
258
- let minScore = Number.MAX_SAFE_INTEGER;
259
- let slotCount = 0;
260
- slot: for (const slot of slots) {
261
- slotCount++;
262
- const attestationGroupByIndexByDataHash = this.attestationGroupByIndexByDataHexBySlot.get(slot);
263
- // should not happen
264
- if (!attestationGroupByIndexByDataHash) {
265
- throw Error(`No aggregated attestation pool for slot=${slot}`);
266
- }
267
-
268
- const epoch = computeEpochAtSlot(slot);
269
- // validateAttestation condition: Attestation target epoch not in previous or current epoch
270
- if (!(epoch === stateEpoch || epoch === statePrevEpoch)) {
271
- continue; // Invalid attestations
272
- }
273
- // validateAttestation condition: Attestation slot not within inclusion window
274
- if (
275
- !(
276
- slot + MIN_ATTESTATION_INCLUSION_DELAY <= stateSlot &&
277
- // Post deneb, attestations are valid for current and previous epoch
278
- (ForkSeq[fork] >= ForkSeq.deneb || stateSlot <= slot + SLOTS_PER_EPOCH)
279
- )
280
- ) {
281
- continue; // Invalid attestations
282
- }
283
-
284
- const inclusionDistance = stateSlot - slot;
285
- for (const attestationGroupByIndex of attestationGroupByIndexByDataHash.values()) {
286
- for (const [committeeIndex, attestationGroup] of attestationGroupByIndex.entries()) {
287
- const notSeenCommitteeMembers = notSeenValidatorsFn(epoch, slot, committeeIndex);
288
- if (notSeenCommitteeMembers === null || notSeenCommitteeMembers.size === 0) {
289
- continue;
290
- }
291
-
292
- if (
293
- slotCount > 2 &&
294
- attestationsByScore.length >= MAX_ATTESTATIONS &&
295
- notSeenCommitteeMembers.size / inclusionDistance < minScore
296
- ) {
297
- // after 2 slots, there are a good chance that we have 2 * MAX_ATTESTATIONS attestations and break the for loop early
298
- // if not, we may have to scan all slots in the pool
299
- // if we have enough attestations and the max possible score is lower than scores of `attestationsByScore`, we should skip
300
- // otherwise it takes time to check attestation, add it and remove it later after the sort by score
301
- continue;
302
- }
303
-
304
- if (validateAttestationDataFn(attestationGroup.data) !== null) {
305
- continue;
306
- }
307
-
308
- // TODO: Is it necessary to validateAttestation for:
309
- // - Attestation committee index not within current committee count
310
- // - Attestation aggregation bits length does not match committee length
311
- //
312
- // These properties should not change after being validate in gossip
313
- // IF they have to be validated, do it only with one attestation per group since same data
314
- // The committeeCountPerSlot can be precomputed once per slot
315
- const getAttestationsResult = attestationGroup.getAttestationsForBlock(
316
- fork,
317
- state.epochCtx.effectiveBalanceIncrements,
318
- notSeenCommitteeMembers,
319
- MAX_ATTESTATIONS_PER_GROUP
320
- );
321
- for (const {attestation, newSeenEffectiveBalance} of getAttestationsResult.result) {
322
- const score = newSeenEffectiveBalance / inclusionDistance;
323
- if (score < minScore) {
324
- minScore = score;
325
- }
326
- attestationsByScore.push({
327
- attestation,
328
- score,
329
- });
330
- }
331
-
332
- // Stop accumulating attestations there are enough that may have good scoring
333
- if (attestationsByScore.length >= MAX_ATTESTATIONS * 2) {
334
- break slot;
335
- }
336
- }
337
- }
216
+ ): Attestation[] {
217
+ const forkSeq = ForkSeq[fork];
218
+ if (forkSeq < ForkSeq.electra) {
219
+ throw new Error("Does not support producing blocks for pre-electra forks anymore");
338
220
  }
339
221
 
340
- const sortedAttestationsByScore = attestationsByScore.sort((a, b) => b.score - a.score);
341
- const attestationsForBlock: phase0.Attestation[] = [];
342
- for (const [i, attestationWithScore] of sortedAttestationsByScore.entries()) {
343
- if (i >= MAX_ATTESTATIONS) {
344
- break;
345
- }
346
- // attestations could be modified in this op pool, so we need to clone for block
347
- attestationsForBlock.push(ssz.phase0.Attestation.clone(attestationWithScore.attestation));
348
- }
349
- return attestationsForBlock;
222
+ return this.getAttestationsForBlockElectra(fork, forkChoice, shufflingCache, state);
350
223
  }
351
224
 
352
225
  /**
@@ -355,6 +228,7 @@ export class AggregatedAttestationPool {
355
228
  getAttestationsForBlockElectra(
356
229
  fork: ForkName,
357
230
  forkChoice: IForkChoice,
231
+ shufflingCache: ShufflingCache,
358
232
  state: CachedBeaconStateAllForks
359
233
  ): electra.Attestation[] {
360
234
  const stateSlot = state.slot;
@@ -362,7 +236,7 @@ export class AggregatedAttestationPool {
362
236
  const statePrevEpoch = stateEpoch - 1;
363
237
  const rootCache = new RootCache(state);
364
238
 
365
- const notSeenValidatorsFn = getNotSeenValidatorsFn(this.config, state);
239
+ const notSeenValidatorsFn = getNotSeenValidatorsFn(this.config, shufflingCache, state);
366
240
  const validateAttestationDataFn = getValidateAttestationDataFn(forkChoice, state);
367
241
 
368
242
  const slots = Array.from(this.attestationGroupByIndexByDataHexBySlot.keys()).sort((a, b) => b - a);
@@ -864,41 +738,14 @@ export function aggregateConsolidation({byCommittee, attData}: AttestationsConso
864
738
  * Pre-compute participation from a CachedBeaconStateAllForks, for use to check if an attestation's committee
865
739
  * has already attested or not.
866
740
  */
867
- export function getNotSeenValidatorsFn(config: BeaconConfig, state: CachedBeaconStateAllForks): GetNotSeenValidatorsFn {
741
+ export function getNotSeenValidatorsFn(
742
+ config: BeaconConfig,
743
+ shufflingCache: ShufflingCache,
744
+ state: CachedBeaconStateAllForks
745
+ ): GetNotSeenValidatorsFn {
868
746
  const stateSlot = state.slot;
869
747
  if (config.getForkName(stateSlot) === ForkName.phase0) {
870
- // Get attestations to be included in a phase0 block.
871
- // As we are close to altair, this is not really important, it's mainly for e2e.
872
- // The performance is not great due to the different BeaconState data structure to altair.
873
- // check for phase0 block already
874
- const phase0State = state as CachedBeaconStatePhase0;
875
- const stateEpoch = computeEpochAtSlot(stateSlot);
876
-
877
- const previousEpochParticipants = extractParticipationPhase0(
878
- phase0State.previousEpochAttestations.getAllReadonly(),
879
- state
880
- );
881
- const currentEpochParticipants = extractParticipationPhase0(
882
- phase0State.currentEpochAttestations.getAllReadonly(),
883
- state
884
- );
885
-
886
- return (epoch: Epoch, slot: Slot, committeeIndex: number) => {
887
- const participants =
888
- epoch === stateEpoch ? currentEpochParticipants : epoch === stateEpoch - 1 ? previousEpochParticipants : null;
889
- if (participants === null) {
890
- return null;
891
- }
892
- const committee = state.epochCtx.getBeaconCommittee(slot, committeeIndex);
893
-
894
- const notSeenCommitteeMembers = new Set<number>();
895
- for (const [i, validatorIndex] of committee.entries()) {
896
- if (!participants.has(validatorIndex)) {
897
- notSeenCommitteeMembers.add(i);
898
- }
899
- }
900
- return notSeenCommitteeMembers.size === 0 ? null : notSeenCommitteeMembers;
901
- };
748
+ throw new Error("getNotSeenValidatorsFn is not supported phase0 state");
902
749
  }
903
750
 
904
751
  // altair and future forks
@@ -927,7 +774,8 @@ export function getNotSeenValidatorsFn(config: BeaconConfig, state: CachedBeacon
927
774
  return notSeenCommitteeMembers.size === 0 ? null : notSeenCommitteeMembers;
928
775
  }
929
776
 
930
- const committee = state.epochCtx.getBeaconCommittee(slot, committeeIndex);
777
+ const decisionRoot = state.epochCtx.getShufflingDecisionRoot(computeEpochAtSlot(slot));
778
+ const committee = shufflingCache.getBeaconCommittee(epoch, decisionRoot, slot, committeeIndex);
931
779
  notSeenCommitteeMembers = new Set<number>();
932
780
  for (const [i, validatorIndex] of committee.entries()) {
933
781
  // no need to check flagIsTimelySource as if validator is not seen, it's participation status is 0
@@ -942,26 +790,6 @@ export function getNotSeenValidatorsFn(config: BeaconConfig, state: CachedBeacon
942
790
  };
943
791
  }
944
792
 
945
- export function extractParticipationPhase0(
946
- attestations: phase0.PendingAttestation[],
947
- state: CachedBeaconStateAllForks
948
- ): Set<ValidatorIndex> {
949
- const {epochCtx} = state;
950
- const allParticipants = new Set<ValidatorIndex>();
951
- for (const att of attestations) {
952
- const aggregationBits = att.aggregationBits;
953
- const attData = att.data;
954
- const attSlot = attData.slot;
955
- const committeeIndex = attData.index;
956
- const committee = epochCtx.getBeaconCommittee(attSlot, committeeIndex);
957
- const participants = aggregationBits.intersectValues(committee);
958
- for (const participant of participants) {
959
- allParticipants.add(participant);
960
- }
961
- }
962
- return allParticipants;
963
- }
964
-
965
793
  /**
966
794
  * This returns a function to validate if an attestation data is compatible to a state.
967
795
  *