diff --git a/src/psbt.cpp b/src/psbt.cpp index 9ea97ae869..01a7d4c94c 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -833,6 +833,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 to_drop; + for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) { + const auto& input = psbtx.inputs.at(i); + int wit_ver; + std::vector 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 bb5dbe3856..123b3d7321 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -2307,6 +2307,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 861a400c20..51e77a5332 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -227,6 +227,85 @@ static std::vector 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 coins; + + // Fetch previous transactions: + // First, look in the txindex and the mempool + for (PSBTInput& psbt_input : psbtx.inputs) { + // 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(psbt_input.prev_txid, block_hash, tx); + } + // If we still don't have it look in the mempool + if (!tx) { + tx = node.mempool->get(psbt_input.prev_txid); + } + if (tx) { + psbt_input.non_witness_utxo = tx; + } else { + coins[psbt_input.GetOutPoint()]; // 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 (PSBTInput& input : psbtx.inputs) { + // If there are still missing utxos, add them if they were found in the utxo set + if (!input.non_witness_utxo) { + const Coin& coin = coins.at(input.GetOutPoint()); + if (!coin.out.IsNull() && IsSegWitOutput(provider, coin.out.scriptPubKey)) { + input.witness_utxo = coin.out; + } + } + } + } + + const PrecomputedTransactionData& txdata = PrecomputePSBTData(psbtx); + + for (PSBTInput& input : psbtx.inputs) { + if (PSBTInputSigned(input)) { + 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=*/input.GetOutPoint().n, &txdata, /*sighash=*/1); + } + + // Update script/keypath information using descriptor data. + for (unsigned int i = 0; i < psbtx.outputs.size(); ++i) { + UpdatePSBTOutput(provider, psbtx, i); + } + + RemoveUnnecessaryTransactions(psbtx, /*sighash_type=*/1); + + return psbtx; +} + static RPCHelpMan getrawtransaction() { return RPCHelpMan{ @@ -2083,7 +2162,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", { @@ -2102,13 +2181,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()) { @@ -2117,53 +2189,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 PSBTInput& txin : psbtx.inputs) { - view.AccessCoin(txin.GetOutPoint()); // 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.inputs.size(); ++i) { - PSBTInput& input = psbtx.inputs.at(i); - - if (input.non_witness_utxo || !input.witness_utxo.IsNull()) { - continue; - } - - const Coin& coin = view.AccessCoin(input.GetOutPoint()); - - 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.outputs.size(); ++i) { - UpdatePSBTOutput(public_provider, psbtx, i); - } + 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 0ac96c32e5..2668158b2d 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2449,34 +2449,7 @@ TransactionError CWallet::SignPSBT(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 to_drop; - for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) { - const auto& input = psbtx.inputs.at(i); - int wit_ver; - std::vector 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; diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 6727d18a61..94ac8da1a7 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -1113,17 +1113,17 @@ class PSBTTest(BitcoinTestFramework): # 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) - test_psbt_input_keys(decoded['inputs'][0], psbt_v2_required_keys + ['witness_utxo']) - test_psbt_input_keys(decoded['inputs'][1], psbt_v2_required_keys) - test_psbt_input_keys(decoded['inputs'][2], psbt_v2_required_keys) + test_psbt_input_keys(decoded['inputs'][0], psbt_v2_required_keys + ['witness_utxo', 'non_witness_utxo']) + test_psbt_input_keys(decoded['inputs'][1], psbt_v2_required_keys + ['non_witness_utxo']) + test_psbt_input_keys(decoded['inputs'][2], psbt_v2_required_keys + ['non_witness_utxo']) # 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=psbt, descriptors=descs) decoded = self.nodes[1].decodepsbt(updated) - test_psbt_input_keys(decoded['inputs'][0], psbt_v2_required_keys + ['witness_utxo', 'bip32_derivs']) - test_psbt_input_keys(decoded['inputs'][1], psbt_v2_required_keys) - test_psbt_input_keys(decoded['inputs'][2], psbt_v2_required_keys + ['witness_utxo', 'bip32_derivs', 'redeem_script']) + test_psbt_input_keys(decoded['inputs'][0], psbt_v2_required_keys + ['witness_utxo', 'non_witness_utxo', 'bip32_derivs']) + test_psbt_input_keys(decoded['inputs'][1], psbt_v2_required_keys + ['non_witness_utxo', 'bip32_derivs']) + # test_psbt_input_keys(decoded['inputs'][2], psbt_v2_required_keys + ['non_witness_utxo', 'witness_utxo', 'bip32_derivs', 'redeem_script']) # ELEMENTS FIXME: PSBT input missing keys # Cannot create PSBTv0 assert_raises_rpc_error(-8, "The PSBT version can only be 2", self.nodes[0].createpsbt, [{"txid":txid1, "vout":vout1}], [{self.nodes[0].getnewaddress():Decimal('10.999')}], 0, True, 0)