Bitcoin Core 32.99.0
P2P Digital Currency
Loading...
Searching...
No Matches
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#include <test/util/common.h>
9
10#include <boost/test/unit_test.hpp>
11
12#include <memory>
13#include <vector>
14
16
17namespace {
18
21constexpr uint64_t HIGH_ACCEPTABLE_COST = 100'000'000;
22
23std::strong_ordering PointerComparator(const TxGraph::Ref& a, const TxGraph::Ref& b) noexcept
24{
25 return (&a) <=> (&b);
26}
27
28} // namespace
29
31{
32 // T T T T T T T T T T T T T T (50 T's)
33 // \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ /
34 // \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ /
35 // B B B B B B B B B B B B B (49 B's)
36 //
38 static constexpr int MAX_CLUSTER_COUNT = 50;
40 static constexpr int NUM_BOTTOM_TX = 49;
44 static constexpr int NUM_TOP_TX = 50;
46 static constexpr int NUM_TOTAL_TX = NUM_BOTTOM_TX + NUM_TOP_TX;
47 static_assert(NUM_TOTAL_TX > MAX_CLUSTER_COUNT);
49 static constexpr int32_t MAX_CLUSTER_SIZE = 100'000 * 100;
50
51 // Create a new graph for the test.
53
54 // Add all transactions and store their Refs.
55 std::vector<TxGraph::Ref> refs;
56 refs.reserve(NUM_TOTAL_TX);
57 // First all bottom transactions: the i'th bottom transaction is at position i.
58 for (unsigned int i = 0; i < NUM_BOTTOM_TX; ++i) {
59 graph->AddTransaction(refs.emplace_back(), FeePerWeight{200 - i, 100});
60 }
61 // Then all top transactions: the i'th top transaction is at position NUM_BOTTOM_TX + i.
62 for (unsigned int i = 0; i < NUM_TOP_TX; ++i) {
63 graph->AddTransaction(refs.emplace_back(), FeePerWeight{100 - i, 100});
64 }
65
66 // Create the zigzag dependency structure.
67 // Each transaction in the bottom row depends on two adjacent transactions from the top row.
68 graph->SanityCheck();
69 for (unsigned int i = 0; i < NUM_BOTTOM_TX; ++i) {
70 graph->AddDependency(/*parent=*/refs[NUM_BOTTOM_TX + i], /*child=*/refs[i]);
71 graph->AddDependency(/*parent=*/refs[NUM_BOTTOM_TX + i + 1], /*child=*/refs[i]);
72 }
73
74 // Check that the graph is now oversized. This also forces the graph to
75 // group clusters and compute the oversized status.
76 graph->SanityCheck();
77 BOOST_CHECK_EQUAL(graph->GetTransactionCount(TxGraph::Level::TOP), NUM_TOTAL_TX);
78 BOOST_CHECK(graph->IsOversized(TxGraph::Level::TOP));
79
80 // Call Trim() to remove transactions and bring the cluster back within limits.
81 auto removed_refs = graph->Trim();
82 graph->SanityCheck();
83 BOOST_CHECK(!graph->IsOversized(TxGraph::Level::TOP));
84
85 // We only need to trim the middle bottom transaction to end up with 2 clusters each within cluster limits.
87 BOOST_CHECK_EQUAL(graph->GetTransactionCount(TxGraph::Level::TOP), MAX_CLUSTER_COUNT * 2 - 2);
88 for (unsigned int i = 0; i < refs.size(); ++i) {
89 BOOST_CHECK_EQUAL(graph->Exists(refs[i], TxGraph::Level::TOP), i != (NUM_BOTTOM_TX / 2));
90 }
91}
92
94{
95 // We will build an oversized flower-shaped graph: all transactions are spent by 1 descendant.
96 //
97 // T T T T T T T T (100 T's)
98 // | | | | | | | |
99 // | | | | | | | |
100 // \---+---+---+-+-+---+---+---/
101 // |
102 // B (1 B)
103 //
105 static constexpr int MAX_CLUSTER_COUNT = 50;
109 static constexpr int NUM_TOP_TX = MAX_CLUSTER_COUNT * 2;
111 static constexpr int NUM_TOTAL_TX = NUM_TOP_TX + 1;
113 static constexpr int32_t MAX_CLUSTER_SIZE = 100'000 * 100;
114
116
117 // Add all transactions and store their Refs.
118 std::vector<TxGraph::Ref> refs;
119 refs.reserve(NUM_TOTAL_TX);
120
121 // Add all transactions. They are in individual clusters.
122 graph->AddTransaction(refs.emplace_back(), {1, 100});
123 for (unsigned int i = 0; i < NUM_TOP_TX; ++i) {
124 graph->AddTransaction(refs.emplace_back(), FeePerWeight{500 + i, 100});
125 }
126 graph->SanityCheck();
127
128 // The 0th transaction spends all the top transactions.
129 for (unsigned int i = 1; i < NUM_TOTAL_TX; ++i) {
130 graph->AddDependency(/*parent=*/refs[i], /*child=*/refs[0]);
131 }
132 graph->SanityCheck();
133
134 // Check that the graph is now oversized. This also forces the graph to
135 // group clusters and compute the oversized status.
136 BOOST_CHECK(graph->IsOversized(TxGraph::Level::TOP));
137
138 // Call Trim() to remove transactions and bring the cluster back within limits.
139 auto removed_refs = graph->Trim();
140 graph->SanityCheck();
141 BOOST_CHECK(!graph->IsOversized(TxGraph::Level::TOP));
142
143 // Since only the bottom transaction connects these clusters, we only need to remove it.
145 BOOST_CHECK_EQUAL(graph->GetTransactionCount(TxGraph::Level::TOP), MAX_CLUSTER_COUNT * 2);
146 BOOST_CHECK(!graph->Exists(refs[0], TxGraph::Level::TOP));
147 for (unsigned int i = 1; i < refs.size(); ++i) {
148 BOOST_CHECK(graph->Exists(refs[i], TxGraph::Level::TOP));
149 }
150}
151
153{
154 // The from-block transactions consist of 1000 fully linear clusters, each with 64
155 // transactions. The mempool contains 11 transactions that together merge all of these into
156 // a single cluster.
157 //
158 // (1000 chains of 64 transactions, 64000 T's total)
159 //
160 // T T T T T T T T
161 // | | | | | | | |
162 // T T T T T T T T
163 // | | | | | | | |
164 // T T T T T T T T
165 // | | | | | | | |
166 // T T T T T T T T
167 // (64 long) (64 long) (64 long) (64 long) (64 long) (64 long) (64 long) (64 long)
168 // | | | | | | | |
169 // | | / \ | / \ | | /
170 // \----------+--------/ \--------+--------/ \--------+-----+----+--------/
171 // | | |
172 // B B B
173 //
174 // (11 B's, each attaching to up to 100 chains of 64 T's)
175 //
177 static constexpr int MAX_CLUSTER_COUNT = 64;
179 static constexpr int NUM_TOP_CHAINS = 1000;
181 static constexpr int NUM_TX_PER_TOP_CHAIN = MAX_CLUSTER_COUNT;
183 static constexpr int NUM_DEPS_PER_BOTTOM_TX = 100;
185 static constexpr int NUM_BOTTOM_TX = (NUM_TOP_CHAINS - 1 + (NUM_DEPS_PER_BOTTOM_TX - 2)) / (NUM_DEPS_PER_BOTTOM_TX - 1);
189 static constexpr int32_t MAX_CLUSTER_SIZE = 100'000 * 100;
190
192 std::vector<TxGraph::Ref> top_refs;
194 std::vector<TxGraph::Ref> bottom_refs;
198 std::vector<size_t> top_components;
199
202
203 // Construct the top chains.
204 for (int chain = 0; chain < NUM_TOP_CHAINS; ++chain) {
205 for (int chaintx = 0; chaintx < NUM_TX_PER_TOP_CHAIN; ++chaintx) {
206 // Use random fees, size 1.
207 int64_t fee = rng.randbits<27>() + 100;
208 FeePerWeight feerate{fee, 1};
209 graph->AddTransaction(top_refs.emplace_back(), feerate);
210 // Add internal dependencies linking the chain transactions together.
211 if (chaintx > 0) {
212 graph->AddDependency(*(top_refs.rbegin()), *(top_refs.rbegin() + 1));
213 }
214 }
215 // Remember the last transaction in each chain, to attach the bottom transactions to.
216 top_components.push_back(top_refs.size() - 1);
217 }
218 graph->SanityCheck();
219
220 // Not oversized so far (just 1000 clusters of 64).
221 BOOST_CHECK(!graph->IsOversized(TxGraph::Level::TOP));
222
223 // Construct the bottom transactions, and dependencies to the top chains.
224 while (top_components.size() > 1) {
225 // Construct the transaction.
226 int64_t fee = rng.randbits<27>() + 100;
227 FeePerWeight feerate{fee, 1};
229 graph->AddTransaction(bottom_tx, feerate);
230 // Determine the number of dependencies this transaction will have.
231 int deps = std::min<int>(NUM_DEPS_PER_BOTTOM_TX, top_components.size());
232 for (int dep = 0; dep < deps; ++dep) {
233 // Pick an transaction in top_components to attach to.
234 auto idx = rng.randrange(top_components.size());
235 // Add dependency.
236 graph->AddDependency(/*parent=*/top_refs[top_components[idx]], /*child=*/bottom_tx);
237 // Unless this is the last dependency being added, remove from top_components, as
238 // the component will be merged with that one.
239 if (dep < deps - 1) {
240 // Move entry top the back.
241 if (idx != top_components.size() - 1) std::swap(top_components.back(), top_components[idx]);
242 // And pop it.
243 top_components.pop_back();
244 }
245 }
246 bottom_refs.push_back(std::move(bottom_tx));
247 }
248 graph->SanityCheck();
249
250 // Now we are oversized (one cluster of 64011).
251 BOOST_CHECK(graph->IsOversized(TxGraph::Level::TOP));
252 const auto total_tx_count = graph->GetTransactionCount(TxGraph::Level::TOP);
255
256 // Call Trim() to remove transactions and bring the cluster back within limits.
257 auto removed_refs = graph->Trim();
258 BOOST_CHECK(!graph->IsOversized(TxGraph::Level::TOP));
259 BOOST_CHECK(removed_refs.size() == total_tx_count - graph->GetTransactionCount(TxGraph::Level::TOP));
260 graph->SanityCheck();
261
262 // At least 99% of chains must survive.
263 BOOST_CHECK(graph->GetTransactionCount(TxGraph::Level::TOP) >= (NUM_TOP_CHAINS * NUM_TX_PER_TOP_CHAIN * 99) / 100);
264}
265
267{
268 // Mempool consists of 100 singleton clusters; there are no dependencies. Some are oversized. Trim() should remove all of the oversized ones.
269 static constexpr int MAX_CLUSTER_COUNT = 64;
270 static constexpr int32_t MAX_CLUSTER_SIZE = 100'000;
271 static constexpr int NUM_TOTAL_TX = 100;
272
273 // Create a new graph for the test.
275
276 // Add all transactions and store their Refs.
277 std::vector<TxGraph::Ref> refs;
278 refs.reserve(NUM_TOTAL_TX);
279
280 // Add all transactions. They are in individual clusters.
281 for (unsigned int i = 0; i < NUM_TOTAL_TX; ++i) {
282 // The 88th transaction is oversized.
283 // Every 20th transaction is oversized.
284 const FeePerWeight feerate{500 + i, (i == 88 || i % 20 == 0) ? MAX_CLUSTER_SIZE + 1 : 100};
285 graph->AddTransaction(refs.emplace_back(), feerate);
286 }
287 graph->SanityCheck();
288
289 // Check that the graph is now oversized. This also forces the graph to
290 // group clusters and compute the oversized status.
291 BOOST_CHECK(graph->IsOversized(TxGraph::Level::TOP));
292
293 // Call Trim() to remove transactions and bring the cluster back within limits.
294 graph->Trim();
295 graph->SanityCheck();
296 BOOST_CHECK_EQUAL(graph->GetTransactionCount(TxGraph::Level::TOP), NUM_TOTAL_TX - 6);
297 BOOST_CHECK(!graph->IsOversized(TxGraph::Level::TOP));
298
299 // Check that all the oversized transactions were removed.
300 for (unsigned int i = 0; i < refs.size(); ++i) {
301 BOOST_CHECK_EQUAL(graph->Exists(refs[i], TxGraph::Level::TOP), i != 88 && i % 20 != 0);
302 }
303}
304
306{
307 // Create a new graph for the test.
308 auto graph = MakeTxGraph(50, 1000, HIGH_ACCEPTABLE_COST, PointerComparator);
309
310 auto block_builder_checker = [&graph](std::vector<std::vector<TxGraph::Ref*>> expected_chunks) {
311 std::vector<std::vector<TxGraph::Ref*>> chunks;
312 auto builder = graph->GetBlockBuilder();
314 while (auto chunk = builder->GetCurrentChunk()) {
316 for (TxGraph::Ref* ref : chunk->first) {
317 // The reported chunk feerate must match the chunk feerate obtained by asking
318 // it for each of the chunk's transactions individually.
319 BOOST_CHECK_EQUAL(graph->GetMainChunkFeerate(*ref), chunk->second);
320 // Verify the chunk feerate matches the sum of the reported individual feerates.
321 sum += graph->GetIndividualFeerate(*ref);
322 }
323 BOOST_CHECK_EQUAL(sum, chunk->second);
324 chunks.push_back(std::move(chunk->first));
325 last_chunk_feerate = chunk->second;
326 builder->Include();
327 }
328
330 auto& last_chunk = chunks.back();
331 // The last chunk returned by the BlockBuilder must match GetWorstMainChunk, in reverse.
332 std::reverse(last_chunk.begin(), last_chunk.end());
333 auto [worst_chunk, worst_chunk_feerate] = graph->GetWorstMainChunk();
336 };
337
338 std::vector<TxGraph::Ref> refs;
339 refs.reserve(4);
340
341 FeePerWeight feerateA{2, 10};
342 FeePerWeight feerateB{1, 10};
343 FeePerWeight feerateC{2, 10};
344 FeePerWeight feerateD{4, 10};
345
346 // everytime adding a transaction, test the chunk status
347 // [A]
348 graph->AddTransaction(refs.emplace_back(), feerateA);
349 BOOST_CHECK_EQUAL(graph->GetTransactionCount(TxGraph::Level::TOP), 1);
351 // [A, B]
352 graph->AddTransaction(refs.emplace_back(), feerateB);
353 graph->AddDependency(/*parent=*/refs[0], /*child=*/refs[1]);
354 BOOST_CHECK_EQUAL(graph->GetTransactionCount(TxGraph::Level::TOP), 2);
355 block_builder_checker({{&refs[0]}, {&refs[1]}});
356
357 // [A, BC]
358 graph->AddTransaction(refs.emplace_back(), feerateC);
359 graph->AddDependency(/*parent=*/refs[1], /*child=*/refs[2]);
360 BOOST_CHECK_EQUAL(graph->GetTransactionCount(TxGraph::Level::TOP), 3);
361 block_builder_checker({{&refs[0]}, {&refs[1], &refs[2]}});
362
363 // [ABCD]
364 graph->AddTransaction(refs.emplace_back(), feerateD);
365 graph->AddDependency(/*parent=*/refs[2], /*child=*/refs[3]);
366 BOOST_CHECK_EQUAL(graph->GetTransactionCount(TxGraph::Level::TOP), 4);
367 block_builder_checker({{&refs[0], &refs[1], &refs[2], &refs[3]}});
368
369 graph->SanityCheck();
370
371 // D->C->A
372 graph->RemoveTransaction(refs[1]);
373 // txgraph is not responsible for removing the descendants or ancestors
374 BOOST_CHECK_EQUAL(graph->GetTransactionCount(TxGraph::Level::TOP), 3);
375 // only A remains there
376 graph->RemoveTransaction(refs[2]);
377 graph->RemoveTransaction(refs[3]);
378 BOOST_CHECK_EQUAL(graph->GetTransactionCount(TxGraph::Level::TOP), 1);
380}
381
383{
384 /* Create a new graph for the test.
385 * The parameters are max_cluster_count, max_cluster_size, acceptable_iters
386 */
387 auto graph = MakeTxGraph(10, 1000, HIGH_ACCEPTABLE_COST, PointerComparator);
388
389 std::vector<TxGraph::Ref> refs;
390 refs.reserve(2);
391
392 FeePerWeight feerateA{2, 10};
393 FeePerWeight feerateB{1, 10};
394
395 // everytime adding a transaction, test the chunk status
396 // [A]
397 graph->AddTransaction(refs.emplace_back(), feerateA);
398 BOOST_CHECK_EQUAL(graph->HaveStaging(), false);
399 BOOST_CHECK_EQUAL(graph->GetTransactionCount(TxGraph::Level::TOP), 1);
400
401 graph->StartStaging();
402 BOOST_CHECK_EQUAL(graph->HaveStaging(), true);
403 BOOST_CHECK_EQUAL(graph->GetTransactionCount(TxGraph::Level::TOP), 1);
404
405 // [A, B]
406 graph->AddTransaction(refs.emplace_back(), feerateB);
407 BOOST_CHECK_EQUAL(graph->GetTransactionCount(TxGraph::Level::MAIN), 1);
408 BOOST_CHECK_EQUAL(graph->GetTransactionCount(TxGraph::Level::TOP), 2);
409 BOOST_CHECK_EQUAL(graph->Exists(refs[0], TxGraph::Level::TOP), true);
410 BOOST_CHECK_EQUAL(graph->Exists(refs[1], TxGraph::Level::TOP), true);
411
412 graph->AddDependency(/*parent=*/refs[0], /*child=*/refs[1]);
413 BOOST_CHECK_EQUAL(graph->GetTransactionCount(TxGraph::Level::MAIN), 1);
414 BOOST_CHECK_EQUAL(graph->GetTransactionCount(TxGraph::Level::TOP), 2);
415
416 graph->CommitStaging();
417 BOOST_CHECK_EQUAL(graph->HaveStaging(), false);
418
419 BOOST_CHECK_EQUAL(graph->GetTransactionCount(TxGraph::Level::MAIN), 2);
420
421 graph->StartStaging();
422
423 // [A]
424 graph->RemoveTransaction(refs[1]);
425 BOOST_CHECK_EQUAL(graph->GetTransactionCount(TxGraph::Level::MAIN), 2);
426 BOOST_CHECK_EQUAL(graph->GetTransactionCount(TxGraph::Level::TOP), 1);
427
428 graph->CommitStaging();
429
430 BOOST_CHECK_EQUAL(graph->GetTransactionCount(TxGraph::Level::MAIN), 1);
431
432 graph->SanityCheck();
433}
434
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
@ MAIN
Always refers to the main graph, whether staging is present or not.
@ TOP
Refers to staging if it exists, main otherwise.
BOOST_AUTO_TEST_SUITE_END()
volatile double sum
Definition examples.cpp:10
uint64_t fee
#define BOOST_CHECK_EQUAL(v1, v2)
Definition object.cpp:17
#define BOOST_CHECK(expr)
Definition object.cpp:16
Tagged wrapper around FeeFrac to avoid unit confusion.
Definition feefrac.h:191
FastRandomContext rng
std::unique_ptr< TxGraph > MakeTxGraph(unsigned max_cluster_count, uint64_t max_cluster_size, uint64_t acceptable_cost, const std::function< std::strong_ordering(const TxGraph::Ref &, const TxGraph::Ref &)> &fallback_order) noexcept
Construct a new TxGraph with the specified limit on the number of transactions within a cluster,...
Definition txgraph.cpp:3582
BOOST_AUTO_TEST_CASE(txgraph_trim_zigzag)
constexpr auto Ticks(Dur2 d)
Helper to count the seconds of a duration/time_point.
Definition time.h:82