Skip to content

Commit 7344a7b

Browse files
committed
Implement utxoupdatepsbt RPC and tests
1 parent e3b1c7a commit 7344a7b

File tree

2 files changed

+93
-1
lines changed

2 files changed

+93
-1
lines changed

src/rpc/rawtransaction.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1691,6 +1691,70 @@ UniValue converttopsbt(const JSONRPCRequest& request)
16911691
return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size());
16921692
}
16931693

1694+
UniValue utxoupdatepsbt(const JSONRPCRequest& request)
1695+
{
1696+
if (request.fHelp || request.params.size() != 1) {
1697+
throw std::runtime_error(
1698+
RPCHelpMan{"utxoupdatepsbt",
1699+
"\nUpdates a PSBT with witness UTXOs retrieved from the UTXO set or the mempool.\n",
1700+
{
1701+
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"}
1702+
},
1703+
RPCResult {
1704+
" \"psbt\" (string) The base64-encoded partially signed transaction with inputs updated\n"
1705+
},
1706+
RPCExamples {
1707+
HelpExampleCli("utxoupdatepsbt", "\"psbt\"")
1708+
}}.ToString());
1709+
}
1710+
1711+
RPCTypeCheck(request.params, {UniValue::VSTR}, true);
1712+
1713+
// Unserialize the transactions
1714+
PartiallySignedTransaction psbtx;
1715+
std::string error;
1716+
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
1717+
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
1718+
}
1719+
1720+
// Fetch previous transactions (inputs):
1721+
CCoinsView viewDummy;
1722+
CCoinsViewCache view(&viewDummy);
1723+
{
1724+
LOCK2(cs_main, mempool.cs);
1725+
CCoinsViewCache &viewChain = *pcoinsTip;
1726+
CCoinsViewMemPool viewMempool(&viewChain, mempool);
1727+
view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
1728+
1729+
for (const CTxIn& txin : psbtx.tx->vin) {
1730+
view.AccessCoin(txin.prevout); // Load entries from viewChain into view; can fail.
1731+
}
1732+
1733+
view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long
1734+
}
1735+
1736+
// Fill the inputs
1737+
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
1738+
PSBTInput& input = psbtx.inputs.at(i);
1739+
1740+
if (input.non_witness_utxo || !input.witness_utxo.IsNull()) {
1741+
continue;
1742+
}
1743+
1744+
const Coin& coin = view.AccessCoin(psbtx.tx->vin[i].prevout);
1745+
1746+
std::vector<std::vector<unsigned char>> solutions_data;
1747+
txnouttype which_type = Solver(coin.out.scriptPubKey, solutions_data);
1748+
if (which_type == TX_WITNESS_V0_SCRIPTHASH || which_type == TX_WITNESS_V0_KEYHASH || which_type == TX_WITNESS_UNKNOWN) {
1749+
input.witness_utxo = coin.out;
1750+
}
1751+
}
1752+
1753+
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
1754+
ssTx << psbtx;
1755+
return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size());
1756+
}
1757+
16941758
// clang-format off
16951759
static const CRPCCommand commands[] =
16961760
{ // category name actor (function) argNames
@@ -1709,6 +1773,7 @@ static const CRPCCommand commands[] =
17091773
{ "rawtransactions", "finalizepsbt", &finalizepsbt, {"psbt", "extract"} },
17101774
{ "rawtransactions", "createpsbt", &createpsbt, {"inputs","outputs","locktime","replaceable"} },
17111775
{ "rawtransactions", "converttopsbt", &converttopsbt, {"hexstring","permitsigdata","iswitness"} },
1776+
{ "rawtransactions", "utxoupdatepsbt", &utxoupdatepsbt, {"psbt"} },
17121777

17131778
{ "blockchain", "gettxoutproof", &gettxoutproof, {"txids", "blockhash"} },
17141779
{ "blockchain", "verifytxoutproof", &verifytxoutproof, {"proof"} },

test/functional/rpc_psbt.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def set_test_params(self):
2020
self.setup_clean_chain = False
2121
self.num_nodes = 3
2222
# TODO: remove -txindex. Currently required for getrawtransaction call.
23-
self.extra_args = [[], ["-txindex"], ["-txindex"]]
23+
self.extra_args = [["-txindex"], ["-txindex"], ["-txindex"]]
2424

2525
def skip_test_if_missing_module(self):
2626
self.skip_if_no_wallet()
@@ -296,5 +296,32 @@ def run_test(self):
296296
# Test decoding error: invalid base64
297297
assert_raises_rpc_error(-22, "TX decode failed invalid base64", self.nodes[0].decodepsbt, ";definitely not base64;")
298298

299+
# Send to all types of addresses
300+
addr1 = self.nodes[1].getnewaddress("", "bech32")
301+
txid1 = self.nodes[0].sendtoaddress(addr1, 11)
302+
vout1 = find_output(self.nodes[0], txid1, 11)
303+
addr2 = self.nodes[1].getnewaddress("", "legacy")
304+
txid2 = self.nodes[0].sendtoaddress(addr2, 11)
305+
vout2 = find_output(self.nodes[0], txid2, 11)
306+
addr3 = self.nodes[1].getnewaddress("", "p2sh-segwit")
307+
txid3 = self.nodes[0].sendtoaddress(addr3, 11)
308+
vout3 = find_output(self.nodes[0], txid3, 11)
309+
self.sync_all()
310+
311+
# Update a PSBT with UTXOs from the node
312+
# Bech32 inputs should be filled with witness UTXO. Other inputs should not be filled because they are non-witness
313+
psbt = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1},{"txid":txid2, "vout":vout2},{"txid":txid3, "vout":vout3}], {self.nodes[0].getnewaddress():32.999})
314+
decoded = self.nodes[1].decodepsbt(psbt)
315+
assert "witness_utxo" not in decoded['inputs'][0] and "non_witness_utxo" not in decoded['inputs'][0]
316+
assert "witness_utxo" not in decoded['inputs'][1] and "non_witness_utxo" not in decoded['inputs'][1]
317+
assert "witness_utxo" not in decoded['inputs'][2] and "non_witness_utxo" not in decoded['inputs'][2]
318+
updated = self.nodes[1].utxoupdatepsbt(psbt)
319+
decoded = self.nodes[1].decodepsbt(updated)
320+
assert "witness_utxo" in decoded['inputs'][0] and "non_witness_utxo" not in decoded['inputs'][0]
321+
assert "witness_utxo" not in decoded['inputs'][1] and "non_witness_utxo" not in decoded['inputs'][1]
322+
assert "witness_utxo" not in decoded['inputs'][2] and "non_witness_utxo" not in decoded['inputs'][2]
323+
324+
325+
299326
if __name__ == '__main__':
300327
PSBTTest().main()

0 commit comments

Comments
 (0)