Skip to content

Commit 2755aa5

Browse files
committed
Merge bitcoin/bitcoin#25939: rpc: In utxoupdatepsbt also look for the tx in the txindex
6e9f8bb rpc, tests: in `utxoupdatepsbt` also look for the transaction in the txindex (ishaanam) a5b4883 rpc: extract psbt updating logic into ProcessPSBT (ishaanam) Pull request description: Previously the `utxoupdatepsbt` RPC, added in #13932, only updated the inputs spending segwit outputs with the `witness_utxo`, because the full transaction is needed for legacy inputs. Before this RPC looked for just the utxos in the utxo set and the mempool. This PR makes it so that if the user has txindex enabled, then the full transaction is looked for there for all inputs. If it is not found in the txindex or txindex isn't enabled, then the mempool is checked for the full transaction. If the transaction an input is spending from is still not found at that point, then the utxo set is searched and the inputs spending segwit outputs are updated with just the utxo. ACKs for top commit: achow101: ACK 6e9f8bb Xekyo: ACK 6e9f8bb Tree-SHA512: 078db3c37a1ecd5816d80a42e8bd1341e416d661f508fa5fce0f4e1249fefd7b92a0d45f44957781cb69d0953145ef096ecdd4545ada39062be27742402dac6f
2 parents f3f5c97 + 6e9f8bb commit 2755aa5

File tree

5 files changed

+133
-88
lines changed

5 files changed

+133
-88
lines changed

src/psbt.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,38 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
442442
return sig_complete;
443443
}
444444

445+
void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx, const int& sighash_type)
446+
{
447+
// Only drop non_witness_utxos if sighash_type != SIGHASH_ANYONECANPAY
448+
if ((sighash_type & 0x80) != SIGHASH_ANYONECANPAY) {
449+
// Figure out if any non_witness_utxos should be dropped
450+
std::vector<unsigned int> to_drop;
451+
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
452+
const auto& input = psbtx.inputs.at(i);
453+
int wit_ver;
454+
std::vector<unsigned char> wit_prog;
455+
if (input.witness_utxo.IsNull() || !input.witness_utxo.scriptPubKey.IsWitnessProgram(wit_ver, wit_prog)) {
456+
// There's a non-segwit input or Segwit v0, so we cannot drop any witness_utxos
457+
to_drop.clear();
458+
break;
459+
}
460+
if (wit_ver == 0) {
461+
// Segwit v0, so we cannot drop any non_witness_utxos
462+
to_drop.clear();
463+
break;
464+
}
465+
if (input.non_witness_utxo) {
466+
to_drop.push_back(i);
467+
}
468+
}
469+
470+
// Drop the non_witness_utxos that we can drop
471+
for (unsigned int i : to_drop) {
472+
psbtx.inputs.at(i).non_witness_utxo = nullptr;
473+
}
474+
}
475+
}
476+
445477
bool FinalizePSBT(PartiallySignedTransaction& psbtx)
446478
{
447479
// Finalize input signatures -- in case we have partial signatures that add up to a complete

src/psbt.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1231,6 +1231,9 @@ bool PSBTInputSignedAndVerified(const PartiallySignedTransaction psbt, unsigned
12311231
**/
12321232
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool finalize = true);
12331233

1234+
/** 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. */
1235+
void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx, const int& sighash_type);
1236+
12341237
/** Counts the unsigned inputs of a PSBT. */
12351238
size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt);
12361239

src/rpc/rawtransaction.cpp

Lines changed: 91 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,91 @@ static std::vector<RPCArg> CreateTxDoc()
172172
};
173173
}
174174

175+
// Update PSBT with information from the mempool, the UTXO set, the txindex, and the provided descriptors
176+
PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std::any& context, const HidingSigningProvider& provider)
177+
{
178+
// Unserialize the transactions
179+
PartiallySignedTransaction psbtx;
180+
std::string error;
181+
if (!DecodeBase64PSBT(psbtx, psbt_string, error)) {
182+
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
183+
}
184+
185+
if (g_txindex) g_txindex->BlockUntilSyncedToCurrentChain();
186+
const NodeContext& node = EnsureAnyNodeContext(context);
187+
188+
// If we can't find the corresponding full transaction for all of our inputs,
189+
// this will be used to find just the utxos for the segwit inputs for which
190+
// the full transaction isn't found
191+
std::map<COutPoint, Coin> coins;
192+
193+
// Fetch previous transactions:
194+
// First, look in the txindex and the mempool
195+
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
196+
PSBTInput& psbt_input = psbtx.inputs.at(i);
197+
const CTxIn& tx_in = psbtx.tx->vin.at(i);
198+
199+
// The `non_witness_utxo` is the whole previous transaction
200+
if (psbt_input.non_witness_utxo) continue;
201+
202+
CTransactionRef tx;
203+
204+
// Look in the txindex
205+
if (g_txindex) {
206+
uint256 block_hash;
207+
g_txindex->FindTx(tx_in.prevout.hash, block_hash, tx);
208+
}
209+
// If we still don't have it look in the mempool
210+
if (!tx) {
211+
tx = node.mempool->get(tx_in.prevout.hash);
212+
}
213+
if (tx) {
214+
psbt_input.non_witness_utxo = tx;
215+
} else {
216+
coins[tx_in.prevout]; // Create empty map entry keyed by prevout
217+
}
218+
}
219+
220+
// If we still haven't found all of the inputs, look for the missing ones in the utxo set
221+
if (!coins.empty()) {
222+
FindCoins(node, coins);
223+
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
224+
PSBTInput& input = psbtx.inputs.at(i);
225+
226+
// If there are still missing utxos, add them if they were found in the utxo set
227+
if (!input.non_witness_utxo) {
228+
const CTxIn& tx_in = psbtx.tx->vin.at(i);
229+
const Coin& coin = coins.at(tx_in.prevout);
230+
if (!coin.out.IsNull() && IsSegWitOutput(provider, coin.out.scriptPubKey)) {
231+
input.witness_utxo = coin.out;
232+
}
233+
}
234+
}
235+
}
236+
237+
const PrecomputedTransactionData& txdata = PrecomputePSBTData(psbtx);
238+
239+
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
240+
if (PSBTInputSigned(psbtx.inputs.at(i))) {
241+
continue;
242+
}
243+
244+
// Update script/keypath information using descriptor data.
245+
// Note that SignPSBTInput does a lot more than just constructing ECDSA signatures
246+
// we don't actually care about those here, in fact.
247+
SignPSBTInput(provider, psbtx, /*index=*/i, &txdata, /*sighash=*/1);
248+
}
249+
250+
// Update script/keypath information using descriptor data.
251+
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
252+
UpdatePSBTOutput(provider, psbtx, i);
253+
}
254+
255+
RemoveUnnecessaryTransactions(psbtx, /*sighash_type=*/1);
256+
257+
return psbtx;
258+
}
259+
175260
static RPCHelpMan getrawtransaction()
176261
{
177262
return RPCHelpMan{
@@ -1580,7 +1665,7 @@ static RPCHelpMan converttopsbt()
15801665
static RPCHelpMan utxoupdatepsbt()
15811666
{
15821667
return RPCHelpMan{"utxoupdatepsbt",
1583-
"\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, the UTXO set or the mempool.\n",
1668+
"\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, the UTXO set, txindex, or the mempool.\n",
15841669
{
15851670
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"},
15861671
{"descriptors", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "An array of either strings or objects", {
@@ -1599,13 +1684,6 @@ static RPCHelpMan utxoupdatepsbt()
15991684
},
16001685
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
16011686
{
1602-
// Unserialize the transactions
1603-
PartiallySignedTransaction psbtx;
1604-
std::string error;
1605-
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
1606-
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
1607-
}
1608-
16091687
// Parse descriptors, if any.
16101688
FlatSigningProvider provider;
16111689
if (!request.params[1].isNull()) {
@@ -1614,53 +1692,12 @@ static RPCHelpMan utxoupdatepsbt()
16141692
EvalDescriptorStringOrObject(descs[i], provider);
16151693
}
16161694
}
1617-
// We don't actually need private keys further on; hide them as a precaution.
1618-
HidingSigningProvider public_provider(&provider, /*hide_secret=*/true, /*hide_origin=*/false);
1619-
1620-
// Fetch previous transactions (inputs):
1621-
CCoinsView viewDummy;
1622-
CCoinsViewCache view(&viewDummy);
1623-
{
1624-
NodeContext& node = EnsureAnyNodeContext(request.context);
1625-
const CTxMemPool& mempool = EnsureMemPool(node);
1626-
ChainstateManager& chainman = EnsureChainman(node);
1627-
LOCK2(cs_main, mempool.cs);
1628-
CCoinsViewCache &viewChain = chainman.ActiveChainstate().CoinsTip();
1629-
CCoinsViewMemPool viewMempool(&viewChain, mempool);
1630-
view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
1631-
1632-
for (const CTxIn& txin : psbtx.tx->vin) {
1633-
view.AccessCoin(txin.prevout); // Load entries from viewChain into view; can fail.
1634-
}
1635-
1636-
view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long
1637-
}
1638-
1639-
// Fill the inputs
1640-
const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
1641-
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
1642-
PSBTInput& input = psbtx.inputs.at(i);
16431695

1644-
if (input.non_witness_utxo || !input.witness_utxo.IsNull()) {
1645-
continue;
1646-
}
1647-
1648-
const Coin& coin = view.AccessCoin(psbtx.tx->vin[i].prevout);
1649-
1650-
if (IsSegWitOutput(provider, coin.out.scriptPubKey)) {
1651-
input.witness_utxo = coin.out;
1652-
}
1653-
1654-
// Update script/keypath information using descriptor data.
1655-
// Note that SignPSBTInput does a lot more than just constructing ECDSA signatures
1656-
// we don't actually care about those here, in fact.
1657-
SignPSBTInput(public_provider, psbtx, i, &txdata, /*sighash=*/1);
1658-
}
1659-
1660-
// Update script/keypath information using descriptor data.
1661-
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
1662-
UpdatePSBTOutput(public_provider, psbtx, i);
1663-
}
1696+
// We don't actually need private keys further on; hide them as a precaution.
1697+
const PartiallySignedTransaction& psbtx = ProcessPSBT(
1698+
request.params[0].get_str(),
1699+
request.context,
1700+
HidingSigningProvider(&provider, /*hide_secret=*/true, /*hide_origin=*/false));
16641701

16651702
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
16661703
ssTx << psbtx;

src/wallet/wallet.cpp

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2184,34 +2184,7 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp
21842184
}
21852185
}
21862186

2187-
// Only drop non_witness_utxos if sighash_type != SIGHASH_ANYONECANPAY
2188-
if ((sighash_type & 0x80) != SIGHASH_ANYONECANPAY) {
2189-
// Figure out if any non_witness_utxos should be dropped
2190-
std::vector<unsigned int> to_drop;
2191-
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
2192-
const auto& input = psbtx.inputs.at(i);
2193-
int wit_ver;
2194-
std::vector<unsigned char> wit_prog;
2195-
if (input.witness_utxo.IsNull() || !input.witness_utxo.scriptPubKey.IsWitnessProgram(wit_ver, wit_prog)) {
2196-
// There's a non-segwit input or Segwit v0, so we cannot drop any witness_utxos
2197-
to_drop.clear();
2198-
break;
2199-
}
2200-
if (wit_ver == 0) {
2201-
// Segwit v0, so we cannot drop any non_witness_utxos
2202-
to_drop.clear();
2203-
break;
2204-
}
2205-
if (input.non_witness_utxo) {
2206-
to_drop.push_back(i);
2207-
}
2208-
}
2209-
2210-
// Drop the non_witness_utxos that we can drop
2211-
for (unsigned int i : to_drop) {
2212-
psbtx.inputs.at(i).non_witness_utxo = nullptr;
2213-
}
2214-
}
2187+
RemoveUnnecessaryTransactions(psbtx, sighash_type);
22152188

22162189
// Complete if every input is now signed
22172190
complete = true;

test/functional/rpc_psbt.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -621,17 +621,17 @@ def test_psbt_input_keys(psbt_input, keys):
621621
# Bech32 inputs should be filled with witness UTXO. Other inputs should not be filled because they are non-witness
622622
updated = self.nodes[1].utxoupdatepsbt(psbt)
623623
decoded = self.nodes[1].decodepsbt(updated)
624-
test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo'])
625-
test_psbt_input_keys(decoded['inputs'][1], [])
626-
test_psbt_input_keys(decoded['inputs'][2], [])
624+
test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'non_witness_utxo'])
625+
test_psbt_input_keys(decoded['inputs'][1], ['non_witness_utxo'])
626+
test_psbt_input_keys(decoded['inputs'][2], ['non_witness_utxo'])
627627

628628
# Try again, now while providing descriptors, making P2SH-segwit work, and causing bip32_derivs and redeem_script to be filled in
629629
descs = [self.nodes[1].getaddressinfo(addr)['desc'] for addr in [addr1,addr2,addr3]]
630630
updated = self.nodes[1].utxoupdatepsbt(psbt=psbt, descriptors=descs)
631631
decoded = self.nodes[1].decodepsbt(updated)
632-
test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'bip32_derivs'])
633-
test_psbt_input_keys(decoded['inputs'][1], [])
634-
test_psbt_input_keys(decoded['inputs'][2], ['witness_utxo', 'bip32_derivs', 'redeem_script'])
632+
test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'non_witness_utxo', 'bip32_derivs'])
633+
test_psbt_input_keys(decoded['inputs'][1], ['non_witness_utxo', 'bip32_derivs'])
634+
test_psbt_input_keys(decoded['inputs'][2], ['non_witness_utxo','witness_utxo', 'bip32_derivs', 'redeem_script'])
635635

636636
# Two PSBTs with a common input should not be joinable
637637
psbt1 = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1}], {self.nodes[0].getnewaddress():Decimal('10.999')})

0 commit comments

Comments
 (0)