UNPKG

@bsv/wallet-toolbox

Version:

BRC100 conforming wallet, wallet storage and wallet signer components

360 lines (342 loc) 12.8 kB
import { Beef, Transaction, WalletOutput } from '@bsv/sdk' import { EntityProvenTxReq, sdk, Services, Setup, StorageKnex, TableOutput, TableProvenTxReq, TableUser, verifyOne, verifyOneOrNone } from '../../../src' import { _tu, TuEnv } from '../../utils/TestUtilsWalletStorage' import { specOpInvalidChange, ValidListOutputsArgs } from '../../../src/sdk' import { LocalWalletTestOptions } from '../../utils/localWalletMethods' import { Format } from '../../../src/utility/Format' import * as dotenv from 'dotenv' dotenv.config() const chain: sdk.Chain = 'main' const options: LocalWalletTestOptions = { setActiveClient: true, useMySQLConnectionForClient: true, useTestIdentityKey: false, useIdentityKey2: false } describe('reqErrorReview.2025.05.06.man tests', () => { jest.setTimeout(99999999) // OVERWRITES EXISTING FILE CONTENTS!!!! test.skip('0 grab reqs history as local sqlite file', async () => { const { env, storage, services } = await _tu.createMainReviewSetup() const { activeStorage: s2 } = await _tu.createSQLiteTestWallet({ filePath: `${__dirname}/reqhistory.sqlite`, databaseName: 'reqhistory', chain: 'main', rootKeyHex: '1'.repeat(64), dropAll: true }) await s2.makeAvailable() const limit = 100 let offset = 0 for (;;) { const r = await storage.knex.raw(` select provenTxReqId as id, txid, status, history from proven_tx_reqs where history is not null limit ${limit} offset ${offset} `) const reqs = r[0] as { id: number; txid: string; status: sdk.ProvenTxReqStatus; history: string }[] for (const req of reqs) { const { id, history, status, txid } = req await s2.insertProvenTxReq({ created_at: new Date(), updated_at: new Date(), provenTxReqId: id, status, attempts: 0, notified: false, txid, history, notify: '', rawTx: [] }) } if (reqs.length < limit) break offset += limit } await s2.destroy() await storage.destroy() }) test('1 review reqs history and final outcome', async () => { let undouble: number[] = [] let uninvalid: number[] = [] let uncompleted: number[] = [] let deunmined: number[] = [] let noSuccessCompleted: number[] = [] let successDouble: number[] = [] let internalizeDouble: number[] = [] let successInvalid: number[] = [] const { activeStorage: storage } = await _tu.createSQLiteTestWallet({ filePath: `${__dirname}/reqhistory.sqlite`, databaseName: 'reqhistory', chain: 'main', rootKeyHex: '1'.repeat(64), dropAll: false }) //const { env, storage, services } = await _tu.createMainReviewSetup() let limit = 100 let offset = 0 let aggSum = -1 const partial: Partial<TableProvenTxReq> = {} let log = '' for (;;) { const reqs = await storage.findProvenTxReqs({ partial, status: undefined, paged: { limit, offset } }) for (const reqApi of reqs) { if (reqApi.provenTxReqId < 11312) continue const r = reviewHistoryNotes(reqApi) if (!r) continue if (r.isCompleted && r.wasDoubleSpend) { undouble.push(reqApi.provenTxReqId) let review = '' if (r.doubleReview) { const rr = r.doubleReview review = `0:${rr.status0},1:${rr.status1},2:${rr.status2},Txs:${rr.competingTxs}` } //log += `undouble ${reqApi.provenTxReqId} arc:${r.brArc} woc:${r.brWoC} bit:${r.brBitails} ${review}\n` } if (r.isCompleted && r.wasInvalid) { uninvalid.push(reqApi.provenTxReqId) //log += `uninvalid ${reqApi.provenTxReqId} arc:${r.brArc} woc:${r.brWoC} bit:${r.brBitails}\n` } if ((r.isDoubleSpend || r.isInvalid) && r.wasCompleted) { uncompleted.push(reqApi.provenTxReqId) } if ((r.isDoubleSpend || r.isInvalid) && r.wasUnmined) { if (r.wasInternalize) internalizeDouble.push(reqApi.provenTxReqId) else { deunmined.push(reqApi.provenTxReqId) log += `deunmined ${reqApi.provenTxReqId} arc:${r.brArc} woc:${r.brWoC} bit:${r.brBitails}\n` } } if (r.aggregate && r.aggregate.successCount === 0 && r.isCompleted) { noSuccessCompleted.push(reqApi.provenTxReqId) } if (r.aggregate && r.aggregate.successCount > 0 && r.isDoubleSpend) { successDouble.push(reqApi.provenTxReqId) } if (r.aggregate && r.aggregate.successCount > 0 && r.isInvalid) { successInvalid.push(reqApi.provenTxReqId) } if (r.aggregate && r.aggSum !== aggSum) { log += `aggSum changed ${aggSum} to ${r.aggSum} reqId=${reqApi.provenTxReqId}\n` aggSum = r.aggSum } } if (reqs.length < limit) break offset += limit } if (undouble.length > 0) log += `undouble: ${JSON.stringify(undouble)}\n` if (uninvalid.length > 0) log += `uninvalid: ${JSON.stringify(uninvalid)}\n` if (uncompleted.length > 0) log += `uncompleted: ${JSON.stringify(uncompleted)}\n` if (deunmined.length > 0) log += `deunmined: ${JSON.stringify(deunmined)}\n` if (internalizeDouble.length > 0) log += `internalizeDouble: ${JSON.stringify(internalizeDouble)}\n` if (noSuccessCompleted.length > 0) log += `noSuccessCompleted: ${JSON.stringify(noSuccessCompleted)}\n` if (successDouble.length > 0) log += `successDouble: ${JSON.stringify(successDouble)}\n` if (successInvalid.length > 0) log += `successInvalid: ${JSON.stringify(successInvalid)}\n` console.log(log) await storage.destroy() }) const uninvalid = [ 10822, 12228, 14884, 14948, 1654, 1649, 2654, 2655, 2656, 2658, 2659, 2660, 2661, 2662, 2663, 2664, 2665, 2666, 2667, 2669, 2707, 2719, 2723, 2724, 2726 ] const undouble = [ 10732, 12303, 12476, 14084, 14111, 14956, 14972, 14874, 14789, 14810, 14813, 14817, 14588, 14640, 14641, 14531, 2753, 2653, 2657, 2670, 2671, 2681, 2684, 2691, 2732, 4343, 4222, 4124, 4148, 3873, 3735, 3514, 3537, 5074, 5125, 4958, 4977, 4730, 4365 ] const deunmined = [ 12304, 12305, 12306, 12307, 12480, 12483, 12484, 12488, 12489, 12490, 12497, 14085, 14086, 14087, 14814, 14816, 14821, 14953, 15170 ] test('2 review deunmined reqs', async () => { const { env, storage, services } = await _tu.createMainReviewSetup() const chaintracker = await services.getChainTracker() let log = '' for (const id of deunmined) { const reqApi = await storage.findProvenTxReqById(id) if (!reqApi) continue const beef = new Beef() beef.mergeRawTx(reqApi.rawTx!) if (reqApi.inputBEEF) beef.mergeBeef(reqApi.inputBEEF) let tx = beef.findTxid(reqApi.txid)!.tx! let allInputsFound = true let ilog = '' for (const input of tx.inputs) { if (beef.findTxid(input.sourceTXID!)) continue try { const ib = await storage.getBeefForTransaction(input.sourceTXID!, {}) if (ib) beef.mergeBeef(ib) } catch (e) { if (input.sourceTXID) { const r2 = verifyOneOrNone(await storage.findProvenTxReqs({ partial: { txid: input.sourceTXID } })) if (r2 && r2.rawTx) { const itx = Transaction.fromBinary(r2.rawTx) ilog += 'missing input ' + Format.toLogStringTransaction(itx) } } allInputsFound = false } } if (allInputsFound) { tx = beef.findAtomicTransaction(reqApi.txid)! try { const ok = await tx.verify('scripts only') log += `${reqApi.provenTxReqId} ${reqApi.txid} ${ok ? 'OK' : 'FAIL'}\n` } catch (e: unknown) { log += `${reqApi.provenTxReqId} ${reqApi.txid} ${sdk.WalletError.fromUnknown(e).message}\n` } } else { log += `${reqApi.provenTxReqId} FAILED ` log += Format.toLogStringBeefTxid(beef, reqApi.txid) log += ilog } } console.log(log) await storage.destroy() }) }) function reviewHistoryNotes(reqApi: TableProvenTxReq): HistoryReviewInfo | undefined { const r: HistoryReviewInfo = { req: new EntityProvenTxReq(reqApi), wasDoubleSpend: false, wasInvalid: false, wasCompleted: false, wasUnmined: false, wasInternalize: false, isDoubleSpend: false, isInvalid: false, isCompleted: false, aggSum: 0, aggregate: undefined } if (!r.req.history?.notes) return undefined for (const note of r.req.history.notes) { if (note.what === 'status') { const statusWas = note.status_was as sdk.ProvenTxReqStatus const statusNow = note.status_now as sdk.ProvenTxReqStatus if (statusNow === 'doubleSpend') { r.isDoubleSpend = r.wasDoubleSpend = true r.isInvalid = false r.isCompleted = false } else if (statusNow === 'invalid') { r.isDoubleSpend = false r.isInvalid = r.wasInvalid = true r.isCompleted = false } else if (statusNow === 'completed') { r.isDoubleSpend = false r.isInvalid = false r.isCompleted = r.wasCompleted = true } else if (statusNow === 'unmined') { r.isDoubleSpend = false r.isInvalid = false r.wasUnmined = true } } else if (note.what === 'aggregateResults') { r.aggregate = { successCount: note.successCount as number, doubleSpendCount: note.doubleSpendCount as number, statusErrorCount: note.statusErrorCount as number, serviceErrorCount: note.serviceErrorCount as number, newReqStatus: note.newReqStatus as sdk.ProvenTxReqStatus } const a = r.aggregate r.aggSum = a.doubleSpendCount + a.statusErrorCount + a.serviceErrorCount + a.successCount } else if (note.what === 'confirmDoubleSpend') { r.doubleReview = { status0: note.getStatus0 as string, status1: note.getStatus1 as string, status2: note.getStatus2 as string, competingTxs: note.competingTxs as string } } else if (note.what === 'internalizeAction') { r.wasInternalize = true } if (note.name === 'WoCpostRawTx') { if (note.what === 'postRawTxErrorMissingInputs') { r.brWoC = 'missingInputs' } else if (note.what === 'postRawTxError') { if (note.status === 504) { r.brWoC = 'serviceError' } } } else if (note.name === 'WoCpostBeef') { if (note.what === 'postBeefSuccess') { r.brWoC = 'success' } else if (note.what === 'postBeefError' && r.brWoC === undefined) { r.brWoC = 'invalidTx' } } else if (note.name === 'ARCpostBeef') { if (note.what === 'postBeefGetTxDataSuccess') { if (note.txStatus === 'STORED') r.brArc = 'success' } } else if (note.name === 'ARCv1tx') { if (note.what === 'postRawTxDoubleSpend') { if (note.txStatus === 'DOUBLE_SPEND_ATTEMPTED') r.brArc = 'doubleSpend' } else if (note.what === 'postRawTxError') { if (note.status === 469) r.brArc = 'badRoots' else if (note.status === 463) r.brArc = 'badBump' } else if (note.what === 'postRawTxSuccess') { if (note.txStatus === 'ANNOUNCED_TO_NETWORK') r.brArc = 'success' else if (note.txStatus === 'SEEN_ON_NETWORK') r.brArc = 'success' else if (note.txStatus === 'REQUESTED_BY_NETWORK') r.brArc = 'success' } } else if (note.name === 'BitailsPostRawTx') { if (note.what === 'postRawsSuccess') { r.brBitails = 'success' } else if (note.what === 'postRawsSuccessAlreadyInMempool') { r.brBitails = 'success' } else if (note.what === 'postRawsErrorMissingInputs') { r.brBitails = 'invalidTx' } else if (note.what === 'postRawsError') { if (note.code === -26) { r.brBitails = 'invalidTx' } else if (note.code === -1) { r.brBitails = 'serviceError' } else if (note.code === 'ESOCKETTIMEDOUT') { r.brBitails = 'serviceError' } } } } return r } interface HistoryReviewInfo { brArc?: string brWoC?: string brBitails?: string req: EntityProvenTxReq wasDoubleSpend: boolean wasInvalid: boolean wasCompleted: boolean wasUnmined: boolean wasInternalize: boolean isDoubleSpend: boolean isInvalid: boolean isCompleted: boolean aggSum: number aggregate?: { successCount: number doubleSpendCount: number statusErrorCount: number serviceErrorCount: number newReqStatus: sdk.ProvenTxReqStatus } doubleReview?: { status0: string status1: string status2: string competingTxs: string } }