Merge #15632: Remove ResendWalletTransactions from the Validation Interface

833d98ae0 [wallet] Remove unnecessary Chain::Lock parameter from ResendWalletTransactions (John Newbery)
52b760fc6 [wallet] Schedule tx rebroadcasts in wallet (John Newbery)
f463cd107 [wallet] Keep track of the best block time in the wallet (John Newbery)

Pull request description:

  Remove the `Broadcast()`/`ResendWalletTransactions()` notification from the Validation interface.

  Closes #15619. See that issue for discussion.

ACKs for commit 833d98:
  ryanofsky:
    utACK 833d98ae07. No changes, just rebase.

Tree-SHA512: 7689f2083608ebad8c95ab6692f7842754e1ebe5508bc926a89cad7105cce41007648f37341ba5feb92b30a7aa87acd3abf264a4f1874e35a7161553f6ff3595
This commit is contained in:
MeshCollider 2019-04-10 09:52:48 +12:00
commit 93de9abe6d
No known key found for this signature in database
GPG key ID: D300116E1C875A3D
9 changed files with 56 additions and 41 deletions

View file

@ -202,17 +202,11 @@ public:
{ {
m_notifications->BlockDisconnected(*block); m_notifications->BlockDisconnected(*block);
} }
void ChainStateFlushed(const CBlockLocator& locator) override { m_notifications->ChainStateFlushed(locator); } void UpdatedBlockTip(const CBlockIndex* index, const CBlockIndex* fork_index, bool is_ibd) override
void ResendWalletTransactions(int64_t best_block_time, CConnman*) override
{ {
// `cs_main` is always held when this method is called, so it is safe to m_notifications->UpdatedBlockTip();
// 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);
} }
void ChainStateFlushed(const CBlockLocator& locator) override { m_notifications->ChainStateFlushed(locator); }
Chain& m_chain; Chain& m_chain;
Chain::Notifications* m_notifications; Chain::Notifications* m_notifications;
}; };
@ -347,6 +341,7 @@ public:
CAmount maxTxFee() override { return ::maxTxFee; } CAmount maxTxFee() override { return ::maxTxFee; }
bool getPruneMode() override { return ::fPruneMode; } bool getPruneMode() override { return ::fPruneMode; }
bool p2pEnabled() override { return g_connman != nullptr; } bool p2pEnabled() override { return g_connman != nullptr; }
bool isReadyToBroadcast() override { return !::fImporting && !::fReindex && !IsInitialBlockDownload(); }
bool isInitialBlockDownload() override { return IsInitialBlockDownload(); } bool isInitialBlockDownload() override { return IsInitialBlockDownload(); }
bool shutdownRequested() override { return ShutdownRequested(); } bool shutdownRequested() override { return ShutdownRequested(); }
int64_t getAdjustedTime() override { return GetAdjustedTime(); } int64_t getAdjustedTime() override { return GetAdjustedTime(); }

View file

@ -223,6 +223,9 @@ public:
//! Check if p2p enabled. //! Check if p2p enabled.
virtual bool p2pEnabled() = 0; virtual bool p2pEnabled() = 0;
//! Check if the node is ready to broadcast transactions.
virtual bool isReadyToBroadcast() = 0;
//! Check if in IBD. //! Check if in IBD.
virtual bool isInitialBlockDownload() = 0; virtual bool isInitialBlockDownload() = 0;
@ -256,8 +259,8 @@ public:
virtual void TransactionRemovedFromMempool(const CTransactionRef& ptx) {} virtual void TransactionRemovedFromMempool(const CTransactionRef& ptx) {}
virtual void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& tx_conflicted) {} virtual void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& tx_conflicted) {}
virtual void BlockDisconnected(const CBlock& block) {} virtual void BlockDisconnected(const CBlock& block) {}
virtual void UpdatedBlockTip() {}
virtual void ChainStateFlushed(const CBlockLocator& locator) {} virtual void ChainStateFlushed(const CBlockLocator& locator) {}
virtual void ResendWalletTransactions(Lock& locked_chain, int64_t best_block_time) {}
}; };
//! Register handler for notifications. //! Register handler for notifications.

View file

@ -175,8 +175,6 @@ namespace {
/** Expiration-time ordered list of (expire time, relay map entry) pairs. */ /** 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::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 struct IteratorComparator
{ {
template<typename I> template<typename I>
@ -1121,8 +1119,6 @@ void PeerLogicValidation::UpdatedBlockTip(const CBlockIndex *pindexNew, const CB
}); });
connman->WakeMessageHandler(); connman->WakeMessageHandler();
} }
nTimeBestReceived = GetTime();
} }
/** /**
@ -3550,14 +3546,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 && !IsInitialBlockDownload())
{
GetMainSignals().Broadcast(nTimeBestReceived, connman);
}
// //
// Try sending block announcements via headers // Try sending block announcements via headers
// //

View file

@ -25,7 +25,6 @@ struct ValidationInterfaceConnections {
boost::signals2::scoped_connection BlockDisconnected; boost::signals2::scoped_connection BlockDisconnected;
boost::signals2::scoped_connection TransactionRemovedFromMempool; boost::signals2::scoped_connection TransactionRemovedFromMempool;
boost::signals2::scoped_connection ChainStateFlushed; boost::signals2::scoped_connection ChainStateFlushed;
boost::signals2::scoped_connection Broadcast;
boost::signals2::scoped_connection BlockChecked; boost::signals2::scoped_connection BlockChecked;
boost::signals2::scoped_connection NewPoWValidBlock; boost::signals2::scoped_connection NewPoWValidBlock;
}; };
@ -37,7 +36,6 @@ struct MainSignalsInstance {
boost::signals2::signal<void (const std::shared_ptr<const CBlock> &)> BlockDisconnected; boost::signals2::signal<void (const std::shared_ptr<const CBlock> &)> BlockDisconnected;
boost::signals2::signal<void (const CTransactionRef &)> TransactionRemovedFromMempool; boost::signals2::signal<void (const CTransactionRef &)> TransactionRemovedFromMempool;
boost::signals2::signal<void (const CBlockLocator &)> ChainStateFlushed; 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 CBlock&, const CValidationState&)> BlockChecked;
boost::signals2::signal<void (const CBlockIndex *, const std::shared_ptr<const CBlock>&)> NewPoWValidBlock; boost::signals2::signal<void (const CBlockIndex *, const std::shared_ptr<const CBlock>&)> NewPoWValidBlock;
@ -101,7 +99,6 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn) {
conns.BlockDisconnected = g_signals.m_internals->BlockDisconnected.connect(std::bind(&CValidationInterface::BlockDisconnected, pwalletIn, std::placeholders::_1)); conns.BlockDisconnected = g_signals.m_internals->BlockDisconnected.connect(std::bind(&CValidationInterface::BlockDisconnected, pwalletIn, std::placeholders::_1));
conns.TransactionRemovedFromMempool = g_signals.m_internals->TransactionRemovedFromMempool.connect(std::bind(&CValidationInterface::TransactionRemovedFromMempool, pwalletIn, std::placeholders::_1)); conns.TransactionRemovedFromMempool = g_signals.m_internals->TransactionRemovedFromMempool.connect(std::bind(&CValidationInterface::TransactionRemovedFromMempool, pwalletIn, std::placeholders::_1));
conns.ChainStateFlushed = g_signals.m_internals->ChainStateFlushed.connect(std::bind(&CValidationInterface::ChainStateFlushed, pwalletIn, std::placeholders::_1)); 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.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.NewPoWValidBlock = g_signals.m_internals->NewPoWValidBlock.connect(std::bind(&CValidationInterface::NewPoWValidBlock, pwalletIn, std::placeholders::_1, std::placeholders::_2));
} }
@ -175,10 +172,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) { void CMainSignals::BlockChecked(const CBlock& block, const CValidationState& state) {
m_internals->BlockChecked(block, state); m_internals->BlockChecked(block, state);
} }

View file

@ -134,8 +134,6 @@ protected:
* Called on a background thread. * Called on a background thread.
*/ */
virtual void ChainStateFlushed(const CBlockLocator &locator) {} 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. * Notifies listeners of a block validation result.
* If the provided CValidationState IsValid, the provided block * If the provided CValidationState IsValid, the provided block
@ -184,7 +182,6 @@ public:
void BlockConnected(const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex, const std::shared_ptr<const std::vector<CTransactionRef>> &); void BlockConnected(const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex, const std::shared_ptr<const std::vector<CTransactionRef>> &);
void BlockDisconnected(const std::shared_ptr<const CBlock> &); void BlockDisconnected(const std::shared_ptr<const CBlock> &);
void ChainStateFlushed(const CBlockLocator &); void ChainStateFlushed(const CBlockLocator &);
void Broadcast(int64_t nBestBlockTime, CConnman* connman);
void BlockChecked(const CBlock&, const CValidationState&); void BlockChecked(const CBlock&, const CValidationState&);
void NewPoWValidBlock(const CBlockIndex *, const std::shared_ptr<const CBlock>&); void NewPoWValidBlock(const CBlockIndex *, const std::shared_ptr<const CBlock>&);
}; };

View file

@ -211,8 +211,9 @@ void StartWallets(CScheduler& scheduler)
pwallet->postInitProcess(); pwallet->postInitProcess();
} }
// Run a thread to flush wallet periodically // Schedule periodic wallet flushes and tx rebroadcasts
scheduler.scheduleEvery(MaybeCompactWalletDB, 500); scheduler.scheduleEvery(MaybeCompactWalletDB, 500);
scheduler.scheduleEvery(MaybeResendWalletTxs, 1000);
} }
void FlushWallets() void FlushWallets()

View file

@ -1276,6 +1276,10 @@ void CWallet::BlockDisconnected(const CBlock& block) {
} }
} }
void CWallet::UpdatedBlockTip()
{
m_best_block_time = GetTime();
}
void CWallet::BlockUntilSyncedToCurrentChain() { void CWallet::BlockUntilSyncedToCurrentChain() {
@ -2110,8 +2114,21 @@ bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const
return CTransaction(tx1) == CTransaction(tx2); 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 // Do this infrequently and randomly to avoid giving away
// that these are our transactions. // that these are our transactions.
if (GetTime() < nNextResend || !fBroadcastTransactions) return; if (GetTime() < nNextResend || !fBroadcastTransactions) return;
@ -2120,12 +2137,13 @@ void CWallet::ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, in
if (fFirst) return; if (fFirst) return;
// Only do it if there's been a new block since last time // 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(); nLastResend = GetTime();
int relayed_tx_count = 0; int relayed_tx_count = 0;
{ // cs_wallet scope { // locked_chain and cs_wallet scope
auto locked_chain = chain().lock();
LOCK(cs_wallet); LOCK(cs_wallet);
// Relay transactions // Relay transactions
@ -2133,10 +2151,10 @@ void CWallet::ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, in
CWalletTx& wtx = item.second; CWalletTx& wtx = item.second;
// only rebroadcast unconfirmed txes older than 5 minutes before the // only rebroadcast unconfirmed txes older than 5 minutes before the
// last block was found // last block was found
if (wtx.nTimeReceived > nBestBlockTime - 5 * 60) continue; if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue;
relayed_tx_count += wtx.RelayWalletTransaction(locked_chain) ? 1 : 0; if (wtx.RelayWalletTransaction(*locked_chain)) ++relayed_tx_count;
} }
} // cs_wallet } // locked_chain and cs_wallet
if (relayed_tx_count > 0) { if (relayed_tx_count > 0) {
WalletLogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed_tx_count); WalletLogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed_tx_count);
@ -2145,7 +2163,12 @@ void CWallet::ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, in
/** @} */ // end of mapWallet /** @} */ // end of mapWallet
void MaybeResendWalletTxs()
{
for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
pwallet->ResendWalletTransactions();
}
}
/** @defgroup Actions /** @defgroup Actions

View file

@ -657,6 +657,8 @@ private:
int64_t nNextResend = 0; int64_t nNextResend = 0;
int64_t nLastResend = 0; int64_t nLastResend = 0;
bool fBroadcastTransactions = false; 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};
/** /**
* Used to keep track of spent outpoints, and * Used to keep track of spent outpoints, and
@ -926,6 +928,7 @@ public:
void TransactionAddedToMempool(const CTransactionRef& tx) override; void TransactionAddedToMempool(const CTransactionRef& tx) override;
void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) override; void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) override;
void BlockDisconnected(const CBlock& block) override; void BlockDisconnected(const CBlock& block) override;
void UpdatedBlockTip() override;
int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update); int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update);
struct ScanResult { struct ScanResult {
@ -946,7 +949,7 @@ public:
ScanResult ScanForWalletTransactions(const uint256& first_block, const uint256& last_block, const WalletRescanReserver& reserver, bool fUpdate); ScanResult ScanForWalletTransactions(const uint256& first_block, const uint256& last_block, const WalletRescanReserver& reserver, bool fUpdate);
void TransactionRemovedFromMempool(const CTransactionRef &ptx) override; void TransactionRemovedFromMempool(const CTransactionRef &ptx) override;
void ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, int64_t nBestBlockTime) override; void ResendWalletTransactions();
struct Balance { struct Balance {
CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more
CAmount m_mine_untrusted_pending{0}; //!< Untrusted, but in mempool (pending) CAmount m_mine_untrusted_pending{0}; //!< Untrusted, but in mempool (pending)
@ -1229,6 +1232,12 @@ public:
friend struct WalletTestingSetup; friend struct WalletTestingSetup;
}; };
/**
* 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. */ /** A key allocated from the key pool. */
class CReserveKey final : public CReserveScript class CReserveKey final : public CReserveScript
{ {

View file

@ -39,6 +39,12 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
self.log.info("Create a new transaction and wait until it's broadcast") self.log.info("Create a new transaction and wait until it's broadcast")
txid = int(node.sendtoaddress(node.getnewaddress(), 1), 16) 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 # Can take a few seconds due to transaction trickling
wait_until(lambda: node.p2p.tx_invs_received[txid] >= 1, lock=mininode_lock) wait_until(lambda: node.p2p.tx_invs_received[txid] >= 1, lock=mininode_lock)