From 10d91c5abe9ed7dcc237c9d52c588e7d26e162a4 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Wed, 24 Aug 2022 16:40:30 -0400 Subject: wallet: Deduplicate Resend and ReacceptWalletTransactions Both of these functions do almost the exact same thing. They can be deduplicated so that their behavior matches except for the filtering aspect. As this function will now always be called on wallet loading, nNextResend will also always be initialized, so wallet_resendwallettransactions.py is updated to account for that. This also resolves a bug where ResendWalletTransactions would fail to rebroadcast txs in insertion order thereby potentially rebroadcasting a child transaction before its parent and causing the child to not actually get rebroadcast. Also names the combined function to ResubmitWalletTransactions as the function just submits the transactions to the mempool rather than doing any sending by itself. --- src/wallet/rpc/backup.cpp | 8 ++--- src/wallet/transaction.h | 7 ++++ src/wallet/wallet.cpp | 90 +++++++++++++++++++++++------------------------ src/wallet/wallet.h | 3 +- 4 files changed, 56 insertions(+), 52 deletions(-) (limited to 'src') diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index 49e5b5d5e7..10e5aa17e4 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -295,7 +295,7 @@ RPCHelpMan importaddress() RescanWallet(*pwallet, reserver); { LOCK(pwallet->cs_wallet); - pwallet->ReacceptWalletTransactions(); + pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); } } @@ -476,7 +476,7 @@ RPCHelpMan importpubkey() RescanWallet(*pwallet, reserver); { LOCK(pwallet->cs_wallet); - pwallet->ReacceptWalletTransactions(); + pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); } } @@ -1397,7 +1397,7 @@ RPCHelpMan importmulti() int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, reserver, true /* update */); { LOCK(pwallet->cs_wallet); - pwallet->ReacceptWalletTransactions(); + pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); } if (pwallet->IsAbortingRescan()) { @@ -1691,7 +1691,7 @@ RPCHelpMan importdescriptors() int64_t scanned_time = pwallet->RescanFromTime(lowest_timestamp, reserver, true /* update */); { LOCK(pwallet->cs_wallet); - pwallet->ReacceptWalletTransactions(); + pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); } if (pwallet->IsAbortingRescan()) { diff --git a/src/wallet/transaction.h b/src/wallet/transaction.h index 271d698e56..27983e356d 100644 --- a/src/wallet/transaction.h +++ b/src/wallet/transaction.h @@ -305,6 +305,13 @@ public: CWalletTx(CWalletTx const &) = delete; void operator=(CWalletTx const &x) = delete; }; + +struct WalletTxOrderComparator { + bool operator()(const CWalletTx* a, const CWalletTx* b) const + { + return a->nOrderPos < b->nOrderPos; + } +}; } // namespace wallet #endif // BITCOIN_WALLET_TRANSACTION_H diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 69a151d5c8..cb64383bf7 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1857,34 +1857,6 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc return result; } -void CWallet::ReacceptWalletTransactions() -{ - // If transactions aren't being broadcasted, don't let them into local mempool either - if (!fBroadcastTransactions) - return; - std::map mapSorted; - - // Sort pending wallet transactions based on their initial wallet insertion order - for (std::pair& item : mapWallet) { - const uint256& wtxid = item.first; - CWalletTx& wtx = item.second; - assert(wtx.GetHash() == wtxid); - - int nDepth = GetTxDepthInMainChain(wtx); - - if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) { - mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx)); - } - } - - // Try to add wallet transactions to memory pool - for (const std::pair& item : mapSorted) { - CWalletTx& wtx = *(item.second); - std::string unused_err_string; - SubmitTxMemoryPoolAndRelay(wtx, unused_err_string, false); - } -} - bool CWallet::SubmitTxMemoryPoolAndRelay(CWalletTx& wtx, std::string& err_string, bool relay) const { AssertLockHeld(cs_wallet); @@ -1925,43 +1897,69 @@ std::set CWallet::GetTxConflicts(const CWalletTx& wtx) const return result; } -// Rebroadcast transactions from the wallet. We do this on a random timer -// to slightly obfuscate which transactions come from our wallet. +// Resubmit transactions from the wallet to the mempool, optionally asking the +// mempool to relay them. On startup, we will do this for all unconfirmed +// transactions but will not ask the mempool to relay them. We do this on startup +// to ensure that our own mempool is aware of our transactions, and to also +// initialize nNextResend so that the actual rebroadcast is scheduled. There +// is a privacy side effect here as not broadcasting on startup also means that we won't +// inform the world of our wallet's state, particularly if the wallet (or node) is not +// yet synced. +// +// Otherwise this function is called periodically in order to relay our unconfirmed txs. +// We do this on a random timer to slightly obfuscate which transactions +// come from our wallet. // -// Ideally, we'd only resend transactions that we think should have been +// TODO: Ideally, we'd only resend transactions that we think should have been // mined in the most recent block. Any transaction that wasn't in the top // blockweight of transactions in the mempool shouldn't have been mined, // and so is probably just sitting in the mempool waiting to be confirmed. // Rebroadcasting does nothing to speed up confirmation and only damages // privacy. -void CWallet::ResendWalletTransactions() +// +// The `force` option results in all unconfirmed transactions being submitted to +// the mempool. This does not necessarily result in those transactions being relayed, +// that depends on the `relay` option. Periodic rebroadcast uses the pattern +// relay=true force=false (also the default values), while loading into the mempool +// (on start, or after import) uses relay=false force=true. +void CWallet::ResubmitWalletTransactions(bool relay, bool force) { + // Don't attempt to resubmit if the wallet is configured to not broadcast, + // even if forcing. + if (!fBroadcastTransactions) return; + // During reindex, importing and IBD, old wallet transactions become // unconfirmed. Don't resend them as that would spam other nodes. - if (!chain().isReadyToBroadcast()) return; + // We only allow forcing mempool submission when not relaying to avoid this spam. + if (!force && relay && !chain().isReadyToBroadcast()) return; // Do this infrequently and randomly to avoid giving away // that these are our transactions. - if (GetTime() < nNextResend || !fBroadcastTransactions) return; - bool fFirst = (nNextResend == 0); + if (!force && GetTime() < nNextResend) return; // resend 12-36 hours from now, ~1 day on average. nNextResend = GetTime() + (12 * 60 * 60) + GetRand(24 * 60 * 60); - if (fFirst) return; int submitted_tx_count = 0; { // cs_wallet scope LOCK(cs_wallet); - // Relay transactions - for (std::pair& item : mapWallet) { - CWalletTx& wtx = item.second; - // Attempt to rebroadcast all txes more than 5 minutes older than - // the last block. SubmitTxMemoryPoolAndRelay() will not rebroadcast - // any confirmed or conflicting txs. - if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue; + // First filter for the transactions we want to rebroadcast. + // We use a set with WalletTxOrderComparator so that rebroadcasting occurs in insertion order + std::set to_submit; + for (auto& [txid, wtx] : mapWallet) { + // Only rebroadcast unconfirmed txs + if (!wtx.isUnconfirmed()) continue; + + // attempt to rebroadcast all txes more than 5 minutes older than + // the last block, or all txs if forcing. + if (!force && wtx.nTimeReceived > m_best_block_time - 5 * 60) continue; + to_submit.insert(&wtx); + } + // Now try submitting the transactions to the memory pool and (optionally) relay them. + for (auto wtx : to_submit) { std::string unused_err_string; - if (SubmitTxMemoryPoolAndRelay(wtx, unused_err_string, true)) ++submitted_tx_count; + if (SubmitTxMemoryPoolAndRelay(*wtx, unused_err_string, relay)) ++submitted_tx_count; } } // cs_wallet @@ -1975,7 +1973,7 @@ void CWallet::ResendWalletTransactions() void MaybeResendWalletTxs(WalletContext& context) { for (const std::shared_ptr& pwallet : GetWallets(context)) { - pwallet->ResendWalletTransactions(); + pwallet->ResubmitWalletTransactions(/*relay=*/true, /*force=*/false); } } @@ -3191,7 +3189,7 @@ void CWallet::postInitProcess() // Add wallet transactions that aren't already in a block to mempool // Do this here as mempool requires genesis block to be loaded - ReacceptWalletTransactions(); + ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); // Update wallet transactions with current mempool transactions. chain().requestMempoolTransactions(*this); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 9281045c21..7abe090fad 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -537,8 +537,7 @@ public: }; ScanResult ScanForWalletTransactions(const uint256& start_block, int start_height, std::optional max_height, const WalletRescanReserver& reserver, bool fUpdate, const bool save_progress); void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) override; - void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - void ResendWalletTransactions(); + void ResubmitWalletTransactions(bool relay, bool force); OutputType TransactionChangeType(const std::optional& change_type, const std::vector& vecSend) const; -- cgit v1.2.3