Merge bitcoin/bitcoin#33268: wallet: Identify transactions spending 0-value outputs, and add tests for anchor outputs in a wallet

113a422822 wallet: Add m_cached_from_me to cache "from me" status (Ava Chow)
609d265ebc test: Add a test for anchor outputs in the wallet (Ava Chow)
c40dc822d7 wallet: Throw an error in sendall if the tx size cannot be calculated (Ava Chow)
39a7dbdd27 wallet: Determine IsFromMe by checking for TXOs of inputs (Ava Chow)
e76c2f7a41 test: Test wallet 'from me' status change (Ava Chow)

Pull request description:

  One of the ways that the wallet would determine if a transaction was sent from the wallet was by checking if the total amount being spent by a transaction from outputs known to the wallet was greater than 0. This has worked fine until recently since there was no reason for 0-value outputs to be created. However, with ephemeral dust and P2A, it is possible to create standard 0-value outputs, and the wallet was not correctly identifying the spends of such outputs. This PR updates `IsFromMe` to only check whether the wallet knows any of the inputs, rather than checking the debit amount of a transaction.

  Additionally, a new functional test is added to test for this case, as well as a few other anchor output related scenarios. This also revealed a bug in `sendall` which would cause an assertion error when trying to spend all of the outputs in a wallet that has anchor outputs.

  Fixes #33265

ACKs for top commit:
  rkrux:
    lgtm ACK 113a422822
  enirox001:
    Tested ACK 113a422. Ran the full functional test suite including `wallet_anchor.py`; all tests passed. Fix for 0 value anchor detection and sendall size errors looks good. LGTM.
  furszy:
    ACK 113a422822

Tree-SHA512: df2ce4b258d1875ad0b4f27a5b9b4437137a5889a7d5ed7fbca65f904615e9572d232a8b8d070760f75ac168c1a49b7981f6b5052308575866dc610d191ca964
This commit is contained in:
merge-script
2025-09-12 14:42:08 +01:00
8 changed files with 182 additions and 3 deletions

View File

@@ -195,7 +195,10 @@ void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx,
bool CachedTxIsFromMe(const CWallet& wallet, const CWalletTx& wtx)
{
return (CachedTxGetDebit(wallet, wtx, /*avoid_reuse=*/false) > 0);
if (!wtx.m_cached_from_me.has_value()) {
wtx.m_cached_from_me = wallet.IsFromMe(*wtx.tx);
}
return wtx.m_cached_from_me.value();
}
// NOLINTNEXTLINE(misc-no-recursion)

View File

@@ -1521,7 +1521,6 @@ RPCHelpMan sendall()
CoinFilterParams coins_params;
coins_params.min_amount = 0;
for (const COutput& output : AvailableCoins(*pwallet, &coin_control, fee_rate, coins_params).All()) {
CHECK_NONFATAL(output.input_bytes > 0);
if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) {
continue;
}
@@ -1544,6 +1543,9 @@ RPCHelpMan sendall()
// estimate final size of tx
const TxSize tx_size{CalculateMaximumSignedTxSize(CTransaction(rawTx), pwallet.get())};
if (tx_size.vsize == -1) {
throw JSONRPCError(RPC_WALLET_ERROR, "Unable to determine the size of the transaction, the wallet contains unsolvable descriptors");
}
const CAmount fee_from_size{fee_rate.GetFee(tx_size.vsize)};
const std::optional<CAmount> total_bump_fees{pwallet->chain().calculateCombinedBumpFee(outpoints_spent, fee_rate)};
CAmount effective_value = total_input_value - fee_from_size - total_bump_fees.value_or(0);

View File

@@ -232,6 +232,8 @@ public:
* CWallet::ComputeTimeSmart().
*/
unsigned int nTimeSmart;
// Cached value for whether the transaction spends any inputs known to the wallet
mutable std::optional<bool> m_cached_from_me{std::nullopt};
int64_t nOrderPos; //!< position in ordered transaction list
std::multimap<int64_t, CWalletTx*>::const_iterator m_it_wtxOrdered;
@@ -339,6 +341,7 @@ public:
m_amounts[CREDIT].Reset();
fChangeCached = false;
m_is_cache_empty = true;
m_cached_from_me = std::nullopt;
}
/** True if only scriptSigs are different */

View File

@@ -1634,7 +1634,11 @@ bool CWallet::IsMine(const COutPoint& outpoint) const
bool CWallet::IsFromMe(const CTransaction& tx) const
{
return (GetDebit(tx) > 0);
LOCK(cs_wallet);
for (const CTxIn& txin : tx.vin) {
if (GetTXO(txin.prevout)) return true;
}
return false;
}
CAmount CWallet::GetDebit(const CTransaction& tx) const