aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/psbt.cpp32
-rw-r--r--src/psbt.h3
-rw-r--r--src/rpc/rawtransaction.cpp145
-rw-r--r--src/wallet/wallet.cpp29
4 files changed, 127 insertions, 82 deletions
diff --git a/src/psbt.cpp b/src/psbt.cpp
index fe45f2318c..009ed966ed 100644
--- a/src/psbt.cpp
+++ b/src/psbt.cpp
@@ -442,6 +442,38 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
return sig_complete;
}
+void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx, const int& sighash_type)
+{
+ // Only drop non_witness_utxos if sighash_type != SIGHASH_ANYONECANPAY
+ if ((sighash_type & 0x80) != SIGHASH_ANYONECANPAY) {
+ // Figure out if any non_witness_utxos should be dropped
+ std::vector<unsigned int> to_drop;
+ for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
+ const auto& input = psbtx.inputs.at(i);
+ int wit_ver;
+ std::vector<unsigned char> wit_prog;
+ if (input.witness_utxo.IsNull() || !input.witness_utxo.scriptPubKey.IsWitnessProgram(wit_ver, wit_prog)) {
+ // There's a non-segwit input or Segwit v0, so we cannot drop any witness_utxos
+ to_drop.clear();
+ break;
+ }
+ if (wit_ver == 0) {
+ // Segwit v0, so we cannot drop any non_witness_utxos
+ to_drop.clear();
+ break;
+ }
+ if (input.non_witness_utxo) {
+ to_drop.push_back(i);
+ }
+ }
+
+ // Drop the non_witness_utxos that we can drop
+ for (unsigned int i : to_drop) {
+ psbtx.inputs.at(i).non_witness_utxo = nullptr;
+ }
+ }
+}
+
bool FinalizePSBT(PartiallySignedTransaction& psbtx)
{
// Finalize input signatures -- in case we have partial signatures that add up to a complete
diff --git a/src/psbt.h b/src/psbt.h
index c497584f36..9464b10268 100644
--- a/src/psbt.h
+++ b/src/psbt.h
@@ -1231,6 +1231,9 @@ bool PSBTInputSignedAndVerified(const PartiallySignedTransaction psbt, unsigned
**/
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool finalize = true);
+/** Reduces the size of the PSBT by dropping unnecessary `non_witness_utxos` (i.e. complete previous transactions) from a psbt when all inputs are segwit v1. */
+void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx, const int& sighash_type);
+
/** Counts the unsigned inputs of a PSBT. */
size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt);
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 21d49fda9d..4a918cbd42 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -172,6 +172,91 @@ static std::vector<RPCArg> CreateTxDoc()
};
}
+// Update PSBT with information from the mempool, the UTXO set, the txindex, and the provided descriptors
+PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std::any& context, const HidingSigningProvider& provider)
+{
+ // Unserialize the transactions
+ PartiallySignedTransaction psbtx;
+ std::string error;
+ if (!DecodeBase64PSBT(psbtx, psbt_string, error)) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
+ }
+
+ if (g_txindex) g_txindex->BlockUntilSyncedToCurrentChain();
+ const NodeContext& node = EnsureAnyNodeContext(context);
+
+ // If we can't find the corresponding full transaction for all of our inputs,
+ // this will be used to find just the utxos for the segwit inputs for which
+ // the full transaction isn't found
+ std::map<COutPoint, Coin> coins;
+
+ // Fetch previous transactions:
+ // First, look in 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);
+
+ // The `non_witness_utxo` is the whole previous transaction
+ if (psbt_input.non_witness_utxo) continue;
+
+ CTransactionRef tx;
+
+ // Look in the txindex
+ if (g_txindex) {
+ uint256 block_hash;
+ g_txindex->FindTx(tx_in.prevout.hash, block_hash, tx);
+ }
+ // If we still don't have it look in the mempool
+ if (!tx) {
+ tx = node.mempool->get(tx_in.prevout.hash);
+ }
+ if (tx) {
+ psbt_input.non_witness_utxo = tx;
+ } else {
+ coins[tx_in.prevout]; // Create empty map entry keyed by prevout
+ }
+ }
+
+ // If we still haven't found all of the inputs, look for the missing ones in the utxo set
+ if (!coins.empty()) {
+ FindCoins(node, coins);
+ for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
+ PSBTInput& input = psbtx.inputs.at(i);
+
+ // If there are still missing utxos, add them if they were found in the utxo set
+ if (!input.non_witness_utxo) {
+ const CTxIn& tx_in = psbtx.tx->vin.at(i);
+ const Coin& coin = coins.at(tx_in.prevout);
+ if (!coin.out.IsNull() && IsSegWitOutput(provider, coin.out.scriptPubKey)) {
+ input.witness_utxo = coin.out;
+ }
+ }
+ }
+ }
+
+ const PrecomputedTransactionData& txdata = PrecomputePSBTData(psbtx);
+
+ for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
+ if (PSBTInputSigned(psbtx.inputs.at(i))) {
+ continue;
+ }
+
+ // 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(provider, psbtx, /*index=*/i, &txdata, /*sighash=*/1);
+ }
+
+ // Update script/keypath information using descriptor data.
+ for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
+ UpdatePSBTOutput(provider, psbtx, i);
+ }
+
+ RemoveUnnecessaryTransactions(psbtx, /*sighash_type=*/1);
+
+ return psbtx;
+}
+
static RPCHelpMan getrawtransaction()
{
return RPCHelpMan{
@@ -1580,7 +1665,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 or the mempool.\n",
+ "\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, 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", {
@@ -1599,13 +1684,6 @@ static RPCHelpMan utxoupdatepsbt()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- // Unserialize the transactions
- PartiallySignedTransaction psbtx;
- std::string error;
- if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
- throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
- }
-
// Parse descriptors, if any.
FlatSigningProvider provider;
if (!request.params[1].isNull()) {
@@ -1614,53 +1692,12 @@ static RPCHelpMan utxoupdatepsbt()
EvalDescriptorStringOrObject(descs[i], provider);
}
}
- // We don't actually need private keys further on; hide them as a precaution.
- HidingSigningProvider public_provider(&provider, /*hide_secret=*/true, /*hide_origin=*/false);
-
- // Fetch previous transactions (inputs):
- CCoinsView viewDummy;
- CCoinsViewCache view(&viewDummy);
- {
- NodeContext& node = EnsureAnyNodeContext(request.context);
- const CTxMemPool& mempool = EnsureMemPool(node);
- ChainstateManager& chainman = EnsureChainman(node);
- LOCK2(cs_main, mempool.cs);
- CCoinsViewCache &viewChain = chainman.ActiveChainstate().CoinsTip();
- CCoinsViewMemPool viewMempool(&viewChain, mempool);
- view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
-
- for (const CTxIn& txin : psbtx.tx->vin) {
- view.AccessCoin(txin.prevout); // Load entries from viewChain into view; can fail.
- }
-
- view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long
- }
-
- // Fill the inputs
- const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
- for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
- PSBTInput& input = psbtx.inputs.at(i);
- if (input.non_witness_utxo || !input.witness_utxo.IsNull()) {
- continue;
- }
-
- const Coin& coin = view.AccessCoin(psbtx.tx->vin[i].prevout);
-
- 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, &txdata, /*sighash=*/1);
- }
-
- // Update script/keypath information using descriptor data.
- for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
- UpdatePSBTOutput(public_provider, psbtx, i);
- }
+ // 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));
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 1095529188..56bd25b90a 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -2184,34 +2184,7 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp
}
}
- // Only drop non_witness_utxos if sighash_type != SIGHASH_ANYONECANPAY
- if ((sighash_type & 0x80) != SIGHASH_ANYONECANPAY) {
- // Figure out if any non_witness_utxos should be dropped
- std::vector<unsigned int> to_drop;
- for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
- const auto& input = psbtx.inputs.at(i);
- int wit_ver;
- std::vector<unsigned char> wit_prog;
- if (input.witness_utxo.IsNull() || !input.witness_utxo.scriptPubKey.IsWitnessProgram(wit_ver, wit_prog)) {
- // There's a non-segwit input or Segwit v0, so we cannot drop any witness_utxos
- to_drop.clear();
- break;
- }
- if (wit_ver == 0) {
- // Segwit v0, so we cannot drop any non_witness_utxos
- to_drop.clear();
- break;
- }
- if (input.non_witness_utxo) {
- to_drop.push_back(i);
- }
- }
-
- // Drop the non_witness_utxos that we can drop
- for (unsigned int i : to_drop) {
- psbtx.inputs.at(i).non_witness_utxo = nullptr;
- }
- }
+ RemoveUnnecessaryTransactions(psbtx, sighash_type);
// Complete if every input is now signed
complete = true;