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)
|
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(
|
throw std::runtime_error(
|
||||||
RPCHelpMan{"utxoupdatepsbt",
|
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 {
|
RPCResult {
|
||||||
" \"psbt\" (string) The base64-encoded partially signed transaction with inputs updated\n"
|
" \"psbt\" (string) The base64-encoded partially signed transaction with inputs updated\n"
|
||||||
|
@ -1505,7 +1512,7 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request)
|
||||||
}}.ToString());
|
}}.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
RPCTypeCheck(request.params, {UniValue::VSTR}, true);
|
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}, true);
|
||||||
|
|
||||||
// Unserialize the transactions
|
// Unserialize the transactions
|
||||||
PartiallySignedTransaction psbtx;
|
PartiallySignedTransaction psbtx;
|
||||||
|
@ -1514,6 +1521,17 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request)
|
||||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
|
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):
|
// Fetch previous transactions (inputs):
|
||||||
CCoinsView viewDummy;
|
CCoinsView viewDummy;
|
||||||
CCoinsViewCache view(&viewDummy);
|
CCoinsViewCache view(&viewDummy);
|
||||||
|
@ -1540,9 +1558,19 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request)
|
||||||
|
|
||||||
const Coin& coin = view.AccessCoin(psbtx.tx->vin[i].prevout);
|
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;
|
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);
|
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
|
|
|
@ -314,18 +314,32 @@ class PSBTTest(BitcoinTestFramework):
|
||||||
vout3 = find_output(self.nodes[0], txid3, 11)
|
vout3 = find_output(self.nodes[0], txid3, 11)
|
||||||
self.sync_all()
|
self.sync_all()
|
||||||
|
|
||||||
# Update a PSBT with UTXOs from the node
|
def test_psbt_input_keys(psbt_input, keys):
|
||||||
# Bech32 inputs should be filled with witness UTXO. Other inputs should not be filled because they are non-witness
|
"""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})
|
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)
|
decoded = self.nodes[1].decodepsbt(psbt)
|
||||||
assert "witness_utxo" not in decoded['inputs'][0] and "non_witness_utxo" not in decoded['inputs'][0]
|
test_psbt_input_keys(decoded['inputs'][0], [])
|
||||||
assert "witness_utxo" not in decoded['inputs'][1] and "non_witness_utxo" not in decoded['inputs'][1]
|
test_psbt_input_keys(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'][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)
|
updated = self.nodes[1].utxoupdatepsbt(psbt)
|
||||||
decoded = self.nodes[1].decodepsbt(updated)
|
decoded = self.nodes[1].decodepsbt(updated)
|
||||||
assert "witness_utxo" in decoded['inputs'][0] and "non_witness_utxo" not in decoded['inputs'][0]
|
test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo'])
|
||||||
assert "witness_utxo" not in decoded['inputs'][1] and "non_witness_utxo" not in decoded['inputs'][1]
|
test_psbt_input_keys(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'][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
|
# 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')})
|
psbt1 = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1}], {self.nodes[0].getnewaddress():Decimal('10.999')})
|
||||||
|
|
Loading…
Add table
Reference in a new issue