aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPieter Wuille <pieter@wuille.net>2024-03-17 09:42:12 -0400
committerPieter Wuille <pieter@wuille.net>2024-04-22 09:36:36 -0400
commitb22901dfa9cc3af94bf13163a28300eb1ab25b22 (patch)
tree9227531a651b5a29fc2ef395f9da6c4ae6d21414
parentba7c67f609a3d9a6da194d4abb7f8a60934907c3 (diff)
Avoid explicitly computing diagram; compare based on chunks
-rw-r--r--src/policy/rbf.cpp8
-rw-r--r--src/test/feefrac_tests.cpp39
-rw-r--r--src/test/fuzz/feeratediagram.cpp16
-rw-r--r--src/test/fuzz/rbf.cpp58
-rw-r--r--src/test/rbf_tests.cpp144
-rw-r--r--src/txmempool.cpp6
-rw-r--r--src/txmempool.h4
-rw-r--r--src/util/feefrac.cpp40
-rw-r--r--src/util/feefrac.h15
9 files changed, 140 insertions, 190 deletions
diff --git a/src/policy/rbf.cpp b/src/policy/rbf.cpp
index 84c3352b9d..2ad79b6f99 100644
--- a/src/policy/rbf.cpp
+++ b/src/policy/rbf.cpp
@@ -191,13 +191,13 @@ std::optional<std::pair<DiagramCheckError, std::string>> ImprovesFeerateDiagram(
int64_t replacement_vsize)
{
// Require that the replacement strictly improves the mempool's feerate diagram.
- const auto diagram_results{pool.CalculateFeerateDiagramsForRBF(replacement_fees, replacement_vsize, direct_conflicts, all_conflicts)};
+ const auto chunk_results{pool.CalculateChunksForRBF(replacement_fees, replacement_vsize, direct_conflicts, all_conflicts)};
- if (!diagram_results.has_value()) {
- return std::make_pair(DiagramCheckError::UNCALCULABLE, util::ErrorString(diagram_results).original);
+ if (!chunk_results.has_value()) {
+ return std::make_pair(DiagramCheckError::UNCALCULABLE, util::ErrorString(chunk_results).original);
}
- if (!std::is_gt(CompareFeerateDiagram(diagram_results.value().second, diagram_results.value().first))) {
+ if (!std::is_gt(CompareChunks(chunk_results.value().second, chunk_results.value().first))) {
return std::make_pair(DiagramCheckError::FAILURE, "insufficient feerate: does not improve feerate diagram");
}
return std::nullopt;
diff --git a/src/test/feefrac_tests.cpp b/src/test/feefrac_tests.cpp
index 2e015b382e..5af3c3d7ed 100644
--- a/src/test/feefrac_tests.cpp
+++ b/src/test/feefrac_tests.cpp
@@ -82,43 +82,4 @@ BOOST_AUTO_TEST_CASE(feefrac_operators)
}
-BOOST_AUTO_TEST_CASE(build_diagram_test)
-{
- FeeFrac p1{1000, 100};
- FeeFrac empty{0, 0};
- FeeFrac zero_fee{0, 1};
- FeeFrac oversized_1{4611686000000, 4000000};
- FeeFrac oversized_2{184467440000000, 100000};
-
- // Diagram-building will reorder chunks
- std::vector<FeeFrac> chunks;
- chunks.push_back(p1);
- chunks.push_back(zero_fee);
- chunks.push_back(empty);
- chunks.push_back(oversized_1);
- chunks.push_back(oversized_2);
-
- // Caller in charge of sorting chunks in case chunk size limit
- // differs from cluster size limit
- std::sort(chunks.begin(), chunks.end(), [](const FeeFrac& a, const FeeFrac& b) { return a > b; });
-
- // Chunks are now sorted in reverse order (largest first)
- BOOST_CHECK(chunks[0] == empty); // empty is considered "highest" fee
- BOOST_CHECK(chunks[1] == oversized_2);
- BOOST_CHECK(chunks[2] == oversized_1);
- BOOST_CHECK(chunks[3] == p1);
- BOOST_CHECK(chunks[4] == zero_fee);
-
- std::vector<FeeFrac> generated_diagram{BuildDiagramFromChunks(chunks)};
- BOOST_CHECK(generated_diagram.size() == 1 + chunks.size());
-
- // Prepended with an empty, then the chunks summed in order as above
- BOOST_CHECK(generated_diagram[0] == empty);
- BOOST_CHECK(generated_diagram[1] == empty);
- BOOST_CHECK(generated_diagram[2] == oversized_2);
- BOOST_CHECK(generated_diagram[3] == oversized_2 + oversized_1);
- BOOST_CHECK(generated_diagram[4] == oversized_2 + oversized_1 + p1);
- BOOST_CHECK(generated_diagram[5] == oversized_2 + oversized_1 + p1 + zero_fee);
-}
-
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/fuzz/feeratediagram.cpp b/src/test/fuzz/feeratediagram.cpp
index 6d710093cb..1a9c5ee946 100644
--- a/src/test/fuzz/feeratediagram.cpp
+++ b/src/test/fuzz/feeratediagram.cpp
@@ -16,6 +16,20 @@
namespace {
+/** Takes the pre-computed and topologically-valid chunks and generates a fee diagram which starts at FeeFrac of (0, 0) */
+std::vector<FeeFrac> BuildDiagramFromChunks(const Span<const FeeFrac> chunks)
+{
+ std::vector<FeeFrac> diagram;
+ diagram.reserve(chunks.size() + 1);
+
+ diagram.emplace_back(0, 0);
+ for (auto& chunk : chunks) {
+ diagram.emplace_back(diagram.back() + chunk);
+ }
+ return diagram;
+}
+
+
/** Evaluate a diagram at a specific size, returning the fee as a fraction.
*
* Fees in diagram cannot exceed 2^32, as the returned evaluation could overflow
@@ -103,7 +117,7 @@ FUZZ_TARGET(build_and_compare_feerate_diagram)
assert(diagram1.front() == empty);
assert(diagram2.front() == empty);
- auto real = CompareFeerateDiagram(diagram1, diagram2);
+ auto real = CompareChunks(chunks1, chunks2);
auto sim = CompareDiagrams(diagram1, diagram2);
assert(real == sim);
diff --git a/src/test/fuzz/rbf.cpp b/src/test/fuzz/rbf.cpp
index 30ed148c37..64785948f6 100644
--- a/src/test/fuzz/rbf.cpp
+++ b/src/test/fuzz/rbf.cpp
@@ -82,17 +82,6 @@ FUZZ_TARGET(rbf, .init = initialize_rbf)
}
}
-void CheckDiagramConcave(std::vector<FeeFrac>& diagram)
-{
- // Diagrams are in monotonically-decreasing feerate order.
- FeeFrac last_chunk = diagram.front();
- for (size_t i = 1; i<diagram.size(); ++i) {
- FeeFrac next_chunk = diagram[i] - diagram[i-1];
- assert(next_chunk <= last_chunk);
- last_chunk = next_chunk;
- }
-}
-
FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
{
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
@@ -107,7 +96,7 @@ FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
std::vector<CTransaction> mempool_txs;
size_t iter{0};
- int64_t replacement_vsize = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(1, 1000000);
+ int32_t replacement_vsize = fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(1, 1000000);
// Keep track of the total vsize of CTxMemPoolEntry's being added to the mempool to avoid overflow
// Add replacement_vsize since this is added to new diagram during RBF check
@@ -167,32 +156,33 @@ FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
pool.CalculateDescendants(txiter, all_conflicts);
}
- // Calculate the feerate diagrams for a replacement.
+ // Calculate the chunks for a replacement.
CAmount replacement_fees = ConsumeMoney(fuzzed_data_provider);
- auto calc_results{pool.CalculateFeerateDiagramsForRBF(replacement_fees, replacement_vsize, direct_conflicts, all_conflicts)};
+ auto calc_results{pool.CalculateChunksForRBF(replacement_fees, replacement_vsize, direct_conflicts, all_conflicts)};
if (calc_results.has_value()) {
- // Sanity checks on the diagrams.
+ // Sanity checks on the chunks.
- // Diagrams start at 0.
- assert(calc_results->first.front().size == 0);
- assert(calc_results->first.front().fee == 0);
- assert(calc_results->second.front().size == 0);
- assert(calc_results->second.front().fee == 0);
-
- CheckDiagramConcave(calc_results->first);
- CheckDiagramConcave(calc_results->second);
+ // Feerates are monotonically decreasing.
+ FeeFrac first_sum;
+ for (size_t i = 0; i < calc_results->first.size(); ++i) {
+ first_sum += calc_results->first[i];
+ if (i) assert(!(calc_results->first[i - 1] << calc_results->first[i]));
+ }
+ FeeFrac second_sum;
+ for (size_t i = 0; i < calc_results->second.size(); ++i) {
+ second_sum += calc_results->second[i];
+ if (i) assert(!(calc_results->second[i - 1] << calc_results->second[i]));
+ }
- CAmount replaced_fee{0};
- int64_t replaced_size{0};
+ FeeFrac replaced;
for (auto txiter : all_conflicts) {
- replaced_fee += txiter->GetModifiedFee();
- replaced_size += txiter->GetTxSize();
+ replaced.fee += txiter->GetModifiedFee();
+ replaced.size += txiter->GetTxSize();
}
- // The total fee of the new diagram should be the total fee of the old
- // diagram - replaced_fee + replacement_fees
- assert(calc_results->first.back().fee - replaced_fee + replacement_fees == calc_results->second.back().fee);
- assert(calc_results->first.back().size - replaced_size + replacement_vsize == calc_results->second.back().size);
+ // The total fee & size of the new diagram minus replaced fee & size should be the total
+ // fee & size of the old diagram minus replacement fee & size.
+ assert((first_sum - replaced) == (second_sum - FeeFrac{replacement_fees, replacement_vsize}));
}
// If internals report error, wrapper should too
@@ -201,10 +191,12 @@ FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
assert(err_tuple.value().first == DiagramCheckError::UNCALCULABLE);
} else {
// Diagram check succeeded
+ auto old_sum = std::accumulate(calc_results->first.begin(), calc_results->first.end(), FeeFrac{});
+ auto new_sum = std::accumulate(calc_results->second.begin(), calc_results->second.end(), FeeFrac{});
if (!err_tuple.has_value()) {
// New diagram's final fee should always match or exceed old diagram's
- assert(calc_results->first.back().fee <= calc_results->second.back().fee);
- } else if (calc_results->first.back().fee > calc_results->second.back().fee) {
+ assert(old_sum.fee <= new_sum.fee);
+ } else if (old_sum.fee > new_sum.fee) {
// Or it failed, and if old diagram had higher fees, it should be a failure
assert(err_tuple.value().first == DiagramCheckError::FAILURE);
}
diff --git a/src/test/rbf_tests.cpp b/src/test/rbf_tests.cpp
index ed33969710..19e45c550a 100644
--- a/src/test/rbf_tests.cpp
+++ b/src/test/rbf_tests.cpp
@@ -426,21 +426,21 @@ BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)
// Replacement of size 1
{
- const auto replace_one{pool.CalculateFeerateDiagramsForRBF(/*replacement_fees=*/0, /*replacement_vsize=*/1, {entry_low}, {entry_low})};
+ const auto replace_one{pool.CalculateChunksForRBF(/*replacement_fees=*/0, /*replacement_vsize=*/1, {entry_low}, {entry_low})};
BOOST_CHECK(replace_one.has_value());
- std::vector<FeeFrac> expected_old_diagram{FeeFrac(0, 0), FeeFrac(low_fee, low_size)};
- BOOST_CHECK(replace_one->first == expected_old_diagram);
- std::vector<FeeFrac> expected_new_diagram{FeeFrac(0, 0), FeeFrac(0, 1)};
- BOOST_CHECK(replace_one->second == expected_new_diagram);
+ std::vector<FeeFrac> expected_old_chunks{{low_fee, low_size}};
+ BOOST_CHECK(replace_one->first == expected_old_chunks);
+ std::vector<FeeFrac> expected_new_chunks{{0, 1}};
+ BOOST_CHECK(replace_one->second == expected_new_chunks);
}
// Non-zero replacement fee/size
{
- const auto replace_one_fee{pool.CalculateFeerateDiagramsForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_low}, {entry_low})};
+ const auto replace_one_fee{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_low}, {entry_low})};
BOOST_CHECK(replace_one_fee.has_value());
- std::vector<FeeFrac> expected_old_diagram{FeeFrac(0, 0), FeeFrac(low_fee, low_size)};
+ std::vector<FeeFrac> expected_old_diagram{{low_fee, low_size}};
BOOST_CHECK(replace_one_fee->first == expected_old_diagram);
- std::vector<FeeFrac> expected_new_diagram{FeeFrac(0, 0), FeeFrac(high_fee, low_size)};
+ std::vector<FeeFrac> expected_new_diagram{{high_fee, low_size}};
BOOST_CHECK(replace_one_fee->second == expected_new_diagram);
}
@@ -451,22 +451,22 @@ BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)
const auto high_size = entry_high->GetTxSize();
{
- const auto replace_single_chunk{pool.CalculateFeerateDiagramsForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_low}, {entry_low, entry_high})};
+ const auto replace_single_chunk{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_low}, {entry_low, entry_high})};
BOOST_CHECK(replace_single_chunk.has_value());
- std::vector<FeeFrac> expected_old_diagram{FeeFrac(0, 0), FeeFrac(low_fee + high_fee, low_size + high_size)};
- BOOST_CHECK(replace_single_chunk->first == expected_old_diagram);
- std::vector<FeeFrac> expected_new_diagram{FeeFrac(0, 0), FeeFrac(high_fee, low_size)};
- BOOST_CHECK(replace_single_chunk->second == expected_new_diagram);
+ std::vector<FeeFrac> expected_old_chunks{{low_fee + high_fee, low_size + high_size}};
+ BOOST_CHECK(replace_single_chunk->first == expected_old_chunks);
+ std::vector<FeeFrac> expected_new_chunks{{high_fee, low_size}};
+ BOOST_CHECK(replace_single_chunk->second == expected_new_chunks);
}
// Conflict with the 2nd tx, resulting in new diagram with three entries
{
- const auto replace_cpfp_child{pool.CalculateFeerateDiagramsForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_high}, {entry_high})};
+ const auto replace_cpfp_child{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_high}, {entry_high})};
BOOST_CHECK(replace_cpfp_child.has_value());
- std::vector<FeeFrac> expected_old_diagram{FeeFrac(0, 0), FeeFrac(low_fee + high_fee, low_size + high_size)};
- BOOST_CHECK(replace_cpfp_child->first == expected_old_diagram);
- std::vector<FeeFrac> expected_new_diagram{FeeFrac(0, 0), FeeFrac(high_fee, low_size), FeeFrac(low_fee + high_fee, low_size + low_size)};
- BOOST_CHECK(replace_cpfp_child->second == expected_new_diagram);
+ std::vector<FeeFrac> expected_old_chunks{{low_fee + high_fee, low_size + high_size}};
+ BOOST_CHECK(replace_cpfp_child->first == expected_old_chunks);
+ std::vector<FeeFrac> expected_new_chunks{{high_fee, low_size}, {low_fee, low_size}};
+ BOOST_CHECK(replace_cpfp_child->second == expected_new_chunks);
}
// third transaction causes the topology check to fail
@@ -476,7 +476,7 @@ BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)
const auto normal_size = entry_normal->GetTxSize();
{
- const auto replace_too_large{pool.CalculateFeerateDiagramsForRBF(/*replacement_fees=*/normal_fee, /*replacement_vsize=*/normal_size, {entry_low}, {entry_low, entry_high, entry_normal})};
+ const auto replace_too_large{pool.CalculateChunksForRBF(/*replacement_fees=*/normal_fee, /*replacement_vsize=*/normal_size, {entry_low}, {entry_low, entry_high, entry_normal})};
BOOST_CHECK(!replace_too_large.has_value());
BOOST_CHECK_EQUAL(util::ErrorString(replace_too_large).original, strprintf("%s has 2 descendants, max 1 allowed", low_tx->GetHash().GetHex()));
}
@@ -493,12 +493,12 @@ BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)
const auto low_size_2 = entry_low_2->GetTxSize();
{
- 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})};
+ const auto replace_two_chunks_single_cluster{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_high_2}, {entry_high_2, entry_low_2})};
BOOST_CHECK(replace_two_chunks_single_cluster.has_value());
- std::vector<FeeFrac> expected_old_diagram{FeeFrac(0, 0), FeeFrac(high_fee, high_size_2), FeeFrac(low_fee + high_fee, low_size_2 + high_size_2)};
- BOOST_CHECK(replace_two_chunks_single_cluster->first == expected_old_diagram);
- std::vector<FeeFrac> expected_new_diagram{FeeFrac(0, 0), FeeFrac(high_fee, low_size_2)};
- BOOST_CHECK(replace_two_chunks_single_cluster->second == expected_new_diagram);
+ std::vector<FeeFrac> expected_old_chunks{{high_fee, high_size_2}, {low_fee, low_size_2}};
+ BOOST_CHECK(replace_two_chunks_single_cluster->first == expected_old_chunks);
+ std::vector<FeeFrac> expected_new_chunks{{high_fee, low_size_2}};
+ BOOST_CHECK(replace_two_chunks_single_cluster->second == expected_new_chunks);
}
// You can have more than two direct conflicts if the there are multiple affected clusters, all of size 2 or less
@@ -515,10 +515,10 @@ BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)
const auto conflict_3_entry = pool.GetIter(conflict_3->GetHash()).value();
{
- 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})};
+ const auto replace_multiple_clusters{pool.CalculateChunksForRBF(/*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})};
BOOST_CHECK(replace_multiple_clusters.has_value());
- BOOST_CHECK(replace_multiple_clusters->first.size() == 4);
- BOOST_CHECK(replace_multiple_clusters->second.size() == 2);
+ BOOST_CHECK(replace_multiple_clusters->first.size() == 3);
+ BOOST_CHECK(replace_multiple_clusters->second.size() == 1);
}
// Add a child transaction to conflict_1 and make it cluster size 2, two chunks due to same feerate
@@ -527,11 +527,11 @@ BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)
const auto conflict_1_child_entry = pool.GetIter(conflict_1_child->GetHash()).value();
{
- 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})};
+ const auto replace_multiple_clusters_2{pool.CalculateChunksForRBF(/*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})};
BOOST_CHECK(replace_multiple_clusters_2.has_value());
- BOOST_CHECK(replace_multiple_clusters_2->first.size() == 5);
- BOOST_CHECK(replace_multiple_clusters_2->second.size() == 2);
+ BOOST_CHECK(replace_multiple_clusters_2->first.size() == 4);
+ BOOST_CHECK(replace_multiple_clusters_2->second.size() == 1);
}
// Add another descendant to conflict_1, making the cluster size > 2 should fail at this point.
@@ -540,86 +540,86 @@ BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)
const auto conflict_1_grand_child_entry = pool.GetIter(conflict_1_child->GetHash()).value();
{
- 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})};
+ const auto replace_cluster_size_3{pool.CalculateChunksForRBF(/*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})};
BOOST_CHECK(!replace_cluster_size_3.has_value());
BOOST_CHECK_EQUAL(util::ErrorString(replace_cluster_size_3).original, strprintf("%s has 2 descendants, max 1 allowed", conflict_1->GetHash().GetHex()));
}
}
-BOOST_AUTO_TEST_CASE(feerate_diagram_utilities)
+BOOST_AUTO_TEST_CASE(feerate_chunks_utilities)
{
- // Sanity check the correctness of the feerate diagram comparison.
+ // Sanity check the correctness of the feerate chunks comparison.
// A strictly better case.
- std::vector<FeeFrac> old_diagram{{FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}}};
- std::vector<FeeFrac> new_diagram{{FeeFrac{0, 0}, FeeFrac{1000, 300}, FeeFrac{1050, 400}}};
+ std::vector<FeeFrac> old_chunks{{{950, 300}, {100, 100}}};
+ std::vector<FeeFrac> new_chunks{{{1000, 300}, {50, 100}}};
- BOOST_CHECK(std::is_lt(CompareFeerateDiagram(old_diagram, new_diagram)));
- BOOST_CHECK(std::is_gt(CompareFeerateDiagram(new_diagram, old_diagram)));
+ BOOST_CHECK(std::is_lt(CompareChunks(old_chunks, new_chunks)));
+ BOOST_CHECK(std::is_gt(CompareChunks(new_chunks, old_chunks)));
// Incomparable diagrams
- old_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}};
- new_diagram = {FeeFrac{0, 0}, FeeFrac{1000, 300}, FeeFrac{1000, 400}};
+ old_chunks = {{950, 300}, {100, 100}};
+ new_chunks = {{1000, 300}, {0, 100}};
- BOOST_CHECK(CompareFeerateDiagram(old_diagram, new_diagram) == std::partial_ordering::unordered);
- BOOST_CHECK(CompareFeerateDiagram(new_diagram, old_diagram) == std::partial_ordering::unordered);
+ BOOST_CHECK(CompareChunks(old_chunks, new_chunks) == std::partial_ordering::unordered);
+ BOOST_CHECK(CompareChunks(new_chunks, old_chunks) == std::partial_ordering::unordered);
// Strictly better but smaller size.
- old_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}};
- new_diagram = {FeeFrac{0, 0}, FeeFrac{1100, 300}};
+ old_chunks = {{950, 300}, {100, 100}};
+ new_chunks = {{1100, 300}};
- BOOST_CHECK(std::is_lt(CompareFeerateDiagram(old_diagram, new_diagram)));
- BOOST_CHECK(std::is_gt(CompareFeerateDiagram(new_diagram, old_diagram)));
+ BOOST_CHECK(std::is_lt(CompareChunks(old_chunks, new_chunks)));
+ BOOST_CHECK(std::is_gt(CompareChunks(new_chunks, old_chunks)));
// New diagram is strictly better due to the first chunk, even though
// second chunk contributes no fees
- old_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}};
- new_diagram = {FeeFrac{0, 0}, FeeFrac{1100, 100}, FeeFrac{1100, 200}};
+ old_chunks = {{950, 300}, {100, 100}};
+ new_chunks = {{1100, 100}, {0, 100}};
- BOOST_CHECK(std::is_lt(CompareFeerateDiagram(old_diagram, new_diagram)));
- BOOST_CHECK(std::is_gt(CompareFeerateDiagram(new_diagram, old_diagram)));
+ BOOST_CHECK(std::is_lt(CompareChunks(old_chunks, new_chunks)));
+ BOOST_CHECK(std::is_gt(CompareChunks(new_chunks, old_chunks)));
// Feerate of first new chunk is better with, but second chunk is worse
- old_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}};
- new_diagram = {FeeFrac{0, 0}, FeeFrac{750, 100}, FeeFrac{999, 350}, FeeFrac{1150, 1000}};
+ old_chunks = {{950, 300}, {100, 100}};
+ new_chunks = {{750, 100}, {249, 250}, {151, 650}};
- BOOST_CHECK(CompareFeerateDiagram(old_diagram, new_diagram) == std::partial_ordering::unordered);
- BOOST_CHECK(CompareFeerateDiagram(new_diagram, old_diagram) == std::partial_ordering::unordered);
+ BOOST_CHECK(CompareChunks(old_chunks, new_chunks) == std::partial_ordering::unordered);
+ BOOST_CHECK(CompareChunks(new_chunks, old_chunks) == std::partial_ordering::unordered);
// If we make the second chunk slightly better, the new diagram now wins.
- old_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}};
- new_diagram = {FeeFrac{0, 0}, FeeFrac{750, 100}, FeeFrac{1000, 350}, FeeFrac{1150, 500}};
+ old_chunks = {{950, 300}, {100, 100}};
+ new_chunks = {{750, 100}, {250, 250}, {150, 150}};
- BOOST_CHECK(std::is_lt(CompareFeerateDiagram(old_diagram, new_diagram)));
- BOOST_CHECK(std::is_gt(CompareFeerateDiagram(new_diagram, old_diagram)));
+ BOOST_CHECK(std::is_lt(CompareChunks(old_chunks, new_chunks)));
+ BOOST_CHECK(std::is_gt(CompareChunks(new_chunks, old_chunks)));
// Identical diagrams, cannot be strictly better
- old_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}};
- new_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}};
+ old_chunks = {{950, 300}, {100, 100}};
+ new_chunks = {{950, 300}, {100, 100}};
- BOOST_CHECK(std::is_eq(CompareFeerateDiagram(old_diagram, new_diagram)));
- BOOST_CHECK(std::is_eq(CompareFeerateDiagram(new_diagram, old_diagram)));
+ BOOST_CHECK(std::is_eq(CompareChunks(old_chunks, new_chunks)));
+ BOOST_CHECK(std::is_eq(CompareChunks(new_chunks, old_chunks)));
// Same aggregate fee, but different total size (trigger single tail fee check step)
- old_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 399}};
- new_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}};
+ old_chunks = {{950, 300}, {100, 99}};
+ new_chunks = {{950, 300}, {100, 100}};
// No change in evaluation when tail check needed.
- BOOST_CHECK(std::is_gt(CompareFeerateDiagram(old_diagram, new_diagram)));
- BOOST_CHECK(std::is_lt(CompareFeerateDiagram(new_diagram, old_diagram)));
+ BOOST_CHECK(std::is_gt(CompareChunks(old_chunks, new_chunks)));
+ BOOST_CHECK(std::is_lt(CompareChunks(new_chunks, old_chunks)));
// Trigger multiple tail fee check steps
- old_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 399}};
- new_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}, FeeFrac{1050, 401}, FeeFrac{1050, 402}};
+ old_chunks = {{950, 300}, {100, 99}};
+ new_chunks = {{950, 300}, {100, 100}, {0, 1}, {0, 1}};
- BOOST_CHECK(std::is_gt(CompareFeerateDiagram(old_diagram, new_diagram)));
- BOOST_CHECK(std::is_lt(CompareFeerateDiagram(new_diagram, old_diagram)));
+ BOOST_CHECK(std::is_gt(CompareChunks(old_chunks, new_chunks)));
+ BOOST_CHECK(std::is_lt(CompareChunks(new_chunks, old_chunks)));
// Multiple tail fee check steps, unordered result
- new_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}, FeeFrac{1050, 401}, FeeFrac{1050, 402}, FeeFrac{1051, 403}};
- BOOST_CHECK(CompareFeerateDiagram(old_diagram, new_diagram) == std::partial_ordering::unordered);
- BOOST_CHECK(CompareFeerateDiagram(new_diagram, old_diagram) == std::partial_ordering::unordered);
+ new_chunks = {{950, 300}, {100, 100}, {0, 1}, {0, 1}, {1, 1}};
+ BOOST_CHECK(CompareChunks(old_chunks, new_chunks) == std::partial_ordering::unordered);
+ BOOST_CHECK(CompareChunks(new_chunks, old_chunks) == std::partial_ordering::unordered);
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/txmempool.cpp b/src/txmempool.cpp
index 82eec6241f..06066e38b2 100644
--- a/src/txmempool.cpp
+++ b/src/txmempool.cpp
@@ -1280,7 +1280,7 @@ std::optional<std::string> CTxMemPool::CheckConflictTopology(const setEntries& d
return std::nullopt;
}
-util::Result<std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>>> CTxMemPool::CalculateFeerateDiagramsForRBF(CAmount replacement_fees, int64_t replacement_vsize, const setEntries& direct_conflicts, const setEntries& all_conflicts)
+util::Result<std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>>> CTxMemPool::CalculateChunksForRBF(CAmount replacement_fees, int64_t replacement_vsize, const setEntries& direct_conflicts, const setEntries& all_conflicts)
{
Assume(replacement_vsize > 0);
@@ -1335,7 +1335,6 @@ util::Result<std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>>> CTxMemPool::
// No topology restrictions post-chunking; sort
std::sort(old_chunks.begin(), old_chunks.end(), std::greater());
- std::vector<FeeFrac> old_diagram = BuildDiagramFromChunks(old_chunks);
std::vector<FeeFrac> new_chunks;
@@ -1365,6 +1364,5 @@ util::Result<std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>>> CTxMemPool::
// No topology restrictions post-chunking; sort
std::sort(new_chunks.begin(), new_chunks.end(), std::greater());
- std::vector<FeeFrac> new_diagram = BuildDiagramFromChunks(new_chunks);
- return std::make_pair(old_diagram, new_diagram);
+ return std::make_pair(old_chunks, new_chunks);
}
diff --git a/src/txmempool.h b/src/txmempool.h
index 9dd4d56da7..52a3dc2d7d 100644
--- a/src/txmempool.h
+++ b/src/txmempool.h
@@ -738,7 +738,7 @@ public:
}
/**
- * Calculate the old and new mempool feerate diagrams relating to the
+ * Calculate the sorted chunks for the old and new mempool relating to the
* clusters that would be affected by a potential replacement transaction.
* (replacement_fees, replacement_vsize) values are gathered from a
* proposed set of replacement transactions that are considered as a single
@@ -752,7 +752,7 @@ public:
* @param[in] all_conflicts All transactions that would be removed
* @return old and new diagram pair respectively, or an error string if the conflicts don't match a calculable topology
*/
- util::Result<std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>>> CalculateFeerateDiagramsForRBF(CAmount replacement_fees, int64_t replacement_vsize, const setEntries& direct_conflicts, const setEntries& all_conflicts) EXCLUSIVE_LOCKS_REQUIRED(cs);
+ util::Result<std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>>> CalculateChunksForRBF(CAmount replacement_fees, int64_t replacement_vsize, const setEntries& direct_conflicts, const setEntries& all_conflicts) EXCLUSIVE_LOCKS_REQUIRED(cs);
/* Check that all direct conflicts are in a cluster size of two or less. Each
* direct conflict may be in a separate cluster.
diff --git a/src/util/feefrac.cpp b/src/util/feefrac.cpp
index 68fb836936..5b6173835c 100644
--- a/src/util/feefrac.cpp
+++ b/src/util/feefrac.cpp
@@ -7,39 +7,26 @@
#include <array>
#include <vector>
-std::vector<FeeFrac> BuildDiagramFromChunks(const Span<const FeeFrac> chunks)
-{
- std::vector<FeeFrac> diagram;
- diagram.reserve(chunks.size() + 1);
-
- diagram.emplace_back(0, 0);
- for (auto& chunk : chunks) {
- diagram.emplace_back(diagram.back() + chunk);
- }
- return diagram;
-}
-
-std::partial_ordering CompareFeerateDiagram(Span<const FeeFrac> dia0, Span<const FeeFrac> dia1)
+std::partial_ordering CompareChunks(Span<const FeeFrac> chunks0, Span<const FeeFrac> chunks1)
{
/** Array to allow indexed access to input diagrams. */
- const std::array<Span<const FeeFrac>, 2> dias = {dia0, dia1};
+ const std::array<Span<const FeeFrac>, 2> chunk = {chunks0, chunks1};
/** How many elements we have processed in each input. */
- size_t next_index[2] = {1, 1};
+ size_t next_index[2] = {0, 0};
+ /** Accumulated fee/sizes in diagrams, up to next_index[i] - 1. */
+ FeeFrac accum[2];
/** Whether the corresponding input is strictly better than the other at least in one place. */
bool better_somewhere[2] = {false, false};
/** Get the first unprocessed point in diagram number dia. */
- const auto next_point = [&](int dia) { return dias[dia][next_index[dia]]; };
+ const auto next_point = [&](int dia) { return chunk[dia][next_index[dia]] + accum[dia]; };
/** Get the last processed point in diagram number dia. */
- const auto prev_point = [&](int dia) { return dias[dia][next_index[dia] - 1]; };
-
- // Diagrams should be non-empty, and first elements zero in size and fee
- Assert(!dia0.empty() && !dia1.empty());
- Assert(prev_point(0).IsEmpty());
- Assert(prev_point(1).IsEmpty());
+ const auto prev_point = [&](int dia) { return accum[dia]; };
+ /** Move to the next point in diagram number dia. */
+ const auto advance = [&](int dia) { accum[dia] += chunk[dia][next_index[dia]++]; };
do {
- bool done_0 = next_index[0] == dias[0].size();
- bool done_1 = next_index[1] == dias[1].size();
+ bool done_0 = next_index[0] == chunk[0].size();
+ bool done_1 = next_index[1] == chunk[1].size();
if (done_0 && done_1) break;
// Determine which diagram has the first unprocessed point. If a single side is finished, use the
@@ -69,17 +56,16 @@ std::partial_ordering CompareFeerateDiagram(Span<const FeeFrac> dia0, Span<const
// If B and P have the same size, B can be marked as processed (in addition to P, see
// below), as we've already performed a comparison at this size.
- if (point_b.size == point_p.size) ++next_index[!unproc_side];
+ if (point_b.size == point_p.size) advance(!unproc_side);
}
// If P lies above AB, unproc_side is better in P. If P lies below AB, then !unproc_side is
// better in P.
if (std::is_gt(cmp)) better_somewhere[unproc_side] = true;
if (std::is_lt(cmp)) better_somewhere[!unproc_side] = true;
- ++next_index[unproc_side];
+ advance(unproc_side);
// If both diagrams are better somewhere, they are incomparable.
if (better_somewhere[0] && better_somewhere[1]) return std::partial_ordering::unordered;
-
} while(true);
// Otherwise compare the better_somewhere values.
diff --git a/src/util/feefrac.h b/src/util/feefrac.h
index 7102f85f88..9772162010 100644
--- a/src/util/feefrac.h
+++ b/src/util/feefrac.h
@@ -146,15 +146,14 @@ struct FeeFrac
}
};
-/** Takes the pre-computed and topologically-valid chunks and generates a fee diagram which starts at FeeFrac of (0, 0) */
-std::vector<FeeFrac> BuildDiagramFromChunks(Span<const FeeFrac> chunks);
-
-/** Compares two feerate diagrams. The shorter one is implicitly
- * extended with a horizontal straight line.
+/** Compare the feerate diagrams implied by the provided sorted chunks data.
+ *
+ * The implied diagram for each starts at (0, 0), then contains for each chunk the cumulative fee
+ * and size up to that chunk, and then extends infinitely to the right with a horizontal line.
*
- * A feerate diagram consists of a list of (fee, size) points with the property that size
- * is strictly increasing and that the first entry is (0, 0).
+ * The caller must guarantee that the sum of the FeeFracs in either of the chunks' data set do not
+ * overflow (so sum fees < 2^63, and sum sizes < 2^31).
*/
-std::partial_ordering CompareFeerateDiagram(Span<const FeeFrac> dia0, Span<const FeeFrac> dia1);
+std::partial_ordering CompareChunks(Span<const FeeFrac> chunks0, Span<const FeeFrac> chunks1);
#endif // BITCOIN_UTIL_FEEFRAC_H