@lodestar/beacon-node 1.48.0-dev.b71ab45af3 → 1.48.0-dev.bb3ffba9e9
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.
- package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/blocks/index.js +4 -4
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +39 -39
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/archiveStore/archiveStore.d.ts +1 -0
- package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
- package/lib/chain/archiveStore/archiveStore.js +1 -0
- package/lib/chain/archiveStore/archiveStore.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/historicalStateRegen.d.ts.map +1 -1
- package/lib/chain/archiveStore/historicalState/historicalStateRegen.js +1 -0
- package/lib/chain/archiveStore/historicalState/historicalStateRegen.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/metrics.d.ts.map +1 -1
- package/lib/chain/archiveStore/historicalState/metrics.js +12 -1
- package/lib/chain/archiveStore/historicalState/metrics.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/types.d.ts +2 -0
- package/lib/chain/archiveStore/historicalState/types.d.ts.map +1 -1
- package/lib/chain/archiveStore/historicalState/types.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/worker.js +4 -1
- package/lib/chain/archiveStore/historicalState/worker.js.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.js +31 -34
- package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +0 -13
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/index.d.ts.map +1 -1
- package/lib/chain/blocks/index.js +3 -2
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/writeBlockInputToDb.d.ts.map +1 -1
- package/lib/chain/blocks/writeBlockInputToDb.js +9 -16
- package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
- package/lib/chain/chain.d.ts +9 -3
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +127 -73
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/emitter.d.ts +1 -1
- package/lib/chain/emitter.js +1 -1
- package/lib/chain/errors/blockError.d.ts +19 -7
- package/lib/chain/errors/blockError.d.ts.map +1 -1
- package/lib/chain/errors/blockError.js +2 -2
- package/lib/chain/errors/blockError.js.map +1 -1
- package/lib/chain/errors/dataColumnSidecarError.d.ts +15 -2
- package/lib/chain/errors/dataColumnSidecarError.d.ts.map +1 -1
- package/lib/chain/errors/dataColumnSidecarError.js.map +1 -1
- package/lib/chain/errors/executionPayloadEnvelope.d.ts +10 -11
- package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/errors/executionPayloadEnvelope.js +0 -2
- package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
- package/lib/chain/interface.d.ts +2 -2
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +13 -13
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +46 -64
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/validation/block.d.ts.map +1 -1
- package/lib/chain/validation/block.js +35 -54
- package/lib/chain/validation/block.js.map +1 -1
- package/lib/chain/validation/dataColumnSidecar.d.ts.map +1 -1
- package/lib/chain/validation/dataColumnSidecar.js +38 -13
- package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.js +10 -29
- package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
- package/lib/chain/validatorMonitor.d.ts +7 -1
- package/lib/chain/validatorMonitor.d.ts.map +1 -1
- package/lib/chain/validatorMonitor.js +22 -5
- package/lib/chain/validatorMonitor.js.map +1 -1
- package/lib/db/beacon.d.ts +14 -5
- package/lib/db/beacon.d.ts.map +1 -1
- package/lib/db/beacon.js +17 -3
- package/lib/db/beacon.js.map +1 -1
- package/lib/db/dataColumnStore.d.ts +39 -0
- package/lib/db/dataColumnStore.d.ts.map +1 -0
- package/lib/db/dataColumnStore.js +81 -0
- package/lib/db/dataColumnStore.js.map +1 -0
- package/lib/db/flatFileStore/atomicWrite.d.ts +8 -0
- package/lib/db/flatFileStore/atomicWrite.d.ts.map +1 -0
- package/lib/db/flatFileStore/atomicWrite.js +61 -0
- package/lib/db/flatFileStore/atomicWrite.js.map +1 -0
- package/lib/db/flatFileStore/dcolFormat.d.ts +90 -0
- package/lib/db/flatFileStore/dcolFormat.d.ts.map +1 -0
- package/lib/db/flatFileStore/dcolFormat.js +286 -0
- package/lib/db/flatFileStore/dcolFormat.js.map +1 -0
- package/lib/db/flatFileStore/errors.d.ts +82 -0
- package/lib/db/flatFileStore/errors.d.ts.map +1 -0
- package/lib/db/flatFileStore/errors.js +32 -0
- package/lib/db/flatFileStore/errors.js.map +1 -0
- package/lib/db/flatFileStore/flatFileStore.d.ts +41 -0
- package/lib/db/flatFileStore/flatFileStore.d.ts.map +1 -0
- package/lib/db/flatFileStore/flatFileStore.js +251 -0
- package/lib/db/flatFileStore/flatFileStore.js.map +1 -0
- package/lib/db/flatFileStore/index.d.ts +4 -0
- package/lib/db/flatFileStore/index.d.ts.map +1 -0
- package/lib/db/flatFileStore/index.js +2 -0
- package/lib/db/flatFileStore/index.js.map +1 -0
- package/lib/db/flatFileStore/interface.d.ts +25 -0
- package/lib/db/flatFileStore/interface.d.ts.map +1 -0
- package/lib/db/flatFileStore/interface.js +2 -0
- package/lib/db/flatFileStore/interface.js.map +1 -0
- package/lib/db/flatFileStore/metrics.d.ts +24 -0
- package/lib/db/flatFileStore/metrics.d.ts.map +1 -0
- package/lib/db/flatFileStore/metrics.js +25 -0
- package/lib/db/flatFileStore/metrics.js.map +1 -0
- package/lib/db/flatFileStore/path.d.ts +9 -0
- package/lib/db/flatFileStore/path.d.ts.map +1 -0
- package/lib/db/flatFileStore/path.js +21 -0
- package/lib/db/flatFileStore/path.js.map +1 -0
- package/lib/db/flatFileStore/slotIndex.d.ts +14 -0
- package/lib/db/flatFileStore/slotIndex.d.ts.map +1 -0
- package/lib/db/flatFileStore/slotIndex.js +46 -0
- package/lib/db/flatFileStore/slotIndex.js.map +1 -0
- package/lib/db/flatFileStore/snappy.d.ts +3 -0
- package/lib/db/flatFileStore/snappy.d.ts.map +1 -0
- package/lib/db/flatFileStore/snappy.js +12 -0
- package/lib/db/flatFileStore/snappy.js.map +1 -0
- package/lib/db/index.d.ts +1 -0
- package/lib/db/index.d.ts.map +1 -1
- package/lib/db/interface.d.ts +8 -1
- package/lib/db/interface.d.ts.map +1 -1
- package/lib/db/repositories/blockArchive.d.ts +8 -1
- package/lib/db/repositories/blockArchive.d.ts.map +1 -1
- package/lib/db/repositories/blockArchive.js +65 -28
- package/lib/db/repositories/blockArchive.js.map +1 -1
- package/lib/db/repositories/blockArchiveIndex.d.ts +1 -2
- package/lib/db/repositories/blockArchiveIndex.d.ts.map +1 -1
- package/lib/db/repositories/blockArchiveIndex.js +3 -7
- package/lib/db/repositories/blockArchiveIndex.js.map +1 -1
- package/lib/execution/engine/mock.d.ts.map +1 -1
- package/lib/execution/engine/mock.js +4 -1
- package/lib/execution/engine/mock.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +18 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +55 -1
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/monitoring/clientStats.js +1 -1
- package/lib/monitoring/clientStats.js.map +1 -1
- package/lib/network/gossip/encoding.d.ts.map +1 -1
- package/lib/network/gossip/encoding.js +0 -7
- package/lib/network/gossip/encoding.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +2 -1
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/gossip/topic.js +4 -20
- package/lib/network/gossip/topic.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +6 -7
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/index.d.ts +49 -3
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +232 -98
- package/lib/network/processor/index.js.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +112 -91
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +2 -0
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
- package/lib/network/reqresp/protocols.d.ts.map +1 -1
- package/lib/network/reqresp/protocols.js +6 -22
- package/lib/network/reqresp/protocols.js.map +1 -1
- package/lib/network/reqresp/rateLimit.js +2 -1
- package/lib/network/reqresp/rateLimit.js.map +1 -1
- package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts +2 -1
- package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts.map +1 -1
- package/lib/network/reqresp/utils/dataColumnResponseValidation.js +14 -9
- package/lib/network/reqresp/utils/dataColumnResponseValidation.js.map +1 -1
- package/lib/node/nodejs.d.ts +2 -1
- package/lib/node/nodejs.d.ts.map +1 -1
- package/lib/node/nodejs.js +4 -2
- package/lib/node/nodejs.js.map +1 -1
- package/lib/util/sszBytes.d.ts +6 -0
- package/lib/util/sszBytes.d.ts.map +1 -1
- package/lib/util/sszBytes.js +1 -1
- package/lib/util/sszBytes.js.map +1 -1
- package/package.json +18 -19
- package/src/api/impl/beacon/blocks/index.ts +8 -4
- package/src/api/impl/validator/index.ts +44 -43
- package/src/chain/archiveStore/archiveStore.ts +6 -1
- package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +1 -0
- package/src/chain/archiveStore/historicalState/metrics.ts +14 -1
- package/src/chain/archiveStore/historicalState/types.ts +2 -0
- package/src/chain/archiveStore/historicalState/worker.ts +4 -1
- package/src/chain/archiveStore/utils/archiveBlocks.ts +34 -39
- package/src/chain/blocks/importBlock.ts +0 -13
- package/src/chain/blocks/index.ts +13 -2
- package/src/chain/blocks/writeBlockInputToDb.ts +10 -17
- package/src/chain/chain.ts +149 -80
- package/src/chain/emitter.ts +1 -1
- package/src/chain/errors/blockError.ts +22 -10
- package/src/chain/errors/dataColumnSidecarError.ts +24 -8
- package/src/chain/errors/executionPayloadEnvelope.ts +14 -11
- package/src/chain/interface.ts +2 -2
- package/src/chain/prepareNextSlot.ts +16 -16
- package/src/chain/produceBlock/produceBlockBody.ts +50 -71
- package/src/chain/validation/block.ts +35 -70
- package/src/chain/validation/dataColumnSidecar.ts +47 -14
- package/src/chain/validation/executionPayloadEnvelope.ts +10 -37
- package/src/chain/validatorMonitor.ts +24 -7
- package/src/db/beacon.ts +33 -6
- package/src/db/dataColumnStore.ts +113 -0
- package/src/db/flatFileStore/atomicWrite.ts +65 -0
- package/src/db/flatFileStore/dcolFormat.ts +389 -0
- package/src/db/flatFileStore/errors.ts +53 -0
- package/src/db/flatFileStore/flatFileStore.ts +310 -0
- package/src/db/flatFileStore/index.ts +3 -0
- package/src/db/flatFileStore/interface.ts +21 -0
- package/src/db/flatFileStore/metrics.ts +48 -0
- package/src/db/flatFileStore/path.ts +32 -0
- package/src/db/flatFileStore/slotIndex.ts +56 -0
- package/src/db/flatFileStore/snappy.ts +14 -0
- package/src/db/index.ts +1 -0
- package/src/db/interface.ts +9 -1
- package/src/db/repositories/blockArchive.ts +89 -30
- package/src/db/repositories/blockArchiveIndex.ts +4 -9
- package/src/execution/engine/mock.ts +4 -1
- package/src/metrics/metrics/lodestar.ts +57 -1
- package/src/monitoring/clientStats.ts +1 -1
- package/src/network/gossip/encoding.ts +0 -7
- package/src/network/gossip/topic.ts +3 -25
- package/src/network/processor/gossipHandlers.ts +11 -11
- package/src/network/processor/index.ts +263 -148
- package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +139 -105
- package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +2 -0
- package/src/network/reqresp/protocols.ts +7 -24
- package/src/network/reqresp/rateLimit.ts +2 -1
- package/src/network/reqresp/utils/dataColumnResponseValidation.ts +15 -7
- package/src/node/nodejs.ts +6 -1
- package/src/util/sszBytes.ts +1 -1
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `.dcol` binary format for multi-column data column files.
|
|
3
|
+
*
|
|
4
|
+
* Layout:
|
|
5
|
+
* HEADER (149 bytes):
|
|
6
|
+
* [version: 1B = 0x01]
|
|
7
|
+
* [_reserved: 4B] — zero-filled
|
|
8
|
+
* [bitmap: 16B] — 128-bit bitmap of which columns are present
|
|
9
|
+
* [blockRoot: 32B]
|
|
10
|
+
* [slot: 8B LE] — only low 4 bytes used (matching SSZ convention)
|
|
11
|
+
* [reserved: 88B] — zero-filled, for future use
|
|
12
|
+
*
|
|
13
|
+
* OFFSET TABLE ((N+1) * 4 bytes, where N = popcount(bitmap)):
|
|
14
|
+
* [offset_0:4B BE] start of column 0's compressed data, relative to data region start
|
|
15
|
+
* [offset_1:4B BE] ...
|
|
16
|
+
* [offset_N:4B BE] end of last column = total data region size (sentinel)
|
|
17
|
+
*
|
|
18
|
+
* DATA REGION:
|
|
19
|
+
* [snappy(column_0)][snappy(column_1)]...[snappy(column_N-1)]
|
|
20
|
+
*
|
|
21
|
+
* Each column is independently Snappy block-compressed. To read column at bitmap position `p`:
|
|
22
|
+
* dataStart = HEADER_SIZE + (N+1)*4
|
|
23
|
+
* colStart = dataStart + offsets[p]
|
|
24
|
+
* colEnd = dataStart + offsets[p+1]
|
|
25
|
+
* column = snappy.uncompress(file[colStart:colEnd])
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import {getSlotFromOffset} from "../../util/sszBytes.js";
|
|
29
|
+
import {DataColumnStoreError, DataColumnStoreErrorCode} from "./errors.js";
|
|
30
|
+
import {compress, uncompress} from "./snappy.js";
|
|
31
|
+
|
|
32
|
+
export const DCOL_VERSION = 0x01;
|
|
33
|
+
export const DCOL_HEADER_SIZE = 149;
|
|
34
|
+
const BITMAP_BYTES = 16;
|
|
35
|
+
const BITMAP_BITS = BITMAP_BYTES * 8;
|
|
36
|
+
const BITMAP_OFFSET = 5;
|
|
37
|
+
const BLOCK_ROOT_OFFSET = 21;
|
|
38
|
+
const BLOCK_ROOT_BYTES = 32;
|
|
39
|
+
const SLOT_OFFSET = 53;
|
|
40
|
+
const MAX_SLOT = 0xffffffff;
|
|
41
|
+
|
|
42
|
+
// --- Bitmap helpers ---
|
|
43
|
+
|
|
44
|
+
/** Get bit `i` from a 16-byte Uint8Array bitmap (little-endian bit order within each byte). */
|
|
45
|
+
export function getBit(bitmap: Uint8Array, i: number): boolean {
|
|
46
|
+
const byteIdx = Math.floor(i / 8);
|
|
47
|
+
const bitIdx = i % 8;
|
|
48
|
+
return (bitmap[byteIdx] & (1 << bitIdx)) !== 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Set bit `i` in a 16-byte Uint8Array bitmap. */
|
|
52
|
+
export function setBit(bitmap: Uint8Array, i: number): void {
|
|
53
|
+
const byteIdx = Math.floor(i / 8);
|
|
54
|
+
const bitIdx = i % 8;
|
|
55
|
+
bitmap[byteIdx] |= 1 << bitIdx;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Count set bits in bitmap for positions 0..i-1 (exclusive of i). */
|
|
59
|
+
export function popcount(bitmap: Uint8Array, upTo: number): number {
|
|
60
|
+
let count = 0;
|
|
61
|
+
for (let j = 0; j < upTo; j++) {
|
|
62
|
+
if (getBit(bitmap, j)) count++;
|
|
63
|
+
}
|
|
64
|
+
return count;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Count total set bits in bitmap. */
|
|
68
|
+
export function totalBits(bitmap: Uint8Array): number {
|
|
69
|
+
let count = 0;
|
|
70
|
+
for (let byteIdx = 0; byteIdx < BITMAP_BYTES; byteIdx++) {
|
|
71
|
+
let b = bitmap[byteIdx];
|
|
72
|
+
while (b) {
|
|
73
|
+
count += b & 1;
|
|
74
|
+
b >>= 1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return count;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// --- Header encode/decode ---
|
|
81
|
+
|
|
82
|
+
export interface DcolHeader {
|
|
83
|
+
version: number;
|
|
84
|
+
bitmap: Uint8Array;
|
|
85
|
+
blockRoot: Uint8Array;
|
|
86
|
+
slot: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function encodeDcolHeader(header: DcolHeader): Uint8Array {
|
|
90
|
+
// Defensive checks at the persistence boundary. Normal sidecars are already validated, but silently truncating
|
|
91
|
+
// malformed metadata here would turn an upstream regression into durable on-disk corruption.
|
|
92
|
+
if (header.bitmap.length !== BITMAP_BYTES) {
|
|
93
|
+
throw new DataColumnStoreError(
|
|
94
|
+
{code: DataColumnStoreErrorCode.INVALID_BITMAP_LENGTH, length: header.bitmap.length},
|
|
95
|
+
`Invalid dcol bitmap length: ${header.bitmap.length}`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
if (header.blockRoot.length !== BLOCK_ROOT_BYTES) {
|
|
99
|
+
throw new DataColumnStoreError(
|
|
100
|
+
{code: DataColumnStoreErrorCode.INVALID_BLOCK_ROOT_LENGTH, length: header.blockRoot.length},
|
|
101
|
+
`Invalid dcol block root length: ${header.blockRoot.length}`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (!Number.isInteger(header.slot) || header.slot < 0 || header.slot > MAX_SLOT) {
|
|
105
|
+
throw new DataColumnStoreError(
|
|
106
|
+
{code: DataColumnStoreErrorCode.INVALID_SLOT, slot: header.slot},
|
|
107
|
+
`Invalid dcol slot: ${header.slot}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const buf = new Uint8Array(DCOL_HEADER_SIZE);
|
|
112
|
+
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
113
|
+
|
|
114
|
+
buf[0] = header.version;
|
|
115
|
+
// bytes 1-4 reserved (zero)
|
|
116
|
+
buf.set(header.bitmap, BITMAP_OFFSET);
|
|
117
|
+
buf.set(header.blockRoot, BLOCK_ROOT_OFFSET);
|
|
118
|
+
|
|
119
|
+
// Slot as 8-byte LE (matching SSZ convention used throughout Lodestar).
|
|
120
|
+
// High 4 bytes are always zero for practical slot values (4B covers ~1634 years).
|
|
121
|
+
view.setUint32(SLOT_OFFSET, header.slot >>> 0, true);
|
|
122
|
+
// bytes SLOT_OFFSET+4..+7 are already zero
|
|
123
|
+
|
|
124
|
+
// reserved bytes are already zero
|
|
125
|
+
return buf;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function parseDcolHeader(data: Uint8Array): DcolHeader {
|
|
129
|
+
if (data.length < DCOL_HEADER_SIZE) {
|
|
130
|
+
throw new DataColumnStoreError(
|
|
131
|
+
{code: DataColumnStoreErrorCode.FILE_TOO_SMALL, actual: data.length, minimum: DCOL_HEADER_SIZE},
|
|
132
|
+
`dcol file too small: ${data.length} < ${DCOL_HEADER_SIZE}`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const version = data[0];
|
|
137
|
+
if (version !== DCOL_VERSION) {
|
|
138
|
+
throw new DataColumnStoreError(
|
|
139
|
+
{code: DataColumnStoreErrorCode.UNSUPPORTED_VERSION, version},
|
|
140
|
+
`Unsupported dcol version: ${version}`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const bitmap = Uint8Array.prototype.slice.call(data, BITMAP_OFFSET, BITMAP_OFFSET + BITMAP_BYTES) as Uint8Array;
|
|
145
|
+
const blockRoot = Uint8Array.prototype.slice.call(data, BLOCK_ROOT_OFFSET, BLOCK_ROOT_OFFSET + 32) as Uint8Array;
|
|
146
|
+
|
|
147
|
+
const slot = getSlotFromOffset(data, SLOT_OFFSET);
|
|
148
|
+
if (slot === null) {
|
|
149
|
+
throw new DataColumnStoreError(
|
|
150
|
+
{code: DataColumnStoreErrorCode.SLOT_OUT_OF_RANGE},
|
|
151
|
+
"dcol slot exceeds 4-byte range"
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {version, bitmap, blockRoot, slot};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// --- Column read helpers ---
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Read a single column by its column index from a dcol file buffer.
|
|
162
|
+
* Returns the uncompressed column data, or null if column is not present.
|
|
163
|
+
*
|
|
164
|
+
* Note: Production reads use getColumnByteRange() + fd.read() for targeted I/O.
|
|
165
|
+
* This function is a convenience for tests and mergeDcolColumns.
|
|
166
|
+
*/
|
|
167
|
+
export function readColumn(fileData: Uint8Array, header: DcolHeader, index: number): Uint8Array | null {
|
|
168
|
+
const {dataStart, offsets} = parseDcolOffsetsFromFile(fileData, header);
|
|
169
|
+
if (!getBit(header.bitmap, index)) return null;
|
|
170
|
+
|
|
171
|
+
const p = popcount(header.bitmap, index);
|
|
172
|
+
const colStart = dataStart + offsets[p];
|
|
173
|
+
const colEnd = dataStart + offsets[p + 1];
|
|
174
|
+
const compressed = fileData.subarray(colStart, colEnd);
|
|
175
|
+
|
|
176
|
+
return uncompress(compressed);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Read all columns from a dcol file buffer.
|
|
181
|
+
* Returns an array of {index, data} for each present column.
|
|
182
|
+
*/
|
|
183
|
+
export function readAllColumns(fileData: Uint8Array, header: DcolHeader): {index: number; data: Uint8Array}[] {
|
|
184
|
+
const result: {index: number; data: Uint8Array}[] = [];
|
|
185
|
+
const {dataStart, offsets} = parseDcolOffsetsFromFile(fileData, header);
|
|
186
|
+
|
|
187
|
+
let pos = 0;
|
|
188
|
+
for (let i = 0; i < 128; i++) {
|
|
189
|
+
if (getBit(header.bitmap, i)) {
|
|
190
|
+
const colStart = dataStart + offsets[pos];
|
|
191
|
+
const colEnd = dataStart + offsets[pos + 1];
|
|
192
|
+
const compressed = fileData.subarray(colStart, colEnd);
|
|
193
|
+
result.push({index: i, data: uncompress(compressed)});
|
|
194
|
+
pos++;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// --- Targeted read helpers (for fd.read()-based access) ---
|
|
202
|
+
|
|
203
|
+
export interface ColumnByteRange {
|
|
204
|
+
/** Absolute file offset to start reading */
|
|
205
|
+
offset: number;
|
|
206
|
+
/** Number of compressed bytes to read */
|
|
207
|
+
length: number;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Compute the file byte range for a specific column, using the header
|
|
212
|
+
* and offset table bytes. Returns null if column is absent.
|
|
213
|
+
*/
|
|
214
|
+
export function getColumnByteRange(header: DcolHeader, offsets: number[], index: number): ColumnByteRange | null {
|
|
215
|
+
if (!getBit(header.bitmap, index)) return null;
|
|
216
|
+
|
|
217
|
+
// popcount(bitmap, index) counts bits in [0, index) — the number of columns
|
|
218
|
+
// stored before this one, giving the correct offset table position.
|
|
219
|
+
const p = popcount(header.bitmap, index);
|
|
220
|
+
const N = totalBits(header.bitmap);
|
|
221
|
+
const dataStart = DCOL_HEADER_SIZE + offsetTableSize(N);
|
|
222
|
+
const colStart = offsets[p];
|
|
223
|
+
const colEnd = offsets[p + 1];
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
offset: dataStart + colStart,
|
|
227
|
+
length: colEnd - colStart,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function parseDcolOffsets(offsetTable: Uint8Array, columnCount: number, dataLength: number): number[] {
|
|
232
|
+
const expectedTableSize = offsetTableSize(columnCount);
|
|
233
|
+
if (offsetTable.length !== expectedTableSize || dataLength < 0) {
|
|
234
|
+
throw invalidOffsetTable("size");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const view = new DataView(offsetTable.buffer, offsetTable.byteOffset, offsetTable.byteLength);
|
|
238
|
+
const offsets = Array.from({length: columnCount + 1}, (_, index) => view.getUint32(index * 4, false));
|
|
239
|
+
if (offsets[0] !== 0) {
|
|
240
|
+
throw invalidOffsetTable("first offset is not zero");
|
|
241
|
+
}
|
|
242
|
+
for (let i = 1; i < offsets.length; i++) {
|
|
243
|
+
if (offsets[i] < offsets[i - 1]) {
|
|
244
|
+
throw invalidOffsetTable("offsets are not monotonic");
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (offsets[columnCount] !== dataLength) {
|
|
248
|
+
throw invalidOffsetTable("terminal offset does not match data length");
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return offsets;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function parseDcolOffsetsFromFile(fileData: Uint8Array, header: DcolHeader): {dataStart: number; offsets: number[]} {
|
|
255
|
+
const columnCount = totalBits(header.bitmap);
|
|
256
|
+
const tableSize = offsetTableSize(columnCount);
|
|
257
|
+
const dataStart = DCOL_HEADER_SIZE + tableSize;
|
|
258
|
+
const offsetTable = fileData.subarray(DCOL_HEADER_SIZE, dataStart);
|
|
259
|
+
return {
|
|
260
|
+
dataStart,
|
|
261
|
+
offsets: parseDcolOffsets(offsetTable, columnCount, fileData.length - dataStart),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function invalidOffsetTable(reason: string): DataColumnStoreError {
|
|
266
|
+
return new DataColumnStoreError(
|
|
267
|
+
{code: DataColumnStoreErrorCode.INVALID_OFFSET_TABLE, reason},
|
|
268
|
+
`Invalid dcol offset table: ${reason}`
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Size of the offset table in bytes for N present columns. */
|
|
273
|
+
export function offsetTableSize(N: number): number {
|
|
274
|
+
return (N + 1) * 4;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// --- Full file encode/decode ---
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Encode a complete dcol file from columns.
|
|
281
|
+
* Each column is independently Snappy block-compressed.
|
|
282
|
+
*/
|
|
283
|
+
export function encodeDcolFile(
|
|
284
|
+
blockRoot: Uint8Array,
|
|
285
|
+
slot: number,
|
|
286
|
+
columns: {index: number; data: Uint8Array}[]
|
|
287
|
+
): Uint8Array {
|
|
288
|
+
if (columns.length === 0) {
|
|
289
|
+
throw new DataColumnStoreError(
|
|
290
|
+
{code: DataColumnStoreErrorCode.EMPTY_COLUMNS},
|
|
291
|
+
"Cannot encode dcol file with zero columns"
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Defensive validation keeps bitmap membership and offset table entries in one-to-one correspondence.
|
|
296
|
+
const seenIndices = new Set<number>();
|
|
297
|
+
for (const {index} of columns) {
|
|
298
|
+
if (!Number.isInteger(index) || index < 0 || index >= BITMAP_BITS) {
|
|
299
|
+
throw new DataColumnStoreError(
|
|
300
|
+
{code: DataColumnStoreErrorCode.INVALID_COLUMN_INDEX, index},
|
|
301
|
+
`Invalid dcol column index: ${index}`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
if (seenIndices.has(index)) {
|
|
305
|
+
throw new DataColumnStoreError(
|
|
306
|
+
{code: DataColumnStoreErrorCode.DUPLICATE_COLUMN_INDEX, index},
|
|
307
|
+
`Duplicate dcol column index: ${index}`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
seenIndices.add(index);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const bitmap = new Uint8Array(BITMAP_BYTES);
|
|
314
|
+
|
|
315
|
+
// Sort by index for deterministic ordering
|
|
316
|
+
const sorted = [...columns].sort((a, b) => a.index - b.index);
|
|
317
|
+
|
|
318
|
+
for (const col of sorted) {
|
|
319
|
+
setBit(bitmap, col.index);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Compress each column
|
|
323
|
+
const compressed: Uint8Array[] = [];
|
|
324
|
+
for (const col of sorted) {
|
|
325
|
+
compressed.push(compress(col.data));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Build offset table
|
|
329
|
+
const N = sorted.length;
|
|
330
|
+
const oTableSize = offsetTableSize(N);
|
|
331
|
+
const offsets = new Uint8Array(oTableSize);
|
|
332
|
+
const offsetView = new DataView(offsets.buffer, offsets.byteOffset, offsets.byteLength);
|
|
333
|
+
|
|
334
|
+
let cumulative = 0;
|
|
335
|
+
for (let i = 0; i < N; i++) {
|
|
336
|
+
offsetView.setUint32(i * 4, cumulative, false); // BE
|
|
337
|
+
cumulative += compressed[i].length;
|
|
338
|
+
}
|
|
339
|
+
offsetView.setUint32(N * 4, cumulative, false); // sentinel: end of last column
|
|
340
|
+
|
|
341
|
+
// Assemble: header + offset table + compressed data
|
|
342
|
+
const header = encodeDcolHeader({version: DCOL_VERSION, bitmap, blockRoot, slot});
|
|
343
|
+
const totalSize = DCOL_HEADER_SIZE + oTableSize + cumulative;
|
|
344
|
+
const result = new Uint8Array(totalSize);
|
|
345
|
+
|
|
346
|
+
result.set(header, 0);
|
|
347
|
+
result.set(offsets, DCOL_HEADER_SIZE);
|
|
348
|
+
|
|
349
|
+
let dataOffset = DCOL_HEADER_SIZE + oTableSize;
|
|
350
|
+
for (const chunk of compressed) {
|
|
351
|
+
result.set(chunk, dataOffset);
|
|
352
|
+
dataOffset += chunk.length;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return result;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Merge new columns into an existing dcol file buffer.
|
|
360
|
+
*/
|
|
361
|
+
export function mergeDcolColumns(existing: Uint8Array, newColumns: {index: number; data: Uint8Array}[]): Uint8Array {
|
|
362
|
+
const header = parseDcolHeader(existing);
|
|
363
|
+
|
|
364
|
+
const existingCols = readAllColumns(existing, header);
|
|
365
|
+
|
|
366
|
+
const allColumns: Map<number, Uint8Array> = new Map();
|
|
367
|
+
for (const col of existingCols) {
|
|
368
|
+
allColumns.set(col.index, col.data);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Defensively reject ambiguous batches while still allowing a new column to overwrite its previously stored value.
|
|
372
|
+
const newIndices = new Set<number>();
|
|
373
|
+
for (const col of newColumns) {
|
|
374
|
+
if (newIndices.has(col.index)) {
|
|
375
|
+
throw new DataColumnStoreError(
|
|
376
|
+
{code: DataColumnStoreErrorCode.DUPLICATE_COLUMN_INDEX, index: col.index},
|
|
377
|
+
`Duplicate dcol column index: ${col.index}`
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
newIndices.add(col.index);
|
|
381
|
+
allColumns.set(col.index, col.data);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const cols = Array.from(allColumns.entries())
|
|
385
|
+
.sort(([a], [b]) => a - b)
|
|
386
|
+
.map(([index, data]) => ({index, data}));
|
|
387
|
+
|
|
388
|
+
return encodeDcolFile(header.blockRoot, header.slot, cols);
|
|
389
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {LodestarError} from "@lodestar/utils";
|
|
2
|
+
|
|
3
|
+
export enum DataColumnStoreErrorCode {
|
|
4
|
+
INVALID_ROOT = "DATA_COLUMN_STORE_INVALID_ROOT",
|
|
5
|
+
INVALID_SLOT = "DATA_COLUMN_STORE_INVALID_SLOT",
|
|
6
|
+
SLOT_OUT_OF_RANGE = "DATA_COLUMN_STORE_SLOT_OUT_OF_RANGE",
|
|
7
|
+
INVALID_BITMAP_LENGTH = "DATA_COLUMN_STORE_INVALID_BITMAP_LENGTH",
|
|
8
|
+
INVALID_BLOCK_ROOT_LENGTH = "DATA_COLUMN_STORE_INVALID_BLOCK_ROOT_LENGTH",
|
|
9
|
+
FILE_TOO_SMALL = "DATA_COLUMN_STORE_FILE_TOO_SMALL",
|
|
10
|
+
UNSUPPORTED_VERSION = "DATA_COLUMN_STORE_UNSUPPORTED_VERSION",
|
|
11
|
+
EMPTY_COLUMNS = "DATA_COLUMN_STORE_EMPTY_COLUMNS",
|
|
12
|
+
INVALID_COLUMN_INDEX = "DATA_COLUMN_STORE_INVALID_COLUMN_INDEX",
|
|
13
|
+
DUPLICATE_COLUMN_INDEX = "DATA_COLUMN_STORE_DUPLICATE_COLUMN_INDEX",
|
|
14
|
+
INVALID_OFFSET_TABLE = "DATA_COLUMN_STORE_INVALID_OFFSET_TABLE",
|
|
15
|
+
SLOT_PRUNED = "DATA_COLUMN_STORE_SLOT_PRUNED",
|
|
16
|
+
SLOT_MISMATCH = "DATA_COLUMN_STORE_SLOT_MISMATCH",
|
|
17
|
+
ROOT_MISMATCH = "DATA_COLUMN_STORE_ROOT_MISMATCH",
|
|
18
|
+
UNEXPECTED_EOF = "DATA_COLUMN_STORE_UNEXPECTED_EOF",
|
|
19
|
+
OPERATION_FAILED = "DATA_COLUMN_STORE_OPERATION_FAILED",
|
|
20
|
+
BATCH_DELETE_FAILED = "DATA_COLUMN_STORE_BATCH_DELETE_FAILED",
|
|
21
|
+
STARTUP_FAILED = "DATA_COLUMN_STORE_STARTUP_FAILED",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type DataColumnStoreErrorType =
|
|
25
|
+
| {code: DataColumnStoreErrorCode.INVALID_ROOT; root: string}
|
|
26
|
+
| {code: DataColumnStoreErrorCode.INVALID_SLOT; slot: number}
|
|
27
|
+
| {code: DataColumnStoreErrorCode.SLOT_OUT_OF_RANGE}
|
|
28
|
+
| {code: DataColumnStoreErrorCode.INVALID_BITMAP_LENGTH; length: number}
|
|
29
|
+
| {code: DataColumnStoreErrorCode.INVALID_BLOCK_ROOT_LENGTH; length: number}
|
|
30
|
+
| {code: DataColumnStoreErrorCode.FILE_TOO_SMALL; actual: number; minimum: number}
|
|
31
|
+
| {code: DataColumnStoreErrorCode.UNSUPPORTED_VERSION; version: number}
|
|
32
|
+
| {code: DataColumnStoreErrorCode.EMPTY_COLUMNS}
|
|
33
|
+
| {code: DataColumnStoreErrorCode.INVALID_COLUMN_INDEX; index: number}
|
|
34
|
+
| {code: DataColumnStoreErrorCode.DUPLICATE_COLUMN_INDEX; index: number}
|
|
35
|
+
| {code: DataColumnStoreErrorCode.INVALID_OFFSET_TABLE; reason: string}
|
|
36
|
+
| {code: DataColumnStoreErrorCode.SLOT_PRUNED; slot: number; minRetainedSlot: number}
|
|
37
|
+
| {code: DataColumnStoreErrorCode.SLOT_MISMATCH; headerSlot: number; pathSlot: number}
|
|
38
|
+
| {code: DataColumnStoreErrorCode.ROOT_MISMATCH; headerRoot: string; pathRoot: string}
|
|
39
|
+
| {code: DataColumnStoreErrorCode.UNEXPECTED_EOF; offset: number}
|
|
40
|
+
| {code: DataColumnStoreErrorCode.OPERATION_FAILED; operation: string}
|
|
41
|
+
| {code: DataColumnStoreErrorCode.BATCH_DELETE_FAILED; failures: number}
|
|
42
|
+
| {code: DataColumnStoreErrorCode.STARTUP_FAILED};
|
|
43
|
+
|
|
44
|
+
export class DataColumnStoreError extends LodestarError<DataColumnStoreErrorType> {
|
|
45
|
+
constructor(type: DataColumnStoreErrorType, message?: string, cause?: unknown) {
|
|
46
|
+
super(type, message);
|
|
47
|
+
this.cause = cause;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function isFsNotFoundError(error: unknown): boolean {
|
|
52
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
53
|
+
}
|