@bsv/wallet-toolbox-client
Version:
Client only Wallet Storage
278 lines • 11.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Monitor = void 0;
const utilityHelpers_1 = require("../utility/utilityHelpers");
const TaskPurge_1 = require("./tasks/TaskPurge");
const TaskReviewStatus_1 = require("./tasks/TaskReviewStatus");
const TaskSyncWhenIdle_1 = require("./tasks/TaskSyncWhenIdle");
const TaskFailAbandoned_1 = require("./tasks/TaskFailAbandoned");
const TaskCheckForProofs_1 = require("./tasks/TaskCheckForProofs");
const TaskClock_1 = require("./tasks/TaskClock");
const TaskNewHeader_1 = require("./tasks/TaskNewHeader");
const TaskMonitorCallHistory_1 = require("./tasks/TaskMonitorCallHistory");
const TaskSendWaiting_1 = require("./tasks/TaskSendWaiting");
const TaskCheckNoSends_1 = require("./tasks/TaskCheckNoSends");
const TaskUnFail_1 = require("./tasks/TaskUnFail");
const WERR_errors_1 = require("../sdk/WERR_errors");
const WalletError_1 = require("../sdk/WalletError");
const Services_1 = require("../services/Services");
/**
* Background task to make sure transactions are processed, transaction proofs are received and propagated,
* and potentially that reorgs update proofs that were already received.
*/
class Monitor {
static createDefaultWalletMonitorOptions(chain, storage, services) {
services || (services = new Services_1.Services(chain));
if (!services.options.chaintracks)
throw new WERR_errors_1.WERR_INVALID_PARAMETER('services.options.chaintracks', 'valid');
const o = {
chain,
services,
storage,
msecsWaitPerMerkleProofServiceReq: 500,
taskRunWaitMsecs: 5000,
abandonedMsecs: 1000 * 60 * 5,
unprovenAttemptsLimitTest: 10,
unprovenAttemptsLimitMain: 144,
chaintracks: services.options.chaintracks
};
return o;
}
constructor(options) {
this.oneSecond = 1000;
this.oneMinute = 60 * this.oneSecond;
this.oneHour = 60 * this.oneMinute;
this.oneDay = 24 * this.oneHour;
this.oneWeek = 7 * this.oneDay;
/**
* _tasks are typically run by the scheduler but may also be run by runTask.
*/
this._tasks = [];
/**
* _otherTasks can be run by runTask but not by scheduler.
*/
this._otherTasks = [];
this._tasksRunning = false;
this.defaultPurgeParams = {
purgeSpent: false,
purgeCompleted: false,
purgeFailed: true,
purgeSpentAge: 2 * this.oneWeek,
purgeCompletedAge: 2 * this.oneWeek,
purgeFailedAge: 5 * this.oneDay
};
this._runAsyncSetup = true;
this.options = { ...options };
this.services = options.services;
this.chain = this.services.chain;
this.storage = options.storage;
this.chaintracks = options.chaintracks;
this.onTransactionProven = options.onTransactionProven;
this.onTransactionBroadcasted = options.onTransactionBroadcasted;
}
addAllTasksToOther() {
this._otherTasks.push(new TaskClock_1.TaskClock(this));
this._otherTasks.push(new TaskNewHeader_1.TaskNewHeader(this));
this._otherTasks.push(new TaskMonitorCallHistory_1.TaskMonitorCallHistory(this));
this._otherTasks.push(new TaskPurge_1.TaskPurge(this, this.defaultPurgeParams));
this._otherTasks.push(new TaskReviewStatus_1.TaskReviewStatus(this));
this._otherTasks.push(new TaskSendWaiting_1.TaskSendWaiting(this));
this._otherTasks.push(new TaskCheckForProofs_1.TaskCheckForProofs(this));
this._otherTasks.push(new TaskCheckNoSends_1.TaskCheckNoSends(this));
this._otherTasks.push(new TaskUnFail_1.TaskUnFail(this));
this._otherTasks.push(new TaskFailAbandoned_1.TaskFailAbandoned(this));
this._otherTasks.push(new TaskSyncWhenIdle_1.TaskSyncWhenIdle(this));
}
/**
* Default tasks with settings appropriate for a single user storage
* possibly with sync'ing enabled
*/
addDefaultTasks() {
this._tasks.push(new TaskClock_1.TaskClock(this));
this._tasks.push(new TaskNewHeader_1.TaskNewHeader(this));
this._tasks.push(new TaskMonitorCallHistory_1.TaskMonitorCallHistory(this));
this._tasks.push(new TaskSendWaiting_1.TaskSendWaiting(this, 8 * this.oneSecond, 7 * this.oneSecond)); // Check every 8 seconds but must be 7 seconds old
this._tasks.push(new TaskCheckForProofs_1.TaskCheckForProofs(this, 2 * this.oneHour)); // Every two hours if no block found
this._tasks.push(new TaskCheckNoSends_1.TaskCheckNoSends(this));
this._tasks.push(new TaskFailAbandoned_1.TaskFailAbandoned(this, 8 * this.oneMinute));
this._tasks.push(new TaskUnFail_1.TaskUnFail(this));
//this._tasks.push(new TaskPurge(this, this.defaultPurgeParams, 6 * this.oneHour))
this._tasks.push(new TaskReviewStatus_1.TaskReviewStatus(this));
}
/**
* Tasks appropriate for multi-user storage
* without sync'ing enabled.
*/
addMultiUserTasks() {
this._tasks.push(new TaskClock_1.TaskClock(this));
this._tasks.push(new TaskNewHeader_1.TaskNewHeader(this));
this._tasks.push(new TaskMonitorCallHistory_1.TaskMonitorCallHistory(this));
this._tasks.push(new TaskSendWaiting_1.TaskSendWaiting(this, 8 * this.oneSecond, 7 * this.oneSecond)); // Check every 8 seconds but must be 7 seconds old
this._tasks.push(new TaskCheckForProofs_1.TaskCheckForProofs(this, 2 * this.oneHour)); // Every two hours if no block found
this._tasks.push(new TaskCheckNoSends_1.TaskCheckNoSends(this));
this._tasks.push(new TaskFailAbandoned_1.TaskFailAbandoned(this, 8 * this.oneMinute));
this._tasks.push(new TaskUnFail_1.TaskUnFail(this));
//this._tasks.push(new TaskPurge(this, this.defaultPurgeParams, 6 * this.oneHour))
this._tasks.push(new TaskReviewStatus_1.TaskReviewStatus(this));
}
addTask(task) {
if (this._tasks.some(t => t.name === task.name))
throw new WERR_errors_1.WERR_BAD_REQUEST(`task ${task.name} has already been added.`);
this._tasks.push(task);
}
removeTask(name) {
this._tasks = this._tasks.filter(t => t.name !== name);
}
async setupChaintracksListeners() {
try {
// TODO: Use a task monitoring the newest block headere to trigger processNewHeader and reorg handling.
}
catch (err) {
/* this chaintracks doesn't support event subscriptions */
}
}
async runTask(name) {
let task = this._tasks.find(t => t.name === name);
let log = '';
if (!task)
task = this._otherTasks.find(t => t.name === name);
if (task) {
await task.asyncSetup();
log = await task.runTask();
}
return log;
}
async runOnce() {
if (this._runAsyncSetup) {
for (const t of this._tasks) {
try {
await t.asyncSetup();
}
catch (eu) {
const e = WalletError_1.WalletError.fromUnknown(eu);
const details = `monitor task ${t.name} asyncSetup error ${e.code} ${e.description}`;
console.log(details);
await this.logEvent('error0', details);
}
if (!this._tasksRunning)
break;
}
this._runAsyncSetup = false;
}
if (this.storage.getActive().isStorageProvider()) {
const tasksToRun = [];
const now = new Date().getTime();
for (const t of this._tasks) {
try {
if (t.trigger(now).run)
tasksToRun.push(t);
}
catch (eu) {
const e = WalletError_1.WalletError.fromUnknown(eu);
const details = `monitor task ${t.name} trigger error ${e.code} ${e.description}`;
console.log(details);
await this.logEvent('error0', details);
}
}
for (const ttr of tasksToRun) {
try {
if (this.storage.getActive().isStorageProvider()) {
const log = await ttr.runTask();
if (log && log.length > 0) {
console.log(`Task${ttr.name} ${log.slice(0, 256)}`);
await this.logEvent(ttr.name, log);
}
}
}
catch (eu) {
const e = WalletError_1.WalletError.fromUnknown(eu);
const details = `monitor task ${ttr.name} runTask error ${e.code} ${e.description}\n${e.stack}`;
console.log(details);
await this.logEvent('error1', details);
}
finally {
ttr.lastRunMsecsSinceEpoch = new Date().getTime();
}
}
}
}
async startTasks() {
if (this._tasksRunning)
throw new WERR_errors_1.WERR_BAD_REQUEST('monitor tasks are already runnining.');
this._tasksRunning = true;
for (; this._tasksRunning;) {
await this.runOnce();
// console.log(`${new Date().toISOString()} tasks run, waiting...`)
await (0, utilityHelpers_1.wait)(this.options.taskRunWaitMsecs);
}
}
async logEvent(event, details) {
await this.storage.runAsStorageProvider(async (sp) => {
await sp.insertMonitorEvent({
created_at: new Date(),
updated_at: new Date(),
id: 0,
event,
details
});
});
}
stopTasks() {
this._tasksRunning = false;
}
/**
* Process new chain header event received from Chaintracks
*
* Kicks processing 'unconfirmed' and 'unmined' request processing.
*
* @param reqs
*/
processNewBlockHeader(header) {
const h = header;
this.lastNewHeader = h;
this.lastNewHeaderWhen = new Date();
// console.log(`WalletMonitor notified of new block header ${h.height}`)
// Nudge the proof checker to try again.
TaskCheckForProofs_1.TaskCheckForProofs.checkNow = true;
}
/**
* This is a function run from a TaskSendWaiting Monitor task.
*
* This allows the user of wallet-toolbox to 'subscribe' for transaction broadcast updates.
*
* @param broadcastResult
*/
callOnBroadcastedTransaction(broadcastResult) {
if (this.onTransactionBroadcasted) {
this.onTransactionBroadcasted(broadcastResult);
}
}
/**
* This is a function run from a TaskCheckForProofs Monitor task.
*
* This allows the user of wallet-toolbox to 'subscribe' for transaction updates.
*
* @param txStatus
*/
callOnProvenTransaction(txStatus) {
if (this.onTransactionProven) {
this.onTransactionProven(txStatus);
}
}
/**
* Process reorg event received from Chaintracks
*
* Reorgs can move recent transactions to new blocks at new index positions.
* Affected transaction proofs become invalid and must be updated.
*
* It is possible for a transaction to become invalid.
*
* Coinbase transactions always become invalid.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
processReorg(depth, oldTip, newTip) {
/* */
}
}
exports.Monitor = Monitor;
//# sourceMappingURL=Monitor.js.map