@bsv/wallet-toolbox-client
Version:
Client only Wallet Storage
1,124 lines • 53.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.StorageKnex = void 0;
const tables_1 = require("./schema/tables");
const KnexMigrations_1 = require("./schema/KnexMigrations");
const StorageProvider_1 = require("./StorageProvider");
const purgeData_1 = require("./methods/purgeData");
const listActionsKnex_1 = require("./methods/listActionsKnex");
const listOutputsKnex_1 = require("./methods/listOutputsKnex");
const reviewStatus_1 = require("./methods/reviewStatus");
const WERR_errors_1 = require("../sdk/WERR_errors");
const utilityHelpers_1 = require("../utility/utilityHelpers");
class StorageKnex extends StorageProvider_1.StorageProvider {
constructor(options) {
super(options);
this._verifiedReadyForDatabaseAccess = false;
if (!options.knex)
throw new WERR_errors_1.WERR_INVALID_PARAMETER('options.knex', `valid`);
this.knex = options.knex;
}
async readSettings() {
return this.validateEntity((0, utilityHelpers_1.verifyOne)(await this.toDb(undefined)('settings')));
}
async getProvenOrRawTx(txid, trx) {
const k = this.toDb(trx);
const r = {
proven: undefined,
rawTx: undefined,
inputBEEF: undefined
};
r.proven = (0, utilityHelpers_1.verifyOneOrNone)(await this.findProvenTxs({ partial: { txid: txid } }));
if (!r.proven) {
const reqRawTx = (0, utilityHelpers_1.verifyOneOrNone)(await k('proven_tx_reqs')
.where('txid', txid)
.whereIn('status', ['unsent', 'unmined', 'unconfirmed', 'sending', 'nosend', 'completed'])
.select('rawTx', 'inputBEEF'));
if (reqRawTx) {
r.rawTx = Array.from(reqRawTx.rawTx);
r.inputBEEF = Array.from(reqRawTx.inputBEEF);
}
}
return r;
}
dbTypeSubstring(source, fromOffset, forLength) {
if (this.dbtype === 'MySQL')
return `substring(${source} from ${fromOffset} for ${forLength})`;
return `substr(${source}, ${fromOffset}, ${forLength})`;
}
async getRawTxOfKnownValidTransaction(txid, offset, length, trx) {
if (!txid)
return undefined;
if (!this.isAvailable())
await this.makeAvailable();
let rawTx = undefined;
if (Number.isInteger(offset) && Number.isInteger(length)) {
let rs = await this.toDb(trx).raw(`select ${this.dbTypeSubstring('rawTx', offset + 1, length)} as rawTx from proven_txs where txid = '${txid}'`);
if (this.dbtype === 'MySQL')
rs = rs[0];
const r = (0, utilityHelpers_1.verifyOneOrNone)(rs);
if (r && r.rawTx) {
rawTx = Array.from(r.rawTx);
}
else {
let rs = await this.toDb(trx).raw(`select ${this.dbTypeSubstring('rawTx', offset + 1, length)} as rawTx from proven_tx_reqs where txid = '${txid}' and status in ('unsent', 'nosend', 'sending', 'unmined', 'completed', 'unfail')`);
if (this.dbtype === 'MySQL')
rs = rs[0];
const r = (0, utilityHelpers_1.verifyOneOrNone)(rs);
if (r && r.rawTx) {
rawTx = Array.from(r.rawTx);
}
}
}
else {
const r = await this.getProvenOrRawTx(txid, trx);
if (r.proven)
rawTx = r.proven.rawTx;
else
rawTx = r.rawTx;
}
return rawTx;
}
getProvenTxsForUserQuery(args) {
const k = this.toDb(args.trx);
let q = k('proven_txs').where(function () {
this.whereExists(k
.select('*')
.from('transactions')
.whereRaw(`proven_txs.provenTxId = transactions.provenTxId and transactions.userId = ${args.userId}`));
});
if (args.paged) {
q = q.limit(args.paged.limit);
q = q.offset(args.paged.offset || 0);
}
if (args.since)
q = q.where('updated_at', '>=', this.validateDateForWhere(args.since));
return q;
}
async getProvenTxsForUser(args) {
const q = this.getProvenTxsForUserQuery(args);
const rs = await q;
return this.validateEntities(rs);
}
getProvenTxReqsForUserQuery(args) {
const k = this.toDb(args.trx);
let q = k('proven_tx_reqs').where(function () {
this.whereExists(k
.select('*')
.from('transactions')
.whereRaw(`proven_tx_reqs.txid = transactions.txid and transactions.userId = ${args.userId}`));
});
if (args.paged) {
q = q.limit(args.paged.limit);
q = q.offset(args.paged.offset || 0);
}
if (args.since)
q = q.where('updated_at', '>=', this.validateDateForWhere(args.since));
return q;
}
async getProvenTxReqsForUser(args) {
const q = this.getProvenTxReqsForUserQuery(args);
const rs = await q;
return this.validateEntities(rs, undefined, ['notified']);
}
getTxLabelMapsForUserQuery(args) {
const k = this.toDb(args.trx);
let q = k('tx_labels_map').whereExists(k
.select('*')
.from('tx_labels')
.whereRaw(`tx_labels.txLabelId = tx_labels_map.txLabelId and tx_labels.userId = ${args.userId}`));
if (args.since)
q = q.where('updated_at', '>=', this.validateDateForWhere(args.since));
if (args.paged) {
q = q.limit(args.paged.limit);
q = q.offset(args.paged.offset || 0);
}
return q;
}
async getTxLabelMapsForUser(args) {
const q = this.getTxLabelMapsForUserQuery(args);
const rs = await q;
return this.validateEntities(rs, undefined, ['isDeleted']);
}
getOutputTagMapsForUserQuery(args) {
const k = this.toDb(args.trx);
let q = k('output_tags_map').whereExists(k
.select('*')
.from('output_tags')
.whereRaw(`output_tags.outputTagId = output_tags_map.outputTagId and output_tags.userId = ${args.userId}`));
if (args.since)
q = q.where('updated_at', '>=', this.validateDateForWhere(args.since));
if (args.paged) {
q = q.limit(args.paged.limit);
q = q.offset(args.paged.offset || 0);
}
return q;
}
async getOutputTagMapsForUser(args) {
const q = this.getOutputTagMapsForUserQuery(args);
const rs = await q;
return this.validateEntities(rs, undefined, ['isDeleted']);
}
async listActions(auth, vargs) {
if (!auth.userId)
throw new WERR_errors_1.WERR_UNAUTHORIZED();
return await (0, listActionsKnex_1.listActions)(this, auth, vargs);
}
async listOutputs(auth, vargs) {
if (!auth.userId)
throw new WERR_errors_1.WERR_UNAUTHORIZED();
return await (0, listOutputsKnex_1.listOutputs)(this, auth, vargs);
}
async insertProvenTx(tx, trx) {
const e = await this.validateEntityForInsert(tx, trx);
if (e.provenTxId === 0)
delete e.provenTxId;
const [id] = await this.toDb(trx)('proven_txs').insert(e);
tx.provenTxId = id;
return tx.provenTxId;
}
async insertProvenTxReq(tx, trx) {
const e = await this.validateEntityForInsert(tx, trx);
if (e.provenTxReqId === 0)
delete e.provenTxReqId;
const [id] = await this.toDb(trx)('proven_tx_reqs').insert(e);
tx.provenTxReqId = id;
return tx.provenTxReqId;
}
async insertUser(user, trx) {
const e = await this.validateEntityForInsert(user, trx);
if (e.userId === 0)
delete e.userId;
const [id] = await this.toDb(trx)('users').insert(e);
user.userId = id;
return user.userId;
}
async insertCertificateAuth(auth, certificate) {
if (!auth.userId || (certificate.userId && certificate.userId !== auth.userId))
throw new WERR_errors_1.WERR_UNAUTHORIZED();
certificate.userId = auth.userId;
return await this.insertCertificate(certificate);
}
async insertCertificate(certificate, trx) {
const e = await this.validateEntityForInsert(certificate, trx, undefined, ['isDeleted']);
const fields = e.fields;
if (e.fields)
delete e.fields;
if (e.certificateId === 0)
delete e.certificateId;
const [id] = await this.toDb(trx)('certificates').insert(e);
certificate.certificateId = id;
if (fields) {
for (const field of fields) {
field.certificateId = id;
field.userId = certificate.userId;
await this.insertCertificateField(field, trx);
}
}
return certificate.certificateId;
}
async insertCertificateField(certificateField, trx) {
const e = await this.validateEntityForInsert(certificateField, trx);
await this.toDb(trx)('certificate_fields').insert(e);
}
async insertOutputBasket(basket, trx) {
const e = await this.validateEntityForInsert(basket, trx, undefined, ['isDeleted']);
if (e.basketId === 0)
delete e.basketId;
const [id] = await this.toDb(trx)('output_baskets').insert(e);
basket.basketId = id;
return basket.basketId;
}
async insertTransaction(tx, trx) {
const e = await this.validateEntityForInsert(tx, trx);
if (e.transactionId === 0)
delete e.transactionId;
const [id] = await this.toDb(trx)('transactions').insert(e);
tx.transactionId = id;
return tx.transactionId;
}
async insertCommission(commission, trx) {
const e = await this.validateEntityForInsert(commission, trx);
if (e.commissionId === 0)
delete e.commissionId;
const [id] = await this.toDb(trx)('commissions').insert(e);
commission.commissionId = id;
return commission.commissionId;
}
async insertOutput(output, trx) {
try {
const e = await this.validateEntityForInsert(output, trx);
if (e.outputId === 0)
delete e.outputId;
const [id] = await this.toDb(trx)('outputs').insert(e);
output.outputId = id;
return output.outputId;
}
catch (e) {
throw e;
}
}
async insertOutputTag(tag, trx) {
const e = await this.validateEntityForInsert(tag, trx, undefined, ['isDeleted']);
if (e.outputTagId === 0)
delete e.outputTagId;
const [id] = await this.toDb(trx)('output_tags').insert(e);
tag.outputTagId = id;
return tag.outputTagId;
}
async insertOutputTagMap(tagMap, trx) {
const e = await this.validateEntityForInsert(tagMap, trx, undefined, ['isDeleted']);
const [id] = await this.toDb(trx)('output_tags_map').insert(e);
}
async insertTxLabel(label, trx) {
const e = await this.validateEntityForInsert(label, trx, undefined, ['isDeleted']);
if (e.txLabelId === 0)
delete e.txLabelId;
const [id] = await this.toDb(trx)('tx_labels').insert(e);
label.txLabelId = id;
return label.txLabelId;
}
async insertTxLabelMap(labelMap, trx) {
const e = await this.validateEntityForInsert(labelMap, trx, undefined, ['isDeleted']);
const [id] = await this.toDb(trx)('tx_labels_map').insert(e);
}
async insertMonitorEvent(event, trx) {
const e = await this.validateEntityForInsert(event, trx);
if (e.id === 0)
delete e.id;
const [id] = await this.toDb(trx)('monitor_events').insert(e);
event.id = id;
return event.id;
}
async insertSyncState(syncState, trx) {
const e = await this.validateEntityForInsert(syncState, trx, ['when'], ['init']);
if (e.syncStateId === 0)
delete e.syncStateId;
const [id] = await this.toDb(trx)('sync_states').insert(e);
syncState.syncStateId = id;
return syncState.syncStateId;
}
async updateCertificateField(certificateId, fieldName, update, trx) {
await this.verifyReadyForDatabaseAccess(trx);
return await this.toDb(trx)('certificate_fields')
.where({ certificateId, fieldName })
.update(this.validatePartialForUpdate(update));
}
async updateCertificate(id, update, trx) {
await this.verifyReadyForDatabaseAccess(trx);
return await this.toDb(trx)('certificates')
.where({ certificateId: id })
.update(this.validatePartialForUpdate(update, undefined, ['isDeleted']));
}
async updateCommission(id, update, trx) {
await this.verifyReadyForDatabaseAccess(trx);
return await this.toDb(trx)('commissions')
.where({ commissionId: id })
.update(this.validatePartialForUpdate(update));
}
async updateOutputBasket(id, update, trx) {
await this.verifyReadyForDatabaseAccess(trx);
return await this.toDb(trx)('output_baskets')
.where({ basketId: id })
.update(this.validatePartialForUpdate(update, undefined, ['isDeleted']));
}
async updateOutput(id, update, trx) {
await this.verifyReadyForDatabaseAccess(trx);
return await this.toDb(trx)('outputs')
.where({ outputId: id })
.update(this.validatePartialForUpdate(update));
}
async updateOutputTagMap(outputId, tagId, update, trx) {
await this.verifyReadyForDatabaseAccess(trx);
return await this.toDb(trx)('output_tags_map')
.where({ outputId, outputTagId: tagId })
.update(this.validatePartialForUpdate(update, undefined, ['isDeleted']));
}
async updateOutputTag(id, update, trx) {
await this.verifyReadyForDatabaseAccess(trx);
return await this.toDb(trx)('output_tags')
.where({ outputTagId: id })
.update(this.validatePartialForUpdate(update, undefined, ['isDeleted']));
}
async updateProvenTxReq(id, update, trx) {
await this.verifyReadyForDatabaseAccess(trx);
let r;
if (Array.isArray(id)) {
r = await this.toDb(trx)('proven_tx_reqs')
.whereIn('provenTxReqId', id)
.update(this.validatePartialForUpdate(update));
}
else if (Number.isInteger(id)) {
r = await this.toDb(trx)('proven_tx_reqs')
.where({ provenTxReqId: id })
.update(this.validatePartialForUpdate(update));
}
else {
throw new WERR_errors_1.WERR_INVALID_PARAMETER('id', 'transactionId or array of transactionId');
}
return r;
}
async updateProvenTx(id, update, trx) {
await this.verifyReadyForDatabaseAccess(trx);
return await this.toDb(trx)('proven_txs')
.where({ provenTxId: id })
.update(this.validatePartialForUpdate(update));
}
async updateSyncState(id, update, trx) {
await this.verifyReadyForDatabaseAccess(trx);
return await this.toDb(trx)('sync_states')
.where({ syncStateId: id })
.update(this.validatePartialForUpdate(update, ['when'], ['init']));
}
async updateTransaction(id, update, trx) {
await this.verifyReadyForDatabaseAccess(trx);
let r;
if (Array.isArray(id)) {
r = await this.toDb(trx)('transactions')
.whereIn('transactionId', id)
.update(await this.validatePartialForUpdate(update));
}
else if (Number.isInteger(id)) {
r = await this.toDb(trx)('transactions')
.where({ transactionId: id })
.update(await this.validatePartialForUpdate(update));
}
else {
throw new WERR_errors_1.WERR_INVALID_PARAMETER('id', 'transactionId or array of transactionId');
}
return r;
}
async updateTxLabelMap(transactionId, txLabelId, update, trx) {
await this.verifyReadyForDatabaseAccess(trx);
return await this.toDb(trx)('tx_labels_map')
.where({ transactionId, txLabelId })
.update(this.validatePartialForUpdate(update, undefined, ['isDeleted']));
}
async updateTxLabel(id, update, trx) {
await this.verifyReadyForDatabaseAccess(trx);
return await this.toDb(trx)('tx_labels')
.where({ txLabelId: id })
.update(this.validatePartialForUpdate(update, undefined, ['isDeleted']));
}
async updateUser(id, update, trx) {
await this.verifyReadyForDatabaseAccess(trx);
return await this.toDb(trx)('users').where({ userId: id }).update(this.validatePartialForUpdate(update));
}
async updateMonitorEvent(id, update, trx) {
await this.verifyReadyForDatabaseAccess(trx);
return await this.toDb(trx)('monitor_events')
.where({ id })
.update(this.validatePartialForUpdate(update));
}
setupQuery(table, args) {
let q = this.toDb(args.trx)(table);
if (args.partial && Object.keys(args.partial).length > 0)
q.where(args.partial);
if (args.since)
q.where('updated_at', '>=', this.validateDateForWhere(args.since));
if (args.orderDescending) {
let sortColumn = '';
switch (table) {
case 'certificates':
sortColumn = 'certificateId';
break;
case 'commissions':
sortColumn = 'commissionId';
break;
case 'output_baskets':
sortColumn = 'basketId';
break;
case 'outputs':
sortColumn = 'outputId';
break;
case 'output_tags':
sortColumn = 'outputTagId';
break;
case 'proven_tx_reqs':
sortColumn = 'provenTxReqId';
break;
case 'proven_txs':
sortColumn = 'provenTxId';
break;
case 'sync_states':
sortColumn = 'syncStateId';
break;
case 'transactions':
sortColumn = 'transactionId';
break;
case 'tx_labels':
sortColumn = 'txLabelId';
break;
case 'users':
sortColumn = 'userId';
break;
case 'monitor_events':
sortColumn = 'id';
break;
default:
break;
}
if (sortColumn !== '') {
q.orderBy(sortColumn, 'desc');
}
}
if (args.paged) {
q.limit(args.paged.limit);
q.offset(args.paged.offset || 0);
}
return q;
}
findCertificateFieldsQuery(args) {
return this.setupQuery('certificate_fields', args);
}
findCertificatesQuery(args) {
const q = this.setupQuery('certificates', args);
if (args.certifiers && args.certifiers.length > 0)
q.whereIn('certifier', args.certifiers);
if (args.types && args.types.length > 0)
q.whereIn('type', args.types);
return q;
}
findCommissionsQuery(args) {
if (args.partial.lockingScript)
throw new WERR_errors_1.WERR_INVALID_PARAMETER('partial.lockingScript', `undefined. Commissions may not be found by lockingScript value.`);
return this.setupQuery('commissions', args);
}
findOutputBasketsQuery(args) {
return this.setupQuery('output_baskets', args);
}
findOutputsQuery(args, count) {
if (args.partial.lockingScript)
throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.lockingScript', `undefined. Outputs may not be found by lockingScript value.`);
const q = this.setupQuery('outputs', args);
if (args.txStatus && args.txStatus.length > 0) {
q.whereRaw(`(select status from transactions where transactions.transactionId = outputs.transactionId) in (${args.txStatus.map(s => `'${s}'`).join(',')})`);
}
if (args.noScript && !count) {
const columns = tables_1.outputColumnsWithoutLockingScript.map(c => `outputs.${c}`);
q.select(columns);
}
return q;
}
findOutputTagMapsQuery(args) {
const q = this.setupQuery('output_tags_map', args);
if (args.tagIds && args.tagIds.length > 0)
q.whereIn('outputTagId', args.tagIds);
return q;
}
findOutputTagsQuery(args) {
return this.setupQuery('output_tags', args);
}
findProvenTxReqsQuery(args) {
if (args.partial.rawTx)
throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.rawTx', `undefined. ProvenTxReqs may not be found by rawTx value.`);
if (args.partial.inputBEEF)
throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.inputBEEF', `undefined. ProvenTxReqs may not be found by inputBEEF value.`);
const q = this.setupQuery('proven_tx_reqs', args);
if (args.status && args.status.length > 0)
q.whereIn('status', args.status);
if (args.txids) {
const txids = args.txids.filter(txid => txid !== undefined);
if (txids.length > 0)
q.whereIn('txid', txids);
}
return q;
}
findProvenTxsQuery(args) {
if (args.partial.rawTx)
throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.rawTx', `undefined. ProvenTxs may not be found by rawTx value.`);
if (args.partial.merklePath)
throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.merklePath', `undefined. ProvenTxs may not be found by merklePath value.`);
return this.setupQuery('proven_txs', args);
}
findSyncStatesQuery(args) {
return this.setupQuery('sync_states', args);
}
findTransactionsQuery(args, count) {
if (args.partial.rawTx)
throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.rawTx', `undefined. Transactions may not be found by rawTx value.`);
if (args.partial.inputBEEF)
throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.inputBEEF', `undefined. Transactions may not be found by inputBEEF value.`);
const q = this.setupQuery('transactions', args);
if (args.status && args.status.length > 0)
q.whereIn('status', args.status);
if (args.noRawTx && !count) {
const columns = tables_1.transactionColumnsWithoutRawTx.map(c => `transactions.${c}`);
q.select(columns);
}
return q;
}
findTxLabelMapsQuery(args) {
const q = this.setupQuery('tx_labels_map', args);
if (args.labelIds && args.labelIds.length > 0)
q.whereIn('txLabelId', args.labelIds);
return q;
}
findTxLabelsQuery(args) {
return this.setupQuery('tx_labels', args);
}
findUsersQuery(args) {
return this.setupQuery('users', args);
}
findMonitorEventsQuery(args) {
return this.setupQuery('monitor_events', args);
}
async findCertificatesAuth(auth, args) {
if (!auth.userId || (args.partial.userId && args.partial.userId !== auth.userId))
throw new WERR_errors_1.WERR_UNAUTHORIZED();
args.partial.userId = auth.userId;
return await this.findCertificates(args);
}
async findOutputBasketsAuth(auth, args) {
if (!auth.userId || (args.partial.userId && args.partial.userId !== auth.userId))
throw new WERR_errors_1.WERR_UNAUTHORIZED();
args.partial.userId = auth.userId;
return await this.findOutputBaskets(args);
}
async findOutputsAuth(auth, args) {
if (!auth.userId || (args.partial.userId && args.partial.userId !== auth.userId))
throw new WERR_errors_1.WERR_UNAUTHORIZED();
args.partial.userId = auth.userId;
return await this.findOutputs(args);
}
async findCertificateFields(args) {
return this.validateEntities(await this.findCertificateFieldsQuery(args));
}
async findCertificates(args) {
const q = this.findCertificatesQuery(args);
let r = await q;
r = this.validateEntities(r, undefined, ['isDeleted']);
if (args.includeFields) {
for (const c of r) {
c.fields = this.validateEntities(await this.findCertificateFields({
partial: { certificateId: c.certificateId, userId: c.userId },
trx: args.trx
}));
}
}
return r;
}
async findCommissions(args) {
const q = this.findCommissionsQuery(args);
const r = await q;
return this.validateEntities(r, undefined, ['isRedeemed']);
}
async findOutputBaskets(args) {
const q = this.findOutputBasketsQuery(args);
const r = await q;
return this.validateEntities(r, undefined, ['isDeleted']);
}
async findOutputs(args) {
const q = this.findOutputsQuery(args);
const r = await q;
if (!args.noScript) {
for (const o of r) {
await this.validateOutputScript(o, args.trx);
}
}
return this.validateEntities(r, undefined, ['spendable', 'change']);
}
async findOutputTagMaps(args) {
const q = this.findOutputTagMapsQuery(args);
const r = await q;
return this.validateEntities(r, undefined, ['isDeleted']);
}
async findOutputTags(args) {
const q = this.findOutputTagsQuery(args);
const r = await q;
return this.validateEntities(r, undefined, ['isDeleted']);
}
async findProvenTxReqs(args) {
const q = this.findProvenTxReqsQuery(args);
const r = await q;
return this.validateEntities(r, undefined, ['notified']);
}
async findProvenTxs(args) {
const q = this.findProvenTxsQuery(args);
const r = await q;
return this.validateEntities(r);
}
async findSyncStates(args) {
const q = this.findSyncStatesQuery(args);
const r = await q;
return this.validateEntities(r, ['when'], ['init']);
}
async findTransactions(args) {
const q = this.findTransactionsQuery(args);
const r = await q;
if (!args.noRawTx) {
for (const t of r) {
await this.validateRawTransaction(t, args.trx);
}
}
return this.validateEntities(r, undefined, ['isOutgoing']);
}
async findTxLabelMaps(args) {
const q = this.findTxLabelMapsQuery(args);
const r = await q;
return this.validateEntities(r, undefined, ['isDeleted']);
}
async findTxLabels(args) {
const q = this.findTxLabelsQuery(args);
const r = await q;
return this.validateEntities(r, undefined, ['isDeleted']);
}
async findUsers(args) {
const q = this.findUsersQuery(args);
const r = await q;
return this.validateEntities(r);
}
async findMonitorEvents(args) {
const q = this.findMonitorEventsQuery(args);
const r = await q;
return this.validateEntities(r, ['when'], undefined);
}
async getCount(q) {
q.count();
const r = await q;
return r[0]['count(*)'];
}
async countCertificateFields(args) {
return await this.getCount(this.findCertificateFieldsQuery(args));
}
async countCertificates(args) {
return await this.getCount(this.findCertificatesQuery(args));
}
async countCommissions(args) {
return await this.getCount(this.findCommissionsQuery(args));
}
async countOutputBaskets(args) {
return await this.getCount(this.findOutputBasketsQuery(args));
}
async countOutputs(args) {
return await this.getCount(this.findOutputsQuery(args, true));
}
async countOutputTagMaps(args) {
return await this.getCount(this.findOutputTagMapsQuery(args));
}
async countOutputTags(args) {
return await this.getCount(this.findOutputTagsQuery(args));
}
async countProvenTxReqs(args) {
return await this.getCount(this.findProvenTxReqsQuery(args));
}
async countProvenTxs(args) {
return await this.getCount(this.findProvenTxsQuery(args));
}
async countSyncStates(args) {
return await this.getCount(this.findSyncStatesQuery(args));
}
async countTransactions(args) {
return await this.getCount(this.findTransactionsQuery(args, true));
}
async countTxLabelMaps(args) {
return await this.getCount(this.findTxLabelMapsQuery(args));
}
async countTxLabels(args) {
return await this.getCount(this.findTxLabelsQuery(args));
}
async countUsers(args) {
return await this.getCount(this.findUsersQuery(args));
}
async countMonitorEvents(args) {
return await this.getCount(this.findMonitorEventsQuery(args));
}
async destroy() {
var _a;
await ((_a = this.knex) === null || _a === void 0 ? void 0 : _a.destroy());
}
async migrate(storageName, storageIdentityKey) {
const config = {
migrationSource: new KnexMigrations_1.KnexMigrations(this.chain, storageName, storageIdentityKey, 1024)
};
await this.knex.migrate.latest(config);
const version = await this.knex.migrate.currentVersion(config);
return version;
}
async dropAllData() {
// Only using migrations to migrate down, don't need valid properties for settings table.
const config = {
migrationSource: new KnexMigrations_1.KnexMigrations('test', '', '', 1024)
};
const count = Object.keys(config.migrationSource.migrations).length;
for (let i = 0; i < count; i++) {
try {
const r = await this.knex.migrate.down(config);
if (!r) {
console.error(`Migration returned falsy result await this.knex.migrate.down(config)`);
break;
}
}
catch (eu) {
break;
}
}
}
async transaction(scope, trx) {
if (trx)
return await scope(trx);
return await this.knex.transaction(async (knextrx) => {
const trx = knextrx;
return await scope(trx);
});
}
/**
* Convert the standard optional `TrxToken` parameter into either a direct knex database instance,
* or a Knex.Transaction as appropriate.
*/
toDb(trx) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const db = !trx ? this.knex : trx;
this.whenLastAccess = new Date();
return db;
}
async validateRawTransaction(t, trx) {
// if there is no txid or there is a rawTransaction return what we have.
if (t.rawTx || !t.txid)
return;
// rawTransaction is missing, see if we moved it ...
const rawTx = await this.getRawTxOfKnownValidTransaction(t.txid, undefined, undefined, trx);
if (!rawTx)
return;
t.rawTx = rawTx;
}
/**
* Make sure database is ready for access:
*
* - dateScheme is known
* - foreign key constraints are enabled
*
* @param trx
*/
async verifyReadyForDatabaseAccess(trx) {
if (!this._settings) {
this._settings = await this.readSettings();
}
if (!this._verifiedReadyForDatabaseAccess) {
// Make sure foreign key constraint checking is turned on in SQLite.
if (this._settings.dbtype === 'SQLite') {
await this.toDb(trx).raw('PRAGMA foreign_keys = ON;');
}
this._verifiedReadyForDatabaseAccess = true;
}
return this._settings.dbtype;
}
/**
* Helper to force uniform behavior across database engines.
* Use to process the update template for entities being updated.
*/
validatePartialForUpdate(update, dateFields, booleanFields) {
if (!this.dbtype)
throw new WERR_errors_1.WERR_INTERNAL('must call verifyReadyForDatabaseAccess first');
const v = update;
if (v.created_at)
v.created_at = this.validateEntityDate(v.created_at);
if (v.updated_at)
v.updated_at = this.validateEntityDate(v.updated_at);
if (!v.created_at)
delete v.created_at;
if (!v.updated_at)
v.updated_at = this.validateEntityDate(new Date());
if (dateFields) {
for (const df of dateFields) {
if (v[df])
v[df] = this.validateOptionalEntityDate(v[df]);
}
}
if (booleanFields) {
for (const df of booleanFields) {
if (update[df] !== undefined)
update[df] = !!update[df] ? 1 : 0;
}
}
for (const key of Object.keys(v)) {
const val = v[key];
if (Array.isArray(val) && (val.length === 0 || typeof val[0] === 'number')) {
v[key] = Buffer.from(val);
}
else if (val === undefined) {
v[key] = null;
}
}
this.isDirty = true;
return v;
}
/**
* Helper to force uniform behavior across database engines.
* Use to process new entities being inserted into the database.
*/
async validateEntityForInsert(entity, trx, dateFields, booleanFields) {
await this.verifyReadyForDatabaseAccess(trx);
const v = { ...entity };
v.created_at = this.validateOptionalEntityDate(v.created_at, true);
v.updated_at = this.validateOptionalEntityDate(v.updated_at, true);
if (!v.created_at)
delete v.created_at;
if (!v.updated_at)
delete v.updated_at;
if (dateFields) {
for (const df of dateFields) {
if (v[df])
v[df] = this.validateOptionalEntityDate(v[df]);
}
}
if (booleanFields) {
for (const df of booleanFields) {
if (entity[df] !== undefined)
entity[df] = !!entity[df] ? 1 : 0;
}
}
for (const key of Object.keys(v)) {
const val = v[key];
if (Array.isArray(val) && (val.length === 0 || typeof val[0] === 'number')) {
v[key] = Buffer.from(val);
}
else if (val === undefined) {
v[key] = null;
}
}
this.isDirty = true;
return v;
}
async getLabelsForTransactionId(transactionId, trx) {
if (transactionId === undefined)
return [];
const labels = await this.toDb(trx)('tx_labels')
.join('tx_labels_map', 'tx_labels_map.txLabelId', 'tx_labels.txLabelId')
.where('tx_labels_map.transactionId', transactionId)
.whereNot('tx_labels_map.isDeleted', true)
.whereNot('tx_labels.isDeleted', true);
return this.validateEntities(labels, undefined, ['isDeleted']);
}
async getTagsForOutputId(outputId, trx) {
const tags = await this.toDb(trx)('output_tags')
.join('output_tags_map', 'output_tags_map.outputTagId', 'output_tags.outputTagId')
.where('output_tags_map.outputId', outputId)
.whereNot('output_tags_map.isDeleted', true)
.whereNot('output_tags.isDeleted', true);
return this.validateEntities(tags, undefined, ['isDeleted']);
}
async purgeData(params, trx) {
return await (0, purgeData_1.purgeData)(this, params, trx);
}
async reviewStatus(args) {
return await (0, reviewStatus_1.reviewStatus)(this, args);
}
/**
* Counts the outputs for userId in basketId that are spendable: true
* AND whose transaction status is one of:
* - completed
* - unproven
* - sending (if excludeSending is false)
*/
async countChangeInputs(userId, basketId, excludeSending) {
const status = ['completed', 'unproven'];
if (!excludeSending)
status.push('sending');
const statusText = status.map(s => `'${s}'`).join(',');
const txStatusCondition = `(SELECT status FROM transactions WHERE outputs.transactionId = transactions.transactionId) in (${statusText})`;
let q = this.knex('outputs').where({ userId, spendable: true, basketId }).whereRaw(txStatusCondition);
const count = await this.getCount(q);
return count;
}
/**
* Finds closest matching available change output to use as input for new transaction.
*
* Transactionally allocate the output such that
*/
async allocateChangeInput(userId, basketId, targetSatoshis, exactSatoshis, excludeSending, transactionId) {
const status = ['completed', 'unproven'];
if (!excludeSending)
status.push('sending');
const statusText = status.map(s => `'${s}'`).join(',');
const r = await this.knex.transaction(async (trx) => {
const txStatusCondition = `AND (SELECT status FROM transactions WHERE outputs.transactionId = transactions.transactionId) in (${statusText})`;
let outputId;
const setOutputId = async (rawQuery) => {
let oidr = await trx.raw(rawQuery);
outputId = undefined;
if (!oidr['outputId'] && oidr.length > 0)
oidr = oidr[0];
if (!oidr['outputId'] && oidr.length > 0)
oidr = oidr[0];
if (oidr['outputId'])
outputId = Number(oidr['outputId']);
};
if (exactSatoshis !== undefined) {
// Find outputId of output that with exactSatoshis
await setOutputId(`
SELECT outputId
FROM outputs
WHERE userId = ${userId}
AND spendable = 1
AND basketId = ${basketId}
${txStatusCondition}
AND satoshis = ${exactSatoshis}
LIMIT 1;
`);
}
if (outputId === undefined) {
// Find outputId of output that would at least fund targetSatoshis
await setOutputId(`
SELECT outputId
FROM outputs
WHERE userId = ${userId}
AND spendable = 1
AND basketId = ${basketId}
${txStatusCondition}
AND satoshis - ${targetSatoshis} = (
SELECT MIN(satoshis - ${targetSatoshis})
FROM outputs
WHERE userId = ${userId}
AND spendable = 1
AND basketId = ${basketId}
${txStatusCondition}
AND satoshis - ${targetSatoshis} >= 0
)
LIMIT 1;
`);
}
if (outputId === undefined) {
// Find outputId of output that would add the most fund targetSatoshis
await setOutputId(`
SELECT outputId
FROM outputs
WHERE userId = ${userId}
AND spendable = 1
AND basketId = ${basketId}
${txStatusCondition}
AND satoshis - ${targetSatoshis} = (
SELECT MAX(satoshis - ${targetSatoshis})
FROM outputs
WHERE userId = ${userId}
AND spendable = 1
AND basketId = ${basketId}
${txStatusCondition}
AND satoshis - ${targetSatoshis} < 0
)
LIMIT 1;
`);
}
if (outputId === undefined)
return undefined;
await this.updateOutput(outputId, {
spendable: false,
spentBy: transactionId
}, trx);
const r = (0, utilityHelpers_1.verifyTruthy)(await this.findOutputById(outputId, trx));
return r;
});
return r;
}
/**
* Helper to force uniform behavior across database engines.
* Use to process all individual records with time stamps retreived from database.
*/
validateEntity(entity, dateFields, booleanFields) {
entity.created_at = this.validateDate(entity.created_at);
entity.updated_at = this.validateDate(entity.updated_at);
if (dateFields) {
for (const df of dateFields) {
if (entity[df])
entity[df] = this.validateDate(entity[df]);
}
}
if (booleanFields) {
for (const df of booleanFields) {
if (entity[df] !== undefined)
entity[df] = !!entity[df];
}
}
for (const key of Object.keys(entity)) {
const val = entity[key];
if (val === null) {
entity[key] = undefined;
}
else if (Buffer.isBuffer(val)) {
entity[key] = Array.from(val);
}
}
return entity;
}
/**
* Helper to force uniform behavior across database engines.
* Use to process all arrays of records with time stamps retreived from database.
* @returns input `entities` array with contained values validated.
*/
validateEntities(entities, dateFields, booleanFields) {
for (let i = 0; i < entities.length; i++) {
entities[i] = this.validateEntity(entities[i], dateFields, booleanFields);
}
return entities;
}
async adminStats(adminIdentityKey) {
if (this.dbtype !== 'MySQL')
throw new WERR_errors_1.WERR_NOT_IMPLEMENTED('adminStats, only MySQL is supported');
const monitorEvent = (0, utilityHelpers_1.verifyOneOrNone)(await this.findMonitorEvents({
partial: { event: 'MonitorCallHistory' },
orderDescending: true,
paged: { limit: 1 }
}));
const monitorStats = monitorEvent ? JSON.parse(monitorEvent.details) : undefined;
const servicesStats = this.getServices().getServicesCallHistory(true);
await this.insertMonitorEvent({
event: 'ServicesCallHistory',
details: JSON.stringify(servicesStats),
created_at: new Date(),
updated_at: new Date(),
id: 0
});
const one_day_ago = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
const one_week_ago = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
const one_month_ago = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
const [[{ usersDay, usersMonth, usersWeek, usersTotal, transactionsDay, transactionsMonth, transactionsWeek, transactionsTotal, txCompletedDay, txCompletedMonth, txCompletedWeek, txCompletedTotal, txFailedDay, txFailedMonth, txFailedWeek, txFailedTotal, txUnprocessedDay, txUnprocessedMonth, txUnprocessedWeek, txUnprocessedTotal, txSendingDay, txSendingMonth, txSendingWeek, txSendingTotal, txUnprovenDay, txUnprovenMonth, txUnprovenWeek, txUnprovenTotal, txUnsignedDay, txUnsignedMonth, txUnsignedWeek, txUnsignedTotal, txNosendDay, txNosendMonth, txNosendWeek, txNosendTotal, txNonfinalDay, txNonfinalMonth, txNonfinalWeek, txNonfinalTotal, txUnfailDay, txUnfailMonth, txUnfailWeek, txUnfailTotal, satoshisDefaultDay, satoshisDefaultMonth, satoshisDefaultWeek, satoshisDefaultTotal, satoshisOtherDay, satoshisOtherMonth, satoshisOtherWeek, satoshisOtherTotal, basketsDay, basketsMonth, basketsWeek, basketsTotal, labelsDay, labelsMonth, labelsWeek, labelsTotal, tagsDay, tagsMonth, tagsWeek, tagsTotal }]] = await this.knex.raw(`
select
(select count(*) from users where created_at > '${one_day_ago}') as usersDay,
(select count(*) from users where created_at > '${one_week_ago}') as usersWeek,
(select count(*) from users where created_at > '${one_month_ago}') as usersMonth,
(select count(*) from users) as usersTotal,
(select count(*) from transactions where created_at > '${one_day_ago}') as transactionsDay,
(select count(*) from transactions where created_at > '${one_week_ago}') as transactionsWeek,
(select count(*) from transactions where created_at > '${one_month_ago}') as transactionsMonth,
(select count(*) from transactions) as transactionsTotal,
(select count(*) from transactions where status = 'completed' and created_at > '${one_day_ago}') as txCompletedDay,
(select count(*) from transactions where status = 'completed' and created_at > '${one_week_ago}') as txCompletedWeek,
(select count(*) from transactions where status = 'completed' and created_at > '${one_month_ago}') as txCompletedMonth,
(select count(*) from transactions where status = 'completed') as txCompletedTotal,
(select count(*) from transactions where status = 'failed' and created_at > '${one_day_ago}') as txFailedDay,
(select count(*) from transactions where status = 'failed' and created_at > '${one_week_ago}') as txFailedWeek,
(select count(*) from transactions where status = 'failed' and created_at > '${one_month_ago}') as txFailedMonth,
(select count(*) from transactions where status = 'failed') as txFailedTotal,
(select count(*) from transactions where status = 'unprocessed' and created_at > '${one_day_ago}') as txUnprocessedDay,
(select count(*) from transactions where status = 'unprocessed' and created_at > '${one_week_ago}') as txUnprocessedWeek,
(select count(*) from transactions where status = 'unprocessed' and created_at > '${one_month_ago}') as txUnprocessedMonth,
(select count(*) from transactions where status = 'unprocessed') as txUnprocessedTotal,
(select count(*) from transactions where status = 'sending' and created_at > '${one_day_ago}') as txSendingDay,
(select count(*) from transactions where status = 'sending' and created_at > '${one_week_ago}') as txSendingWeek,
(select count(*) from transactions where status = 'sending' and created_at > '${one_month_ago}') as txSendingMonth,
(select count(*) from transactions where status = 'sending') as txSendingTotal,
(select count(*) from transactions where status = 'unproven' and created_at > '${one_day_ago}') as txUnprovenDay,
(select count(*) from transactions where status = 'unproven' and created_at > '${one_week_ago}') as txUnprovenWeek,
(select count(*) from transactions where status = 'unproven' and created_at > '${one_month_ago}') as txUnprovenMonth,
(select count(*) from transactions where status = 'unproven') as txUnprovenTotal,
(select count(*) from transactions where status = 'unsigned' and created_at > '${one_day_ago}') as txUnsignedDay,
(select count(*) from transactions where status = 'unsigned' and created_at > '${one_week_ago}') as txUnsignedWeek,
(select count(*) from transactions where status = 'unsigned' and created_at > '${one_month_ago}') as txUnsignedMonth,
(select count(*) from transactions where status = 'unsigned') as txUnsignedTotal,
(select count(*) from transactions where status = 'nosend' and created_at > '${one_day_ago}') as txNosendDay,
(select count(*) from transactions where status = 'nosend' and created_at > '${one_week_ago}') as txNosendWeek,
(select count(*) from transactions where status = 'nosend' and created_at > '${one_month_ago}') as txNosendMonth,
(select count(*) from transactions where status = 'nosend') as txNosendTotal,
(select count(*) from transactions where status = 'nonfinal' and created_at > '${one_day_ago}') as txNonfinalDay,
(select count(*) from transactions where status = 'nonfinal' and created_at > '${one_week_ago}') as txNonfinalWeek,
(select count(*) from transactions where status = 'nonfinal' and created_at > '${one_month_ago}') as txNonfinalMonth,
(select count(*) from transactions where status = 'nonfinal') as txNonfinalTotal,
(select count(*) from transactions where status = 'unfail' and created_at > '${one_day_ago}') as txUnfailDay,
(select count(*) from transactions where status = 'unfail' and created_at > '${one_week_ago}') as txUnfailWeek,
(select count(*) from transactions where status = 'unfail' and created_at > '${one_month_ago}') as txUnfailMonth,
(select count(*) from transactions where status = 'unfail') as txUnfailTotal,
(select sum(satoshis) from outputs where spendable = 1 and \`change\` = 1 and created_at > '${one_day_ago}') as satoshisDefaultDay,
(select sum(satoshis) from outputs where spendable = 1 and \`change\` = 1 and created_at > '${one_week_ago}') as satoshisDefaultWeek,
(select sum(satoshis) from outputs where spendable = 1 and \`change\` = 1 and created_at > '${one_month_ago}') as satoshisDefaultMonth,
(select sum(satoshis) from outputs where spendable = 1 and \`change\` = 1) as satoshisDefaultTotal,
(sel