@bsv/wallet-toolbox-client
Version:
Client only Wallet Storage
325 lines • 13.6 kB
JavaScript
;
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