diff --git a/src/wallet/coinjoin.cpp b/src/wallet/coinjoin.cpp index 0a2b6705d49b..d4f12cad2713 100644 --- a/src/wallet/coinjoin.cpp +++ b/src/wallet/coinjoin.cpp @@ -284,10 +284,13 @@ int CWallet::GetRealOutpointCoinJoinRounds(const COutPoint& outpoint, int nRound const CWalletTx* wtx{GetWalletTx(outpoint.hash)}; if (wtx == nullptr || wtx->tx == nullptr) { - // no such tx in this wallet - *nRoundsRef = -1; + // No such tx in this wallet: don't memoize the result, the wallet may + // still learn about the tx later (e.g. a rescan in progress) and nothing + // invalidates the cache when it does. RPCs feed user-supplied outpoints + // into this via IsFullyMixed, so this path is reachable for any outpoint. + mapOutpointRoundsCache.erase(pair.first); WalletCJLogPrint(this, "%s FAILED %-70s %3d\n", __func__, outpoint.ToStringShort(), -1); - return *nRoundsRef; + return -1; } // bounds check diff --git a/src/wallet/test/coinjoin_tests.cpp b/src/wallet/test/coinjoin_tests.cpp index 582593382a1c..4fcec9982b0b 100644 --- a/src/wallet/test/coinjoin_tests.cpp +++ b/src/wallet/test/coinjoin_tests.cpp @@ -552,6 +552,29 @@ BOOST_FIXTURE_TEST_CASE(coinjoin_rebalance_rounds_reset_tests, CTransactionBuild } } +BOOST_FIXTURE_TEST_CASE(coinjoin_rounds_cache_unknown_tx_tests, CTransactionBuilderTestSetup) +{ + constexpr CAmount nDenomAmount{10000100}; // 0.100001 DASH + BOOST_REQUIRE(CoinJoin::IsDenominatedAmount(nDenomAmount)); + + CompactTallyItem tallyItem = GetTallyItem({nDenomAmount}); + const CScript scriptOurs = GetScriptForRawPubKey(coinbaseKey.GetPubKey()); + + CMutableTransaction mtxMix; + mtxMix.vin.emplace_back(tallyItem.outpoints[0]); + mtxMix.vout.emplace_back(nDenomAmount, scriptOurs); + const COutPoint outpointMixed{mtxMix.GetHash(), 0}; + + // Queried before the wallet knows the tx, the way RPCs do for user-supplied + // preset inputs: unknown, reported as -1 + BOOST_CHECK_EQUAL(wallet->GetRealOutpointCoinJoinRounds(outpointMixed), -1); + + // Once the wallet learns the tx its rounds are computed from it; the miss + // above must not stick in mapOutpointRoundsCache + BOOST_REQUIRE(wallet->AddToWallet(MakeTransactionRef(mtxMix), TxStateInMempool{})); + BOOST_CHECK_EQUAL(wallet->GetRealOutpointCoinJoinRounds(outpointMixed), 1); +} + BOOST_FIXTURE_TEST_CASE(CTransactionBuilderTest, CTransactionBuilderTestSetup) { // NOTE: Mock wallet version is FEATURE_BASE which means that it uses uncompressed pubkeys