/tmp/bitcoin/src/node/txorphanage.cpp
Line | Count | Source |
1 | | // Copyright (c) 2021-present The Bitcoin Core developers |
2 | | // Distributed under the MIT software license, see the accompanying |
3 | | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
4 | | |
5 | | #include <node/txorphanage.h> |
6 | | |
7 | | #include <consensus/validation.h> |
8 | | #include <policy/policy.h> |
9 | | #include <primitives/transaction.h> |
10 | | #include <util/feefrac.h> |
11 | | #include <util/hasher.h> |
12 | | #include <util/log.h> |
13 | | #include <util/time.h> |
14 | | |
15 | | #include <boost/multi_index/indexed_by.hpp> |
16 | | #include <boost/multi_index/ordered_index.hpp> |
17 | | #include <boost/multi_index/tag.hpp> |
18 | | #include <boost/multi_index_container.hpp> |
19 | | |
20 | | #include <cassert> |
21 | | #include <cmath> |
22 | | #include <unordered_map> |
23 | | |
24 | | namespace node { |
25 | | /** Minimum NodeId for lower_bound lookups (in practice, NodeIds start at 0). */ |
26 | | static constexpr NodeId MIN_PEER{std::numeric_limits<NodeId>::min()}; |
27 | | /** Maximum NodeId for upper_bound lookups. */ |
28 | | static constexpr NodeId MAX_PEER{std::numeric_limits<NodeId>::max()}; |
29 | | class TxOrphanageImpl final : public TxOrphanage { |
30 | | // Type alias for sequence numbers |
31 | | using SequenceNumber = uint64_t; |
32 | | /** Global sequence number, increment each time an announcement is added. */ |
33 | | SequenceNumber m_current_sequence{0}; |
34 | | |
35 | | /** One orphan announcement. Each announcement (i.e. combination of wtxid, nodeid) is unique. There may be multiple |
36 | | * announcements for the same tx, and multiple transactions with the same txid but different wtxid are possible. */ |
37 | | struct Announcement |
38 | | { |
39 | | const CTransactionRef m_tx; |
40 | | /** Which peer announced this tx */ |
41 | | const NodeId m_announcer; |
42 | | /** What order this transaction entered the orphanage. */ |
43 | | const SequenceNumber m_entry_sequence; |
44 | | /** Whether this tx should be reconsidered. Always starts out false. A peer's workset is the collection of all |
45 | | * announcements with m_reconsider=true. */ |
46 | | bool m_reconsider{false}; |
47 | | |
48 | | Announcement(const CTransactionRef& tx, NodeId peer, SequenceNumber seq) : |
49 | 979 | m_tx{tx}, m_announcer{peer}, m_entry_sequence{seq} |
50 | 979 | { } |
51 | | |
52 | | /** Get an approximation for "memory usage". The total memory is a function of the memory used to store the |
53 | | * transaction itself, each entry in m_orphans, and each entry in m_outpoint_to_orphan_wtxids. We use weight because |
54 | | * it is often higher than the actual memory usage of the transaction. This metric conveniently encompasses |
55 | | * m_outpoint_to_orphan_wtxids usage since input data does not get the witness discount, and makes it easier to |
56 | | * reason about each peer's limits using well-understood transaction attributes. */ |
57 | 4.35k | TxOrphanage::Usage GetMemUsage() const { |
58 | 4.35k | return GetTransactionWeight(*m_tx); |
59 | 4.35k | } |
60 | | |
61 | | /** Get an approximation of how much this transaction contributes to latency in EraseForBlock and EraseForPeer. |
62 | | * The computation time is a function of the number of entries in m_orphans (thus 1 per announcement) and the |
63 | | * number of entries in m_outpoint_to_orphan_wtxids (thus an additional 1 for every 10 inputs). Transactions with a |
64 | | * small number of inputs (9 or fewer) are counted as 1 to make it easier to reason about each peer's limits in |
65 | | * terms of "normal" transactions. */ |
66 | 4.35k | TxOrphanage::Count GetLatencyScore() const { |
67 | 4.35k | return 1 + (m_tx->vin.size() / 10); |
68 | 4.35k | } |
69 | | }; |
70 | | |
71 | | // Index by wtxid, then peer |
72 | | struct ByWtxid {}; |
73 | | using ByWtxidView = std::tuple<Wtxid, NodeId>; |
74 | | struct WtxidExtractor |
75 | | { |
76 | | using result_type = ByWtxidView; |
77 | | result_type operator()(const Announcement& ann) const |
78 | 43.4k | { |
79 | 43.4k | return ByWtxidView{ann.m_tx->GetWitnessHash(), ann.m_announcer}; |
80 | 43.4k | } |
81 | | }; |
82 | | |
83 | | // Sort by peer, then by whether it is ready to reconsider, then by recency. |
84 | | struct ByPeer {}; |
85 | | using ByPeerView = std::tuple<NodeId, bool, SequenceNumber>; |
86 | | struct ByPeerViewExtractor { |
87 | | using result_type = ByPeerView; |
88 | | result_type operator()(const Announcement& ann) const |
89 | 210k | { |
90 | 210k | return ByPeerView{ann.m_announcer, ann.m_reconsider, ann.m_entry_sequence}; |
91 | 210k | } |
92 | | }; |
93 | | |
94 | | using AnnouncementMap = boost::multi_index::multi_index_container< |
95 | | Announcement, |
96 | | boost::multi_index::indexed_by< |
97 | | boost::multi_index::ordered_unique<boost::multi_index::tag<ByWtxid>, WtxidExtractor>, |
98 | | boost::multi_index::ordered_unique<boost::multi_index::tag<ByPeer>, ByPeerViewExtractor> |
99 | | > |
100 | | >; |
101 | | template<typename Tag> |
102 | | using Iter = typename AnnouncementMap::index<Tag>::type::iterator; |
103 | | AnnouncementMap m_orphans; |
104 | | |
105 | | const TxOrphanage::Count m_max_global_latency_score{DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE}; |
106 | | const TxOrphanage::Usage m_reserved_usage_per_peer{DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER}; |
107 | | |
108 | | /** Number of unique orphans by wtxid. Less than or equal to the number of entries in m_orphans. */ |
109 | | TxOrphanage::Count m_unique_orphans{0}; |
110 | | |
111 | | /** Memory used by orphans (see Announcement::GetMemUsage()), deduplicated by wtxid. */ |
112 | | TxOrphanage::Usage m_unique_orphan_usage{0}; |
113 | | |
114 | | /** The sum of each unique transaction's latency scores including the inputs only (see Announcement::GetLatencyScore |
115 | | * but subtract 1 for the announcements themselves). The total orphanage's latency score is given by this value + |
116 | | * the number of entries in m_orphans. */ |
117 | | TxOrphanage::Count m_unique_rounded_input_scores{0}; |
118 | | |
119 | | /** Index from the parents' outputs to wtxids that exist in m_orphans. Used to find children of |
120 | | * a transaction that can be reconsidered and to remove entries that conflict with a block.*/ |
121 | | std::unordered_map<COutPoint, std::set<Wtxid>, SaltedOutpointHasher> m_outpoint_to_orphan_wtxids; |
122 | | |
123 | | /** Set of Wtxids for which (exactly) one announcement with m_reconsider=true exists. */ |
124 | | std::set<Wtxid> m_reconsiderable_wtxids; |
125 | | |
126 | | struct PeerDoSInfo { |
127 | | TxOrphanage::Usage m_total_usage{0}; |
128 | | TxOrphanage::Count m_count_announcements{0}; |
129 | | TxOrphanage::Count m_total_latency_score{0}; |
130 | | bool operator==(const PeerDoSInfo& other) const |
131 | 17 | { |
132 | 17 | return m_total_usage == other.m_total_usage && |
133 | 17 | m_count_announcements == other.m_count_announcements && |
134 | 17 | m_total_latency_score == other.m_total_latency_score; |
135 | 17 | } |
136 | | void Add(const Announcement& ann) |
137 | 976 | { |
138 | 976 | m_total_usage += ann.GetMemUsage(); |
139 | 976 | m_total_latency_score += ann.GetLatencyScore(); |
140 | 976 | m_count_announcements += 1; |
141 | 976 | } |
142 | | bool Subtract(const Announcement& ann) |
143 | 723 | { |
144 | 723 | Assume(m_total_usage >= ann.GetMemUsage()); |
145 | 723 | Assume(m_total_latency_score >= ann.GetLatencyScore()); |
146 | 723 | Assume(m_count_announcements >= 1); |
147 | | |
148 | 723 | m_total_usage -= ann.GetMemUsage(); |
149 | 723 | m_total_latency_score -= ann.GetLatencyScore(); |
150 | 723 | m_count_announcements -= 1; |
151 | 723 | return m_count_announcements == 0; |
152 | 723 | } |
153 | | /** There are 2 DoS scores: |
154 | | * - Latency score (ratio of total latency score / max allowed latency score) |
155 | | * - Memory score (ratio of total memory usage / max allowed memory usage). |
156 | | * |
157 | | * If the peer is using more than the allowed for either resource, its DoS score is > 1. |
158 | | * A peer having a DoS score > 1 does not necessarily mean that something is wrong, since we |
159 | | * do not trim unless the orphanage exceeds global limits, but it means that this peer will |
160 | | * be selected for trimming sooner. If the global latency score or global memory usage |
161 | | * limits are exceeded, it must be that there is a peer whose DoS score > 1. */ |
162 | | FeeFrac GetDosScore(TxOrphanage::Count max_peer_latency_score, TxOrphanage::Usage max_peer_memory) const |
163 | 1.68k | { |
164 | 1.68k | assert(max_peer_latency_score > 0); |
165 | 1.68k | assert(max_peer_memory > 0); |
166 | 1.68k | const FeeFrac latency_score(m_total_latency_score, max_peer_latency_score); |
167 | 1.68k | const FeeFrac mem_score(m_total_usage, max_peer_memory); |
168 | 1.68k | return std::max<ByRatioNegSize<FeeFrac>>(latency_score, mem_score); |
169 | 1.68k | } |
170 | | }; |
171 | | /** Store per-peer statistics. Used to determine each peer's DoS score. The size of this map is used to determine the |
172 | | * number of peers and thus global {latency score, memory} limits. */ |
173 | | std::unordered_map<NodeId, PeerDoSInfo> m_peer_orphanage_info; |
174 | | |
175 | | /** Erase from m_orphans and update m_peer_orphanage_info. */ |
176 | | template<typename Tag> |
177 | | void Erase(Iter<Tag> it); |
178 | | |
179 | | /** Erase by wtxid. */ |
180 | | bool EraseTxInternal(const Wtxid& wtxid); |
181 | | |
182 | | /** Check if there is exactly one announcement with the same wtxid as it. */ |
183 | | bool IsUnique(Iter<ByWtxid> it) const; |
184 | | |
185 | | /** Check if the orphanage needs trimming. */ |
186 | | bool NeedsTrim() const; |
187 | | |
188 | | /** Limit the orphanage to MaxGlobalLatencyScore and MaxGlobalUsage. */ |
189 | | void LimitOrphans(); |
190 | | |
191 | | public: |
192 | 1.33k | TxOrphanageImpl() = default; |
193 | | TxOrphanageImpl(Count max_global_latency_score, Usage reserved_peer_usage) : |
194 | 6 | m_max_global_latency_score{max_global_latency_score}, |
195 | 6 | m_reserved_usage_per_peer{reserved_peer_usage} |
196 | 6 | {} |
197 | 1.34k | ~TxOrphanageImpl() noexcept override = default; |
198 | | |
199 | | TxOrphanage::Count CountAnnouncements() const override; |
200 | | TxOrphanage::Count CountUniqueOrphans() const override; |
201 | | TxOrphanage::Count AnnouncementsFromPeer(NodeId peer) const override; |
202 | | TxOrphanage::Count LatencyScoreFromPeer(NodeId peer) const override; |
203 | | TxOrphanage::Usage UsageByPeer(NodeId peer) const override; |
204 | | |
205 | | TxOrphanage::Count MaxGlobalLatencyScore() const override; |
206 | | TxOrphanage::Count TotalLatencyScore() const override; |
207 | | TxOrphanage::Usage ReservedPeerUsage() const override; |
208 | | |
209 | | /** Maximum allowed (deduplicated) latency score for all transactions (see Announcement::GetLatencyScore()). Dynamic |
210 | | * based on number of peers. Each peer has an equal amount, but the global maximum latency score stays constant. The |
211 | | * number of peers times MaxPeerLatencyScore() (rounded) adds up to MaxGlobalLatencyScore(). As long as every peer's |
212 | | * m_total_latency_score / MaxPeerLatencyScore() < 1, MaxGlobalLatencyScore() is not exceeded. */ |
213 | | TxOrphanage::Count MaxPeerLatencyScore() const override; |
214 | | |
215 | | /** Maximum allowed (deduplicated) memory usage for all transactions (see Announcement::GetMemUsage()). Dynamic based |
216 | | * on number of peers. More peers means more allowed memory usage. The number of peers times ReservedPeerUsage() |
217 | | * adds up to MaxGlobalUsage(). As long as every peer's m_total_usage / ReservedPeerUsage() < 1, MaxGlobalUsage() is |
218 | | * not exceeded. */ |
219 | | TxOrphanage::Usage MaxGlobalUsage() const override; |
220 | | |
221 | | bool AddTx(const CTransactionRef& tx, NodeId peer) override; |
222 | | bool AddAnnouncer(const Wtxid& wtxid, NodeId peer) override; |
223 | | CTransactionRef GetTx(const Wtxid& wtxid) const override; |
224 | | bool HaveTx(const Wtxid& wtxid) const override; |
225 | | bool HaveTxFromPeer(const Wtxid& wtxid, NodeId peer) const override; |
226 | | CTransactionRef GetTxToReconsider(NodeId peer) override; |
227 | | bool EraseTx(const Wtxid& wtxid) override; |
228 | | void EraseForPeer(NodeId peer) override; |
229 | | void EraseForBlock(const CBlock& block) override; |
230 | | std::vector<std::pair<Wtxid, NodeId>> AddChildrenToWorkSet(const CTransaction& tx, FastRandomContext& rng) override; |
231 | | bool HaveTxToReconsider(NodeId peer) override; |
232 | | std::vector<CTransactionRef> GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId nodeid) const override; |
233 | | std::vector<OrphanInfo> GetOrphanTransactions() const override; |
234 | | TxOrphanage::Usage TotalOrphanUsage() const override; |
235 | | void SanityCheck() const override; |
236 | | }; |
237 | | |
238 | | template<typename Tag> |
239 | | void TxOrphanageImpl::Erase(Iter<Tag> it) |
240 | 723 | { |
241 | | // Update m_peer_orphanage_info and clean up entries if they point to an empty struct. |
242 | | // This means peers that are not storing any orphans do not have an entry in |
243 | | // m_peer_orphanage_info (they can be added back later if they announce another orphan) and |
244 | | // ensures disconnected peers are not tracked forever. |
245 | 723 | auto peer_it = m_peer_orphanage_info.find(it->m_announcer); |
246 | 723 | Assume(peer_it != m_peer_orphanage_info.end()); |
247 | 723 | if (peer_it->second.Subtract(*it)) m_peer_orphanage_info.erase(peer_it); |
248 | | |
249 | 723 | if (IsUnique(m_orphans.project<ByWtxid>(it))) { |
250 | 682 | m_unique_orphans -= 1; |
251 | 682 | m_unique_rounded_input_scores -= it->GetLatencyScore() - 1; |
252 | 682 | m_unique_orphan_usage -= it->GetMemUsage(); |
253 | | |
254 | | // Remove references in m_outpoint_to_orphan_wtxids |
255 | 682 | const auto& wtxid{it->m_tx->GetWitnessHash()}; |
256 | 709 | for (const auto& input : it->m_tx->vin) { |
257 | 709 | auto it_prev = m_outpoint_to_orphan_wtxids.find(input.prevout); |
258 | 709 | if (it_prev != m_outpoint_to_orphan_wtxids.end()) { |
259 | 709 | it_prev->second.erase(wtxid); |
260 | | // Clean up keys if they point to an empty set. |
261 | 709 | if (it_prev->second.empty()) { |
262 | 702 | m_outpoint_to_orphan_wtxids.erase(it_prev); |
263 | 702 | } |
264 | 709 | } |
265 | 709 | } |
266 | 682 | } |
267 | | |
268 | | // If this was the (unique) reconsiderable announcement for its wtxid, then the wtxid won't |
269 | | // have any reconsiderable announcements left after erasing. |
270 | 723 | if (it->m_reconsider) m_reconsiderable_wtxids.erase(it->m_tx->GetWitnessHash()); |
271 | | |
272 | 723 | m_orphans.get<Tag>().erase(it); |
273 | 723 | } void node::TxOrphanageImpl::Erase<node::TxOrphanageImpl::ByWtxid>(boost::multi_index::multi_index_container<node::TxOrphanageImpl::Announcement, boost::multi_index::indexed_by<boost::multi_index::ordered_unique<boost::multi_index::tag<node::TxOrphanageImpl::ByWtxid, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na>, node::TxOrphanageImpl::WtxidExtractor, mpl_::na>, boost::multi_index::ordered_unique<boost::multi_index::tag<node::TxOrphanageImpl::ByPeer, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na>, node::TxOrphanageImpl::ByPeerViewExtractor, mpl_::na>, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na>, std::allocator<node::TxOrphanageImpl::Announcement>>::index<node::TxOrphanageImpl::ByWtxid>::type::iterator) Line | Count | Source | 240 | 155 | { | 241 | | // Update m_peer_orphanage_info and clean up entries if they point to an empty struct. | 242 | | // This means peers that are not storing any orphans do not have an entry in | 243 | | // m_peer_orphanage_info (they can be added back later if they announce another orphan) and | 244 | | // ensures disconnected peers are not tracked forever. | 245 | 155 | auto peer_it = m_peer_orphanage_info.find(it->m_announcer); | 246 | 155 | Assume(peer_it != m_peer_orphanage_info.end()); | 247 | 155 | if (peer_it->second.Subtract(*it)) m_peer_orphanage_info.erase(peer_it); | 248 | | | 249 | 155 | if (IsUnique(m_orphans.project<ByWtxid>(it))) { | 250 | 153 | m_unique_orphans -= 1; | 251 | 153 | m_unique_rounded_input_scores -= it->GetLatencyScore() - 1; | 252 | 153 | m_unique_orphan_usage -= it->GetMemUsage(); | 253 | | | 254 | | // Remove references in m_outpoint_to_orphan_wtxids | 255 | 153 | const auto& wtxid{it->m_tx->GetWitnessHash()}; | 256 | 169 | for (const auto& input : it->m_tx->vin) { | 257 | 169 | auto it_prev = m_outpoint_to_orphan_wtxids.find(input.prevout); | 258 | 169 | if (it_prev != m_outpoint_to_orphan_wtxids.end()) { | 259 | 169 | it_prev->second.erase(wtxid); | 260 | | // Clean up keys if they point to an empty set. | 261 | 169 | if (it_prev->second.empty()) { | 262 | 165 | m_outpoint_to_orphan_wtxids.erase(it_prev); | 263 | 165 | } | 264 | 169 | } | 265 | 169 | } | 266 | 153 | } | 267 | | | 268 | | // If this was the (unique) reconsiderable announcement for its wtxid, then the wtxid won't | 269 | | // have any reconsiderable announcements left after erasing. | 270 | 155 | if (it->m_reconsider) m_reconsiderable_wtxids.erase(it->m_tx->GetWitnessHash()); | 271 | | | 272 | 155 | m_orphans.get<Tag>().erase(it); | 273 | 155 | } |
void node::TxOrphanageImpl::Erase<node::TxOrphanageImpl::ByPeer>(boost::multi_index::multi_index_container<node::TxOrphanageImpl::Announcement, boost::multi_index::indexed_by<boost::multi_index::ordered_unique<boost::multi_index::tag<node::TxOrphanageImpl::ByWtxid, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na>, node::TxOrphanageImpl::WtxidExtractor, mpl_::na>, boost::multi_index::ordered_unique<boost::multi_index::tag<node::TxOrphanageImpl::ByPeer, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na>, node::TxOrphanageImpl::ByPeerViewExtractor, mpl_::na>, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na>, std::allocator<node::TxOrphanageImpl::Announcement>>::index<node::TxOrphanageImpl::ByPeer>::type::iterator) Line | Count | Source | 240 | 568 | { | 241 | | // Update m_peer_orphanage_info and clean up entries if they point to an empty struct. | 242 | | // This means peers that are not storing any orphans do not have an entry in | 243 | | // m_peer_orphanage_info (they can be added back later if they announce another orphan) and | 244 | | // ensures disconnected peers are not tracked forever. | 245 | 568 | auto peer_it = m_peer_orphanage_info.find(it->m_announcer); | 246 | 568 | Assume(peer_it != m_peer_orphanage_info.end()); | 247 | 568 | if (peer_it->second.Subtract(*it)) m_peer_orphanage_info.erase(peer_it); | 248 | | | 249 | 568 | if (IsUnique(m_orphans.project<ByWtxid>(it))) { | 250 | 529 | m_unique_orphans -= 1; | 251 | 529 | m_unique_rounded_input_scores -= it->GetLatencyScore() - 1; | 252 | 529 | m_unique_orphan_usage -= it->GetMemUsage(); | 253 | | | 254 | | // Remove references in m_outpoint_to_orphan_wtxids | 255 | 529 | const auto& wtxid{it->m_tx->GetWitnessHash()}; | 256 | 540 | for (const auto& input : it->m_tx->vin) { | 257 | 540 | auto it_prev = m_outpoint_to_orphan_wtxids.find(input.prevout); | 258 | 540 | if (it_prev != m_outpoint_to_orphan_wtxids.end()) { | 259 | 540 | it_prev->second.erase(wtxid); | 260 | | // Clean up keys if they point to an empty set. | 261 | 540 | if (it_prev->second.empty()) { | 262 | 537 | m_outpoint_to_orphan_wtxids.erase(it_prev); | 263 | 537 | } | 264 | 540 | } | 265 | 540 | } | 266 | 529 | } | 267 | | | 268 | | // If this was the (unique) reconsiderable announcement for its wtxid, then the wtxid won't | 269 | | // have any reconsiderable announcements left after erasing. | 270 | 568 | if (it->m_reconsider) m_reconsiderable_wtxids.erase(it->m_tx->GetWitnessHash()); | 271 | | | 272 | 568 | m_orphans.get<Tag>().erase(it); | 273 | 568 | } |
|
274 | | |
275 | | bool TxOrphanageImpl::IsUnique(Iter<ByWtxid> it) const |
276 | 1.69k | { |
277 | | // Iterators ByWtxid are sorted by wtxid, so check if neighboring elements have the same wtxid. |
278 | 1.69k | auto& index = m_orphans.get<ByWtxid>(); |
279 | 1.69k | if (it == index.end()) return false; |
280 | 1.69k | if (std::next(it) != index.end() && std::next(it)->m_tx->GetWitnessHash() == it->m_tx->GetWitnessHash()) return false; |
281 | 1.65k | if (it != index.begin() && std::prev(it)->m_tx->GetWitnessHash() == it->m_tx->GetWitnessHash()) return false; |
282 | 1.59k | return true; |
283 | 1.65k | } |
284 | | |
285 | | TxOrphanage::Usage TxOrphanageImpl::UsageByPeer(NodeId peer) const |
286 | 1.78k | { |
287 | 1.78k | auto it = m_peer_orphanage_info.find(peer); |
288 | 1.78k | return it == m_peer_orphanage_info.end() ? 0 : it->second.m_total_usage; |
289 | 1.78k | } |
290 | | |
291 | 34 | TxOrphanage::Count TxOrphanageImpl::CountAnnouncements() const { return m_orphans.size(); } |
292 | | |
293 | 14.5k | TxOrphanage::Usage TxOrphanageImpl::TotalOrphanUsage() const { return m_unique_orphan_usage; } |
294 | | |
295 | 1.20k | TxOrphanage::Count TxOrphanageImpl::CountUniqueOrphans() const { return m_unique_orphans; } |
296 | | |
297 | 73 | TxOrphanage::Count TxOrphanageImpl::AnnouncementsFromPeer(NodeId peer) const { |
298 | 73 | auto it = m_peer_orphanage_info.find(peer); |
299 | 73 | return it == m_peer_orphanage_info.end() ? 0 : it->second.m_count_announcements; |
300 | 73 | } |
301 | | |
302 | 2 | TxOrphanage::Count TxOrphanageImpl::LatencyScoreFromPeer(NodeId peer) const { |
303 | 2 | auto it = m_peer_orphanage_info.find(peer); |
304 | 2 | return it == m_peer_orphanage_info.end() ? 0 : it->second.m_total_latency_score; |
305 | 2 | } |
306 | | |
307 | | bool TxOrphanageImpl::AddTx(const CTransactionRef& tx, NodeId peer) |
308 | 984 | { |
309 | 984 | const auto& wtxid{tx->GetWitnessHash()}; |
310 | 984 | const auto& txid{tx->GetHash()}; |
311 | | |
312 | | // Ignore transactions above max standard size to avoid a send-big-orphans memory exhaustion attack. |
313 | 984 | TxOrphanage::Usage sz = GetTransactionWeight(*tx); |
314 | 984 | if (sz > MAX_STANDARD_TX_WEIGHT) { |
315 | 11 | LogDebug(BCLog::TXPACKAGES, "ignoring large orphan tx (size: %u, txid: %s, wtxid: %s)\n", sz, txid.ToString(), wtxid.ToString()); |
316 | 11 | return false; |
317 | 11 | } |
318 | | |
319 | | // We will return false if the tx already exists under a different peer. |
320 | 973 | const bool brand_new{!HaveTx(wtxid)}; |
321 | | |
322 | 973 | auto [iter, inserted] = m_orphans.get<ByWtxid>().emplace(tx, peer, m_current_sequence); |
323 | | // If the announcement (same wtxid, same peer) already exists, emplacement fails. Return false. |
324 | 973 | if (!inserted) return false; |
325 | | |
326 | 971 | ++m_current_sequence; |
327 | 971 | auto& peer_info = m_peer_orphanage_info.try_emplace(peer).first->second; |
328 | 971 | peer_info.Add(*iter); |
329 | | |
330 | | // Add links in m_outpoint_to_orphan_wtxids |
331 | 971 | if (brand_new) { |
332 | 1.60k | for (const auto& input : tx->vin) { |
333 | 1.60k | auto& wtxids_for_prevout = m_outpoint_to_orphan_wtxids.try_emplace(input.prevout).first->second; |
334 | 1.60k | wtxids_for_prevout.emplace(wtxid); |
335 | 1.60k | } |
336 | | |
337 | 917 | m_unique_orphans += 1; |
338 | 917 | m_unique_orphan_usage += iter->GetMemUsage(); |
339 | 917 | m_unique_rounded_input_scores += iter->GetLatencyScore() - 1; |
340 | | |
341 | 917 | LogDebug(BCLog::TXPACKAGES, "stored orphan tx %s (wtxid=%s), weight: %u (mapsz %u outsz %u)\n", |
342 | 917 | txid.ToString(), wtxid.ToString(), sz, m_orphans.size(), m_outpoint_to_orphan_wtxids.size()); |
343 | 917 | Assume(IsUnique(iter)); |
344 | 917 | } else { |
345 | 54 | LogDebug(BCLog::TXPACKAGES, "added peer=%d as announcer of orphan tx %s (wtxid=%s)\n", |
346 | 54 | peer, txid.ToString(), wtxid.ToString()); |
347 | 54 | Assume(!IsUnique(iter)); |
348 | 54 | } |
349 | | |
350 | | // DoS prevention: do not allow m_orphanage to grow unbounded (see CVE-2012-3789) |
351 | 971 | LimitOrphans(); |
352 | 971 | return brand_new; |
353 | 973 | } |
354 | | |
355 | | bool TxOrphanageImpl::AddAnnouncer(const Wtxid& wtxid, NodeId peer) |
356 | 6 | { |
357 | 6 | auto& index_by_wtxid = m_orphans.get<ByWtxid>(); |
358 | 6 | auto it = index_by_wtxid.lower_bound(ByWtxidView{wtxid, MIN_PEER}); |
359 | | |
360 | | // Do nothing if this transaction isn't already present. We can't create an entry if we don't |
361 | | // have the tx data. |
362 | 6 | if (it == index_by_wtxid.end()) return false; |
363 | 6 | if (it->m_tx->GetWitnessHash() != wtxid) return false; |
364 | | |
365 | | // Add another announcement, copying the CTransactionRef from one that already exists. |
366 | 6 | const auto& ptx = it->m_tx; |
367 | 6 | auto [iter, inserted] = index_by_wtxid.emplace(ptx, peer, m_current_sequence); |
368 | | // If the announcement (same wtxid, same peer) already exists, emplacement fails. Return false. |
369 | 6 | if (!inserted) return false; |
370 | | |
371 | 5 | ++m_current_sequence; |
372 | 5 | auto& peer_info = m_peer_orphanage_info.try_emplace(peer).first->second; |
373 | 5 | peer_info.Add(*iter); |
374 | | |
375 | 5 | const auto& txid = ptx->GetHash(); |
376 | 5 | LogDebug(BCLog::TXPACKAGES, "added peer=%d as announcer of orphan tx %s (wtxid=%s)\n", |
377 | 5 | peer, txid.ToString(), wtxid.ToString()); |
378 | | |
379 | 5 | Assume(!IsUnique(iter)); |
380 | | |
381 | | // DoS prevention: do not allow m_orphanage to grow unbounded (see CVE-2012-3789) |
382 | 5 | LimitOrphans(); |
383 | 5 | return true; |
384 | 6 | } |
385 | | |
386 | | bool TxOrphanageImpl::EraseTxInternal(const Wtxid& wtxid) |
387 | 12.3k | { |
388 | 12.3k | auto& index_by_wtxid = m_orphans.get<ByWtxid>(); |
389 | | |
390 | 12.3k | auto it = index_by_wtxid.lower_bound(ByWtxidView{wtxid, MIN_PEER}); |
391 | 12.3k | if (it == index_by_wtxid.end() || it->m_tx->GetWitnessHash() != wtxid) return false; |
392 | | |
393 | 153 | auto it_end = index_by_wtxid.upper_bound(ByWtxidView{wtxid, MAX_PEER}); |
394 | 153 | unsigned int num_ann{0}; |
395 | 153 | const auto txid = it->m_tx->GetHash(); |
396 | 308 | while (it != it_end) { |
397 | 155 | Assume(it->m_tx->GetWitnessHash() == wtxid); |
398 | 155 | Erase<ByWtxid>(it++); |
399 | 155 | num_ann += 1; |
400 | 155 | } |
401 | 153 | LogDebug(BCLog::TXPACKAGES, "removed orphan tx %s (wtxid=%s) (%u announcements)\n", txid.ToString(), wtxid.ToString(), num_ann); |
402 | | |
403 | 153 | return true; |
404 | 12.3k | } |
405 | | |
406 | | bool TxOrphanageImpl::EraseTx(const Wtxid& wtxid) |
407 | 12.2k | { |
408 | 12.2k | const auto ret = EraseTxInternal(wtxid); |
409 | | |
410 | | // Deletions can cause the orphanage's MaxGlobalUsage to decrease, so we may need to trim here. |
411 | 12.2k | LimitOrphans(); |
412 | | |
413 | 12.2k | return ret; |
414 | 12.2k | } |
415 | | |
416 | | /** Erase all entries by this peer. */ |
417 | | void TxOrphanageImpl::EraseForPeer(NodeId peer) |
418 | 1.79k | { |
419 | 1.79k | auto& index_by_peer = m_orphans.get<ByPeer>(); |
420 | 1.79k | auto it = index_by_peer.lower_bound(ByPeerView{peer, false, 0}); |
421 | 1.79k | if (it == index_by_peer.end() || it->m_announcer != peer) return; |
422 | | |
423 | 69 | unsigned int num_ann{0}; |
424 | 492 | while (it != index_by_peer.end() && it->m_announcer == peer) { |
425 | | // Delete item, cleaning up m_outpoint_to_orphan_wtxids iff this entry is unique by wtxid. |
426 | 423 | Erase<ByPeer>(it++); |
427 | 423 | num_ann += 1; |
428 | 423 | } |
429 | 69 | Assume(!m_peer_orphanage_info.contains(peer)); |
430 | | |
431 | 69 | if (num_ann > 0) LogDebug(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) from peer=%d\n", num_ann, peer); |
432 | | |
433 | | // Deletions can cause the orphanage's MaxGlobalUsage to decrease, so we may need to trim here. |
434 | 69 | LimitOrphans(); |
435 | 69 | } |
436 | | |
437 | | /** If the data structure needs trimming, evicts announcements by selecting the DoSiest peer and evicting its oldest |
438 | | * announcement (sorting non-reconsiderable orphans first, to give reconsiderable orphans a greater chance of being |
439 | | * processed). Does nothing if no global limits are exceeded. This eviction strategy effectively "reserves" an |
440 | | * amount of announcements and space for each peer. The reserved amount is protected from eviction even if there |
441 | | * are peers spamming the orphanage. |
442 | | */ |
443 | | void TxOrphanageImpl::LimitOrphans() |
444 | 13.3k | { |
445 | 13.3k | if (!NeedsTrim()) return; |
446 | | |
447 | 136 | const auto original_unique_txns{CountUniqueOrphans()}; |
448 | | |
449 | | // Even though it's possible for MaxPeerLatencyScore to increase within this call to LimitOrphans |
450 | | // (e.g. if a peer's orphans are removed entirely, changing the number of peers), use consistent limits throughout. |
451 | 136 | const auto max_lat{MaxPeerLatencyScore()}; |
452 | 136 | const auto max_mem{ReservedPeerUsage()}; |
453 | | |
454 | | // We have exceeded the global limit(s). Now, identify who is using too much and evict their orphans. |
455 | | // Create a heap of pairs (NodeId, DoS score), sorted by descending DoS score. |
456 | 136 | std::vector<std::pair<NodeId, FeeFrac>> heap_peer_dos; |
457 | 136 | heap_peer_dos.reserve(m_peer_orphanage_info.size()); |
458 | 1.53k | for (const auto& [nodeid, entry] : m_peer_orphanage_info) { |
459 | | // Performance optimization: only consider peers with a DoS score > 1. |
460 | 1.53k | const auto dos_score = entry.GetDosScore(max_lat, max_mem); |
461 | 1.53k | if (ByRatio{dos_score} > ByRatio{FeeFrac{1, 1}}) { |
462 | 136 | heap_peer_dos.emplace_back(nodeid, dos_score); |
463 | 136 | } |
464 | 1.53k | } |
465 | 136 | static constexpr auto compare_score = [](const auto& left, const auto& right) { |
466 | 0 | if (left.second != right.second) { |
467 | | // Note: if ratios are the same, this tiebreaks by denominator. In practice, since the |
468 | | // latency denominator (number of announcements and inputs) is always lower, this means |
469 | | // that a peer with only high latency scores will be targeted before a peer using a lot |
470 | | // of memory, even if they have the same ratios. |
471 | 0 | return ByRatioNegSize{left.second} < ByRatioNegSize{right.second}; |
472 | 0 | } |
473 | | // Tiebreak by considering the more recent peer (higher NodeId) to be worse. |
474 | 0 | return left.first < right.first; |
475 | 0 | }; |
476 | 136 | std::make_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score); |
477 | | |
478 | 136 | unsigned int num_erased{0}; |
479 | | // This outer loop finds the peer with the highest DoS score, which is a fraction of memory and latency scores |
480 | | // over the respective allowances. We continue until the orphanage is within global limits. That means some peers |
481 | | // might still have a DoS score > 1 at the end. |
482 | 136 | do { |
483 | 136 | Assume(!heap_peer_dos.empty()); |
484 | | // This is a max-heap, so the worst peer is at the front. pop_heap() |
485 | | // moves it to the back, and the next worst peer is moved to the front. |
486 | 136 | std::pop_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score); |
487 | 136 | const auto [worst_peer, dos_score] = std::move(heap_peer_dos.back()); |
488 | 136 | heap_peer_dos.pop_back(); |
489 | | |
490 | | // If needs trim, then at least one peer has a DoS score higher than 1. |
491 | 136 | Assume(ByRatio{dos_score} > ByRatio{FeeFrac(1, 1)}); |
492 | | |
493 | 136 | auto it_worst_peer = m_peer_orphanage_info.find(worst_peer); |
494 | | |
495 | | // This inner loop trims until this peer is no longer the DoSiest one or has a score within 1. The score 1 is |
496 | | // just a conservative fallback: once the last peer goes below ratio 1, NeedsTrim() will return false anyway. |
497 | | // We evict the oldest announcement(s) from this peer, sorting non-reconsiderable before reconsiderable. |
498 | | // The number of inner loop iterations is bounded by the total number of announcements. |
499 | 136 | const auto& dos_threshold = heap_peer_dos.empty() ? FeeFrac{1, 1} : heap_peer_dos.front().second; |
500 | 136 | auto it_ann = m_orphans.get<ByPeer>().lower_bound(ByPeerView{worst_peer, false, 0}); |
501 | 136 | unsigned int num_erased_this_round{0}; |
502 | 136 | unsigned int starting_num_ann{it_worst_peer->second.m_count_announcements}; |
503 | 194 | while (NeedsTrim()) { |
504 | 145 | if (!Assume(it_ann != m_orphans.get<ByPeer>().end())) break; |
505 | 145 | if (!Assume(it_ann->m_announcer == worst_peer)) break; |
506 | | |
507 | 145 | Erase<ByPeer>(it_ann++); |
508 | 145 | num_erased += 1; |
509 | 145 | num_erased_this_round += 1; |
510 | | |
511 | | // If we erased the last orphan from this peer, it_worst_peer will be invalidated. |
512 | 145 | it_worst_peer = m_peer_orphanage_info.find(worst_peer); |
513 | 145 | if (it_worst_peer == m_peer_orphanage_info.end() || |
514 | 145 | ByRatioNegSize{it_worst_peer->second.GetDosScore(max_lat, max_mem)} <= ByRatioNegSize{dos_threshold}) break; |
515 | 145 | } |
516 | 136 | LogDebug(BCLog::TXPACKAGES, "peer=%d orphanage overflow, removed %u of %u announcements\n", worst_peer, num_erased_this_round, starting_num_ann); |
517 | | |
518 | 136 | if (!NeedsTrim()) break; |
519 | | |
520 | | // Unless this peer is empty, put it back in the heap so we continue to consider evicting its orphans. |
521 | | // We may select this peer for evictions again if there are multiple DoSy peers. |
522 | 0 | if (it_worst_peer != m_peer_orphanage_info.end() && it_worst_peer->second.m_count_announcements > 0) { |
523 | 0 | heap_peer_dos.emplace_back(worst_peer, it_worst_peer->second.GetDosScore(max_lat, max_mem)); |
524 | 0 | std::push_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score); |
525 | 0 | } |
526 | 0 | } while (true); |
527 | | |
528 | 136 | const auto remaining_unique_orphans{CountUniqueOrphans()}; |
529 | 136 | LogDebug(BCLog::TXPACKAGES, "orphanage overflow, removed %u tx (%u announcements)\n", original_unique_txns - remaining_unique_orphans, num_erased); |
530 | 136 | } |
531 | | |
532 | | std::vector<std::pair<Wtxid, NodeId>> TxOrphanageImpl::AddChildrenToWorkSet(const CTransaction& tx, FastRandomContext& rng) |
533 | 137k | { |
534 | 137k | if (m_orphans.empty()) return {}; |
535 | | |
536 | 138 | std::vector<std::pair<Wtxid, NodeId>> ret; |
537 | 138 | auto& index_by_wtxid = m_orphans.get<ByWtxid>(); |
538 | 303 | for (unsigned int i = 0; i < tx.vout.size(); i++) { |
539 | 165 | const auto it_by_prev = m_outpoint_to_orphan_wtxids.find(COutPoint(tx.GetHash(), i)); |
540 | 165 | if (it_by_prev != m_outpoint_to_orphan_wtxids.end()) { |
541 | 52 | for (const auto& wtxid : it_by_prev->second) { |
542 | | // If a reconsiderable announcement for this wtxid already exists, skip it. |
543 | 52 | if (m_reconsiderable_wtxids.contains(wtxid)) continue; |
544 | | |
545 | | // Belt and suspenders, each entry in m_outpoint_to_orphan_wtxids should always have at least 1 announcement. |
546 | 52 | auto it = index_by_wtxid.lower_bound(ByWtxidView{wtxid, MIN_PEER}); |
547 | 52 | if (!Assume(it != index_by_wtxid.end() && it->m_tx->GetWitnessHash() == wtxid)) continue; |
548 | | |
549 | | // Select a random peer to assign orphan processing, reducing wasted work if the orphan is still missing |
550 | | // inputs. However, we don't want to create an issue in which the assigned peer can purposefully stop us |
551 | | // from processing the orphan by disconnecting. |
552 | 52 | auto it_end = index_by_wtxid.upper_bound(ByWtxidView{wtxid, MAX_PEER}); |
553 | 52 | const auto num_announcers{std::distance(it, it_end)}; |
554 | 52 | if (!Assume(num_announcers > 0)) continue; |
555 | 52 | std::advance(it, rng.randrange(num_announcers)); |
556 | | |
557 | 52 | if (!Assume(it->m_tx->GetWitnessHash() == wtxid)) break; |
558 | | |
559 | | // Mark this orphan as ready to be reconsidered. |
560 | 52 | static constexpr auto mark_reconsidered_modifier = [](auto& ann) { ann.m_reconsider = true; }; |
561 | 52 | Assume(!it->m_reconsider); |
562 | 52 | index_by_wtxid.modify(it, mark_reconsidered_modifier); |
563 | 52 | ret.emplace_back(wtxid, it->m_announcer); |
564 | 52 | m_reconsiderable_wtxids.insert(wtxid); |
565 | | |
566 | 52 | LogDebug(BCLog::TXPACKAGES, "added %s (wtxid=%s) to peer %d workset\n", |
567 | 52 | it->m_tx->GetHash().ToString(), it->m_tx->GetWitnessHash().ToString(), it->m_announcer); |
568 | 52 | } |
569 | 49 | } |
570 | 165 | } |
571 | 138 | return ret; |
572 | 137k | } |
573 | | |
574 | | bool TxOrphanageImpl::HaveTx(const Wtxid& wtxid) const |
575 | 69.4k | { |
576 | 69.4k | auto it_lower = m_orphans.get<ByWtxid>().lower_bound(ByWtxidView{wtxid, MIN_PEER}); |
577 | 69.4k | return it_lower != m_orphans.get<ByWtxid>().end() && it_lower->m_tx->GetWitnessHash() == wtxid; |
578 | 69.4k | } |
579 | | |
580 | | CTransactionRef TxOrphanageImpl::GetTx(const Wtxid& wtxid) const |
581 | 27.4k | { |
582 | 27.4k | auto it_lower = m_orphans.get<ByWtxid>().lower_bound(ByWtxidView{wtxid, MIN_PEER}); |
583 | 27.4k | if (it_lower != m_orphans.get<ByWtxid>().end() && it_lower->m_tx->GetWitnessHash() == wtxid) return it_lower->m_tx; |
584 | 27.3k | return nullptr; |
585 | 27.4k | } |
586 | | |
587 | | bool TxOrphanageImpl::HaveTxFromPeer(const Wtxid& wtxid, NodeId peer) const |
588 | 712 | { |
589 | 712 | return m_orphans.get<ByWtxid>().count(ByWtxidView{wtxid, peer}) > 0; |
590 | 712 | } |
591 | | |
592 | | /** If there is a tx that can be reconsidered, return it and set it back to |
593 | | * non-reconsiderable. Otherwise, return a nullptr. */ |
594 | | CTransactionRef TxOrphanageImpl::GetTxToReconsider(NodeId peer) |
595 | 342k | { |
596 | 342k | auto it = m_orphans.get<ByPeer>().lower_bound(ByPeerView{peer, true, 0}); |
597 | 342k | if (it != m_orphans.get<ByPeer>().end() && it->m_announcer == peer && it->m_reconsider) { |
598 | | // Flip m_reconsider. Even if this transaction stays in orphanage, it shouldn't be |
599 | | // reconsidered again until there is a new reason to do so. |
600 | 51 | static constexpr auto mark_reconsidered_modifier = [](auto& ann) { ann.m_reconsider = false; }; |
601 | 51 | m_orphans.get<ByPeer>().modify(it, mark_reconsidered_modifier); |
602 | | // As there is exactly one m_reconsider announcement per reconsiderable wtxids, flipping |
603 | | // the m_reconsider flag means the wtxid is no longer reconsiderable. |
604 | 51 | m_reconsiderable_wtxids.erase(it->m_tx->GetWitnessHash()); |
605 | 51 | return it->m_tx; |
606 | 51 | } |
607 | 342k | return nullptr; |
608 | 342k | } |
609 | | |
610 | | /** Return whether there is a tx that can be reconsidered. */ |
611 | | bool TxOrphanageImpl::HaveTxToReconsider(NodeId peer) |
612 | 138k | { |
613 | 138k | auto it = m_orphans.get<ByPeer>().lower_bound(ByPeerView{peer, true, 0}); |
614 | 138k | return it != m_orphans.get<ByPeer>().end() && it->m_announcer == peer && it->m_reconsider; |
615 | 138k | } |
616 | | |
617 | | void TxOrphanageImpl::EraseForBlock(const CBlock& block) |
618 | 79.2k | { |
619 | 79.2k | if (m_orphans.empty()) return; |
620 | | |
621 | 19 | std::set<Wtxid> wtxids_to_erase; |
622 | 256 | for (const CTransactionRef& ptx : block.vtx) { |
623 | 256 | const CTransaction& block_tx = *ptx; |
624 | | |
625 | | // Which orphan pool entries must we evict? |
626 | 258 | for (const auto& input : block_tx.vin) { |
627 | 258 | auto it_prev = m_outpoint_to_orphan_wtxids.find(input.prevout); |
628 | 258 | if (it_prev != m_outpoint_to_orphan_wtxids.end()) { |
629 | | // Copy all wtxids to wtxids_to_erase. |
630 | 72 | std::copy(it_prev->second.cbegin(), it_prev->second.cend(), std::inserter(wtxids_to_erase, wtxids_to_erase.end())); |
631 | 72 | } |
632 | 258 | } |
633 | 256 | } |
634 | | |
635 | 19 | unsigned int num_erased{0}; |
636 | 72 | for (const auto& wtxid : wtxids_to_erase) { |
637 | | // Don't use EraseTx here because it calls LimitOrphans and announcements deleted in that call are not reflected |
638 | | // in its return result. Waiting until the end to do LimitOrphans helps save repeated computation and allows us |
639 | | // to check that num_erased is what we expect. |
640 | 72 | num_erased += EraseTxInternal(wtxid) ? 1 : 0; |
641 | 72 | } |
642 | | |
643 | 19 | if (num_erased != 0) { |
644 | 8 | LogDebug(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) included or conflicted by block\n", num_erased); |
645 | 8 | } |
646 | 19 | Assume(wtxids_to_erase.size() == num_erased); |
647 | | |
648 | | // Deletions can cause the orphanage's MaxGlobalUsage to decrease, so we may need to trim here. |
649 | 19 | LimitOrphans(); |
650 | 19 | } |
651 | | |
652 | | std::vector<CTransactionRef> TxOrphanageImpl::GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId peer) const |
653 | 65 | { |
654 | 65 | std::vector<CTransactionRef> children_found; |
655 | 65 | const auto& parent_txid{parent->GetHash()}; |
656 | | |
657 | | // Iterate through all orphans from this peer, in reverse order, so that more recent |
658 | | // transactions are added first. Doing so helps avoid work when one of the orphans replaced |
659 | | // an earlier one. Since we require the NodeId to match, one peer's announcement order does |
660 | | // not bias how we process other peer's orphans. |
661 | 65 | auto& index_by_peer = m_orphans.get<ByPeer>(); |
662 | 65 | auto it_upper = index_by_peer.upper_bound(ByPeerView{peer, true, std::numeric_limits<uint64_t>::max()}); |
663 | 65 | auto it_lower = index_by_peer.lower_bound(ByPeerView{peer, false, 0}); |
664 | | |
665 | 130 | while (it_upper != it_lower) { |
666 | 65 | --it_upper; |
667 | 65 | if (!Assume(it_upper->m_announcer == peer)) break; |
668 | | // Check if this tx spends from parent. |
669 | 80 | for (const auto& input : it_upper->m_tx->vin) { |
670 | 80 | if (input.prevout.hash == parent_txid) { |
671 | 44 | children_found.emplace_back(it_upper->m_tx); |
672 | 44 | break; |
673 | 44 | } |
674 | 80 | } |
675 | 65 | } |
676 | 65 | return children_found; |
677 | 65 | } |
678 | | |
679 | | std::vector<TxOrphanage::OrphanInfo> TxOrphanageImpl::GetOrphanTransactions() const |
680 | 228 | { |
681 | 228 | std::vector<TxOrphanage::OrphanInfo> result; |
682 | 228 | result.reserve(m_unique_orphans); |
683 | | |
684 | 228 | auto& index_by_wtxid = m_orphans.get<ByWtxid>(); |
685 | 228 | auto it = index_by_wtxid.begin(); |
686 | 228 | std::set<NodeId> this_orphan_announcers; |
687 | 18.5k | while (it != index_by_wtxid.end()) { |
688 | 18.3k | this_orphan_announcers.insert(it->m_announcer); |
689 | | // If this is the last entry, or the next entry has a different wtxid, build a OrphanInfo. |
690 | 18.3k | if (std::next(it) == index_by_wtxid.end() || std::next(it)->m_tx->GetWitnessHash() != it->m_tx->GetWitnessHash()) { |
691 | 18.3k | result.emplace_back(it->m_tx, std::move(this_orphan_announcers)); |
692 | 18.3k | this_orphan_announcers.clear(); |
693 | 18.3k | } |
694 | | |
695 | 18.3k | ++it; |
696 | 18.3k | } |
697 | 228 | Assume(m_unique_orphans == result.size()); |
698 | | |
699 | 228 | return result; |
700 | 228 | } |
701 | | |
702 | | void TxOrphanageImpl::SanityCheck() const |
703 | 10 | { |
704 | 10 | std::unordered_map<NodeId, PeerDoSInfo> reconstructed_peer_info; |
705 | 10 | std::map<Wtxid, std::pair<TxOrphanage::Usage, TxOrphanage::Count>> unique_wtxids_to_scores; |
706 | 10 | std::set<COutPoint> all_outpoints; |
707 | 10 | std::set<Wtxid> reconstructed_reconsiderable_wtxids; |
708 | | |
709 | 176 | for (auto it = m_orphans.begin(); it != m_orphans.end(); ++it) { |
710 | 1.02k | for (const auto& input : it->m_tx->vin) { |
711 | 1.02k | all_outpoints.insert(input.prevout); |
712 | 1.02k | } |
713 | 166 | unique_wtxids_to_scores.emplace(it->m_tx->GetWitnessHash(), std::make_pair(it->GetMemUsage(), it->GetLatencyScore() - 1)); |
714 | | |
715 | 166 | auto& peer_info = reconstructed_peer_info[it->m_announcer]; |
716 | 166 | peer_info.m_total_usage += it->GetMemUsage(); |
717 | 166 | peer_info.m_count_announcements += 1; |
718 | 166 | peer_info.m_total_latency_score += it->GetLatencyScore(); |
719 | | |
720 | 166 | if (it->m_reconsider) { |
721 | 1 | auto [_, added] = reconstructed_reconsiderable_wtxids.insert(it->m_tx->GetWitnessHash()); |
722 | | // Check that there is only ever 1 announcement per wtxid with m_reconsider set. |
723 | 1 | assert(added); |
724 | 1 | } |
725 | 166 | } |
726 | 10 | assert(reconstructed_peer_info.size() == m_peer_orphanage_info.size()); |
727 | | |
728 | | // Recalculated per-peer stats are identical to m_peer_orphanage_info |
729 | 10 | assert(reconstructed_peer_info == m_peer_orphanage_info); |
730 | | |
731 | | // Recalculated set of reconsiderable wtxids must match. |
732 | 10 | assert(m_reconsiderable_wtxids == reconstructed_reconsiderable_wtxids); |
733 | | |
734 | | // All outpoints exist in m_outpoint_to_orphan_wtxids, all keys in m_outpoint_to_orphan_wtxids correspond to some |
735 | | // orphan, and all wtxids referenced in m_outpoint_to_orphan_wtxids are also in m_orphans. |
736 | | // This ensures m_outpoint_to_orphan_wtxids is cleaned up. |
737 | 10 | assert(all_outpoints.size() == m_outpoint_to_orphan_wtxids.size()); |
738 | 234 | for (const auto& [outpoint, wtxid_set] : m_outpoint_to_orphan_wtxids) { |
739 | 234 | assert(all_outpoints.contains(outpoint)); |
740 | 765 | for (const auto& wtxid : wtxid_set) { |
741 | 765 | assert(unique_wtxids_to_scores.contains(wtxid)); |
742 | 765 | } |
743 | 234 | } |
744 | | |
745 | | // Cached m_unique_orphans value is correct. |
746 | 10 | assert(m_orphans.size() >= m_unique_orphans); |
747 | 10 | assert(m_orphans.size() <= m_peer_orphanage_info.size() * m_unique_orphans); |
748 | 10 | assert(unique_wtxids_to_scores.size() == m_unique_orphans); |
749 | | |
750 | 10 | const auto calculated_dedup_usage = std::accumulate(unique_wtxids_to_scores.begin(), unique_wtxids_to_scores.end(), |
751 | 151 | TxOrphanage::Usage{0}, [](TxOrphanage::Usage sum, const auto pair) { return sum + pair.second.first; }); |
752 | 10 | assert(calculated_dedup_usage == m_unique_orphan_usage); |
753 | | |
754 | | // Global usage is deduplicated, should be less than or equal to the sum of all per-peer usages. |
755 | 10 | const auto summed_peer_usage = std::accumulate(m_peer_orphanage_info.begin(), m_peer_orphanage_info.end(), |
756 | 17 | TxOrphanage::Usage{0}, [](TxOrphanage::Usage sum, const auto pair) { return sum + pair.second.m_total_usage; }); |
757 | 10 | assert(summed_peer_usage >= m_unique_orphan_usage); |
758 | | |
759 | | // Cached m_unique_rounded_input_scores value is correct. |
760 | 10 | const auto calculated_total_latency_score = std::accumulate(unique_wtxids_to_scores.begin(), unique_wtxids_to_scores.end(), |
761 | 151 | TxOrphanage::Count{0}, [](TxOrphanage::Count sum, const auto pair) { return sum + pair.second.second; }); |
762 | 10 | assert(calculated_total_latency_score == m_unique_rounded_input_scores); |
763 | | |
764 | | // Global latency score is deduplicated, should be less than or equal to the sum of all per-peer latency scores. |
765 | 10 | const auto summed_peer_latency_score = std::accumulate(m_peer_orphanage_info.begin(), m_peer_orphanage_info.end(), |
766 | 17 | TxOrphanage::Count{0}, [](TxOrphanage::Count sum, const auto pair) { return sum + pair.second.m_total_latency_score; }); |
767 | 10 | assert(summed_peer_latency_score >= m_unique_rounded_input_scores + m_orphans.size()); |
768 | | |
769 | 10 | assert(!NeedsTrim()); |
770 | 10 | } |
771 | | |
772 | 13.7k | TxOrphanage::Count TxOrphanageImpl::MaxGlobalLatencyScore() const { return m_max_global_latency_score; } |
773 | 13.7k | TxOrphanage::Count TxOrphanageImpl::TotalLatencyScore() const { return m_unique_rounded_input_scores + m_orphans.size(); } |
774 | 143 | TxOrphanage::Usage TxOrphanageImpl::ReservedPeerUsage() const { return m_reserved_usage_per_peer; } |
775 | 153 | TxOrphanage::Count TxOrphanageImpl::MaxPeerLatencyScore() const { return m_max_global_latency_score / std::max<unsigned int>(m_peer_orphanage_info.size(), 1); } |
776 | 13.5k | TxOrphanage::Usage TxOrphanageImpl::MaxGlobalUsage() const { return m_reserved_usage_per_peer * std::max<int64_t>(m_peer_orphanage_info.size(), 1); } |
777 | | |
778 | | bool TxOrphanageImpl::NeedsTrim() const |
779 | 13.6k | { |
780 | 13.6k | return TotalLatencyScore() > MaxGlobalLatencyScore() || TotalOrphanUsage() > MaxGlobalUsage(); |
781 | 13.6k | } |
782 | | std::unique_ptr<TxOrphanage> MakeTxOrphanage() noexcept |
783 | 1.33k | { |
784 | 1.33k | return std::make_unique<TxOrphanageImpl>(); |
785 | 1.33k | } |
786 | | std::unique_ptr<TxOrphanage> MakeTxOrphanage(TxOrphanage::Count max_global_latency_score, TxOrphanage::Usage reserved_peer_usage) noexcept |
787 | 6 | { |
788 | 6 | return std::make_unique<TxOrphanageImpl>(max_global_latency_score, reserved_peer_usage); |
789 | 6 | } |
790 | | } // namespace node |