@bsv/wallet-toolbox-client 2.8.0 → 2.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/out/index.client.cjs +259 -74
- package/out/index.client.cjs.map +1 -1
- package/out/index.client.d.cts +171 -5
- package/out/index.client.d.cts.map +1 -1
- package/out/index.client.d.mts +171 -5
- package/out/index.client.d.mts.map +1 -1
- package/out/index.client.mjs +258 -75
- package/out/index.client.mjs.map +1 -1
- package/package.json +2 -2
package/out/index.client.mjs
CHANGED
|
@@ -19562,6 +19562,24 @@ function isLiveBlockHeader(header) {
|
|
|
19562
19562
|
return "chainWork" in header && typeof header.previousHash === "string";
|
|
19563
19563
|
}
|
|
19564
19564
|
//#endregion
|
|
19565
|
+
//#region ../src/services/chaintracker/chaintracks/Api/BulkFileDataValidatorApi.ts
|
|
19566
|
+
/**
|
|
19567
|
+
* Identifies deterministic rejection of the supplied immutable bytes.
|
|
19568
|
+
* Operational failures such as worker crashes and queue saturation deliberately
|
|
19569
|
+
* use ordinary errors so callers preserve the cache entry and avoid downloading
|
|
19570
|
+
* a replacement that cannot be validated.
|
|
19571
|
+
*
|
|
19572
|
+
* @public
|
|
19573
|
+
*/
|
|
19574
|
+
var BulkFileDataValidationError = class extends Error {
|
|
19575
|
+
data;
|
|
19576
|
+
constructor(message, data) {
|
|
19577
|
+
super(message);
|
|
19578
|
+
this.data = data;
|
|
19579
|
+
this.name = "BulkFileDataValidationError";
|
|
19580
|
+
}
|
|
19581
|
+
};
|
|
19582
|
+
//#endregion
|
|
19565
19583
|
//#region ../src/services/chaintracker/chaintracks/util/HeightRange.ts
|
|
19566
19584
|
/**
|
|
19567
19585
|
* Represents a range of block heights.
|
|
@@ -19780,6 +19798,8 @@ var Chaintracks = class {
|
|
|
19780
19798
|
lastPresentHeight = -1;
|
|
19781
19799
|
lastPresentHeightMsecs = 0;
|
|
19782
19800
|
lastPresentHeightMaxAge = 60 * 1e3;
|
|
19801
|
+
presentHeightRefresh;
|
|
19802
|
+
mainLoopHeartbeatMsecs = 0;
|
|
19783
19803
|
lock = new SingleWriterMultiReaderLock();
|
|
19784
19804
|
sourceStatus = /* @__PURE__ */ new Map();
|
|
19785
19805
|
constructor(options) {
|
|
@@ -19817,12 +19837,31 @@ var Chaintracks = class {
|
|
|
19817
19837
|
return this.chain;
|
|
19818
19838
|
}
|
|
19819
19839
|
/**
|
|
19820
|
-
*
|
|
19821
|
-
*
|
|
19840
|
+
* Returns the last known valid height immediately and refreshes stale state
|
|
19841
|
+
* once in the background. Cold start waits for the single shared refresh.
|
|
19822
19842
|
*/
|
|
19823
19843
|
async getPresentHeight() {
|
|
19824
19844
|
const now = Date.now();
|
|
19825
19845
|
if (this.lastPresentHeight >= 0 && now - this.lastPresentHeightMsecs < this.lastPresentHeightMaxAge) return this.lastPresentHeight;
|
|
19846
|
+
if (this.lastPresentHeight >= 0) {
|
|
19847
|
+
this.refreshPresentHeight().catch((error) => {
|
|
19848
|
+
this.log(`Background present-height refresh failed: ${WalletError.fromUnknown(error).message}`);
|
|
19849
|
+
});
|
|
19850
|
+
return this.lastPresentHeight;
|
|
19851
|
+
}
|
|
19852
|
+
return await this.refreshPresentHeight();
|
|
19853
|
+
}
|
|
19854
|
+
async refreshPresentHeight() {
|
|
19855
|
+
if (this.presentHeightRefresh != null) return await this.presentHeightRefresh;
|
|
19856
|
+
const refresh = this.loadPresentHeight();
|
|
19857
|
+
this.presentHeightRefresh = refresh;
|
|
19858
|
+
try {
|
|
19859
|
+
return await refresh;
|
|
19860
|
+
} finally {
|
|
19861
|
+
if (this.presentHeightRefresh === refresh) this.presentHeightRefresh = void 0;
|
|
19862
|
+
}
|
|
19863
|
+
}
|
|
19864
|
+
async loadPresentHeight() {
|
|
19826
19865
|
for (const [index, bulk] of this.bulkIngestors.entries()) {
|
|
19827
19866
|
const source = this.sourceName("bulk", index, bulk);
|
|
19828
19867
|
try {
|
|
@@ -19830,7 +19869,7 @@ var Chaintracks = class {
|
|
|
19830
19869
|
if (presentHeight != null && Number.isInteger(presentHeight) && presentHeight >= 0) {
|
|
19831
19870
|
this.markSourceSuccess(source, "bulk");
|
|
19832
19871
|
this.lastPresentHeight = presentHeight;
|
|
19833
|
-
this.lastPresentHeightMsecs = now;
|
|
19872
|
+
this.lastPresentHeightMsecs = Date.now();
|
|
19834
19873
|
return presentHeight;
|
|
19835
19874
|
}
|
|
19836
19875
|
} catch (uerr) {
|
|
@@ -19845,7 +19884,7 @@ var Chaintracks = class {
|
|
|
19845
19884
|
const localHeight = Math.max(ranges.bulk.maxHeight, ranges.live.maxHeight);
|
|
19846
19885
|
if (localHeight >= 0) {
|
|
19847
19886
|
this.lastPresentHeight = localHeight;
|
|
19848
|
-
this.lastPresentHeightMsecs = now;
|
|
19887
|
+
this.lastPresentHeightMsecs = Date.now();
|
|
19849
19888
|
return localHeight;
|
|
19850
19889
|
}
|
|
19851
19890
|
} catch (error) {
|
|
@@ -19856,6 +19895,19 @@ var Chaintracks = class {
|
|
|
19856
19895
|
async currentHeight() {
|
|
19857
19896
|
return await this.getPresentHeight();
|
|
19858
19897
|
}
|
|
19898
|
+
/** Returns local process state without locks, storage reads, or network I/O. */
|
|
19899
|
+
getAvailabilitySnapshot() {
|
|
19900
|
+
return {
|
|
19901
|
+
available: this.available,
|
|
19902
|
+
startupError: this.startupError?.message,
|
|
19903
|
+
presentHeight: this.lastPresentHeight >= 0 ? this.lastPresentHeight : void 0,
|
|
19904
|
+
presentHeightUpdatedAt: this.lastPresentHeightMsecs > 0 ? new Date(this.lastPresentHeightMsecs).toISOString() : void 0,
|
|
19905
|
+
presentHeightRefreshInFlight: this.presentHeightRefresh != null,
|
|
19906
|
+
mainLoopHeartbeatAt: this.mainLoopHeartbeatMsecs > 0 ? new Date(this.mainLoopHeartbeatMsecs).toISOString() : void 0,
|
|
19907
|
+
sources: Array.from(this.sourceStatus.values()).map((status) => ({ ...status })),
|
|
19908
|
+
bulkData: this.storage.bulkManager.getStats()
|
|
19909
|
+
};
|
|
19910
|
+
}
|
|
19859
19911
|
async subscribeHeaders(listener) {
|
|
19860
19912
|
const ID = randomBytesBase64(8);
|
|
19861
19913
|
this.callbacks.header[ID] = listener;
|
|
@@ -19920,6 +19972,7 @@ var Chaintracks = class {
|
|
|
19920
19972
|
for (const liveIn of this.liveIngestors) await liveIn.shutdown();
|
|
19921
19973
|
for (const bulkIn of this.bulkIngestors) await bulkIn.shutdown();
|
|
19922
19974
|
await Promise.all(this.promises);
|
|
19975
|
+
await this.storage.bulkManager.destroy();
|
|
19923
19976
|
await this.storage.destroy();
|
|
19924
19977
|
this.available = false;
|
|
19925
19978
|
this.stopMainThread = false;
|
|
@@ -20201,9 +20254,11 @@ var Chaintracks = class {
|
|
|
20201
20254
|
const syncCheckRepeatMsecs = 1800 * 1e3;
|
|
20202
20255
|
while (!this.stopMainThread) try {
|
|
20203
20256
|
const now = Date.now();
|
|
20257
|
+
this.mainLoopHeartbeatMsecs = now;
|
|
20204
20258
|
lastSyncCheck = now;
|
|
20205
20259
|
lastBulkSync = await this.runBulkSyncIfNeeded(now, lastBulkSync, cdnSyncRepeatMsecs);
|
|
20206
20260
|
await this.processLiveHeaderQueue(lastSyncCheck, syncCheckRepeatMsecs);
|
|
20261
|
+
this.mainLoopHeartbeatMsecs = Date.now();
|
|
20207
20262
|
} catch (error_) {
|
|
20208
20263
|
const e = WalletError.fromUnknown(error_);
|
|
20209
20264
|
if (this.available) this.log(`Error occurred during chaintracks main thread processing: ${e.stack || e.message}`);
|
|
@@ -20215,7 +20270,7 @@ var Chaintracks = class {
|
|
|
20215
20270
|
}
|
|
20216
20271
|
/** Returns (potentially updated) lastBulkSync timestamp. */
|
|
20217
20272
|
async runBulkSyncIfNeeded(now, lastBulkSync, cdnSyncRepeatMsecs) {
|
|
20218
|
-
const presentHeight = await this.
|
|
20273
|
+
const presentHeight = await this.refreshPresentHeight();
|
|
20219
20274
|
const before = await this.storage.getAvailableHeightRanges();
|
|
20220
20275
|
let skipBulkSync = !before.live.isEmpty && before.live.maxHeight >= presentHeight - this.addLiveRecursionLimit / 2;
|
|
20221
20276
|
if (skipBulkSync && now - lastBulkSync > cdnSyncRepeatMsecs) skipBulkSync = false;
|
|
@@ -21364,12 +21419,12 @@ var ChaintracksFetch = class {
|
|
|
21364
21419
|
this.maxRetryMsecs = positiveSafeInteger(options.maxRetryMsecs, DEFAULT_MAX_RETRY_MSECS, "maxRetryMsecs");
|
|
21365
21420
|
this.random = options.random ?? Math.random;
|
|
21366
21421
|
}
|
|
21367
|
-
async download(url, maxResponseBytes) {
|
|
21422
|
+
async download(url, maxResponseBytes, options) {
|
|
21368
21423
|
const responseLimit = maxResponseBytes == null ? this.maxResponseBytes : Math.min(this.maxResponseBytes, positiveSafeInteger(maxResponseBytes, this.maxResponseBytes, "maxResponseBytes"));
|
|
21369
21424
|
return await this.requestBytes(url, {
|
|
21370
21425
|
method: "GET",
|
|
21371
21426
|
headers: { Accept: "application/octet-stream" }
|
|
21372
|
-
}, "download", responseLimit);
|
|
21427
|
+
}, "download", responseLimit, options);
|
|
21373
21428
|
}
|
|
21374
21429
|
async fetchJson(url) {
|
|
21375
21430
|
const bytes = await this.requestBytes(url, {
|
|
@@ -21378,8 +21433,9 @@ var ChaintracksFetch = class {
|
|
|
21378
21433
|
}, "fetch JSON", this.maxResponseBytes);
|
|
21379
21434
|
return JSON.parse(new TextDecoder().decode(bytes));
|
|
21380
21435
|
}
|
|
21381
|
-
async requestBytes(url, init, kind, maxResponseBytes) {
|
|
21436
|
+
async requestBytes(url, init, kind, maxResponseBytes, downloadOptions) {
|
|
21382
21437
|
for (let retry = 0;; retry++) {
|
|
21438
|
+
if (retry > 0) await downloadOptions?.beforeRetry?.(retry + 1);
|
|
21383
21439
|
const controller = new AbortController();
|
|
21384
21440
|
const timeout = setTimeout(() => controller.abort(), this.timeoutMsecs);
|
|
21385
21441
|
try {
|
|
@@ -21455,6 +21511,38 @@ var ChaintracksFetch = class {
|
|
|
21455
21511
|
}
|
|
21456
21512
|
};
|
|
21457
21513
|
//#endregion
|
|
21514
|
+
//#region ../src/services/chaintracker/chaintracks/util/InlineBulkFileDataValidator.ts
|
|
21515
|
+
/**
|
|
21516
|
+
* Portable complete-object validator. Node services should normally inject
|
|
21517
|
+
* `NodeBulkFileDataValidator`; browser and mobile consumers retain this
|
|
21518
|
+
* dependency-free fallback.
|
|
21519
|
+
*
|
|
21520
|
+
* @public
|
|
21521
|
+
*/
|
|
21522
|
+
var InlineBulkFileDataValidator = class {
|
|
21523
|
+
async validate(request) {
|
|
21524
|
+
try {
|
|
21525
|
+
const expectedLength = request.count * 80;
|
|
21526
|
+
if (request.data.length !== expectedLength) throw new WERR_INVALID_PARAMETER("file.data", `bulk file ${request.fileName} data length ${request.data.length} does not match expected count ${request.count}`);
|
|
21527
|
+
const fileHash = asString(Hash.sha256(asArray(request.data)), "base64");
|
|
21528
|
+
if (request.fileHash != null && fileHash !== request.fileHash) throw new WERR_INVALID_PARAMETER("fileHash", `a match for retrieved data for ${request.fileName}`);
|
|
21529
|
+
const { lastHeaderHash, lastChainWork } = validateBufferOfHeaders(request.data, request.prevHash, 0, request.count, request.prevChainWork);
|
|
21530
|
+
if (request.lastHash && request.lastHash !== lastHeaderHash) throw new WERR_INVALID_PARAMETER("file.lastHash", `expected ${request.lastHash} but got ${lastHeaderHash}`);
|
|
21531
|
+
if (request.lastChainWork && request.lastChainWork !== lastChainWork) throw new WERR_INVALID_PARAMETER("file.lastChainWork", `expected ${request.lastChainWork} but got ${lastChainWork}`);
|
|
21532
|
+
if (request.firstHeight === 0 && request.chain != null) validateGenesisHeader(request.data, request.chain);
|
|
21533
|
+
return {
|
|
21534
|
+
data: request.data,
|
|
21535
|
+
fileHash,
|
|
21536
|
+
lastHeaderHash,
|
|
21537
|
+
lastChainWork
|
|
21538
|
+
};
|
|
21539
|
+
} catch (error) {
|
|
21540
|
+
if (error instanceof BulkFileDataValidationError) throw error;
|
|
21541
|
+
throw new BulkFileDataValidationError(error instanceof Error ? error.message : String(error), request.data);
|
|
21542
|
+
}
|
|
21543
|
+
}
|
|
21544
|
+
};
|
|
21545
|
+
//#endregion
|
|
21458
21546
|
//#region ../src/services/chaintracker/chaintracks/util/BulkFileDataManager.ts
|
|
21459
21547
|
/**
|
|
21460
21548
|
* Manages bulk file data (typically 8MB chunks of 100,000 headers each).
|
|
@@ -21480,6 +21568,7 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21480
21568
|
fileHashToIndex = {};
|
|
21481
21569
|
lock = new SingleWriterMultiReaderLock();
|
|
21482
21570
|
inFlightLoads = /* @__PURE__ */ new Map();
|
|
21571
|
+
failedLoads = /* @__PURE__ */ new Map();
|
|
21483
21572
|
storage;
|
|
21484
21573
|
stats = {
|
|
21485
21574
|
memoryHits: 0,
|
|
@@ -21489,7 +21578,8 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21489
21578
|
persistentCacheRejects: 0,
|
|
21490
21579
|
coalescedLoads: 0,
|
|
21491
21580
|
downloads: 0,
|
|
21492
|
-
downloadedBytes: 0
|
|
21581
|
+
downloadedBytes: 0,
|
|
21582
|
+
loadBackoffs: 0
|
|
21493
21583
|
};
|
|
21494
21584
|
chain;
|
|
21495
21585
|
maxPerFile;
|
|
@@ -21498,6 +21588,8 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21498
21588
|
fromKnownSourceUrl;
|
|
21499
21589
|
cache;
|
|
21500
21590
|
downloadBudget;
|
|
21591
|
+
validator;
|
|
21592
|
+
failedLoadRetryMsecs;
|
|
21501
21593
|
constructor(options) {
|
|
21502
21594
|
const resolvedOptions = typeof options === "object" ? options : BulkFileDataManager.createDefaultOptions(options);
|
|
21503
21595
|
this.chain = resolvedOptions.chain;
|
|
@@ -21507,10 +21599,17 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21507
21599
|
this.fetch = resolvedOptions.fetch;
|
|
21508
21600
|
this.cache = resolvedOptions.cache;
|
|
21509
21601
|
this.downloadBudget = resolvedOptions.downloadBudget;
|
|
21602
|
+
this.validator = resolvedOptions.validator ?? new InlineBulkFileDataValidator();
|
|
21603
|
+
this.failedLoadRetryMsecs = resolvedOptions.failedLoadRetryMsecs ?? 30 * 1e3;
|
|
21604
|
+
if (!Number.isSafeInteger(this.failedLoadRetryMsecs) || this.failedLoadRetryMsecs < 0) throw new WERR_INVALID_PARAMETER("failedLoadRetryMsecs", "a non-negative safe integer");
|
|
21510
21605
|
this.deleteBulkFilesNoLock();
|
|
21511
21606
|
}
|
|
21512
21607
|
getStats() {
|
|
21513
|
-
return {
|
|
21608
|
+
return {
|
|
21609
|
+
...this.stats,
|
|
21610
|
+
validation: this.validator.getStats?.(),
|
|
21611
|
+
downloadBudget: this.downloadBudget?.snapshot?.()
|
|
21612
|
+
};
|
|
21514
21613
|
}
|
|
21515
21614
|
async deleteBulkFiles() {
|
|
21516
21615
|
return await this.lock.withWriteLock(async () => this.deleteBulkFilesNoLock());
|
|
@@ -21729,9 +21828,29 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21729
21828
|
});
|
|
21730
21829
|
}
|
|
21731
21830
|
async getDataFromFile(file, offset, length) {
|
|
21732
|
-
const
|
|
21733
|
-
|
|
21734
|
-
|
|
21831
|
+
const resolved = await this.lock.withReadLock(async () => {
|
|
21832
|
+
const resolved = this.getBfdForHeight(file.firstHeight);
|
|
21833
|
+
if (resolved == null || resolved.count < file.count) throw new WERR_INVALID_PARAMETER("file", `a match for ${file.firstHeight}, ${file.count} in the BulkFileDataManager.`);
|
|
21834
|
+
return {
|
|
21835
|
+
current: resolved,
|
|
21836
|
+
snapshot: snapshotBfd(resolved)
|
|
21837
|
+
};
|
|
21838
|
+
});
|
|
21839
|
+
return await this.getDataFromSnapshot(resolved.current, resolved.snapshot, offset, length);
|
|
21840
|
+
}
|
|
21841
|
+
async getDataFromSnapshot(original, snapshot, offset, length) {
|
|
21842
|
+
const data = await this.getDataFromFileNoLock(snapshot, offset, length);
|
|
21843
|
+
if (snapshot.data != null) await this.lock.withWriteLock(async () => {
|
|
21844
|
+
if (this.bfds.includes(original) && original.fileHash === snapshot.fileHash && original.firstHeight === snapshot.firstHeight && original.count === snapshot.count) {
|
|
21845
|
+
original.data = snapshot.data;
|
|
21846
|
+
original.validated = true;
|
|
21847
|
+
original.lastHash = snapshot.lastHash;
|
|
21848
|
+
original.lastChainWork = snapshot.lastChainWork;
|
|
21849
|
+
original.mru = Date.now();
|
|
21850
|
+
this.ensureMaxRetained();
|
|
21851
|
+
}
|
|
21852
|
+
});
|
|
21853
|
+
return data;
|
|
21735
21854
|
}
|
|
21736
21855
|
async getDataFromFileNoLock(bfd, offset, length) {
|
|
21737
21856
|
const fileLength = bfd.count * 80;
|
|
@@ -21742,15 +21861,19 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21742
21861
|
return (await this.ensureData(bfd)).slice(offset, offset + length);
|
|
21743
21862
|
}
|
|
21744
21863
|
async findHeaderForHeightOrUndefined(height) {
|
|
21745
|
-
|
|
21864
|
+
const resolved = await this.lock.withReadLock(async () => {
|
|
21746
21865
|
if (!Number.isInteger(height) || height < 0) throw new WERR_INVALID_PARAMETER("height", `a non-negative integer (${height}).`);
|
|
21747
21866
|
const file = this.bfds.find((f) => f.firstHeight <= height && f.firstHeight + f.count > height);
|
|
21748
21867
|
if (file == null) return void 0;
|
|
21749
|
-
|
|
21750
|
-
|
|
21751
|
-
|
|
21752
|
-
|
|
21868
|
+
return {
|
|
21869
|
+
current: file,
|
|
21870
|
+
snapshot: snapshotBfd(file),
|
|
21871
|
+
offset: (height - file.firstHeight) * 80
|
|
21872
|
+
};
|
|
21753
21873
|
});
|
|
21874
|
+
if (resolved == null) return void 0;
|
|
21875
|
+
const data = await this.getDataFromSnapshot(resolved.current, resolved.snapshot, resolved.offset, 80);
|
|
21876
|
+
return data == null ? void 0 : deserializeBlockHeader(data, height, 0);
|
|
21754
21877
|
}
|
|
21755
21878
|
async getFileForHeight(height) {
|
|
21756
21879
|
return await this.lock.withReadLock(async () => {
|
|
@@ -21805,22 +21928,30 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21805
21928
|
return bfd;
|
|
21806
21929
|
}
|
|
21807
21930
|
async validateBfdData(bfd, expectedFileHash) {
|
|
21808
|
-
await this.ensureData(bfd);
|
|
21809
|
-
|
|
21810
|
-
bfd.fileHash = asString(Hash.sha256(asArray(bfd.data)), "base64");
|
|
21811
|
-
if (expectedFileHash && expectedFileHash !== bfd.fileHash) throw new WERR_INVALID_PARAMETER("file.fileHash", `expected ${expectedFileHash} but got ${bfd.fileHash}`);
|
|
21812
|
-
this.validateBfdHeaders(bfd);
|
|
21931
|
+
const data = await this.ensureData(bfd);
|
|
21932
|
+
bfd.data = await this.validateRetrievedData(bfd, data, expectedFileHash);
|
|
21813
21933
|
}
|
|
21814
|
-
validateBfdHeaders(bfd) {
|
|
21934
|
+
async validateBfdHeaders(bfd, expectedFileHash = bfd.fileHash) {
|
|
21815
21935
|
const pbf = bfd.firstHeight > 0 ? this.getBfdForHeight(bfd.firstHeight - 1) : void 0;
|
|
21816
21936
|
const prevHash = pbf?.lastHash ?? "00".repeat(32);
|
|
21817
21937
|
const prevChainWork = pbf?.lastChainWork ?? "00".repeat(32);
|
|
21818
|
-
const
|
|
21819
|
-
|
|
21820
|
-
|
|
21821
|
-
|
|
21822
|
-
|
|
21823
|
-
|
|
21938
|
+
const result = await this.validator.validate({
|
|
21939
|
+
fileName: bfd.fileName,
|
|
21940
|
+
data: bfd.data,
|
|
21941
|
+
count: bfd.count,
|
|
21942
|
+
fileHash: expectedFileHash,
|
|
21943
|
+
firstHeight: bfd.firstHeight,
|
|
21944
|
+
prevHash,
|
|
21945
|
+
prevChainWork,
|
|
21946
|
+
lastHash: bfd.lastHash,
|
|
21947
|
+
lastChainWork: bfd.lastChainWork,
|
|
21948
|
+
chain: bfd.chain
|
|
21949
|
+
});
|
|
21950
|
+
bfd.data = result.data;
|
|
21951
|
+
bfd.fileHash = result.fileHash;
|
|
21952
|
+
bfd.lastHash = result.lastHeaderHash;
|
|
21953
|
+
bfd.lastChainWork = result.lastChainWork;
|
|
21954
|
+
return result.data;
|
|
21824
21955
|
}
|
|
21825
21956
|
async ReValidate() {
|
|
21826
21957
|
return await this.lock.withReadLock(async () => await this.ReValidateNoLock());
|
|
@@ -22011,62 +22142,96 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
22011
22142
|
this.ensureMaxRetained();
|
|
22012
22143
|
return data;
|
|
22013
22144
|
}
|
|
22145
|
+
const failed = this.failedLoads.get(key);
|
|
22146
|
+
if (failed != null) {
|
|
22147
|
+
if (Date.now() < failed.retryAt) {
|
|
22148
|
+
this.stats.loadBackoffs++;
|
|
22149
|
+
throw failed.error;
|
|
22150
|
+
}
|
|
22151
|
+
this.failedLoads.delete(key);
|
|
22152
|
+
}
|
|
22014
22153
|
const load = this.loadAndValidateData(bfd);
|
|
22015
22154
|
this.inFlightLoads.set(key, load);
|
|
22016
22155
|
try {
|
|
22017
22156
|
const data = await load;
|
|
22018
22157
|
bfd.data = data;
|
|
22019
22158
|
bfd.validated = true;
|
|
22159
|
+
this.failedLoads.delete(key);
|
|
22020
22160
|
bfd.mru = Date.now();
|
|
22021
22161
|
this.ensureMaxRetained();
|
|
22022
22162
|
return data;
|
|
22163
|
+
} catch (error) {
|
|
22164
|
+
const resolved = error instanceof Error ? error : new Error(String(error));
|
|
22165
|
+
this.failedLoads.set(key, {
|
|
22166
|
+
retryAt: Date.now() + this.failedLoadRetryMsecs,
|
|
22167
|
+
error: resolved
|
|
22168
|
+
});
|
|
22169
|
+
throw resolved;
|
|
22023
22170
|
} finally {
|
|
22024
22171
|
if (this.inFlightLoads.get(key) === load) this.inFlightLoads.delete(key);
|
|
22025
22172
|
}
|
|
22026
22173
|
}
|
|
22027
22174
|
async loadAndValidateData(bfd) {
|
|
22028
|
-
|
|
22029
|
-
|
|
22030
|
-
|
|
22031
|
-
|
|
22032
|
-
|
|
22033
|
-
|
|
22034
|
-
}
|
|
22035
|
-
if (this.cache != null) {
|
|
22036
|
-
const cached = await this.cache.get(bfd);
|
|
22037
|
-
if (cached != null) try {
|
|
22038
|
-
this.validateRetrievedData(bfd, cached);
|
|
22039
|
-
this.stats.persistentCacheHits++;
|
|
22040
|
-
return cached;
|
|
22041
|
-
} catch (error) {
|
|
22042
|
-
this.stats.persistentCacheRejects++;
|
|
22043
|
-
await this.cache.delete?.(bfd);
|
|
22044
|
-
this.log(`Rejected corrupt bulk-header cache entry ${bfd.fileName}: ${String(error)}`);
|
|
22045
|
-
}
|
|
22046
|
-
else this.stats.persistentCacheMisses++;
|
|
22047
|
-
}
|
|
22048
|
-
if (this.fetch != null && bfd.sourceUrl) {
|
|
22049
|
-
const expectedBytes = bfd.count * 80;
|
|
22050
|
-
await this.downloadBudget?.consume(expectedBytes);
|
|
22051
|
-
const url = this.fetch.pathJoin(bfd.sourceUrl, bfd.fileName);
|
|
22052
|
-
const downloaded = await this.fetch.download(url, expectedBytes);
|
|
22053
|
-
if (downloaded == null) throw new WERR_INVALID_PARAMETER("sourceUrl", `data not found for sourceUrl ${url}`);
|
|
22054
|
-
this.validateRetrievedData(bfd, downloaded);
|
|
22055
|
-
this.stats.downloads++;
|
|
22056
|
-
this.stats.downloadedBytes += downloaded.length;
|
|
22057
|
-
await this.cache?.set(bfd, downloaded);
|
|
22058
|
-
return downloaded;
|
|
22059
|
-
}
|
|
22175
|
+
const stored = await this.loadFromStorage(bfd);
|
|
22176
|
+
if (stored != null) return stored;
|
|
22177
|
+
const cached = await this.loadFromCache(bfd);
|
|
22178
|
+
if (cached != null) return cached;
|
|
22179
|
+
const downloaded = await this.loadFromRemote(bfd);
|
|
22180
|
+
if (downloaded != null) return downloaded;
|
|
22060
22181
|
throw new WERR_INVALID_PARAMETER("data", `defined. Unable to retrieve data for ${bfd.fileName}`);
|
|
22061
22182
|
}
|
|
22062
|
-
|
|
22063
|
-
if (
|
|
22064
|
-
|
|
22183
|
+
async loadFromStorage(bfd) {
|
|
22184
|
+
if (this.storage == null || !bfd.fileId) return void 0;
|
|
22185
|
+
const stored = await this.storage.getBulkFileData(bfd.fileId);
|
|
22186
|
+
if (stored == null) throw new WERR_INVALID_PARAMETER("fileId", `valid, data not found for fileId ${bfd.fileId}`);
|
|
22187
|
+
const validated = await this.validateRetrievedData(bfd, stored);
|
|
22188
|
+
this.stats.storageHits++;
|
|
22189
|
+
return validated;
|
|
22190
|
+
}
|
|
22191
|
+
async loadFromCache(bfd) {
|
|
22192
|
+
if (this.cache == null) return void 0;
|
|
22193
|
+
const cached = await this.cache.get(bfd);
|
|
22194
|
+
if (cached == null) {
|
|
22195
|
+
this.stats.persistentCacheMisses++;
|
|
22196
|
+
return;
|
|
22197
|
+
}
|
|
22198
|
+
try {
|
|
22199
|
+
const validated = await this.validateRetrievedData(bfd, cached);
|
|
22200
|
+
this.stats.persistentCacheHits++;
|
|
22201
|
+
await this.cache.promoteValidated?.(bfd, validated);
|
|
22202
|
+
return validated;
|
|
22203
|
+
} catch (error) {
|
|
22204
|
+
if (!(error instanceof BulkFileDataValidationError)) throw error;
|
|
22205
|
+
this.stats.persistentCacheRejects++;
|
|
22206
|
+
let rejectedData = error.data;
|
|
22207
|
+
if (!(rejectedData instanceof Uint8Array) && cached.byteLength > 0) rejectedData = cached;
|
|
22208
|
+
await this.cache.quarantine?.(bfd, String(error), rejectedData);
|
|
22209
|
+
this.log(`Rejected corrupt bulk-header cache entry ${bfd.fileName}: ${String(error)}`);
|
|
22210
|
+
return;
|
|
22211
|
+
}
|
|
22212
|
+
}
|
|
22213
|
+
async loadFromRemote(bfd) {
|
|
22214
|
+
if (this.fetch == null || !bfd.sourceUrl) return void 0;
|
|
22215
|
+
const expectedBytes = bfd.count * 80;
|
|
22216
|
+
await this.downloadBudget?.consume(expectedBytes);
|
|
22217
|
+
const url = this.fetch.pathJoin(bfd.sourceUrl, bfd.fileName);
|
|
22218
|
+
const downloaded = await this.fetch.download(url, expectedBytes, { beforeRetry: async () => await this.downloadBudget?.consume(expectedBytes) });
|
|
22219
|
+
if (downloaded == null) throw new WERR_INVALID_PARAMETER("sourceUrl", `data not found for sourceUrl ${url}`);
|
|
22220
|
+
const validated = await this.validateRetrievedData(bfd, downloaded);
|
|
22221
|
+
this.stats.downloads++;
|
|
22222
|
+
this.stats.downloadedBytes += validated.length;
|
|
22223
|
+
await this.cache?.set(bfd, validated);
|
|
22224
|
+
return validated;
|
|
22225
|
+
}
|
|
22226
|
+
async validateRetrievedData(bfd, data, expectedFileHash = bfd.fileHash) {
|
|
22065
22227
|
const candidate = {
|
|
22066
22228
|
...bfd,
|
|
22067
22229
|
data
|
|
22068
22230
|
};
|
|
22069
|
-
this.validateBfdHeaders(candidate);
|
|
22231
|
+
const validated = await this.validateBfdHeaders(candidate, expectedFileHash);
|
|
22232
|
+
bfd.lastHash = candidate.lastHash;
|
|
22233
|
+
bfd.lastChainWork = candidate.lastChainWork;
|
|
22234
|
+
return validated;
|
|
22070
22235
|
}
|
|
22071
22236
|
ensureMaxRetained() {
|
|
22072
22237
|
if (this.maxRetained === void 0) return;
|
|
@@ -22102,17 +22267,24 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
22102
22267
|
i++;
|
|
22103
22268
|
const data = await reader.read();
|
|
22104
22269
|
if (data == null || data.length === 0) break;
|
|
22105
|
-
const
|
|
22106
|
-
|
|
22107
|
-
|
|
22270
|
+
const validated = await this.validator.validate({
|
|
22271
|
+
fileName: toFileName(i),
|
|
22272
|
+
data,
|
|
22273
|
+
count: data.length / 80,
|
|
22274
|
+
firstHeight,
|
|
22275
|
+
prevHash: lastHeaderHash,
|
|
22276
|
+
prevChainWork: lastChainWork,
|
|
22277
|
+
chain
|
|
22278
|
+
});
|
|
22279
|
+
await toFs.writeFile(toPath(i), validated.data);
|
|
22108
22280
|
const file = {
|
|
22109
22281
|
chain,
|
|
22110
|
-
count: data.length / 80,
|
|
22111
|
-
fileHash,
|
|
22282
|
+
count: validated.data.length / 80,
|
|
22283
|
+
fileHash: validated.fileHash,
|
|
22112
22284
|
fileName: toFileName(i),
|
|
22113
22285
|
firstHeight,
|
|
22114
|
-
lastChainWork:
|
|
22115
|
-
lastHash:
|
|
22286
|
+
lastChainWork: validated.lastChainWork,
|
|
22287
|
+
lastHash: validated.lastHeaderHash,
|
|
22116
22288
|
prevChainWork: lastChainWork,
|
|
22117
22289
|
prevHash: lastHeaderHash,
|
|
22118
22290
|
sourceUrl
|
|
@@ -22124,7 +22296,16 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
22124
22296
|
}
|
|
22125
22297
|
await toFs.writeFile(toJsonPath(), asUint8Array(JSON.stringify(toBulkFiles), "utf8"));
|
|
22126
22298
|
}
|
|
22299
|
+
async destroy() {
|
|
22300
|
+
await this.validator.destroy?.();
|
|
22301
|
+
}
|
|
22127
22302
|
};
|
|
22303
|
+
function snapshotBfd(file) {
|
|
22304
|
+
return {
|
|
22305
|
+
...file,
|
|
22306
|
+
data: file.data
|
|
22307
|
+
};
|
|
22308
|
+
}
|
|
22128
22309
|
function selectBulkHeaderFiles(files, chain, maxPerFile) {
|
|
22129
22310
|
const r = [];
|
|
22130
22311
|
let height = 0;
|
|
@@ -26993,7 +27174,8 @@ function createDefaultBulkFileDataManager(params) {
|
|
|
26993
27174
|
maxRetained: params.maxRetained,
|
|
26994
27175
|
fromKnownSourceUrl: params.cdnUrl,
|
|
26995
27176
|
cache: params.sources.bulkFileCache,
|
|
26996
|
-
downloadBudget: params.sources.bulkFileDownloadBudget
|
|
27177
|
+
downloadBudget: params.sources.bulkFileDownloadBudget,
|
|
27178
|
+
validator: params.sources.bulkFileDataValidator
|
|
26997
27179
|
});
|
|
26998
27180
|
}
|
|
26999
27181
|
function createDefaultChaintracksStorageOptions(params) {
|
|
@@ -27518,6 +27700,7 @@ var FixedWindowBulkFileDownloadBudget = class {
|
|
|
27518
27700
|
return {
|
|
27519
27701
|
maxBytes: this.maxBytes,
|
|
27520
27702
|
consumedBytes: this.consumedBytes,
|
|
27703
|
+
remainingBytes: this.maxBytes - this.consumedBytes,
|
|
27521
27704
|
windowStartedAt: this.windowStartedAt,
|
|
27522
27705
|
windowMsecs: this.windowMsecs
|
|
27523
27706
|
};
|
|
@@ -36781,6 +36964,6 @@ var WalletPermissionsManager = class WalletPermissionsManager {
|
|
|
36781
36964
|
}
|
|
36782
36965
|
};
|
|
36783
36966
|
//#endregion
|
|
36784
|
-
export { ARGON2ID_DEFAULT_HASH_LENGTH, ARGON2ID_DEFAULT_ITERATIONS, ARGON2ID_DEFAULT_MEMORY_KIB, ARGON2ID_DEFAULT_PARALLELISM, ARGON2ID_MAX_ITERATIONS, ARGON2ID_MAX_MEMORY_KIB, ARGON2ID_MAX_PARALLELISM, AuthMethodInteractor, BHServiceClient, BRC153_REFERENCE_PREFIX, BulkFileDataManager, BulkFileDataReader, BulkFilesReader, BulkFilesReaderFs, BulkFilesReaderStorage, BulkHeaderFile, BulkHeaderFileFs, BulkHeaderFileStorage, BulkHeaderFiles, BulkIngestorBase, BulkIngestorCDN, BulkIngestorCDNBabbage, BulkIngestorChaintracks, BulkIngestorWhatsOnChainCdn, BulkStorageBase, CWIStyleWalletManager, Chaintracks, ChaintracksChainTracker, ChaintracksFetch, ChaintracksFetchError, ChaintracksServiceClient, ChaintracksStorageBase, ChaintracksStorageIdb, ChaintracksStorageNoDb, DEFAULT_MANAGED_CHANGE_MAX_OUTPUTS_PER_ACTION, DEFAULT_MANAGED_CHANGE_MIGRATION_INPUTS_PER_ACTION, DEFAULT_MANAGED_CHANGE_MINIMUM_SATOSHIS, DEFAULT_MANAGED_CHANGE_PENDING_COMPARISON_INPUTS, DEFAULT_MANAGED_CHANGE_TARGET_UTXOS, DEFAULT_PROFILE_ID, DEFAULT_SETTINGS, DevConsoleInteractor, EntityBase, EntityCertificate, EntityCertificateField, EntityCommission, EntityOutput, EntityOutputBasket, EntityOutputTag, EntityOutputTagMap, EntityProvenTx, EntityProvenTxReq, EntitySyncState, EntityTransaction, EntityTxLabel, EntityTxLabelMap, EntityUser, FixedWindowBulkFileDownloadBudget, GoChaintracksServiceClient, HeightRange, KDF_MAX_HASH_LENGTH, LEGACY_MANAGED_CHANGE_MINIMUM_SATOSHIS, LiveIngestorBase, LiveIngestorChaintracksSSE, LiveIngestorWhatsOnChainPoll, LocalChainTracker, MAX_STATE_SNAPSHOT_BYTES, MergeEntity, Monitor, OverlayUMPTokenInteractor, PBKDF2_MAX_ITERATIONS, PBKDF2_NUM_ROUNDS, PersonaIDInteractor, PrivilegedKeyManager, ScriptTemplateBRC29, Services, SetupClient, SimpleWalletManager, StorageClient, StorageIdb, StorageProvider, StorageSyncReader, TESTNET_DEFAULT_SETTINGS, TwilioPhoneInteractor, UMPTokenLookupError, WABAccountContinuityError, WABClient, WABClientError, WABTransport, Wallet, WalletAuthenticationManager, WalletLogger, WalletPermissionsManager, WalletSettingsManager, WalletSigner, WalletStorageManager, WhatsOnChainServices, applyBrc153ReferenceLabel, arcDefaultUrl, arcGorillaPoolUrl, arcadeDefaultUrl, arraysEqual, asArray, asBsvSdkPrivateKey, asBsvSdkPublickKey, asBsvSdkScript, asBsvSdkTx, asString, asUint8Array, brc29ProtocolID, buildChaintracksOptionsWithIngestors, convertProofToMerklePath, createAndStartDefaultChaintracks, createDefaultBulkFileDataManager, createDefaultChaintracksClient, createDefaultChaintracksStorageOptions, createDefaultIdbChaintracksOptions, createDefaultNoDbChaintracksOptions, createDefaultWalletServicesOptions, createIdbChaintracks, createNoDbChaintracks, createSyncMap, decryptBRC39, defaultManagedChangePolicy, doubleSha256BE, doubleSha256LE, encryptBRC39, exportBRC38, exportBRC38Json, exportBRC39, getIdentityKey, getLabelToSpecOp, getListOutputsSpecOp, importBRC38, importBRC39, isAutoSpendableChangeOutput, isBaseBlockHeader, isBlockHeader, isBrc153ReferenceLabel, isLegacyManagedChangeBasketDefault, isLive, isLiveBlockHeader, isManagedChangeOutput, logCreateActionArgs, logWalletError, logger, makeAtomicBeef, makeBrc114ActionTimeLabel, makeBrc153ReferenceLabel, managedChangeOutputFields, maxDate, optionalArraysEqual, outputColumnsWithoutLockingScript, parseBRC38Json, parseBrc114ActionTimeLabels, parseBrc153ReferenceLabel, parseFileLink, parseTxScriptOffsets, partitionActionLabels, randomBytes, randomBytesBase64, randomBytesHex, resolveDefaultChaintracksArguments, sdk_exports as sdk, selectBulkHeaderFiles, sha256Hash, stampLog, stampLogFormat, startChaintracks, tableAuthSessionToPeerSession, throwDummyReviewActions, toBinaryBaseBlockHeader, toDefaultChaintracksArguments, toLookupNetworkPreset, toWalletNetwork, transactionColumnsWithoutRawTx, upgradeLegacyManagedChangeBasketDefault, blockHeaderUtilities_exports as utils, validateManagedChangePolicy, validateScriptHash, validateSecondsSinceEpoch, validateStorageFeeModel, verifyHexString, verifyId, verifyInteger, verifyNumber, verifyOne, verifyOneOrNone, verifyOptionalHexString, verifyTruthy, wait, wocGetHeadersHeaderToBlockHeader };
|
|
36967
|
+
export { ARGON2ID_DEFAULT_HASH_LENGTH, ARGON2ID_DEFAULT_ITERATIONS, ARGON2ID_DEFAULT_MEMORY_KIB, ARGON2ID_DEFAULT_PARALLELISM, ARGON2ID_MAX_ITERATIONS, ARGON2ID_MAX_MEMORY_KIB, ARGON2ID_MAX_PARALLELISM, AuthMethodInteractor, BHServiceClient, BRC153_REFERENCE_PREFIX, BulkFileDataManager, BulkFileDataReader, BulkFileDataValidationError, BulkFilesReader, BulkFilesReaderFs, BulkFilesReaderStorage, BulkHeaderFile, BulkHeaderFileFs, BulkHeaderFileStorage, BulkHeaderFiles, BulkIngestorBase, BulkIngestorCDN, BulkIngestorCDNBabbage, BulkIngestorChaintracks, BulkIngestorWhatsOnChainCdn, BulkStorageBase, CWIStyleWalletManager, Chaintracks, ChaintracksChainTracker, ChaintracksFetch, ChaintracksFetchError, ChaintracksServiceClient, ChaintracksStorageBase, ChaintracksStorageIdb, ChaintracksStorageNoDb, DEFAULT_MANAGED_CHANGE_MAX_OUTPUTS_PER_ACTION, DEFAULT_MANAGED_CHANGE_MIGRATION_INPUTS_PER_ACTION, DEFAULT_MANAGED_CHANGE_MINIMUM_SATOSHIS, DEFAULT_MANAGED_CHANGE_PENDING_COMPARISON_INPUTS, DEFAULT_MANAGED_CHANGE_TARGET_UTXOS, DEFAULT_PROFILE_ID, DEFAULT_SETTINGS, DevConsoleInteractor, EntityBase, EntityCertificate, EntityCertificateField, EntityCommission, EntityOutput, EntityOutputBasket, EntityOutputTag, EntityOutputTagMap, EntityProvenTx, EntityProvenTxReq, EntitySyncState, EntityTransaction, EntityTxLabel, EntityTxLabelMap, EntityUser, FixedWindowBulkFileDownloadBudget, GoChaintracksServiceClient, HeightRange, InlineBulkFileDataValidator, KDF_MAX_HASH_LENGTH, LEGACY_MANAGED_CHANGE_MINIMUM_SATOSHIS, LiveIngestorBase, LiveIngestorChaintracksSSE, LiveIngestorWhatsOnChainPoll, LocalChainTracker, MAX_STATE_SNAPSHOT_BYTES, MergeEntity, Monitor, OverlayUMPTokenInteractor, PBKDF2_MAX_ITERATIONS, PBKDF2_NUM_ROUNDS, PersonaIDInteractor, PrivilegedKeyManager, ScriptTemplateBRC29, Services, SetupClient, SimpleWalletManager, StorageClient, StorageIdb, StorageProvider, StorageSyncReader, TESTNET_DEFAULT_SETTINGS, TwilioPhoneInteractor, UMPTokenLookupError, WABAccountContinuityError, WABClient, WABClientError, WABTransport, Wallet, WalletAuthenticationManager, WalletLogger, WalletPermissionsManager, WalletSettingsManager, WalletSigner, WalletStorageManager, WhatsOnChainServices, applyBrc153ReferenceLabel, arcDefaultUrl, arcGorillaPoolUrl, arcadeDefaultUrl, arraysEqual, asArray, asBsvSdkPrivateKey, asBsvSdkPublickKey, asBsvSdkScript, asBsvSdkTx, asString, asUint8Array, brc29ProtocolID, buildChaintracksOptionsWithIngestors, convertProofToMerklePath, createAndStartDefaultChaintracks, createDefaultBulkFileDataManager, createDefaultChaintracksClient, createDefaultChaintracksStorageOptions, createDefaultIdbChaintracksOptions, createDefaultNoDbChaintracksOptions, createDefaultWalletServicesOptions, createIdbChaintracks, createNoDbChaintracks, createSyncMap, decryptBRC39, defaultManagedChangePolicy, doubleSha256BE, doubleSha256LE, encryptBRC39, exportBRC38, exportBRC38Json, exportBRC39, getIdentityKey, getLabelToSpecOp, getListOutputsSpecOp, importBRC38, importBRC39, isAutoSpendableChangeOutput, isBaseBlockHeader, isBlockHeader, isBrc153ReferenceLabel, isLegacyManagedChangeBasketDefault, isLive, isLiveBlockHeader, isManagedChangeOutput, logCreateActionArgs, logWalletError, logger, makeAtomicBeef, makeBrc114ActionTimeLabel, makeBrc153ReferenceLabel, managedChangeOutputFields, maxDate, optionalArraysEqual, outputColumnsWithoutLockingScript, parseBRC38Json, parseBrc114ActionTimeLabels, parseBrc153ReferenceLabel, parseFileLink, parseTxScriptOffsets, partitionActionLabels, randomBytes, randomBytesBase64, randomBytesHex, resolveDefaultChaintracksArguments, sdk_exports as sdk, selectBulkHeaderFiles, sha256Hash, stampLog, stampLogFormat, startChaintracks, tableAuthSessionToPeerSession, throwDummyReviewActions, toBinaryBaseBlockHeader, toDefaultChaintracksArguments, toLookupNetworkPreset, toWalletNetwork, transactionColumnsWithoutRawTx, upgradeLegacyManagedChangeBasketDefault, blockHeaderUtilities_exports as utils, validateManagedChangePolicy, validateScriptHash, validateSecondsSinceEpoch, validateStorageFeeModel, verifyHexString, verifyId, verifyInteger, verifyNumber, verifyOne, verifyOneOrNone, verifyOptionalHexString, verifyTruthy, wait, wocGetHeadersHeaderToBlockHeader };
|
|
36785
36968
|
|
|
36786
36969
|
//# sourceMappingURL=index.client.mjs.map
|