@bsv/wallet-toolbox-client
Version:
Client only Wallet Storage
1,184 lines • 86.6 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeDate = exports.triggerForeignKeyConstraintError = exports.triggerUniqueConstraintError = exports.logUniqueConstraintError = exports.setLogging = exports.validateUpdateTime = exports.verifyValues = exports.updateTable = exports.logger = exports._tu = exports.TestUtilsWalletStorage = void 0;
exports.expectToThrowWERR = expectToThrowWERR;
exports.cleanUnsentTransactionsUsingAbort = cleanUnsentTransactionsUsingAbort;
exports.cleanUnsignedTransactionsUsingAbort = cleanUnsignedTransactionsUsingAbort;
exports.cleanUnprocessedTransactionsUsingAbort = cleanUnprocessedTransactionsUsingAbort;
exports.logTransaction = logTransaction;
exports.logOutput = logOutput;
exports.logInput = logInput;
exports.logBasket = logBasket;
const dotenv = __importStar(require("dotenv"));
const path_1 = __importDefault(require("path"));
const fs_1 = require("fs");
const knex_1 = require("knex");
const sdk_1 = require("@bsv/sdk");
const StorageIdb_1 = require("../../src/storage/StorageIdb");
const Setup_1 = require("../../src/Setup");
const StorageKnex_1 = require("../../src/storage/StorageKnex");
const Services_1 = require("../../src/services/Services");
const WERR_errors_1 = require("../../src/sdk/WERR_errors");
const WalletStorageManager_1 = require("../../src/storage/WalletStorageManager");
const Monitor_1 = require("../../src/monitor/Monitor");
const PrivilegedKeyManager_1 = require("../../src/sdk/PrivilegedKeyManager");
const Wallet_1 = require("../../src/Wallet");
const StorageClient_1 = require("../../src/storage/remoting/StorageClient");
const utilityHelpers_1 = require("../../src/utility/utilityHelpers");
const WalletError_1 = require("../../src/sdk/WalletError");
const StorageSyncReader_1 = require("../../src/storage/StorageSyncReader");
const utilityHelpers_noBuffer_1 = require("../../src/utility/utilityHelpers.noBuffer");
const ScriptTemplateBRC29_1 = require("../../src/utility/ScriptTemplateBRC29");
dotenv.config();
const localMySqlConnection = process.env.MYSQL_CONNECTION || '';
class TestUtilsWalletStorage {
/**
* @param chain
* @returns true if .env has truthy idenityKey, idenityKey2 values for chain
*/
static noEnv(chain) {
try {
Setup_1.Setup.getEnv(chain);
return false;
}
catch (_a) {
return true;
}
}
/**
* @param chain
* @returns true if .env is not valid for chain or testIdentityKey or testFilePath are undefined or empty.
*/
static noTestEnv(chain) {
try {
const env = _tu.getEnv(chain);
return !env.testIdentityKey || !env.testFilePath;
}
catch (_a) {
return true;
}
}
static getEnvFlags(chain) {
const logTests = !!process.env.LOGTESTS;
const runMySQL = !!process.env.RUNMYSQL;
const runSlowTests = !!process.env.RUNSLOWTESTS;
return {
chain,
runMySQL,
runSlowTests,
logTests
};
}
static getEnv(chain) {
const flagsEnv = _tu.getEnvFlags(chain);
// Identity keys of the lead maintainer of this repo...
const identityKey = (chain === 'main' ? process.env.MY_MAIN_IDENTITY : process.env.MY_TEST_IDENTITY) || '';
const filePath = chain === 'main' ? process.env.MY_MAIN_FILEPATH : process.env.MY_TEST_FILEPATH;
const identityKey2 = (chain === 'main' ? process.env.MY_MAIN_IDENTITY2 : process.env.MY_TEST_IDENTITY2) || '';
const testIdentityKey = chain === 'main' ? process.env.TEST_MAIN_IDENTITY : process.env.TEST_TEST_IDENTITY;
const testFilePath = chain === 'main' ? process.env.TEST_MAIN_FILEPATH : process.env.TEST_TEST_FILEPATH;
const cloudMySQLConnection = chain === 'main' ? process.env.MAIN_CLOUD_MYSQL_CONNECTION : process.env.TEST_CLOUD_MYSQL_CONNECTION;
const DEV_KEYS = process.env.DEV_KEYS || '{}';
const taalApiKey = (chain === 'main' ? process.env.MAIN_TAAL_API_KEY : process.env.TEST_TAAL_API_KEY) || '';
const bitailsApiKey = (chain === 'main' ? process.env.MAIN_BITAILS_API_KEY : process.env.TEST_BITAILS_API_KEY) || '';
const whatsonchainApiKey = (chain === 'main' ? process.env.MAIN_WHATSONCHAIN_API_KEY : process.env.TEST_WHATSONCHAIN_API_KEY) || '';
return {
...flagsEnv,
identityKey,
identityKey2,
taalApiKey,
bitailsApiKey,
whatsonchainApiKey,
devKeys: JSON.parse(DEV_KEYS),
filePath,
testIdentityKey,
testFilePath,
cloudMySQLConnection
};
}
static async createMainReviewSetup() {
const env = _tu.getEnv('main');
if (!env.cloudMySQLConnection)
throw new WERR_errors_1.WERR_INVALID_PARAMETER('env.cloudMySQLConnection', 'valid');
const knex = Setup_1.Setup.createMySQLKnex(env.cloudMySQLConnection);
const storage = new StorageKnex_1.StorageKnex({
chain: env.chain,
knex: knex,
commissionSatoshis: 0,
commissionPubKeyHex: undefined,
feeModel: { model: 'sat/kb', value: 1 }
});
const servicesOptions = Services_1.Services.createDefaultOptions(env.chain);
if (env.whatsonchainApiKey)
servicesOptions.whatsOnChainApiKey = env.whatsonchainApiKey;
const services = new Services_1.Services(servicesOptions);
storage.setServices(services);
await storage.makeAvailable();
return { env, storage, services };
}
static async createNoSendP2PKHTestOutpoint(address, satoshis, noSendChange, wallet) {
return await _tu.createNoSendP2PKHTestOutpoints(1, address, satoshis, noSendChange, wallet);
}
static async createNoSendP2PKHTestOutpoints(count, address, satoshis, noSendChange, wallet) {
const outputs = [];
for (let i = 0; i < count; i++) {
outputs.push({
basket: `test-p2pkh-output-${i}`,
satoshis,
lockingScript: _tu.getLockP2PKH(address).toHex(),
outputDescription: `p2pkh ${i}`
});
}
const createArgs = {
description: `to ${address}`,
outputs,
options: {
noSendChange,
randomizeOutputs: false,
signAndProcess: false,
noSend: true
}
};
const cr = await wallet.createAction(createArgs);
noSendChange = cr.noSendChange;
expect(cr.noSendChange).toBeTruthy();
expect(cr.sendWithResults).toBeUndefined();
expect(cr.tx).toBeUndefined();
expect(cr.txid).toBeUndefined();
expect(cr.signableTransaction).toBeTruthy();
const st = cr.signableTransaction;
expect(st.reference).toBeTruthy();
// const tx = Transaction.fromAtomicBEEF(st.tx) // Transaction doesn't support V2 Beef yet.
const atomicBeef = sdk_1.Beef.fromBinary(st.tx);
const tx = atomicBeef.txs[atomicBeef.txs.length - 1].tx;
for (const input of tx.inputs) {
expect(atomicBeef.findTxid(input.sourceTXID)).toBeTruthy();
}
// Spending authorization check happens here...
//expect(st.amount > 242 && st.amount < 300).toBe(true)
// sign and complete
const signArgs = {
reference: st.reference,
spends: {},
options: {
returnTXIDOnly: true,
noSend: true
}
};
const sr = await wallet.signAction(signArgs);
let txid = sr.txid;
// Update the noSendChange txid to final signed value.
noSendChange = noSendChange.map(op => `${txid}.${op.split('.')[1]}`);
return { noSendChange, txid, cr, sr };
}
static getKeyPair(priv) {
if (priv === undefined)
priv = sdk_1.PrivateKey.fromRandom();
else if (typeof priv === 'string')
priv = new sdk_1.PrivateKey(priv, 'hex');
const pub = sdk_1.PublicKey.fromPrivateKey(priv);
const address = pub.toAddress();
return { privateKey: priv, publicKey: pub, address };
}
static getLockP2PKH(address) {
const p2pkh = new sdk_1.P2PKH();
const lock = p2pkh.lock(address);
return lock;
}
static getUnlockP2PKH(priv, satoshis) {
const p2pkh = new sdk_1.P2PKH();
const lock = _tu.getLockP2PKH(_tu.getKeyPair(priv).address);
// Prepare to pay with SIGHASH_ALL and without ANYONE_CAN_PAY.
// In otherwords:
// - all outputs must remain in the current order, amount and locking scripts.
// - all inputs must remain from the current outpoints and sequence numbers.
// (unlock scripts are never signed)
const unlock = p2pkh.unlock(priv, 'all', false, satoshis, lock);
return unlock;
}
static async createWalletOnly(args) {
args.chain || (args.chain = 'test');
args.rootKeyHex || (args.rootKeyHex = '1'.repeat(64));
const rootKey = sdk_1.PrivateKey.fromHex(args.rootKeyHex);
const identityKey = rootKey.toPublicKey().toString();
const keyDeriver = new sdk_1.CachedKeyDeriver(rootKey);
const chain = args.chain;
const storage = new WalletStorageManager_1.WalletStorageManager(identityKey, args.active, args.backups);
if (storage.canMakeAvailable())
await storage.makeAvailable();
const env = _tu.getEnv(args.chain);
const serviceOptions = Services_1.Services.createDefaultOptions(env.chain);
serviceOptions.taalApiKey = env.taalApiKey;
serviceOptions.arcConfig.apiKey = env.taalApiKey;
serviceOptions.bitailsApiKey = env.bitailsApiKey;
serviceOptions.whatsOnChainApiKey = env.whatsonchainApiKey;
const services = new Services_1.Services(serviceOptions);
const monopts = Monitor_1.Monitor.createDefaultWalletMonitorOptions(chain, storage, services);
const monitor = new Monitor_1.Monitor(monopts);
monitor.addDefaultTasks();
let privilegedKeyManager = undefined;
if (args.privKeyHex) {
const privKey = sdk_1.PrivateKey.fromString(args.privKeyHex);
privilegedKeyManager = new PrivilegedKeyManager_1.PrivilegedKeyManager(async () => privKey);
}
const wallet = new Wallet_1.Wallet({
chain,
keyDeriver,
storage,
services,
monitor,
privilegedKeyManager
});
const r = {
rootKey,
identityKey,
keyDeriver,
chain,
storage,
services,
monitor,
wallet
};
return r;
}
/**
* Creates a wallet with both local sqlite and cloud stores, the local store is left active.
*
* Requires a valid .env file with chain matching testIdentityKey and testFilePath properties valid.
* Or `args` with those properties.
*
* Verifies wallet has at least 1000 satoshis in at least 10 change utxos.
*
* @param chain
*
* @returns {TestWalletNoSetup}
*/
static async createTestWallet(args) {
let chain;
let rootKeyHex;
let filePath;
let addLocalBackup = false;
let setActiveClient = false;
let useMySQLConnectionForClient = false;
if (typeof args === 'string') {
chain = args;
const env = _tu.getEnv(chain);
if (!env.testIdentityKey || !env.testFilePath) {
throw new WERR_errors_1.WERR_INVALID_PARAMETER('env.testIdentityKey and env.testFilePath', 'valid');
}
rootKeyHex = env.devKeys[env.testIdentityKey];
filePath = env.testFilePath;
}
else {
chain = args.chain;
rootKeyHex = args.rootKeyHex;
filePath = args.filePath;
addLocalBackup = args.addLocalBackup === true;
setActiveClient = args.setActiveClient === true;
useMySQLConnectionForClient = args.useMySQLConnectionForClient === true;
}
const databaseName = path_1.default.parse(filePath).name;
const setup = await _tu.createSQLiteTestWallet({
filePath,
rootKeyHex,
databaseName,
chain
});
setup.localStorageIdentityKey = setup.storage.getActiveStore();
let client;
if (useMySQLConnectionForClient) {
const env = _tu.getEnv(chain);
if (!env.cloudMySQLConnection)
throw new WERR_errors_1.WERR_INVALID_PARAMETER('env.cloundMySQLConnection', 'valid');
const connection = JSON.parse(env.cloudMySQLConnection);
client = new StorageKnex_1.StorageKnex({
...StorageKnex_1.StorageKnex.defaultOptions(),
knex: _tu.createMySQLFromConnection(connection),
chain: env.chain
});
}
else {
const endpointUrl = chain === 'main' ? 'https://storage.babbage.systems' : 'https://staging-storage.babbage.systems';
client = new StorageClient_1.StorageClient(setup.wallet, endpointUrl);
}
setup.clientStorageIdentityKey = (await client.makeAvailable()).storageIdentityKey;
await setup.wallet.storage.addWalletStorageProvider(client);
if (addLocalBackup) {
const backupName = `${databaseName}_backup`;
const backupPath = filePath.replace(databaseName, backupName);
const localBackup = new StorageKnex_1.StorageKnex({
...StorageKnex_1.StorageKnex.defaultOptions(),
knex: _tu.createLocalSQLite(backupPath),
chain
});
await localBackup.migrate(backupName, (0, utilityHelpers_1.randomBytesHex)(33));
setup.localBackupStorageIdentityKey = (await localBackup.makeAvailable()).storageIdentityKey;
await setup.wallet.storage.addWalletStorageProvider(localBackup);
}
// SETTING ACTIVE
// SETTING ACTIVE
// SETTING ACTIVE
const log = await setup.storage.setActive(setActiveClient ? setup.clientStorageIdentityKey : setup.localStorageIdentityKey);
(0, exports.logger)(log);
let needsBackup = false;
if (setup.storage.getActiveStore() === setup.localStorageIdentityKey) {
const basket = (0, utilityHelpers_1.verifyOne)(await setup.activeStorage.findOutputBaskets({
partial: {
userId: setup.storage.getActiveUser().userId,
name: 'default'
}
}));
if (basket.minimumDesiredUTXOValue !== 5 || basket.numberOfDesiredUTXOs < 32) {
needsBackup = true;
await setup.activeStorage.updateOutputBasket(basket.basketId, {
minimumDesiredUTXOValue: 5,
numberOfDesiredUTXOs: 32
});
}
}
const balance = await setup.wallet.balanceAndUtxos();
if (balance.total < 1000) {
throw new WERR_errors_1.WERR_INSUFFICIENT_FUNDS(1000, 1000 - balance.total);
}
if (balance.utxos.length <= 10) {
const args = {
description: 'spread change'
};
await setup.wallet.createAction(args);
needsBackup = true;
}
if (needsBackup) {
const log2 = await setup.storage.updateBackups();
console.log(log2);
}
return setup;
}
static async createTestWalletWithStorageClient(args) {
args.chain || (args.chain = 'test');
const wo = await _tu.createWalletOnly({
chain: args.chain,
rootKeyHex: args.rootKeyHex
});
args.endpointUrl || (args.endpointUrl = args.chain === 'main' ? 'https://storage.babbage.systems' : 'https://staging-storage.babbage.systems');
const client = new StorageClient_1.StorageClient(wo.wallet, args.endpointUrl);
await wo.storage.addWalletStorageProvider(client);
return wo;
}
static async createKnexTestWalletWithSetup(args) {
const wo = await _tu.createWalletOnly({
chain: args.chain,
rootKeyHex: args.rootKeyHex,
privKeyHex: args.privKeyHex
});
const activeStorage = new StorageKnex_1.StorageKnex({
chain: wo.chain,
knex: args.knex,
commissionSatoshis: 0,
commissionPubKeyHex: undefined,
feeModel: { model: 'sat/kb', value: 1 }
});
if (args.dropAll)
await activeStorage.dropAllData();
await activeStorage.migrate(args.databaseName, (0, utilityHelpers_1.randomBytesHex)(33));
await activeStorage.makeAvailable();
const setup = await args.insertSetup(activeStorage, wo.identityKey);
await wo.storage.addWalletStorageProvider(activeStorage);
const { user, isNew } = await activeStorage.findOrInsertUser(wo.identityKey);
const userId = user.userId;
const r = {
...wo,
activeStorage,
setup,
userId
};
return r;
}
/**
* Returns path to temporary file in project's './test/data/tmp/' folder.
*
* Creates any missing folders.
*
* Optionally tries to delete any existing file. This may fail if the file file is locked
* by another process.
*
* Optionally copies filename (or if filename has no dir component, a file of the same filename from the project's './test/data' folder) to initialize file's contents.
*
* CAUTION: returned file path will include four random hex digits unless tryToDelete is true. Files must be purged periodically.
*
* @param filename target filename without path, optionally just extension in which case random name is used
* @param tryToDelete true to attempt to delete an existing file at the returned file path.
* @param copyToTmp true to copy file of same filename from './test/data' (or elsewhere if filename has path) to tmp folder
* @param reuseExisting true to use existing file if found, otherwise a random string is added to filename.
* @returns path in './test/data/tmp' folder.
*/
static async newTmpFile(filename = '', tryToDelete = false, copyToTmp = false, reuseExisting = false) {
const tmpFolder = './test/data/tmp/';
const p = path_1.default.parse(filename);
const dstDir = tmpFolder;
const dstName = `${p.name}${tryToDelete || reuseExisting ? '' : (0, utilityHelpers_1.randomBytesHex)(6)}`;
const dstExt = p.ext || 'tmp';
const dstPath = path_1.default.resolve(`${dstDir}${dstName}${dstExt}`);
await fs_1.promises.mkdir(tmpFolder, { recursive: true });
if (!reuseExisting && (tryToDelete || copyToTmp))
try {
await fs_1.promises.unlink(dstPath);
}
catch (eu) {
const e = WalletError_1.WalletError.fromUnknown(eu);
if (e.name !== 'ENOENT') {
throw e;
}
}
if (copyToTmp) {
const srcPath = p.dir ? path_1.default.resolve(filename) : path_1.default.resolve(`./test/data/${filename}`);
await fs_1.promises.copyFile(srcPath, dstPath);
}
return dstPath;
}
static async copyFile(srcPath, dstPath) {
await fs_1.promises.copyFile(srcPath, dstPath);
}
static async existingDataFile(filename) {
const folder = './test/data/';
return folder + filename;
}
static createLocalSQLite(filename) {
const config = {
client: 'sqlite3',
connection: { filename },
useNullAsDefault: true
};
const knex = (0, knex_1.knex)(config);
return knex;
}
static createMySQLFromConnection(connection) {
const config = {
client: 'mysql2',
connection,
useNullAsDefault: true,
pool: { min: 0, max: 7, idleTimeoutMillis: 15000 }
};
const knex = (0, knex_1.knex)(config);
return knex;
}
static createLocalMySQL(database) {
const connection = JSON.parse(localMySqlConnection || '{}');
connection['database'] = database;
const config = {
client: 'mysql2',
connection,
useNullAsDefault: true,
pool: { min: 0, max: 7, idleTimeoutMillis: 15000 }
};
const knex = (0, knex_1.knex)(config);
return knex;
}
static async createMySQLTestWallet(args) {
return await this.createKnexTestWallet({
...args,
knex: _tu.createLocalMySQL(args.databaseName)
});
}
static async createMySQLTestSetup1Wallet(args) {
return await this.createKnexTestSetup1Wallet({
...args,
dropAll: true,
knex: _tu.createLocalMySQL(args.databaseName)
});
}
static async createMySQLTestSetup2Wallet(args) {
return await this.createKnexTestSetup2Wallet({
...args,
dropAll: true,
knex: _tu.createLocalMySQL(args.databaseName)
});
}
static async createSQLiteTestWallet(args) {
const localSQLiteFile = args.filePath || (await _tu.newTmpFile(`${args.databaseName}.sqlite`, false, false, true));
return await this.createKnexTestWallet({
...args,
knex: _tu.createLocalSQLite(localSQLiteFile)
});
}
static async createSQLiteTestSetup1Wallet(args) {
const localSQLiteFile = await _tu.newTmpFile(`${args.databaseName}.sqlite`, false, false, true);
return await this.createKnexTestSetup1Wallet({
...args,
dropAll: true,
knex: _tu.createLocalSQLite(localSQLiteFile)
});
}
static async createSQLiteTestSetup2Wallet(args) {
const localSQLiteFile = await _tu.newTmpFile(`${args.databaseName}.sqlite`, false, false, true);
return await this.createKnexTestSetup2Wallet({
...args,
dropAll: true,
knex: _tu.createLocalSQLite(localSQLiteFile)
});
}
static async createKnexTestWallet(args) {
return await _tu.createKnexTestWalletWithSetup({
...args,
insertSetup: insertEmptySetup
});
}
static async createKnexTestSetup1Wallet(args) {
return await _tu.createKnexTestWalletWithSetup({
...args,
insertSetup: _tu.createTestSetup1
});
}
static async createKnexTestSetup2Wallet(args) {
return await _tu.createKnexTestWalletWithSetup({
...args,
insertSetup: async (storage, identityKey) => {
return _tu.createTestSetup2(storage, identityKey, args.mockData);
}
});
}
static async fileExists(file) {
try {
const f = await fs_1.promises.open(file, 'r');
await f.close();
return true;
}
catch (eu) {
return false;
}
}
//if (await _tu.fileExists(walletFile))
static async createLegacyWalletSQLiteCopy(databaseName) {
const walletFile = await _tu.newTmpFile(`${databaseName}.sqlite`, false, false, true);
const walletKnex = _tu.createLocalSQLite(walletFile);
return await _tu.createLegacyWalletCopy(databaseName, walletKnex, walletFile);
}
static async createLegacyWalletMySQLCopy(databaseName) {
const walletKnex = _tu.createLocalMySQL(databaseName);
return await _tu.createLegacyWalletCopy(databaseName, walletKnex);
}
static async createLiveWalletSQLiteWARNING(databaseFullPath = './test/data/walletLiveTestData.sqlite') {
return await this.createKnexTestWallet({
chain: 'test',
rootKeyHex: _tu.legacyRootKeyHex,
databaseName: 'walletLiveTestData',
knex: _tu.createLocalSQLite(databaseFullPath)
});
}
static async createWalletSQLite(databaseFullPath = './test/data/tmp/walletNewTestData.sqlite', databaseName = 'walletNewTestData') {
return await this.createSQLiteTestWallet({
filePath: databaseFullPath,
databaseName,
chain: 'test',
rootKeyHex: '1'.repeat(64),
dropAll: true
});
}
static async createLegacyWalletCopy(databaseName, walletKnex, tryCopyToPath) {
const readerFile = await _tu.existingDataFile(`walletLegacyTestData.sqlite`);
let useReader = true;
if (tryCopyToPath) {
await _tu.copyFile(readerFile, tryCopyToPath);
//console.log('USING FILE COPY INSTEAD OF SOURCE DB SYNC')
useReader = false;
}
const chain = 'test';
const rootKeyHex = _tu.legacyRootKeyHex;
const identityKey = '03ac2d10bdb0023f4145cc2eba2fcd2ad3070cb2107b0b48170c46a9440e4cc3fe';
const rootKey = sdk_1.PrivateKey.fromHex(rootKeyHex);
const keyDeriver = new sdk_1.CachedKeyDeriver(rootKey);
const activeStorage = new StorageKnex_1.StorageKnex({
chain,
knex: walletKnex,
commissionSatoshis: 0,
commissionPubKeyHex: undefined,
feeModel: { model: 'sat/kb', value: 1 }
});
if (useReader)
await activeStorage.dropAllData();
await activeStorage.migrate(databaseName, (0, utilityHelpers_1.randomBytesHex)(33));
await activeStorage.makeAvailable();
const storage = new WalletStorageManager_1.WalletStorageManager(identityKey, activeStorage);
await storage.makeAvailable();
if (useReader) {
const readerKnex = _tu.createLocalSQLite(readerFile);
const reader = new StorageKnex_1.StorageKnex({
chain,
knex: readerKnex,
commissionSatoshis: 0,
commissionPubKeyHex: undefined,
feeModel: { model: 'sat/kb', value: 1 }
});
await reader.makeAvailable();
await storage.syncFromReader(identityKey, new StorageSyncReader_1.StorageSyncReader({ identityKey }, reader));
await reader.destroy();
}
const services = new Services_1.Services(chain);
const monopts = Monitor_1.Monitor.createDefaultWalletMonitorOptions(chain, storage, services);
const monitor = new Monitor_1.Monitor(monopts);
const wallet = new Wallet_1.Wallet({ chain, keyDeriver, storage, services, monitor });
const userId = (0, utilityHelpers_1.verifyTruthy)(await activeStorage.findUserByIdentityKey(identityKey)).userId;
const r = {
rootKey,
identityKey,
keyDeriver,
chain,
activeStorage,
storage,
setup: {},
services,
monitor,
wallet,
userId
};
return r;
}
static wrapProfiling(o, name) {
const getFunctionsNames = (obj) => {
let fNames = [];
do {
fNames = fNames.concat(Object.getOwnPropertyNames(obj).filter(p => p !== 'constructor' && typeof obj[p] === 'function'));
} while ((obj = Object.getPrototypeOf(obj)) && obj !== Object.prototype);
return fNames;
};
const notifyPerformance = (fn, performanceDetails) => {
setTimeout(() => {
let { functionName, args, startTime, endTime } = performanceDetails;
let _args = args;
if (Array.isArray(args)) {
_args = args.map(arg => {
if (typeof arg === 'function') {
let fName = arg.name;
if (!fName) {
fName = 'function';
}
else if (fName === 'callbackWrapper') {
fName = 'callback';
}
arg = `[${fName} Function]`;
}
return arg;
});
}
fn({ functionName, args: _args, startTime, endTime });
}, 0);
};
const stats = {};
function logger(args) {
let s = stats[args.functionName];
if (!s) {
s = { count: 0, totalMsecs: 0 };
stats[args.functionName] = s;
}
s.count++;
s.totalMsecs += args.endTime - args.startTime;
}
const performanceWrapper = (obj, objectName, performanceNotificationCallback) => {
let _notifyPerformance = notifyPerformance.bind(null, performanceNotificationCallback);
let fNames = getFunctionsNames(obj);
for (let fName of fNames) {
let originalFunction = obj[fName];
let wrapperFunction = (...args) => {
let callbackFnIndex = -1;
let startTime = Date.now();
let _callBack = args.filter((arg, i) => {
let _isFunction = typeof arg === 'function';
if (_isFunction) {
callbackFnIndex = i;
}
return _isFunction;
})[0];
if (_callBack) {
let callbackWrapper = (...callbackArgs) => {
let endTime = Date.now();
_notifyPerformance({ functionName: `${objectName}.${fName}`, args, startTime, endTime });
_callBack.apply(null, callbackArgs);
};
args[callbackFnIndex] = callbackWrapper;
}
let originalReturnObject = originalFunction.apply(obj, args);
let isPromiseType = originalReturnObject &&
typeof originalReturnObject.then === 'function' &&
typeof originalReturnObject.catch === 'function';
if (isPromiseType) {
return originalReturnObject
.then(resolveArgs => {
let endTime = Date.now();
_notifyPerformance({ functionName: `${objectName}.${fName}`, args, startTime, endTime });
return Promise.resolve(resolveArgs);
})
.catch((...rejectArgs) => {
let endTime = Date.now();
_notifyPerformance({ functionName: `${objectName}.${fName}`, args, startTime, endTime });
return Promise.reject(...rejectArgs);
});
}
if (!_callBack && !isPromiseType) {
let endTime = Date.now();
_notifyPerformance({ functionName: `${objectName}.${fName}`, args, startTime, endTime });
}
return originalReturnObject;
};
obj[fName] = wrapperFunction;
}
return obj;
};
const functionNames = getFunctionsNames(o);
performanceWrapper(o, name, logger);
return stats;
}
static async createIdbLegacyWalletCopy(databaseName) {
const chain = 'test';
const readerFile = await _tu.existingDataFile(`walletLegacyTestData.sqlite`);
const readerKnex = _tu.createLocalSQLite(readerFile);
const reader = new StorageKnex_1.StorageKnex({
chain,
knex: readerKnex,
commissionSatoshis: 0,
commissionPubKeyHex: undefined,
feeModel: { model: 'sat/kb', value: 1 }
});
await reader.makeAvailable();
const rootKeyHex = _tu.legacyRootKeyHex;
const identityKey = '03ac2d10bdb0023f4145cc2eba2fcd2ad3070cb2107b0b48170c46a9440e4cc3fe';
const rootKey = sdk_1.PrivateKey.fromHex(rootKeyHex);
const keyDeriver = new sdk_1.CachedKeyDeriver(rootKey);
const activeStorage = new StorageIdb_1.StorageIdb({
chain,
commissionSatoshis: 0,
commissionPubKeyHex: undefined,
feeModel: { model: 'sat/kb', value: 1 }
});
await activeStorage.dropAllData();
await activeStorage.migrate(databaseName, (0, utilityHelpers_1.randomBytesHex)(33));
await activeStorage.makeAvailable();
const storage = new WalletStorageManager_1.WalletStorageManager(identityKey, activeStorage);
await storage.makeAvailable();
await storage.syncFromReader(identityKey, new StorageSyncReader_1.StorageSyncReader({ identityKey }, reader));
await reader.destroy();
const services = new Services_1.Services(chain);
const monopts = Monitor_1.Monitor.createDefaultWalletMonitorOptions(chain, storage, services);
const monitor = new Monitor_1.Monitor(monopts);
const wallet = new Wallet_1.Wallet({ chain, keyDeriver, storage, services, monitor });
const userId = (0, utilityHelpers_1.verifyTruthy)(await activeStorage.findUserByIdentityKey(identityKey)).userId;
const r = {
rootKey,
identityKey,
keyDeriver,
chain,
activeStorage,
storage,
setup: {},
services,
monitor,
wallet,
userId
};
return r;
}
static makeSampleCert(subject) {
subject || (subject = sdk_1.PrivateKey.fromRandom().toPublicKey().toString());
const certifier = sdk_1.PrivateKey.fromRandom();
const verifier = sdk_1.PrivateKey.fromRandom();
const cert = {
type: sdk_1.Utils.toBase64(new Array(32).fill(1)),
serialNumber: sdk_1.Utils.toBase64(new Array(32).fill(2)),
revocationOutpoint: 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef.1',
subject,
certifier: certifier.toPublicKey().toString(),
fields: {
name: 'Alice',
email: 'alice@example.com',
organization: 'Example Corp'
},
signature: ''
};
return { cert, subject, certifier };
}
static async insertTestProvenTx(storage, txid, trx) {
const now = new Date();
const ptx = {
created_at: now,
updated_at: now,
provenTxId: 0,
txid: txid || (0, utilityHelpers_1.randomBytesHex)(32),
height: 1,
index: 0,
merklePath: [1, 2, 3, 4, 5, 6, 7, 8],
rawTx: [4, 5, 6],
blockHash: (0, utilityHelpers_1.randomBytesHex)(32),
merkleRoot: (0, utilityHelpers_1.randomBytesHex)(32)
};
await storage.insertProvenTx(ptx, trx);
return ptx;
}
static async insertTestProvenTxReq(storage, txid, provenTxId, onlyRequired) {
const now = new Date();
const ptxreq = {
// Required:
created_at: now,
updated_at: now,
provenTxReqId: 0,
txid: txid || (0, utilityHelpers_1.randomBytesHex)(32),
status: 'nosend',
attempts: 0,
notified: false,
history: '{}',
notify: '{}',
rawTx: [4, 5, 6],
// Optional:
provenTxId: provenTxId || undefined,
batch: onlyRequired ? undefined : (0, utilityHelpers_1.randomBytesBase64)(10),
inputBEEF: onlyRequired ? undefined : [1, 2, 3]
};
await storage.insertProvenTxReq(ptxreq);
return ptxreq;
}
static async insertTestUser(storage, identityKey) {
const now = new Date();
const e = {
created_at: now,
updated_at: now,
userId: 0,
identityKey: identityKey || (0, utilityHelpers_1.randomBytesHex)(33),
activeStorage: storage.getSettings().storageIdentityKey
};
await storage.insertUser(e);
return e;
}
static async insertTestCertificate(storage, u) {
const now = new Date();
u || (u = await _tu.insertTestUser(storage));
const e = {
created_at: now,
updated_at: now,
certificateId: 0,
userId: u.userId,
type: (0, utilityHelpers_1.randomBytesBase64)(33),
serialNumber: (0, utilityHelpers_1.randomBytesBase64)(33),
certifier: (0, utilityHelpers_1.randomBytesHex)(33),
subject: (0, utilityHelpers_1.randomBytesHex)(33),
verifier: undefined,
revocationOutpoint: `${(0, utilityHelpers_1.randomBytesHex)(32)}.999`,
signature: (0, utilityHelpers_1.randomBytesHex)(50),
isDeleted: false
};
await storage.insertCertificate(e);
return e;
}
static async insertTestCertificateField(storage, c, name, value) {
const now = new Date();
const e = {
created_at: now,
updated_at: now,
certificateId: c.certificateId,
userId: c.userId,
fieldName: name,
fieldValue: value,
masterKey: (0, utilityHelpers_1.randomBytesBase64)(40)
};
await storage.insertCertificateField(e);
return e;
}
static async insertTestOutputBasket(storage, u, partial) {
const now = new Date();
let user;
if (u === undefined) {
user = await _tu.insertTestUser(storage);
}
else if (typeof u === 'number') {
user = (0, utilityHelpers_1.verifyOne)(await storage.findUsers({ partial: { userId: u } }));
}
else {
user = u;
}
const e = {
created_at: now,
updated_at: now,
basketId: 0,
userId: user.userId,
name: (0, utilityHelpers_1.randomBytesHex)(6),
numberOfDesiredUTXOs: 42,
minimumDesiredUTXOValue: 1642,
isDeleted: false,
...(partial || {})
};
await storage.insertOutputBasket(e);
return e;
}
static async insertTestTransaction(storage, u, onlyRequired, partial) {
const now = new Date();
u || (u = await _tu.insertTestUser(storage));
const e = {
// Required:
created_at: now,
updated_at: now,
transactionId: 0,
userId: u.userId,
status: 'nosend',
reference: (0, utilityHelpers_1.randomBytesBase64)(10),
isOutgoing: true,
satoshis: 9999,
description: 'buy me a river',
// Optional:
version: onlyRequired ? undefined : 0,
lockTime: onlyRequired ? undefined : 500000000,
txid: onlyRequired ? undefined : (0, utilityHelpers_1.randomBytesHex)(32),
inputBEEF: onlyRequired ? undefined : new sdk_1.Beef().toBinary(),
rawTx: onlyRequired ? undefined : [1, 2, 3],
...(partial || {})
};
await storage.insertTransaction(e);
return { tx: e, user: u };
}
static async insertTestOutput(storage, t, vout, satoshis, basket, requiredOnly, partial) {
const now = new Date();
const e = {
created_at: now,
updated_at: now,
outputId: 0,
userId: t.userId,
transactionId: t.transactionId,
basketId: basket ? basket.basketId : undefined,
spendable: true,
change: true,
outputDescription: 'not mutch to say',
vout,
satoshis,
providedBy: 'you',
purpose: 'secret',
type: 'custom',
txid: requiredOnly ? undefined : (0, utilityHelpers_1.randomBytesHex)(32),
senderIdentityKey: requiredOnly ? undefined : (0, utilityHelpers_1.randomBytesHex)(32),
derivationPrefix: requiredOnly ? undefined : (0, utilityHelpers_1.randomBytesHex)(16),
derivationSuffix: requiredOnly ? undefined : (0, utilityHelpers_1.randomBytesHex)(16),
spentBy: undefined, // must be a valid transactionId
sequenceNumber: requiredOnly ? undefined : 42,
spendingDescription: requiredOnly ? undefined : (0, utilityHelpers_1.randomBytesHex)(16),
scriptLength: requiredOnly ? undefined : 36,
scriptOffset: requiredOnly ? undefined : 12,
lockingScript: requiredOnly ? undefined : (0, utilityHelpers_noBuffer_1.asArray)((0, utilityHelpers_1.randomBytesHex)(36)),
...(partial || {})
};
await storage.insertOutput(e);
return e;
}
static async insertTestOutputTag(storage, u, partial) {
const now = new Date();
const e = {
created_at: now,
updated_at: now,
outputTagId: 0,
userId: u.userId,
tag: (0, utilityHelpers_1.randomBytesHex)(6),
isDeleted: false,
...(partial || {})
};
await storage.insertOutputTag(e);
return e;
}
static async insertTestOutputTagMap(storage, o, tag) {
const now = new Date();
const e = {
created_at: now,
updated_at: now,
outputTagId: tag.outputTagId,
outputId: o.outputId,
isDeleted: false
};
await storage.insertOutputTagMap(e);
return e;
}
static async insertTestTxLabel(storage, u, partial) {
const now = new Date();
const e = {
created_at: now,
updated_at: now,
txLabelId: 0,
userId: u.userId,
label: (0, utilityHelpers_1.randomBytesHex)(6),
isDeleted: false,
...(partial || {})
};
await storage.insertTxLabel(e);
return e;
}
static async insertTestTxLabelMap(storage, tx, label, partial) {
const now = new Date();
const e = {
created_at: now,
updated_at: now,
txLabelId: label.txLabelId,
transactionId: tx.transactionId,
isDeleted: false,
...(partial || {})
};
await storage.insertTxLabelMap(e);
return e;
}
static async insertTestSyncState(storage, u) {
const now = new Date();
const settings = await storage.getSettings();
const e = {
created_at: now,
updated_at: now,
syncStateId: 0,
userId: u.userId,
storageIdentityKey: settings.storageIdentityKey,
storageName: settings.storageName,
status: 'unknown',
init: false,
refNum: (0, utilityHelpers_1.randomBytesBase64)(10),
syncMap: '{}'
};
await storage.insertSyncState(e);
return e;
}
static async insertTestMonitorEvent(storage) {
const now = new Date();
const e = {
created_at: now,
updated_at: now,
id: 0,
event: 'nothing much happened'
};
await storage.insertMonitorEvent(e);
return e;
}
static async insertTestCommission(storage, t) {
const now = new Date();
const e = {
created_at: now,
updated_at: now,
commissionId: 0,
userId: t.userId,
transactionId: t.transactionId,
satoshis: 200,
keyOffset: (0, utilityHelpers_1.randomBytesBase64)(32),
isRedeemed: false,
lockingScript: [1, 2, 3]
};
await storage.insertCommission(e);
return e;
}
static async createTestSetup1(storage, u1IdentityKey) {
const u1 = await _tu.insertTestUser(storage, u1IdentityKey);
const u1basket1 = await _tu.insertTestOutputBasket(storage, u1);
const u1basket2 = await _tu.insertTestOutputBasket(storage, u1);
const u1label1 = await _tu.insertTestTxLabel(storage, u1);
const u1label2 = await _tu.insertTestTxLabel(storage, u1);
const u1tag1 = await _tu.insertTestOutputTag(storage, u1);
const u1tag2 = await _tu.insertTestOutputTag(storage, u1);
const { tx: u1tx1 } = await _tu.insertTestTransaction(storage, u1);
const u1comm1 = await _tu.insertTestCommission(storage, u1tx1);
const u1tx1label1 = await _tu.insertTestTxLabelMap(storage, u1tx1, u1label1);
const u1tx1label2 = await _tu.insertTestTxLabelMap(storage, u1tx1, u1label2);
const u1tx1o0 = await _tu.insertTestOutput(storage, u1tx1, 0, 101, u1basket1);
const u1o0tag1 = await _tu.insertTestOutputTagMap(storage, u1tx1o0, u1tag1);
const u1o0tag2 = await _tu.insertTestOutputTagMap(storage, u1tx1o0, u1tag2);
const u1tx1o1 = await _tu.insertTestOutput(storage, u1tx1, 1, 111, u1basket2);
const u1o1tag1 = await _tu.insertTestOutputTagMap(storage, u1tx1o1, u1tag1);
const u1cert1 = await _tu.insertTestCertificate(storage, u1);
const u1cert1field1 = await _tu.insertTestCertificateField(storage, u1cert1, 'bob', 'your uncle');
const u1cert1field2 = await _tu.insertTestCertificateField(storage, u1cert1, 'name', 'alice');
const u1cert2 = await _tu.insertTestCertificate(storage, u1);
const u1cert2field1 = await _tu.insertTestCertificateField(storage, u1cert2, 'name', 'alice');
const u1cert3 = await _tu.insertTestCertificate(storage, u1);
const u1sync1 = await _tu.insertTestSyncState(storage, u1);
const u2 = await _tu.insertTestUser(storage);
const u2basket1 = await _tu.insertTestOutputBasket(storage, u2);
const u2label1 = await _tu.insertTestTxLabel(storage, u2);
const { tx: u2tx1 } = await _tu.insertTestTransaction(storage, u2, true);
const u2comm1 = await _tu.insertTestCommission(storage, u2tx1);
const u2tx1label1 = await _tu.insertTestTxLabelMap(storage, u2tx1, u2label1);
const u2tx1o0 = await _tu.insertTestOutput(storage, u2tx1, 0, 101, u2basket1);
const { tx: u2tx2 } = await _tu.insertTestTransaction(storage, u2, true);
const u2comm2 = await _tu.insertTestCommission(storage, u2tx2);
const proven1 = await _tu.insertTestProvenTx(storage);
const req1 = await _tu.insertTestProvenTxReq(storage, undefined, undefined, true);
const req2 = await _tu.insertTestProvenTxReq(storage, proven1.txid, proven1.provenTxId);
const we1 = await _tu.insertTestMonitorEvent(storage);
return {
u1,
u1basket1,
u1basket2,
u1label1,
u1label2,
u1tag1,
u1tag2,
u1tx1,
u1comm1,
u1tx1label1,
u1tx1label2,
u1tx1o0,
u1o0tag1,
u1o0tag2,
u1tx1o1,
u1o1tag1,
u1cert1,
u1cert1field1,
u1cert1field2,
u1cert2,
u1cert2field1,
u1cert3,
u1sync1,
u2,
u2basket1,
u2label1,
u2tx1,
u2comm1,
u2tx1label1,
u2tx1o0,
u2tx2,
u2comm2,
proven1,
req1,
req2,
we1
};
}
static async createTestSetup2(storage, identityKey, mockData = { actions: [] }) {
if (!mockData || !mockData.actions) {
throw new Error('mockData.actions is required');
}
const now = new Date();
const inputTxMap = {};
const outputMap = {};
// only one user
const user = await _tu.insertTestUser(storage, identityKey);
// Fir