Bitcoin Core 29.99.0
P2P Digital Currency
txgraph_tests.cpp
Go to the documentation of this file.
1// Copyright (c) 2023-present The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or https://opensource.org/license/mit/.
4
5#include <txgraph.h>
6
7#include <random.h>
8
9#include <boost/test/unit_test.hpp>
10
11#include <memory>
12#include <vector>
13
14BOOST_AUTO_TEST_SUITE(txgraph_tests)
15
16
18static constexpr uint64_t NUM_ACCEPTABLE_ITERS = 100'000'000;
19
20BOOST_AUTO_TEST_CASE(txgraph_trim_zigzag)
21{
22 // T T T T T T T T T T T T T T (50 T's)
23 // \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ /
24 // \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ /
25 // B B B B B B B B B B B B B (49 B's)
26 //
28 static constexpr int MAX_CLUSTER_COUNT = 50;
30 static constexpr int NUM_BOTTOM_TX = 49;
34 static constexpr int NUM_TOP_TX = 50;
36 static constexpr int NUM_TOTAL_TX = NUM_BOTTOM_TX + NUM_TOP_TX;
37 static_assert(NUM_TOTAL_TX > MAX_CLUSTER_COUNT);
39 static constexpr int32_t MAX_CLUSTER_SIZE = 100'000 * 100;
40
41 // Create a new graph for the test.
42 auto graph = MakeTxGraph(MAX_CLUSTER_COUNT, MAX_CLUSTER_SIZE, NUM_ACCEPTABLE_ITERS);
43
44 // Add all transactions and store their Refs.
45 std::vector<TxGraph::Ref> refs;
46 refs.reserve(NUM_TOTAL_TX);
47 // First all bottom transactions: the i'th bottom transaction is at position i.
48 for (unsigned int i = 0; i < NUM_BOTTOM_TX; ++i) {
49 refs.push_back(graph->AddTransaction(FeePerWeight{200 - i, 100}));
50 }
51 // Then all top transactions: the i'th top transaction is at position NUM_BOTTOM_TX + i.
52 for (unsigned int i = 0; i < NUM_TOP_TX; ++i) {
53 refs.push_back(graph->AddTransaction(FeePerWeight{100 - i, 100}));
54 }
55
56 // Create the zigzag dependency structure.
57 // Each transaction in the bottom row depends on two adjacent transactions from the top row.
58 graph->SanityCheck();
59 for (unsigned int i = 0; i < NUM_BOTTOM_TX; ++i) {
60 graph->AddDependency(/*parent=*/refs[NUM_BOTTOM_TX + i], /*child=*/refs[i]);
61 graph->AddDependency(/*parent=*/refs[NUM_BOTTOM_TX + i + 1], /*child=*/refs[i]);
62 }
63
64 // Check that the graph is now oversized. This also forces the graph to
65 // group clusters and compute the oversized status.
66 graph->SanityCheck();
67 BOOST_CHECK_EQUAL(graph->GetTransactionCount(), NUM_TOTAL_TX);
68 BOOST_CHECK(graph->IsOversized(/*main_only=*/false));
69
70 // Call Trim() to remove transactions and bring the cluster back within limits.
71 auto removed_refs = graph->Trim();
72 graph->SanityCheck();
73 BOOST_CHECK(!graph->IsOversized(/*main_only=*/false));
74
75 // We only need to trim the middle bottom transaction to end up with 2 clusters each within cluster limits.
76 BOOST_CHECK_EQUAL(removed_refs.size(), 1);
77 BOOST_CHECK_EQUAL(graph->GetTransactionCount(), MAX_CLUSTER_COUNT * 2 - 2);
78 for (unsigned int i = 0; i < refs.size(); ++i) {
79 BOOST_CHECK_EQUAL(graph->Exists(refs[i]), i != (NUM_BOTTOM_TX / 2));
80 }
81}
82
83BOOST_AUTO_TEST_CASE(txgraph_trim_flower)
84{
85 // We will build an oversized flower-shaped graph: all transactions are spent by 1 descendant.
86 //
87 // T T T T T T T T (100 T's)
88 // | | | | | | | |
89 // | | | | | | | |
90 // \---+---+---+-+-+---+---+---/
91 // |
92 // B (1 B)
93 //
95 static constexpr int MAX_CLUSTER_COUNT = 50;
99 static constexpr int NUM_TOP_TX = MAX_CLUSTER_COUNT * 2;
101 static constexpr int NUM_TOTAL_TX = NUM_TOP_TX + 1;
103 static constexpr int32_t MAX_CLUSTER_SIZE = 100'000 * 100;
104
105 auto graph = MakeTxGraph(MAX_CLUSTER_COUNT, MAX_CLUSTER_SIZE, NUM_ACCEPTABLE_ITERS);
106
107 // Add all transactions and store their Refs.
108 std::vector<TxGraph::Ref> refs;
109 refs.reserve(NUM_TOTAL_TX);
110
111 // Add all transactions. They are in individual clusters.
112 refs.push_back(graph->AddTransaction({1, 100}));
113 for (unsigned int i = 0; i < NUM_TOP_TX; ++i) {
114 refs.push_back(graph->AddTransaction(FeePerWeight{500 + i, 100}));
115 }
116 graph->SanityCheck();
117
118 // The 0th transaction spends all the top transactions.
119 for (unsigned int i = 1; i < NUM_TOTAL_TX; ++i) {
120 graph->AddDependency(/*parent=*/refs[i], /*child=*/refs[0]);
121 }
122 graph->SanityCheck();
123
124 // Check that the graph is now oversized. This also forces the graph to
125 // group clusters and compute the oversized status.
126 BOOST_CHECK(graph->IsOversized(/*main_only=*/false));
127
128 // Call Trim() to remove transactions and bring the cluster back within limits.
129 auto removed_refs = graph->Trim();
130 graph->SanityCheck();
131 BOOST_CHECK(!graph->IsOversized(/*main_only=*/false));
132
133 // Since only the bottom transaction connects these clusters, we only need to remove it.
134 BOOST_CHECK_EQUAL(removed_refs.size(), 1);
135 BOOST_CHECK_EQUAL(graph->GetTransactionCount(false), MAX_CLUSTER_COUNT * 2);
136 BOOST_CHECK(!graph->Exists(refs[0]));
137 for (unsigned int i = 1; i < refs.size(); ++i) {
138 BOOST_CHECK(graph->Exists(refs[i]));
139 }
140}
141
142BOOST_AUTO_TEST_CASE(txgraph_trim_huge)
143{
144 // The from-block transactions consist of 1000 fully linear clusters, each with 64
145 // transactions. The mempool contains 11 transactions that together merge all of these into
146 // a single cluster.
147 //
148 // (1000 chains of 64 transactions, 64000 T's total)
149 //
150 // T T T T T T T T
151 // | | | | | | | |
152 // T T T T T T T T
153 // | | | | | | | |
154 // T T T T T T T T
155 // | | | | | | | |
156 // T T T T T T T T
157 // (64 long) (64 long) (64 long) (64 long) (64 long) (64 long) (64 long) (64 long)
158 // | | | | | | | |
159 // | | / \ | / \ | | /
160 // \----------+--------/ \--------+--------/ \--------+-----+----+--------/
161 // | | |
162 // B B B
163 //
164 // (11 B's, each attaching to up to 100 chains of 64 T's)
165 //
167 static constexpr int MAX_CLUSTER_COUNT = 64;
169 static constexpr int NUM_TOP_CHAINS = 1000;
171 static constexpr int NUM_TX_PER_TOP_CHAIN = MAX_CLUSTER_COUNT;
173 static constexpr int NUM_DEPS_PER_BOTTOM_TX = 100;
175 static constexpr int NUM_BOTTOM_TX = (NUM_TOP_CHAINS - 1 + (NUM_DEPS_PER_BOTTOM_TX - 2)) / (NUM_DEPS_PER_BOTTOM_TX - 1);
177 static constexpr int NUM_TOTAL_TX = NUM_TOP_CHAINS * NUM_TX_PER_TOP_CHAIN + NUM_BOTTOM_TX;
179 static constexpr int32_t MAX_CLUSTER_SIZE = 100'000 * 100;
180
182 std::vector<TxGraph::Ref> top_refs;
184 std::vector<TxGraph::Ref> bottom_refs;
188 std::vector<size_t> top_components;
189
191 auto graph = MakeTxGraph(MAX_CLUSTER_COUNT, MAX_CLUSTER_SIZE, NUM_ACCEPTABLE_ITERS);
192
193 // Construct the top chains.
194 for (int chain = 0; chain < NUM_TOP_CHAINS; ++chain) {
195 for (int chaintx = 0; chaintx < NUM_TX_PER_TOP_CHAIN; ++chaintx) {
196 // Use random fees, size 1.
197 int64_t fee = rng.randbits<27>() + 100;
198 FeePerWeight feerate{fee, 1};
199 top_refs.push_back(graph->AddTransaction(feerate));
200 // Add internal dependencies linked the chain transactions together.
201 if (chaintx > 0) {
202 graph->AddDependency(*(top_refs.rbegin()), *(top_refs.rbegin() + 1));
203 }
204 }
205 // Remember the last transaction in each chain, to attach the bottom transactions to.
206 top_components.push_back(top_refs.size() - 1);
207 }
208 graph->SanityCheck();
209
210 // Not oversized so far (just 1000 clusters of 64).
211 BOOST_CHECK(!graph->IsOversized());
212
213 // Construct the bottom transactions, and dependencies to the top chains.
214 while (top_components.size() > 1) {
215 // Construct the transaction.
216 int64_t fee = rng.randbits<27>() + 100;
217 FeePerWeight feerate{fee, 1};
218 auto bottom_tx = graph->AddTransaction(feerate);
219 // Determine the number of dependencies this transaction will have.
220 int deps = std::min<int>(NUM_DEPS_PER_BOTTOM_TX, top_components.size());
221 for (int dep = 0; dep < deps; ++dep) {
222 // Pick an transaction in top_components to attach to.
223 auto idx = rng.randrange(top_components.size());
224 // Add dependency.
225 graph->AddDependency(/*parent=*/top_refs[top_components[idx]], /*child=*/bottom_tx);
226 // Unless this is the last dependency being added, remove from top_components, as
227 // the component will be merged with that one.
228 if (dep < deps - 1) {
229 // Move entry top the back.
230 if (idx != top_components.size() - 1) std::swap(top_components.back(), top_components[idx]);
231 // And pop it.
232 top_components.pop_back();
233 }
234 }
235 bottom_refs.push_back(std::move(bottom_tx));
236 }
237 graph->SanityCheck();
238
239 // Now we are oversized (one cluster of 64011).
240 BOOST_CHECK(graph->IsOversized());
241 const auto total_tx_count = graph->GetTransactionCount();
242 BOOST_CHECK(total_tx_count == top_refs.size() + bottom_refs.size());
243 BOOST_CHECK(total_tx_count == NUM_TOTAL_TX);
244
245 // Call Trim() to remove transactions and bring the cluster back within limits.
246 auto removed_refs = graph->Trim();
247 BOOST_CHECK(!graph->IsOversized());
248 BOOST_CHECK(removed_refs.size() == total_tx_count - graph->GetTransactionCount());
249 graph->SanityCheck();
250
251 // At least 99% of chains must survive.
252 BOOST_CHECK(graph->GetTransactionCount() >= (NUM_TOP_CHAINS * NUM_TX_PER_TOP_CHAIN * 99) / 100);
253}
254
255BOOST_AUTO_TEST_CASE(txgraph_trim_big_singletons)
256{
257 // Mempool consists of 100 singleton clusters; there are no dependencies. Some are oversized. Trim() should remove all of the oversized ones.
258 static constexpr int MAX_CLUSTER_COUNT = 64;
259 static constexpr int32_t MAX_CLUSTER_SIZE = 100'000;
260 static constexpr int NUM_TOTAL_TX = 100;
261
262 // Create a new graph for the test.
263 auto graph = MakeTxGraph(MAX_CLUSTER_COUNT, MAX_CLUSTER_SIZE, NUM_ACCEPTABLE_ITERS);
264
265 // Add all transactions and store their Refs.
266 std::vector<TxGraph::Ref> refs;
267 refs.reserve(NUM_TOTAL_TX);
268
269 // Add all transactions. They are in individual clusters.
270 for (unsigned int i = 0; i < NUM_TOTAL_TX; ++i) {
271 // The 88th transaction is oversized.
272 // Every 20th transaction is oversized.
273 const FeePerWeight feerate{500 + i, (i == 88 || i % 20 == 0) ? MAX_CLUSTER_SIZE + 1 : 100};
274 refs.push_back(graph->AddTransaction(feerate));
275 }
276 graph->SanityCheck();
277
278 // Check that the graph is now oversized. This also forces the graph to
279 // group clusters and compute the oversized status.
280 BOOST_CHECK(graph->IsOversized(/*main_only=*/false));
281
282 // Call Trim() to remove transactions and bring the cluster back within limits.
283 auto removed_refs = graph->Trim();
284 graph->SanityCheck();
285 BOOST_CHECK_EQUAL(graph->GetTransactionCount(), NUM_TOTAL_TX - 6);
286 BOOST_CHECK(!graph->IsOversized(/*main_only=*/false));
287
288 // Check that all the oversized transactions were removed.
289 for (unsigned int i = 0; i < refs.size(); ++i) {
290 BOOST_CHECK_EQUAL(graph->Exists(refs[i]), i != 88 && i % 20 != 0);
291 }
292}
293
Fast randomness source.
Definition: random.h:386
I randrange(I range) noexcept
Generate a random integer in the range [0..range), with range > 0.
Definition: random.h:254
uint64_t randbits(int bits) noexcept
Generate a random (bits)-bit integer.
Definition: random.h:204
BOOST_AUTO_TEST_SUITE_END()
uint64_t fee
#define BOOST_CHECK_EQUAL(v1, v2)
Definition: object.cpp:18
#define BOOST_CHECK(expr)
Definition: object.cpp:17
Tagged wrapper around FeeFrac to avoid unit confusion.
Definition: feefrac.h:239
std::unique_ptr< TxGraph > MakeTxGraph(unsigned max_cluster_count, uint64_t max_cluster_size, uint64_t acceptable_iters) noexcept
Construct a new TxGraph with the specified limit on the number of transactions within a cluster,...
Definition: txgraph.cpp:2943
BOOST_AUTO_TEST_CASE(txgraph_trim_zigzag)
static constexpr uint64_t NUM_ACCEPTABLE_ITERS
The number used as acceptable_iters argument in these tests.