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.
This commit is contained in:
parent
3135c1a2d2
commit
26fe9b9909
2 changed files with 55 additions and 13 deletions
|
@ -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);
|
||||
|
|
|
@ -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')})
|
||||
|
|
Loading…
Reference in a new issue