Skip to content

Commit

Permalink
merge bitcoin#15632: Remove ResendWalletTransactions from the Validat…
Browse files Browse the repository at this point in the history
…ion Interface
  • Loading branch information
kwvg committed Dec 11, 2021
1 parent 15d363a commit f53b0ab
Show file tree
Hide file tree
Showing 9 changed files with 56 additions and 48 deletions.
13 changes: 4 additions & 9 deletions src/interfaces/chain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,17 +217,11 @@ class NotificationsHandlerImpl : public Handler, CValidationInterface
{
m_notifications->BlockDisconnected(*block);
}
void ChainStateFlushed(const CBlockLocator& locator) override { m_notifications->ChainStateFlushed(locator); }
void ResendWalletTransactions(int64_t best_block_time, CConnman*) override
void UpdatedBlockTip(const CBlockIndex* index, const CBlockIndex* fork_index, bool is_ibd) override
{
// `cs_main` is always held when this method is called, so it is safe to
// call `assumeLocked`. This is awkward, and the `assumeLocked` method
// should be able to be removed entirely if `ResendWalletTransactions`
// is replaced by a wallet timer as suggested in
// https://github.com/bitcoin/bitcoin/issues/15619
auto locked_chain = m_chain.assumeLocked();
m_notifications->ResendWalletTransactions(*locked_chain, best_block_time);
m_notifications->UpdatedBlockTip();
}
void ChainStateFlushed(const CBlockLocator& locator) override { m_notifications->ChainStateFlushed(locator); }
void NotifyChainLock(const CBlockIndex* pindexChainLock, const std::shared_ptr<const llmq::CChainLockSig>& clsig) override
{
m_notifications->NotifyChainLock(pindexChainLock, clsig);
Expand Down Expand Up @@ -365,6 +359,7 @@ class ChainImpl : public Chain
CAmount maxTxFee() override { return ::maxTxFee; }
bool getPruneMode() override { return ::fPruneMode; }
bool p2pEnabled() override { return g_connman != nullptr; }
bool isReadyToBroadcast() override { return !::fImporting && !::fReindex && !::ChainstateActive().IsInitialBlockDownload(); }
bool isInitialBlockDownload() override { return ::ChainstateActive().IsInitialBlockDownload(); }
bool shutdownRequested() override { return ShutdownRequested(); }
int64_t getAdjustedTime() override { return GetAdjustedTime(); }
Expand Down
5 changes: 4 additions & 1 deletion src/interfaces/chain.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ class Chain
//! Check if p2p enabled.
virtual bool p2pEnabled() = 0;

//! Check if the node is ready to broadcast transactions.
virtual bool isReadyToBroadcast() = 0;

//! Check if in IBD.
virtual bool isInitialBlockDownload() = 0;

Expand Down Expand Up @@ -256,8 +259,8 @@ class Chain
virtual void TransactionRemovedFromMempool(const CTransactionRef& ptx, MemPoolRemovalReason reason) {}
virtual void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& tx_conflicted) {}
virtual void BlockDisconnected(const CBlock& block) {}
virtual void UpdatedBlockTip() {}
virtual void ChainStateFlushed(const CBlockLocator& locator) {}
virtual void ResendWalletTransactions(Lock& locked_chain, int64_t best_block_time) {}
virtual void NotifyChainLock(const CBlockIndex* pindexChainLock, const std::shared_ptr<const llmq::CChainLockSig>& clsig) {}
virtual void NotifyTransactionLock(const CTransactionRef &tx, const std::shared_ptr<const llmq::CInstantSendLock>& islock) {}
};
Expand Down
19 changes: 0 additions & 19 deletions src/net_processing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,6 @@ namespace {
/** Expiration-time ordered list of (expire time, relay map entry) pairs. */
std::deque<std::pair<int64_t, MapRelay::iterator>> vRelayExpiration GUARDED_BY(cs_main);

std::atomic<int64_t> nTimeBestReceived(0); // Used only to inform the wallet of when we last received a block

struct IteratorComparator
{
template<typename I>
Expand Down Expand Up @@ -1314,8 +1312,6 @@ void PeerLogicValidation::UpdatedBlockTip(const CBlockIndex *pindexNew, const CB
});
connman->WakeMessageHandler();
}

nTimeBestReceived = GetTime();
}

/**
Expand Down Expand Up @@ -4381,21 +4377,6 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
}
}

// Resend wallet transactions that haven't gotten in a block yet
// Except during reindex, importing and IBD, when old wallet
// transactions become unconfirmed and spams other nodes.
if (!fReindex && !fImporting && !::ChainstateActive().IsInitialBlockDownload())
{
static int64_t nLastBroadcastTime = 0;
// HACK: Call this only once every few seconds. SendMessages is called once per peer, which makes this signal very expensive
// The proper solution would be to move this out of here, but this is not worth the effort right now as bitcoin#15632 will later do this.
// Luckily, the Broadcast signal is not used for anything else then CWallet::ResendWalletTransactionsBefore.
if (nNow - nLastBroadcastTime >= 5000000) {
GetMainSignals().Broadcast(nTimeBestReceived, connman);
nLastBroadcastTime = nNow;
}
}

//
// Try sending block announcements via headers
//
Expand Down
7 changes: 0 additions & 7 deletions src/validationinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ struct ValidationInterfaceConnections {
boost::signals2::scoped_connection BlockDisconnected;
boost::signals2::scoped_connection TransactionRemovedFromMempool;
boost::signals2::scoped_connection ChainStateFlushed;
boost::signals2::scoped_connection Broadcast;
boost::signals2::scoped_connection BlockChecked;
boost::signals2::scoped_connection NewPoWValidBlock;
boost::signals2::scoped_connection AcceptedBlockHeader;
Expand All @@ -48,7 +47,6 @@ struct MainSignalsInstance {
boost::signals2::signal<void (const std::shared_ptr<const CBlock> &, const CBlockIndex* pindexDisconnected)> BlockDisconnected;
boost::signals2::signal<void (const CTransactionRef &, MemPoolRemovalReason)> TransactionRemovedFromMempool;
boost::signals2::signal<void (const CBlockLocator &)> ChainStateFlushed;
boost::signals2::signal<void (int64_t nBestBlockTime, CConnman* connman)> Broadcast;
boost::signals2::signal<void (const CBlock&, const CValidationState&)> BlockChecked;
boost::signals2::signal<void (const CBlockIndex *, const std::shared_ptr<const CBlock>&)> NewPoWValidBlock;
boost::signals2::signal<void (const CBlockIndex *)>AcceptedBlockHeader;
Expand Down Expand Up @@ -122,7 +120,6 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn) {
conns.NotifyChainLock = g_signals.m_internals->NotifyChainLock.connect(std::bind(&CValidationInterface::NotifyChainLock, pwalletIn, std::placeholders::_1, std::placeholders::_2));
conns.TransactionRemovedFromMempool = g_signals.m_internals->TransactionRemovedFromMempool.connect(std::bind(&CValidationInterface::TransactionRemovedFromMempool, pwalletIn, std::placeholders::_1, std::placeholders::_2));
conns.ChainStateFlushed = g_signals.m_internals->ChainStateFlushed.connect(std::bind(&CValidationInterface::ChainStateFlushed, pwalletIn, std::placeholders::_1));
conns.Broadcast = g_signals.m_internals->Broadcast.connect(std::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, std::placeholders::_1, std::placeholders::_2));
conns.BlockChecked = g_signals.m_internals->BlockChecked.connect(std::bind(&CValidationInterface::BlockChecked, pwalletIn, std::placeholders::_1, std::placeholders::_2));
conns.NewPoWValidBlock = g_signals.m_internals->NewPoWValidBlock.connect(std::bind(&CValidationInterface::NewPoWValidBlock, pwalletIn, std::placeholders::_1, std::placeholders::_2));
conns.NotifyGovernanceObject = g_signals.m_internals->NotifyGovernanceObject.connect(std::bind(&CValidationInterface::NotifyGovernanceObject, pwalletIn, std::placeholders::_1));
Expand Down Expand Up @@ -205,10 +202,6 @@ void CMainSignals::ChainStateFlushed(const CBlockLocator &locator) {
});
}

void CMainSignals::Broadcast(int64_t nBestBlockTime, CConnman* connman) {
m_internals->Broadcast(nBestBlockTime, connman);
}

void CMainSignals::BlockChecked(const CBlock& block, const CValidationState& state) {
m_internals->BlockChecked(block, state);
}
Expand Down
3 changes: 0 additions & 3 deletions src/validationinterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,6 @@ class CValidationInterface {
* Called on a background thread.
*/
virtual void ChainStateFlushed(const CBlockLocator &locator) {}
/** Tells listeners to broadcast their data. */
virtual void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) {}
/**
* Notifies listeners of a block validation result.
* If the provided CValidationState IsValid, the provided block
Expand Down Expand Up @@ -216,7 +214,6 @@ class CMainSignals {
void NotifyRecoveredSig(const std::shared_ptr<const llmq::CRecoveredSig> &sig);
void NotifyMasternodeListChanged(bool undo, const CDeterministicMNList& oldMNList, const CDeterministicMNListDiff& diff);
void ChainStateFlushed(const CBlockLocator &);
void Broadcast(int64_t nBestBlockTime, CConnman* connman);
void BlockChecked(const CBlock&, const CValidationState&);
void NewPoWValidBlock(const CBlockIndex *, const std::shared_ptr<const CBlock>&);
};
Expand Down
3 changes: 2 additions & 1 deletion src/wallet/load.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,9 @@ void StartWallets(CScheduler& scheduler)
pwallet->postInitProcess();
}

// Run a thread to flush wallet periodically
// Schedule periodic wallet flushes and tx rebroadcasts
scheduler.scheduleEvery(MaybeCompactWalletDB, 500);
scheduler.scheduleEvery(MaybeResendWalletTxs, 1000);

if (!fMasternodeMode && CCoinJoinClientOptions::IsEnabled()) {
scheduler.scheduleEvery(std::bind(&DoCoinJoinMaintenance, std::ref(*g_connman)), 1 * 1000);
Expand Down
37 changes: 30 additions & 7 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1473,6 +1473,10 @@ void CWallet::BlockDisconnected(const CBlock& block) {
fAnonymizableTallyCachedNonDenom = false;
}

void CWallet::UpdatedBlockTip()
{
m_best_block_time = GetTime();
}


void CWallet::BlockUntilSyncedToCurrentChain() {
Expand Down Expand Up @@ -2598,8 +2602,21 @@ bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const
return CTransaction(tx1) == CTransaction(tx2);
}

void CWallet::ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, int64_t nBestBlockTime)
// Rebroadcast transactions from the wallet. 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
// 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()
{
// During reindex, importing and IBD, old wallet transactions become
// unconfirmed. Don't resend them as that would spam other nodes.
if (!chain().isReadyToBroadcast()) return;

// Do this infrequently and randomly to avoid giving away
// that these are our transactions.
if (GetTime() < nNextResend || !fBroadcastTransactions) return;
Expand All @@ -2608,23 +2625,24 @@ void CWallet::ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, in
if (fFirst) return;

// Only do it if there's been a new block since last time
if (nBestBlockTime < nLastResend) return;
if (m_best_block_time < nLastResend) return;
nLastResend = GetTime();

int relayed_tx_count = 0;

{ // mempool.cs and cs_wallet scope
{ // locked_chain, mempool.cs and cs_wallet scope
auto locked_chain = chain().lock();
LOCK2(mempool.cs, cs_wallet);

// Relay transactions
for (std::pair<const uint256, CWalletTx>& item : mapWallet) {
CWalletTx& wtx = item.second;
// only rebroadcast unconfirmed txes older than 5 minutes before the
// last block was found
if (wtx.nTimeReceived > nBestBlockTime - 5 * 60) continue;
relayed_tx_count += wtx.RelayWalletTransaction(*locked_chain) ? 1 : 0;
if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue;
if (wtx.RelayWalletTransaction(*locked_chain)) ++relayed_tx_count;
}
} // mempool.cs and cs_wallet
} // locked_chain, mempool.cs and cs_wallet

if (relayed_tx_count > 0) {
WalletLogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed_tx_count);
Expand All @@ -2633,7 +2651,12 @@ void CWallet::ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, in

/** @} */ // end of mapWallet


void MaybeResendWalletTxs()
{
for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
pwallet->ResendWalletTransactions();
}
}


/** @defgroup Actions
Expand Down
11 changes: 10 additions & 1 deletion src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,8 @@ class CWallet final : public CCryptoKeyStore, private interfaces::Chain::Notific
int64_t nNextResend = 0;
int64_t nLastResend = 0;
bool fBroadcastTransactions = false;
// Local time that the tip block was received. Used to schedule wallet rebroadcasts.
std::atomic<int64_t> m_best_block_time {0};

mutable bool fAnonymizableTallyCached = false;
mutable std::vector<CompactTallyItem> vecAnonymizableTallyCached;
Expand Down Expand Up @@ -983,6 +985,7 @@ class CWallet final : public CCryptoKeyStore, private interfaces::Chain::Notific
void TransactionAddedToMempool(const CTransactionRef& tx, int64_t nAcceptTime) override;
void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) override;
void BlockDisconnected(const CBlock& block) override;
void UpdatedBlockTip() override;
int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update);

struct ScanResult {
Expand All @@ -1003,7 +1006,7 @@ class CWallet final : public CCryptoKeyStore, private interfaces::Chain::Notific
ScanResult ScanForWalletTransactions(const uint256& first_block, const uint256& last_block, const WalletRescanReserver& reserver, bool fUpdate);
void TransactionRemovedFromMempool(const CTransactionRef &ptx, MemPoolRemovalReason reason) override;
void ReacceptWalletTransactions();
void ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, int64_t nBestBlockTime) override;
void ResendWalletTransactions();
struct Balance {
CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more
CAmount m_mine_untrusted_pending{0}; //!< Untrusted, but in mempool (pending)
Expand Down Expand Up @@ -1282,6 +1285,12 @@ class CWallet final : public CCryptoKeyStore, private interfaces::Chain::Notific
bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override;
};

/**
* Called periodically by the schedule thread. Prompts individual wallets to resend
* their transactions. Actual rebroadcast schedule is managed by the wallets themselves.
*/
void MaybeResendWalletTxs();

/** A key allocated from the key pool. */
class CReserveKey final : public CReserveScript
{
Expand Down
6 changes: 6 additions & 0 deletions test/functional/wallet_resendwallettransactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ def run_test(self):
self.log.info("Create a new transaction and wait until it's broadcast")
txid = int(node.sendtoaddress(node.getnewaddress(), 1), 16)

# Wallet rebroadcast is first scheduled 1 sec after startup (see
# nNextResend in ResendWalletTransactions()). Sleep for just over a
# second to be certain that it has been called before the first
# setmocktime call below.
time.sleep(1.1)

# Can take a few seconds due to transaction trickling
wait_until(lambda: node.p2p.tx_invs_received[txid] >= 1, lock=mininode_lock)

Expand Down

0 comments on commit f53b0ab

Please sign in to comment.