UNPKG

@bsv/wallet-toolbox-client

Version:
325 lines 13.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ChaintracksStorageNoDb = void 0; const ChaintracksStorageBase_1 = require("../Storage/ChaintracksStorageBase"); const blockHeaderUtilities_1 = require("../util/blockHeaderUtilities"); const HeightRange_1 = require("../util/HeightRange"); const BulkFilesReader_1 = require("../util/BulkFilesReader"); const ChaintracksFetch_1 = require("../util/ChaintracksFetch"); const WERR_errors_1 = require("../../../../sdk/WERR_errors"); class ChaintracksStorageNoDb extends ChaintracksStorageBase_1.ChaintracksStorageBase { constructor(options) { super(options); } async destroy() { } async getData() { if (this.chain === 'main') { return ChaintracksStorageNoDb.mainData; } else if (this.chain === 'test') { return ChaintracksStorageNoDb.testData; } else { throw new WERR_errors_1.WERR_INVALID_PARAMETER('chain', `either 'main' or 'test. '${this.chain}' is unsupported.`); } } async deleteLiveBlockHeaders() { const data = await this.getData(); data.liveHeaders.clear(); data.maxHeaderId = 0; data.tipHeaderId = 0; data.hashToHeaderId.clear(); } async deleteOlderLiveBlockHeaders(maxHeight) { const data = await this.getData(); let deletedCount = 0; // Clear previousHeaderId references for (const [headerId, header] of data.liveHeaders) { if (header.previousHeaderId) { const prevHeader = data.liveHeaders.get(header.previousHeaderId); if (prevHeader && prevHeader.height <= maxHeight) { data.liveHeaders.set(headerId, { ...header, previousHeaderId: null }); } } } // Delete headers up to maxHeight const headersToDelete = new Set(); for (const [headerId, header] of data.liveHeaders) { if (header.height <= maxHeight) { headersToDelete.add(headerId); data.hashToHeaderId.delete(header.hash); } } deletedCount = headersToDelete.size; for (const headerId of headersToDelete) { data.liveHeaders.delete(headerId); } // Update tipHeaderId if necessary if (data.liveHeaders.size > 0) { const tip = Array.from(data.liveHeaders.values()).find(h => h.isActive && h.isChainTip); data.tipHeaderId = tip ? tip.headerId : 0; } else { data.tipHeaderId = 0; } return deletedCount; } async findChainTipHeader() { const data = await this.getData(); const tip = Array.from(data.liveHeaders.values()).find(h => h.isActive && h.isChainTip); if (!tip) throw new Error('Database contains no active chain tip header.'); return tip; } async findChainTipHeaderOrUndefined() { const data = await this.getData(); return Array.from(data.liveHeaders.values()).find(h => h.isActive && h.isChainTip); } async findLiveHeaderForBlockHash(hash) { const data = await this.getData(); const headerId = data.hashToHeaderId.get(hash); return headerId ? data.liveHeaders.get(headerId) || null : null; } async findLiveHeaderForHeaderId(headerId) { const data = await this.getData(); const header = data.liveHeaders.get(headerId); if (!header) throw new Error(`HeaderId ${headerId} not found in live header database.`); return header; } async findLiveHeaderForHeight(height) { const data = await this.getData(); return Array.from(data.liveHeaders.values()).find(h => h.height === height && h.isActive) || null; } async findLiveHeaderForMerkleRoot(merkleRoot) { const data = await this.getData(); return Array.from(data.liveHeaders.values()).find(h => h.merkleRoot === merkleRoot) || null; } async findLiveHeightRange() { const data = await this.getData(); const activeHeaders = Array.from(data.liveHeaders.values()).filter(h => h.isActive); if (activeHeaders.length === 0) { return { minHeight: 0, maxHeight: -1 }; } const minHeight = Math.min(...activeHeaders.map(h => h.height)); const maxHeight = Math.max(...activeHeaders.map(h => h.height)); return { minHeight, maxHeight }; } async findMaxHeaderId() { const data = await this.getData(); return data.maxHeaderId; } async getLiveHeightRange() { const data = await this.getData(); const activeHeaders = Array.from(data.liveHeaders.values()).filter(h => h.isActive); if (activeHeaders.length === 0) { return new HeightRange_1.HeightRange(0, -1); } const minHeight = Math.min(...activeHeaders.map(h => h.height)); const maxHeight = Math.max(...activeHeaders.map(h => h.height)); return new HeightRange_1.HeightRange(minHeight, maxHeight); } async liveHeadersForBulk(count) { const data = await this.getData(); return Array.from(data.liveHeaders.values()) .filter(h => h.isActive) .sort((a, b) => a.height - b.height) .slice(0, count); } async getHeaders(height, count) { if (count <= 0) return []; const data = await this.getData(); const headers = Array.from(data.liveHeaders.values()) .filter(h => h.isActive && h.height >= height && h.height < height + count) .sort((a, b) => a.height - b.height) .slice(0, count); const bufs = []; if (headers.length === 0 || headers[0].height > height) { const bulkCount = headers.length === 0 ? count : headers[0].height - height; const range = new HeightRange_1.HeightRange(height, height + bulkCount - 1); const reader = await BulkFilesReader_1.BulkFilesReaderStorage.fromStorage(this, new ChaintracksFetch_1.ChaintracksFetch(), range, bulkCount * 80); const bulkData = await reader.read(); if (bulkData) { bufs.push(bulkData); } } if (headers.length > 0) { let buf = new Uint8Array(headers.length * 80); for (let i = 0; i < headers.length; i++) { const h = headers[i]; const ha = (0, blockHeaderUtilities_1.serializeBaseBlockHeader)(h); buf.set(ha, i * 80); } bufs.push(buf); } const r = []; for (const bh of bufs) { for (const b of bh) { r.push(b); } } return r; } async insertHeader(header, prev) { const data = await this.getData(); let ok = true; let dupe = false; let noPrev = false; let badPrev = false; let noActiveAncestor = false; let noTip = false; let setActiveChainTip = false; let reorgDepth = 0; let priorTip; // Check for duplicate if (data.hashToHeaderId.has(header.hash)) { dupe = true; return { added: false, dupe, isActiveTip: false, reorgDepth, priorTip, noPrev, badPrev, noActiveAncestor, noTip }; } // Find previous header let oneBack = Array.from(data.liveHeaders.values()).find(h => h.hash === header.previousHash); if (!oneBack && prev && prev.hash === header.previousHash && prev.height + 1 === header.height) { oneBack = prev; } if (!oneBack) { // Check if this is first live header if (data.liveHeaders.size === 0) { const lbf = await this.bulkManager.getLastFile(); if (lbf && header.previousHash === lbf.lastHash && header.height === lbf.firstHeight + lbf.count) { const chainWork = (0, blockHeaderUtilities_1.addWork)(lbf.lastChainWork, (0, blockHeaderUtilities_1.convertBitsToWork)(header.bits)); const newHeader = { ...header, headerId: ++data.maxHeaderId, previousHeaderId: null, chainWork, isChainTip: true, isActive: true }; data.liveHeaders.set(newHeader.headerId, newHeader); data.hashToHeaderId.set(header.hash, newHeader.headerId); data.tipHeaderId = newHeader.headerId; return { added: true, dupe, isActiveTip: true, reorgDepth, priorTip, noPrev, badPrev, noActiveAncestor, noTip }; } noPrev = true; return { added: false, dupe, isActiveTip: false, reorgDepth, priorTip, noPrev, badPrev, noActiveAncestor, noTip }; } noPrev = true; return { added: false, dupe, isActiveTip: false, reorgDepth, priorTip, noPrev, badPrev, noActiveAncestor, noTip }; } if (oneBack.height + 1 !== header.height) { badPrev = true; return { added: false, dupe, isActiveTip: false, reorgDepth, priorTip, noPrev, badPrev, noActiveAncestor, noTip }; } const chainWork = (0, blockHeaderUtilities_1.addWork)(oneBack.chainWork, (0, blockHeaderUtilities_1.convertBitsToWork)(header.bits)); let tip = oneBack.isActive && oneBack.isChainTip ? oneBack : Array.from(data.liveHeaders.values()).find(h => h.isActive && h.isChainTip); if (!tip) { noTip = true; return { added: false, dupe, isActiveTip: false, reorgDepth, priorTip, noPrev, badPrev, noActiveAncestor, noTip }; } priorTip = tip; setActiveChainTip = (0, blockHeaderUtilities_1.isMoreWork)(chainWork, tip.chainWork); const newHeader = { ...header, headerId: ++data.maxHeaderId, previousHeaderId: oneBack === prev ? null : oneBack.headerId, chainWork, isChainTip: setActiveChainTip, isActive: setActiveChainTip }; if (setActiveChainTip) { let activeAncestor = oneBack; while (!activeAncestor.isActive) { const previousHeader = data.liveHeaders.get(activeAncestor.previousHeaderId); if (!previousHeader) { noActiveAncestor = true; return { added: false, dupe, isActiveTip: false, reorgDepth, priorTip, noPrev, badPrev, noActiveAncestor, noTip }; } activeAncestor = previousHeader; } if (!(oneBack.isActive && oneBack.isChainTip)) { reorgDepth = Math.min(priorTip.height, header.height) - activeAncestor.height; } if (activeAncestor.headerId !== oneBack.headerId) { let headerToDeactivate = Array.from(data.liveHeaders.values()).find(h => h.isChainTip && h.isActive); while (headerToDeactivate && headerToDeactivate.headerId !== activeAncestor.headerId) { data.liveHeaders.set(headerToDeactivate.headerId, { ...headerToDeactivate, isActive: false }); headerToDeactivate = data.liveHeaders.get(headerToDeactivate.previousHeaderId); } let headerToActivate = oneBack; while (headerToActivate.headerId !== activeAncestor.headerId) { data.liveHeaders.set(headerToActivate.headerId, { ...headerToActivate, isActive: true }); headerToActivate = data.liveHeaders.get(headerToActivate.previousHeaderId); } } } if (oneBack.isChainTip && oneBack !== prev) { data.liveHeaders.set(oneBack.headerId, { ...oneBack, isChainTip: false }); } data.liveHeaders.set(newHeader.headerId, newHeader); data.hashToHeaderId.set(newHeader.hash, newHeader.headerId); if (setActiveChainTip) { data.tipHeaderId = newHeader.headerId; this.pruneLiveBlockHeaders(newHeader.height); } return { added: ok, dupe, isActiveTip: setActiveChainTip, reorgDepth, priorTip, noPrev, badPrev, noActiveAncestor, noTip }; } } exports.ChaintracksStorageNoDb = ChaintracksStorageNoDb; ChaintracksStorageNoDb.mainData = { chain: 'main', liveHeaders: new Map(), maxHeaderId: 0, tipHeaderId: 0, hashToHeaderId: new Map() }; ChaintracksStorageNoDb.testData = { chain: 'test', liveHeaders: new Map(), maxHeaderId: 0, tipHeaderId: 0, hashToHeaderId: new Map() }; //# sourceMappingURL=ChaintracksStorageNoDb.js.map