Bitcoin Core 31.99.0
P2P Digital Currency
txorphanage.cpp
Go to the documentation of this file.
1// Copyright (c) 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 <bench/bench.h>
8#include <net.h>
9#include <policy/policy.h>
10#include <primitives/block.h>
12#include <node/txorphanage.h>
13#include <random.h>
15#include <threadsafety.h>
16#include <util/check.h>
17
18#include <algorithm>
19#include <cstddef>
20#include <cstdint>
21#include <memory>
22#include <numeric>
23#include <vector>
24
26static constexpr int64_t APPROX_WEIGHT_PER_INPUT{200};
27
28// Creates a transaction with num_inputs inputs and 1 output, padded to target_weight. Use this function to maximize m_outpoint_to_orphan_it operations.
29// If num_inputs is 0, we maximize the number of inputs.
30static CTransactionRef MakeTransactionBulkedTo(unsigned int num_inputs, int64_t target_weight, FastRandomContext& det_rand)
31{
33 assert(target_weight >= 40 + APPROX_WEIGHT_PER_INPUT);
34 if (!num_inputs) num_inputs = (target_weight - 40) / APPROX_WEIGHT_PER_INPUT;
35 for (unsigned int i = 0; i < num_inputs; ++i) {
36 tx.vin.emplace_back(Txid::FromUint256(det_rand.rand256()), 0);
37 }
38 assert(GetTransactionWeight(*MakeTransactionRef(tx)) <= target_weight);
39
40 tx.vout.resize(1);
41
42 // If necessary, pad the transaction to the target weight.
43 if (GetTransactionWeight(*MakeTransactionRef(tx)) < target_weight - 4) {
44 BulkTransaction(tx, target_weight);
45 }
46 return MakeTransactionRef(tx);
47}
48
49// Constructs a transaction using a subset of inputs[start_input : start_input + num_inputs] up to the weight_limit.
50static CTransactionRef MakeTransactionSpendingUpTo(const std::vector<CTxIn>& inputs, unsigned int start_input, unsigned int num_inputs, int64_t weight_limit)
51{
53 for (unsigned int i{start_input}; i < start_input + num_inputs; ++i) {
54 if (GetTransactionWeight(*MakeTransactionRef(tx)) + APPROX_WEIGHT_PER_INPUT >= weight_limit) break;
55 tx.vin.emplace_back(inputs.at(i % inputs.size()));
56 }
57 assert(tx.vin.size() > 0);
58 return MakeTransactionRef(tx);
59}
61{
62 FastRandomContext det_rand{true};
63
64 // Fill up announcements slots with tiny txns, followed by a single large one
65 unsigned int NUM_TINY_TRANSACTIONS((node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE));
66
67 // Construct transactions to submit to orphanage: 1-in-1-out tiny transactions
68 std::vector<CTransactionRef> tiny_txs;
69 tiny_txs.reserve(NUM_TINY_TRANSACTIONS);
70 for (unsigned int i{0}; i < NUM_TINY_TRANSACTIONS; ++i) {
71 tiny_txs.emplace_back(MakeTransactionBulkedTo(1, TINY_TX_WEIGHT, det_rand));
72 }
73 auto large_tx = MakeTransactionBulkedTo(1, MAX_STANDARD_TX_WEIGHT, det_rand);
75
76 const auto orphanage{node::MakeTxOrphanage(/*max_global_latency_score=*/node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE, /*reserved_peer_usage=*/node::DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER)};
77
78 // Populate the orphanage. To maximize the number of evictions, first fill up with tiny transactions, then add a huge one.
79 NodeId peer{0};
80 // Add tiny transactions until we are just about to hit the memory limit, up to the max number of announcements.
81 // We use the same tiny transactions for all peers to minimize their contribution to the usage limit.
82 int64_t total_weight_to_add{0};
83 for (unsigned int txindex{0}; txindex < NUM_TINY_TRANSACTIONS; ++txindex) {
84 const auto& tx{tiny_txs.at(txindex)};
85
86 total_weight_to_add += GetTransactionWeight(*tx);
87 if (total_weight_to_add > orphanage->MaxGlobalUsage()) break;
88
89 assert(orphanage->AddTx(tx, peer));
90
91 // Sanity check: we should always be exiting at the point of hitting the weight limit.
92 assert(txindex < NUM_TINY_TRANSACTIONS - 1);
93 }
94
95 // In the real world, we always trim after each new tx.
96 // If we need to trim already, that means the benchmark is not representative of what LimitOrphans may do in a single call.
97 assert(orphanage->TotalOrphanUsage() <= orphanage->MaxGlobalUsage());
98 assert(orphanage->TotalLatencyScore() <= orphanage->MaxGlobalLatencyScore());
99 assert(orphanage->TotalOrphanUsage() + TINY_TX_WEIGHT > orphanage->MaxGlobalUsage());
100
102 // Lastly, add the large transaction.
103 const auto num_announcements_before_trim{orphanage->CountAnnouncements()};
104 assert(orphanage->AddTx(large_tx, peer));
105
106 // If there are multiple peers, note that they all have the same DoS score. We will evict only 1 item at a time for each new DoSiest peer.
107 const auto num_announcements_after_trim{orphanage->CountAnnouncements()};
108 const auto num_evicted{num_announcements_before_trim - num_announcements_after_trim};
109
110 // The number of evictions is the same regardless of the number of peers. In both cases, we can exceed the
111 // usage limit using 1 maximally-sized transaction.
113 });
114}
116{
117 // Best number is just below sqrt(DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE)
118 static constexpr unsigned int NUM_PEERS{39};
119 // All peers will have the same transactions. We want to be just under the weight limit, so divide the max usage limit by the number of unique transactions.
120 static constexpr node::TxOrphanage::Count NUM_UNIQUE_TXNS{node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE / NUM_PEERS};
121 static constexpr node::TxOrphanage::Usage TOTAL_USAGE_LIMIT{node::DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER * NUM_PEERS};
122 // Subtract 4 because BulkTransaction rounds up and we must avoid going over the weight limit early.
123 static constexpr node::TxOrphanage::Usage LARGE_TX_WEIGHT{TOTAL_USAGE_LIMIT / NUM_UNIQUE_TXNS - 4};
124 static_assert(LARGE_TX_WEIGHT >= TINY_TX_WEIGHT * 2, "Tx is too small, increase NUM_PEERS");
125 // The orphanage does not permit any transactions larger than 400'000, so this test will not work if the large tx is much larger.
126 static_assert(LARGE_TX_WEIGHT <= MAX_STANDARD_TX_WEIGHT, "Tx is too large, decrease NUM_PEERS");
127
128 FastRandomContext det_rand{true};
129 // Construct large transactions
130 std::vector<CTransactionRef> shared_txs;
131 shared_txs.reserve(NUM_UNIQUE_TXNS);
132 for (unsigned int i{0}; i < NUM_UNIQUE_TXNS; ++i) {
133 shared_txs.emplace_back(MakeTransactionBulkedTo(9, LARGE_TX_WEIGHT, det_rand));
134 }
135 std::vector<size_t> indexes;
136 indexes.resize(NUM_UNIQUE_TXNS);
137 std::iota(indexes.begin(), indexes.end(), 0);
138
139 const auto orphanage{node::MakeTxOrphanage(/*max_global_latency_score=*/node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE, /*reserved_peer_usage=*/node::DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER)};
140 // Every peer sends the same transactions, all from shared_txs.
141 // Each peer has 1 or 2 assigned transactions, which they must place as the last and second-to-last positions.
142 // The assignments ensure that every transaction is in some peer's last 2 transactions, and is thus remains in the orphanage until the end of LimitOrphans.
143 static_assert(NUM_UNIQUE_TXNS <= NUM_PEERS * 2);
144
145 // We need each peer to send some transactions so that the global limit (which is a function of the number of peers providing at least 1 announcement) rises.
146 for (unsigned int i{0}; i < NUM_UNIQUE_TXNS; ++i) {
147 for (NodeId peer{0}; peer < NUM_PEERS; ++peer) {
148 const CTransactionRef& reserved_last_tx{shared_txs.at(peer)};
149 CTransactionRef reserved_second_to_last_tx{peer < NUM_UNIQUE_TXNS - NUM_PEERS ? shared_txs.at(peer + NUM_PEERS) : nullptr};
150
151 const auto& tx{shared_txs.at(indexes.at(i))};
152 if (tx == reserved_last_tx) {
153 // Skip
154 } else if (reserved_second_to_last_tx && tx == reserved_second_to_last_tx) {
155 // Skip
156 } else {
157 orphanage->AddTx(tx, peer);
158 }
159 }
160 }
161
162 // Now add the final reserved transactions.
163 for (NodeId peer{0}; peer < NUM_PEERS; ++peer) {
164 const CTransactionRef& reserved_last_tx{shared_txs.at(peer)};
165 CTransactionRef reserved_second_to_last_tx{peer < NUM_UNIQUE_TXNS - NUM_PEERS ? shared_txs.at(peer + NUM_PEERS) : nullptr};
166 // Add the final reserved transactions.
167 if (reserved_second_to_last_tx) {
168 orphanage->AddTx(reserved_second_to_last_tx, peer);
169 }
170 orphanage->AddTx(reserved_last_tx, peer);
171 }
172
173 assert(orphanage->CountAnnouncements() == NUM_PEERS * NUM_UNIQUE_TXNS);
174 const auto total_usage{orphanage->TotalOrphanUsage()};
175 const auto max_usage{orphanage->MaxGlobalUsage()};
176 assert(max_usage - total_usage <= LARGE_TX_WEIGHT);
177 assert(orphanage->TotalLatencyScore() <= orphanage->MaxGlobalLatencyScore());
178
179 auto last_tx = MakeTransactionBulkedTo(0, max_usage - total_usage + 1, det_rand);
180
182 const auto num_announcements_before_trim{orphanage->CountAnnouncements()};
183 // There is a small gap between the total usage and the max usage. Add a transaction to fill it.
184 assert(orphanage->AddTx(last_tx, 0));
185
186 // If there are multiple peers, note that they all have the same DoS score. We will evict only 1 item at a time for each new DoSiest peer.
187 const auto num_evicted{num_announcements_before_trim - orphanage->CountAnnouncements() + 1};
188 // The trimming happens as a round robin. In the first NUM_UNIQUE_TXNS - 2 rounds for each peer, only duplicates are evicted.
189 // Once each peer has 2 transactions left, it's possible to select a peer whose oldest transaction is unique.
190 assert(num_evicted >= (NUM_UNIQUE_TXNS - 2) * NUM_PEERS);
191 });
192}
193
194static void OrphanageEraseAll(benchmark::Bench& bench, bool block_or_disconnect)
195{
196 FastRandomContext det_rand{true};
197 const auto orphanage{node::MakeTxOrphanage(/*max_global_latency_score=*/node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE, /*reserved_peer_usage=*/node::DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER)};
198 // This is an unrealistically large number of inputs for a block, as there is almost no room given to witness data,
199 // outputs, and overhead for individual transactions. The entire block is 1 transaction with 20,000 inputs.
200 constexpr unsigned int NUM_BLOCK_INPUTS{MAX_BLOCK_WEIGHT / APPROX_WEIGHT_PER_INPUT};
201 const auto block_tx{MakeTransactionBulkedTo(NUM_BLOCK_INPUTS, MAX_BLOCK_WEIGHT - 4000, det_rand)};
202 CBlock block;
203 block.vtx.push_back(block_tx);
204
205 // Transactions with 9 inputs maximize the computation / LatencyScore ratio.
206 constexpr unsigned int INPUTS_PER_TX{9};
207 constexpr unsigned int NUM_PEERS{125};
208 constexpr unsigned int NUM_TXNS_PER_PEER = node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE / NUM_PEERS;
209
210 // Divide the block's inputs evenly among the peers.
211 constexpr unsigned int INPUTS_PER_PEER = NUM_BLOCK_INPUTS / NUM_PEERS;
212 static_assert(INPUTS_PER_PEER > 0);
213 // All the block inputs are spent by the orphanage transactions. Each peer is assigned 76 of them.
214 // Each peer has 24 transactions spending 9 inputs each, so jumping by 3 ensures we cover all of the inputs.
215 static_assert(7 * NUM_TXNS_PER_PEER + INPUTS_PER_TX - 1 >= INPUTS_PER_PEER);
216
217 for (NodeId peer{0}; peer < NUM_PEERS; ++peer) {
218 int64_t weight_left_for_peer{node::DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER};
219 for (unsigned int txnum{0}; txnum < node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE / NUM_PEERS; ++txnum) {
220 // Transactions must be unique since they use different (though overlapping) inputs.
221 const unsigned int start_input = peer * INPUTS_PER_PEER + txnum * 7;
222
223 // Note that we shouldn't be able to hit the weight limit with these small transactions.
224 const int64_t weight_limit{std::min<int64_t>(weight_left_for_peer, MAX_STANDARD_TX_WEIGHT)};
225 auto ptx = MakeTransactionSpendingUpTo(block_tx->vin, /*start_input=*/start_input, /*num_inputs=*/INPUTS_PER_TX, /*weight_limit=*/weight_limit);
226
228 assert(!orphanage->HaveTx(ptx->GetWitnessHash()));
229 assert(orphanage->AddTx(ptx, peer));
230
231 weight_left_for_peer -= GetTransactionWeight(*ptx);
232 if (weight_left_for_peer < TINY_TX_WEIGHT * 2) break;
233 }
234 }
235
236 // If these fail, it means this benchmark is not realistic because the orphanage would have been trimmed already.
237 assert(orphanage->TotalLatencyScore() <= orphanage->MaxGlobalLatencyScore());
238 assert(orphanage->TotalOrphanUsage() <= orphanage->MaxGlobalUsage());
239
240 // 3000 announcements (and unique transactions) in the orphanage.
241 // They spend a total of 27,000 inputs (20,000 unique ones)
242 assert(orphanage->CountAnnouncements() == NUM_PEERS * NUM_TXNS_PER_PEER);
243 assert(orphanage->TotalLatencyScore() == orphanage->CountAnnouncements());
244
246 if (block_or_disconnect) {
247 // Erase everything through EraseForBlock.
248 // Every tx conflicts with this block.
249 orphanage->EraseForBlock(block);
250 assert(orphanage->CountAnnouncements() == 0);
251 } else {
252 // Erase everything through EraseForPeer.
253 for (NodeId peer{0}; peer < NUM_PEERS; ++peer) {
254 orphanage->EraseForPeer(peer);
255 }
256 assert(orphanage->CountAnnouncements() == 0);
257 }
258 });
259}
260
262{
263 OrphanageEraseAll(bench, /*block_or_disconnect=*/true);
264}
266{
267 OrphanageEraseAll(bench, /*block_or_disconnect=*/false);
268}
269
static CTransactionRef MakeTransactionSpendingUpTo(const std::vector< CTxIn > &inputs, unsigned int start_input, unsigned int num_inputs, int64_t weight_limit)
Definition: txorphanage.cpp:50
static void OrphanageEraseForPeer(benchmark::Bench &bench)
static void OrphanageEraseForBlock(benchmark::Bench &bench)
BENCHMARK(OrphanageSinglePeerEviction)
static CTransactionRef MakeTransactionBulkedTo(unsigned int num_inputs, int64_t target_weight, FastRandomContext &det_rand)
Definition: txorphanage.cpp:30
static void OrphanageEraseAll(benchmark::Bench &bench, bool block_or_disconnect)
static constexpr node::TxOrphanage::Usage TINY_TX_WEIGHT
Definition: txorphanage.cpp:25
static constexpr int64_t APPROX_WEIGHT_PER_INPUT
Definition: txorphanage.cpp:26
static void OrphanageSinglePeerEviction(benchmark::Bench &bench)
Definition: txorphanage.cpp:60
static void OrphanageMultiPeerEviction(benchmark::Bench &bench)
Definition: block.h:74
std::vector< CTransactionRef > vtx
Definition: block.h:77
Fast randomness source.
Definition: random.h:386
uint256 rand256() noexcept
generate a random uint256.
Definition: random.h:317
Main entry point to nanobench's benchmarking facility.
Definition: nanobench.h:649
Bench & run(char const *benchmarkName, Op &&op)
Repeatedly calls op() based on the configuration, and performs measurements.
Definition: nanobench.h:1308
Bench & epochs(size_t numEpochs) noexcept
Controls number of epochs, the number of measurements to perform.
Bench & epochIterations(uint64_t numIters) noexcept
Sets exactly the number of iterations for each epoch.
unsigned int Count
Definition: txorphanage.h:41
static transaction_identifier FromUint256(const uint256 &id)
static int32_t GetTransactionWeight(const CTransaction &tx)
Definition: validation.h:132
static const unsigned int MAX_BLOCK_WEIGHT
The maximum allowed weight for a block, see BIP 141 (network rule)
Definition: consensus.h:15
std::unique_ptr< TxOrphanage > MakeTxOrphanage() noexcept
Create a new TxOrphanage instance.
static constexpr unsigned int DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE
Default value for TxOrphanage::m_max_global_latency_score.
Definition: txorphanage.h:23
static constexpr int64_t DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER
Default value for TxOrphanage::m_reserved_usage_per_peer.
Definition: txorphanage.h:20
int64_t NodeId
Definition: net.h:103
static constexpr int32_t MAX_STANDARD_TX_WEIGHT
The maximum weight for transactions we're willing to relay/mine.
Definition: policy.h:38
static CTransactionRef MakeTransactionRef(Tx &&txIn)
Definition: transaction.h:404
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:403
A mutable version of CTransaction.
Definition: transaction.h:358
std::vector< CTxOut > vout
Definition: transaction.h:360
std::vector< CTxIn > vin
Definition: transaction.h:359
#define NO_THREAD_SAFETY_ANALYSIS
Definition: threadsafety.h:51
void BulkTransaction(CMutableTransaction &tx, int32_t target_weight)
assert(!tx.IsCoinBase())