All files / src/storage/methods attemptToPostReqsToNetwork.ts

70% Statements 56/80
48.57% Branches 17/35
100% Functions 4/4
75.71% Lines 53/70

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 16457x   57x               57x   11x           11x 12x                       12x   11x 11x 12x 12x     12x     12x         12x 12x 12x                 12x     11x                       11x 11x 11x 11x   11x     11x 13x 12x 12x 12x   12x     12x 12x   5x 12x 12x         12x       11x 12x 12x     12x 7x 7x 7x     5x         5x           12x   7x   12x 12x 7x 7x 7x 7x             11x                                          
import { Beef } from '@bsv/sdk'
import { StorageProvider } from "../StorageProvider"
import { entity, sdk } from '../../index.client'
 
/**
 * Attempt to post one or more `ProvenTxReq` with status 'unsent'
 * to the bitcoin network.
 * 
 * @param reqs 
 */
export async function attemptToPostReqsToNetwork(storage: StorageProvider, reqs: entity.ProvenTxReq[], trx?: sdk.TrxToken): Promise<PostReqsToNetworkResult> {
 
    const r: PostReqsToNetworkResult = {
        status: 'success',
        beef: new Beef(),
        details: [],
        log: ''
    }
    for (const req of reqs) {
        r.details.push({
            txid: req.txid,
            req,
            status: "unknown",
            pbrft: {
                txid: req.txid,
                status: "error"
            },
            data: undefined,
            error: undefined
        })
    }
    const txids = reqs.map(r => r.txid)
 
    let invalid: boolean = false
    for (const rb of reqs) {
        let badReq: boolean = false
        Iif (!rb.rawTx) {
            badReq = true; rb.addHistoryNote(`invalid req: rawTx must be valid`);
        }
        Iif (!rb.notify.transactionIds || rb.notify.transactionIds.length < 1) {
            badReq = true; rb.addHistoryNote(`invalid req: must have at least one transaction to notify`);
        }
        Iif (rb.attempts > 10) {
            badReq = true; rb.addHistoryNote(`invalid req: too many attempts ${rb.attempts}`);
        }
 
        // Accumulate batch beefs.
        if (!badReq) {
            try {
                await storage.mergeReqToBeefToShareExternally(rb.api, r.beef, [], trx)
            } catch (eu: unknown) {
                const e = sdk.WalletError.fromUnknown(eu)
                Iif (e.code === 'WERR_INVALID_PARAMETER' && (e as sdk.WERR_INVALID_PARAMETER).parameter === 'txid') {
                    badReq = true; rb.addHistoryNote(`invalid req: depends on txid which is unknown`);
                }
            }
        }
 
        Iif (badReq) invalid = true
    }
 
    Iif (invalid) {
        for (const req of reqs) {
            // batch passes or fails as a whole...prior to post to network attempt.
            req.status = 'invalid'
            await req.updateStorageDynamicProperties(storage)
            r.log += `status set to ${req.status}\n`
        }
        return r;
    }
 
    // Use cwi-external-services to post the aggregate beef
    // and add the new results to aggregate results.
    const services = await storage.getServices()
    const pbrs = await services.postBeef(r.beef, txids)
    const pbrOk = pbrs.find(p => p.status === 'success')
    r.pbr = pbrOk ? pbrOk : pbrs.length > 0 ? pbrs[0] : undefined
 
    Iif (!r.pbr) {
        r.status = 'error'
    } else {
        for (const d of r.details) {
            const pbrft = r.pbr.txidResults.find(t => t.txid === d.txid)
            Iif (!pbrft) throw new sdk.WERR_INTERNAL(`postBeef service failed to return result for txid ${d.txid}`);
            d.pbrft = pbrft
            Iif (r.pbr.data)
                d.data = JSON.stringify(r.pbr.data)
            Iif (r.pbr.error)
                d.error = r.pbr.error.code
            // Need to learn how double spend is reported by these services.
            d.status = pbrft.status === 'success' ? 'success' : 'unknown'
            if (d.status !== 'success')
                // If any txid result fails, the aggregate result is error.
                r.status = 'error';
            d.req.attempts++
            const note = {
                what: 'postReqsToNetwork result',
                name: r.pbr.name,
                result: d
            }
            d.req.addHistoryNote(note)
        }
    }
 
    for (const d of r.details) {
        let newReqStatus: sdk.ProvenTxReqStatus | undefined = undefined
        let newTxStatus: sdk.TransactionStatus | undefined = undefined
        // For each req, three outcomes are handled:
        // 1. success: req status from unprocessed(!isDelayed)/sending(isDelayed) to unmined, tx from sending to unproven
        if (d.status === 'success') {
            if (['nosend', 'unprocessed', 'sending', 'unsent'].indexOf(d.req.status) > -1)
                newReqStatus = 'unmined';
            newTxStatus = 'unproven' // but only if sending
        }
        // 2. doubleSpend: req status to doubleSpend, tx to failed
        else Iif (d.status === 'doubleSpend') {
            newReqStatus = 'doubleSpend'
            newTxStatus = 'failed'
        }
        // 3. unknown: req status from unprocessed to sending or remains sending, tx remains sending
        else if (d.status === 'unknown') {
            /* no status updates */
        } else E{
            throw new sdk.WERR_INTERNAL(`unexpected status ${d.status}`)
        }
 
        if (newReqStatus) {
            // Only advance the status of req.
            d.req.status = newReqStatus
        }
        await d.req.updateStorageDynamicProperties(storage)
        if (newTxStatus) {
            const ids = d.req.notify.transactionIds
            Iif (!ids || ids.length < 1) throw new sdk.WERR_INTERNAL(`req must have at least one transactionId to notify`);
            for (const id of ids) {
                await storage.updateTransactionStatus(newTxStatus, id)
            }
        }
    }
 
    // Fetch the updated history.
    // log += .req.historyPretty(since, indent + 2)
    return r
}
 
 
export type PostReqsToNetworkDetailsStatus = 'success' | 'doubleSpend' | 'unknown'
 
export interface PostReqsToNetworkDetails {
    txid: string
    req: entity.ProvenTxReq
    status: PostReqsToNetworkDetailsStatus
    pbrft: sdk.PostTxResultForTxid
    data?: string
    error?: string
}
 
export interface PostReqsToNetworkResult {
    status: "success" | "error"
    beef: Beef
    details: PostReqsToNetworkDetails[]
    pbr?: sdk.PostBeefResult
    log: string
}