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