UNPKG

wallet-storage

Version:

BRC100 conforming wallet, wallet storage and wallet signer components

343 lines (285 loc) 16.9 kB
import { MerklePath } from '@bsv/sdk' import { asArray, entity, sdk, verifyOne, verifyTruthy, wait } from "../../src/index.client" import { TaskCheckForProofs } from "../../src/monitor/tasks/TaskCheckForProofs" import { TaskClock } from "../../src/monitor/tasks/TaskClock" import { TaskNewHeader } from "../../src/monitor/tasks/TaskNewHeader" import { TaskPurge } from "../../src/monitor/tasks/TaskPurge" import { TaskSendWaiting } from "../../src/monitor/tasks/TaskSendWaiting" import { _tu, TestSetup1Wallet, TestWallet } from "../utils/TestUtilsWalletStorage" import exp from 'constants' import { TaskReviewStatus } from '../../src/monitor/tasks/TaskReviewStatus' describe('Monitor tests', () => { jest.setTimeout(99999999) const env = _tu.getEnv('test') const ctxs: TestSetup1Wallet[] = [] beforeAll(async () => { ctxs.push(await _tu.createSQLiteTestSetup1Wallet({ databaseName: 'walletMonitorMain', chain: 'main', rootKeyHex: '3'.repeat(64)})) //ctxs.push(await _tu.createSQLiteTestSetup1Wallet({ databaseName: 'walletMonitorTest', chain: 'test', rootKeyHex: '3'.repeat(64)})) }) afterAll(async () => { for (const ctx of ctxs) { await ctx.storage.destroy() } }) test('0 TaskClock', async () => { if (!env.runSlowTests) return // This test takes a bit over a minute to run... un-skip it to work on it. for (const { chain, wallet, services, monitor } of ctxs) { if (!monitor) throw new sdk.WERR_INTERNAL('test requires setup with monitor'); { // The clock attempts to update nextMinute to msecs for each minute. // Starting the clock and waiting a bit over a minute should see the value // increase by one minute's worth of msecs. const task = new TaskClock(monitor) monitor._tasks.push(task) const msecsFirst = task.nextMinute const startTasksPromise = monitor.startTasks() await wait(monitor.oneMinute * 1.1) const msecsNext = task.nextMinute monitor.stopTasks() const elapsed = (msecsNext - msecsFirst) / monitor.oneMinute expect(elapsed === 1 || elapsed === 2).toBe(true) await startTasksPromise } } }) test('1 TaskNewHeader', async () => { if (!env.runSlowTests) return // This test takes 10+ seconds to run... un-skip it to work on it. for (const { chain, wallet, services, monitor } of ctxs) { if (!monitor) throw new sdk.WERR_INTERNAL('test requires setup with monitor'); { // The new header task polls chaintracks for latest header and if new sets flag to check for proofs. // Starting the clock and waiting a bit should cause first header to be fetched and flag to be set. const task = new TaskNewHeader(monitor) monitor._tasks.push(task) expect(TaskCheckForProofs.checkNow).toBe(false) const startTasksPromise = monitor.startTasks() await wait(monitor.oneSecond * 10) expect(task.header).toBeTruthy() expect(TaskCheckForProofs.checkNow).toBe(true) monitor.stopTasks() await startTasksPromise } } }) test.skip('2 TaskPurge', async () => { /* ** The following code is to test against un-purged data copied from staging-dojo: const ctxs: TestWallet<{}>[] = [] const env = _tu.getEnv('test') const identityKeyTone = '03ac2d10bdb0023f4145cc2eba2fcd2ad3070cb2107b0b48170c46a9440e4cc3fe' const rootKeyHex = env.devKeys[identityKeyTone] ctxs.push(await _tu.createMySQLTestWallet({ databaseName: 'stagingdojotone', chain: 'test', rootKeyHex, dropAll: false })) */ for (const { chain, wallet, services, monitor } of ctxs) { if (!monitor) throw new sdk.WERR_INTERNAL('test requires setup with monitor'); { const task = new TaskPurge(monitor, { purgeCompleted: true, purgeFailed: true, purgeSpent: true, purgeCompletedAge: 1, purgeFailedAge: 1, purgeSpentAge: 1, }) TaskPurge.checkNow = true monitor._tasks.push(task) await monitor.runTask('Purge') } } for (const ctx of ctxs) { await ctx.storage.destroy() } }) test('3 TaskSendWaiting success', async () => { await runMockedSendWaiting('success', 'monitorTest3') }) test('4 TaskSendWaiting error', async () => { await runMockedSendWaiting('error', 'monitorTest4') }) test('5 TaskCheckForProofs success', async () => { const ctxs: TestWallet<{}>[] = [] ctxs.push(await _tu.createLegacyWalletSQLiteCopy('monitorTest5')) let txidsPosted: string[] = [] let mockResultIndex = 0 const expectedTxids = [ "c099c52277426abb863dc902d0389b008ddf2301d6b40ac718746ac16ca59136", "6935ce33b9e3b9ee60360ce0606aa0a0970b4840203f457b5559212676dc33ab", "67ca2475886b3fc2edd76a2eb8c32bd0bc308176c7dff463e0507942aeebcbec", "3fa94b62a3b10d8c18bada527a9b68c4e70db67140719df16c44fb0328782532", "519675259eff036c6597e4a497d37c132e718171dde4ea2257e84c947ecf656b", ] _tu.mockMerklePathServicesAsCallback(ctxs, async (txid) => { expect(expectedTxids).toContain(txid) const r = mockGetMerklePathResults[mockResultIndex++] return r }) for (const { activeStorage: storage, monitor } of ctxs) { if (!monitor) throw new sdk.WERR_INTERNAL('test requires setup with monitor') { for (const txid of expectedTxids) { // no matching ProvenTx exists. expect((await storage.findProvenTxs({ partial: { txid } })).length).toBe(0) const req = verifyTruthy(await entity.ProvenTxReq.fromStorageTxid(storage, txid)) expect(req.status).toBe('unmined') } const task = new TaskCheckForProofs(monitor, 1) monitor._tasks.push(task) await monitor.runTask('CheckForProofs') for (const txid of expectedTxids) { const proven = verifyOne(await storage.findProvenTxs({ partial: { txid } })) expect(proven.merklePath).toBeTruthy() const req = verifyTruthy(await entity.ProvenTxReq.fromStorageTxid(storage, txid)) expect(req.status).toBe('completed') expect(req.provenTxId).toBe(proven.provenTxId) } } } for (const ctx of ctxs) { await ctx.storage.destroy() } }) test('6 TaskCheckForProofs fail', async () => { const ctxs: TestWallet<{}>[] = [] ctxs.push(await _tu.createLegacyWalletSQLiteCopy('monitorTest6')) let txidsPosted: string[] = [] let mockResultIndex = 0 const expectedTxids = [ "c099c52277426abb863dc902d0389b008ddf2301d6b40ac718746ac16ca59136", "6935ce33b9e3b9ee60360ce0606aa0a0970b4840203f457b5559212676dc33ab", "67ca2475886b3fc2edd76a2eb8c32bd0bc308176c7dff463e0507942aeebcbec", "3fa94b62a3b10d8c18bada527a9b68c4e70db67140719df16c44fb0328782532", "519675259eff036c6597e4a497d37c132e718171dde4ea2257e84c947ecf656b", ] _tu.mockMerklePathServicesAsCallback(ctxs, async (txid) => { expect(expectedTxids).toContain(txid) const r = {} return r }) for (const { activeStorage: storage, monitor } of ctxs) { if (!monitor) throw new sdk.WERR_INTERNAL('test requires setup with monitor') { const attempts: number[] = [] for (const txid of expectedTxids) { // no matching ProvenTx exists. expect((await storage.findProvenTxs({ partial: { txid } })).length).toBe(0) const req = verifyTruthy(await entity.ProvenTxReq.fromStorageTxid(storage, txid)) expect(req.status).toBe('unmined') attempts.push(req.attempts) } const task = new TaskCheckForProofs(monitor, 1) monitor._tasks.push(task) await monitor.runTask('CheckForProofs') let i = -1 for (const txid of expectedTxids) { i++ // no matching ProvenTx exists. expect((await storage.findProvenTxs({ partial: { txid } })).length).toBe(0) const req = verifyTruthy(await entity.ProvenTxReq.fromStorageTxid(storage, txid)) expect(req.status).toBe('unmined') expect(req.attempts).toBeGreaterThanOrEqual(attempts[i]) } } } for (const ctx of ctxs) { await ctx.storage.destroy() } }) const mockGetMerklePathResults: sdk.GetMerklePathResult[] = [ { name: "WoCTsc", merklePath: new MerklePath( 1652142, [ [{ offset: 2, hash: "74c55a15a08ea491e02c41a6934c4177666c0dbda2781d0cf9743d3ad68a4623" }, { offset: 3, hash: "c099c52277426abb863dc902d0389b008ddf2301d6b40ac718746ac16ca59136", txid: true }], [{ offset: 0, hash: "2574544a253c91e69c7d5b4478af95d39420ad2c8e44c78b280f1bd5e7a11849" }], [{ offset: 1, hash: "8903289601da1910820c3471d41ae9187a7d46d6e39e636840b176519bdc5d00" }]]), header: { version: 536870912, previousHash: "0000000039f1c7dc943d50883e531022825bf5c15a40db2cedde7d203ca3d644", merkleRoot: "68bde58600fbd2c716871356cc2ad34b43ac67ac8d7a879dd966429d5a6935b2", time: 1734530373, bits: 474103450, nonce: 3894752803, height: 1652142, hash: "000000000d9419a409f83f16e2c162b4e44266986d6b9ee02d1b97d9556d9a3a" }, }, { name: "WoCTsc", merklePath: new MerklePath(1652142, [[{ offset: 4, hash: "6935ce33b9e3b9ee60360ce0606aa0a0970b4840203f457b5559212676dc33ab", txid: true }, { offset: 5, duplicate: true }], [{ offset: 3, hash: "65b5a77f61ca87af5766546e4a22129da89f3378322ef29aac6cdc94c1f637f3" }], [{ offset: 0, hash: "0aeaa5c76cba5495f922ae0b52805c0d12c2ffa54d2829d250c958d67c7c5073" }]]), header: { version: 536870912, previousHash: "0000000039f1c7dc943d50883e531022825bf5c15a40db2cedde7d203ca3d644", merkleRoot: "68bde58600fbd2c716871356cc2ad34b43ac67ac8d7a879dd966429d5a6935b2", time: 1734530373, bits: 474103450, nonce: 3894752803, height: 1652142, hash: "000000000d9419a409f83f16e2c162b4e44266986d6b9ee02d1b97d9556d9a3a" }, }, { name: "WoCTsc", merklePath: new MerklePath(1652145, [[{ offset: 0, hash: "c160acfce1c29c648614b722f1c490473fd7aea0c60d21be95ae981eb0c9c4f0" }, { offset: 1, hash: "67ca2475886b3fc2edd76a2eb8c32bd0bc308176c7dff463e0507942aeebcbec", txid: true }], [{ offset: 1, hash: "c0eb049e4d3872d63bd3402dd4d6bc8022a170155493a994e1da692f08b2f2d0" }]]), header: { version: 536870912, previousHash: "000000001888ff57f4848f181f9f69cab27f2388d7c2edd99b8c004ae482cca7", merkleRoot: "f990936bc3267ba4911acc490107ed1841eedbd2c5017e1074891285df30f255", time: 1734532172, bits: 474081547, nonce: 740519774, height: 1652145, hash: "0000000003ea4ecae9254b992f292137fde1de66cc809d1a81cfd60cab4ba160" } }, { name: "WoCTsc", merklePath: new MerklePath(1652145, [[{ offset: 2, hash: "3fa94b62a3b10d8c18bada527a9b68c4e70db67140719df16c44fb0328782532", txid: true }, { offset: 3, duplicate: true }], [{ offset: 0, hash: "5eec838112f0eabc45e68c8ec14f76e74b0ea636180d91ccf034f5f3c5114edf" }]]), header: { version: 536870912, previousHash: "000000001888ff57f4848f181f9f69cab27f2388d7c2edd99b8c004ae482cca7", merkleRoot: "f990936bc3267ba4911acc490107ed1841eedbd2c5017e1074891285df30f255", time: 1734532172, bits: 474081547, nonce: 740519774, height: 1652145, hash: "0000000003ea4ecae9254b992f292137fde1de66cc809d1a81cfd60cab4ba160" } }, { name: "WoCTsc", merklePath: new MerklePath(1652160, [[{ offset: 0, hash: "ee8d57d6c3f5be3238709f539dc224c44c2c848414cb5969bfa8c81c2768ad6b" }, { offset: 1, hash: "519675259eff036c6597e4a497d37c132e718171dde4ea2257e84c947ecf656b", txid: true }]]), header: { version: 536870912, previousHash: "0000000012dbd406fef49503c545bafd940ba2f2c9b05ef351177b71fe96e7d8", merkleRoot: "c2714feeccc7db8ea4235799e6490271867008dd39e3cf8a6e9aa20fd47f3222", time: 1734538772, bits: 474045917, nonce: 2431702809, height: 1652160, hash: "000000001c5d2b3beb2e1f1f21f69f77cb979ed92f99d2cdd1a2618349b575ca" } } ] async function runMockedSendWaiting(postBeefMockStatus, database: string) { const ctxs: TestWallet<{}>[] = [] ctxs.push(await _tu.createLegacyWalletSQLiteCopy(database)) let txidsPosted: string[] = [] _tu.mockPostServicesAsCallback(ctxs, (beef, txids) => { txidsPosted.push(...txids); return postBeefMockStatus as 'success' | 'error' }) for (const { activeStorage: storage, monitor } of ctxs) { if (!monitor) throw new sdk.WERR_INTERNAL('test requires setup with monitor') { const task = new TaskSendWaiting(monitor, 1, 1) monitor._tasks.push(task) txidsPosted = [] const expectedTxids = [ "d9ec73b2e0f06e0f482d2d1db9ceccf2f212f0b24afbe10846ac907567be571f", "b7634f08d8c7f3c6244050bebf73a79f40e672aba7d5232663609a58b123b816", "3d2ea64ee584a1f6eb161dbedf3a8d299e3e4497ac7a203d23c044c998c6aa08", "a3a8fe7f541c1383ff7b975af49b27284ae720af5f2705d8409baaf519190d26", "6d68cc6fa7363e59aaccbaa65f0ca613a6ae8af718453ab5d3a2b022c59b5cc6", ] for (const txid of expectedTxids) { const req = verifyOne(await storage.findProvenTxReqs({ partial: { txid } })) expect(req.status).toBe('unsent') const notifyIds = new entity.ProvenTxReq(req).notify.transactionIds || [] for (const transactionId of notifyIds) { const tx = verifyTruthy(await storage.findTransactionById(transactionId)) expect(['nosend', 'unprocessed', 'sending']).toContain(tx.status) } } await monitor.runTask('SendWaiting') expect(txidsPosted).toEqual(expectedTxids) for (const txid of expectedTxids) { const req = verifyOne(await storage.findProvenTxReqs({ partial: { txid } })) if (env.logTests) console.log(new entity.ProvenTxReq(req).historyPretty()) const notifyIds = new entity.ProvenTxReq(req).notify.transactionIds || [] switch (postBeefMockStatus) { case 'success': { expect(req.status).toBe('unmined') for (const transactionId of notifyIds) { const tx = verifyTruthy(await storage.findTransactionById(transactionId)) expect(['unproven']).toContain(tx.status) } } break; case 'error': { expect(req.status).toBe('unsent') } } } } } for (const ctx of ctxs) { await ctx.storage.destroy() } } test('7 TaskReviewStatus', async () => { const ctxs: TestWallet<{}>[] = [] ctxs.push(await _tu.createLegacyWalletSQLiteCopy('monitorTest7')) //ctxs.push(await _tu.createLegacyWalletMySQLCopy('monitorTest7')) for (const { activeStorage: storage, monitor } of ctxs) { const reqs = await storage.findProvenTxReqs({ partial: { status: 'unmined' }}) const crus = await storage.updateProvenTxReq(reqs[0].provenTxReqId, { status: 'invalid' }) const ctus = await storage.updateTransaction(23, { provenTxId: undefined }) const task = new TaskReviewStatus(monitor, 1, 1000 * 5) monitor._tasks.push(task) const log = await monitor.runTask('ReviewStatus') //console.log(log) } for (const ctx of ctxs) { await ctx.storage.destroy() } }) })