Skip to content

Commit 26fe9b9

Browse files
committed
Add support for descriptors to utxoupdatepsbt
This adds a descriptors argument to the utxoupdatepsbt RPC. This means: * Input and output scripts and keys will be filled in when known * P2SH-witness outputs will be filled in from the UTXO set when a descriptor is provided to show they're segwit outputs.
1 parent 3135c1a commit 26fe9b9

File tree

2 files changed

+55
-13
lines changed

2 files changed

+55
-13
lines changed

src/rpc/rawtransaction.cpp

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1490,12 +1490,19 @@ UniValue converttopsbt(const JSONRPCRequest& request)
14901490

14911491
UniValue utxoupdatepsbt(const JSONRPCRequest& request)
14921492
{
1493-
if (request.fHelp || request.params.size() != 1) {
1493+
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) {
14941494
throw std::runtime_error(
14951495
RPCHelpMan{"utxoupdatepsbt",
1496-
"\nUpdates a PSBT with witness UTXOs retrieved from the UTXO set or the mempool.\n",
1496+
"\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, the UTXO set or the mempool.\n",
14971497
{
1498-
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"}
1498+
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"},
1499+
{"descriptors", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "An array of either strings or objects", {
1500+
{"", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"},
1501+
{"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with an output descriptor and extra information", {
1502+
{"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"},
1503+
{"range", RPCArg::Type::RANGE, "1000", "Up to what index HD chains should be explored (either end or [begin,end])"},
1504+
}},
1505+
}},
14991506
},
15001507
RPCResult {
15011508
" \"psbt\" (string) The base64-encoded partially signed transaction with inputs updated\n"
@@ -1505,7 +1512,7 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request)
15051512
}}.ToString());
15061513
}
15071514

1508-
RPCTypeCheck(request.params, {UniValue::VSTR}, true);
1515+
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}, true);
15091516

15101517
// Unserialize the transactions
15111518
PartiallySignedTransaction psbtx;
@@ -1514,6 +1521,17 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request)
15141521
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
15151522
}
15161523

1524+
// Parse descriptors, if any.
1525+
FlatSigningProvider provider;
1526+
if (!request.params[1].isNull()) {
1527+
auto descs = request.params[1].get_array();
1528+
for (size_t i = 0; i < descs.size(); ++i) {
1529+
EvalDescriptorStringOrObject(descs[i], provider);
1530+
}
1531+
}
1532+
// We don't actually need private keys further on; hide them as a precaution.
1533+
HidingSigningProvider public_provider(&provider, /* nosign */ true, /* nobip32derivs */ false);
1534+
15171535
// Fetch previous transactions (inputs):
15181536
CCoinsView viewDummy;
15191537
CCoinsViewCache view(&viewDummy);
@@ -1540,9 +1558,19 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request)
15401558

15411559
const Coin& coin = view.AccessCoin(psbtx.tx->vin[i].prevout);
15421560

1543-
if (IsSegWitOutput(DUMMY_SIGNING_PROVIDER, coin.out.scriptPubKey)) {
1561+
if (IsSegWitOutput(provider, coin.out.scriptPubKey)) {
15441562
input.witness_utxo = coin.out;
15451563
}
1564+
1565+
// Update script/keypath information using descriptor data.
1566+
// Note that SignPSBTInput does a lot more than just constructing ECDSA signatures
1567+
// we don't actually care about those here, in fact.
1568+
SignPSBTInput(public_provider, psbtx, i, /* sighash_type */ 1);
1569+
}
1570+
1571+
// Update script/keypath information using descriptor data.
1572+
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
1573+
UpdatePSBTOutput(public_provider, psbtx, i);
15461574
}
15471575

15481576
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);

test/functional/rpc_psbt.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -314,18 +314,32 @@ def run_test(self):
314314
vout3 = find_output(self.nodes[0], txid3, 11)
315315
self.sync_all()
316316

317-
# Update a PSBT with UTXOs from the node
318-
# Bech32 inputs should be filled with witness UTXO. Other inputs should not be filled because they are non-witness
317+
def test_psbt_input_keys(psbt_input, keys):
318+
"""Check that the psbt input has only the expected keys."""
319+
assert_equal(set(keys), set(psbt_input.keys()))
320+
321+
# Create a PSBT. None of the inputs are filled initially
319322
psbt = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1},{"txid":txid2, "vout":vout2},{"txid":txid3, "vout":vout3}], {self.nodes[0].getnewaddress():32.999})
320323
decoded = self.nodes[1].decodepsbt(psbt)
321-
assert "witness_utxo" not in decoded['inputs'][0] and "non_witness_utxo" not in decoded['inputs'][0]
322-
assert "witness_utxo" not in decoded['inputs'][1] and "non_witness_utxo" not in decoded['inputs'][1]
323-
assert "witness_utxo" not in decoded['inputs'][2] and "non_witness_utxo" not in decoded['inputs'][2]
324+
test_psbt_input_keys(decoded['inputs'][0], [])
325+
test_psbt_input_keys(decoded['inputs'][1], [])
326+
test_psbt_input_keys(decoded['inputs'][2], [])
327+
328+
# Update a PSBT with UTXOs from the node
329+
# Bech32 inputs should be filled with witness UTXO. Other inputs should not be filled because they are non-witness
324330
updated = self.nodes[1].utxoupdatepsbt(psbt)
325331
decoded = self.nodes[1].decodepsbt(updated)
326-
assert "witness_utxo" in decoded['inputs'][0] and "non_witness_utxo" not in decoded['inputs'][0]
327-
assert "witness_utxo" not in decoded['inputs'][1] and "non_witness_utxo" not in decoded['inputs'][1]
328-
assert "witness_utxo" not in decoded['inputs'][2] and "non_witness_utxo" not in decoded['inputs'][2]
332+
test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo'])
333+
test_psbt_input_keys(decoded['inputs'][1], [])
334+
test_psbt_input_keys(decoded['inputs'][2], [])
335+
336+
# Try again, now while providing descriptors, making P2SH-segwit work, and causing bip32_derivs and redeem_script to be filled in
337+
descs = [self.nodes[1].getaddressinfo(addr)['desc'] for addr in [addr1,addr2,addr3]]
338+
updated = self.nodes[1].utxoupdatepsbt(psbt, descs)
339+
decoded = self.nodes[1].decodepsbt(updated)
340+
test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'bip32_derivs'])
341+
test_psbt_input_keys(decoded['inputs'][1], [])
342+
test_psbt_input_keys(decoded['inputs'][2], ['witness_utxo', 'bip32_derivs', 'redeem_script'])
329343

330344
# Two PSBTs with a common input should not be joinable
331345
psbt1 = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1}], {self.nodes[0].getnewaddress():Decimal('10.999')})

0 commit comments

Comments
 (0)