Skip to content

Commit 10d91c5

Browse files
committed
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.
1 parent e191fac commit 10d91c5

File tree

5 files changed

+58
-60
lines changed

5 files changed

+58
-60
lines changed

src/wallet/rpc/backup.cpp

+4-4
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ RPCHelpMan importaddress()
295295
RescanWallet(*pwallet, reserver);
296296
{
297297
LOCK(pwallet->cs_wallet);
298-
pwallet->ReacceptWalletTransactions();
298+
pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true);
299299
}
300300
}
301301

@@ -476,7 +476,7 @@ RPCHelpMan importpubkey()
476476
RescanWallet(*pwallet, reserver);
477477
{
478478
LOCK(pwallet->cs_wallet);
479-
pwallet->ReacceptWalletTransactions();
479+
pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true);
480480
}
481481
}
482482

@@ -1397,7 +1397,7 @@ RPCHelpMan importmulti()
13971397
int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, reserver, true /* update */);
13981398
{
13991399
LOCK(pwallet->cs_wallet);
1400-
pwallet->ReacceptWalletTransactions();
1400+
pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true);
14011401
}
14021402

14031403
if (pwallet->IsAbortingRescan()) {
@@ -1691,7 +1691,7 @@ RPCHelpMan importdescriptors()
16911691
int64_t scanned_time = pwallet->RescanFromTime(lowest_timestamp, reserver, true /* update */);
16921692
{
16931693
LOCK(pwallet->cs_wallet);
1694-
pwallet->ReacceptWalletTransactions();
1694+
pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true);
16951695
}
16961696

16971697
if (pwallet->IsAbortingRescan()) {

src/wallet/transaction.h

+7
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,13 @@ class CWalletTx
305305
CWalletTx(CWalletTx const &) = delete;
306306
void operator=(CWalletTx const &x) = delete;
307307
};
308+
309+
struct WalletTxOrderComparator {
310+
bool operator()(const CWalletTx* a, const CWalletTx* b) const
311+
{
312+
return a->nOrderPos < b->nOrderPos;
313+
}
314+
};
308315
} // namespace wallet
309316

310317
#endif // BITCOIN_WALLET_TRANSACTION_H

src/wallet/wallet.cpp

+44-46
Original file line numberDiff line numberDiff line change
@@ -1857,34 +1857,6 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
18571857
return result;
18581858
}
18591859

1860-
void CWallet::ReacceptWalletTransactions()
1861-
{
1862-
// If transactions aren't being broadcasted, don't let them into local mempool either
1863-
if (!fBroadcastTransactions)
1864-
return;
1865-
std::map<int64_t, CWalletTx*> mapSorted;
1866-
1867-
// Sort pending wallet transactions based on their initial wallet insertion order
1868-
for (std::pair<const uint256, CWalletTx>& item : mapWallet) {
1869-
const uint256& wtxid = item.first;
1870-
CWalletTx& wtx = item.second;
1871-
assert(wtx.GetHash() == wtxid);
1872-
1873-
int nDepth = GetTxDepthInMainChain(wtx);
1874-
1875-
if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) {
1876-
mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx));
1877-
}
1878-
}
1879-
1880-
// Try to add wallet transactions to memory pool
1881-
for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) {
1882-
CWalletTx& wtx = *(item.second);
1883-
std::string unused_err_string;
1884-
SubmitTxMemoryPoolAndRelay(wtx, unused_err_string, false);
1885-
}
1886-
}
1887-
18881860
bool CWallet::SubmitTxMemoryPoolAndRelay(CWalletTx& wtx, std::string& err_string, bool relay) const
18891861
{
18901862
AssertLockHeld(cs_wallet);
@@ -1925,43 +1897,69 @@ std::set<uint256> CWallet::GetTxConflicts(const CWalletTx& wtx) const
19251897
return result;
19261898
}
19271899

1928-
// Rebroadcast transactions from the wallet. We do this on a random timer
1929-
// to slightly obfuscate which transactions come from our wallet.
1900+
// Resubmit transactions from the wallet to the mempool, optionally asking the
1901+
// mempool to relay them. On startup, we will do this for all unconfirmed
1902+
// transactions but will not ask the mempool to relay them. We do this on startup
1903+
// to ensure that our own mempool is aware of our transactions, and to also
1904+
// initialize nNextResend so that the actual rebroadcast is scheduled. There
1905+
// is a privacy side effect here as not broadcasting on startup also means that we won't
1906+
// inform the world of our wallet's state, particularly if the wallet (or node) is not
1907+
// yet synced.
1908+
//
1909+
// Otherwise this function is called periodically in order to relay our unconfirmed txs.
1910+
// We do this on a random timer to slightly obfuscate which transactions
1911+
// come from our wallet.
19301912
//
1931-
// Ideally, we'd only resend transactions that we think should have been
1913+
// TODO: Ideally, we'd only resend transactions that we think should have been
19321914
// mined in the most recent block. Any transaction that wasn't in the top
19331915
// blockweight of transactions in the mempool shouldn't have been mined,
19341916
// and so is probably just sitting in the mempool waiting to be confirmed.
19351917
// Rebroadcasting does nothing to speed up confirmation and only damages
19361918
// privacy.
1937-
void CWallet::ResendWalletTransactions()
1919+
//
1920+
// The `force` option results in all unconfirmed transactions being submitted to
1921+
// the mempool. This does not necessarily result in those transactions being relayed,
1922+
// that depends on the `relay` option. Periodic rebroadcast uses the pattern
1923+
// relay=true force=false (also the default values), while loading into the mempool
1924+
// (on start, or after import) uses relay=false force=true.
1925+
void CWallet::ResubmitWalletTransactions(bool relay, bool force)
19381926
{
1927+
// Don't attempt to resubmit if the wallet is configured to not broadcast,
1928+
// even if forcing.
1929+
if (!fBroadcastTransactions) return;
1930+
19391931
// During reindex, importing and IBD, old wallet transactions become
19401932
// unconfirmed. Don't resend them as that would spam other nodes.
1941-
if (!chain().isReadyToBroadcast()) return;
1933+
// We only allow forcing mempool submission when not relaying to avoid this spam.
1934+
if (!force && relay && !chain().isReadyToBroadcast()) return;
19421935

19431936
// Do this infrequently and randomly to avoid giving away
19441937
// that these are our transactions.
1945-
if (GetTime() < nNextResend || !fBroadcastTransactions) return;
1946-
bool fFirst = (nNextResend == 0);
1938+
if (!force && GetTime() < nNextResend) return;
19471939
// resend 12-36 hours from now, ~1 day on average.
19481940
nNextResend = GetTime() + (12 * 60 * 60) + GetRand(24 * 60 * 60);
1949-
if (fFirst) return;
19501941

19511942
int submitted_tx_count = 0;
19521943

19531944
{ // cs_wallet scope
19541945
LOCK(cs_wallet);
19551946

1956-
// Relay transactions
1957-
for (std::pair<const uint256, CWalletTx>& item : mapWallet) {
1958-
CWalletTx& wtx = item.second;
1959-
// Attempt to rebroadcast all txes more than 5 minutes older than
1960-
// the last block. SubmitTxMemoryPoolAndRelay() will not rebroadcast
1961-
// any confirmed or conflicting txs.
1962-
if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue;
1947+
// First filter for the transactions we want to rebroadcast.
1948+
// We use a set with WalletTxOrderComparator so that rebroadcasting occurs in insertion order
1949+
std::set<CWalletTx*, WalletTxOrderComparator> to_submit;
1950+
for (auto& [txid, wtx] : mapWallet) {
1951+
// Only rebroadcast unconfirmed txs
1952+
if (!wtx.isUnconfirmed()) continue;
1953+
1954+
// attempt to rebroadcast all txes more than 5 minutes older than
1955+
// the last block, or all txs if forcing.
1956+
if (!force && wtx.nTimeReceived > m_best_block_time - 5 * 60) continue;
1957+
to_submit.insert(&wtx);
1958+
}
1959+
// Now try submitting the transactions to the memory pool and (optionally) relay them.
1960+
for (auto wtx : to_submit) {
19631961
std::string unused_err_string;
1964-
if (SubmitTxMemoryPoolAndRelay(wtx, unused_err_string, true)) ++submitted_tx_count;
1962+
if (SubmitTxMemoryPoolAndRelay(*wtx, unused_err_string, relay)) ++submitted_tx_count;
19651963
}
19661964
} // cs_wallet
19671965

@@ -1975,7 +1973,7 @@ void CWallet::ResendWalletTransactions()
19751973
void MaybeResendWalletTxs(WalletContext& context)
19761974
{
19771975
for (const std::shared_ptr<CWallet>& pwallet : GetWallets(context)) {
1978-
pwallet->ResendWalletTransactions();
1976+
pwallet->ResubmitWalletTransactions(/*relay=*/true, /*force=*/false);
19791977
}
19801978
}
19811979

@@ -3191,7 +3189,7 @@ void CWallet::postInitProcess()
31913189

31923190
// Add wallet transactions that aren't already in a block to mempool
31933191
// Do this here as mempool requires genesis block to be loaded
3194-
ReacceptWalletTransactions();
3192+
ResubmitWalletTransactions(/*relay=*/false, /*force=*/true);
31953193

31963194
// Update wallet transactions with current mempool transactions.
31973195
chain().requestMempoolTransactions(*this);

src/wallet/wallet.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -537,8 +537,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
537537
};
538538
ScanResult ScanForWalletTransactions(const uint256& start_block, int start_height, std::optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate, const bool save_progress);
539539
void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) override;
540-
void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
541-
void ResendWalletTransactions();
540+
void ResubmitWalletTransactions(bool relay, bool force);
542541

543542
OutputType TransactionChangeType(const std::optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const;
544543

test/functional/wallet_resendwallettransactions.py

+2-8
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,6 @@ def run_test(self):
2929
self.log.info("Create a new transaction and wait until it's broadcast")
3030
txid = node.sendtoaddress(node.getnewaddress(), 1)
3131

32-
# Wallet rebroadcast is first scheduled 1 min sec after startup (see
33-
# nNextResend in ResendWalletTransactions()). Tell scheduler to call
34-
# MaybeResendWalletTxs now to initialize nNextResend before the first
35-
# setmocktime call below.
36-
node.mockscheduler(60)
37-
3832
# Can take a few seconds due to transaction trickling
3933
peer_first.wait_for_broadcast([txid])
4034

@@ -51,7 +45,7 @@ def run_test(self):
5145
block.solve()
5246
node.submitblock(block.serialize().hex())
5347

54-
# Set correct m_best_block_time, which is used in ResendWalletTransactions
48+
# Set correct m_best_block_time, which is used in ResubmitWalletTransactions
5549
node.syncwithvalidationinterfacequeue()
5650
now = int(time.time())
5751

@@ -66,7 +60,7 @@ def run_test(self):
6660
self.log.info("Bump time & check that transaction is rebroadcast")
6761
# Transaction should be rebroadcast approximately 24 hours in the future,
6862
# but can range from 12-36. So bump 36 hours to be sure.
69-
with node.assert_debug_log(['ResendWalletTransactions: resubmit 1 unconfirmed transactions']):
63+
with node.assert_debug_log(['resubmit 1 unconfirmed transactions']):
7064
node.setmocktime(now + 36 * 60 * 60)
7165
# Tell scheduler to call MaybeResendWalletTxs now.
7266
node.mockscheduler(60)

0 commit comments

Comments
 (0)