@cogcoin/client 0.5.4 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +1 -1
  2. package/dist/app-paths.d.ts +2 -0
  3. package/dist/app-paths.js +4 -0
  4. package/dist/art/wallet.txt +9 -9
  5. package/dist/bitcoind/bootstrap/chunk-manifest.d.ts +14 -0
  6. package/dist/bitcoind/bootstrap/chunk-manifest.js +85 -0
  7. package/dist/bitcoind/bootstrap/chunk-recovery.d.ts +4 -0
  8. package/dist/bitcoind/bootstrap/chunk-recovery.js +122 -0
  9. package/dist/bitcoind/bootstrap/constants.d.ts +3 -1
  10. package/dist/bitcoind/bootstrap/constants.js +3 -1
  11. package/dist/bitcoind/bootstrap/controller.d.ts +6 -1
  12. package/dist/bitcoind/bootstrap/controller.js +14 -7
  13. package/dist/bitcoind/bootstrap/default-snapshot-chunk-manifest.d.ts +2 -0
  14. package/dist/bitcoind/bootstrap/default-snapshot-chunk-manifest.js +2309 -0
  15. package/dist/bitcoind/bootstrap/download.js +177 -83
  16. package/dist/bitcoind/bootstrap/headers.d.ts +4 -2
  17. package/dist/bitcoind/bootstrap/headers.js +29 -4
  18. package/dist/bitcoind/bootstrap/state.d.ts +11 -1
  19. package/dist/bitcoind/bootstrap/state.js +50 -23
  20. package/dist/bitcoind/bootstrap/types.d.ts +12 -1
  21. package/dist/bitcoind/client/internal-types.d.ts +1 -0
  22. package/dist/bitcoind/client/managed-client.js +27 -13
  23. package/dist/bitcoind/client/sync-engine.js +42 -5
  24. package/dist/bitcoind/errors.js +9 -0
  25. package/dist/bitcoind/types.d.ts +9 -0
  26. package/dist/cli/output.js +1 -1
  27. package/dist/wallet/lifecycle.js +64 -5
  28. package/dist/wallet/runtime.d.ts +2 -0
  29. package/dist/wallet/runtime.js +2 -0
  30. package/dist/wallet/state/pending-init.d.ts +24 -0
  31. package/dist/wallet/state/pending-init.js +59 -0
  32. package/dist/wallet/state/provider.d.ts +1 -0
  33. package/dist/wallet/state/provider.js +7 -1
  34. package/dist/wallet/types.d.ts +8 -0
  35. package/package.json +4 -2
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # `@cogcoin/client`
2
2
 
3
- `@cogcoin/client@0.5.4` is the store-backed Cogcoin client package for applications that want a local wallet, durable SQLite-backed state, and a managed Bitcoin Core integration around `@cogcoin/indexer`. It publishes the reusable client APIs, the SQLite adapter, the managed `bitcoind` integration, and the first-party `cogcoin` CLI in one package.
3
+ `@cogcoin/client@0.5.5` is the store-backed Cogcoin client package for applications that want a local wallet, durable SQLite-backed state, and a managed Bitcoin Core integration around `@cogcoin/indexer`. It publishes the reusable client APIs, the SQLite adapter, the managed `bitcoind` integration, and the first-party `cogcoin` CLI in one package.
4
4
 
5
5
  Use Node 22 or newer.
6
6
 
@@ -17,6 +17,8 @@ export interface CogcoinResolvedPaths {
17
17
  walletStateDirectory: string;
18
18
  walletStatePath: string;
19
19
  walletStateBackupPath: string;
20
+ walletInitPendingPath: string;
21
+ walletInitPendingBackupPath: string;
20
22
  walletUnlockSessionPath: string;
21
23
  walletExplicitLockPath: string;
22
24
  walletControlLockPath: string;
package/dist/app-paths.js CHANGED
@@ -63,6 +63,8 @@ export function resolveCogcoinPathsForTesting(resolution = {}) {
63
63
  const walletStateDirectory = stateRoot;
64
64
  const walletStatePath = joinForPlatform(platform, walletStateDirectory, "wallet-state.enc");
65
65
  const walletStateBackupPath = joinForPlatform(platform, walletStateDirectory, "wallet-state.enc.bak");
66
+ const walletInitPendingPath = joinForPlatform(platform, walletStateDirectory, "wallet-init-pending.enc");
67
+ const walletInitPendingBackupPath = joinForPlatform(platform, walletStateDirectory, "wallet-init-pending.enc.bak");
66
68
  const walletUnlockSessionPath = joinForPlatform(platform, runtimeRoot, "wallet-unlock-session.enc");
67
69
  const walletExplicitLockPath = joinForPlatform(platform, runtimeRoot, "wallet-explicit-lock.json");
68
70
  const walletControlLockPath = joinForPlatform(platform, runtimeRoot, "wallet-control.lock");
@@ -91,6 +93,8 @@ export function resolveCogcoinPathsForTesting(resolution = {}) {
91
93
  walletStateDirectory,
92
94
  walletStatePath,
93
95
  walletStateBackupPath,
96
+ walletInitPendingPath,
97
+ walletInitPendingBackupPath,
94
98
  walletUnlockSessionPath,
95
99
  walletExplicitLockPath,
96
100
  walletControlLockPath,
@@ -1,10 +1,10 @@
1
1
  ⛭ Write this down. Keep it secret, keep it safe. ⛭
2
- ▐▀▀▀▀▀▀▚ ╔──────────────────────────────────────────────────────────────────╗
3
- ▛▞▀▀▀▀▀▀▚ │ │
4
- ▌▝▀▀▀▀▀▀▀▌ 1.achieved 6.achieved 11.achieved 16.achieved 21.achieved │
5
- ▗▙▙ 2.achieved 7.achieved 12.achieved 17.achieved 22.achieved │
6
- ▐ ▌ ▌ │ 3.achieved 8.achieved 13.achieved 18.achieved 23.achieved │
7
- ▐▀▀▚ ▌ │ 4.achieved 9.achieved 14.achieved 19.achieved 24.achieved │
8
- ▐▄▄▞ ▌ │ 5.achieved 10.achieved 15.achieved 20.achieved │
9
- ▘▘ ▌ │
10
- ▝▀▀▀▀▀▀▀▀▘ ╚──────────────────────────────────────────────────────────────────╝
2
+ ▐▀▀▀▀▀▀▀▀▚ ╔──────────────────────────────────────────────────────────────────╗
3
+ ▛▞▀▀▀▀▀▀▀▀▚ │ │
4
+ ▌▝▀▀▀▀▀▀▀▀▀▌│ 1.achieved 6.achieved 11.achieved 16.achieved 21.achieved │
5
+ ▗▙▙ ▌│ 2.achieved 7.achieved 12.achieved 17.achieved 22.achieved │
6
+ ▐ ▌ ▌│ 3.achieved 8.achieved 13.achieved 18.achieved 23.achieved │
7
+ ▐▀▀▚ ▌│ 4.achieved 9.achieved 14.achieved 19.achieved 24.achieved │
8
+ ▐▄▄▞ ▌│ 5.achieved 10.achieved 15.achieved 20.achieved │
9
+ ▘▘ ▌│
10
+ ▝▀▀▀▀▀▀▀▀▀▀▘╚──────────────────────────────────────────────────────────────────╝
@@ -0,0 +1,14 @@
1
+ import type { BootstrapPersistentState } from "./types.js";
2
+ import type { SnapshotChunkManifest, SnapshotMetadata } from "../types.js";
3
+ export declare function resolveBundledSnapshotChunkManifest(snapshot?: SnapshotMetadata): SnapshotChunkManifest;
4
+ export declare function resolveSnapshotChunkCount(manifest: SnapshotChunkManifest): number;
5
+ export declare function resolveSnapshotChunkSize(manifest: SnapshotChunkManifest, chunkIndex: number): number;
6
+ export declare function resolveVerifiedChunkBytes(manifest: SnapshotChunkManifest, verifiedChunkCount: number): number;
7
+ export declare function resolveVerifiedChunkCountFromBytes(manifest: SnapshotChunkManifest, bytes: number): number;
8
+ export declare function clampVerifiedChunkCount(manifest: SnapshotChunkManifest, verifiedChunkCount: number): number;
9
+ export declare function stateHasTrustedIntegrityFrontier(state: BootstrapPersistentState, manifest: SnapshotChunkManifest): boolean;
10
+ export declare function createSnapshotChunkManifestRecord(options: {
11
+ snapshot: SnapshotMetadata;
12
+ chunkSha256s: string[];
13
+ chunkSizeBytes?: number;
14
+ }): SnapshotChunkManifest;
@@ -0,0 +1,85 @@
1
+ import { DEFAULT_SNAPSHOT_CHUNK_SIZE_BYTES, SNAPSHOT_CHUNK_MANIFEST_VERSION, } from "./constants.js";
2
+ import { DEFAULT_SNAPSHOT_CHUNK_MANIFEST } from "./default-snapshot-chunk-manifest.js";
3
+ import { DEFAULT_SNAPSHOT_METADATA } from "./constants.js";
4
+ function snapshotMatchesManifest(manifest, snapshot) {
5
+ return manifest.snapshotFilename === snapshot.filename
6
+ && manifest.snapshotHeight === snapshot.height
7
+ && manifest.snapshotSizeBytes === snapshot.sizeBytes
8
+ && manifest.snapshotSha256 === snapshot.sha256;
9
+ }
10
+ function snapshotMatchesDefault(snapshot) {
11
+ return snapshot.filename === DEFAULT_SNAPSHOT_METADATA.filename
12
+ && snapshot.height === DEFAULT_SNAPSHOT_METADATA.height
13
+ && snapshot.sizeBytes === DEFAULT_SNAPSHOT_METADATA.sizeBytes
14
+ && snapshot.sha256 === DEFAULT_SNAPSHOT_METADATA.sha256;
15
+ }
16
+ export function resolveBundledSnapshotChunkManifest(snapshot = DEFAULT_SNAPSHOT_METADATA) {
17
+ if (!snapshotMatchesDefault(snapshot)) {
18
+ throw new Error(`snapshot_chunk_manifest_unavailable_${snapshot.filename}`);
19
+ }
20
+ if (!snapshotMatchesManifest(DEFAULT_SNAPSHOT_CHUNK_MANIFEST, snapshot)) {
21
+ throw new Error(`snapshot_chunk_manifest_mismatch_${snapshot.filename}`);
22
+ }
23
+ return DEFAULT_SNAPSHOT_CHUNK_MANIFEST;
24
+ }
25
+ export function resolveSnapshotChunkCount(manifest) {
26
+ return manifest.chunkSha256s.length;
27
+ }
28
+ export function resolveSnapshotChunkSize(manifest, chunkIndex) {
29
+ if (chunkIndex < 0 || chunkIndex >= manifest.chunkSha256s.length) {
30
+ throw new Error(`snapshot_chunk_index_out_of_range_${chunkIndex}`);
31
+ }
32
+ const lastChunkIndex = manifest.chunkSha256s.length - 1;
33
+ if (chunkIndex < lastChunkIndex) {
34
+ return manifest.chunkSizeBytes;
35
+ }
36
+ const trailingBytes = manifest.snapshotSizeBytes % manifest.chunkSizeBytes;
37
+ return trailingBytes === 0 ? manifest.chunkSizeBytes : trailingBytes;
38
+ }
39
+ export function resolveVerifiedChunkBytes(manifest, verifiedChunkCount) {
40
+ const chunkCount = clampVerifiedChunkCount(manifest, verifiedChunkCount);
41
+ if (chunkCount <= 0) {
42
+ return 0;
43
+ }
44
+ if (chunkCount >= manifest.chunkSha256s.length) {
45
+ return manifest.snapshotSizeBytes;
46
+ }
47
+ return chunkCount * manifest.chunkSizeBytes;
48
+ }
49
+ export function resolveVerifiedChunkCountFromBytes(manifest, bytes) {
50
+ if (bytes >= manifest.snapshotSizeBytes) {
51
+ return manifest.chunkSha256s.length;
52
+ }
53
+ if (bytes <= 0) {
54
+ return 0;
55
+ }
56
+ return Math.floor(bytes / manifest.chunkSizeBytes);
57
+ }
58
+ export function clampVerifiedChunkCount(manifest, verifiedChunkCount) {
59
+ if (!Number.isFinite(verifiedChunkCount) || verifiedChunkCount <= 0) {
60
+ return 0;
61
+ }
62
+ return Math.min(Math.trunc(verifiedChunkCount), manifest.chunkSha256s.length);
63
+ }
64
+ export function stateHasTrustedIntegrityFrontier(state, manifest) {
65
+ if (state.integrityVersion !== SNAPSHOT_CHUNK_MANIFEST_VERSION) {
66
+ return false;
67
+ }
68
+ if (state.chunkSizeBytes !== manifest.chunkSizeBytes) {
69
+ return false;
70
+ }
71
+ const verifiedChunkCount = clampVerifiedChunkCount(manifest, state.verifiedChunkCount);
72
+ const verifiedBytes = resolveVerifiedChunkBytes(manifest, verifiedChunkCount);
73
+ return state.downloadedBytes === verifiedBytes;
74
+ }
75
+ export function createSnapshotChunkManifestRecord(options) {
76
+ return {
77
+ formatVersion: SNAPSHOT_CHUNK_MANIFEST_VERSION,
78
+ chunkSizeBytes: options.chunkSizeBytes ?? DEFAULT_SNAPSHOT_CHUNK_SIZE_BYTES,
79
+ snapshotFilename: options.snapshot.filename,
80
+ snapshotHeight: options.snapshot.height,
81
+ snapshotSizeBytes: options.snapshot.sizeBytes,
82
+ snapshotSha256: options.snapshot.sha256,
83
+ chunkSha256s: options.chunkSha256s,
84
+ };
85
+ }
@@ -0,0 +1,4 @@
1
+ import type { BootstrapPaths, BootstrapPersistentState, BootstrapStateSnapshotIdentity } from "./types.js";
2
+ import type { SnapshotChunkManifest } from "../types.js";
3
+ export declare function applyVerifiedFrontierState(state: BootstrapPersistentState, manifest: SnapshotChunkManifest, verifiedChunkCount: number): void;
4
+ export declare function reconcileSnapshotDownloadArtifacts(paths: BootstrapPaths, state: BootstrapPersistentState, manifest: SnapshotChunkManifest, snapshotIdentity: BootstrapStateSnapshotIdentity): Promise<void>;
@@ -0,0 +1,122 @@
1
+ import { createHash } from "node:crypto";
2
+ import { open, rename, rm } from "node:fs/promises";
3
+ import { clampVerifiedChunkCount, resolveSnapshotChunkCount, resolveSnapshotChunkSize, resolveVerifiedChunkBytes, resolveVerifiedChunkCountFromBytes, stateHasTrustedIntegrityFrontier, } from "./chunk-manifest.js";
4
+ import { resetSnapshotFiles, statOrNull } from "./snapshot-file.js";
5
+ const TRUSTED_FRONTIER_REVERIFY_CHUNKS = 2;
6
+ const HASH_READ_BUFFER_BYTES = 1024 * 1024;
7
+ async function moveSnapshotPathToPartial(paths) {
8
+ const partInfo = await statOrNull(paths.partialSnapshotPath);
9
+ const fullInfo = await statOrNull(paths.snapshotPath);
10
+ if (fullInfo === null) {
11
+ return;
12
+ }
13
+ if (partInfo !== null && partInfo.size > fullInfo.size) {
14
+ await rm(paths.snapshotPath, { force: true });
15
+ return;
16
+ }
17
+ if (partInfo !== null) {
18
+ await rm(paths.partialSnapshotPath, { force: true });
19
+ }
20
+ await rename(paths.snapshotPath, paths.partialSnapshotPath);
21
+ }
22
+ async function hashChunkRange(path, manifest, chunkIndex) {
23
+ const file = await open(path, "r");
24
+ const chunkSizeBytes = resolveSnapshotChunkSize(manifest, chunkIndex);
25
+ const buffer = Buffer.allocUnsafe(Math.min(HASH_READ_BUFFER_BYTES, chunkSizeBytes));
26
+ const hash = createHash("sha256");
27
+ let remainingBytes = chunkSizeBytes;
28
+ let position = resolveVerifiedChunkBytes(manifest, chunkIndex);
29
+ try {
30
+ while (remainingBytes > 0) {
31
+ const readLength = Math.min(buffer.length, remainingBytes);
32
+ const { bytesRead } = await file.read(buffer, 0, readLength, position);
33
+ if (bytesRead === 0) {
34
+ return null;
35
+ }
36
+ hash.update(buffer.subarray(0, bytesRead));
37
+ remainingBytes -= bytesRead;
38
+ position += bytesRead;
39
+ if (bytesRead < readLength) {
40
+ return remainingBytes === 0 ? hash.digest("hex") : null;
41
+ }
42
+ }
43
+ }
44
+ finally {
45
+ await file.close();
46
+ }
47
+ return hash.digest("hex");
48
+ }
49
+ async function scanVerifiedPrefix(path, manifest, fileSize) {
50
+ const chunkCount = resolveSnapshotChunkCount(manifest);
51
+ for (let chunkIndex = 0; chunkIndex < chunkCount; chunkIndex += 1) {
52
+ const expectedChunkEnd = resolveVerifiedChunkBytes(manifest, chunkIndex + 1);
53
+ if (fileSize < expectedChunkEnd) {
54
+ return chunkIndex;
55
+ }
56
+ const actualSha256 = await hashChunkRange(path, manifest, chunkIndex);
57
+ if (actualSha256 !== manifest.chunkSha256s[chunkIndex]) {
58
+ return chunkIndex;
59
+ }
60
+ }
61
+ return chunkCount;
62
+ }
63
+ async function reverifyTrustedFrontier(path, manifest, verifiedChunkCount, fileSize) {
64
+ const maxCompleteChunkCount = resolveVerifiedChunkCountFromBytes(manifest, Math.min(fileSize, manifest.snapshotSizeBytes));
65
+ const tentativeVerifiedChunkCount = Math.min(clampVerifiedChunkCount(manifest, verifiedChunkCount), maxCompleteChunkCount);
66
+ const startChunk = Math.max(0, tentativeVerifiedChunkCount - TRUSTED_FRONTIER_REVERIFY_CHUNKS);
67
+ for (let chunkIndex = startChunk; chunkIndex < tentativeVerifiedChunkCount; chunkIndex += 1) {
68
+ const actualSha256 = await hashChunkRange(path, manifest, chunkIndex);
69
+ if (actualSha256 !== manifest.chunkSha256s[chunkIndex]) {
70
+ return chunkIndex;
71
+ }
72
+ }
73
+ return tentativeVerifiedChunkCount;
74
+ }
75
+ async function truncatePartialSnapshot(paths, verifiedBytes) {
76
+ const file = await open(paths.partialSnapshotPath, "a+");
77
+ try {
78
+ await file.truncate(verifiedBytes);
79
+ await file.sync();
80
+ }
81
+ finally {
82
+ await file.close();
83
+ }
84
+ }
85
+ export function applyVerifiedFrontierState(state, manifest, verifiedChunkCount) {
86
+ const clampedVerifiedChunkCount = clampVerifiedChunkCount(manifest, verifiedChunkCount);
87
+ state.integrityVersion = manifest.formatVersion;
88
+ state.chunkSizeBytes = manifest.chunkSizeBytes;
89
+ state.verifiedChunkCount = clampedVerifiedChunkCount;
90
+ state.downloadedBytes = resolveVerifiedChunkBytes(manifest, clampedVerifiedChunkCount);
91
+ }
92
+ export async function reconcileSnapshotDownloadArtifacts(paths, state, manifest, snapshotIdentity) {
93
+ if (snapshotIdentity === "different") {
94
+ await resetSnapshotFiles(paths);
95
+ state.phase = "snapshot_download";
96
+ state.loadTxOutSetComplete = false;
97
+ state.baseHeight = null;
98
+ state.tipHashHex = null;
99
+ state.lastError = null;
100
+ applyVerifiedFrontierState(state, manifest, 0);
101
+ state.validated = false;
102
+ return;
103
+ }
104
+ if (!state.validated) {
105
+ await moveSnapshotPathToPartial(paths);
106
+ }
107
+ const partialInfo = await statOrNull(paths.partialSnapshotPath);
108
+ if (partialInfo === null) {
109
+ applyVerifiedFrontierState(state, manifest, 0);
110
+ state.validated = false;
111
+ return;
112
+ }
113
+ const verifiedChunkCount = stateHasTrustedIntegrityFrontier(state, manifest)
114
+ ? await reverifyTrustedFrontier(paths.partialSnapshotPath, manifest, state.verifiedChunkCount, partialInfo.size)
115
+ : await scanVerifiedPrefix(paths.partialSnapshotPath, manifest, partialInfo.size);
116
+ const verifiedBytes = resolveVerifiedChunkBytes(manifest, verifiedChunkCount);
117
+ if (partialInfo.size !== verifiedBytes) {
118
+ await truncatePartialSnapshot(paths, verifiedBytes);
119
+ }
120
+ applyVerifiedFrontierState(state, manifest, verifiedChunkCount);
121
+ state.validated = false;
122
+ }
@@ -1,5 +1,7 @@
1
1
  import type { SnapshotMetadata } from "../types.js";
2
- export declare const SNAPSHOT_METADATA_VERSION = 1;
2
+ export declare const BOOTSTRAP_STATE_VERSION = 2;
3
+ export declare const SNAPSHOT_CHUNK_MANIFEST_VERSION = 1;
4
+ export declare const DEFAULT_SNAPSHOT_CHUNK_SIZE_BYTES: number;
3
5
  export declare const DOWNLOAD_RETRY_BASE_MS = 1000;
4
6
  export declare const DOWNLOAD_RETRY_MAX_MS = 30000;
5
7
  export declare const HEADER_POLL_MS = 2000;
@@ -1,4 +1,6 @@
1
- export const SNAPSHOT_METADATA_VERSION = 1;
1
+ export const BOOTSTRAP_STATE_VERSION = 2;
2
+ export const SNAPSHOT_CHUNK_MANIFEST_VERSION = 1;
3
+ export const DEFAULT_SNAPSHOT_CHUNK_SIZE_BYTES = 4 * 1024 * 1024;
2
4
  export const DOWNLOAD_RETRY_BASE_MS = 1_000;
3
5
  export const DOWNLOAD_RETRY_MAX_MS = 30_000;
4
6
  export const HEADER_POLL_MS = 2_000;
@@ -13,11 +13,16 @@ export declare class AssumeUtxoBootstrapController {
13
13
  });
14
14
  get quoteStatePath(): string;
15
15
  get snapshot(): SnapshotMetadata;
16
- ensureReady(indexedTip: ClientTip | null, expectedChain: "main" | "regtest"): Promise<void>;
16
+ ensureReady(indexedTip: ClientTip | null, expectedChain: "main" | "regtest", options?: {
17
+ signal?: AbortSignal;
18
+ }): Promise<void>;
17
19
  getStateForTesting(): Promise<{
18
20
  metadataVersion: number;
19
21
  snapshot: SnapshotMetadata;
20
22
  phase: BootstrapPhase;
23
+ integrityVersion: number;
24
+ chunkSizeBytes: number;
25
+ verifiedChunkCount: number;
21
26
  downloadedBytes: number;
22
27
  validated: boolean;
23
28
  loadTxOutSetComplete: boolean;
@@ -4,7 +4,7 @@ import { DEFAULT_SNAPSHOT_METADATA } from "./constants.js";
4
4
  import { downloadSnapshotFileForTesting } from "./download.js";
5
5
  import { waitForHeaders } from "./headers.js";
6
6
  import { resolveBootstrapPaths } from "./paths.js";
7
- import { loadBootstrapState, saveBootstrapState } from "./state.js";
7
+ import { loadBootstrapStateRecord, saveBootstrapState } from "./state.js";
8
8
  import { isSnapshotAlreadyLoaded } from "./chainstate.js";
9
9
  async function loadSnapshotIntoNode(rpc, snapshotPath) {
10
10
  return rpc.loadTxOutSet(snapshotPath);
@@ -15,7 +15,7 @@ export class AssumeUtxoBootstrapController {
15
15
  #progress;
16
16
  #snapshot;
17
17
  #fetchImpl;
18
- #statePromise = null;
18
+ #stateRecordPromise = null;
19
19
  constructor(options) {
20
20
  this.#rpc = options.rpc;
21
21
  this.#progress = options.progress;
@@ -29,7 +29,7 @@ export class AssumeUtxoBootstrapController {
29
29
  get snapshot() {
30
30
  return this.#snapshot;
31
31
  }
32
- async ensureReady(indexedTip, expectedChain) {
32
+ async ensureReady(indexedTip, expectedChain, options = {}) {
33
33
  if (expectedChain !== "main") {
34
34
  await this.#progress.setPhase("paused", {
35
35
  ...createBootstrapProgressForTesting("paused", this.#snapshot),
@@ -45,7 +45,7 @@ export class AssumeUtxoBootstrapController {
45
45
  });
46
46
  return;
47
47
  }
48
- const state = await this.#loadState();
48
+ const { state, snapshotIdentity } = await this.#loadStateRecord();
49
49
  if (state.loadTxOutSetComplete && await isSnapshotAlreadyLoaded(this.#rpc, this.#snapshot, state)) {
50
50
  await this.#progress.setPhase("bitcoin_sync", {
51
51
  blocks: state.baseHeight,
@@ -63,9 +63,13 @@ export class AssumeUtxoBootstrapController {
63
63
  paths: this.#paths,
64
64
  progress: this.#progress,
65
65
  state,
66
+ signal: options.signal,
67
+ snapshotIdentity,
66
68
  });
67
69
  if (!await isSnapshotAlreadyLoaded(this.#rpc, this.#snapshot, state)) {
68
- await waitForHeaders(this.#rpc, this.#snapshot, this.#progress);
70
+ await waitForHeaders(this.#rpc, this.#snapshot, this.#progress, {
71
+ signal: options.signal,
72
+ });
69
73
  await this.#progress.setPhase("load_snapshot", {
70
74
  downloadedBytes: this.#snapshot.sizeBytes,
71
75
  totalBytes: this.#snapshot.sizeBytes,
@@ -95,7 +99,10 @@ export class AssumeUtxoBootstrapController {
95
99
  return { ...(await this.#loadState()) };
96
100
  }
97
101
  async #loadState() {
98
- this.#statePromise ??= loadBootstrapState(this.#paths, this.#snapshot);
99
- return this.#statePromise;
102
+ return (await this.#loadStateRecord()).state;
103
+ }
104
+ async #loadStateRecord() {
105
+ this.#stateRecordPromise ??= loadBootstrapStateRecord(this.#paths, this.#snapshot);
106
+ return this.#stateRecordPromise;
100
107
  }
101
108
  }
@@ -0,0 +1,2 @@
1
+ import type { SnapshotChunkManifest } from "../types.js";
2
+ export declare const DEFAULT_SNAPSHOT_CHUNK_MANIFEST: SnapshotChunkManifest;