Skip to content

Commit 9607277

Browse files
committed
Merge bitcoin/bitcoin#30111: locks: introduce mutex for tx download, flush rejection filters once per tip change
c85acce [refactor] delete EraseTxNoLock, just use EraseTx (glozow) 6ff8406 remove obsoleted TxOrphanage::m_mutex (glozow) 61745c7 lock m_recent_confirmed_transactions using m_tx_download_mutex (glozow) 723ea0f remove obsoleted hashRecentRejectsChainTip (glozow) 18a4355 update recent_rejects filters on ActiveTipChange (glozow) 36f170d add ValidationInterface::ActiveTipChange (glozow) 3eb1307 guard TxRequest and rejection caches with new mutex (glozow) Pull request description: See #27463 for full project tracking. This contains the first few commits of #30110, which require some thinking about thread safety in review. - Introduce a new `m_tx_download_mutex` which guards the transaction download data structures including `m_txrequest`, the rolling bloom filters, and `m_orphanage`. Later this should become the mutex guarding `TxDownloadManager`. - `m_txrequest` doesn't need to be guarded using `cs_main` anymore - `m_recent_confirmed_transactions` doesn't need its own lock anymore - `m_orphanage` doesn't need its own lock anymore - Adds a new `ValidationInterface` event, `ActiveTipChanged`, which is a synchronous callback whenever the tip of the active chainstate changes. - Flush `m_recent_rejects` and `m_recent_rejects_reconsiderable` on `ActiveTipChanged` just once instead of checking the tip every time `AlreadyHaveTx` is called. This should speed up calls to that function (no longer comparing a block hash each time) and removes the need to lock `cs_main` every time it is called. Motivation: - These data structures need synchronization. While we are holding `m_tx_download_mutex`, these should hold: - a tx hash in `m_txrequest` is not also in `m_orphanage` - a tx hash in `m_txrequest` is not also in `m_recent_rejects` or `m_recent_confirmed_transactions` - In the future, orphan resolution tracking should also be synchronized. If a tx has an entry in the orphan resolution tracker, it is also in `m_orphanage`, and not in `m_txrequest`, etc. - Currently, `cs_main` is used to e.g. sync accesses to `m_txrequest`. We should not broaden the scope of things it locks. - Currently, we need to know the current chainstate every time we call `AlreadyHaveTx` so we can decide whether we should update it. Every call compares the current tip hash with `hashRecentRejectsChainTip`. It is more efficient to have a validation interface callback that updates the rejection filters whenever the chain tip changes. ACKs for top commit: instagibbs: reACK c85acce dergoegge: Code review ACK c85acce theStack: Light code-review ACK c85acce hebasto: ACK c85acce, I have reviewed the code and it looks OK. Tree-SHA512: c3bd524b5de1cafc9a10770dadb484cc479d6d4c687d80dd0f176d339fd95f73b85cb44cb3b6b464d38a52e20feda00aa2a1da5a73339e31831687e4bd0aa0c5
2 parents 1518c08 + c85acce commit 9607277

File tree

7 files changed

+128
-124
lines changed

7 files changed

+128
-124
lines changed

src/net_processing.cpp

Lines changed: 81 additions & 64 deletions
Large diffs are not rendered by default.

src/test/orphanage_tests.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,13 @@ BOOST_FIXTURE_TEST_SUITE(orphanage_tests, TestingSetup)
2121
class TxOrphanageTest : public TxOrphanage
2222
{
2323
public:
24-
inline size_t CountOrphans() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
24+
inline size_t CountOrphans() const
2525
{
26-
LOCK(m_mutex);
2726
return m_orphans.size();
2827
}
2928

30-
CTransactionRef RandomOrphan() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
29+
CTransactionRef RandomOrphan()
3130
{
32-
LOCK(m_mutex);
3331
std::map<Wtxid, OrphanTx>::iterator it;
3432
it = m_orphans.lower_bound(Wtxid::FromUint256(InsecureRand256()));
3533
if (it == m_orphans.end())

src/txorphanage.cpp

Lines changed: 4 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ static constexpr auto ORPHAN_TX_EXPIRE_INTERVAL{5min};
2020

2121
bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer)
2222
{
23-
LOCK(m_mutex);
24-
2523
const Txid& hash = tx->GetHash();
2624
const Wtxid& wtxid = tx->GetWitnessHash();
2725
if (m_orphans.count(wtxid))
@@ -55,13 +53,6 @@ bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer)
5553

5654
int TxOrphanage::EraseTx(const Wtxid& wtxid)
5755
{
58-
LOCK(m_mutex);
59-
return EraseTxNoLock(wtxid);
60-
}
61-
62-
int TxOrphanage::EraseTxNoLock(const Wtxid& wtxid)
63-
{
64-
AssertLockHeld(m_mutex);
6556
std::map<Wtxid, OrphanTx>::iterator it = m_orphans.find(wtxid);
6657
if (it == m_orphans.end())
6758
return 0;
@@ -97,8 +88,6 @@ int TxOrphanage::EraseTxNoLock(const Wtxid& wtxid)
9788

9889
void TxOrphanage::EraseForPeer(NodeId peer)
9990
{
100-
LOCK(m_mutex);
101-
10291
m_peer_work_set.erase(peer);
10392

10493
int nErased = 0;
@@ -108,16 +97,14 @@ void TxOrphanage::EraseForPeer(NodeId peer)
10897
// increment to avoid iterator becoming invalid after erasure
10998
const auto& [wtxid, orphan] = *iter++;
11099
if (orphan.fromPeer == peer) {
111-
nErased += EraseTxNoLock(wtxid);
100+
nErased += EraseTx(wtxid);
112101
}
113102
}
114103
if (nErased > 0) LogPrint(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) from peer=%d\n", nErased, peer);
115104
}
116105

117106
void TxOrphanage::LimitOrphans(unsigned int max_orphans, FastRandomContext& rng)
118107
{
119-
LOCK(m_mutex);
120-
121108
unsigned int nEvicted = 0;
122109
auto nNow{Now<NodeSeconds>()};
123110
if (m_next_sweep <= nNow) {
@@ -129,7 +116,7 @@ void TxOrphanage::LimitOrphans(unsigned int max_orphans, FastRandomContext& rng)
129116
{
130117
std::map<Wtxid, OrphanTx>::iterator maybeErase = iter++;
131118
if (maybeErase->second.nTimeExpire <= nNow) {
132-
nErased += EraseTxNoLock(maybeErase->second.tx->GetWitnessHash());
119+
nErased += EraseTx(maybeErase->second.tx->GetWitnessHash());
133120
} else {
134121
nMinExpTime = std::min(maybeErase->second.nTimeExpire, nMinExpTime);
135122
}
@@ -142,17 +129,14 @@ void TxOrphanage::LimitOrphans(unsigned int max_orphans, FastRandomContext& rng)
142129
{
143130
// Evict a random orphan:
144131
size_t randompos = rng.randrange(m_orphan_list.size());
145-
EraseTxNoLock(m_orphan_list[randompos]->second.tx->GetWitnessHash());
132+
EraseTx(m_orphan_list[randompos]->second.tx->GetWitnessHash());
146133
++nEvicted;
147134
}
148135
if (nEvicted > 0) LogPrint(BCLog::TXPACKAGES, "orphanage overflow, removed %u tx\n", nEvicted);
149136
}
150137

151138
void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx)
152139
{
153-
LOCK(m_mutex);
154-
155-
156140
for (unsigned int i = 0; i < tx.vout.size(); i++) {
157141
const auto it_by_prev = m_outpoint_to_orphan_it.find(COutPoint(tx.GetHash(), i));
158142
if (it_by_prev != m_outpoint_to_orphan_it.end()) {
@@ -171,14 +155,11 @@ void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx)
171155

172156
bool TxOrphanage::HaveTx(const Wtxid& wtxid) const
173157
{
174-
LOCK(m_mutex);
175158
return m_orphans.count(wtxid);
176159
}
177160

178161
CTransactionRef TxOrphanage::GetTxToReconsider(NodeId peer)
179162
{
180-
LOCK(m_mutex);
181-
182163
auto work_set_it = m_peer_work_set.find(peer);
183164
if (work_set_it != m_peer_work_set.end()) {
184165
auto& work_set = work_set_it->second;
@@ -197,8 +178,6 @@ CTransactionRef TxOrphanage::GetTxToReconsider(NodeId peer)
197178

198179
bool TxOrphanage::HaveTxToReconsider(NodeId peer)
199180
{
200-
LOCK(m_mutex);
201-
202181
auto work_set_it = m_peer_work_set.find(peer);
203182
if (work_set_it != m_peer_work_set.end()) {
204183
auto& work_set = work_set_it->second;
@@ -209,8 +188,6 @@ bool TxOrphanage::HaveTxToReconsider(NodeId peer)
209188

210189
void TxOrphanage::EraseForBlock(const CBlock& block)
211190
{
212-
LOCK(m_mutex);
213-
214191
std::vector<Wtxid> vOrphanErase;
215192

216193
for (const CTransactionRef& ptx : block.vtx) {
@@ -231,16 +208,14 @@ void TxOrphanage::EraseForBlock(const CBlock& block)
231208
if (vOrphanErase.size()) {
232209
int nErased = 0;
233210
for (const auto& orphanHash : vOrphanErase) {
234-
nErased += EraseTxNoLock(orphanHash);
211+
nErased += EraseTx(orphanHash);
235212
}
236213
LogPrint(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) included or conflicted by block\n", nErased);
237214
}
238215
}
239216

240217
std::vector<CTransactionRef> TxOrphanage::GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId nodeid) const
241218
{
242-
LOCK(m_mutex);
243-
244219
// First construct a vector of iterators to ensure we do not return duplicates of the same tx
245220
// and so we can sort by nTimeExpire.
246221
std::vector<OrphanMap::iterator> iters;
@@ -281,8 +256,6 @@ std::vector<CTransactionRef> TxOrphanage::GetChildrenFromSamePeer(const CTransac
281256

282257
std::vector<std::pair<CTransactionRef, NodeId>> TxOrphanage::GetChildrenFromDifferentPeer(const CTransactionRef& parent, NodeId nodeid) const
283258
{
284-
LOCK(m_mutex);
285-
286259
// First construct vector of iterators to ensure we do not return duplicates of the same tx.
287260
std::vector<OrphanMap::iterator> iters;
288261

src/txorphanage.h

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,55 +22,51 @@
2222
class TxOrphanage {
2323
public:
2424
/** Add a new orphan transaction */
25-
bool AddTx(const CTransactionRef& tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
25+
bool AddTx(const CTransactionRef& tx, NodeId peer);
2626

2727
/** Check if we already have an orphan transaction (by wtxid only) */
28-
bool HaveTx(const Wtxid& wtxid) const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
28+
bool HaveTx(const Wtxid& wtxid) const;
2929

3030
/** Extract a transaction from a peer's work set
3131
* Returns nullptr if there are no transactions to work on.
3232
* Otherwise returns the transaction reference, and removes
3333
* it from the work set.
3434
*/
35-
CTransactionRef GetTxToReconsider(NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
35+
CTransactionRef GetTxToReconsider(NodeId peer);
3636

3737
/** Erase an orphan by wtxid */
38-
int EraseTx(const Wtxid& wtxid) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
38+
int EraseTx(const Wtxid& wtxid);
3939

4040
/** Erase all orphans announced by a peer (eg, after that peer disconnects) */
41-
void EraseForPeer(NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
41+
void EraseForPeer(NodeId peer);
4242

4343
/** Erase all orphans included in or invalidated by a new block */
44-
void EraseForBlock(const CBlock& block) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
44+
void EraseForBlock(const CBlock& block);
4545

4646
/** Limit the orphanage to the given maximum */
47-
void LimitOrphans(unsigned int max_orphans, FastRandomContext& rng) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
47+
void LimitOrphans(unsigned int max_orphans, FastRandomContext& rng);
4848

4949
/** Add any orphans that list a particular tx as a parent into the from peer's work set */
50-
void AddChildrenToWorkSet(const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);;
50+
void AddChildrenToWorkSet(const CTransaction& tx);
5151

5252
/** Does this peer have any work to do? */
53-
bool HaveTxToReconsider(NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);;
53+
bool HaveTxToReconsider(NodeId peer);
5454

5555
/** Get all children that spend from this tx and were received from nodeid. Sorted from most
5656
* recent to least recent. */
57-
std::vector<CTransactionRef> GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId nodeid) const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
57+
std::vector<CTransactionRef> GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId nodeid) const;
5858

5959
/** Get all children that spend from this tx but were not received from nodeid. Also return
6060
* which peer provided each tx. */
61-
std::vector<std::pair<CTransactionRef, NodeId>> GetChildrenFromDifferentPeer(const CTransactionRef& parent, NodeId nodeid) const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
61+
std::vector<std::pair<CTransactionRef, NodeId>> GetChildrenFromDifferentPeer(const CTransactionRef& parent, NodeId nodeid) const;
6262

6363
/** Return how many entries exist in the orphange */
64-
size_t Size() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
64+
size_t Size()
6565
{
66-
LOCK(m_mutex);
6766
return m_orphans.size();
6867
}
6968

7069
protected:
71-
/** Guards orphan transactions */
72-
mutable Mutex m_mutex;
73-
7470
struct OrphanTx {
7571
CTransactionRef tx;
7672
NodeId fromPeer;
@@ -80,10 +76,10 @@ class TxOrphanage {
8076

8177
/** Map from wtxid to orphan transaction record. Limited by
8278
* -maxorphantx/DEFAULT_MAX_ORPHAN_TRANSACTIONS */
83-
std::map<Wtxid, OrphanTx> m_orphans GUARDED_BY(m_mutex);
79+
std::map<Wtxid, OrphanTx> m_orphans;
8480

8581
/** Which peer provided the orphans that need to be reconsidered */
86-
std::map<NodeId, std::set<Wtxid>> m_peer_work_set GUARDED_BY(m_mutex);
82+
std::map<NodeId, std::set<Wtxid>> m_peer_work_set;
8783

8884
using OrphanMap = decltype(m_orphans);
8985

@@ -98,16 +94,13 @@ class TxOrphanage {
9894

9995
/** Index from the parents' COutPoint into the m_orphans. Used
10096
* to remove orphan transactions from the m_orphans */
101-
std::map<COutPoint, std::set<OrphanMap::iterator, IteratorComparator>> m_outpoint_to_orphan_it GUARDED_BY(m_mutex);
97+
std::map<COutPoint, std::set<OrphanMap::iterator, IteratorComparator>> m_outpoint_to_orphan_it;
10298

10399
/** Orphan transactions in vector for quick random eviction */
104-
std::vector<OrphanMap::iterator> m_orphan_list GUARDED_BY(m_mutex);
105-
106-
/** Erase an orphan by wtxid */
107-
int EraseTxNoLock(const Wtxid& wtxid) EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
100+
std::vector<OrphanMap::iterator> m_orphan_list;
108101

109102
/** Timestamp for the next scheduled sweep of expired orphans */
110-
NodeSeconds m_next_sweep GUARDED_BY(m_mutex){0s};
103+
NodeSeconds m_next_sweep{0s};
111104
};
112105

113106
#endif // BITCOIN_TXORPHANAGE_H

src/validation.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3473,6 +3473,7 @@ bool Chainstate::ActivateBestChain(BlockValidationState& state, std::shared_ptr<
34733473

34743474
{
34753475
LOCK(cs_main);
3476+
{
34763477
// Lock transaction pool for at least as long as it takes for connectTrace to be consumed
34773478
LOCK(MempoolMutex());
34783479
const bool was_in_ibd = m_chainman.IsInitialBlockDownload();
@@ -3549,7 +3550,12 @@ bool Chainstate::ActivateBestChain(BlockValidationState& state, std::shared_ptr<
35493550
break;
35503551
}
35513552
}
3552-
}
3553+
} // release MempoolMutex
3554+
// Notify external listeners about the new tip, even if pindexFork == pindexNewTip.
3555+
if (m_chainman.m_options.signals && this == &m_chainman.ActiveChainstate()) {
3556+
m_chainman.m_options.signals->ActiveTipChange(pindexNewTip, m_chainman.IsInitialBlockDownload());
3557+
}
3558+
} // release cs_main
35533559
// When we reach this point, we switched to a new tip (stored in pindexNewTip).
35543560

35553561
if (exited_ibd) {
@@ -3768,6 +3774,12 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
37683774
// distinguish user-initiated invalidateblock changes from other
37693775
// changes.
37703776
(void)m_chainman.GetNotifications().blockTip(GetSynchronizationState(m_chainman.IsInitialBlockDownload(), m_chainman.m_blockman.m_blockfiles_indexed), *to_mark_failed->pprev);
3777+
3778+
// Fire ActiveTipChange now for the current chain tip to make sure clients are notified.
3779+
// ActivateBestChain may call this as well, but not necessarily.
3780+
if (m_chainman.m_options.signals) {
3781+
m_chainman.m_options.signals->ActiveTipChange(m_chain.Tip(), m_chainman.IsInitialBlockDownload());
3782+
}
37713783
}
37723784
return true;
37733785
}

src/validationinterface.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@ void ValidationSignals::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlo
183183
fInitialDownload);
184184
}
185185

186+
void ValidationSignals::ActiveTipChange(const CBlockIndex *new_tip, bool is_ibd)
187+
{
188+
LOG_EVENT("%s: new block hash=%s block height=%d", __func__, new_tip->GetBlockHash().ToString(), new_tip->nHeight);
189+
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.ActiveTipChange(new_tip, is_ibd); });
190+
}
191+
186192
void ValidationSignals::TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t mempool_sequence)
187193
{
188194
auto event = [tx, mempool_sequence, this] {

src/validationinterface.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ class CValidationInterface {
6161
* Called on a background thread. Only called for the active chainstate.
6262
*/
6363
virtual void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) {}
64+
/**
65+
* Notifies listeners any time the block chain tip changes, synchronously.
66+
*/
67+
virtual void ActiveTipChange(const CBlockIndex* new_tip, bool is_ibd) {};
6468
/**
6569
* Notifies listeners of a transaction having been added to mempool.
6670
*
@@ -214,6 +218,7 @@ class ValidationSignals {
214218
void SyncWithValidationInterfaceQueue() LOCKS_EXCLUDED(cs_main);
215219

216220
void UpdatedBlockTip(const CBlockIndex *, const CBlockIndex *, bool fInitialDownload);
221+
void ActiveTipChange(const CBlockIndex*, bool);
217222
void TransactionAddedToMempool(const NewMempoolTransactionInfo&, uint64_t mempool_sequence);
218223
void TransactionRemovedFromMempool(const CTransactionRef&, MemPoolRemovalReason, uint64_t mempool_sequence);
219224
void MempoolTransactionsRemovedForBlock(const std::vector<RemovedMempoolTransactionInfo>&, unsigned int nBlockHeight);

0 commit comments

Comments
 (0)