/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 <logging.h> |
9 | | #include <policy/policy.h> |
10 | | #include <primitives/transaction.h> |
11 | | #include <util/feefrac.h> |
12 | | #include <util/time.h> |
13 | | #include <util/hasher.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 | 967 | m_tx{tx}, m_announcer{peer}, m_entry_sequence{seq} |
50 | 967 | { } |
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.29k | TxOrphanage::Usage GetMemUsage() const { |
58 | 4.29k | return GetTransactionWeight(*m_tx); |
59 | 4.29k | } |
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.29k | TxOrphanage::Count GetLatencyScore() const { |
67 | 4.29k | return 1 + (m_tx->vin.size() / 10); |
68 | 4.29k | } |
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 | 42.5k | { |
79 | 42.5k | return ByWtxidView{ann.m_tx->GetWitnessHash(), ann.m_announcer}; |
80 | 42.5k | } |
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 | 191k | { |
90 | 191k | return ByPeerView{ann.m_announcer, ann.m_reconsider, ann.m_entry_sequence}; |
91 | 191k | } |
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 | 964 | { |
138 | 964 | m_total_usage += ann.GetMemUsage(); |
139 | 964 | m_total_latency_score += ann.GetLatencyScore(); |
140 | 964 | m_count_announcements += 1; |
141 | 964 | } |
142 | | bool Subtract(const Announcement& ann) |
143 | 711 | { |
144 | 711 | Assume(m_total_usage >= ann.GetMemUsage()); |
145 | 711 | Assume(m_total_latency_score >= ann.GetLatencyScore()); |
146 | 711 | Assume(m_count_announcements >= 1); |
147 | | |
148 | 711 | m_total_usage -= ann.GetMemUsage(); |
149 | 711 | m_total_latency_score -= ann.GetLatencyScore(); |
150 | 711 | m_count_announcements -= 1; |
151 | 711 | return m_count_announcements == 0; |
152 | 711 | } |
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.24k | 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.24k | ~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 | 711 | { |
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 | 711 | auto peer_it = m_peer_orphanage_info.find(it->m_announcer); |
246 | 711 | Assume(peer_it != m_peer_orphanage_info.end()); |
247 | 711 | if (peer_it->second.Subtract(*it)) m_peer_orphanage_info.erase(peer_it); |
248 | | |
249 | 711 | if (IsUnique(m_orphans.project<ByWtxid>(it))) { |
250 | 670 | m_unique_orphans -= 1; |
251 | 670 | m_unique_rounded_input_scores -= it->GetLatencyScore() - 1; |
252 | 670 | m_unique_orphan_usage -= it->GetMemUsage(); |
253 | | |
254 | | // Remove references in m_outpoint_to_orphan_wtxids |
255 | 670 | const auto& wtxid{it->m_tx->GetWitnessHash()}; |
256 | 696 | for (const auto& input : it->m_tx->vin) { |
257 | 696 | auto it_prev = m_outpoint_to_orphan_wtxids.find(input.prevout); |
258 | 696 | if (it_prev != m_outpoint_to_orphan_wtxids.end()) { |
259 | 696 | it_prev->second.erase(wtxid); |
260 | | // Clean up keys if they point to an empty set. |
261 | 696 | if (it_prev->second.empty()) { |
262 | 689 | m_outpoint_to_orphan_wtxids.erase(it_prev); |
263 | 689 | } |
264 | 696 | } |
265 | 696 | } |
266 | 670 | } |
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 | 711 | if (it->m_reconsider) m_reconsiderable_wtxids.erase(it->m_tx->GetWitnessHash()); |
271 | | |
272 | 711 | m_orphans.get<Tag>().erase(it); |
273 | 711 | } 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 | 144 | { | 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 | 144 | auto peer_it = m_peer_orphanage_info.find(it->m_announcer); | 246 | 144 | Assume(peer_it != m_peer_orphanage_info.end()); | 247 | 144 | if (peer_it->second.Subtract(*it)) m_peer_orphanage_info.erase(peer_it); | 248 | | | 249 | 144 | if (IsUnique(m_orphans.project<ByWtxid>(it))) { | 250 | 142 | m_unique_orphans -= 1; | 251 | 142 | m_unique_rounded_input_scores -= it->GetLatencyScore() - 1; | 252 | 142 | m_unique_orphan_usage -= it->GetMemUsage(); | 253 | | | 254 | | // Remove references in m_outpoint_to_orphan_wtxids | 255 | 142 | const auto& wtxid{it->m_tx->GetWitnessHash()}; | 256 | 158 | for (const auto& input : it->m_tx->vin) { | 257 | 158 | auto it_prev = m_outpoint_to_orphan_wtxids.find(input.prevout); | 258 | 158 | if (it_prev != m_outpoint_to_orphan_wtxids.end()) { | 259 | 158 | it_prev->second.erase(wtxid); | 260 | | // Clean up keys if they point to an empty set. | 261 | 158 | if (it_prev->second.empty()) { | 262 | 154 | m_outpoint_to_orphan_wtxids.erase(it_prev); | 263 | 154 | } | 264 | 158 | } | 265 | 158 | } | 266 | 142 | } | 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 | 144 | if (it->m_reconsider) m_reconsiderable_wtxids.erase(it->m_tx->GetWitnessHash()); | 271 | | | 272 | 144 | m_orphans.get<Tag>().erase(it); | 273 | 144 | } |
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 | 567 | { | 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 | 567 | auto peer_it = m_peer_orphanage_info.find(it->m_announcer); | 246 | 567 | Assume(peer_it != m_peer_orphanage_info.end()); | 247 | 567 | if (peer_it->second.Subtract(*it)) m_peer_orphanage_info.erase(peer_it); | 248 | | | 249 | 567 | if (IsUnique(m_orphans.project<ByWtxid>(it))) { | 250 | 528 | m_unique_orphans -= 1; | 251 | 528 | m_unique_rounded_input_scores -= it->GetLatencyScore() - 1; | 252 | 528 | m_unique_orphan_usage -= it->GetMemUsage(); | 253 | | | 254 | | // Remove references in m_outpoint_to_orphan_wtxids | 255 | 528 | const auto& wtxid{it->m_tx->GetWitnessHash()}; | 256 | 538 | for (const auto& input : it->m_tx->vin) { | 257 | 538 | auto it_prev = m_outpoint_to_orphan_wtxids.find(input.prevout); | 258 | 538 | if (it_prev != m_outpoint_to_orphan_wtxids.end()) { | 259 | 538 | it_prev->second.erase(wtxid); | 260 | | // Clean up keys if they point to an empty set. | 261 | 538 | if (it_prev->second.empty()) { | 262 | 535 | m_outpoint_to_orphan_wtxids.erase(it_prev); | 263 | 535 | } | 264 | 538 | } | 265 | 538 | } | 266 | 528 | } | 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 | 567 | if (it->m_reconsider) m_reconsiderable_wtxids.erase(it->m_tx->GetWitnessHash()); | 271 | | | 272 | 567 | m_orphans.get<Tag>().erase(it); | 273 | 567 | } |
|
274 | | |
275 | | bool TxOrphanageImpl::IsUnique(Iter<ByWtxid> it) const |
276 | 1.67k | { |
277 | | // Iterators ByWtxid are sorted by wtxid, so check if neighboring elements have the same wtxid. |
278 | 1.67k | auto& index = m_orphans.get<ByWtxid>(); |
279 | 1.67k | if (it == index.end()) return false; |
280 | 1.67k | if (std::next(it) != index.end() && std::next(it)->m_tx->GetWitnessHash() == it->m_tx->GetWitnessHash()) return false; |
281 | 1.63k | if (it != index.begin() && std::prev(it)->m_tx->GetWitnessHash() == it->m_tx->GetWitnessHash()) return false; |
282 | 1.57k | return true; |
283 | 1.63k | } |
284 | | |
285 | | TxOrphanage::Usage TxOrphanageImpl::UsageByPeer(NodeId peer) const |
286 | 1.62k | { |
287 | 1.62k | auto it = m_peer_orphanage_info.find(peer); |
288 | 1.62k | return it == m_peer_orphanage_info.end() ? 0 : it->second.m_total_usage; |
289 | 1.62k | } |
290 | | |
291 | 34 | TxOrphanage::Count TxOrphanageImpl::CountAnnouncements() const { return m_orphans.size(); } |
292 | | |
293 | 13.7k | TxOrphanage::Usage TxOrphanageImpl::TotalOrphanUsage() const { return m_unique_orphan_usage; } |
294 | | |
295 | 1.12k | 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 | 972 | { |
309 | 972 | const auto& wtxid{tx->GetWitnessHash()}; |
310 | 972 | const auto& txid{tx->GetHash()}; |
311 | | |
312 | | // Ignore transactions above max standard size to avoid a send-big-orphans memory exhaustion attack. |
313 | 972 | TxOrphanage::Usage sz = GetTransactionWeight(*tx); |
314 | 972 | 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 | 961 | const bool brand_new{!HaveTx(wtxid)}; |
321 | | |
322 | 961 | 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 | 961 | if (!inserted) return false; |
325 | | |
326 | 959 | ++m_current_sequence; |
327 | 959 | auto& peer_info = m_peer_orphanage_info.try_emplace(peer).first->second; |
328 | 959 | peer_info.Add(*iter); |
329 | | |
330 | | // Add links in m_outpoint_to_orphan_wtxids |
331 | 959 | if (brand_new) { |
332 | 1.59k | for (const auto& input : tx->vin) { |
333 | 1.59k | auto& wtxids_for_prevout = m_outpoint_to_orphan_wtxids.try_emplace(input.prevout).first->second; |
334 | 1.59k | wtxids_for_prevout.emplace(wtxid); |
335 | 1.59k | } |
336 | | |
337 | 905 | m_unique_orphans += 1; |
338 | 905 | m_unique_orphan_usage += iter->GetMemUsage(); |
339 | 905 | m_unique_rounded_input_scores += iter->GetLatencyScore() - 1; |
340 | | |
341 | 905 | LogDebug(BCLog::TXPACKAGES, "stored orphan tx %s (wtxid=%s), weight: %u (mapsz %u outsz %u)\n", |
342 | 905 | txid.ToString(), wtxid.ToString(), sz, m_orphans.size(), m_outpoint_to_orphan_wtxids.size()); |
343 | 905 | Assume(IsUnique(iter)); |
344 | 905 | } 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 | 959 | LimitOrphans(); |
352 | 959 | return brand_new; |
353 | 961 | } |
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 | 11.7k | { |
388 | 11.7k | auto& index_by_wtxid = m_orphans.get<ByWtxid>(); |
389 | | |
390 | 11.7k | auto it = index_by_wtxid.lower_bound(ByWtxidView{wtxid, MIN_PEER}); |
391 | 11.7k | if (it == index_by_wtxid.end() || it->m_tx->GetWitnessHash() != wtxid) return false; |
392 | | |
393 | 142 | auto it_end = index_by_wtxid.upper_bound(ByWtxidView{wtxid, MAX_PEER}); |
394 | 142 | unsigned int num_ann{0}; |
395 | 142 | const auto txid = it->m_tx->GetHash(); |
396 | 286 | while (it != it_end) { |
397 | 144 | Assume(it->m_tx->GetWitnessHash() == wtxid); |
398 | 144 | Erase<ByWtxid>(it++); |
399 | 144 | num_ann += 1; |
400 | 144 | } |
401 | 142 | LogDebug(BCLog::TXPACKAGES, "removed orphan tx %s (wtxid=%s) (%u announcements)\n", txid.ToString(), wtxid.ToString(), num_ann); |
402 | | |
403 | 142 | return true; |
404 | 11.7k | } |
405 | | |
406 | | bool TxOrphanageImpl::EraseTx(const Wtxid& wtxid) |
407 | 11.6k | { |
408 | 11.6k | const auto ret = EraseTxInternal(wtxid); |
409 | | |
410 | | // Deletions can cause the orphanage's MaxGlobalUsage to decrease, so we may need to trim here. |
411 | 11.6k | LimitOrphans(); |
412 | | |
413 | 11.6k | return ret; |
414 | 11.6k | } |
415 | | |
416 | | /** Erase all entries by this peer. */ |
417 | | void TxOrphanageImpl::EraseForPeer(NodeId peer) |
418 | 1.62k | { |
419 | 1.62k | auto& index_by_peer = m_orphans.get<ByPeer>(); |
420 | 1.62k | auto it = index_by_peer.lower_bound(ByPeerView{peer, false, 0}); |
421 | 1.62k | if (it == index_by_peer.end() || it->m_announcer != peer) return; |
422 | | |
423 | 73 | unsigned int num_ann{0}; |
424 | 495 | 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 | 422 | Erase<ByPeer>(it++); |
427 | 422 | num_ann += 1; |
428 | 422 | } |
429 | 73 | Assume(!m_peer_orphanage_info.contains(peer)); |
430 | | |
431 | 73 | 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 | 73 | LimitOrphans(); |
435 | 73 | } |
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 | 12.7k | { |
445 | 12.7k | 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 | 11.3k | { |
534 | 11.3k | std::vector<std::pair<Wtxid, NodeId>> ret; |
535 | 11.3k | auto& index_by_wtxid = m_orphans.get<ByWtxid>(); |
536 | 42.7k | for (unsigned int i = 0; i < tx.vout.size(); i++) { |
537 | 31.3k | const auto it_by_prev = m_outpoint_to_orphan_wtxids.find(COutPoint(tx.GetHash(), i)); |
538 | 31.3k | if (it_by_prev != m_outpoint_to_orphan_wtxids.end()) { |
539 | 51 | for (const auto& wtxid : it_by_prev->second) { |
540 | | // If a reconsiderable announcement for this wtxid already exists, skip it. |
541 | 51 | if (m_reconsiderable_wtxids.contains(wtxid)) continue; |
542 | | |
543 | | // Belt and suspenders, each entry in m_outpoint_to_orphan_wtxids should always have at least 1 announcement. |
544 | 51 | auto it = index_by_wtxid.lower_bound(ByWtxidView{wtxid, MIN_PEER}); |
545 | 51 | if (!Assume(it != index_by_wtxid.end() && it->m_tx->GetWitnessHash() == wtxid)) continue; |
546 | | |
547 | | // Select a random peer to assign orphan processing, reducing wasted work if the orphan is still missing |
548 | | // inputs. However, we don't want to create an issue in which the assigned peer can purposefully stop us |
549 | | // from processing the orphan by disconnecting. |
550 | 51 | auto it_end = index_by_wtxid.upper_bound(ByWtxidView{wtxid, MAX_PEER}); |
551 | 51 | const auto num_announcers{std::distance(it, it_end)}; |
552 | 51 | if (!Assume(num_announcers > 0)) continue; |
553 | 51 | std::advance(it, rng.randrange(num_announcers)); |
554 | | |
555 | 51 | if (!Assume(it->m_tx->GetWitnessHash() == wtxid)) break; |
556 | | |
557 | | // Mark this orphan as ready to be reconsidered. |
558 | 51 | static constexpr auto mark_reconsidered_modifier = [](auto& ann) { ann.m_reconsider = true; }; |
559 | 51 | Assume(!it->m_reconsider); |
560 | 51 | index_by_wtxid.modify(it, mark_reconsidered_modifier); |
561 | 51 | ret.emplace_back(wtxid, it->m_announcer); |
562 | 51 | m_reconsiderable_wtxids.insert(wtxid); |
563 | | |
564 | 51 | LogDebug(BCLog::TXPACKAGES, "added %s (wtxid=%s) to peer %d workset\n", |
565 | 51 | it->m_tx->GetHash().ToString(), it->m_tx->GetWitnessHash().ToString(), it->m_announcer); |
566 | 51 | } |
567 | 48 | } |
568 | 31.3k | } |
569 | 11.3k | return ret; |
570 | 11.3k | } |
571 | | |
572 | | bool TxOrphanageImpl::HaveTx(const Wtxid& wtxid) const |
573 | 65.6k | { |
574 | 65.6k | auto it_lower = m_orphans.get<ByWtxid>().lower_bound(ByWtxidView{wtxid, MIN_PEER}); |
575 | 65.6k | return it_lower != m_orphans.get<ByWtxid>().end() && it_lower->m_tx->GetWitnessHash() == wtxid; |
576 | 65.6k | } |
577 | | |
578 | | CTransactionRef TxOrphanageImpl::GetTx(const Wtxid& wtxid) const |
579 | 26.0k | { |
580 | 26.0k | auto it_lower = m_orphans.get<ByWtxid>().lower_bound(ByWtxidView{wtxid, MIN_PEER}); |
581 | 26.0k | if (it_lower != m_orphans.get<ByWtxid>().end() && it_lower->m_tx->GetWitnessHash() == wtxid) return it_lower->m_tx; |
582 | 26.0k | return nullptr; |
583 | 26.0k | } |
584 | | |
585 | | bool TxOrphanageImpl::HaveTxFromPeer(const Wtxid& wtxid, NodeId peer) const |
586 | 700 | { |
587 | 700 | return m_orphans.get<ByWtxid>().count(ByWtxidView{wtxid, peer}) > 0; |
588 | 700 | } |
589 | | |
590 | | /** If there is a tx that can be reconsidered, return it and set it back to |
591 | | * non-reconsiderable. Otherwise, return a nullptr. */ |
592 | | CTransactionRef TxOrphanageImpl::GetTxToReconsider(NodeId peer) |
593 | 369k | { |
594 | 369k | auto it = m_orphans.get<ByPeer>().lower_bound(ByPeerView{peer, true, 0}); |
595 | 369k | if (it != m_orphans.get<ByPeer>().end() && it->m_announcer == peer && it->m_reconsider) { |
596 | | // Flip m_reconsider. Even if this transaction stays in orphanage, it shouldn't be |
597 | | // reconsidered again until there is a new reason to do so. |
598 | 50 | static constexpr auto mark_reconsidered_modifier = [](auto& ann) { ann.m_reconsider = false; }; |
599 | 50 | m_orphans.get<ByPeer>().modify(it, mark_reconsidered_modifier); |
600 | | // As there is exactly one m_reconsider announcement per reconsiderable wtxids, flipping |
601 | | // the m_reconsider flag means the wtxid is no longer reconsiderable. |
602 | 50 | m_reconsiderable_wtxids.erase(it->m_tx->GetWitnessHash()); |
603 | 50 | return it->m_tx; |
604 | 50 | } |
605 | 369k | return nullptr; |
606 | 369k | } |
607 | | |
608 | | /** Return whether there is a tx that can be reconsidered. */ |
609 | | bool TxOrphanageImpl::HaveTxToReconsider(NodeId peer) |
610 | 155k | { |
611 | 155k | auto it = m_orphans.get<ByPeer>().lower_bound(ByPeerView{peer, true, 0}); |
612 | 155k | return it != m_orphans.get<ByPeer>().end() && it->m_announcer == peer && it->m_reconsider; |
613 | 155k | } |
614 | | |
615 | | void TxOrphanageImpl::EraseForBlock(const CBlock& block) |
616 | 82.8k | { |
617 | 82.8k | if (m_orphans.empty()) return; |
618 | | |
619 | 14 | std::set<Wtxid> wtxids_to_erase; |
620 | 119 | for (const CTransactionRef& ptx : block.vtx) { |
621 | 119 | const CTransaction& block_tx = *ptx; |
622 | | |
623 | | // Which orphan pool entries must we evict? |
624 | 120 | for (const auto& input : block_tx.vin) { |
625 | 120 | auto it_prev = m_outpoint_to_orphan_wtxids.find(input.prevout); |
626 | 120 | if (it_prev != m_outpoint_to_orphan_wtxids.end()) { |
627 | | // Copy all wtxids to wtxids_to_erase. |
628 | 62 | std::copy(it_prev->second.cbegin(), it_prev->second.cend(), std::inserter(wtxids_to_erase, wtxids_to_erase.end())); |
629 | 62 | } |
630 | 120 | } |
631 | 119 | } |
632 | | |
633 | 14 | unsigned int num_erased{0}; |
634 | 62 | for (const auto& wtxid : wtxids_to_erase) { |
635 | | // Don't use EraseTx here because it calls LimitOrphans and announcements deleted in that call are not reflected |
636 | | // in its return result. Waiting until the end to do LimitOrphans helps save repeated computation and allows us |
637 | | // to check that num_erased is what we expect. |
638 | 62 | num_erased += EraseTxInternal(wtxid) ? 1 : 0; |
639 | 62 | } |
640 | | |
641 | 14 | if (num_erased != 0) { |
642 | 7 | LogDebug(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) included or conflicted by block\n", num_erased); |
643 | 7 | } |
644 | 14 | Assume(wtxids_to_erase.size() == num_erased); |
645 | | |
646 | | // Deletions can cause the orphanage's MaxGlobalUsage to decrease, so we may need to trim here. |
647 | 14 | LimitOrphans(); |
648 | 14 | } |
649 | | |
650 | | std::vector<CTransactionRef> TxOrphanageImpl::GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId peer) const |
651 | 67 | { |
652 | 67 | std::vector<CTransactionRef> children_found; |
653 | 67 | const auto& parent_txid{parent->GetHash()}; |
654 | | |
655 | | // Iterate through all orphans from this peer, in reverse order, so that more recent |
656 | | // transactions are added first. Doing so helps avoid work when one of the orphans replaced |
657 | | // an earlier one. Since we require the NodeId to match, one peer's announcement order does |
658 | | // not bias how we process other peer's orphans. |
659 | 67 | auto& index_by_peer = m_orphans.get<ByPeer>(); |
660 | 67 | auto it_upper = index_by_peer.upper_bound(ByPeerView{peer, true, std::numeric_limits<uint64_t>::max()}); |
661 | 67 | auto it_lower = index_by_peer.lower_bound(ByPeerView{peer, false, 0}); |
662 | | |
663 | 132 | while (it_upper != it_lower) { |
664 | 65 | --it_upper; |
665 | 65 | if (!Assume(it_upper->m_announcer == peer)) break; |
666 | | // Check if this tx spends from parent. |
667 | 80 | for (const auto& input : it_upper->m_tx->vin) { |
668 | 80 | if (input.prevout.hash == parent_txid) { |
669 | 44 | children_found.emplace_back(it_upper->m_tx); |
670 | 44 | break; |
671 | 44 | } |
672 | 80 | } |
673 | 65 | } |
674 | 67 | return children_found; |
675 | 67 | } |
676 | | |
677 | | std::vector<TxOrphanage::OrphanInfo> TxOrphanageImpl::GetOrphanTransactions() const |
678 | 226 | { |
679 | 226 | std::vector<TxOrphanage::OrphanInfo> result; |
680 | 226 | result.reserve(m_unique_orphans); |
681 | | |
682 | 226 | auto& index_by_wtxid = m_orphans.get<ByWtxid>(); |
683 | 226 | auto it = index_by_wtxid.begin(); |
684 | 226 | std::set<NodeId> this_orphan_announcers; |
685 | 18.5k | while (it != index_by_wtxid.end()) { |
686 | 18.3k | this_orphan_announcers.insert(it->m_announcer); |
687 | | // If this is the last entry, or the next entry has a different wtxid, build a OrphanInfo. |
688 | 18.3k | if (std::next(it) == index_by_wtxid.end() || std::next(it)->m_tx->GetWitnessHash() != it->m_tx->GetWitnessHash()) { |
689 | 18.3k | result.emplace_back(it->m_tx, std::move(this_orphan_announcers)); |
690 | 18.3k | this_orphan_announcers.clear(); |
691 | 18.3k | } |
692 | | |
693 | 18.3k | ++it; |
694 | 18.3k | } |
695 | 226 | Assume(m_unique_orphans == result.size()); |
696 | | |
697 | 226 | return result; |
698 | 226 | } |
699 | | |
700 | | void TxOrphanageImpl::SanityCheck() const |
701 | 10 | { |
702 | 10 | std::unordered_map<NodeId, PeerDoSInfo> reconstructed_peer_info; |
703 | 10 | std::map<Wtxid, std::pair<TxOrphanage::Usage, TxOrphanage::Count>> unique_wtxids_to_scores; |
704 | 10 | std::set<COutPoint> all_outpoints; |
705 | 10 | std::set<Wtxid> reconstructed_reconsiderable_wtxids; |
706 | | |
707 | 176 | for (auto it = m_orphans.begin(); it != m_orphans.end(); ++it) { |
708 | 1.02k | for (const auto& input : it->m_tx->vin) { |
709 | 1.02k | all_outpoints.insert(input.prevout); |
710 | 1.02k | } |
711 | 166 | unique_wtxids_to_scores.emplace(it->m_tx->GetWitnessHash(), std::make_pair(it->GetMemUsage(), it->GetLatencyScore() - 1)); |
712 | | |
713 | 166 | auto& peer_info = reconstructed_peer_info[it->m_announcer]; |
714 | 166 | peer_info.m_total_usage += it->GetMemUsage(); |
715 | 166 | peer_info.m_count_announcements += 1; |
716 | 166 | peer_info.m_total_latency_score += it->GetLatencyScore(); |
717 | | |
718 | 166 | if (it->m_reconsider) { |
719 | 1 | auto [_, added] = reconstructed_reconsiderable_wtxids.insert(it->m_tx->GetWitnessHash()); |
720 | | // Check that there is only ever 1 announcement per wtxid with m_reconsider set. |
721 | 1 | assert(added); |
722 | 1 | } |
723 | 166 | } |
724 | 10 | assert(reconstructed_peer_info.size() == m_peer_orphanage_info.size()); |
725 | | |
726 | | // Recalculated per-peer stats are identical to m_peer_orphanage_info |
727 | 10 | assert(reconstructed_peer_info == m_peer_orphanage_info); |
728 | | |
729 | | // Recalculated set of reconsiderable wtxids must match. |
730 | 10 | assert(m_reconsiderable_wtxids == reconstructed_reconsiderable_wtxids); |
731 | | |
732 | | // All outpoints exist in m_outpoint_to_orphan_wtxids, all keys in m_outpoint_to_orphan_wtxids correspond to some |
733 | | // orphan, and all wtxids referenced in m_outpoint_to_orphan_wtxids are also in m_orphans. |
734 | | // This ensures m_outpoint_to_orphan_wtxids is cleaned up. |
735 | 10 | assert(all_outpoints.size() == m_outpoint_to_orphan_wtxids.size()); |
736 | 234 | for (const auto& [outpoint, wtxid_set] : m_outpoint_to_orphan_wtxids) { |
737 | 234 | assert(all_outpoints.contains(outpoint)); |
738 | 765 | for (const auto& wtxid : wtxid_set) { |
739 | 765 | assert(unique_wtxids_to_scores.contains(wtxid)); |
740 | 765 | } |
741 | 234 | } |
742 | | |
743 | | // Cached m_unique_orphans value is correct. |
744 | 10 | assert(m_orphans.size() >= m_unique_orphans); |
745 | 10 | assert(m_orphans.size() <= m_peer_orphanage_info.size() * m_unique_orphans); |
746 | 10 | assert(unique_wtxids_to_scores.size() == m_unique_orphans); |
747 | | |
748 | 10 | const auto calculated_dedup_usage = std::accumulate(unique_wtxids_to_scores.begin(), unique_wtxids_to_scores.end(), |
749 | 151 | TxOrphanage::Usage{0}, [](TxOrphanage::Usage sum, const auto pair) { return sum + pair.second.first; }); |
750 | 10 | assert(calculated_dedup_usage == m_unique_orphan_usage); |
751 | | |
752 | | // Global usage is deduplicated, should be less than or equal to the sum of all per-peer usages. |
753 | 10 | const auto summed_peer_usage = std::accumulate(m_peer_orphanage_info.begin(), m_peer_orphanage_info.end(), |
754 | 17 | TxOrphanage::Usage{0}, [](TxOrphanage::Usage sum, const auto pair) { return sum + pair.second.m_total_usage; }); |
755 | 10 | assert(summed_peer_usage >= m_unique_orphan_usage); |
756 | | |
757 | | // Cached m_unique_rounded_input_scores value is correct. |
758 | 10 | const auto calculated_total_latency_score = std::accumulate(unique_wtxids_to_scores.begin(), unique_wtxids_to_scores.end(), |
759 | 151 | TxOrphanage::Count{0}, [](TxOrphanage::Count sum, const auto pair) { return sum + pair.second.second; }); |
760 | 10 | assert(calculated_total_latency_score == m_unique_rounded_input_scores); |
761 | | |
762 | | // Global latency score is deduplicated, should be less than or equal to the sum of all per-peer latency scores. |
763 | 10 | const auto summed_peer_latency_score = std::accumulate(m_peer_orphanage_info.begin(), m_peer_orphanage_info.end(), |
764 | 17 | TxOrphanage::Count{0}, [](TxOrphanage::Count sum, const auto pair) { return sum + pair.second.m_total_latency_score; }); |
765 | 10 | assert(summed_peer_latency_score >= m_unique_rounded_input_scores + m_orphans.size()); |
766 | | |
767 | 10 | assert(!NeedsTrim()); |
768 | 10 | } |
769 | | |
770 | 13.0k | TxOrphanage::Count TxOrphanageImpl::MaxGlobalLatencyScore() const { return m_max_global_latency_score; } |
771 | 13.0k | TxOrphanage::Count TxOrphanageImpl::TotalLatencyScore() const { return m_unique_rounded_input_scores + m_orphans.size(); } |
772 | 143 | TxOrphanage::Usage TxOrphanageImpl::ReservedPeerUsage() const { return m_reserved_usage_per_peer; } |
773 | 153 | TxOrphanage::Count TxOrphanageImpl::MaxPeerLatencyScore() const { return m_max_global_latency_score / std::max<unsigned int>(m_peer_orphanage_info.size(), 1); } |
774 | 12.9k | TxOrphanage::Usage TxOrphanageImpl::MaxGlobalUsage() const { return m_reserved_usage_per_peer * std::max<int64_t>(m_peer_orphanage_info.size(), 1); } |
775 | | |
776 | | bool TxOrphanageImpl::NeedsTrim() const |
777 | 13.0k | { |
778 | 13.0k | return TotalLatencyScore() > MaxGlobalLatencyScore() || TotalOrphanUsage() > MaxGlobalUsage(); |
779 | 13.0k | } |
780 | | std::unique_ptr<TxOrphanage> MakeTxOrphanage() noexcept |
781 | 1.24k | { |
782 | 1.24k | return std::make_unique<TxOrphanageImpl>(); |
783 | 1.24k | } |
784 | | std::unique_ptr<TxOrphanage> MakeTxOrphanage(TxOrphanage::Count max_global_latency_score, TxOrphanage::Usage reserved_peer_usage) noexcept |
785 | 6 | { |
786 | 6 | return std::make_unique<TxOrphanageImpl>(max_global_latency_score, reserved_peer_usage); |
787 | 6 | } |
788 | | } // namespace node |