Bitcoin ABC 0.30.7
P2P Digital Currency
txpool.cpp
Go to the documentation of this file.
1// Copyright (c) 2021 The Bitcoin Core developers
2// Copyright (c) 2024 The Bitcoin developers
3// Distributed under the MIT software license, see the accompanying
4// file COPYING or http://www.opensource.org/licenses/mit-license.php.
5
6#include <txpool.h>
7
9#include <logging.h>
10#include <policy/policy.h>
11#include <random.h>
12
13#include <cassert>
14
15bool TxPool::AddTx(const CTransactionRef &tx, NodeId peer) {
17
18 const TxId &txid = tx->GetId();
19 if (m_pool_txs.count(txid)) {
20 return false;
21 }
22
23 // Ignore big transactions, to avoid a memory exhaustion attack. If a peer
24 // has a legitimate large transaction then we assume it will rebroadcast it
25 // later.
26 unsigned int sz = tx->GetTotalSize();
27 if (sz > MAX_STANDARD_TX_SIZE) {
29 "ignoring large %s tx (size: %u, hash: %s)\n", txKind, sz,
30 txid.ToString());
31 return false;
32 }
33
34 auto ret = m_pool_txs.emplace(
35 txid,
36 PoolTx{tx, peer, Now<NodeSeconds>() + expireTime, m_txs_list.size()});
37 assert(ret.second);
38 m_txs_list.push_back(ret.first);
39 for (const CTxIn &txin : tx->vin) {
40 m_outpoint_to_tx_it[txin.prevout].insert(ret.first);
41 }
42
44 "stored %s tx %s, size: %u (mapsz %u outsz %u)\n", txKind,
45 txid.ToString(), sz, m_pool_txs.size(),
46 m_outpoint_to_tx_it.size());
47 return true;
48}
49
50int TxPool::EraseTx(const TxId &txid) {
52 return EraseTxNoLock(txid);
53}
54
55int TxPool::EraseTxNoLock(const TxId &txid) {
57 std::map<TxId, PoolTx>::iterator it = m_pool_txs.find(txid);
58 if (it == m_pool_txs.end()) {
59 return 0;
60 }
61 for (const CTxIn &txin : it->second.tx->vin) {
62 auto itPrev = m_outpoint_to_tx_it.find(txin.prevout);
63 if (itPrev == m_outpoint_to_tx_it.end()) {
64 continue;
65 }
66 itPrev->second.erase(it);
67 if (itPrev->second.empty()) {
68 m_outpoint_to_tx_it.erase(itPrev);
69 }
70 }
71
72 size_t old_pos = it->second.list_pos;
73 assert(m_txs_list[old_pos] == it);
74 if (old_pos + 1 != m_txs_list.size()) {
75 // Unless we're deleting the last entry in m_txs_list, move the last
76 // entry to the position we're deleting.
77 auto it_last = m_txs_list.back();
78 m_txs_list[old_pos] = it_last;
79 it_last->second.list_pos = old_pos;
80 }
81
82 // Time spent in pool = difference between current and entry time.
83 // Entry time is equal to expireTime earlier than entry's expiry.
84 LogPrint(BCLog::TXPACKAGES, " removed %s tx %s after %ds\n", txKind,
85 txid.ToString(),
86 Ticks<std::chrono::seconds>(NodeClock::now() + expireTime -
87 it->second.nTimeExpire));
88 m_txs_list.pop_back();
89
90 m_pool_txs.erase(it);
91 return 1;
92}
93
96
97 m_peer_work_set.erase(peer);
98
99 int nErased = 0;
100 std::map<TxId, PoolTx>::iterator iter = m_pool_txs.begin();
101 while (iter != m_pool_txs.end()) {
102 // increment to avoid iterator becoming invalid after erasure
103 const auto &[txid, orphan] = *iter++;
104 if (orphan.fromPeer == peer) {
105 nErased += EraseTxNoLock(txid);
106 }
107 }
108 if (nErased > 0) {
110 "Erased %d %s transaction(s) from peer=%d\n", nErased, txKind,
111 peer);
112 }
113}
114
115unsigned int TxPool::LimitTxs(unsigned int max_txs, FastRandomContext &rng) {
116 LOCK(m_mutex);
117
118 unsigned int nEvicted = 0;
119 auto nNow{Now<NodeSeconds>()};
120 if (m_next_sweep <= nNow) {
121 // Sweep out expired orphan pool entries:
122 int nErased = 0;
123 auto nMinExpTime{nNow + expireTime - expireInterval};
124 std::map<TxId, PoolTx>::iterator iter = m_pool_txs.begin();
125 while (iter != m_pool_txs.end()) {
126 std::map<TxId, PoolTx>::iterator maybeErase = iter++;
127 if (maybeErase->second.nTimeExpire <= nNow) {
128 nErased += EraseTxNoLock(maybeErase->second.tx->GetId());
129 } else {
130 nMinExpTime =
131 std::min(maybeErase->second.nTimeExpire, nMinExpTime);
132 }
133 }
134 // Sweep again 5 minutes after the next entry that expires in order to
135 // batch the linear scan.
136 m_next_sweep = nMinExpTime + expireInterval;
137 if (nErased > 0) {
138 LogPrint(BCLog::TXPACKAGES, "Erased %d %s tx due to expiration\n",
139 nErased, txKind);
140 }
141 }
142 while (m_pool_txs.size() > max_txs) {
143 // Evict a random tx:
144 size_t randompos = rng.randrange(m_txs_list.size());
145 EraseTxNoLock(m_txs_list[randompos]->first);
146 ++nEvicted;
147 }
148 return nEvicted;
149}
150
151void TxPool::AddChildrenToWorkSet(const CTransaction &tx) {
152 LOCK(m_mutex);
153
154 for (size_t i = 0; i < tx.vout.size(); i++) {
155 const auto it_by_prev =
156 m_outpoint_to_tx_it.find(COutPoint(tx.GetId(), i));
157 if (it_by_prev != m_outpoint_to_tx_it.end()) {
158 for (const auto &elem : it_by_prev->second) {
159 // Get this peer's work set, emplacing an empty set if it didn't
160 // exist
161 std::set<TxId> &work_set =
162 m_peer_work_set.try_emplace(elem->second.fromPeer)
163 .first->second;
164 // Add this tx to the work set
165 work_set.insert(elem->first);
167 "added %s tx %s to peer %d workset\n", txKind,
168 tx.GetId().ToString(), elem->second.fromPeer);
169 }
170 }
171 }
172}
173
174bool TxPool::HaveTx(const TxId &txid) const {
175 LOCK(m_mutex);
176 return m_pool_txs.count(txid);
177}
178
180 LOCK(m_mutex);
181
182 const auto tx_it = m_pool_txs.find(txid);
183 if (tx_it != m_pool_txs.end()) {
184 return tx_it->second.tx;
185 }
186
187 return nullptr;
188}
189
190std::vector<CTransactionRef>
192 LOCK(m_mutex);
193
194 std::vector<CTransactionRef> conflictingTxs;
195 for (const auto &txin : tx->vin) {
196 auto itByPrev = m_outpoint_to_tx_it.find(txin.prevout);
197 if (itByPrev == m_outpoint_to_tx_it.end()) {
198 continue;
199 }
200
201 for (auto mi = itByPrev->second.begin(); mi != itByPrev->second.end();
202 ++mi) {
203 conflictingTxs.push_back((*mi)->second.tx);
204 }
205 }
206 return conflictingTxs;
207}
208
210 LOCK(m_mutex);
211
212 auto work_set_it = m_peer_work_set.find(peer);
213 if (work_set_it != m_peer_work_set.end()) {
214 auto &work_set = work_set_it->second;
215 while (!work_set.empty()) {
216 TxId txid = *work_set.begin();
217 work_set.erase(work_set.begin());
218
219 const auto tx_it = m_pool_txs.find(txid);
220 if (tx_it != m_pool_txs.end()) {
221 return tx_it->second.tx;
222 }
223 }
224 }
225 return nullptr;
226}
227
229 LOCK(m_mutex);
230
231 auto work_set_it = m_peer_work_set.find(peer);
232 if (work_set_it != m_peer_work_set.end()) {
233 auto &work_set = work_set_it->second;
234 return !work_set.empty();
235 }
236 return false;
237}
238
239void TxPool::EraseForBlock(const CBlock &block) {
240 LOCK(m_mutex);
241
242 std::vector<TxId> vTxErase;
243
244 for (const CTransactionRef &ptx : block.vtx) {
245 const CTransaction &tx = *ptx;
246
247 // Which pool entries must we evict?
248 for (const auto &txin : tx.vin) {
249 auto itByPrev = m_outpoint_to_tx_it.find(txin.prevout);
250 if (itByPrev == m_outpoint_to_tx_it.end()) {
251 continue;
252 }
253
254 for (auto mi = itByPrev->second.begin();
255 mi != itByPrev->second.end(); ++mi) {
256 const CTransaction &orphanTx = *(*mi)->second.tx;
257 const TxId &txid = orphanTx.GetId();
258 vTxErase.push_back(txid);
259 }
260 }
261 }
262
263 // Erase transactions included or precluded by this block
264 if (vTxErase.size()) {
265 int nErased = 0;
266 for (const auto &txid : vTxErase) {
267 nErased += EraseTxNoLock(txid);
268 }
269 LogPrint(
271 "Erased %d %s transaction(s) included or conflicted by block\n",
272 nErased, txKind);
273 }
274}
275
276std::vector<CTransactionRef>
278 NodeId nodeid) const {
279 LOCK(m_mutex);
280
281 // First construct a vector of iterators to ensure we do not return
282 // duplicates of the same tx and so we can sort by nTimeExpire.
283 std::vector<PoolTxMap::iterator> iters;
284
285 // For each output, get all entries spending this prevout, filtering for
286 // ones from the specified peer.
287 for (unsigned int i = 0; i < parent->vout.size(); i++) {
288 const auto it_by_prev =
289 m_outpoint_to_tx_it.find(COutPoint(parent->GetId(), i));
290 if (it_by_prev != m_outpoint_to_tx_it.end()) {
291 for (const auto &elem : it_by_prev->second) {
292 if (elem->second.fromPeer == nodeid) {
293 iters.emplace_back(elem);
294 }
295 }
296 }
297 }
298
299 // Sort by address so that duplicates can be deleted. At the same time, sort
300 // so that more recent txs (which expire later) come first. Break ties
301 // based on address, as nTimeExpire is quantified in seconds and it is
302 // possible for txs to have the same expiry.
303 std::sort(iters.begin(), iters.end(), [](const auto &lhs, const auto &rhs) {
304 if (lhs->second.nTimeExpire == rhs->second.nTimeExpire) {
305 return &(*lhs) < &(*rhs);
306 }
307 return lhs->second.nTimeExpire > rhs->second.nTimeExpire;
308 });
309 // Erase duplicates
310 iters.erase(std::unique(iters.begin(), iters.end()), iters.end());
311
312 // Convert to a vector of CTransactionRef
313 std::vector<CTransactionRef> children_found;
314 children_found.reserve(iters.size());
315 for (const auto &child_iter : iters) {
316 children_found.emplace_back(child_iter->second.tx);
317 }
318 return children_found;
319}
320
321std::vector<std::pair<CTransactionRef, NodeId>>
323 NodeId nodeid) const {
324 LOCK(m_mutex);
325
326 // First construct vector of iterators to ensure we do not return duplicates
327 // of the same tx.
328 std::vector<PoolTxMap::iterator> iters;
329
330 // For each output, get all entries spending this prevout, filtering for
331 // ones not from the specified peer.
332 for (unsigned int i = 0; i < parent->vout.size(); i++) {
333 const auto it_by_prev =
334 m_outpoint_to_tx_it.find(COutPoint(parent->GetId(), i));
335 if (it_by_prev != m_outpoint_to_tx_it.end()) {
336 for (const auto &elem : it_by_prev->second) {
337 if (elem->second.fromPeer != nodeid) {
338 iters.emplace_back(elem);
339 }
340 }
341 }
342 }
343
344 // Erase duplicates
345 std::sort(iters.begin(), iters.end(), IteratorComparator());
346 iters.erase(std::unique(iters.begin(), iters.end()), iters.end());
347
348 // Convert iterators to pair<CTransactionRef, NodeId>
349 std::vector<std::pair<CTransactionRef, NodeId>> children_found;
350 children_found.reserve(iters.size());
351 for (const auto &child_iter : iters) {
352 children_found.emplace_back(child_iter->second.tx,
353 child_iter->second.fromPeer);
354 }
355 return children_found;
356}
Definition: block.h:60
std::vector< CTransactionRef > vtx
Definition: block.h:63
Fast randomness source.
Definition: random.h:156
uint64_t randrange(uint64_t range) noexcept
Generate a random integer in the range [0..range).
Definition: random.h:231
CTransactionRef GetTx(const TxId &txid) const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Definition: txpool.cpp:179
int EraseTx(const TxId &txid) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Erase a tx by txid.
Definition: txpool.cpp:50
const std::chrono::seconds expireInterval
Minimum time between transactions expire time checks.
Definition: txpool.h:107
void EraseForPeer(NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Erase all txs announced by a peer (eg, after that peer disconnects)
Definition: txpool.cpp:94
std::vector< CTransactionRef > GetChildrenFromSamePeer(const CTransactionRef &parent, NodeId nodeid) const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Get all children that spend from this tx and were received from nodeid.
Definition: txpool.cpp:277
bool AddTx(const CTransactionRef &tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Add a new transaction to the pool.
Definition: txpool.cpp:15
int EraseTxNoLock(const TxId &txid) EXCLUSIVE_LOCKS_REQUIRED(m_mutex)
Erase a transaction by txid.
Definition: txpool.cpp:55
unsigned int LimitTxs(unsigned int max_txs, FastRandomContext &rng) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Limit the txs to the given maximum.
Definition: txpool.cpp:115
void EraseForBlock(const CBlock &block) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Erase all txs included in or invalidated by a new block.
Definition: txpool.cpp:239
const std::chrono::seconds expireTime
Expiration time for transactions.
Definition: txpool.h:105
std::vector< CTransactionRef > GetConflictTxs(const CTransactionRef &tx) const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Definition: txpool.cpp:191
void AddChildrenToWorkSet(const CTransaction &tx) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Add any tx that list a particular tx as a parent into the from peer's work set.
Definition: txpool.cpp:151
const std::string txKind
The transaction kind as string, used for logging.
Definition: txpool.h:103
Mutex m_mutex
Guards transactions.
Definition: txpool.h:110
bool HaveTx(const TxId &txid) const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Check if we already have an the transaction.
Definition: txpool.cpp:174
CTransactionRef GetTxToReconsider(NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Extract a transaction from a peer's work set.
Definition: txpool.cpp:209
std::vector< std::pair< CTransactionRef, NodeId > > GetChildrenFromDifferentPeer(const CTransactionRef &parent, NodeId nodeid) const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Get all children that spend from this tx but were not received from nodeid.
Definition: txpool.cpp:322
bool HaveTxToReconsider(NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Does this peer have any work to do?
Definition: txpool.cpp:228
uint8_t * begin()
Definition: uint256.h:85
std::string ToString() const
Definition: uint256.h:80
#define LogPrint(category,...)
Definition: logging.h:238
@ TXPACKAGES
Definition: logging.h:70
int64_t NodeId
Definition: nodeid.h:10
static constexpr unsigned int MAX_STANDARD_TX_SIZE
The maximum size for transactions we're willing to relay/mine.
Definition: policy.h:34
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:315
static time_point now() noexcept
Return current system time or mocked time, if set.
Definition: time.cpp:71
A TxId is the identifier of a transaction.
Definition: txid.h:14
#define LOCK(cs)
Definition: sync.h:306
AssertLockHeld(pool.cs)
assert(!tx.IsCoinBase())