@lodestar/beacon-node 1.48.0-dev.a427d3dbb5 → 1.48.0-dev.acdde443d2

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 (137) hide show
  1. package/lib/chain/archiveStore/archiveStore.d.ts +1 -0
  2. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  3. package/lib/chain/archiveStore/archiveStore.js +1 -0
  4. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  5. package/lib/chain/archiveStore/historicalState/historicalStateRegen.d.ts.map +1 -1
  6. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js +1 -0
  7. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js.map +1 -1
  8. package/lib/chain/archiveStore/historicalState/types.d.ts +2 -0
  9. package/lib/chain/archiveStore/historicalState/types.d.ts.map +1 -1
  10. package/lib/chain/archiveStore/historicalState/types.js.map +1 -1
  11. package/lib/chain/archiveStore/historicalState/worker.js +4 -1
  12. package/lib/chain/archiveStore/historicalState/worker.js.map +1 -1
  13. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
  14. package/lib/chain/archiveStore/utils/archiveBlocks.js +31 -34
  15. package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
  16. package/lib/chain/blocks/writeBlockInputToDb.d.ts.map +1 -1
  17. package/lib/chain/blocks/writeBlockInputToDb.js +9 -16
  18. package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
  19. package/lib/chain/chain.d.ts +2 -1
  20. package/lib/chain/chain.d.ts.map +1 -1
  21. package/lib/chain/chain.js +47 -24
  22. package/lib/chain/chain.js.map +1 -1
  23. package/lib/db/beacon.d.ts +14 -5
  24. package/lib/db/beacon.d.ts.map +1 -1
  25. package/lib/db/beacon.js +17 -3
  26. package/lib/db/beacon.js.map +1 -1
  27. package/lib/db/dataColumnStore.d.ts +39 -0
  28. package/lib/db/dataColumnStore.d.ts.map +1 -0
  29. package/lib/db/dataColumnStore.js +81 -0
  30. package/lib/db/dataColumnStore.js.map +1 -0
  31. package/lib/db/flatFileStore/atomicWrite.d.ts +8 -0
  32. package/lib/db/flatFileStore/atomicWrite.d.ts.map +1 -0
  33. package/lib/db/flatFileStore/atomicWrite.js +61 -0
  34. package/lib/db/flatFileStore/atomicWrite.js.map +1 -0
  35. package/lib/db/flatFileStore/dcolFormat.d.ts +90 -0
  36. package/lib/db/flatFileStore/dcolFormat.d.ts.map +1 -0
  37. package/lib/db/flatFileStore/dcolFormat.js +286 -0
  38. package/lib/db/flatFileStore/dcolFormat.js.map +1 -0
  39. package/lib/db/flatFileStore/errors.d.ts +82 -0
  40. package/lib/db/flatFileStore/errors.d.ts.map +1 -0
  41. package/lib/db/flatFileStore/errors.js +32 -0
  42. package/lib/db/flatFileStore/errors.js.map +1 -0
  43. package/lib/db/flatFileStore/flatFileStore.d.ts +41 -0
  44. package/lib/db/flatFileStore/flatFileStore.d.ts.map +1 -0
  45. package/lib/db/flatFileStore/flatFileStore.js +251 -0
  46. package/lib/db/flatFileStore/flatFileStore.js.map +1 -0
  47. package/lib/db/flatFileStore/index.d.ts +4 -0
  48. package/lib/db/flatFileStore/index.d.ts.map +1 -0
  49. package/lib/db/flatFileStore/index.js +2 -0
  50. package/lib/db/flatFileStore/index.js.map +1 -0
  51. package/lib/db/flatFileStore/interface.d.ts +25 -0
  52. package/lib/db/flatFileStore/interface.d.ts.map +1 -0
  53. package/lib/db/flatFileStore/interface.js +2 -0
  54. package/lib/db/flatFileStore/interface.js.map +1 -0
  55. package/lib/db/flatFileStore/metrics.d.ts +24 -0
  56. package/lib/db/flatFileStore/metrics.d.ts.map +1 -0
  57. package/lib/db/flatFileStore/metrics.js +25 -0
  58. package/lib/db/flatFileStore/metrics.js.map +1 -0
  59. package/lib/db/flatFileStore/path.d.ts +9 -0
  60. package/lib/db/flatFileStore/path.d.ts.map +1 -0
  61. package/lib/db/flatFileStore/path.js +21 -0
  62. package/lib/db/flatFileStore/path.js.map +1 -0
  63. package/lib/db/flatFileStore/slotIndex.d.ts +14 -0
  64. package/lib/db/flatFileStore/slotIndex.d.ts.map +1 -0
  65. package/lib/db/flatFileStore/slotIndex.js +46 -0
  66. package/lib/db/flatFileStore/slotIndex.js.map +1 -0
  67. package/lib/db/flatFileStore/snappy.d.ts +3 -0
  68. package/lib/db/flatFileStore/snappy.d.ts.map +1 -0
  69. package/lib/db/flatFileStore/snappy.js +12 -0
  70. package/lib/db/flatFileStore/snappy.js.map +1 -0
  71. package/lib/db/index.d.ts +1 -0
  72. package/lib/db/index.d.ts.map +1 -1
  73. package/lib/db/interface.d.ts +8 -1
  74. package/lib/db/interface.d.ts.map +1 -1
  75. package/lib/db/repositories/blockArchive.d.ts +8 -1
  76. package/lib/db/repositories/blockArchive.d.ts.map +1 -1
  77. package/lib/db/repositories/blockArchive.js +65 -28
  78. package/lib/db/repositories/blockArchive.js.map +1 -1
  79. package/lib/db/repositories/blockArchiveIndex.d.ts +1 -2
  80. package/lib/db/repositories/blockArchiveIndex.d.ts.map +1 -1
  81. package/lib/db/repositories/blockArchiveIndex.js +3 -7
  82. package/lib/db/repositories/blockArchiveIndex.js.map +1 -1
  83. package/lib/metrics/metrics/lodestar.d.ts +18 -0
  84. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  85. package/lib/metrics/metrics/lodestar.js +55 -1
  86. package/lib/metrics/metrics/lodestar.js.map +1 -1
  87. package/lib/monitoring/clientStats.js +1 -1
  88. package/lib/monitoring/clientStats.js.map +1 -1
  89. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  90. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +112 -91
  91. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  92. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.d.ts.map +1 -1
  93. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +2 -0
  94. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
  95. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts +2 -1
  96. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts.map +1 -1
  97. package/lib/network/reqresp/utils/dataColumnResponseValidation.js +14 -9
  98. package/lib/network/reqresp/utils/dataColumnResponseValidation.js.map +1 -1
  99. package/lib/node/nodejs.d.ts +2 -1
  100. package/lib/node/nodejs.d.ts.map +1 -1
  101. package/lib/node/nodejs.js +4 -2
  102. package/lib/node/nodejs.js.map +1 -1
  103. package/lib/util/sszBytes.d.ts +6 -0
  104. package/lib/util/sszBytes.d.ts.map +1 -1
  105. package/lib/util/sszBytes.js +1 -1
  106. package/lib/util/sszBytes.js.map +1 -1
  107. package/package.json +15 -15
  108. package/src/chain/archiveStore/archiveStore.ts +6 -1
  109. package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +1 -0
  110. package/src/chain/archiveStore/historicalState/types.ts +2 -0
  111. package/src/chain/archiveStore/historicalState/worker.ts +4 -1
  112. package/src/chain/archiveStore/utils/archiveBlocks.ts +34 -39
  113. package/src/chain/blocks/writeBlockInputToDb.ts +10 -17
  114. package/src/chain/chain.ts +53 -23
  115. package/src/db/beacon.ts +33 -6
  116. package/src/db/dataColumnStore.ts +113 -0
  117. package/src/db/flatFileStore/atomicWrite.ts +65 -0
  118. package/src/db/flatFileStore/dcolFormat.ts +389 -0
  119. package/src/db/flatFileStore/errors.ts +53 -0
  120. package/src/db/flatFileStore/flatFileStore.ts +310 -0
  121. package/src/db/flatFileStore/index.ts +3 -0
  122. package/src/db/flatFileStore/interface.ts +21 -0
  123. package/src/db/flatFileStore/metrics.ts +48 -0
  124. package/src/db/flatFileStore/path.ts +32 -0
  125. package/src/db/flatFileStore/slotIndex.ts +56 -0
  126. package/src/db/flatFileStore/snappy.ts +14 -0
  127. package/src/db/index.ts +1 -0
  128. package/src/db/interface.ts +9 -1
  129. package/src/db/repositories/blockArchive.ts +89 -30
  130. package/src/db/repositories/blockArchiveIndex.ts +4 -9
  131. package/src/metrics/metrics/lodestar.ts +57 -1
  132. package/src/monitoring/clientStats.ts +1 -1
  133. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +139 -105
  134. package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +2 -0
  135. package/src/network/reqresp/utils/dataColumnResponseValidation.ts +15 -7
  136. package/src/node/nodejs.ts +6 -1
  137. package/src/util/sszBytes.ts +1 -1
@@ -1,7 +1,6 @@
1
1
  import {Db, encodeKey} from "@lodestar/db";
2
2
  import {ForkAll} from "@lodestar/params";
3
3
  import {Root, SSZTypesFor, SignedBeaconBlock, Slot, ssz} from "@lodestar/types";
4
- import {intToBytes} from "@lodestar/utils";
5
4
  import {Bucket, getBucketNameByValue} from "../buckets.js";
6
5
 
7
6
  export const rootIndexBucketId = getBucketNameByValue(Bucket.index_blockArchiveRootIndex);
@@ -15,14 +14,6 @@ export async function getParentRootIndex(db: Db, parentRoot: Root): Promise<Uint
15
14
  return db.get(getParentRootIndexKey(parentRoot), {bucketId: parentRootIndexBucketId});
16
15
  }
17
16
 
18
- export async function storeRootIndex(db: Db, slot: Slot, blockRoot: Root): Promise<void> {
19
- return db.put(getRootIndexKey(blockRoot), intToBytes(slot, 8, "be"), {bucketId: rootIndexBucketId});
20
- }
21
-
22
- export async function storeParentRootIndex(db: Db, slot: Slot, parentRoot: Root): Promise<void> {
23
- return db.put(getParentRootIndexKey(parentRoot), intToBytes(slot, 8, "be"), {bucketId: parentRootIndexBucketId});
24
- }
25
-
26
17
  export async function deleteRootIndex(
27
18
  db: Db,
28
19
  signedBeaconBlockType: SSZTypesFor<ForkAll, "SignedBeaconBlock">,
@@ -43,3 +34,7 @@ export function getParentRootIndexKey(parentRoot: Root): Uint8Array {
43
34
  export function getRootIndexKey(root: Root): Uint8Array {
44
35
  return encodeKey(Bucket.index_blockArchiveRootIndex, root);
45
36
  }
37
+
38
+ export function getSlotIndexKey(slot: Slot): Uint8Array {
39
+ return encodeKey(Bucket.index_mainChain, slot);
40
+ }
@@ -21,6 +21,7 @@ import {ReprocessStatus} from "../../chain/reprocess.js";
21
21
  import {RejectReason} from "../../chain/seenCache/seenAttestationData.js";
22
22
  import {CacheItemType} from "../../chain/stateCache/types.js";
23
23
  import {OpSource} from "../../chain/validatorMonitor.js";
24
+ import type {FlatFileStoreOperation} from "../../db/flatFileStore/metrics.js";
24
25
  import {ExecutionPayloadStatus} from "../../execution/index.js";
25
26
  import {GossipType} from "../../network/index.js";
26
27
  import {CannotAcceptWorkReason, ReprocessRejectReason} from "../../network/processor/index.js";
@@ -2099,7 +2100,7 @@ export function createLodestarMetrics(
2099
2100
  }),
2100
2101
  dbSizeTotal: register.gauge({
2101
2102
  name: "lodestar_db_size_bytes_total",
2102
- help: "Approximate number of bytes of file system space used by db",
2103
+ help: "Approximate number of bytes of file system space used by LevelDB",
2103
2104
  }),
2104
2105
  dbApproximateSizeTime: register.histogram({
2105
2106
  name: "lodestar_db_approximate_size_time_seconds",
@@ -2108,6 +2109,61 @@ export function createLodestarMetrics(
2108
2109
  }),
2109
2110
  },
2110
2111
 
2112
+ flatFileStore: {
2113
+ operationDuration: {
2114
+ read: register.histogram({
2115
+ name: "lodestar_flat_file_store_read_duration_seconds",
2116
+ help: "Duration of flat file store reads in seconds",
2117
+ buckets: [0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05],
2118
+ }),
2119
+ write: register.histogram({
2120
+ name: "lodestar_flat_file_store_write_duration_seconds",
2121
+ help: "Duration of flat file store writes in seconds",
2122
+ buckets: [0.005, 0.01, 0.05, 0.1, 0.5, 1],
2123
+ }),
2124
+ delete: register.histogram({
2125
+ name: "lodestar_flat_file_store_delete_duration_seconds",
2126
+ help: "Duration of flat file store deletions in seconds",
2127
+ buckets: [0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05],
2128
+ }),
2129
+ prune: register.histogram({
2130
+ name: "lodestar_flat_file_store_prune_duration_seconds",
2131
+ help: "Duration of flat file store pruning in seconds",
2132
+ buckets: [0.0005, 0.01, 0.05, 0.5, 5, 30],
2133
+ }),
2134
+ },
2135
+ operationErrors: register.counter<{operation: FlatFileStoreOperation}>({
2136
+ name: "lodestar_flat_file_store_operation_errors_total",
2137
+ help: "Total count of failed flat file store operations",
2138
+ labelNames: ["operation"],
2139
+ }),
2140
+ readBytes: register.counter({
2141
+ name: "lodestar_flat_file_store_read_bytes_total",
2142
+ help: "Total bytes read from flat file storage",
2143
+ }),
2144
+ writeBytes: register.counter({
2145
+ name: "lodestar_flat_file_store_write_bytes_total",
2146
+ help: "Total bytes written to flat file storage",
2147
+ }),
2148
+ prunedDirectories: register.counter({
2149
+ name: "lodestar_flat_file_store_pruned_directories_total",
2150
+ help: "Total count of slot directories pruned from flat file storage",
2151
+ }),
2152
+ slotIndexSize: register.gauge({
2153
+ name: "lodestar_flat_file_store_indexed_slots",
2154
+ help: "Count of indexed flat file slot directories, including empty directories awaiting pruning",
2155
+ }),
2156
+ startupDuration: register.histogram({
2157
+ name: "lodestar_flat_file_store_startup_duration_seconds",
2158
+ help: "Duration of flat file store slot index reconstruction in seconds",
2159
+ buckets: [0.1, 0.5, 1, 5, 10, 30, 60, 300],
2160
+ }),
2161
+ startupErrors: register.counter({
2162
+ name: "lodestar_flat_file_store_startup_errors_total",
2163
+ help: "Total count of flat file store startup failures",
2164
+ }),
2165
+ },
2166
+
2111
2167
  pruneHistory: {
2112
2168
  pruneCount: register.gauge({
2113
2169
  name: "lodestar_prune_history_prune_count_total",
@@ -107,7 +107,7 @@ function createBeaconNodeStats(): ClientStats {
107
107
  metricName: "lodestar_db_size_bytes_total",
108
108
  jsonType: JsonType.Number,
109
109
  defaultValue: 0,
110
- description: "Amount of bytes consumed on disk by the beacon node's database",
110
+ description: "Amount of bytes consumed on disk by the beacon node's LevelDB database",
111
111
  }),
112
112
  networkLibp2pBytesTotalReceive: new MetricProperty({
113
113
  jsonKey: "network_libp2p_bytes_total_receive",
@@ -1,11 +1,11 @@
1
1
  import {PeerId} from "@libp2p/interface";
2
2
  import {ChainConfig} from "@lodestar/config";
3
3
  import {PayloadStatus} from "@lodestar/fork-choice";
4
- import {ForkSeq, GENESIS_SLOT} from "@lodestar/params";
4
+ import {ForkSeq, GENESIS_SLOT, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
5
5
  import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp";
6
6
  import {computeEpochAtSlot} from "@lodestar/state-transition";
7
- import {ColumnIndex, Epoch, fulu} from "@lodestar/types";
8
- import {fromHex} from "@lodestar/utils";
7
+ import {ColumnIndex, Epoch, RootHex, fulu} from "@lodestar/types";
8
+ import {fromHex, toRootHex} from "@lodestar/utils";
9
9
  import {IBeaconChain} from "../../../chain/index.js";
10
10
  import {IBeaconDb} from "../../../db/index.js";
11
11
  import {prettyPrintPeerId} from "../../util.js";
@@ -50,121 +50,155 @@ export async function* onDataColumnSidecarsByRange(
50
50
  );
51
51
  }
52
52
 
53
- const finalized = db.dataColumnSidecarArchive;
54
53
  const finalizedSlot = chain.forkChoice.getFinalizedBlock().slot;
55
- // Columns of the last finalized block live in different DBs depending on fork:
56
- // - Pre-gloas (fulu): migrated to dataColumnSidecarArchive in the same finalization run.
57
- // - Post-gloas: stay in the hot db (db.dataColumnSidecar) until the next finalization run,
58
- // because the migration filter requires payloadStatus === FULL for gloas blocks.
59
- // archiveMaxSlot is the last slot whose columns are served by the archive loop below;
60
- // anything above it is served by the headChain loop.
54
+ // At Gloas, finalizing the beacon block does not finalize its payload. Keep the boundary block in the
55
+ // fork-choice-backed section until the following finalization, matching ExecutionPayloadEnvelopesByRange.
61
56
  const isPostGloasFinalized = chain.config.getForkSeq(finalizedSlot) >= ForkSeq.gloas;
62
57
  const archiveMaxSlot = isPostGloasFinalized ? finalizedSlot - 1 : finalizedSlot;
58
+ const headBlock = chain.forkChoice.getHead();
59
+ const headChain = chain.forkChoice.getAllAncestorBlocks(headBlock.blockRoot, headBlock.payloadStatus);
63
60
 
64
- // Finalized range of columns
65
- if (startSlot <= archiveMaxSlot) {
66
- const archiveEnd = Math.min(endSlot, archiveMaxSlot + 1);
67
- for (let slot = startSlot; slot < archiveEnd; slot++) {
68
- const dataColumnSidecars = await finalized.getManyBinary(slot, availableColumns);
69
-
70
- const unavailableColumnIndices: ColumnIndex[] = [];
71
- for (let i = 0; i < dataColumnSidecars.length; i++) {
72
- const dataColumnSidecarBytes = dataColumnSidecars[i];
73
- if (dataColumnSidecarBytes) {
74
- yield {
75
- data: dataColumnSidecarBytes,
76
- boundary: chain.config.getForkBoundaryAtEpoch(computeEpochAtSlot(slot)),
77
- };
78
- }
79
-
80
- // TODO: Check blobs for that block and respond resource_unavailable
81
- // After we have consensus from other teams on the specs
82
- else {
83
- unavailableColumnIndices.push(availableColumns[i]);
84
- }
61
+ for await (const block of resolveCanonicalDataColumnBlocks(
62
+ chain,
63
+ db,
64
+ headChain,
65
+ startSlot,
66
+ endSlot,
67
+ archiveMaxSlot,
68
+ finalizedSlot
69
+ )) {
70
+ const dataColumnSidecars =
71
+ block.blockRoot === null
72
+ ? await db.dataColumnSidecarArchive.getManyBinary(block.slot, availableColumns)
73
+ : await chain.getSerializedDataColumnSidecars(block.slot, block.blockRoot, availableColumns);
74
+ if (block.blockRoot === null && dataColumnSidecars.every((sidecar) => sidecar === undefined)) continue;
75
+ const unavailableColumnIndices: ColumnIndex[] = [];
76
+ for (let i = 0; i < dataColumnSidecars.length; i++) {
77
+ const dataColumnSidecarBytes = dataColumnSidecars[i];
78
+ if (dataColumnSidecarBytes) {
79
+ yield {
80
+ data: dataColumnSidecarBytes,
81
+ boundary: chain.config.getForkBoundaryAtEpoch(computeEpochAtSlot(block.slot)),
82
+ };
83
+ } else {
84
+ unavailableColumnIndices.push(availableColumns[i]);
85
85
  }
86
+ }
86
87
 
87
- if (unavailableColumnIndices.length) {
88
- await handleColumnSidecarUnavailability({
89
- chain,
90
- db,
91
- metrics: chain.metrics,
92
- unavailableColumnIndices,
93
- slot,
94
- requestedColumns,
95
- availableColumns,
96
- });
97
- }
88
+ if (unavailableColumnIndices.length > 0) {
89
+ await handleColumnSidecarUnavailability({
90
+ chain,
91
+ db,
92
+ metrics: chain.metrics,
93
+ unavailableColumnIndices,
94
+ blockRoot: block.unavailabilityBlockRoot ? fromHex(block.unavailabilityBlockRoot) : undefined,
95
+ finalized: block.finalized,
96
+ slot: block.slot,
97
+ requestedColumns,
98
+ availableColumns,
99
+ });
98
100
  }
99
101
  }
102
+ }
100
103
 
101
- // Non-finalized range of columns
102
- if (endSlot > archiveMaxSlot) {
103
- const headBlock = chain.forkChoice.getHead();
104
- const headRoot = headBlock.blockRoot;
105
- // getAllAncestorBlocks includes the last finalized block as its final element.
106
- // Skip anything the archive loop above already served via the block.slot > archiveMaxSlot
107
- // filter below (pre-gloas this skips finalizedSlot, post-gloas it keeps it).
108
- const headChain = chain.forkChoice.getAllAncestorBlocks(headRoot, headBlock.payloadStatus);
109
-
110
- // Iterate head chain with ascending block numbers
111
- for (let i = headChain.length - 1; i >= 0; i--) {
112
- const block = headChain[i];
113
-
114
- // Must include only columns in the range requested
115
- if (block.slot > archiveMaxSlot && block.slot >= startSlot && block.slot < endSlot) {
116
- // Post-gloas, columns exist only for FULL blocks (pre-gloas blocks are always FULL)
117
- if (block.payloadStatus !== PayloadStatus.FULL) {
118
- continue;
119
- }
120
-
121
- // Note: Here the forkChoice head may change due to a re-org, so the headChain reflects the canonical chain
122
- // at the time of the start of the request. Spec is clear the chain of columns must be consistent, but on
123
- // re-org there's no need to abort the request
124
- // Spec: https://github.com/ethereum/consensus-specs/blob/ad36024441cf910d428d03f87f331fbbd2b3e5f1/specs/fulu/p2p-interface.md#L425-L429
125
- const dataColumnSidecars = await chain.getSerializedDataColumnSidecars(
126
- block.slot,
127
- block.blockRoot,
128
- availableColumns
129
- );
130
-
131
- const unavailableColumnIndices: ColumnIndex[] = [];
132
- for (let i = 0; i < dataColumnSidecars.length; i++) {
133
- const dataColumnSidecarBytes = dataColumnSidecars[i];
134
- if (dataColumnSidecarBytes) {
135
- yield {
136
- data: dataColumnSidecarBytes,
137
- boundary: chain.config.getForkBoundaryAtEpoch(computeEpochAtSlot(block.slot)),
138
- };
139
- }
140
-
141
- // TODO: Check blobs for that block and respond resource_unavailable
142
- // After we have consensus from other teams on the specs
143
- else {
144
- unavailableColumnIndices.push(availableColumns[i]);
145
- }
146
- }
147
-
148
- if (unavailableColumnIndices.length) {
149
- await handleColumnSidecarUnavailability({
150
- chain,
151
- db,
152
- metrics: chain.metrics,
153
- unavailableColumnIndices,
154
- blockRoot: fromHex(block.blockRoot),
155
- slot: block.slot,
156
- requestedColumns,
157
- availableColumns,
158
- });
159
- }
160
- }
104
+ type CanonicalDataColumnBlock = {
105
+ slot: number;
106
+ blockRoot: RootHex | null;
107
+ unavailabilityBlockRoot?: RootHex;
108
+ finalized: boolean;
109
+ };
161
110
 
162
- // If block is after endSlot, stop iterating
163
- else if (block.slot >= endSlot) {
164
- break;
111
+ async function* resolveCanonicalDataColumnBlocks(
112
+ chain: IBeaconChain,
113
+ db: IBeaconDb,
114
+ headChain: ReturnType<IBeaconChain["forkChoice"]["getAllAncestorBlocks"]>,
115
+ startSlot: number,
116
+ endSlot: number,
117
+ archiveMaxSlot: number,
118
+ finalizedSlot: number
119
+ ): AsyncIterable<CanonicalDataColumnBlock> {
120
+ const canonicalBlocksBySlot = new Map(headChain.map((block) => [block.slot, block]));
121
+ const oldestForkChoiceSlot = headChain.at(-1)?.slot ?? Number.POSITIVE_INFINITY;
122
+ const archiveEnd = Math.min(endSlot, archiveMaxSlot + 1);
123
+ const recentRoots = getRecentCanonicalRoots(chain, startSlot, Math.min(archiveEnd, oldestForkChoiceSlot));
124
+
125
+ for (let slot = startSlot; slot < archiveEnd; slot++) {
126
+ if (slot >= oldestForkChoiceSlot) {
127
+ const block = canonicalBlocksBySlot.get(slot);
128
+ if (block?.payloadStatus === PayloadStatus.FULL) {
129
+ yield {
130
+ slot,
131
+ blockRoot: block.blockRoot,
132
+ unavailabilityBlockRoot: block.blockRoot,
133
+ finalized: true,
134
+ };
165
135
  }
136
+ continue;
137
+ }
138
+
139
+ let blockRoot = recentRoots.get(slot);
140
+ if (blockRoot === null) continue;
141
+ if (blockRoot === undefined) {
142
+ const root = await db.blockArchive.getRootBySlot(slot);
143
+ blockRoot = root === null ? null : toRootHex(root);
166
144
  }
145
+ if (blockRoot === null && (db.lastLegacyArchiveSlot === null || slot > db.lastLegacyArchiveSlot)) continue;
146
+ if (
147
+ chain.config.getForkSeq(slot) >= ForkSeq.gloas &&
148
+ !(await hasExecutionPayloadEnvelope(chain, db, slot, blockRoot))
149
+ ) {
150
+ continue;
151
+ }
152
+ yield {
153
+ slot,
154
+ blockRoot,
155
+ unavailabilityBlockRoot: chain.config.getForkSeq(slot) >= ForkSeq.gloas ? (blockRoot ?? undefined) : undefined,
156
+ finalized: true,
157
+ };
158
+ }
159
+
160
+ for (let i = headChain.length - 1; i >= 0; i--) {
161
+ const block = headChain[i];
162
+ if (block.slot >= endSlot) break;
163
+ if (block.slot <= archiveMaxSlot || block.slot < startSlot || block.payloadStatus !== PayloadStatus.FULL) continue;
164
+ yield {
165
+ slot: block.slot,
166
+ blockRoot: block.blockRoot,
167
+ unavailabilityBlockRoot: block.blockRoot,
168
+ finalized: block.slot <= finalizedSlot,
169
+ };
170
+ }
171
+ }
172
+
173
+ /** Copy roots synchronously so serving a request never retains a head-state reference across I/O. */
174
+ function getRecentCanonicalRoots(chain: IBeaconChain, startSlot: number, endSlot: number): Map<number, RootHex | null> {
175
+ const roots = new Map<number, RootHex | null>();
176
+ if (startSlot >= endSlot) return roots;
177
+ const state = chain.getHeadState();
178
+ // Skipped slots repeat the prior root. At the oldest ring entry, that prior root is unavailable.
179
+ const start = Math.max(startSlot, state.slot - SLOTS_PER_HISTORICAL_ROOT + 1, 1);
180
+ const end = Math.min(endSlot, state.slot);
181
+ if (start >= end) return roots;
182
+ let previousRoot = toRootHex(state.getBlockRootAtSlot(start - 1));
183
+ for (let slot = start; slot < end; slot++) {
184
+ const root = toRootHex(state.getBlockRootAtSlot(slot));
185
+ roots.set(slot, root === previousRoot ? null : root);
186
+ previousRoot = root;
187
+ }
188
+ return roots;
189
+ }
190
+
191
+ async function hasExecutionPayloadEnvelope(
192
+ chain: IBeaconChain,
193
+ db: IBeaconDb,
194
+ slot: number,
195
+ blockRoot: RootHex | null
196
+ ): Promise<boolean> {
197
+ if (blockRoot !== null) {
198
+ if (chain.seenPayloadEnvelopeInputCache.hasPayload(blockRoot)) return true;
199
+ if ((await db.executionPayloadEnvelope.getBinary(fromHex(blockRoot))) !== null) return true;
167
200
  }
201
+ return (await db.executionPayloadEnvelopeArchive.getBinary(slot)) !== null;
168
202
  }
169
203
 
170
204
  export function validateDataColumnSidecarsByRangeRequest(
@@ -81,12 +81,14 @@ export async function* onDataColumnSidecarsByRoot(
81
81
  }
82
82
 
83
83
  if (unavailableColumnIndices.length) {
84
+ const finalized = !block || (await db.blockArchive.getSlotByRoot(blockRoot)) === slot;
84
85
  await handleColumnSidecarUnavailability({
85
86
  chain,
86
87
  db,
87
88
  metrics: chain.metrics,
88
89
  slot,
89
90
  blockRoot,
91
+ finalized,
90
92
  unavailableColumnIndices,
91
93
  requestedColumns,
92
94
  availableColumns,
@@ -16,12 +16,14 @@ export async function handleColumnSidecarUnavailability({
16
16
  availableColumns,
17
17
  slot,
18
18
  blockRoot,
19
+ finalized,
19
20
  }: {
20
21
  chain: IBeaconChain;
21
22
  db: IBeaconDb;
22
23
  metrics: Metrics | null;
23
24
  slot: Slot;
24
25
  blockRoot?: Uint8Array;
26
+ finalized: boolean;
25
27
  unavailableColumnIndices: ColumnIndex[];
26
28
  requestedColumns: ColumnIndex[];
27
29
  availableColumns: ColumnIndex[];
@@ -38,17 +40,23 @@ export async function handleColumnSidecarUnavailability({
38
40
 
39
41
  chain.logger.debug("dataColumnSidecar requested unavailable", logData);
40
42
 
41
- // Post-gloas, columns exist only for FULL blocks; a finalized block is FULL if its envelope was
42
- // archived. Bid blobsCount is unreliable here since an EMPTY block's bid may still commit to blobs
43
- if (blockRoot === undefined && chain.config.getForkSeq(slot) >= ForkSeq.gloas) {
44
- const envelopeBytes = await db.executionPayloadEnvelopeArchive.getBinary(slot);
45
- if (!envelopeBytes) return;
43
+ // Post-gloas, columns exist only for FULL blocks. The envelope may still be hot while finalization
44
+ // archiving is in progress. Bid blobsCount is unreliable since an EMPTY block's bid may still commit to blobs.
45
+ if (chain.config.getForkSeq(slot) >= ForkSeq.gloas) {
46
+ const hasCachedEnvelope = blockRoot ? chain.seenPayloadEnvelopeInputCache.hasPayload(toRootHex(blockRoot)) : false;
47
+ if (!hasCachedEnvelope) {
48
+ const hotEnvelopeBytes = blockRoot ? await db.executionPayloadEnvelope.getBinary(blockRoot) : null;
49
+ const envelopeBytes =
50
+ hotEnvelopeBytes ?? (finalized ? await db.executionPayloadEnvelopeArchive.getBinary(slot) : null);
51
+ if (!envelopeBytes) return;
52
+ }
46
53
  }
47
54
 
48
- const blockBytes = blockRoot ? await db.block.getBinary(blockRoot) : await db.blockArchive.getBinary(slot);
55
+ const hotBlockBytes = blockRoot ? await db.block.getBinary(blockRoot) : null;
56
+ const blockBytes = hotBlockBytes ?? (finalized ? await db.blockArchive.getBinary(slot) : null);
49
57
  if (!blockBytes) {
50
58
  chain.logger.verbose(
51
- `Expected ${blockRoot ? "unfinalized" : "finalized"} block not found while handling unavailable dataColumnSidecar`,
59
+ `Expected ${finalized ? "finalized" : "unfinalized"} block not found while handling unavailable dataColumnSidecar`,
52
60
  {
53
61
  slot,
54
62
  blockRoot: blockRoot ? toRootHex(blockRoot) : "unknown",
@@ -54,6 +54,7 @@ export type BeaconNodeInitModules = {
54
54
  processShutdownCallback: ProcessShutdownCallback;
55
55
  privateKey: PrivateKey;
56
56
  dataDir: string;
57
+ dataColumnDir: string;
57
58
  peerStoreDir?: string;
58
59
  anchorState: IBeaconStateView;
59
60
  isAnchorStateFinalized: boolean;
@@ -156,6 +157,7 @@ export class BeaconNode {
156
157
  processShutdownCallback,
157
158
  privateKey,
158
159
  dataDir,
160
+ dataColumnDir,
159
161
  peerStoreDir,
160
162
  anchorState,
161
163
  isAnchorStateFinalized,
@@ -181,7 +183,7 @@ export class BeaconNode {
181
183
  metrics = createMetrics(opts.metrics, anchorState.genesisTime, metricsRegistries);
182
184
  initBeaconMetrics(metrics, anchorState);
183
185
  // Since the db is instantiated before this, metrics must be injected manually afterwards
184
- db.setMetrics(metrics.db);
186
+ db.setMetrics(metrics.db, metrics.flatFileStore);
185
187
  signal.addEventListener("abort", metrics.close, {once: true});
186
188
  }
187
189
 
@@ -198,6 +200,8 @@ export class BeaconNode {
198
200
 
199
201
  const clock = new Clock({config, genesisTime: anchorState.genesisTime, signal});
200
202
 
203
+ await db.init();
204
+
201
205
  // Prune hot db repos
202
206
  // TODO: Should this call be awaited?
203
207
  await db.pruneHotDb();
@@ -244,6 +248,7 @@ export class BeaconNode {
244
248
  clock,
245
249
  pubkeyCache,
246
250
  dataDir,
251
+ dataColumnDir,
247
252
  db,
248
253
  dbName: opts.db.name,
249
254
  logger: logger.child({module: LoggerModule.chain}),
@@ -754,7 +754,7 @@ export function getParentBlockRootFromSignedExecutionPayloadBidSerialized(data:
754
754
  *
755
755
  * If the high bytes are not zero, return null
756
756
  */
757
- function getSlotFromOffset(data: Uint8Array, offset: number): Slot | null {
757
+ export function getSlotFromOffset(data: Uint8Array, offset: number): Slot | null {
758
758
  return checkSlotHighBytes(data, offset) ? getSlotFromOffsetTrusted(data, offset) : null;
759
759
  }
760
760