All files / src/storage/methods getBeefForTransaction.ts

33.33% Statements 13/39
25% Branches 7/28
66.66% Functions 2/3
34.21% Lines 13/38

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 10857x 57x                                       57x       6x       6x   6x                                           6x 6x   6x           6x   6x 6x 6x                                                                                  
import { Beef } from '@bsv/sdk'
import { asBsvSdkTx, entity, sdk, StorageProvider, verifyTruthy } from '../../index.client'
 
/**
 * Creates a `Beef` to support the validity of a transaction identified by its `txid`.
 * 
 * `dojo.storage` is used to retrieve proven transactions and their merkle paths,
 * or proven_tx_req record with beef of external inputs (internal inputs meged by recursion).
 * Otherwise external services are used.
 * 
 * `dojo.options.maxRecursionDepth` can be set to prevent overly deep chained dependencies. Will throw ERR_EXTSVS_ENVELOPE_DEPTH if exceeded.
 * 
 * If `trustSelf` is true, a partial `Beef` will be returned where transactions known by `dojo.storage` to
 * be valid by verified proof are represented solely by 'txid'.
 * 
 * If `knownTxids` is defined, any 'txid' required by the `Beef` that appears in the array is represented solely as a 'known' txid. 
 * 
 * @param storage the chain on which txid exists.
 * @param txid the transaction hash for which an envelope is requested.
 * @param options
 */
export async function getBeefForTransaction(storage: StorageProvider, txid: string, options: sdk.StorageGetBeefOptions) : Promise<Beef>
{
    const beef =
        // deserialize mergeToBeef if it is an array
        Array.isArray(options.mergeToBeef) ? Beef.fromBinary(options.mergeToBeef)
        // otherwise if undefined create a new Beef
        : (options.mergeToBeef || new Beef())
 
    await mergeBeefForTransactionRecurse(beef, storage, txid, options, 0)
 
    return beef
}
 
/**
 * @returns rawTx if txid known to network, if merkle proof available then also proven result is valid.
 */
async function getProvenOrRawTxFromServices(dojo: StorageProvider, txid: string, options: sdk.StorageGetBeefOptions): Promise<sdk.ProvenOrRawTx> {
    const services = dojo.getServices();
    const por = await entity.ProvenTx.fromTxid(txid, await dojo.getServices());
    Iif (por.proven && !options.ignoreStorage && !options.ignoreNewProven) {
        por.proven.provenTxId = await dojo.insertProvenTx(por.proven.toApi());
    }
    return { proven: por.proven?.toApi(), rawTx: por.rawTx };
}
 
async function mergeBeefForTransactionRecurse(
    beef: Beef,
    dojo: StorageProvider,
    txid: string,
    options: sdk.StorageGetBeefOptions,
    recursionDepth: number
): Promise<Beef> {
    const maxDepth = dojo.maxRecursionDepth;
    Iif (maxDepth && maxDepth <= recursionDepth) throw new sdk.WERR_INVALID_OPERATION(`Maximum BEEF depth exceeded. Limit is ${dojo.maxRecursionDepth}`);
 
    Iif (options.knownTxids && options.knownTxids.indexOf(txid) > -1) {
        // This txid is one of the txids the caller claims to already know are valid...
        beef.mergeTxidOnly(txid);
        return beef
    }
 
    if (!options.ignoreStorage) {
        // if we can use storage, ask storage if it has the txid
        const knownBeef = await dojo.getValidBeefForTxid(txid, beef, options.trustSelf, options.knownTxids)
        if (knownBeef)
            return knownBeef
    }
 
    Iif (options.ignoreServices)
        throw new sdk.WERR_INVALID_PARAMETER(`txid ${txid}`, `valid transaction on chain ${dojo.chain}`);
 
    // if storage doesn't know about txid, use services
    // to find it and if it has a proof, remember it.
    const r = await getProvenOrRawTxFromServices(dojo, txid, options);
 
    Iif (r.proven && options.minProofLevel && options.minProofLevel > recursionDepth) {
        // ignore proof at this recursion depth
        r.proven = undefined;
    }
 
    Iif (r.proven) {
        // dojo has proven this txid,
        // merge both the raw transaction and its merkle path
        beef.mergeRawTx(r.proven.rawTx);
        beef.mergeBump(new entity.ProvenTx(r.proven).getMerklePath());
        return beef
    }
    
    Iif (!r.rawTx)
        throw new sdk.WERR_INVALID_PARAMETER(`txid ${txid}`, `valid transaction on chain ${dojo.chain}`);
 
    // merge the raw transaction and recurse over its inputs.
    beef.mergeRawTx(r.rawTx!);
    // recurse inputs
    const tx = asBsvSdkTx(r.rawTx!);
    for (const input of tx.inputs) {
        const inputTxid = verifyTruthy(input.sourceTXID);
        Iif (!beef.findTxid(inputTxid)) {
            // Only if the txid is not already in the list of beef transactions.
            await mergeBeefForTransactionRecurse(beef, dojo, inputTxid, options, recursionDepth + 1);
        }
    }
 
    return beef
}