@secondlayer/shared 7.8.0 → 7.9.0
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/dist/src/archive/derived-stage-report.d.ts +99 -0
- package/dist/src/archive/derived-stage-report.js +64 -0
- package/dist/src/archive/derived-stage-report.js.map +10 -0
- package/dist/src/archive/disk-guard.d.ts +46 -0
- package/dist/src/archive/disk-guard.js +69 -0
- package/dist/src/archive/disk-guard.js.map +10 -0
- package/dist/src/archive/per-height-digest.d.ts +99 -0
- package/dist/src/archive/per-height-digest.js +194 -0
- package/dist/src/archive/per-height-digest.js.map +11 -0
- package/dist/src/archive/range-digest.d.ts +7 -0
- package/dist/src/archive/semantic-digest-builder.d.ts +1094 -0
- package/dist/src/archive/semantic-digest-builder.js +302 -0
- package/dist/src/archive/semantic-digest-builder.js.map +11 -0
- package/dist/src/archive/semantic-digest.d.ts +131 -0
- package/dist/src/archive/semantic-digest.js +143 -0
- package/dist/src/archive/semantic-digest.js.map +10 -0
- package/dist/src/db/index.d.ts +11 -1
- package/dist/src/db/index.js +26 -1
- package/dist/src/db/index.js.map +6 -5
- package/dist/src/db/queries/chain-reorgs.d.ts +7 -0
- package/dist/src/db/queries/chain-reorgs.js +25 -1
- package/dist/src/db/queries/chain-reorgs.js.map +6 -5
- package/dist/src/db/queries/contracts.d.ts +7 -0
- package/dist/src/db/queries/instance.d.ts +1038 -0
- package/dist/src/db/queries/instance.js +92 -0
- package/dist/src/db/queries/instance.js.map +10 -0
- package/dist/src/db/queries/integrity.d.ts +7 -0
- package/dist/src/db/queries/subgraph-gaps.d.ts +7 -0
- package/dist/src/db/queries/subgraph-operations.d.ts +7 -0
- package/dist/src/db/queries/subgraphs.d.ts +10 -1
- package/dist/src/db/queries/subgraphs.js +47 -23
- package/dist/src/db/queries/subgraphs.js.map +7 -7
- package/dist/src/db/queries/subscriptions.d.ts +7 -0
- package/dist/src/db/queries/subscriptions.js +47 -31
- package/dist/src/db/queries/subscriptions.js.map +5 -5
- package/dist/src/db/schema.d.ts +8 -1
- package/dist/src/index.d.ts +11 -1
- package/dist/src/index.js +26 -1
- package/dist/src/index.js.map +6 -5
- package/dist/src/node/client.d.ts +11 -1
- package/dist/src/node/client.js +42 -4
- package/dist/src/node/client.js.map +3 -3
- package/dist/src/node/local-client.d.ts +7 -0
- package/dist/src/node/nakamoto.d.ts +22 -7
- package/dist/src/node/nakamoto.js +46 -5
- package/dist/src/node/nakamoto.js.map +3 -3
- package/migrations/0115_instances.ts +33 -0
- package/package.json +25 -1
package/dist/src/node/client.js
CHANGED
|
@@ -29,6 +29,12 @@ var TIMESTAMP_OFF = 133;
|
|
|
29
29
|
var MINER_SIG_OFF = 141;
|
|
30
30
|
var SIGNER_VEC_OFF = 206;
|
|
31
31
|
var SIG_LEN = 65;
|
|
32
|
+
var PROBLEMATIC_TX_MARKER_LEN = 5;
|
|
33
|
+
var NAKAMOTO_BLOCK_VERSION = 0;
|
|
34
|
+
var NAKAMOTO_BLOCK_VERSION_EPOCH_4 = 1;
|
|
35
|
+
function versionIncludesProblematicTxs(version) {
|
|
36
|
+
return (version & 127) >= NAKAMOTO_BLOCK_VERSION_EPOCH_4;
|
|
37
|
+
}
|
|
32
38
|
function u32(b, off) {
|
|
33
39
|
return new DataView(b.buffer, b.byteOffset, b.byteLength).getUint32(off);
|
|
34
40
|
}
|
|
@@ -39,22 +45,53 @@ function parseNakamotoBlockHeader(raw) {
|
|
|
39
45
|
if (raw.length < PREFIX_LEN + 4) {
|
|
40
46
|
throw new Error("raw block too short for a Nakamoto header");
|
|
41
47
|
}
|
|
48
|
+
const version = raw[0];
|
|
42
49
|
const signerCount = u32(raw, SIGNER_VEC_OFF);
|
|
43
50
|
const sigsStart = SIGNER_VEC_OFF + 4;
|
|
51
|
+
if (sigsStart + signerCount * SIG_LEN > raw.length) {
|
|
52
|
+
throw new Error("raw block truncated in signer_signature vector");
|
|
53
|
+
}
|
|
44
54
|
const signerSignatures = [];
|
|
45
55
|
for (let i = 0;i < signerCount; i++) {
|
|
46
56
|
const off = sigsStart + i * SIG_LEN;
|
|
47
57
|
signerSignatures.push(toHex(raw.subarray(off, off + SIG_LEN)));
|
|
48
58
|
}
|
|
49
59
|
const poxOff = sigsStart + signerCount * SIG_LEN;
|
|
60
|
+
if (poxOff + 6 > raw.length) {
|
|
61
|
+
throw new Error("raw block truncated in pox_treatment");
|
|
62
|
+
}
|
|
50
63
|
const poxDataLen = u32(raw, poxOff + 2);
|
|
51
64
|
const poxEnd = poxOff + 6 + poxDataLen;
|
|
65
|
+
if (poxEnd > raw.length) {
|
|
66
|
+
throw new Error("raw block truncated in pox_treatment data");
|
|
67
|
+
}
|
|
52
68
|
const poxTreatment = raw.subarray(poxOff, poxEnd);
|
|
53
|
-
|
|
69
|
+
let headerEnd = poxEnd;
|
|
70
|
+
const problematicTxs = [];
|
|
71
|
+
if (versionIncludesProblematicTxs(version)) {
|
|
72
|
+
if (poxEnd + 4 > raw.length) {
|
|
73
|
+
throw new Error("raw block truncated in problematic_txs count");
|
|
74
|
+
}
|
|
75
|
+
const markerCount = u32(raw, poxEnd);
|
|
76
|
+
headerEnd = poxEnd + 4 + markerCount * PROBLEMATIC_TX_MARKER_LEN;
|
|
77
|
+
if (headerEnd > raw.length) {
|
|
78
|
+
throw new Error("raw block truncated in problematic_txs list");
|
|
79
|
+
}
|
|
80
|
+
for (let i = 0;i < markerCount; i++) {
|
|
81
|
+
const off = poxEnd + 4 + i * PROBLEMATIC_TX_MARKER_LEN;
|
|
82
|
+
problematicTxs.push({
|
|
83
|
+
txIndex: u32(raw, off),
|
|
84
|
+
category: raw[off + 4] ?? 0
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const markersBytes = raw.subarray(poxEnd, headerEnd);
|
|
89
|
+
const preimage = new Uint8Array(PREFIX_LEN + poxTreatment.length + markersBytes.length);
|
|
54
90
|
preimage.set(raw.subarray(0, PREFIX_LEN), 0);
|
|
55
91
|
preimage.set(poxTreatment, PREFIX_LEN);
|
|
92
|
+
preimage.set(markersBytes, PREFIX_LEN + poxTreatment.length);
|
|
56
93
|
return {
|
|
57
|
-
version
|
|
94
|
+
version,
|
|
58
95
|
chainLength: u64(raw, 1),
|
|
59
96
|
burnSpent: u64(raw, 9),
|
|
60
97
|
consensusHash: toHex(raw.subarray(CONSENSUS_HASH_OFF, CONSENSUS_HASH_OFF + 20)),
|
|
@@ -65,8 +102,9 @@ function parseNakamotoBlockHeader(raw) {
|
|
|
65
102
|
minerSignature: toHex(raw.subarray(MINER_SIG_OFF, MINER_SIG_OFF + SIG_LEN)),
|
|
66
103
|
signerSignatures,
|
|
67
104
|
poxTreatment,
|
|
105
|
+
problematicTxs,
|
|
68
106
|
signerSignatureHashPreimage: preimage,
|
|
69
|
-
headerByteLength:
|
|
107
|
+
headerByteLength: headerEnd
|
|
70
108
|
};
|
|
71
109
|
}
|
|
72
110
|
function nakamotoBlockHash(header) {
|
|
@@ -242,5 +280,5 @@ export {
|
|
|
242
280
|
StacksNodeClient
|
|
243
281
|
};
|
|
244
282
|
|
|
245
|
-
//# debugId=
|
|
283
|
+
//# debugId=1B1DA7B9F8EBA8A564756E2164756E21
|
|
246
284
|
//# sourceMappingURL=client.js.map
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/node/nakamoto.ts", "../src/node/client.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import { createHash } from \"node:crypto\";\n\n/**\n * Nakamoto block-header parsing + consensus hashing for trustless verification.\n *\n * Every constant here is verified bit-exact against mainnet `stacks-node\n * 3.4.0.0.3` (see docs/design/trustless-verification-proofs.md, Appendix). This\n * Building block for transaction-inclusion / block-canonicity proofs: fetch a\n * raw `/v3/blocks/{id}` body, parse the header, and recompute the block_hash,\n * index_block_hash, and tx_merkle_root the chain itself commits to.\n */\n\n/** SHA-512/256 — the hash Stacks uses everywhere (NOT truncated SHA-512). */\nexport function sha512_256(bytes: Uint8Array): Uint8Array {\n\treturn createHash(\"sha512-256\").update(bytes).digest();\n}\n\nconst toHex = (b: Uint8Array): string => Buffer.from(b).toString(\"hex\");\nconst fromHex = (h: string): Uint8Array =>\n\tUint8Array.from(Buffer.from(h.startsWith(\"0x\") ? h.slice(2) : h, \"hex\"));\n\n// Fixed-size header prefix: version..miner_signature, before signer_signature.\nconst PREFIX_LEN = 206;\nconst CONSENSUS_HASH_OFF = 17;\nconst TX_MERKLE_ROOT_OFF = 69;\nconst STATE_INDEX_ROOT_OFF = 101;\nconst TIMESTAMP_OFF = 133;\nconst MINER_SIG_OFF = 141;\nconst SIGNER_VEC_OFF = 206; // u32 count, then 65 bytes per signer\nconst SIG_LEN = 65;\n\nexport interface NakamotoBlockHeader {\n\tversion: number;\n\tchainLength: bigint;\n\tburnSpent: bigint;\n\t/** 20-byte consensus hash (hex). */\n\tconsensusHash: string;\n\t/** 32-byte parent StacksBlockId (hex). */\n\tparentBlockId: string;\n\t/** 32-byte SHA512/256 merkle root over the block's txids (hex). */\n\ttxMerkleRoot: string;\n\t/** 32-byte MARF root after applying this block (hex). */\n\tstateIndexRoot: string;\n\ttimestamp: bigint;\n\t/** 65-byte recoverable ECDSA miner signature (hex). */\n\tminerSignature: string;\n\t/** Per-signer recoverable ECDSA signatures, reward-set order (hex, 65B each). */\n\tsignerSignatures: string[];\n\t/** Full serialized pox_treatment BitVec bytes (u16 bits ‖ u32 len ‖ data). */\n\tpoxTreatment: Uint8Array;\n\t/**\n\t * Exact bytes whose SHA512/256 IS the block_hash / signer_signature_hash:\n\t * the header with the signer_signature vector omitted (header[0:206] ‖ pox).\n\t */\n\tsignerSignatureHashPreimage: Uint8Array;\n\t/** Offset at which the tx `Vec` begins (= total header byte length). */\n\theaderByteLength: number;\n}\n\nfunction u32(b: Uint8Array, off: number): number {\n\treturn new DataView(b.buffer, b.byteOffset, b.byteLength).getUint32(off);\n}\nfunction u64(b: Uint8Array, off: number): bigint {\n\treturn new DataView(b.buffer, b.byteOffset, b.byteLength).getBigUint64(off);\n}\n\n/** Parse the Nakamoto block header from the raw `/v3/blocks` body. */\nexport function parseNakamotoBlockHeader(raw: Uint8Array): NakamotoBlockHeader {\n\tif (raw.length < PREFIX_LEN + 4) {\n\t\tthrow new Error(\"raw block too short for a Nakamoto header\");\n\t}\n\tconst signerCount = u32(raw, SIGNER_VEC_OFF);\n\tconst sigsStart = SIGNER_VEC_OFF + 4;\n\tconst signerSignatures: string[] = [];\n\tfor (let i = 0; i < signerCount; i++) {\n\t\tconst off = sigsStart + i * SIG_LEN;\n\t\tsignerSignatures.push(toHex(raw.subarray(off, off + SIG_LEN)));\n\t}\n\t// pox_treatment: u16 num_bits ‖ u32 data_len ‖ data[data_len].\n\tconst poxOff = sigsStart + signerCount * SIG_LEN;\n\tconst poxDataLen = u32(raw, poxOff + 2);\n\tconst poxEnd = poxOff + 6 + poxDataLen;\n\tconst poxTreatment = raw.subarray(poxOff, poxEnd);\n\n\t// block_hash preimage = header minus signer_signature = prefix[0:206] ‖ pox.\n\tconst preimage = new Uint8Array(PREFIX_LEN + poxTreatment.length);\n\tpreimage.set(raw.subarray(0, PREFIX_LEN), 0);\n\tpreimage.set(poxTreatment, PREFIX_LEN);\n\n\treturn {\n\t\tversion: raw[0],\n\t\tchainLength: u64(raw, 1),\n\t\tburnSpent: u64(raw, 9),\n\t\tconsensusHash: toHex(\n\t\t\traw.subarray(CONSENSUS_HASH_OFF, CONSENSUS_HASH_OFF + 20),\n\t\t),\n\t\tparentBlockId: toHex(raw.subarray(37, 69)),\n\t\ttxMerkleRoot: toHex(\n\t\t\traw.subarray(TX_MERKLE_ROOT_OFF, TX_MERKLE_ROOT_OFF + 32),\n\t\t),\n\t\tstateIndexRoot: toHex(\n\t\t\traw.subarray(STATE_INDEX_ROOT_OFF, STATE_INDEX_ROOT_OFF + 32),\n\t\t),\n\t\ttimestamp: u64(raw, TIMESTAMP_OFF),\n\t\tminerSignature: toHex(raw.subarray(MINER_SIG_OFF, MINER_SIG_OFF + SIG_LEN)),\n\t\tsignerSignatures,\n\t\tpoxTreatment,\n\t\tsignerSignatureHashPreimage: preimage,\n\t\theaderByteLength: poxEnd,\n\t};\n}\n\n/**\n * block_hash (== signer_signature_hash): SHA512/256 over the header with the\n * signer_signature vector omitted. This is what each signer signs.\n */\nexport function nakamotoBlockHash(header: NakamotoBlockHeader): string {\n\treturn toHex(sha512_256(header.signerSignatureHashPreimage));\n}\n\n/** index_block_hash (StacksBlockId) = SHA512/256(block_hash ‖ consensus_hash). */\nexport function nakamotoBlockId(\n\tblockHashHex: string,\n\tconsensusHashHex: string,\n): string {\n\tconst a = fromHex(blockHashHex);\n\tconst b = fromHex(consensusHashHex);\n\tconst buf = new Uint8Array(a.length + b.length);\n\tbuf.set(a, 0);\n\tbuf.set(b, a.length);\n\treturn toHex(sha512_256(buf));\n}\n\n/** A Stacks txid = SHA512/256 of the transaction's consensus serialization. */\nexport function stacksTxid(rawTx: Uint8Array): string {\n\treturn toHex(sha512_256(rawTx));\n}\n\nconst LEAF_TAG = 0x00;\nconst NODE_TAG = 0x01;\n\nfunction tagged(tag: number, ...parts: Uint8Array[]): Uint8Array {\n\tconst len = parts.reduce((n, p) => n + p.length, 1);\n\tconst buf = new Uint8Array(len);\n\tbuf[0] = tag;\n\tlet o = 1;\n\tfor (const p of parts) {\n\t\tbuf.set(p, o);\n\t\to += p.length;\n\t}\n\treturn sha512_256(buf);\n}\n\n/**\n * tx_merkle_root over the block's txids (hex), reproducing the consensus rule:\n * leaf = H(0x00 ‖ txid), node = H(0x01 ‖ left ‖ right), odd level duplicates the\n * last node. Returns the root hex; throws on an empty tx list.\n */\nexport function txMerkleRoot(txidsHex: string[]): string {\n\tif (txidsHex.length === 0) throw new Error(\"no transactions\");\n\tlet level = txidsHex.map((t) => tagged(LEAF_TAG, fromHex(t)));\n\twhile (level.length > 1) {\n\t\tif (level.length % 2 === 1) level.push(level[level.length - 1]);\n\t\tconst next: Uint8Array[] = [];\n\t\tfor (let i = 0; i < level.length; i += 2) {\n\t\t\tnext.push(tagged(NODE_TAG, level[i], level[i + 1]));\n\t\t}\n\t\tlevel = next;\n\t}\n\treturn toHex(level[0]);\n}\n\n/** One authentication-path step: the sibling hash and which side it's on. */\nexport interface MerkleProofStep {\n\t/** Side the SIBLING is on relative to the accumulator. */\n\tposition: \"left\" | \"right\";\n\t/** Sibling node hash (hex). */\n\thash: string;\n}\n\n/**\n * Build the tx-inclusion authentication path for the tx at `index` in a block,\n * reproducing the consensus merkle tree (incl. duplicate-last-on-odd). The path\n * lets a verifier recompute `tx_merkle_root` from just the target txid.\n */\nexport function txMerkleProof(\n\ttxidsHex: string[],\n\tindex: number,\n): MerkleProofStep[] {\n\tif (index < 0 || index >= txidsHex.length) {\n\t\tthrow new Error(\"index out of range\");\n\t}\n\tlet level = txidsHex.map((t) => tagged(LEAF_TAG, fromHex(t)));\n\tlet idx = index;\n\tconst path: MerkleProofStep[] = [];\n\twhile (level.length > 1) {\n\t\tif (level.length % 2 === 1) level.push(level[level.length - 1]);\n\t\tconst siblingIdx = idx % 2 === 0 ? idx + 1 : idx - 1;\n\t\tpath.push({\n\t\t\tposition: idx % 2 === 0 ? \"right\" : \"left\",\n\t\t\thash: toHex(level[siblingIdx]),\n\t\t});\n\t\tconst next: Uint8Array[] = [];\n\t\tfor (let i = 0; i < level.length; i += 2) {\n\t\t\tnext.push(tagged(NODE_TAG, level[i], level[i + 1]));\n\t\t}\n\t\tlevel = next;\n\t\tidx = Math.floor(idx / 2);\n\t}\n\treturn path;\n}\n\n/**\n * Verify a tx-inclusion proof: fold the target `txid` (hex) up through `path`\n * and check it equals `txMerkleRoot` (hex). The verifier recomputes the txid\n * itself from the raw tx bytes, so nothing here is trusted.\n */\nexport function verifyTxMerkleProof(\n\ttxidHex: string,\n\tpath: MerkleProofStep[],\n\ttxMerkleRootHex: string,\n): boolean {\n\tlet acc = tagged(LEAF_TAG, fromHex(txidHex));\n\tfor (const step of path) {\n\t\tconst sib = fromHex(step.hash);\n\t\tacc =\n\t\t\tstep.position === \"right\"\n\t\t\t\t? tagged(NODE_TAG, acc, sib)\n\t\t\t\t: tagged(NODE_TAG, sib, acc);\n\t}\n\tconst root = txMerkleRootHex.startsWith(\"0x\")\n\t\t? txMerkleRootHex.slice(2)\n\t\t: txMerkleRootHex;\n\treturn toHex(acc) === root;\n}\n\n/**\n * Fetch and parse a Nakamoto block from a stacks-node. `blockId` is the\n * index_block_hash (with or without 0x). Returns the raw bytes + parsed header +\n * the recomputed block_hash / index_block_hash so a caller can cross-check.\n */\nexport async function fetchNakamotoBlock(opts: {\n\tnodeUrl: string;\n\tblockId: string;\n\tfetchImpl?: typeof fetch;\n}): Promise<{\n\traw: Uint8Array;\n\theader: NakamotoBlockHeader;\n\tblockHash: string;\n\tindexBlockHash: string;\n}> {\n\tconst id = opts.blockId.startsWith(\"0x\")\n\t\t? opts.blockId.slice(2)\n\t\t: opts.blockId;\n\tconst f = opts.fetchImpl ?? fetch;\n\tconst res = await f(`${opts.nodeUrl.replace(/\\/+$/, \"\")}/v3/blocks/${id}`);\n\tif (!res.ok) {\n\t\tthrow new Error(`/v3/blocks/${id} returned ${res.status}`);\n\t}\n\tconst raw = new Uint8Array(await res.arrayBuffer());\n\tconst header = parseNakamotoBlockHeader(raw);\n\tconst blockHash = nakamotoBlockHash(header);\n\treturn {\n\t\traw,\n\t\theader,\n\t\tblockHash,\n\t\tindexBlockHash: nakamotoBlockId(blockHash, header.consensusHash),\n\t};\n}\n",
|
|
5
|
+
"import { createHash } from \"node:crypto\";\n\n/**\n * Nakamoto block-header parsing + consensus hashing for trustless verification.\n *\n * Version 0 is bit-exact against mainnet `stacks-node 3.4.0.0.3`. Version 1\n * (Epoch 4.0 / PoX-5, first mainnet height 8,665,568) appends\n * `problematic_txs` after `pox_treatment` and includes those bytes in the\n * signer-signature-hash preimage. Pre-4.0 hashes are unchanged.\n */\n\n/** SHA-512/256 — the hash Stacks uses everywhere (NOT truncated SHA-512). */\nexport function sha512_256(bytes: Uint8Array): Uint8Array {\n\treturn createHash(\"sha512-256\").update(bytes).digest();\n}\n\nconst toHex = (b: Uint8Array): string => Buffer.from(b).toString(\"hex\");\nconst fromHex = (h: string): Uint8Array =>\n\tUint8Array.from(Buffer.from(h.startsWith(\"0x\") ? h.slice(2) : h, \"hex\"));\n\n// Fixed-size header prefix: version..miner_signature, before signer_signature.\nconst PREFIX_LEN = 206;\nconst CONSENSUS_HASH_OFF = 17;\nconst TX_MERKLE_ROOT_OFF = 69;\nconst STATE_INDEX_ROOT_OFF = 101;\nconst TIMESTAMP_OFF = 133;\nconst MINER_SIG_OFF = 141;\nconst SIGNER_VEC_OFF = 206; // u32 count, then 65 bytes per signer\nconst SIG_LEN = 65;\nconst PROBLEMATIC_TX_MARKER_LEN = 5; // u32 tx_index ‖ u8 category\n\n/** Pre-Epoch-4.0 Nakamoto header. Omits `problematic_txs`. */\nexport const NAKAMOTO_BLOCK_VERSION = 0;\n/** Epoch 4.0+ header. Serializes and hashes `problematic_txs`. */\nexport const NAKAMOTO_BLOCK_VERSION_EPOCH_4 = 1;\n\nexport type ProblematicTxMarker = {\n\ttxIndex: number;\n\tcategory: number;\n};\n\n/** Low 7 bits are the header version; high bit 0x80 is the shadow-block flag. */\nexport function versionIncludesProblematicTxs(version: number): boolean {\n\treturn (version & 0x7f) >= NAKAMOTO_BLOCK_VERSION_EPOCH_4;\n}\n\nexport interface NakamotoBlockHeader {\n\tversion: number;\n\tchainLength: bigint;\n\tburnSpent: bigint;\n\t/** 20-byte consensus hash (hex). */\n\tconsensusHash: string;\n\t/** 32-byte parent StacksBlockId (hex). */\n\tparentBlockId: string;\n\t/** 32-byte SHA512/256 merkle root over the block's txids (hex). */\n\ttxMerkleRoot: string;\n\t/** 32-byte MARF root after applying this block (hex). */\n\tstateIndexRoot: string;\n\ttimestamp: bigint;\n\t/** 65-byte recoverable ECDSA miner signature (hex). */\n\tminerSignature: string;\n\t/** Per-signer recoverable ECDSA signatures, reward-set order (hex, 65B each). */\n\tsignerSignatures: string[];\n\t/** Full serialized pox_treatment BitVec bytes (u16 bits ‖ u32 len ‖ data). */\n\tpoxTreatment: Uint8Array;\n\t/**\n\t * Epoch 4.0+ marker list. Empty (and absent from the wire) on version 0.\n\t * An empty list still serializes as a u32 count of 0 on version 1+.\n\t */\n\tproblematicTxs: ProblematicTxMarker[];\n\t/**\n\t * Exact bytes whose SHA512/256 IS the block_hash / signer_signature_hash:\n\t * header minus the signer_signature vector. Version 0 is\n\t * prefix[0:206] ‖ pox; version 1+ is prefix[0:206] ‖ pox ‖ problematic_txs.\n\t */\n\tsignerSignatureHashPreimage: Uint8Array;\n\t/** Offset at which the tx `Vec` begins (= total header byte length). */\n\theaderByteLength: number;\n}\n\nfunction u32(b: Uint8Array, off: number): number {\n\treturn new DataView(b.buffer, b.byteOffset, b.byteLength).getUint32(off);\n}\nfunction u64(b: Uint8Array, off: number): bigint {\n\treturn new DataView(b.buffer, b.byteOffset, b.byteLength).getBigUint64(off);\n}\n\n/** Parse the Nakamoto block header from the raw `/v3/blocks` body. */\nexport function parseNakamotoBlockHeader(raw: Uint8Array): NakamotoBlockHeader {\n\tif (raw.length < PREFIX_LEN + 4) {\n\t\tthrow new Error(\"raw block too short for a Nakamoto header\");\n\t}\n\tconst version = raw[0];\n\tconst signerCount = u32(raw, SIGNER_VEC_OFF);\n\tconst sigsStart = SIGNER_VEC_OFF + 4;\n\tif (sigsStart + signerCount * SIG_LEN > raw.length) {\n\t\tthrow new Error(\"raw block truncated in signer_signature vector\");\n\t}\n\tconst signerSignatures: string[] = [];\n\tfor (let i = 0; i < signerCount; i++) {\n\t\tconst off = sigsStart + i * SIG_LEN;\n\t\tsignerSignatures.push(toHex(raw.subarray(off, off + SIG_LEN)));\n\t}\n\t// pox_treatment: u16 num_bits ‖ u32 data_len ‖ data[data_len].\n\tconst poxOff = sigsStart + signerCount * SIG_LEN;\n\tif (poxOff + 6 > raw.length) {\n\t\tthrow new Error(\"raw block truncated in pox_treatment\");\n\t}\n\tconst poxDataLen = u32(raw, poxOff + 2);\n\tconst poxEnd = poxOff + 6 + poxDataLen;\n\tif (poxEnd > raw.length) {\n\t\tthrow new Error(\"raw block truncated in pox_treatment data\");\n\t}\n\tconst poxTreatment = raw.subarray(poxOff, poxEnd);\n\n\t// Epoch 4.0+ (version low-7-bits >= 1): Vec<ProblematicTxMarker>\n\t// after pox_treatment. Version 0 omits the field entirely so pre-fork\n\t// hashes stay byte-identical.\n\tlet headerEnd = poxEnd;\n\tconst problematicTxs: ProblematicTxMarker[] = [];\n\tif (versionIncludesProblematicTxs(version)) {\n\t\tif (poxEnd + 4 > raw.length) {\n\t\t\tthrow new Error(\"raw block truncated in problematic_txs count\");\n\t\t}\n\t\tconst markerCount = u32(raw, poxEnd);\n\t\theaderEnd = poxEnd + 4 + markerCount * PROBLEMATIC_TX_MARKER_LEN;\n\t\tif (headerEnd > raw.length) {\n\t\t\tthrow new Error(\"raw block truncated in problematic_txs list\");\n\t\t}\n\t\tfor (let i = 0; i < markerCount; i++) {\n\t\t\tconst off = poxEnd + 4 + i * PROBLEMATIC_TX_MARKER_LEN;\n\t\t\tproblematicTxs.push({\n\t\t\t\ttxIndex: u32(raw, off),\n\t\t\t\tcategory: raw[off + 4] ?? 0,\n\t\t\t});\n\t\t}\n\t}\n\n\t// block_hash preimage = header minus signer_signature.\n\t// v0: prefix[0:206] ‖ pox\n\t// v1: prefix[0:206] ‖ pox ‖ problematic_txs (count + markers)\n\tconst markersBytes = raw.subarray(poxEnd, headerEnd);\n\tconst preimage = new Uint8Array(\n\t\tPREFIX_LEN + poxTreatment.length + markersBytes.length,\n\t);\n\tpreimage.set(raw.subarray(0, PREFIX_LEN), 0);\n\tpreimage.set(poxTreatment, PREFIX_LEN);\n\tpreimage.set(markersBytes, PREFIX_LEN + poxTreatment.length);\n\n\treturn {\n\t\tversion,\n\t\tchainLength: u64(raw, 1),\n\t\tburnSpent: u64(raw, 9),\n\t\tconsensusHash: toHex(\n\t\t\traw.subarray(CONSENSUS_HASH_OFF, CONSENSUS_HASH_OFF + 20),\n\t\t),\n\t\tparentBlockId: toHex(raw.subarray(37, 69)),\n\t\ttxMerkleRoot: toHex(\n\t\t\traw.subarray(TX_MERKLE_ROOT_OFF, TX_MERKLE_ROOT_OFF + 32),\n\t\t),\n\t\tstateIndexRoot: toHex(\n\t\t\traw.subarray(STATE_INDEX_ROOT_OFF, STATE_INDEX_ROOT_OFF + 32),\n\t\t),\n\t\ttimestamp: u64(raw, TIMESTAMP_OFF),\n\t\tminerSignature: toHex(raw.subarray(MINER_SIG_OFF, MINER_SIG_OFF + SIG_LEN)),\n\t\tsignerSignatures,\n\t\tpoxTreatment,\n\t\tproblematicTxs,\n\t\tsignerSignatureHashPreimage: preimage,\n\t\theaderByteLength: headerEnd,\n\t};\n}\n\n/**\n * block_hash (== signer_signature_hash): SHA512/256 over the header with the\n * signer_signature vector omitted. This is what each signer signs.\n */\nexport function nakamotoBlockHash(header: NakamotoBlockHeader): string {\n\treturn toHex(sha512_256(header.signerSignatureHashPreimage));\n}\n\n/** index_block_hash (StacksBlockId) = SHA512/256(block_hash ‖ consensus_hash). */\nexport function nakamotoBlockId(\n\tblockHashHex: string,\n\tconsensusHashHex: string,\n): string {\n\tconst a = fromHex(blockHashHex);\n\tconst b = fromHex(consensusHashHex);\n\tconst buf = new Uint8Array(a.length + b.length);\n\tbuf.set(a, 0);\n\tbuf.set(b, a.length);\n\treturn toHex(sha512_256(buf));\n}\n\n/** A Stacks txid = SHA512/256 of the transaction's consensus serialization. */\nexport function stacksTxid(rawTx: Uint8Array): string {\n\treturn toHex(sha512_256(rawTx));\n}\n\nconst LEAF_TAG = 0x00;\nconst NODE_TAG = 0x01;\n\nfunction tagged(tag: number, ...parts: Uint8Array[]): Uint8Array {\n\tconst len = parts.reduce((n, p) => n + p.length, 1);\n\tconst buf = new Uint8Array(len);\n\tbuf[0] = tag;\n\tlet o = 1;\n\tfor (const p of parts) {\n\t\tbuf.set(p, o);\n\t\to += p.length;\n\t}\n\treturn sha512_256(buf);\n}\n\n/**\n * tx_merkle_root over the block's txids (hex), reproducing the consensus rule:\n * leaf = H(0x00 ‖ txid), node = H(0x01 ‖ left ‖ right), odd level duplicates the\n * last node. Returns the root hex; throws on an empty tx list.\n */\nexport function txMerkleRoot(txidsHex: string[]): string {\n\tif (txidsHex.length === 0) throw new Error(\"no transactions\");\n\tlet level = txidsHex.map((t) => tagged(LEAF_TAG, fromHex(t)));\n\twhile (level.length > 1) {\n\t\tif (level.length % 2 === 1) level.push(level[level.length - 1]);\n\t\tconst next: Uint8Array[] = [];\n\t\tfor (let i = 0; i < level.length; i += 2) {\n\t\t\tnext.push(tagged(NODE_TAG, level[i], level[i + 1]));\n\t\t}\n\t\tlevel = next;\n\t}\n\treturn toHex(level[0]);\n}\n\n/** One authentication-path step: the sibling hash and which side it's on. */\nexport interface MerkleProofStep {\n\t/** Side the SIBLING is on relative to the accumulator. */\n\tposition: \"left\" | \"right\";\n\t/** Sibling node hash (hex). */\n\thash: string;\n}\n\n/**\n * Build the tx-inclusion authentication path for the tx at `index` in a block,\n * reproducing the consensus merkle tree (incl. duplicate-last-on-odd). The path\n * lets a verifier recompute `tx_merkle_root` from just the target txid.\n */\nexport function txMerkleProof(\n\ttxidsHex: string[],\n\tindex: number,\n): MerkleProofStep[] {\n\tif (index < 0 || index >= txidsHex.length) {\n\t\tthrow new Error(\"index out of range\");\n\t}\n\tlet level = txidsHex.map((t) => tagged(LEAF_TAG, fromHex(t)));\n\tlet idx = index;\n\tconst path: MerkleProofStep[] = [];\n\twhile (level.length > 1) {\n\t\tif (level.length % 2 === 1) level.push(level[level.length - 1]);\n\t\tconst siblingIdx = idx % 2 === 0 ? idx + 1 : idx - 1;\n\t\tpath.push({\n\t\t\tposition: idx % 2 === 0 ? \"right\" : \"left\",\n\t\t\thash: toHex(level[siblingIdx]),\n\t\t});\n\t\tconst next: Uint8Array[] = [];\n\t\tfor (let i = 0; i < level.length; i += 2) {\n\t\t\tnext.push(tagged(NODE_TAG, level[i], level[i + 1]));\n\t\t}\n\t\tlevel = next;\n\t\tidx = Math.floor(idx / 2);\n\t}\n\treturn path;\n}\n\n/**\n * Verify a tx-inclusion proof: fold the target `txid` (hex) up through `path`\n * and check it equals `txMerkleRoot` (hex). The verifier recomputes the txid\n * itself from the raw tx bytes, so nothing here is trusted.\n */\nexport function verifyTxMerkleProof(\n\ttxidHex: string,\n\tpath: MerkleProofStep[],\n\ttxMerkleRootHex: string,\n): boolean {\n\tlet acc = tagged(LEAF_TAG, fromHex(txidHex));\n\tfor (const step of path) {\n\t\tconst sib = fromHex(step.hash);\n\t\tacc =\n\t\t\tstep.position === \"right\"\n\t\t\t\t? tagged(NODE_TAG, acc, sib)\n\t\t\t\t: tagged(NODE_TAG, sib, acc);\n\t}\n\tconst root = txMerkleRootHex.startsWith(\"0x\")\n\t\t? txMerkleRootHex.slice(2)\n\t\t: txMerkleRootHex;\n\treturn toHex(acc) === root;\n}\n\n/**\n * Fetch and parse a Nakamoto block from a stacks-node. `blockId` is the\n * index_block_hash (with or without 0x). Returns the raw bytes + parsed header +\n * the recomputed block_hash / index_block_hash so a caller can cross-check.\n */\nexport async function fetchNakamotoBlock(opts: {\n\tnodeUrl: string;\n\tblockId: string;\n\tfetchImpl?: typeof fetch;\n}): Promise<{\n\traw: Uint8Array;\n\theader: NakamotoBlockHeader;\n\tblockHash: string;\n\tindexBlockHash: string;\n}> {\n\tconst id = opts.blockId.startsWith(\"0x\")\n\t\t? opts.blockId.slice(2)\n\t\t: opts.blockId;\n\tconst f = opts.fetchImpl ?? fetch;\n\tconst res = await f(`${opts.nodeUrl.replace(/\\/+$/, \"\")}/v3/blocks/${id}`);\n\tif (!res.ok) {\n\t\tthrow new Error(`/v3/blocks/${id} returned ${res.status}`);\n\t}\n\tconst raw = new Uint8Array(await res.arrayBuffer());\n\tconst header = parseNakamotoBlockHeader(raw);\n\tconst blockHash = nakamotoBlockHash(header);\n\treturn {\n\t\traw,\n\t\theader,\n\t\tblockHash,\n\t\tindexBlockHash: nakamotoBlockId(blockHash, header.consensusHash),\n\t};\n}\n",
|
|
6
6
|
"import { http, createPublicClient } from \"@secondlayer/stacks\";\nimport { getContractAbi, getRawBlock } from \"@secondlayer/stacks/actions\";\nimport type { RewardSet } from \"./consensus.ts\";\nimport {\n\ttype NakamotoBlockHeader,\n\tnakamotoBlockHash,\n\tnakamotoBlockId,\n\tparseNakamotoBlockHeader,\n} from \"./nakamoto.ts\";\n\nexport interface NodeInfo {\n\tpeer_version: number;\n\tpox_consensus: string;\n\tburn_block_height: number;\n\tstable_pox_consensus: string;\n\tstable_burn_block_height: number;\n\tserver_version: string;\n\tnetwork_id: number;\n\tparent_network_id: number;\n\tstacks_tip_height: number;\n\tstacks_tip: string;\n\tstacks_tip_consensus_hash: string;\n\tgenesis_chainstate_hash: string;\n}\n\nexport interface BlockResponse {\n\thash: string;\n\theight: number;\n\tparent_block_hash: string;\n\tburn_block_height: number;\n\tburn_block_hash: string;\n\tburn_block_time: number;\n\tindex_block_hash: string;\n\tparent_index_block_hash: string;\n\tminer_txid: string;\n\ttxs: string[];\n\t// Full block data varies by endpoint — kept minimal for fetch use case\n}\n\nexport class StacksNodeClient {\n\tprivate rpcUrl: string;\n\n\tconstructor(rpcUrl?: string) {\n\t\tthis.rpcUrl =\n\t\t\trpcUrl || process.env.STACKS_NODE_RPC_URL || \"http://localhost:20443\";\n\t}\n\n\t/**\n\t * A stacks-backed client for the given call's original timeout, with no\n\t * retries — matches every method's prior single-attempt fetch semantics\n\t * (in particular `getInfo`'s fast-fail timing, which `isHealthy()` and its\n\t * callers depend on).\n\t */\n\tprivate client(timeoutMs: number) {\n\t\treturn createPublicClient({\n\t\t\ttransport: http(this.rpcUrl, { retryCount: 0, timeout: timeoutMs }),\n\t\t});\n\t}\n\n\tasync getInfo(): Promise<NodeInfo> {\n\t\tconst data = await this.client(10_000).request(\"/v2/info\", {\n\t\t\tmethod: \"GET\",\n\t\t});\n\t\treturn data as NodeInfo;\n\t}\n\n\tasync getBlock(height: number): Promise<BlockResponse | null> {\n\t\treturn getRawBlock(this.client(30_000), { height });\n\t}\n\n\t/**\n\t * Fetch + parse a Nakamoto block by its index_block_hash. Returns the raw\n\t * bytes, the parsed header, and the recomputed block_hash / index_block_hash\n\t * (so a caller can cross-check the node's answer). Null on 404.\n\t */\n\tasync getNakamotoBlock(blockId: string): Promise<{\n\t\traw: Uint8Array;\n\t\theader: NakamotoBlockHeader;\n\t\tblockHash: string;\n\t\tindexBlockHash: string;\n\t} | null> {\n\t\tconst id = blockId.startsWith(\"0x\") ? blockId.slice(2) : blockId;\n\t\tconst res = await fetch(`${this.rpcUrl}/v3/blocks/${id}`, {\n\t\t\tsignal: AbortSignal.timeout(30_000),\n\t\t});\n\t\tif (res.status === 404) return null;\n\t\tif (!res.ok) {\n\t\t\tthrow new Error(`Node RPC /v3/blocks/${id} returned ${res.status}`);\n\t\t}\n\t\tconst raw = new Uint8Array(await res.arrayBuffer());\n\t\tconst header = parseNakamotoBlockHeader(raw);\n\t\tconst blockHash = nakamotoBlockHash(header);\n\t\treturn {\n\t\t\traw,\n\t\t\theader,\n\t\t\tblockHash,\n\t\t\tindexBlockHash: nakamotoBlockId(blockHash, header.consensusHash),\n\t\t};\n\t}\n\n\t/**\n\t * Fetch the reward set (signer keys + weights) for a reward cycle from\n\t * `/v3/stacker_set/{cycle}`. Null on 404 (cycle not yet computed).\n\t */\n\tasync getRewardSet(cycle: number): Promise<RewardSet | null> {\n\t\tconst res = await fetch(`${this.rpcUrl}/v3/stacker_set/${cycle}`, {\n\t\t\tsignal: AbortSignal.timeout(15_000),\n\t\t});\n\t\tif (res.status === 404) return null;\n\t\tif (!res.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Node RPC /v3/stacker_set/${cycle} returned ${res.status}`,\n\t\t\t);\n\t\t}\n\t\tconst body = (await res.json()) as {\n\t\t\tstacker_set: { signers: { signing_key: string; weight: number }[] };\n\t\t};\n\t\tconst signers = body.stacker_set.signers.map((s) => ({\n\t\t\tsigning_key: s.signing_key.startsWith(\"0x\")\n\t\t\t\t? s.signing_key.slice(2)\n\t\t\t\t: s.signing_key,\n\t\t\tweight: s.weight,\n\t\t}));\n\t\treturn {\n\t\t\tsigners,\n\t\t\ttotal_weight: signers.reduce((sum, s) => sum + s.weight, 0),\n\t\t};\n\t}\n\n\tasync isHealthy(): Promise<boolean> {\n\t\ttry {\n\t\t\tconst info = await this.getInfo();\n\t\t\treturn info.stacks_tip_height > 0;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync getContractAbi(contractId: string): Promise<unknown> {\n\t\treturn getContractAbi(this.client(30_000), { contract: contractId });\n\t}\n\n\tgetRpcUrl(): string {\n\t\treturn this.rpcUrl;\n\t}\n}\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": ";;;;;;;;;;;;;;;;;AAAA;
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "1B1DA7B9F8EBA8A564756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
|
@@ -191,6 +191,12 @@ interface ApiKeysTable {
|
|
|
191
191
|
revoked_at: Date | null;
|
|
192
192
|
created_at: Generated<Date>;
|
|
193
193
|
}
|
|
194
|
+
/** Singleton self-host / publisher identity. Network is immutable. */
|
|
195
|
+
interface InstancesTable {
|
|
196
|
+
id: Generated<string>;
|
|
197
|
+
network: string;
|
|
198
|
+
created_at: Generated<Date>;
|
|
199
|
+
}
|
|
194
200
|
interface AccountsTable {
|
|
195
201
|
id: Generated<string>;
|
|
196
202
|
/** NULL for unclaimed ghost accounts (anonymous self-serve keys). */
|
|
@@ -733,6 +739,7 @@ interface Database {
|
|
|
733
739
|
contracts: ContractsTable;
|
|
734
740
|
subgraphs: SubgraphsTable;
|
|
735
741
|
api_keys: ApiKeysTable;
|
|
742
|
+
instances: InstancesTable;
|
|
736
743
|
accounts: AccountsTable;
|
|
737
744
|
sessions: SessionsTable;
|
|
738
745
|
magic_links: MagicLinksTable;
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Nakamoto block-header parsing + consensus hashing for trustless verification.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* index_block_hash, and tx_merkle_root the chain itself commits to.
|
|
4
|
+
* Version 0 is bit-exact against mainnet `stacks-node 3.4.0.0.3`. Version 1
|
|
5
|
+
* (Epoch 4.0 / PoX-5, first mainnet height 8,665,568) appends
|
|
6
|
+
* `problematic_txs` after `pox_treatment` and includes those bytes in the
|
|
7
|
+
* signer-signature-hash preimage. Pre-4.0 hashes are unchanged.
|
|
9
8
|
*/
|
|
10
9
|
/** SHA-512/256 — the hash Stacks uses everywhere (NOT truncated SHA-512). */
|
|
11
10
|
declare function sha512_256(bytes: Uint8Array): Uint8Array;
|
|
11
|
+
/** Pre-Epoch-4.0 Nakamoto header. Omits `problematic_txs`. */
|
|
12
|
+
declare const NAKAMOTO_BLOCK_VERSION = 0;
|
|
13
|
+
/** Epoch 4.0+ header. Serializes and hashes `problematic_txs`. */
|
|
14
|
+
declare const NAKAMOTO_BLOCK_VERSION_EPOCH_4 = 1;
|
|
15
|
+
type ProblematicTxMarker = {
|
|
16
|
+
txIndex: number
|
|
17
|
+
category: number
|
|
18
|
+
};
|
|
19
|
+
/** Low 7 bits are the header version; high bit 0x80 is the shadow-block flag. */
|
|
20
|
+
declare function versionIncludesProblematicTxs(version: number): boolean;
|
|
12
21
|
interface NakamotoBlockHeader {
|
|
13
22
|
version: number;
|
|
14
23
|
chainLength: bigint;
|
|
@@ -29,8 +38,14 @@ interface NakamotoBlockHeader {
|
|
|
29
38
|
/** Full serialized pox_treatment BitVec bytes (u16 bits ‖ u32 len ‖ data). */
|
|
30
39
|
poxTreatment: Uint8Array;
|
|
31
40
|
/**
|
|
41
|
+
* Epoch 4.0+ marker list. Empty (and absent from the wire) on version 0.
|
|
42
|
+
* An empty list still serializes as a u32 count of 0 on version 1+.
|
|
43
|
+
*/
|
|
44
|
+
problematicTxs: ProblematicTxMarker[];
|
|
45
|
+
/**
|
|
32
46
|
* Exact bytes whose SHA512/256 IS the block_hash / signer_signature_hash:
|
|
33
|
-
*
|
|
47
|
+
* header minus the signer_signature vector. Version 0 is
|
|
48
|
+
* prefix[0:206] ‖ pox; version 1+ is prefix[0:206] ‖ pox ‖ problematic_txs.
|
|
34
49
|
*/
|
|
35
50
|
signerSignatureHashPreimage: Uint8Array;
|
|
36
51
|
/** Offset at which the tx `Vec` begins (= total header byte length). */
|
|
@@ -87,4 +102,4 @@ declare function fetchNakamotoBlock(opts: {
|
|
|
87
102
|
blockHash: string
|
|
88
103
|
indexBlockHash: string
|
|
89
104
|
}>;
|
|
90
|
-
export { verifyTxMerkleProof, txMerkleRoot, txMerkleProof, stacksTxid, sha512_256, parseNakamotoBlockHeader, nakamotoBlockId, nakamotoBlockHash, fetchNakamotoBlock, NakamotoBlockHeader, MerkleProofStep };
|
|
105
|
+
export { versionIncludesProblematicTxs, verifyTxMerkleProof, txMerkleRoot, txMerkleProof, stacksTxid, sha512_256, parseNakamotoBlockHeader, nakamotoBlockId, nakamotoBlockHash, fetchNakamotoBlock, ProblematicTxMarker, NakamotoBlockHeader, NAKAMOTO_BLOCK_VERSION_EPOCH_4, NAKAMOTO_BLOCK_VERSION, MerkleProofStep };
|
|
@@ -29,6 +29,12 @@ var TIMESTAMP_OFF = 133;
|
|
|
29
29
|
var MINER_SIG_OFF = 141;
|
|
30
30
|
var SIGNER_VEC_OFF = 206;
|
|
31
31
|
var SIG_LEN = 65;
|
|
32
|
+
var PROBLEMATIC_TX_MARKER_LEN = 5;
|
|
33
|
+
var NAKAMOTO_BLOCK_VERSION = 0;
|
|
34
|
+
var NAKAMOTO_BLOCK_VERSION_EPOCH_4 = 1;
|
|
35
|
+
function versionIncludesProblematicTxs(version) {
|
|
36
|
+
return (version & 127) >= NAKAMOTO_BLOCK_VERSION_EPOCH_4;
|
|
37
|
+
}
|
|
32
38
|
function u32(b, off) {
|
|
33
39
|
return new DataView(b.buffer, b.byteOffset, b.byteLength).getUint32(off);
|
|
34
40
|
}
|
|
@@ -39,22 +45,53 @@ function parseNakamotoBlockHeader(raw) {
|
|
|
39
45
|
if (raw.length < PREFIX_LEN + 4) {
|
|
40
46
|
throw new Error("raw block too short for a Nakamoto header");
|
|
41
47
|
}
|
|
48
|
+
const version = raw[0];
|
|
42
49
|
const signerCount = u32(raw, SIGNER_VEC_OFF);
|
|
43
50
|
const sigsStart = SIGNER_VEC_OFF + 4;
|
|
51
|
+
if (sigsStart + signerCount * SIG_LEN > raw.length) {
|
|
52
|
+
throw new Error("raw block truncated in signer_signature vector");
|
|
53
|
+
}
|
|
44
54
|
const signerSignatures = [];
|
|
45
55
|
for (let i = 0;i < signerCount; i++) {
|
|
46
56
|
const off = sigsStart + i * SIG_LEN;
|
|
47
57
|
signerSignatures.push(toHex(raw.subarray(off, off + SIG_LEN)));
|
|
48
58
|
}
|
|
49
59
|
const poxOff = sigsStart + signerCount * SIG_LEN;
|
|
60
|
+
if (poxOff + 6 > raw.length) {
|
|
61
|
+
throw new Error("raw block truncated in pox_treatment");
|
|
62
|
+
}
|
|
50
63
|
const poxDataLen = u32(raw, poxOff + 2);
|
|
51
64
|
const poxEnd = poxOff + 6 + poxDataLen;
|
|
65
|
+
if (poxEnd > raw.length) {
|
|
66
|
+
throw new Error("raw block truncated in pox_treatment data");
|
|
67
|
+
}
|
|
52
68
|
const poxTreatment = raw.subarray(poxOff, poxEnd);
|
|
53
|
-
|
|
69
|
+
let headerEnd = poxEnd;
|
|
70
|
+
const problematicTxs = [];
|
|
71
|
+
if (versionIncludesProblematicTxs(version)) {
|
|
72
|
+
if (poxEnd + 4 > raw.length) {
|
|
73
|
+
throw new Error("raw block truncated in problematic_txs count");
|
|
74
|
+
}
|
|
75
|
+
const markerCount = u32(raw, poxEnd);
|
|
76
|
+
headerEnd = poxEnd + 4 + markerCount * PROBLEMATIC_TX_MARKER_LEN;
|
|
77
|
+
if (headerEnd > raw.length) {
|
|
78
|
+
throw new Error("raw block truncated in problematic_txs list");
|
|
79
|
+
}
|
|
80
|
+
for (let i = 0;i < markerCount; i++) {
|
|
81
|
+
const off = poxEnd + 4 + i * PROBLEMATIC_TX_MARKER_LEN;
|
|
82
|
+
problematicTxs.push({
|
|
83
|
+
txIndex: u32(raw, off),
|
|
84
|
+
category: raw[off + 4] ?? 0
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const markersBytes = raw.subarray(poxEnd, headerEnd);
|
|
89
|
+
const preimage = new Uint8Array(PREFIX_LEN + poxTreatment.length + markersBytes.length);
|
|
54
90
|
preimage.set(raw.subarray(0, PREFIX_LEN), 0);
|
|
55
91
|
preimage.set(poxTreatment, PREFIX_LEN);
|
|
92
|
+
preimage.set(markersBytes, PREFIX_LEN + poxTreatment.length);
|
|
56
93
|
return {
|
|
57
|
-
version
|
|
94
|
+
version,
|
|
58
95
|
chainLength: u64(raw, 1),
|
|
59
96
|
burnSpent: u64(raw, 9),
|
|
60
97
|
consensusHash: toHex(raw.subarray(CONSENSUS_HASH_OFF, CONSENSUS_HASH_OFF + 20)),
|
|
@@ -65,8 +102,9 @@ function parseNakamotoBlockHeader(raw) {
|
|
|
65
102
|
minerSignature: toHex(raw.subarray(MINER_SIG_OFF, MINER_SIG_OFF + SIG_LEN)),
|
|
66
103
|
signerSignatures,
|
|
67
104
|
poxTreatment,
|
|
105
|
+
problematicTxs,
|
|
68
106
|
signerSignatureHashPreimage: preimage,
|
|
69
|
-
headerByteLength:
|
|
107
|
+
headerByteLength: headerEnd
|
|
70
108
|
};
|
|
71
109
|
}
|
|
72
110
|
function nakamotoBlockHash(header) {
|
|
@@ -162,6 +200,7 @@ async function fetchNakamotoBlock(opts) {
|
|
|
162
200
|
};
|
|
163
201
|
}
|
|
164
202
|
export {
|
|
203
|
+
versionIncludesProblematicTxs,
|
|
165
204
|
verifyTxMerkleProof,
|
|
166
205
|
txMerkleRoot,
|
|
167
206
|
txMerkleProof,
|
|
@@ -170,8 +209,10 @@ export {
|
|
|
170
209
|
parseNakamotoBlockHeader,
|
|
171
210
|
nakamotoBlockId,
|
|
172
211
|
nakamotoBlockHash,
|
|
173
|
-
fetchNakamotoBlock
|
|
212
|
+
fetchNakamotoBlock,
|
|
213
|
+
NAKAMOTO_BLOCK_VERSION_EPOCH_4,
|
|
214
|
+
NAKAMOTO_BLOCK_VERSION
|
|
174
215
|
};
|
|
175
216
|
|
|
176
|
-
//# debugId=
|
|
217
|
+
//# debugId=7772B63DB5967E2A64756E2164756E21
|
|
177
218
|
//# sourceMappingURL=nakamoto.js.map
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/node/nakamoto.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import { createHash } from \"node:crypto\";\n\n/**\n * Nakamoto block-header parsing + consensus hashing for trustless verification.\n *\n * Every constant here is verified bit-exact against mainnet `stacks-node\n * 3.4.0.0.3` (see docs/design/trustless-verification-proofs.md, Appendix). This\n * Building block for transaction-inclusion / block-canonicity proofs: fetch a\n * raw `/v3/blocks/{id}` body, parse the header, and recompute the block_hash,\n * index_block_hash, and tx_merkle_root the chain itself commits to.\n */\n\n/** SHA-512/256 — the hash Stacks uses everywhere (NOT truncated SHA-512). */\nexport function sha512_256(bytes: Uint8Array): Uint8Array {\n\treturn createHash(\"sha512-256\").update(bytes).digest();\n}\n\nconst toHex = (b: Uint8Array): string => Buffer.from(b).toString(\"hex\");\nconst fromHex = (h: string): Uint8Array =>\n\tUint8Array.from(Buffer.from(h.startsWith(\"0x\") ? h.slice(2) : h, \"hex\"));\n\n// Fixed-size header prefix: version..miner_signature, before signer_signature.\nconst PREFIX_LEN = 206;\nconst CONSENSUS_HASH_OFF = 17;\nconst TX_MERKLE_ROOT_OFF = 69;\nconst STATE_INDEX_ROOT_OFF = 101;\nconst TIMESTAMP_OFF = 133;\nconst MINER_SIG_OFF = 141;\nconst SIGNER_VEC_OFF = 206; // u32 count, then 65 bytes per signer\nconst SIG_LEN = 65;\n\nexport interface NakamotoBlockHeader {\n\tversion: number;\n\tchainLength: bigint;\n\tburnSpent: bigint;\n\t/** 20-byte consensus hash (hex). */\n\tconsensusHash: string;\n\t/** 32-byte parent StacksBlockId (hex). */\n\tparentBlockId: string;\n\t/** 32-byte SHA512/256 merkle root over the block's txids (hex). */\n\ttxMerkleRoot: string;\n\t/** 32-byte MARF root after applying this block (hex). */\n\tstateIndexRoot: string;\n\ttimestamp: bigint;\n\t/** 65-byte recoverable ECDSA miner signature (hex). */\n\tminerSignature: string;\n\t/** Per-signer recoverable ECDSA signatures, reward-set order (hex, 65B each). */\n\tsignerSignatures: string[];\n\t/** Full serialized pox_treatment BitVec bytes (u16 bits ‖ u32 len ‖ data). */\n\tpoxTreatment: Uint8Array;\n\t/**\n\t * Exact bytes whose SHA512/256 IS the block_hash / signer_signature_hash:\n\t * the header with the signer_signature vector omitted (header[0:206] ‖ pox).\n\t */\n\tsignerSignatureHashPreimage: Uint8Array;\n\t/** Offset at which the tx `Vec` begins (= total header byte length). */\n\theaderByteLength: number;\n}\n\nfunction u32(b: Uint8Array, off: number): number {\n\treturn new DataView(b.buffer, b.byteOffset, b.byteLength).getUint32(off);\n}\nfunction u64(b: Uint8Array, off: number): bigint {\n\treturn new DataView(b.buffer, b.byteOffset, b.byteLength).getBigUint64(off);\n}\n\n/** Parse the Nakamoto block header from the raw `/v3/blocks` body. */\nexport function parseNakamotoBlockHeader(raw: Uint8Array): NakamotoBlockHeader {\n\tif (raw.length < PREFIX_LEN + 4) {\n\t\tthrow new Error(\"raw block too short for a Nakamoto header\");\n\t}\n\tconst signerCount = u32(raw, SIGNER_VEC_OFF);\n\tconst sigsStart = SIGNER_VEC_OFF + 4;\n\tconst signerSignatures: string[] = [];\n\tfor (let i = 0; i < signerCount; i++) {\n\t\tconst off = sigsStart + i * SIG_LEN;\n\t\tsignerSignatures.push(toHex(raw.subarray(off, off + SIG_LEN)));\n\t}\n\t// pox_treatment: u16 num_bits ‖ u32 data_len ‖ data[data_len].\n\tconst poxOff = sigsStart + signerCount * SIG_LEN;\n\tconst poxDataLen = u32(raw, poxOff + 2);\n\tconst poxEnd = poxOff + 6 + poxDataLen;\n\tconst poxTreatment = raw.subarray(poxOff, poxEnd);\n\n\t// block_hash preimage = header minus signer_signature = prefix[0:206] ‖ pox.\n\tconst preimage = new Uint8Array(PREFIX_LEN + poxTreatment.length);\n\tpreimage.set(raw.subarray(0, PREFIX_LEN), 0);\n\tpreimage.set(poxTreatment, PREFIX_LEN);\n\n\treturn {\n\t\tversion: raw[0],\n\t\tchainLength: u64(raw, 1),\n\t\tburnSpent: u64(raw, 9),\n\t\tconsensusHash: toHex(\n\t\t\traw.subarray(CONSENSUS_HASH_OFF, CONSENSUS_HASH_OFF + 20),\n\t\t),\n\t\tparentBlockId: toHex(raw.subarray(37, 69)),\n\t\ttxMerkleRoot: toHex(\n\t\t\traw.subarray(TX_MERKLE_ROOT_OFF, TX_MERKLE_ROOT_OFF + 32),\n\t\t),\n\t\tstateIndexRoot: toHex(\n\t\t\traw.subarray(STATE_INDEX_ROOT_OFF, STATE_INDEX_ROOT_OFF + 32),\n\t\t),\n\t\ttimestamp: u64(raw, TIMESTAMP_OFF),\n\t\tminerSignature: toHex(raw.subarray(MINER_SIG_OFF, MINER_SIG_OFF + SIG_LEN)),\n\t\tsignerSignatures,\n\t\tpoxTreatment,\n\t\tsignerSignatureHashPreimage: preimage,\n\t\theaderByteLength: poxEnd,\n\t};\n}\n\n/**\n * block_hash (== signer_signature_hash): SHA512/256 over the header with the\n * signer_signature vector omitted. This is what each signer signs.\n */\nexport function nakamotoBlockHash(header: NakamotoBlockHeader): string {\n\treturn toHex(sha512_256(header.signerSignatureHashPreimage));\n}\n\n/** index_block_hash (StacksBlockId) = SHA512/256(block_hash ‖ consensus_hash). */\nexport function nakamotoBlockId(\n\tblockHashHex: string,\n\tconsensusHashHex: string,\n): string {\n\tconst a = fromHex(blockHashHex);\n\tconst b = fromHex(consensusHashHex);\n\tconst buf = new Uint8Array(a.length + b.length);\n\tbuf.set(a, 0);\n\tbuf.set(b, a.length);\n\treturn toHex(sha512_256(buf));\n}\n\n/** A Stacks txid = SHA512/256 of the transaction's consensus serialization. */\nexport function stacksTxid(rawTx: Uint8Array): string {\n\treturn toHex(sha512_256(rawTx));\n}\n\nconst LEAF_TAG = 0x00;\nconst NODE_TAG = 0x01;\n\nfunction tagged(tag: number, ...parts: Uint8Array[]): Uint8Array {\n\tconst len = parts.reduce((n, p) => n + p.length, 1);\n\tconst buf = new Uint8Array(len);\n\tbuf[0] = tag;\n\tlet o = 1;\n\tfor (const p of parts) {\n\t\tbuf.set(p, o);\n\t\to += p.length;\n\t}\n\treturn sha512_256(buf);\n}\n\n/**\n * tx_merkle_root over the block's txids (hex), reproducing the consensus rule:\n * leaf = H(0x00 ‖ txid), node = H(0x01 ‖ left ‖ right), odd level duplicates the\n * last node. Returns the root hex; throws on an empty tx list.\n */\nexport function txMerkleRoot(txidsHex: string[]): string {\n\tif (txidsHex.length === 0) throw new Error(\"no transactions\");\n\tlet level = txidsHex.map((t) => tagged(LEAF_TAG, fromHex(t)));\n\twhile (level.length > 1) {\n\t\tif (level.length % 2 === 1) level.push(level[level.length - 1]);\n\t\tconst next: Uint8Array[] = [];\n\t\tfor (let i = 0; i < level.length; i += 2) {\n\t\t\tnext.push(tagged(NODE_TAG, level[i], level[i + 1]));\n\t\t}\n\t\tlevel = next;\n\t}\n\treturn toHex(level[0]);\n}\n\n/** One authentication-path step: the sibling hash and which side it's on. */\nexport interface MerkleProofStep {\n\t/** Side the SIBLING is on relative to the accumulator. */\n\tposition: \"left\" | \"right\";\n\t/** Sibling node hash (hex). */\n\thash: string;\n}\n\n/**\n * Build the tx-inclusion authentication path for the tx at `index` in a block,\n * reproducing the consensus merkle tree (incl. duplicate-last-on-odd). The path\n * lets a verifier recompute `tx_merkle_root` from just the target txid.\n */\nexport function txMerkleProof(\n\ttxidsHex: string[],\n\tindex: number,\n): MerkleProofStep[] {\n\tif (index < 0 || index >= txidsHex.length) {\n\t\tthrow new Error(\"index out of range\");\n\t}\n\tlet level = txidsHex.map((t) => tagged(LEAF_TAG, fromHex(t)));\n\tlet idx = index;\n\tconst path: MerkleProofStep[] = [];\n\twhile (level.length > 1) {\n\t\tif (level.length % 2 === 1) level.push(level[level.length - 1]);\n\t\tconst siblingIdx = idx % 2 === 0 ? idx + 1 : idx - 1;\n\t\tpath.push({\n\t\t\tposition: idx % 2 === 0 ? \"right\" : \"left\",\n\t\t\thash: toHex(level[siblingIdx]),\n\t\t});\n\t\tconst next: Uint8Array[] = [];\n\t\tfor (let i = 0; i < level.length; i += 2) {\n\t\t\tnext.push(tagged(NODE_TAG, level[i], level[i + 1]));\n\t\t}\n\t\tlevel = next;\n\t\tidx = Math.floor(idx / 2);\n\t}\n\treturn path;\n}\n\n/**\n * Verify a tx-inclusion proof: fold the target `txid` (hex) up through `path`\n * and check it equals `txMerkleRoot` (hex). The verifier recomputes the txid\n * itself from the raw tx bytes, so nothing here is trusted.\n */\nexport function verifyTxMerkleProof(\n\ttxidHex: string,\n\tpath: MerkleProofStep[],\n\ttxMerkleRootHex: string,\n): boolean {\n\tlet acc = tagged(LEAF_TAG, fromHex(txidHex));\n\tfor (const step of path) {\n\t\tconst sib = fromHex(step.hash);\n\t\tacc =\n\t\t\tstep.position === \"right\"\n\t\t\t\t? tagged(NODE_TAG, acc, sib)\n\t\t\t\t: tagged(NODE_TAG, sib, acc);\n\t}\n\tconst root = txMerkleRootHex.startsWith(\"0x\")\n\t\t? txMerkleRootHex.slice(2)\n\t\t: txMerkleRootHex;\n\treturn toHex(acc) === root;\n}\n\n/**\n * Fetch and parse a Nakamoto block from a stacks-node. `blockId` is the\n * index_block_hash (with or without 0x). Returns the raw bytes + parsed header +\n * the recomputed block_hash / index_block_hash so a caller can cross-check.\n */\nexport async function fetchNakamotoBlock(opts: {\n\tnodeUrl: string;\n\tblockId: string;\n\tfetchImpl?: typeof fetch;\n}): Promise<{\n\traw: Uint8Array;\n\theader: NakamotoBlockHeader;\n\tblockHash: string;\n\tindexBlockHash: string;\n}> {\n\tconst id = opts.blockId.startsWith(\"0x\")\n\t\t? opts.blockId.slice(2)\n\t\t: opts.blockId;\n\tconst f = opts.fetchImpl ?? fetch;\n\tconst res = await f(`${opts.nodeUrl.replace(/\\/+$/, \"\")}/v3/blocks/${id}`);\n\tif (!res.ok) {\n\t\tthrow new Error(`/v3/blocks/${id} returned ${res.status}`);\n\t}\n\tconst raw = new Uint8Array(await res.arrayBuffer());\n\tconst header = parseNakamotoBlockHeader(raw);\n\tconst blockHash = nakamotoBlockHash(header);\n\treturn {\n\t\traw,\n\t\theader,\n\t\tblockHash,\n\t\tindexBlockHash: nakamotoBlockId(blockHash, header.consensusHash),\n\t};\n}\n"
|
|
5
|
+
"import { createHash } from \"node:crypto\";\n\n/**\n * Nakamoto block-header parsing + consensus hashing for trustless verification.\n *\n * Version 0 is bit-exact against mainnet `stacks-node 3.4.0.0.3`. Version 1\n * (Epoch 4.0 / PoX-5, first mainnet height 8,665,568) appends\n * `problematic_txs` after `pox_treatment` and includes those bytes in the\n * signer-signature-hash preimage. Pre-4.0 hashes are unchanged.\n */\n\n/** SHA-512/256 — the hash Stacks uses everywhere (NOT truncated SHA-512). */\nexport function sha512_256(bytes: Uint8Array): Uint8Array {\n\treturn createHash(\"sha512-256\").update(bytes).digest();\n}\n\nconst toHex = (b: Uint8Array): string => Buffer.from(b).toString(\"hex\");\nconst fromHex = (h: string): Uint8Array =>\n\tUint8Array.from(Buffer.from(h.startsWith(\"0x\") ? h.slice(2) : h, \"hex\"));\n\n// Fixed-size header prefix: version..miner_signature, before signer_signature.\nconst PREFIX_LEN = 206;\nconst CONSENSUS_HASH_OFF = 17;\nconst TX_MERKLE_ROOT_OFF = 69;\nconst STATE_INDEX_ROOT_OFF = 101;\nconst TIMESTAMP_OFF = 133;\nconst MINER_SIG_OFF = 141;\nconst SIGNER_VEC_OFF = 206; // u32 count, then 65 bytes per signer\nconst SIG_LEN = 65;\nconst PROBLEMATIC_TX_MARKER_LEN = 5; // u32 tx_index ‖ u8 category\n\n/** Pre-Epoch-4.0 Nakamoto header. Omits `problematic_txs`. */\nexport const NAKAMOTO_BLOCK_VERSION = 0;\n/** Epoch 4.0+ header. Serializes and hashes `problematic_txs`. */\nexport const NAKAMOTO_BLOCK_VERSION_EPOCH_4 = 1;\n\nexport type ProblematicTxMarker = {\n\ttxIndex: number;\n\tcategory: number;\n};\n\n/** Low 7 bits are the header version; high bit 0x80 is the shadow-block flag. */\nexport function versionIncludesProblematicTxs(version: number): boolean {\n\treturn (version & 0x7f) >= NAKAMOTO_BLOCK_VERSION_EPOCH_4;\n}\n\nexport interface NakamotoBlockHeader {\n\tversion: number;\n\tchainLength: bigint;\n\tburnSpent: bigint;\n\t/** 20-byte consensus hash (hex). */\n\tconsensusHash: string;\n\t/** 32-byte parent StacksBlockId (hex). */\n\tparentBlockId: string;\n\t/** 32-byte SHA512/256 merkle root over the block's txids (hex). */\n\ttxMerkleRoot: string;\n\t/** 32-byte MARF root after applying this block (hex). */\n\tstateIndexRoot: string;\n\ttimestamp: bigint;\n\t/** 65-byte recoverable ECDSA miner signature (hex). */\n\tminerSignature: string;\n\t/** Per-signer recoverable ECDSA signatures, reward-set order (hex, 65B each). */\n\tsignerSignatures: string[];\n\t/** Full serialized pox_treatment BitVec bytes (u16 bits ‖ u32 len ‖ data). */\n\tpoxTreatment: Uint8Array;\n\t/**\n\t * Epoch 4.0+ marker list. Empty (and absent from the wire) on version 0.\n\t * An empty list still serializes as a u32 count of 0 on version 1+.\n\t */\n\tproblematicTxs: ProblematicTxMarker[];\n\t/**\n\t * Exact bytes whose SHA512/256 IS the block_hash / signer_signature_hash:\n\t * header minus the signer_signature vector. Version 0 is\n\t * prefix[0:206] ‖ pox; version 1+ is prefix[0:206] ‖ pox ‖ problematic_txs.\n\t */\n\tsignerSignatureHashPreimage: Uint8Array;\n\t/** Offset at which the tx `Vec` begins (= total header byte length). */\n\theaderByteLength: number;\n}\n\nfunction u32(b: Uint8Array, off: number): number {\n\treturn new DataView(b.buffer, b.byteOffset, b.byteLength).getUint32(off);\n}\nfunction u64(b: Uint8Array, off: number): bigint {\n\treturn new DataView(b.buffer, b.byteOffset, b.byteLength).getBigUint64(off);\n}\n\n/** Parse the Nakamoto block header from the raw `/v3/blocks` body. */\nexport function parseNakamotoBlockHeader(raw: Uint8Array): NakamotoBlockHeader {\n\tif (raw.length < PREFIX_LEN + 4) {\n\t\tthrow new Error(\"raw block too short for a Nakamoto header\");\n\t}\n\tconst version = raw[0];\n\tconst signerCount = u32(raw, SIGNER_VEC_OFF);\n\tconst sigsStart = SIGNER_VEC_OFF + 4;\n\tif (sigsStart + signerCount * SIG_LEN > raw.length) {\n\t\tthrow new Error(\"raw block truncated in signer_signature vector\");\n\t}\n\tconst signerSignatures: string[] = [];\n\tfor (let i = 0; i < signerCount; i++) {\n\t\tconst off = sigsStart + i * SIG_LEN;\n\t\tsignerSignatures.push(toHex(raw.subarray(off, off + SIG_LEN)));\n\t}\n\t// pox_treatment: u16 num_bits ‖ u32 data_len ‖ data[data_len].\n\tconst poxOff = sigsStart + signerCount * SIG_LEN;\n\tif (poxOff + 6 > raw.length) {\n\t\tthrow new Error(\"raw block truncated in pox_treatment\");\n\t}\n\tconst poxDataLen = u32(raw, poxOff + 2);\n\tconst poxEnd = poxOff + 6 + poxDataLen;\n\tif (poxEnd > raw.length) {\n\t\tthrow new Error(\"raw block truncated in pox_treatment data\");\n\t}\n\tconst poxTreatment = raw.subarray(poxOff, poxEnd);\n\n\t// Epoch 4.0+ (version low-7-bits >= 1): Vec<ProblematicTxMarker>\n\t// after pox_treatment. Version 0 omits the field entirely so pre-fork\n\t// hashes stay byte-identical.\n\tlet headerEnd = poxEnd;\n\tconst problematicTxs: ProblematicTxMarker[] = [];\n\tif (versionIncludesProblematicTxs(version)) {\n\t\tif (poxEnd + 4 > raw.length) {\n\t\t\tthrow new Error(\"raw block truncated in problematic_txs count\");\n\t\t}\n\t\tconst markerCount = u32(raw, poxEnd);\n\t\theaderEnd = poxEnd + 4 + markerCount * PROBLEMATIC_TX_MARKER_LEN;\n\t\tif (headerEnd > raw.length) {\n\t\t\tthrow new Error(\"raw block truncated in problematic_txs list\");\n\t\t}\n\t\tfor (let i = 0; i < markerCount; i++) {\n\t\t\tconst off = poxEnd + 4 + i * PROBLEMATIC_TX_MARKER_LEN;\n\t\t\tproblematicTxs.push({\n\t\t\t\ttxIndex: u32(raw, off),\n\t\t\t\tcategory: raw[off + 4] ?? 0,\n\t\t\t});\n\t\t}\n\t}\n\n\t// block_hash preimage = header minus signer_signature.\n\t// v0: prefix[0:206] ‖ pox\n\t// v1: prefix[0:206] ‖ pox ‖ problematic_txs (count + markers)\n\tconst markersBytes = raw.subarray(poxEnd, headerEnd);\n\tconst preimage = new Uint8Array(\n\t\tPREFIX_LEN + poxTreatment.length + markersBytes.length,\n\t);\n\tpreimage.set(raw.subarray(0, PREFIX_LEN), 0);\n\tpreimage.set(poxTreatment, PREFIX_LEN);\n\tpreimage.set(markersBytes, PREFIX_LEN + poxTreatment.length);\n\n\treturn {\n\t\tversion,\n\t\tchainLength: u64(raw, 1),\n\t\tburnSpent: u64(raw, 9),\n\t\tconsensusHash: toHex(\n\t\t\traw.subarray(CONSENSUS_HASH_OFF, CONSENSUS_HASH_OFF + 20),\n\t\t),\n\t\tparentBlockId: toHex(raw.subarray(37, 69)),\n\t\ttxMerkleRoot: toHex(\n\t\t\traw.subarray(TX_MERKLE_ROOT_OFF, TX_MERKLE_ROOT_OFF + 32),\n\t\t),\n\t\tstateIndexRoot: toHex(\n\t\t\traw.subarray(STATE_INDEX_ROOT_OFF, STATE_INDEX_ROOT_OFF + 32),\n\t\t),\n\t\ttimestamp: u64(raw, TIMESTAMP_OFF),\n\t\tminerSignature: toHex(raw.subarray(MINER_SIG_OFF, MINER_SIG_OFF + SIG_LEN)),\n\t\tsignerSignatures,\n\t\tpoxTreatment,\n\t\tproblematicTxs,\n\t\tsignerSignatureHashPreimage: preimage,\n\t\theaderByteLength: headerEnd,\n\t};\n}\n\n/**\n * block_hash (== signer_signature_hash): SHA512/256 over the header with the\n * signer_signature vector omitted. This is what each signer signs.\n */\nexport function nakamotoBlockHash(header: NakamotoBlockHeader): string {\n\treturn toHex(sha512_256(header.signerSignatureHashPreimage));\n}\n\n/** index_block_hash (StacksBlockId) = SHA512/256(block_hash ‖ consensus_hash). */\nexport function nakamotoBlockId(\n\tblockHashHex: string,\n\tconsensusHashHex: string,\n): string {\n\tconst a = fromHex(blockHashHex);\n\tconst b = fromHex(consensusHashHex);\n\tconst buf = new Uint8Array(a.length + b.length);\n\tbuf.set(a, 0);\n\tbuf.set(b, a.length);\n\treturn toHex(sha512_256(buf));\n}\n\n/** A Stacks txid = SHA512/256 of the transaction's consensus serialization. */\nexport function stacksTxid(rawTx: Uint8Array): string {\n\treturn toHex(sha512_256(rawTx));\n}\n\nconst LEAF_TAG = 0x00;\nconst NODE_TAG = 0x01;\n\nfunction tagged(tag: number, ...parts: Uint8Array[]): Uint8Array {\n\tconst len = parts.reduce((n, p) => n + p.length, 1);\n\tconst buf = new Uint8Array(len);\n\tbuf[0] = tag;\n\tlet o = 1;\n\tfor (const p of parts) {\n\t\tbuf.set(p, o);\n\t\to += p.length;\n\t}\n\treturn sha512_256(buf);\n}\n\n/**\n * tx_merkle_root over the block's txids (hex), reproducing the consensus rule:\n * leaf = H(0x00 ‖ txid), node = H(0x01 ‖ left ‖ right), odd level duplicates the\n * last node. Returns the root hex; throws on an empty tx list.\n */\nexport function txMerkleRoot(txidsHex: string[]): string {\n\tif (txidsHex.length === 0) throw new Error(\"no transactions\");\n\tlet level = txidsHex.map((t) => tagged(LEAF_TAG, fromHex(t)));\n\twhile (level.length > 1) {\n\t\tif (level.length % 2 === 1) level.push(level[level.length - 1]);\n\t\tconst next: Uint8Array[] = [];\n\t\tfor (let i = 0; i < level.length; i += 2) {\n\t\t\tnext.push(tagged(NODE_TAG, level[i], level[i + 1]));\n\t\t}\n\t\tlevel = next;\n\t}\n\treturn toHex(level[0]);\n}\n\n/** One authentication-path step: the sibling hash and which side it's on. */\nexport interface MerkleProofStep {\n\t/** Side the SIBLING is on relative to the accumulator. */\n\tposition: \"left\" | \"right\";\n\t/** Sibling node hash (hex). */\n\thash: string;\n}\n\n/**\n * Build the tx-inclusion authentication path for the tx at `index` in a block,\n * reproducing the consensus merkle tree (incl. duplicate-last-on-odd). The path\n * lets a verifier recompute `tx_merkle_root` from just the target txid.\n */\nexport function txMerkleProof(\n\ttxidsHex: string[],\n\tindex: number,\n): MerkleProofStep[] {\n\tif (index < 0 || index >= txidsHex.length) {\n\t\tthrow new Error(\"index out of range\");\n\t}\n\tlet level = txidsHex.map((t) => tagged(LEAF_TAG, fromHex(t)));\n\tlet idx = index;\n\tconst path: MerkleProofStep[] = [];\n\twhile (level.length > 1) {\n\t\tif (level.length % 2 === 1) level.push(level[level.length - 1]);\n\t\tconst siblingIdx = idx % 2 === 0 ? idx + 1 : idx - 1;\n\t\tpath.push({\n\t\t\tposition: idx % 2 === 0 ? \"right\" : \"left\",\n\t\t\thash: toHex(level[siblingIdx]),\n\t\t});\n\t\tconst next: Uint8Array[] = [];\n\t\tfor (let i = 0; i < level.length; i += 2) {\n\t\t\tnext.push(tagged(NODE_TAG, level[i], level[i + 1]));\n\t\t}\n\t\tlevel = next;\n\t\tidx = Math.floor(idx / 2);\n\t}\n\treturn path;\n}\n\n/**\n * Verify a tx-inclusion proof: fold the target `txid` (hex) up through `path`\n * and check it equals `txMerkleRoot` (hex). The verifier recomputes the txid\n * itself from the raw tx bytes, so nothing here is trusted.\n */\nexport function verifyTxMerkleProof(\n\ttxidHex: string,\n\tpath: MerkleProofStep[],\n\ttxMerkleRootHex: string,\n): boolean {\n\tlet acc = tagged(LEAF_TAG, fromHex(txidHex));\n\tfor (const step of path) {\n\t\tconst sib = fromHex(step.hash);\n\t\tacc =\n\t\t\tstep.position === \"right\"\n\t\t\t\t? tagged(NODE_TAG, acc, sib)\n\t\t\t\t: tagged(NODE_TAG, sib, acc);\n\t}\n\tconst root = txMerkleRootHex.startsWith(\"0x\")\n\t\t? txMerkleRootHex.slice(2)\n\t\t: txMerkleRootHex;\n\treturn toHex(acc) === root;\n}\n\n/**\n * Fetch and parse a Nakamoto block from a stacks-node. `blockId` is the\n * index_block_hash (with or without 0x). Returns the raw bytes + parsed header +\n * the recomputed block_hash / index_block_hash so a caller can cross-check.\n */\nexport async function fetchNakamotoBlock(opts: {\n\tnodeUrl: string;\n\tblockId: string;\n\tfetchImpl?: typeof fetch;\n}): Promise<{\n\traw: Uint8Array;\n\theader: NakamotoBlockHeader;\n\tblockHash: string;\n\tindexBlockHash: string;\n}> {\n\tconst id = opts.blockId.startsWith(\"0x\")\n\t\t? opts.blockId.slice(2)\n\t\t: opts.blockId;\n\tconst f = opts.fetchImpl ?? fetch;\n\tconst res = await f(`${opts.nodeUrl.replace(/\\/+$/, \"\")}/v3/blocks/${id}`);\n\tif (!res.ok) {\n\t\tthrow new Error(`/v3/blocks/${id} returned ${res.status}`);\n\t}\n\tconst raw = new Uint8Array(await res.arrayBuffer());\n\tconst header = parseNakamotoBlockHeader(raw);\n\tconst blockHash = nakamotoBlockHash(header);\n\treturn {\n\t\traw,\n\t\theader,\n\t\tblockHash,\n\t\tindexBlockHash: nakamotoBlockId(blockHash, header.consensusHash),\n\t};\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;AAAA;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;AAAA;AAYO,SAAS,UAAU,CAAC,OAA+B;AAAA,EACzD,OAAO,WAAW,YAAY,EAAE,OAAO,KAAK,EAAE,OAAO;AAAA;AAGtD,IAAM,QAAQ,CAAC,MAA0B,OAAO,KAAK,CAAC,EAAE,SAAS,KAAK;AACtE,IAAM,UAAU,CAAC,MAChB,WAAW,KAAK,OAAO,KAAK,EAAE,WAAW,IAAI,IAAI,EAAE,MAAM,CAAC,IAAI,GAAG,KAAK,CAAC;AAGxE,IAAM,aAAa;AACnB,IAAM,qBAAqB;AAC3B,IAAM,qBAAqB;AAC3B,IAAM,uBAAuB;AAC7B,IAAM,gBAAgB;AACtB,IAAM,gBAAgB;AACtB,IAAM,iBAAiB;AACvB,IAAM,UAAU;AAChB,IAAM,4BAA4B;AAG3B,IAAM,yBAAyB;AAE/B,IAAM,iCAAiC;AAQvC,SAAS,6BAA6B,CAAC,SAA0B;AAAA,EACvE,QAAQ,UAAU,QAAS;AAAA;AAqC5B,SAAS,GAAG,CAAC,GAAe,KAAqB;AAAA,EAChD,OAAO,IAAI,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,GAAG;AAAA;AAExE,SAAS,GAAG,CAAC,GAAe,KAAqB;AAAA,EAChD,OAAO,IAAI,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,GAAG;AAAA;AAIpE,SAAS,wBAAwB,CAAC,KAAsC;AAAA,EAC9E,IAAI,IAAI,SAAS,aAAa,GAAG;AAAA,IAChC,MAAM,IAAI,MAAM,2CAA2C;AAAA,EAC5D;AAAA,EACA,MAAM,UAAU,IAAI;AAAA,EACpB,MAAM,cAAc,IAAI,KAAK,cAAc;AAAA,EAC3C,MAAM,YAAY,iBAAiB;AAAA,EACnC,IAAI,YAAY,cAAc,UAAU,IAAI,QAAQ;AAAA,IACnD,MAAM,IAAI,MAAM,gDAAgD;AAAA,EACjE;AAAA,EACA,MAAM,mBAA6B,CAAC;AAAA,EACpC,SAAS,IAAI,EAAG,IAAI,aAAa,KAAK;AAAA,IACrC,MAAM,MAAM,YAAY,IAAI;AAAA,IAC5B,iBAAiB,KAAK,MAAM,IAAI,SAAS,KAAK,MAAM,OAAO,CAAC,CAAC;AAAA,EAC9D;AAAA,EAEA,MAAM,SAAS,YAAY,cAAc;AAAA,EACzC,IAAI,SAAS,IAAI,IAAI,QAAQ;AAAA,IAC5B,MAAM,IAAI,MAAM,sCAAsC;AAAA,EACvD;AAAA,EACA,MAAM,aAAa,IAAI,KAAK,SAAS,CAAC;AAAA,EACtC,MAAM,SAAS,SAAS,IAAI;AAAA,EAC5B,IAAI,SAAS,IAAI,QAAQ;AAAA,IACxB,MAAM,IAAI,MAAM,2CAA2C;AAAA,EAC5D;AAAA,EACA,MAAM,eAAe,IAAI,SAAS,QAAQ,MAAM;AAAA,EAKhD,IAAI,YAAY;AAAA,EAChB,MAAM,iBAAwC,CAAC;AAAA,EAC/C,IAAI,8BAA8B,OAAO,GAAG;AAAA,IAC3C,IAAI,SAAS,IAAI,IAAI,QAAQ;AAAA,MAC5B,MAAM,IAAI,MAAM,8CAA8C;AAAA,IAC/D;AAAA,IACA,MAAM,cAAc,IAAI,KAAK,MAAM;AAAA,IACnC,YAAY,SAAS,IAAI,cAAc;AAAA,IACvC,IAAI,YAAY,IAAI,QAAQ;AAAA,MAC3B,MAAM,IAAI,MAAM,6CAA6C;AAAA,IAC9D;AAAA,IACA,SAAS,IAAI,EAAG,IAAI,aAAa,KAAK;AAAA,MACrC,MAAM,MAAM,SAAS,IAAI,IAAI;AAAA,MAC7B,eAAe,KAAK;AAAA,QACnB,SAAS,IAAI,KAAK,GAAG;AAAA,QACrB,UAAU,IAAI,MAAM,MAAM;AAAA,MAC3B,CAAC;AAAA,IACF;AAAA,EACD;AAAA,EAKA,MAAM,eAAe,IAAI,SAAS,QAAQ,SAAS;AAAA,EACnD,MAAM,WAAW,IAAI,WACpB,aAAa,aAAa,SAAS,aAAa,MACjD;AAAA,EACA,SAAS,IAAI,IAAI,SAAS,GAAG,UAAU,GAAG,CAAC;AAAA,EAC3C,SAAS,IAAI,cAAc,UAAU;AAAA,EACrC,SAAS,IAAI,cAAc,aAAa,aAAa,MAAM;AAAA,EAE3D,OAAO;AAAA,IACN;AAAA,IACA,aAAa,IAAI,KAAK,CAAC;AAAA,IACvB,WAAW,IAAI,KAAK,CAAC;AAAA,IACrB,eAAe,MACd,IAAI,SAAS,oBAAoB,qBAAqB,EAAE,CACzD;AAAA,IACA,eAAe,MAAM,IAAI,SAAS,IAAI,EAAE,CAAC;AAAA,IACzC,cAAc,MACb,IAAI,SAAS,oBAAoB,qBAAqB,EAAE,CACzD;AAAA,IACA,gBAAgB,MACf,IAAI,SAAS,sBAAsB,uBAAuB,EAAE,CAC7D;AAAA,IACA,WAAW,IAAI,KAAK,aAAa;AAAA,IACjC,gBAAgB,MAAM,IAAI,SAAS,eAAe,gBAAgB,OAAO,CAAC;AAAA,IAC1E;AAAA,IACA;AAAA,IACA;AAAA,IACA,6BAA6B;AAAA,IAC7B,kBAAkB;AAAA,EACnB;AAAA;AAOM,SAAS,iBAAiB,CAAC,QAAqC;AAAA,EACtE,OAAO,MAAM,WAAW,OAAO,2BAA2B,CAAC;AAAA;AAIrD,SAAS,eAAe,CAC9B,cACA,kBACS;AAAA,EACT,MAAM,IAAI,QAAQ,YAAY;AAAA,EAC9B,MAAM,IAAI,QAAQ,gBAAgB;AAAA,EAClC,MAAM,MAAM,IAAI,WAAW,EAAE,SAAS,EAAE,MAAM;AAAA,EAC9C,IAAI,IAAI,GAAG,CAAC;AAAA,EACZ,IAAI,IAAI,GAAG,EAAE,MAAM;AAAA,EACnB,OAAO,MAAM,WAAW,GAAG,CAAC;AAAA;AAItB,SAAS,UAAU,CAAC,OAA2B;AAAA,EACrD,OAAO,MAAM,WAAW,KAAK,CAAC;AAAA;AAG/B,IAAM,WAAW;AACjB,IAAM,WAAW;AAEjB,SAAS,MAAM,CAAC,QAAgB,OAAiC;AAAA,EAChE,MAAM,MAAM,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,QAAQ,CAAC;AAAA,EAClD,MAAM,MAAM,IAAI,WAAW,GAAG;AAAA,EAC9B,IAAI,KAAK;AAAA,EACT,IAAI,IAAI;AAAA,EACR,WAAW,KAAK,OAAO;AAAA,IACtB,IAAI,IAAI,GAAG,CAAC;AAAA,IACZ,KAAK,EAAE;AAAA,EACR;AAAA,EACA,OAAO,WAAW,GAAG;AAAA;AAQf,SAAS,YAAY,CAAC,UAA4B;AAAA,EACxD,IAAI,SAAS,WAAW;AAAA,IAAG,MAAM,IAAI,MAAM,iBAAiB;AAAA,EAC5D,IAAI,QAAQ,SAAS,IAAI,CAAC,MAAM,OAAO,UAAU,QAAQ,CAAC,CAAC,CAAC;AAAA,EAC5D,OAAO,MAAM,SAAS,GAAG;AAAA,IACxB,IAAI,MAAM,SAAS,MAAM;AAAA,MAAG,MAAM,KAAK,MAAM,MAAM,SAAS,EAAE;AAAA,IAC9D,MAAM,OAAqB,CAAC;AAAA,IAC5B,SAAS,IAAI,EAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;AAAA,MACzC,KAAK,KAAK,OAAO,UAAU,MAAM,IAAI,MAAM,IAAI,EAAE,CAAC;AAAA,IACnD;AAAA,IACA,QAAQ;AAAA,EACT;AAAA,EACA,OAAO,MAAM,MAAM,EAAE;AAAA;AAgBf,SAAS,aAAa,CAC5B,UACA,OACoB;AAAA,EACpB,IAAI,QAAQ,KAAK,SAAS,SAAS,QAAQ;AAAA,IAC1C,MAAM,IAAI,MAAM,oBAAoB;AAAA,EACrC;AAAA,EACA,IAAI,QAAQ,SAAS,IAAI,CAAC,MAAM,OAAO,UAAU,QAAQ,CAAC,CAAC,CAAC;AAAA,EAC5D,IAAI,MAAM;AAAA,EACV,MAAM,OAA0B,CAAC;AAAA,EACjC,OAAO,MAAM,SAAS,GAAG;AAAA,IACxB,IAAI,MAAM,SAAS,MAAM;AAAA,MAAG,MAAM,KAAK,MAAM,MAAM,SAAS,EAAE;AAAA,IAC9D,MAAM,aAAa,MAAM,MAAM,IAAI,MAAM,IAAI,MAAM;AAAA,IACnD,KAAK,KAAK;AAAA,MACT,UAAU,MAAM,MAAM,IAAI,UAAU;AAAA,MACpC,MAAM,MAAM,MAAM,WAAW;AAAA,IAC9B,CAAC;AAAA,IACD,MAAM,OAAqB,CAAC;AAAA,IAC5B,SAAS,IAAI,EAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;AAAA,MACzC,KAAK,KAAK,OAAO,UAAU,MAAM,IAAI,MAAM,IAAI,EAAE,CAAC;AAAA,IACnD;AAAA,IACA,QAAQ;AAAA,IACR,MAAM,KAAK,MAAM,MAAM,CAAC;AAAA,EACzB;AAAA,EACA,OAAO;AAAA;AAQD,SAAS,mBAAmB,CAClC,SACA,MACA,iBACU;AAAA,EACV,IAAI,MAAM,OAAO,UAAU,QAAQ,OAAO,CAAC;AAAA,EAC3C,WAAW,QAAQ,MAAM;AAAA,IACxB,MAAM,MAAM,QAAQ,KAAK,IAAI;AAAA,IAC7B,MACC,KAAK,aAAa,UACf,OAAO,UAAU,KAAK,GAAG,IACzB,OAAO,UAAU,KAAK,GAAG;AAAA,EAC9B;AAAA,EACA,MAAM,OAAO,gBAAgB,WAAW,IAAI,IACzC,gBAAgB,MAAM,CAAC,IACvB;AAAA,EACH,OAAO,MAAM,GAAG,MAAM;AAAA;AAQvB,eAAsB,kBAAkB,CAAC,MAStC;AAAA,EACF,MAAM,KAAK,KAAK,QAAQ,WAAW,IAAI,IACpC,KAAK,QAAQ,MAAM,CAAC,IACpB,KAAK;AAAA,EACR,MAAM,IAAI,KAAK,aAAa;AAAA,EAC5B,MAAM,MAAM,MAAM,EAAE,GAAG,KAAK,QAAQ,QAAQ,QAAQ,EAAE,eAAe,IAAI;AAAA,EACzE,IAAI,CAAC,IAAI,IAAI;AAAA,IACZ,MAAM,IAAI,MAAM,cAAc,eAAe,IAAI,QAAQ;AAAA,EAC1D;AAAA,EACA,MAAM,MAAM,IAAI,WAAW,MAAM,IAAI,YAAY,CAAC;AAAA,EAClD,MAAM,SAAS,yBAAyB,GAAG;AAAA,EAC3C,MAAM,YAAY,kBAAkB,MAAM;AAAA,EAC1C,OAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB,gBAAgB,WAAW,OAAO,aAAa;AAAA,EAChE;AAAA;",
|
|
8
|
+
"debugId": "7772B63DB5967E2A64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type Kysely, sql } from "kysely";
|
|
2
|
+
import { onControlPlane } from "../src/db/migration-role.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Local instance identity — one row per database, network immutable.
|
|
6
|
+
*
|
|
7
|
+
* Self-host has no account. This is the identity root: later auth, namespace,
|
|
8
|
+
* and bootstrap hang off it instead of `accounts`. Hosted/platform keeps
|
|
9
|
+
* accounts; this table still exists there as the publisher's network pin.
|
|
10
|
+
*/
|
|
11
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
12
|
+
await onControlPlane(async () => {
|
|
13
|
+
await sql`SET lock_timeout = '30s'`.execute(db);
|
|
14
|
+
await sql`
|
|
15
|
+
CREATE TABLE instances (
|
|
16
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
17
|
+
network TEXT NOT NULL
|
|
18
|
+
CHECK (network IN ('mainnet', 'testnet', 'devnet')),
|
|
19
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
20
|
+
)
|
|
21
|
+
`.execute(db);
|
|
22
|
+
// At most one instance per database.
|
|
23
|
+
await sql`
|
|
24
|
+
CREATE UNIQUE INDEX instances_singleton ON instances ((TRUE))
|
|
25
|
+
`.execute(db);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
30
|
+
await onControlPlane(async () => {
|
|
31
|
+
await sql`DROP TABLE IF EXISTS instances`.execute(db);
|
|
32
|
+
});
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@secondlayer/shared",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.9.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
"types": "./dist/src/db/index.d.ts",
|
|
21
21
|
"import": "./dist/src/db/index.js"
|
|
22
22
|
},
|
|
23
|
+
"./db/queries/instance": {
|
|
24
|
+
"types": "./dist/src/db/queries/instance.d.ts",
|
|
25
|
+
"import": "./dist/src/db/queries/instance.js"
|
|
26
|
+
},
|
|
23
27
|
"./db/queries/integrity": {
|
|
24
28
|
"types": "./dist/src/db/queries/integrity.d.ts",
|
|
25
29
|
"import": "./dist/src/db/queries/integrity.js"
|
|
@@ -108,6 +112,10 @@
|
|
|
108
112
|
"types": "./dist/src/streams-bulk-manifest.d.ts",
|
|
109
113
|
"import": "./dist/src/streams-bulk-manifest.js"
|
|
110
114
|
},
|
|
115
|
+
"./archive/disk-guard": {
|
|
116
|
+
"types": "./dist/src/archive/disk-guard.d.ts",
|
|
117
|
+
"import": "./dist/src/archive/disk-guard.js"
|
|
118
|
+
},
|
|
111
119
|
"./archive/progress": {
|
|
112
120
|
"types": "./dist/src/archive/progress.d.ts",
|
|
113
121
|
"import": "./dist/src/archive/progress.js"
|
|
@@ -128,6 +136,22 @@
|
|
|
128
136
|
"types": "./dist/src/archive/range-digest.d.ts",
|
|
129
137
|
"import": "./dist/src/archive/range-digest.js"
|
|
130
138
|
},
|
|
139
|
+
"./archive/semantic-digest": {
|
|
140
|
+
"types": "./dist/src/archive/semantic-digest.d.ts",
|
|
141
|
+
"import": "./dist/src/archive/semantic-digest.js"
|
|
142
|
+
},
|
|
143
|
+
"./archive/semantic-digest-builder": {
|
|
144
|
+
"types": "./dist/src/archive/semantic-digest-builder.d.ts",
|
|
145
|
+
"import": "./dist/src/archive/semantic-digest-builder.js"
|
|
146
|
+
},
|
|
147
|
+
"./archive/per-height-digest": {
|
|
148
|
+
"types": "./dist/src/archive/per-height-digest.d.ts",
|
|
149
|
+
"import": "./dist/src/archive/per-height-digest.js"
|
|
150
|
+
},
|
|
151
|
+
"./archive/derived-stage-report": {
|
|
152
|
+
"types": "./dist/src/archive/derived-stage-report.d.ts",
|
|
153
|
+
"import": "./dist/src/archive/derived-stage-report.js"
|
|
154
|
+
},
|
|
131
155
|
"./errors": {
|
|
132
156
|
"types": "./dist/src/errors.d.ts",
|
|
133
157
|
"import": "./dist/src/errors.js"
|