aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/rpc/rawtransaction.cpp38
-rwxr-xr-xtest/functional/rpc_psbt.py30
2 files changed, 55 insertions, 13 deletions
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 7675ad0109..eabf89bdba 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -1490,12 +1490,19 @@ UniValue converttopsbt(const JSONRPCRequest& request)
UniValue utxoupdatepsbt(const JSONRPCRequest& request)
{
- if (request.fHelp || request.params.size() != 1) {
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) {
throw std::runtime_error(
RPCHelpMan{"utxoupdatepsbt",
- "\nUpdates a PSBT with witness UTXOs retrieved from the UTXO set or the mempool.\n",
+ "\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, the UTXO set or the mempool.\n",
{
- {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"}
+ {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"},
+ {"descriptors", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "An array of either strings or objects", {
+ {"", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"},
+ {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with an output descriptor and extra information", {
+ {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"},
+ {"range", RPCArg::Type::RANGE, "1000", "Up to what index HD chains should be explored (either end or [begin,end])"},
+ }},
+ }},
},
RPCResult {
" \"psbt\" (string) The base64-encoded partially signed transaction with inputs updated\n"
@@ -1505,7 +1512,7 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request)
}}.ToString());
}
- RPCTypeCheck(request.params, {UniValue::VSTR}, true);
+ RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}, true);
// Unserialize the transactions
PartiallySignedTransaction psbtx;
@@ -1514,6 +1521,17 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request)
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
}
+ // Parse descriptors, if any.
+ FlatSigningProvider provider;
+ if (!request.params[1].isNull()) {
+ auto descs = request.params[1].get_array();
+ for (size_t i = 0; i < descs.size(); ++i) {
+ EvalDescriptorStringOrObject(descs[i], provider);
+ }
+ }
+ // We don't actually need private keys further on; hide them as a precaution.
+ HidingSigningProvider public_provider(&provider, /* nosign */ true, /* nobip32derivs */ false);
+
// Fetch previous transactions (inputs):
CCoinsView viewDummy;
CCoinsViewCache view(&viewDummy);
@@ -1540,9 +1558,19 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request)
const Coin& coin = view.AccessCoin(psbtx.tx->vin[i].prevout);
- if (IsSegWitOutput(DUMMY_SIGNING_PROVIDER, coin.out.scriptPubKey)) {
+ if (IsSegWitOutput(provider, coin.out.scriptPubKey)) {
input.witness_utxo = coin.out;
}
+
+ // Update script/keypath information using descriptor data.
+ // Note that SignPSBTInput does a lot more than just constructing ECDSA signatures
+ // we don't actually care about those here, in fact.
+ SignPSBTInput(public_provider, psbtx, i, /* sighash_type */ 1);
+ }
+
+ // Update script/keypath information using descriptor data.
+ for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
+ UpdatePSBTOutput(public_provider, psbtx, i);
}
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index 8a7ea7aa58..a63b52c59e 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -314,18 +314,32 @@ class PSBTTest(BitcoinTestFramework):
vout3 = find_output(self.nodes[0], txid3, 11)
self.sync_all()
- # Update a PSBT with UTXOs from the node
- # Bech32 inputs should be filled with witness UTXO. Other inputs should not be filled because they are non-witness
+ def test_psbt_input_keys(psbt_input, keys):
+ """Check that the psbt input has only the expected keys."""
+ assert_equal(set(keys), set(psbt_input.keys()))
+
+ # Create a PSBT. None of the inputs are filled initially
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)
- assert "witness_utxo" not in decoded['inputs'][0] and "non_witness_utxo" not in decoded['inputs'][0]
- assert "witness_utxo" not in decoded['inputs'][1] and "non_witness_utxo" not in decoded['inputs'][1]
- assert "witness_utxo" not in decoded['inputs'][2] and "non_witness_utxo" not in decoded['inputs'][2]
+ test_psbt_input_keys(decoded['inputs'][0], [])
+ test_psbt_input_keys(decoded['inputs'][1], [])
+ test_psbt_input_keys(decoded['inputs'][2], [])
+
+ # Update a PSBT with UTXOs from the node
+ # Bech32 inputs should be filled with witness UTXO. Other inputs should not be filled because they are non-witness
updated = self.nodes[1].utxoupdatepsbt(psbt)
decoded = self.nodes[1].decodepsbt(updated)
- assert "witness_utxo" in decoded['inputs'][0] and "non_witness_utxo" not in decoded['inputs'][0]
- assert "witness_utxo" not in decoded['inputs'][1] and "non_witness_utxo" not in decoded['inputs'][1]
- assert "witness_utxo" not in decoded['inputs'][2] and "non_witness_utxo" not in decoded['inputs'][2]
+ test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo'])
+ test_psbt_input_keys(decoded['inputs'][1], [])
+ test_psbt_input_keys(decoded['inputs'][2], [])
+
+ # Try again, now while providing descriptors, making P2SH-segwit work, and causing bip32_derivs and redeem_script to be filled in
+ descs = [self.nodes[1].getaddressinfo(addr)['desc'] for addr in [addr1,addr2,addr3]]
+ updated = self.nodes[1].utxoupdatepsbt(psbt, descs)
+ decoded = self.nodes[1].decodepsbt(updated)
+ test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'bip32_derivs'])
+ test_psbt_input_keys(decoded['inputs'][1], [])
+ test_psbt_input_keys(decoded['inputs'][2], ['witness_utxo', 'bip32_derivs', 'redeem_script'])
# Two PSBTs with a common input should not be joinable
psbt1 = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1}], {self.nodes[0].getnewaddress():Decimal('10.999')})