Bitcoin Core 32.99.0
P2P Digital Currency
Loading...
Searching...
No Matches
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.
31{
35 for (unsigned int i = 0; i < num_inputs; ++i) {
36 tx.vin.emplace_back(Txid::FromUint256(det_rand.rand256()), 0);
37 }
39
40 tx.vout.resize(1);
41
42 // If necessary, pad the transaction to the 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) {
55 tx.vin.emplace_back(inputs.at(i % inputs.size()));
56 }
57 assert(tx.vin.size() > 0);
58 return MakeTransactionRef(tx);
59}
61{
63
64 // Fill up announcements slots with tiny txns, followed by a single large one
66
67 // Construct transactions to submit to orphanage: 1-in-1-out tiny transactions
68 std::vector<CTransactionRef> tiny_txs;
70 for (unsigned int i{0}; i < NUM_TINY_TRANSACTIONS; ++i) {
72 }
75
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.
83 for (unsigned int txindex{0}; txindex < NUM_TINY_TRANSACTIONS; ++txindex) {
84 const auto& tx{tiny_txs.at(txindex)};
85
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.
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
101 bench.epochs(1).epochIterations(1).run([&]() NO_THREAD_SAFETY_ANALYSIS {
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()};
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.
122 // Subtract 4 because BulkTransaction rounds up and we must avoid going over the weight limit early.
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
129 // Construct large transactions
130 std::vector<CTransactionRef> shared_txs;
132 for (unsigned int i{0}; i < NUM_UNIQUE_TXNS; ++i) {
134 }
135 std::vector<size_t> indexes;
136 indexes.resize(NUM_UNIQUE_TXNS);
137 std::iota(indexes.begin(), indexes.end(), 0);
138
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) {
150
151 const auto& tx{shared_txs.at(indexes.at(i))};
152 if (tx == reserved_last_tx) {
153 // Skip
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) {
166 // Add the final reserved transactions.
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()};
177 assert(orphanage->TotalLatencyScore() <= orphanage->MaxGlobalLatencyScore());
178
180
181 bench.epochs(1).epochIterations(1).run([&]() NO_THREAD_SAFETY_ANALYSIS {
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.
191 });
192}
193
195{
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.
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};
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) {
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.
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
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
245 bench.epochs(1).epochIterations(1).run([&]() NO_THREAD_SAFETY_ANALYSIS {
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
#define BENCHMARK(n)
Definition bench.h:69
static CTransactionRef MakeTransactionSpendingUpTo(const std::vector< CTxIn > &inputs, unsigned int start_input, unsigned int num_inputs, int64_t weight_limit)
static void OrphanageEraseForPeer(benchmark::Bench &bench)
static void OrphanageEraseForBlock(benchmark::Bench &bench)
static CTransactionRef MakeTransactionBulkedTo(unsigned int num_inputs, int64_t target_weight, FastRandomContext &det_rand)
static void OrphanageEraseAll(benchmark::Bench &bench, bool block_or_disconnect)
static constexpr node::TxOrphanage::Usage TINY_TX_WEIGHT
static constexpr int64_t APPROX_WEIGHT_PER_INPUT
static void OrphanageSinglePeerEviction(benchmark::Bench &bench)
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
Main entry point to nanobench's benchmarking facility.
Definition nanobench.h:649
unsigned int Count
Definition txorphanage.h:44
static transaction_identifier FromUint256(const uint256 &id)
static int32_t GetTransactionWeight(const CTransaction &tx)
Definition validation.h:139
constexpr 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.
constexpr unsigned int DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE
Default value for TxOrphanage::m_max_global_latency_score.
Definition txorphanage.h:23
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:108
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)
std::shared_ptr< const CTransaction > CTransactionRef
A mutable version of CTransaction.
std::vector< CTxOut > vout
std::vector< CTxIn > vin
#define NO_THREAD_SAFETY_ANALYSIS
void BulkTransaction(CMutableTransaction &tx, int32_t target_weight)
constexpr auto Ticks(Dur2 d)
Helper to count the seconds of a duration/time_point.
Definition time.h:82
assert(!tx.IsCoinBase())