UNPKG

@bsv/wallet-toolbox

Version:

BRC100 conforming wallet, wallet storage and wallet signer components

505 lines (436 loc) 16.1 kB
import * as bsv from '@bsv/sdk' import { createSyncMap, sdk, sha256Hash, SyncMap } from '../../../../../src' import { TestUtilsWalletStorage as _tu, TestWalletNoSetup } from '../../../../../test/utils/TestUtilsWalletStorage' import { EntityProvenTx } from '../EntityProvenTx' describe('ProvenTx class method tests', () => { jest.setTimeout(99999999) const env = _tu.getEnv('test') const ctxs: TestWalletNoSetup[] = [] const ctxs2: TestWalletNoSetup[] = [] beforeAll(async () => { if (env.runMySQL) { ctxs.push(await _tu.createLegacyWalletMySQLCopy('ProvenTxTests')) ctxs2.push(await _tu.createLegacyWalletMySQLCopy('ProvenTxTests2')) } ctxs.push(await _tu.createLegacyWalletSQLiteCopy('ProvenTxTests')) ctxs2.push(await _tu.createLegacyWalletSQLiteCopy('ProvenTxTests2')) }) afterAll(async () => { for (const ctx of ctxs) { await ctx.storage.destroy() } for (const ctx of ctxs2) { await ctx.storage.destroy() } }) test('0_fromTxid: valid txid with rawTx and Merkle proof (real database)', async () => { const ctx = ctxs[0] const txid = '2795b293c698b2244147aaba745db887a632d21990c474df46d842ec3e52f122' // Using a valid txid from the table // Fetch the rawTx and Merkle proof data directly from the database const provenTxRecord = await ctx.activeStorage.findProvenTxs({ partial: { txid } }) expect(provenTxRecord.length).toBeGreaterThan(0) // Ensure the record exists in the database const rawTx = provenTxRecord[0]?.rawTx const height = provenTxRecord[0]?.height const blockHash = provenTxRecord[0]?.blockHash const merkleRoot = provenTxRecord[0]?.merkleRoot const merklePathBinary = provenTxRecord[0]?.merklePath || [] const services: sdk.WalletServices = { chain: 'test', hashOutputScript: (script: string): string => { const hash = bsv.Utils.toHex(sha256Hash(bsv.Utils.toArray(script, 'hex'))) return hash }, getRawTx: async (requestedTxid: string) => { if (requestedTxid === txid) { return { txid: requestedTxid, rawTx } } throw new Error('Unexpected txid') }, getMerklePath: async (requestedTxid: string) => { if (requestedTxid === txid) { return { merklePath: { path: [[{ hash: txid, offset: 0 }]], blockHeight: height, toBinary: () => merklePathBinary, computeRoot: () => merkleRoot, verifyProof: () => true, toHex: () => Buffer.from(merklePathBinary).toString('hex'), indexOf: () => 0, findOrComputeLeaf: () => ({ hash: txid, offset: 0 }), verify: () => true, combine: () => ({}) as bsv.MerklePath, trim: () => ({}) as bsv.MerklePath } as unknown as bsv.MerklePath, header: { version: 1, previousHash: 'prev-hash', merkleRoot: merkleRoot, time: 1610000000, bits: 123456, nonce: 78910, height: height, hash: blockHash }, name: 'mock-service' } } throw new Error('Unexpected txid') }, getChainTracker: () => Promise.resolve({ isValidRootForHeight: async (root: string, height: number) => true, currentHeight: async () => height }), getHeaderForHeight: async () => Promise.resolve([1, 2, 3, 4]), getHeight: async () => height, getBsvExchangeRate: async () => 0, getFiatExchangeRate: async () => 1, postBeef: async () => [], getStatusForTxids: async () => ({ name: 'mock-service', status: 'success', results: [] }), isUtxo: async () => true, getUtxoStatus: async () => ({ name: 'mock-service', status: 'success', isUtxo: true, details: [] }), getScriptHashHistory: async () => ({ name: 'mock-service', status: 'success', history: [] }), hashToHeader: async () => ({ version: 1, previousHash: 'prev-hash', merkleRoot: merkleRoot, time: 1610000000, bits: 123456, nonce: 78910, height: height, hash: blockHash }), nLockTimeIsFinal: async () => true, getBeefForTxid: async () => new bsv.Beef(), getServicesCallHistory: () => ({ version: 1, getMerklePath: { serviceName: '', historyByProvider: {} }, getRawTx: { serviceName: '', historyByProvider: {} }, postBeef: { serviceName: '', historyByProvider: {} }, getStatusForTxids: { serviceName: '', historyByProvider: {} }, getUtxoStatus: { serviceName: '', historyByProvider: {} }, getScriptHashHistory: { serviceName: '', historyByProvider: {} }, updateFiatExchangeRates: { serviceName: '', historyByProvider: {} } }) } // Call the method under test const result = await EntityProvenTx.fromTxid(txid, services) // Validate the ProvenTx result expect(result.proven).toBeDefined() expect(result.proven!.txid).toBe(txid) // Validate Merkle proof details expect(result.proven!.height).toBe(height) expect(Buffer.from(result.proven!.merklePath).toString('hex')).toEqual( Buffer.from(merklePathBinary).toString('hex') ) expect(result.proven!.blockHash).toBe(blockHash) expect(result.proven!.merkleRoot).toBe(merkleRoot) expect(result.rawTx).toEqual(rawTx) }) test('1_fromTxid: txid with no rawTx available', async () => { const ctx = ctxs[0] const txid = 'missing-txid' const services: sdk.WalletServices = ctx.services // Call the method under test const result = await EntityProvenTx.fromTxid(txid, services) // Validate that ProvenTx could not be created expect(result.proven).toBeUndefined() expect(result.rawTx).toBeUndefined() }) test('2_fromTxid: txid with no Merkle proof available', async () => { const ctx = ctxs[0] const txid = 'no-merkle-proof-txid' const services: sdk.WalletServices = ctx.services // Verify the rawTx and Merkle proof const rawTx = await services.getRawTx(txid) const merkleProof = await services.getMerklePath(txid) // Call the method under test const result = await EntityProvenTx.fromTxid(txid, services) // Validate the ProvenTx result expect(result.proven).toBeUndefined() expect(result.rawTx).toEqual(rawTx.rawTx) }) test('3_ProvenTx getters and setters', () => { // Mock data to initialize the ProvenTx entity const mockData = { provenTxId: 1, created_at: new Date('2025-01-01T00:00:00Z'), updated_at: new Date('2025-01-02T00:00:00Z'), txid: '2795b293c698b2244147aaba745db887a632d21990c474df46d842ec3e52f122', height: 123, index: 0, merklePath: [0x04, 0x05, 0x06], rawTx: [0x01, 0x02, 0x03], blockHash: 'mock-block-hash', merkleRoot: 'mock-merkle-root' } // Initialize the ProvenTx entity with mock data const provenTx = new EntityProvenTx(mockData) // Validate getters expect(provenTx.provenTxId).toBe(mockData.provenTxId) expect(provenTx.created_at).toEqual(mockData.created_at) expect(provenTx.updated_at).toEqual(mockData.updated_at) expect(provenTx.txid).toBe(mockData.txid) expect(provenTx.height).toBe(mockData.height) expect(provenTx.index).toBe(mockData.index) expect(provenTx.merklePath).toEqual(mockData.merklePath) expect(provenTx.rawTx).toEqual(mockData.rawTx) expect(provenTx.blockHash).toBe(mockData.blockHash) expect(provenTx.merkleRoot).toBe(mockData.merkleRoot) // Validate setters provenTx.provenTxId = 2 provenTx.created_at = new Date('2025-02-01T00:00:00Z') provenTx.updated_at = new Date('2025-02-02T00:00:00Z') provenTx.txid = 'a3b2f0935c7b5bb7a841a09e535c13be86f4df0e7a91cebdc33812bfcc0eb9d7' provenTx.height = 456 provenTx.index = 1 provenTx.merklePath = [0x07, 0x08, 0x09] provenTx.rawTx = [0x0a, 0x0b, 0x0c] provenTx.blockHash = 'new-block-hash' provenTx.merkleRoot = 'new-merkle-root' // Validate updated values expect(provenTx.provenTxId).toBe(2) expect(provenTx.created_at).toEqual(new Date('2025-02-01T00:00:00Z')) expect(provenTx.updated_at).toEqual(new Date('2025-02-02T00:00:00Z')) expect(provenTx.txid).toBe('a3b2f0935c7b5bb7a841a09e535c13be86f4df0e7a91cebdc33812bfcc0eb9d7') expect(provenTx.height).toBe(456) expect(provenTx.index).toBe(1) expect(provenTx.merklePath).toEqual([0x07, 0x08, 0x09]) expect(provenTx.rawTx).toEqual([0x0a, 0x0b, 0x0c]) expect(provenTx.blockHash).toBe('new-block-hash') expect(provenTx.merkleRoot).toBe('new-merkle-root') // Validate overridden methods expect(provenTx.id).toBe(2) expect(provenTx.entityName).toBe('provenTx') expect(provenTx.entityTable).toBe('proven_txs') // Update id via overridden setter provenTx.id = 3 expect(provenTx.provenTxId).toBe(3) }) // Test: equals identifies matching ProvenTx entities test('4_equals: identifies matching ProvenTx entities', async () => { const ctx1 = ctxs[0] const ctx2 = ctxs2[0] // Insert a ProvenTx into the first database const provenTx1 = new EntityProvenTx({ provenTxId: 401, txid: 'valid-txid', created_at: new Date('2023-01-01'), updated_at: new Date('2023-01-02'), height: 1588740, index: 0, merklePath: [1, 2, 3], rawTx: [4, 5, 6], blockHash: 'block-hash', merkleRoot: 'merkle-root' }) await ctx1.activeStorage.insertProvenTx(provenTx1.toApi()) // Insert a matching ProvenTx into the second database const provenTx2 = new EntityProvenTx({ provenTxId: 401, txid: 'valid-txid', created_at: new Date('2023-01-01'), updated_at: new Date('2023-01-02'), height: 1588740, index: 0, merklePath: [1, 2, 3], rawTx: [4, 5, 6], blockHash: 'block-hash', merkleRoot: 'merkle-root' }) await ctx2.activeStorage.insertProvenTx(provenTx2.toApi()) const syncMap = createSyncMap() syncMap.provenTx.idMap = { [provenTx1.provenTxId]: provenTx2.provenTxId } // Verify the ProvenTx entities match expect(provenTx1.equals(provenTx2.toApi(), syncMap)).toBe(true) }) // Test: equals identifies non-matching txid test('5_equals: identifies non-matching txid', async () => { const provenTx1 = new EntityProvenTx({ provenTxId: 102, txid: 'txid1', created_at: new Date('2023-01-01'), updated_at: new Date('2023-01-02'), height: 1588740, index: 0, merklePath: [1, 2, 3], rawTx: [4, 5, 6], blockHash: 'block-hash', merkleRoot: 'merkle-root' }) const provenTx2 = new EntityProvenTx({ provenTxId: 103, txid: 'txid2', created_at: new Date('2023-01-01'), updated_at: new Date('2023-01-02'), height: 1588740, index: 0, merklePath: [1, 2, 3], rawTx: [4, 5, 6], blockHash: 'block-hash', merkleRoot: 'merkle-root' }) expect(provenTx1.equals(provenTx2.toApi())).toBe(false) }) // Test: equals identifies non-matching height test('6_equals: identifies non-matching height', async () => { const provenTx1 = new EntityProvenTx({ provenTxId: 104, txid: 'valid-txid', created_at: new Date('2023-01-01'), updated_at: new Date('2023-01-02'), height: 1588740, index: 0, merklePath: [1, 2, 3], rawTx: [4, 5, 6], blockHash: 'block-hash', merkleRoot: 'merkle-root' }) const provenTx2 = new EntityProvenTx({ provenTxId: 105, txid: 'valid-txid', created_at: new Date('2023-01-01'), updated_at: new Date('2023-01-02'), height: 1588741, index: 0, merklePath: [1, 2, 3], rawTx: [4, 5, 6], blockHash: 'block-hash', merkleRoot: 'merkle-root' }) expect(provenTx1.equals(provenTx2.toApi())).toBe(false) }) // Test: equals identifies non-matching merklePath test('7_equals: identifies non-matching merklePath', async () => { const provenTx1 = new EntityProvenTx({ provenTxId: 106, txid: 'valid-txid', created_at: new Date('2023-01-01'), updated_at: new Date('2023-01-02'), height: 1588740, index: 0, merklePath: [1, 2, 3], rawTx: [4, 5, 6], blockHash: 'block-hash', merkleRoot: 'merkle-root' }) const provenTx2 = new EntityProvenTx({ provenTxId: 107, txid: 'valid-txid', created_at: new Date('2023-01-01'), updated_at: new Date('2023-01-02'), height: 1588740, index: 0, merklePath: [1, 2, 4], rawTx: [4, 5, 6], blockHash: 'block-hash', merkleRoot: 'merkle-root' }) expect(provenTx1.equals(provenTx2.toApi())).toBe(false) }) // Test: equals identifies non-matching syncMap test('8_equals: identifies non-matching syncMap', async () => { const provenTx1 = new EntityProvenTx({ provenTxId: 108, txid: 'valid-txid', created_at: new Date('2023-01-01'), updated_at: new Date('2023-01-02'), height: 1588740, index: 0, merklePath: [1, 2, 3], rawTx: [4, 5, 6], blockHash: 'block-hash', merkleRoot: 'merkle-root' }) const provenTx2 = new EntityProvenTx({ provenTxId: 109, txid: 'valid-txid', created_at: new Date('2023-01-01'), updated_at: new Date('2023-01-02'), height: 1588740, index: 0, merklePath: [1, 2, 3], rawTx: [4, 5, 6], blockHash: 'block-hash', merkleRoot: 'merkle-root' }) const syncMap = createSyncMap() syncMap.provenTx.idMap = { 108: 999 } expect(provenTx1.equals(provenTx2.toApi(), syncMap)).toBe(false) }) test('9_equals: provenTxId mismatch without syncMap', async () => { const ctx1 = ctxs[0] const ctx2 = ctxs2[0] // Insert a ProvenTx record into the first database const tx1 = new EntityProvenTx({ provenTxId: 405, txid: 'txid1', created_at: new Date('2023-01-01'), updated_at: new Date('2023-01-02'), height: 100, index: 1, merklePath: [1, 2, 3], rawTx: [4, 5, 6], blockHash: 'block-hash-1', merkleRoot: 'merkle-root-1' }) await ctx1.activeStorage.insertProvenTx(tx1.toApi()) // Insert a different ProvenTx record into the second database with a mismatched provenTxId const tx2 = new EntityProvenTx({ provenTxId: 406, txid: 'txid1', created_at: new Date('2023-01-01'), updated_at: new Date('2023-01-02'), height: 100, index: 1, merklePath: [1, 2, 3], rawTx: [4, 5, 6], blockHash: 'block-hash-1', merkleRoot: 'merkle-root-1' }) await ctx2.activeStorage.insertProvenTx(tx2.toApi()) // Verify that `equals` returns false because provenTxId mismatch without a syncMap expect(tx1.equals(tx2.toApi())).toBe(false) }) test('10_mergeExisting: always returns false', async () => { const ctx = ctxs[0] // Create a ProvenTx entity const provenTx = new EntityProvenTx({ provenTxId: 101, txid: 'txid1', created_at: new Date('2023-01-01'), updated_at: new Date('2023-01-02'), height: 100, index: 1, merklePath: [1, 2, 3], rawTx: [4, 5, 6], blockHash: 'block-hash-1', merkleRoot: 'merkle-root-1' }) const mockSyncMap = createSyncMap() mockSyncMap.provenTx.idMap = { [provenTx.provenTxId]: provenTx.provenTxId } // Create mock storage, syncMap, and trx token const mockStorage = ctx.activeStorage const mockTrx: sdk.TrxToken = {} // Call the mergeExisting method const result = await provenTx.mergeExisting(mockStorage, new Date(), provenTx.toApi(), mockSyncMap, mockTrx) // Assert that it always returns false expect(result).toBe(false) }) })