| 726 | } |
| 727 | |
| 728 | static RPCHelpMan combinerawtransaction() |
| 729 | { |
| 730 | return RPCHelpMan{"combinerawtransaction", |
| 731 | "\nCombine multiple partially signed transactions into one transaction.\n" |
| 732 | "The combined transaction may be another partially signed transaction or a \n" |
| 733 | "fully signed transaction.", |
| 734 | { |
| 735 | {"txs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The hex strings of partially signed transactions", |
| 736 | { |
| 737 | {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A hex-encoded raw transaction"}, |
| 738 | }, |
| 739 | }, |
| 740 | }, |
| 741 | RPCResult{ |
| 742 | RPCResult::Type::STR, "", "The hex-encoded raw transaction with signature(s)" |
| 743 | }, |
| 744 | RPCExamples{ |
| 745 | HelpExampleCli("combinerawtransaction", R"('["myhex1", "myhex2", "myhex3"]')") |
| 746 | }, |
| 747 | [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue |
| 748 | { |
| 749 | |
| 750 | UniValue txs = request.params[0].get_array(); |
| 751 | std::vector<CMutableTransaction> txVariants(txs.size()); |
| 752 | |
| 753 | for (unsigned int idx = 0; idx < txs.size(); idx++) { |
| 754 | if (!DecodeHexTx(txVariants[idx], txs[idx].get_str())) { |
| 755 | throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed for tx %d. Make sure the tx has at least one input.", idx)); |
| 756 | } |
| 757 | } |
| 758 | |
| 759 | if (txVariants.empty()) { |
| 760 | throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Missing transactions"); |
| 761 | } |
| 762 | |
| 763 | // mergedTx will end up with all the signatures; it |
| 764 | // starts as a clone of the rawtx: |
| 765 | CMutableTransaction mergedTx(txVariants[0]); |
| 766 | |
| 767 | // Fetch previous transactions (inputs): |
| 768 | CCoinsView viewDummy; |
| 769 | CCoinsViewCache view(&viewDummy); |
| 770 | { |
| 771 | NodeContext& node = EnsureAnyNodeContext(request.context); |
| 772 | const CTxMemPool& mempool = EnsureMemPool(node); |
| 773 | ChainstateManager& chainman = EnsureChainman(node); |
| 774 | LOCK2(cs_main, mempool.cs); |
| 775 | CCoinsViewCache &viewChain = chainman.ActiveChainstate().CoinsTip(); |
| 776 | CCoinsViewMemPool viewMempool(&viewChain, mempool); |
| 777 | view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view |
| 778 | |
| 779 | for (const CTxIn& txin : mergedTx.vin) { |
| 780 | view.AccessCoin(txin.prevout); // Load entries from viewChain into view; can fail. |
| 781 | } |
| 782 | |
| 783 | view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long |
| 784 | } |
| 785 |
nothing calls this directly
no test coverage detected