diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -1708,6 +1708,66 @@ return EncodeBase64((uint8_t *)ssTx.data(), ssTx.size()); } +UniValue utxoupdatepsbt(const Config &config, const JSONRPCRequest &request) { + if (request.fHelp || request.params.size() != 1) { + throw std::runtime_error(RPCHelpMan{ + "utxoupdatepsbt", + "\nUpdates a PSBT with witness UTXOs retrieved from the UTXO set " + "or the mempool.\n", + {{"psbt", RPCArg::Type::STR, + /* opt */ false, /* default_val */ "", + "A base64 string of a PSBT"}}, + RPCResult{" \"psbt\" (string) The base64-encoded " + "partially signed transaction with inputs updated\n"}, + RPCExamples{HelpExampleCli("utxoupdatepsbt", "\"psbt\"")}} + .ToString()); + } + + RPCTypeCheck(request.params, {UniValue::VSTR}, true); + + // Unserialize the transactions + PartiallySignedTransaction psbtx; + std::string error; + if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, + strprintf("TX decode failed %s", error)); + } + + // Fetch previous transactions (inputs): + CCoinsView viewDummy; + CCoinsViewCache view(&viewDummy); + { + LOCK2(cs_main, g_mempool.cs); + CCoinsViewCache &viewChain = *pcoinsTip; + CCoinsViewMemPool viewMempool(&viewChain, g_mempool); + // temporarily switch cache backend to db+mempool view + view.SetBackend(viewMempool); + + for (const CTxIn &txin : psbtx.tx->vin) { + // Load entries from viewChain into view; can fail. + view.AccessCoin(txin.prevout); + } + + // switch back to avoid locking mempool for too long + view.SetBackend(viewDummy); + } + + // Fill the inputs + for (size_t i = 0; i < psbtx.tx->vin.size(); ++i) { + PSBTInput &input = psbtx.inputs.at(i); + + if (!input.utxo.IsNull()) { + continue; + } + + input.utxo = view.AccessCoin(psbtx.tx->vin[i].prevout).GetTxOut(); + } + + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + return EncodeBase64((uint8_t *)ssTx.data(), ssTx.size()); +} + // clang-format off static const CRPCCommand commands[] = { // category name actor (function) argNames @@ -1725,7 +1785,7 @@ { "rawtransactions", "finalizepsbt", finalizepsbt, {"psbt", "extract"} }, { "rawtransactions", "createpsbt", createpsbt, {"inputs","outputs","locktime"} }, { "rawtransactions", "converttopsbt", converttopsbt, {"hexstring","permitsigdata"} }, - + { "rawtransactions", "utxoupdatepsbt", utxoupdatepsbt, {"psbt"} }, { "blockchain", "gettxoutproof", gettxoutproof, {"txids", "blockhash"} }, { "blockchain", "verifytxoutproof", verifytxoutproof, {"proof"} }, }; diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -230,6 +230,25 @@ [], [{p2pkh: 1}], 0, {"includeWatching": True}, True) self.nodes[0].decodepsbt(psbt['psbt']) + # Send to all types of addresses + addr1 = self.nodes[1].getnewaddress("") # originally bech32 + txid1 = self.nodes[0].sendtoaddress(addr1, 11) + vout1 = find_output(self.nodes[0], txid1, 11) + addr2 = self.nodes[1].getnewaddress("") # originally legacy + txid2 = self.nodes[0].sendtoaddress(addr2, 11) + vout2 = find_output(self.nodes[0], txid2, 11) + addr3 = self.nodes[1].getnewaddress("") # originally p2sh-segwit + txid3 = self.nodes[0].sendtoaddress(addr3, 11) + vout3 = find_output(self.nodes[0], txid3, 11) + self.sync_all() + + # Update a PSBT with UTXOs from the node + psbt = self.nodes[1].createpsbt([{"txid": txid1, "vout": vout1}, {"txid": txid2, "vout": vout2}, { + "txid": txid3, "vout": vout3}], {self.nodes[0].getnewaddress(): 32.999}) + decoded = self.nodes[1].decodepsbt(psbt) + updated = self.nodes[1].utxoupdatepsbt(psbt) + decoded = self.nodes[1].decodepsbt(updated) + if __name__ == '__main__': PSBTTest().main()