UNPKG

wallet-storage-client

Version:
174 lines 7.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TaskCheckForProofs = void 0; const index_client_1 = require("../../index.client"); const WalletMonitorTask_1 = require("./WalletMonitorTask"); /** * `TaskCheckForProofs` is a WalletMonitor task that retreives merkle proofs for * transactions. * * It is normally triggered by the Chaintracks new block header event. * * When a new block is found, cwi-external-services are used to obtain proofs for * any transactions that are currently in the 'unmined' or 'unknown' state. * * If a proof is obtained and validated, a new ProvenTx record is created and * the original ProvenTxReq status is advanced to 'notifying'. */ class TaskCheckForProofs extends WalletMonitorTask_1.WalletMonitorTask { constructor(monitor, triggerMsecs = 0) { super(monitor, TaskCheckForProofs.taskName); this.triggerMsecs = triggerMsecs; } /** * Normally triggered by checkNow getting set by new block header found event from chaintracks */ trigger(nowMsecsSinceEpoch) { return { run: (TaskCheckForProofs.checkNow || this.triggerMsecs > 0 && nowMsecsSinceEpoch - this.lastRunMsecsSinceEpoch > this.triggerMsecs) }; } async runTask() { let log = ''; const countsAsAttempt = TaskCheckForProofs.checkNow; TaskCheckForProofs.checkNow = false; const limit = 100; let offset = 0; for (;;) { const reqs = await this.storage.findProvenTxReqs({ partial: {}, status: ['callback', 'unmined', 'nosend', 'sending', 'unknown', 'unconfirmed'], paged: { limit, offset } }); if (reqs.length === 0) break; log += `${reqs.length} reqs with status 'callback', 'unmined', 'nosend', 'sending', 'unknown', or 'unconfirmed'\n`; const r = await this.getProofs(reqs, 2, countsAsAttempt); log += `${r.log}\n`; //console.log(log); if (reqs.length < limit) break; offset += limit; } return log; } /** * Process an array of table.ProvenTxReq (typically with status 'unmined' or 'unknown') * * If req is invalid, set status 'invalid' * * Verify the requests are valid, lookup proofs or updated transaction status using the array of getProofServices, * * When proofs are found, create new ProvenTxApi records and transition the requests' status to 'unconfirmed' or 'notifying', * depending on chaintracks succeeding on proof verification. * * Increments attempts if proofs where requested. * * @param reqs * @returns reqs partitioned by status */ async getProofs(reqs, indent = 0, countsAsAttempt = false, ignoreStatus = false) { const proven = []; const invalid = []; let log = ''; for (const reqApi of reqs) { log += ' '.repeat(indent); log += `reqId ${reqApi.provenTxReqId} txid ${reqApi.txid}: `; if (!ignoreStatus && reqApi.status !== 'callback' && reqApi.status !== 'unmined' && reqApi.status !== 'unknown' && reqApi.status !== 'unconfirmed' && reqApi.status !== 'nosend' && reqApi.status !== 'sending') { log += `status of '${reqApi.status}' is not ready to be proven.\n`; continue; } const req = new index_client_1.entity.ProvenTxReq(reqApi); if (Number.isInteger(req.provenTxId)) { log += `Already linked to provenTxId ${req.provenTxId}.\n`; req.notified = false; req.status = 'completed'; await req.updateStorageDynamicProperties(this.storage); proven.push(reqApi); continue; } log += '\n'; let reqIsValid = false; if (req.rawTx) { const txid = (0, index_client_1.asString)((0, index_client_1.doubleSha256BE)(req.rawTx)); if (txid === req.txid) reqIsValid = true; } if (!reqIsValid) { log += ` rawTx doesn't hash to txid. status => invalid.\n`; req.notified = false; req.status = 'invalid'; await req.updateStorageDynamicProperties(this.storage); invalid.push(reqApi); continue; } const limit = this.monitor.chain === 'main' ? this.monitor.options.unprovenAttemptsLimitMain : this.monitor.options.unprovenAttemptsLimitTest; if (!ignoreStatus && req.attempts > limit) { log += ` too many failed attempts ${req.attempts}\n`; req.notified = false; req.status = 'invalid'; await req.updateStorageDynamicProperties(this.storage); invalid.push(reqApi); continue; } const since = new Date(); let r; let ptx; // External services will try multiple providers until one returns a proof, // or they all fail. // There may also be an array of proofs to consider when a transaction // is recently mined and appears in orphan blocks in addition to active chain blocks. // Since orphan blocks can end up on chain again, multiple proofs has value. // // On failure, there may be a mapi response, or an error. // // The proofs returned are considered sequentially, validating and chaintracks confirming. // // If a good proof is found, proceed to using it. // // When all received proofs fail, force a bump to the next service provider and try // one more time. // r = await this.monitor.services.getMerklePath(req.txid); ptx = await index_client_1.entity.ProvenTx.fromReq(req, r, countsAsAttempt && req.status !== 'nosend'); if (ptx) { // We have a merklePath proof for the request (and a block header) const { provenTxReqId, status, txid, attempts, history } = req.toApi(); const { index, height, blockHash, merklePath, merkleRoot } = ptx.toApi(); const r = await this.storage.runAsStorageProvider(async (sp) => { return await sp.updateProvenTxReqWithNewProvenTx({ provenTxReqId, status, txid, attempts, history, index, height, blockHash, merklePath, merkleRoot }); }); req.status = r.status; req.apiHistory = r.history; req.provenTxId = r.provenTxId; req.notified = true; } else { if (countsAsAttempt && req.status !== 'nosend') { req.attempts++; } await req.updateStorageDynamicProperties(this.storage); await req.refreshFromStorage(this.storage); } log += req.historyPretty(since, indent + 2) + '\n'; if (req.status === 'completed') proven.push(req.api); if (req.status === 'invalid') invalid.push(req.api); } return { proven, invalid, log }; } } exports.TaskCheckForProofs = TaskCheckForProofs; TaskCheckForProofs.taskName = 'CheckForProofs'; /** * An external service such as the chaintracks new block header * listener can set this true to cause */ TaskCheckForProofs.checkNow = false; //# sourceMappingURL=TaskCheckForProofs.js.map