mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-01 09:35:52 -05:00
Merge 87ceb610a7
into 85f96b01b7
This commit is contained in:
commit
077e1f97c1
5 changed files with 115 additions and 5 deletions
|
@ -32,6 +32,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||
{ "setmocktime", 0, "timestamp" },
|
||||
{ "mockscheduler", 0, "delta_time" },
|
||||
{ "utxoupdatepsbt", 1, "descriptors" },
|
||||
{ "utxoupdatepsbt", 2, "prevtxs" },
|
||||
{ "generatetoaddress", 0, "nblocks" },
|
||||
{ "generatetoaddress", 2, "maxtries" },
|
||||
{ "generatetodescriptor", 0, "num_blocks" },
|
||||
|
@ -176,6 +177,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||
{ "descriptorprocesspsbt", 1, "descriptors"},
|
||||
{ "descriptorprocesspsbt", 3, "bip32derivs" },
|
||||
{ "descriptorprocesspsbt", 4, "finalize" },
|
||||
{ "descriptorprocesspsbt", 5, "prevtxs" },
|
||||
{ "createpsbt", 0, "inputs" },
|
||||
{ "createpsbt", 1, "outputs" },
|
||||
{ "createpsbt", 2, "locktime" },
|
||||
|
|
|
@ -163,7 +163,7 @@ static std::vector<RPCArg> CreateTxDoc()
|
|||
|
||||
// Update PSBT with information from the mempool, the UTXO set, the txindex, and the provided descriptors.
|
||||
// Optionally, sign the inputs that we can using information from the descriptors.
|
||||
PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std::any& context, const HidingSigningProvider& provider, int sighash_type, bool finalize)
|
||||
PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std::any& context, const HidingSigningProvider& provider, int sighash_type, const std::optional<std::vector<CTransactionRef>>& prev_txs, bool finalize)
|
||||
{
|
||||
// Unserialize the transactions
|
||||
PartiallySignedTransaction psbtx;
|
||||
|
@ -180,8 +180,20 @@ PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std
|
|||
// the full transaction isn't found
|
||||
std::map<COutPoint, Coin> coins;
|
||||
|
||||
// Filter prev_txs to unique txids and create lookup
|
||||
std::map<Txid, CTransactionRef> prev_tx_map;
|
||||
if (prev_txs.has_value()) {
|
||||
for (const auto& tx : prev_txs.value()) {
|
||||
const auto txid = tx->GetHash();
|
||||
if (prev_tx_map.count(txid)) {
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("Duplicate txids in prev_txs %s", txid.GetHex()));
|
||||
}
|
||||
prev_tx_map[txid] = tx;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch previous transactions:
|
||||
// First, look in the txindex and the mempool
|
||||
// First, look in prev_txs, the txindex, and the mempool
|
||||
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
||||
PSBTInput& psbt_input = psbtx.inputs.at(i);
|
||||
const CTxIn& tx_in = psbtx.tx->vin.at(i);
|
||||
|
@ -191,8 +203,17 @@ PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std
|
|||
|
||||
CTransactionRef tx;
|
||||
|
||||
// Look in the txindex
|
||||
if (g_txindex) {
|
||||
// First look in provided dependant transactions
|
||||
if (prev_tx_map.contains(tx_in.prevout.hash)) {
|
||||
tx = prev_tx_map[tx_in.prevout.hash];
|
||||
// Sanity check it has an output
|
||||
// at the right index
|
||||
if (tx_in.prevout.n >= tx->vout.size()) {
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("Previous tx has too few outputs for PSBT input %s", tx->GetHash().GetHex()));
|
||||
}
|
||||
}
|
||||
// Then look in the txindex
|
||||
if (!tx && g_txindex) {
|
||||
uint256 block_hash;
|
||||
g_txindex->FindTx(tx_in.prevout.hash, block_hash, tx);
|
||||
}
|
||||
|
@ -1656,7 +1677,7 @@ static RPCHelpMan converttopsbt()
|
|||
static RPCHelpMan utxoupdatepsbt()
|
||||
{
|
||||
return RPCHelpMan{"utxoupdatepsbt",
|
||||
"\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, the UTXO set, txindex, or the mempool.\n",
|
||||
"\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, provided dependant transactions, the UTXO set, txindex, or the mempool.\n",
|
||||
{
|
||||
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"},
|
||||
{"descriptors", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "An array of either strings or objects", {
|
||||
|
@ -1666,6 +1687,9 @@ static RPCHelpMan utxoupdatepsbt()
|
|||
{"range", RPCArg::Type::RANGE, RPCArg::Default{1000}, "Up to what index HD chains should be explored (either end or [begin,end])"},
|
||||
}},
|
||||
}},
|
||||
{"prevtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "An array of dependant serialized transactions as hex", {
|
||||
{"", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A serialized previous transaction in hex"},
|
||||
}},
|
||||
},
|
||||
RPCResult {
|
||||
RPCResult::Type::STR, "", "The base64-encoded partially signed transaction with inputs updated"
|
||||
|
@ -1684,12 +1708,18 @@ static RPCHelpMan utxoupdatepsbt()
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<CTransactionRef> prev_txns;
|
||||
if (!request.params[2].isNull()) {
|
||||
prev_txns = ParseTransactionVector(request.params[2]);
|
||||
}
|
||||
|
||||
// We don't actually need private keys further on; hide them as a precaution.
|
||||
const PartiallySignedTransaction& psbtx = ProcessPSBT(
|
||||
request.params[0].get_str(),
|
||||
request.context,
|
||||
HidingSigningProvider(&provider, /*hide_secret=*/true, /*hide_origin=*/false),
|
||||
/*sighash_type=*/SIGHASH_ALL,
|
||||
/*prev_txs=*/prev_txns,
|
||||
/*finalize=*/false);
|
||||
|
||||
DataStream ssTx{};
|
||||
|
@ -1933,6 +1963,9 @@ RPCHelpMan descriptorprocesspsbt()
|
|||
" \"SINGLE|ANYONECANPAY\""},
|
||||
{"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"},
|
||||
{"finalize", RPCArg::Type::BOOL, RPCArg::Default{true}, "Also finalize inputs if possible"},
|
||||
{"prevtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "An array of dependant serialized transactions as hex", {
|
||||
{"", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A serialized previous transaction in hex"},
|
||||
}},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
|
@ -1960,11 +1993,17 @@ RPCHelpMan descriptorprocesspsbt()
|
|||
bool bip32derivs = request.params[3].isNull() ? true : request.params[3].get_bool();
|
||||
bool finalize = request.params[4].isNull() ? true : request.params[4].get_bool();
|
||||
|
||||
std::vector<CTransactionRef> prev_txns;
|
||||
if (!request.params[5].isNull()) {
|
||||
prev_txns = ParseTransactionVector(request.params[5]);
|
||||
}
|
||||
|
||||
const PartiallySignedTransaction& psbtx = ProcessPSBT(
|
||||
request.params[0].get_str(),
|
||||
request.context,
|
||||
HidingSigningProvider(&provider, /*hide_secret=*/false, !bip32derivs),
|
||||
sighash_type,
|
||||
/*prev_txs=*/prev_txns,
|
||||
finalize);
|
||||
|
||||
// Check whether or not all of the inputs are now signed
|
||||
|
|
|
@ -1387,6 +1387,23 @@ std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, Fl
|
|||
return ret;
|
||||
}
|
||||
|
||||
std::vector<CTransactionRef> ParseTransactionVector(const UniValue txns_param)
|
||||
{
|
||||
std::vector<CTransactionRef> txns;
|
||||
const UniValue& raw_transactions = txns_param.get_array();
|
||||
txns.reserve(raw_transactions.size());
|
||||
|
||||
for (const auto& rawtx : raw_transactions.getValues()) {
|
||||
CMutableTransaction mtx;
|
||||
if (!DecodeHexTx(mtx, rawtx.get_str())) {
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
|
||||
"TX decode failed: " + rawtx.get_str() + " Make sure the prev tx has at least one input.");
|
||||
}
|
||||
txns.emplace_back(MakeTransactionRef(std::move(mtx)));
|
||||
}
|
||||
return txns;
|
||||
}
|
||||
|
||||
/** Convert a vector of bilingual strings to a UniValue::VARR containing their original untranslated values. */
|
||||
[[nodiscard]] static UniValue BilingualStringsToUniValue(const std::vector<bilingual_str>& bilingual_strings)
|
||||
{
|
||||
|
|
|
@ -153,6 +153,9 @@ std::pair<int64_t, int64_t> ParseDescriptorRange(const UniValue& value);
|
|||
/** Evaluate a descriptor given as a string, or as a {"desc":...,"range":...} object, with default range of 1000. */
|
||||
std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, FlatSigningProvider& provider, const bool expand_priv = false);
|
||||
|
||||
/** Parses a vector of transactions from a univalue array. */
|
||||
std::vector<CTransactionRef> ParseTransactionVector(const UniValue txns_param);
|
||||
|
||||
/**
|
||||
* Serializing JSON objects depends on the outer type. Only arrays and
|
||||
* dictionaries can be nested in json. The top-level outer type is "NONE".
|
||||
|
|
|
@ -210,6 +210,55 @@ class PSBTTest(BitcoinTestFramework):
|
|||
assert_equal(decoded_psbt["tx"]["vout"][changepos]["scriptPubKey"]["type"], expected_type)
|
||||
|
||||
def run_test(self):
|
||||
|
||||
self.log.info("Test that PSBT can have user-provided UTXOs filled and signed")
|
||||
|
||||
# Create 1 parent 1 child chain from same wallet
|
||||
psbtx_parent = self.nodes[0].walletcreatefundedpsbt([], {self.nodes[0].getnewaddress():10})['psbt']
|
||||
processed_parent = self.nodes[0].walletprocesspsbt(psbtx_parent)
|
||||
parent_txinfo = self.nodes[0].decoderawtransaction(processed_parent["hex"])
|
||||
parent_txid = parent_txinfo["txid"]
|
||||
parent_vout = 0 # just take the first output to spend
|
||||
|
||||
psbtx_child = self.nodes[0].createpsbt([{"txid": parent_txid, "vout": parent_vout}], {self.nodes[0].getnewaddress(): parent_txinfo["vout"][0]["value"] - Decimal("0.01")})
|
||||
|
||||
# Can not sign due to lack of utxo
|
||||
res = self.nodes[0].walletprocesspsbt(psbtx_child)
|
||||
assert not res["complete"]
|
||||
|
||||
prev_txs = [processed_parent["hex"]]
|
||||
utxo_updated = self.nodes[0].utxoupdatepsbt(psbt=psbtx_child, prevtxs=prev_txs)
|
||||
res = self.nodes[0].walletprocesspsbt(utxo_updated)
|
||||
assert res["complete"]
|
||||
|
||||
# And descriptorprocesspsbt does the same
|
||||
utxo_updated = self.nodes[0].descriptorprocesspsbt(psbt=psbtx_child, descriptors=[], prevtxs=prev_txs)
|
||||
res = self.nodes[0].walletprocesspsbt(utxo_updated["psbt"])
|
||||
assert res["complete"]
|
||||
|
||||
# Multiple inputs are ok, even if unrelated transactions included
|
||||
prev_txs = [processed_parent["hex"], self.nodes[0].createrawtransaction([], [])]
|
||||
utxo_updated = self.nodes[0].utxoupdatepsbt(psbt=psbtx_child, prevtxs=prev_txs)
|
||||
res = self.nodes[0].walletprocesspsbt(utxo_updated)
|
||||
assert res["complete"]
|
||||
|
||||
# If only irrelevant previous transactions are included, it's a no-op
|
||||
prev_txs = [self.nodes[0].createrawtransaction([], [])]
|
||||
utxo_updated = self.nodes[0].utxoupdatepsbt(psbt=psbtx_child, prevtxs=prev_txs)
|
||||
assert_equal(utxo_updated, psbtx_child)
|
||||
res = self.nodes[0].walletprocesspsbt(utxo_updated)
|
||||
assert not res["complete"]
|
||||
|
||||
# If there's a txid collision, it's rejected
|
||||
prev_txs = [processed_parent["hex"], processed_parent["hex"]]
|
||||
assert_raises_rpc_error(-22, f"Duplicate txids in prev_txs {parent_txid}", self.nodes[0].utxoupdatepsbt, psbt=psbtx_child, prevtxs=prev_txs)
|
||||
|
||||
# Should abort safely if supplied transaction matches txid of prevout, but has insufficient outputs to match with prevout.n
|
||||
psbtx_bad_child = self.nodes[0].createpsbt([{"txid": parent_txid, "vout": len(parent_txinfo["vout"])}], {self.nodes[0].getnewaddress(): parent_txinfo["vout"][0]["value"] - Decimal("0.01")})
|
||||
|
||||
prev_txs = [processed_parent["hex"]]
|
||||
assert_raises_rpc_error(-22, f"Previous tx has too few outputs for PSBT input {parent_txid}", self.nodes[0].utxoupdatepsbt, psbt=psbtx_bad_child, prevtxs=prev_txs)
|
||||
|
||||
# Create and fund a raw tx for sending 10 BTC
|
||||
psbtx1 = self.nodes[0].walletcreatefundedpsbt([], {self.nodes[2].getnewaddress():10})['psbt']
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue