@@ -334,6 +334,164 @@ BOOST_FIXTURE_TEST_CASE(improves_feerate, TestChain100Setup)
334
334
BOOST_CHECK (res3.has_value ());
335
335
BOOST_CHECK (res3.value ().first == DiagramCheckError::UNCALCULABLE);
336
336
BOOST_CHECK (res3.value ().second == strprintf (" %s has 2 descendants, max 1 allowed" , tx1->GetHash ().GetHex ()));
337
+
338
+ }
339
+
340
+ BOOST_FIXTURE_TEST_CASE (calc_feerate_diagram_rbf, TestChain100Setup)
341
+ {
342
+ CTxMemPool& pool = *Assert (m_node.mempool );
343
+ LOCK2 (::cs_main, pool.cs );
344
+ TestMemPoolEntryHelper entry;
345
+
346
+ const CAmount low_fee{CENT/100 };
347
+ const CAmount normal_fee{CENT/10 };
348
+ const CAmount high_fee{CENT};
349
+
350
+ // low -> high -> medium fee transactions that would result in two chunks together
351
+ const auto low_tx = make_tx (/* inputs=*/ {m_coinbase_txns[0 ]}, /* output_values=*/ {10 * COIN});
352
+ pool.addUnchecked (entry.Fee (low_fee).FromTx (low_tx));
353
+
354
+ const auto entry_low = pool.GetIter (low_tx->GetHash ()).value ();
355
+ const auto low_size = entry_low->GetTxSize ();
356
+
357
+ std::vector<FeeFrac> old_diagram, new_diagram;
358
+
359
+ // Replacement of size 1
360
+ const auto replace_one{pool.CalculateFeerateDiagramsForRBF (/* replacement_fees=*/ 0 , /* replacement_vsize=*/ 1 , {entry_low}, {entry_low})};
361
+ BOOST_CHECK (replace_one.has_value ());
362
+ old_diagram = replace_one->first ;
363
+ new_diagram = replace_one->second ;
364
+ BOOST_CHECK (old_diagram.size () == 2 );
365
+ BOOST_CHECK (new_diagram.size () == 2 );
366
+ BOOST_CHECK (old_diagram[0 ] == FeeFrac (0 , 0 ));
367
+ BOOST_CHECK (old_diagram[1 ] == FeeFrac (low_fee, low_size));
368
+ BOOST_CHECK (new_diagram[0 ] == FeeFrac (0 , 0 ));
369
+ BOOST_CHECK (new_diagram[1 ] == FeeFrac (0 , 1 ));
370
+
371
+ // Non-zero replacement fee/size
372
+ const auto replace_one_fee{pool.CalculateFeerateDiagramsForRBF (/* replacement_fees=*/ high_fee, /* replacement_vsize=*/ low_size, {entry_low}, {entry_low})};
373
+ BOOST_CHECK (replace_one_fee.has_value ());
374
+ old_diagram = replace_one_fee->first ;
375
+ new_diagram = replace_one_fee->second ;
376
+ BOOST_CHECK (old_diagram.size () == 2 );
377
+ BOOST_CHECK (new_diagram.size () == 2 );
378
+ BOOST_CHECK (old_diagram[0 ] == FeeFrac (0 , 0 ));
379
+ BOOST_CHECK (old_diagram[1 ] == FeeFrac (low_fee, low_size));
380
+ BOOST_CHECK (new_diagram[0 ] == FeeFrac (0 , 0 ));
381
+ BOOST_CHECK (new_diagram[1 ] == FeeFrac (high_fee, low_size));
382
+
383
+ // Add a second transaction to the cluster that will make a single chunk, to be evicted in the RBF
384
+ const auto high_tx = make_tx (/* inputs=*/ {low_tx}, /* output_values=*/ {995 * CENT});
385
+ pool.addUnchecked (entry.Fee (high_fee).FromTx (high_tx));
386
+ const auto entry_high = pool.GetIter (high_tx->GetHash ()).value ();
387
+ const auto high_size = entry_high->GetTxSize ();
388
+
389
+ const auto replace_single_chunk{pool.CalculateFeerateDiagramsForRBF (/* replacement_fees=*/ high_fee, /* replacement_vsize=*/ low_size, {entry_low}, {entry_low, entry_high})};
390
+ BOOST_CHECK (replace_single_chunk.has_value ());
391
+ old_diagram = replace_single_chunk->first ;
392
+ new_diagram = replace_single_chunk->second ;
393
+ BOOST_CHECK (old_diagram.size () == 2 );
394
+ BOOST_CHECK (new_diagram.size () == 2 );
395
+ BOOST_CHECK (old_diagram[0 ] == FeeFrac (0 , 0 ));
396
+ BOOST_CHECK (old_diagram[1 ] == FeeFrac (low_fee + high_fee, low_size + high_size));
397
+ BOOST_CHECK (new_diagram[0 ] == FeeFrac (0 , 0 ));
398
+ BOOST_CHECK (new_diagram[1 ] == FeeFrac (high_fee, low_size));
399
+
400
+ // Conflict with the 2nd tx, resulting in new diagram with three entries
401
+ const auto replace_cpfp_child{pool.CalculateFeerateDiagramsForRBF (/* replacement_fees=*/ high_fee, /* replacement_vsize=*/ low_size, {entry_high}, {entry_high})};
402
+ BOOST_CHECK (replace_cpfp_child.has_value ());
403
+ old_diagram = replace_cpfp_child->first ;
404
+ new_diagram = replace_cpfp_child->second ;
405
+ BOOST_CHECK (old_diagram.size () == 2 );
406
+ BOOST_CHECK (new_diagram.size () == 3 );
407
+ BOOST_CHECK (old_diagram[0 ] == FeeFrac (0 , 0 ));
408
+ BOOST_CHECK (old_diagram[1 ] == FeeFrac (low_fee + high_fee, low_size + high_size));
409
+ BOOST_CHECK (new_diagram[0 ] == FeeFrac (0 , 0 ));
410
+ BOOST_CHECK (new_diagram[1 ] == FeeFrac (high_fee, low_size));
411
+ BOOST_CHECK (new_diagram[2 ] == FeeFrac (low_fee + high_fee, low_size + low_size));
412
+
413
+ // third transaction causes the topology check to fail
414
+ const auto normal_tx = make_tx (/* inputs=*/ {high_tx}, /* output_values=*/ {995 * CENT});
415
+ pool.addUnchecked (entry.Fee (normal_fee).FromTx (normal_tx));
416
+ const auto entry_normal = pool.GetIter (normal_tx->GetHash ()).value ();
417
+ const auto normal_size = entry_normal->GetTxSize ();
418
+
419
+ const auto replace_too_large{pool.CalculateFeerateDiagramsForRBF (/* replacement_fees=*/ normal_fee, /* replacement_vsize=*/ normal_size, {entry_low}, {entry_low, entry_high, entry_normal})};
420
+ BOOST_CHECK (!replace_too_large.has_value ());
421
+ BOOST_CHECK_EQUAL (util::ErrorString (replace_too_large).original , strprintf (" %s has 2 descendants, max 1 allowed" , low_tx->GetHash ().GetHex ()));
422
+ old_diagram.clear ();
423
+ new_diagram.clear ();
424
+
425
+ // Make a size 2 cluster that is itself two chunks; evict both txns
426
+ const auto high_tx_2 = make_tx (/* inputs=*/ {m_coinbase_txns[1 ]}, /* output_values=*/ {10 * COIN});
427
+ pool.addUnchecked (entry.Fee (high_fee).FromTx (high_tx_2));
428
+ const auto entry_high_2 = pool.GetIter (high_tx_2->GetHash ()).value ();
429
+ const auto high_size_2 = entry_high_2->GetTxSize ();
430
+
431
+ const auto low_tx_2 = make_tx (/* inputs=*/ {high_tx_2}, /* output_values=*/ {9 * COIN});
432
+ pool.addUnchecked (entry.Fee (low_fee).FromTx (low_tx_2));
433
+ const auto entry_low_2 = pool.GetIter (low_tx_2->GetHash ()).value ();
434
+ const auto low_size_2 = entry_low_2->GetTxSize ();
435
+
436
+ const auto replace_two_chunks_single_cluster{pool.CalculateFeerateDiagramsForRBF (/* replacement_fees=*/ high_fee, /* replacement_vsize=*/ low_size, {entry_high_2}, {entry_high_2, entry_low_2})};
437
+ BOOST_CHECK (replace_two_chunks_single_cluster.has_value ());
438
+ old_diagram = replace_two_chunks_single_cluster->first ;
439
+ new_diagram = replace_two_chunks_single_cluster->second ;
440
+ BOOST_CHECK (old_diagram.size () == 3 );
441
+ BOOST_CHECK (new_diagram.size () == 2 );
442
+ BOOST_CHECK (old_diagram[0 ] == FeeFrac (0 , 0 ));
443
+ BOOST_CHECK (old_diagram[1 ] == FeeFrac (high_fee, high_size_2));
444
+ BOOST_CHECK (old_diagram[2 ] == FeeFrac (low_fee + high_fee, low_size_2 + high_size_2));
445
+ BOOST_CHECK (new_diagram[0 ] == FeeFrac (0 , 0 ));
446
+ BOOST_CHECK (new_diagram[1 ] == FeeFrac (high_fee, low_size_2));
447
+
448
+ // You can have more than two direct conflicts if the there are multiple effected clusters, all of size 2 or less
449
+ const auto conflict_1 = make_tx (/* inputs=*/ {m_coinbase_txns[2 ]}, /* output_values=*/ {10 * COIN});
450
+ pool.addUnchecked (entry.Fee (low_fee).FromTx (conflict_1));
451
+ const auto conflict_1_entry = pool.GetIter (conflict_1->GetHash ()).value ();
452
+
453
+ const auto conflict_2 = make_tx (/* inputs=*/ {m_coinbase_txns[3 ]}, /* output_values=*/ {10 * COIN});
454
+ pool.addUnchecked (entry.Fee (low_fee).FromTx (conflict_2));
455
+ const auto conflict_2_entry = pool.GetIter (conflict_2->GetHash ()).value ();
456
+
457
+ const auto conflict_3 = make_tx (/* inputs=*/ {m_coinbase_txns[4 ]}, /* output_values=*/ {10 * COIN});
458
+ pool.addUnchecked (entry.Fee (low_fee).FromTx (conflict_3));
459
+ const auto conflict_3_entry = pool.GetIter (conflict_3->GetHash ()).value ();
460
+
461
+ const auto replace_multiple_clusters{pool.CalculateFeerateDiagramsForRBF (/* replacement_fees=*/ high_fee, /* replacement_vsize=*/ low_size, {conflict_1_entry, conflict_2_entry, conflict_3_entry}, {conflict_1_entry, conflict_2_entry, conflict_3_entry})};
462
+
463
+ BOOST_CHECK (replace_multiple_clusters.has_value ());
464
+ old_diagram = replace_multiple_clusters->first ;
465
+ new_diagram = replace_multiple_clusters->second ;
466
+ BOOST_CHECK (old_diagram.size () == 4 );
467
+ BOOST_CHECK (new_diagram.size () == 2 );
468
+
469
+ // Add a child transaction to conflict_1 and make it cluster size 2, still one chunk due to same feerate
470
+ const auto conflict_1_child = make_tx (/* inputs=*/ {conflict_1}, /* output_values=*/ {995 * CENT});
471
+ pool.addUnchecked (entry.Fee (low_fee).FromTx (conflict_1_child));
472
+ const auto conflict_1_child_entry = pool.GetIter (conflict_1_child->GetHash ()).value ();
473
+
474
+ const auto replace_multiple_clusters_2{pool.CalculateFeerateDiagramsForRBF (/* replacement_fees=*/ high_fee, /* replacement_vsize=*/ low_size, {conflict_1_entry, conflict_2_entry, conflict_3_entry}, {conflict_1_entry, conflict_2_entry, conflict_3_entry, conflict_1_child_entry})};
475
+
476
+ BOOST_CHECK (replace_multiple_clusters_2.has_value ());
477
+ old_diagram = replace_multiple_clusters_2->first ;
478
+ new_diagram = replace_multiple_clusters_2->second ;
479
+ BOOST_CHECK (old_diagram.size () == 4 );
480
+ BOOST_CHECK (new_diagram.size () == 2 );
481
+ old_diagram.clear ();
482
+ new_diagram.clear ();
483
+
484
+ // Add another descendant to conflict_1, making the cluster size > 2 should fail at this point.
485
+ const auto conflict_1_grand_child = make_tx (/* inputs=*/ {conflict_1_child}, /* output_values=*/ {995 * CENT});
486
+ pool.addUnchecked (entry.Fee (high_fee).FromTx (conflict_1_grand_child));
487
+ const auto conflict_1_grand_child_entry = pool.GetIter (conflict_1_child->GetHash ()).value ();
488
+
489
+ const auto replace_cluster_size_3{pool.CalculateFeerateDiagramsForRBF (/* replacement_fees=*/ high_fee, /* replacement_vsize=*/ low_size, {conflict_1_entry, conflict_2_entry, conflict_3_entry}, {conflict_1_entry, conflict_2_entry, conflict_3_entry, conflict_1_child_entry, conflict_1_grand_child_entry})};
490
+
491
+ BOOST_CHECK (!replace_cluster_size_3.has_value ());
492
+ BOOST_CHECK_EQUAL (util::ErrorString (replace_cluster_size_3).original , strprintf (" %s has 2 descendants, max 1 allowed" , conflict_1->GetHash ().GetHex ()));
493
+ BOOST_CHECK (old_diagram.empty ());
494
+ BOOST_CHECK (new_diagram.empty ());
337
495
}
338
496
339
497
BOOST_AUTO_TEST_CASE (feerate_diagram_utilities)
0 commit comments