Merge bitcoin/bitcoin#29757: feefrac: avoid explicitly computing diagram; compare based on chunks

b22901dfa9 Avoid explicitly computing diagram; compare based on chunks (Pieter Wuille)

Pull request description:

  This merges the `BuildDiagramFromChunks` and `CompareFeeRateDiagram` introduced in #29242 into a single `CompareChunks` function, which operates on sorted chunk data rather than diagrams, instead computing the diagram on the fly.

  This avoids the need for the construction of an intermediary diagram object, and removes the slightly arbitrary "all diagrams must start at (0, 0)" requirement.

  Not a big deal, but I think the result is a bit cleaner and not really more complicated.

ACKs for top commit:
  glozow:
    reACK b22901d
  instagibbs:
    reACK https://github.com/bitcoin/bitcoin/pull/29757/commits/b22901dfa9cc3af94bf13163a28300eb1ab25b22

Tree-SHA512: ca37bdf61d9a9cb5435f4da73e97ead33bf65828ad9af49b87336b1ece70db8ced1c21f517fc6eb6d616311c91f3da75ecae6b9bd42547133e3a3c5320b7816d
This commit is contained in:
glozow committed 2024-04-24 16:51:56 +01:00
commit 2a07c4662d
9 files changed
+142 -192

No files matched your search

+4 -4
View File
@@ -191,13 +191,13 @@ std::optional<std::pair<DiagramCheckError, std::string>> ImprovesFeerateDiagram(
int64_t replacement_vsize) int64_t replacement_vsize)
{ {
// Require that the replacement strictly improves the mempool's feerate diagram. // 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()) { if (!chunk_results.has_value()) {
return std::make_pair(DiagramCheckError::UNCALCULABLE, util::ErrorString(diagram_results).original); 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::make_pair(DiagramCheckError::FAILURE, "insufficient feerate: does not improve feerate diagram");
} }
return std::nullopt; return std::nullopt;
-39
View File
@@ -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() BOOST_AUTO_TEST_SUITE_END()
+15 -1
View File
@@ -16,6 +16,20 @@
namespace { 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. /** 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 * 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(diagram1.front() == empty);
assert(diagram2.front() == empty); assert(diagram2.front() == empty);
auto real = CompareFeerateDiagram(diagram1, diagram2); auto real = CompareChunks(chunks1, chunks2);
auto sim = CompareDiagrams(diagram1, diagram2); auto sim = CompareDiagrams(diagram1, diagram2);
assert(real == sim); assert(real == sim);
+27 -35
View File
@@ -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) FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
{ {
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); 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; std::vector<CTransaction> mempool_txs;
size_t iter{0}; 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 // 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 // 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); 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); 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()) { if (calc_results.has_value()) {
// Sanity checks on the diagrams. // Sanity checks on the chunks.
// Diagrams start at 0. // Feerates are monotonically decreasing.
assert(calc_results->first.front().size == 0); FeeFrac first_sum;
assert(calc_results->first.front().fee == 0); for (size_t i = 0; i < calc_results->first.size(); ++i) {
assert(calc_results->second.front().size == 0); first_sum += calc_results->first[i];
assert(calc_results->second.front().fee == 0); if (i) assert(!(calc_results->first[i - 1] << calc_results->first[i]));
CheckDiagramConcave(calc_results->first);
CheckDiagramConcave(calc_results->second);
CAmount replaced_fee{0};
int64_t replaced_size{0};
for (auto txiter : all_conflicts) {
replaced_fee += txiter->GetModifiedFee();
replaced_size += txiter->GetTxSize();
} }
// The total fee of the new diagram should be the total fee of the old FeeFrac second_sum;
// diagram - replaced_fee + replacement_fees for (size_t i = 0; i < calc_results->second.size(); ++i) {
assert(calc_results->first.back().fee - replaced_fee + replacement_fees == calc_results->second.back().fee); second_sum += calc_results->second[i];
assert(calc_results->first.back().size - replaced_size + replacement_vsize == calc_results->second.back().size); if (i) assert(!(calc_results->second[i - 1] << calc_results->second[i]));
}
FeeFrac replaced;
for (auto txiter : all_conflicts) {
replaced.fee += txiter->GetModifiedFee();
replaced.size += txiter->GetTxSize();
}
// 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 // 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); assert(err_tuple.value().first == DiagramCheckError::UNCALCULABLE);
} else { } else {
// Diagram check succeeded // 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()) { if (!err_tuple.has_value()) {
// New diagram's final fee should always match or exceed old diagram's // New diagram's final fee should always match or exceed old diagram's
assert(calc_results->first.back().fee <= calc_results->second.back().fee); assert(old_sum.fee <= new_sum.fee);
} else if (calc_results->first.back().fee > calc_results->second.back().fee) { } else if (old_sum.fee > new_sum.fee) {
// Or it failed, and if old diagram had higher fees, it should be a failure // Or it failed, and if old diagram had higher fees, it should be a failure
assert(err_tuple.value().first == DiagramCheckError::FAILURE); assert(err_tuple.value().first == DiagramCheckError::FAILURE);
} }
+72 -72
View File
@@ -426,21 +426,21 @@ BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)
// Replacement of size 1 // 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()); BOOST_CHECK(replace_one.has_value());
std::vector<FeeFrac> expected_old_diagram{FeeFrac(0, 0), FeeFrac(low_fee, low_size)}; std::vector<FeeFrac> expected_old_chunks{{low_fee, low_size}};
BOOST_CHECK(replace_one->first == expected_old_diagram); BOOST_CHECK(replace_one->first == expected_old_chunks);
std::vector<FeeFrac> expected_new_diagram{FeeFrac(0, 0), FeeFrac(0, 1)}; std::vector<FeeFrac> expected_new_chunks{{0, 1}};
BOOST_CHECK(replace_one->second == expected_new_diagram); BOOST_CHECK(replace_one->second == expected_new_chunks);
} }
// Non-zero replacement fee/size // 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()); 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); 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); 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 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()); 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)}; std::vector<FeeFrac> expected_old_chunks{{low_fee + high_fee, low_size + high_size}};
BOOST_CHECK(replace_single_chunk->first == expected_old_diagram); BOOST_CHECK(replace_single_chunk->first == expected_old_chunks);
std::vector<FeeFrac> expected_new_diagram{FeeFrac(0, 0), FeeFrac(high_fee, low_size)}; std::vector<FeeFrac> expected_new_chunks{{high_fee, low_size}};
BOOST_CHECK(replace_single_chunk->second == expected_new_diagram); BOOST_CHECK(replace_single_chunk->second == expected_new_chunks);
} }
// Conflict with the 2nd tx, resulting in new diagram with three entries // 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()); 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)}; std::vector<FeeFrac> expected_old_chunks{{low_fee + high_fee, low_size + high_size}};
BOOST_CHECK(replace_cpfp_child->first == expected_old_diagram); BOOST_CHECK(replace_cpfp_child->first == expected_old_chunks);
std::vector<FeeFrac> expected_new_diagram{FeeFrac(0, 0), FeeFrac(high_fee, low_size), FeeFrac(low_fee + high_fee, low_size + low_size)}; std::vector<FeeFrac> expected_new_chunks{{high_fee, low_size}, {low_fee, low_size}};
BOOST_CHECK(replace_cpfp_child->second == expected_new_diagram); BOOST_CHECK(replace_cpfp_child->second == expected_new_chunks);
} }
// third transaction causes the topology check to fail // 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 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(!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())); 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 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()); 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)}; 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_diagram); BOOST_CHECK(replace_two_chunks_single_cluster->first == expected_old_chunks);
std::vector<FeeFrac> expected_new_diagram{FeeFrac(0, 0), FeeFrac(high_fee, low_size_2)}; std::vector<FeeFrac> expected_new_chunks{{high_fee, low_size_2}};
BOOST_CHECK(replace_two_chunks_single_cluster->second == expected_new_diagram); 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 // 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 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.has_value());
BOOST_CHECK(replace_multiple_clusters->first.size() == 4); BOOST_CHECK(replace_multiple_clusters->first.size() == 3);
BOOST_CHECK(replace_multiple_clusters->second.size() == 2); 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 // 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 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.has_value());
BOOST_CHECK(replace_multiple_clusters_2->first.size() == 5); BOOST_CHECK(replace_multiple_clusters_2->first.size() == 4);
BOOST_CHECK(replace_multiple_clusters_2->second.size() == 2); 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. // 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 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(!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_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. // A strictly better case.
std::vector<FeeFrac> old_diagram{{FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}}}; std::vector<FeeFrac> old_chunks{{{950, 300}, {100, 100}}};
std::vector<FeeFrac> new_diagram{{FeeFrac{0, 0}, FeeFrac{1000, 300}, FeeFrac{1050, 400}}}; std::vector<FeeFrac> new_chunks{{{1000, 300}, {50, 100}}};
BOOST_CHECK(std::is_lt(CompareFeerateDiagram(old_diagram, new_diagram))); BOOST_CHECK(std::is_lt(CompareChunks(old_chunks, new_chunks)));
BOOST_CHECK(std::is_gt(CompareFeerateDiagram(new_diagram, old_diagram))); BOOST_CHECK(std::is_gt(CompareChunks(new_chunks, old_chunks)));
// Incomparable diagrams // Incomparable diagrams
old_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}}; old_chunks = {{950, 300}, {100, 100}};
new_diagram = {FeeFrac{0, 0}, FeeFrac{1000, 300}, FeeFrac{1000, 400}}; new_chunks = {{1000, 300}, {0, 100}};
BOOST_CHECK(CompareFeerateDiagram(old_diagram, new_diagram) == std::partial_ordering::unordered); BOOST_CHECK(CompareChunks(old_chunks, new_chunks) == std::partial_ordering::unordered);
BOOST_CHECK(CompareFeerateDiagram(new_diagram, old_diagram) == std::partial_ordering::unordered); BOOST_CHECK(CompareChunks(new_chunks, old_chunks) == std::partial_ordering::unordered);
// Strictly better but smaller size. // Strictly better but smaller size.
old_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}}; old_chunks = {{950, 300}, {100, 100}};
new_diagram = {FeeFrac{0, 0}, FeeFrac{1100, 300}}; new_chunks = {{1100, 300}};
BOOST_CHECK(std::is_lt(CompareFeerateDiagram(old_diagram, new_diagram))); BOOST_CHECK(std::is_lt(CompareChunks(old_chunks, new_chunks)));
BOOST_CHECK(std::is_gt(CompareFeerateDiagram(new_diagram, old_diagram))); BOOST_CHECK(std::is_gt(CompareChunks(new_chunks, old_chunks)));
// New diagram is strictly better due to the first chunk, even though // New diagram is strictly better due to the first chunk, even though
// second chunk contributes no fees // second chunk contributes no fees
old_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}}; old_chunks = {{950, 300}, {100, 100}};
new_diagram = {FeeFrac{0, 0}, FeeFrac{1100, 100}, FeeFrac{1100, 200}}; new_chunks = {{1100, 100}, {0, 100}};
BOOST_CHECK(std::is_lt(CompareFeerateDiagram(old_diagram, new_diagram))); BOOST_CHECK(std::is_lt(CompareChunks(old_chunks, new_chunks)));
BOOST_CHECK(std::is_gt(CompareFeerateDiagram(new_diagram, old_diagram))); BOOST_CHECK(std::is_gt(CompareChunks(new_chunks, old_chunks)));
// Feerate of first new chunk is better with, but second chunk is worse // Feerate of first new chunk is better with, but second chunk is worse
old_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}}; old_chunks = {{950, 300}, {100, 100}};
new_diagram = {FeeFrac{0, 0}, FeeFrac{750, 100}, FeeFrac{999, 350}, FeeFrac{1150, 1000}}; new_chunks = {{750, 100}, {249, 250}, {151, 650}};
BOOST_CHECK(CompareFeerateDiagram(old_diagram, new_diagram) == std::partial_ordering::unordered); BOOST_CHECK(CompareChunks(old_chunks, new_chunks) == std::partial_ordering::unordered);
BOOST_CHECK(CompareFeerateDiagram(new_diagram, old_diagram) == 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. // If we make the second chunk slightly better, the new diagram now wins.
old_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}}; old_chunks = {{950, 300}, {100, 100}};
new_diagram = {FeeFrac{0, 0}, FeeFrac{750, 100}, FeeFrac{1000, 350}, FeeFrac{1150, 500}}; new_chunks = {{750, 100}, {250, 250}, {150, 150}};
BOOST_CHECK(std::is_lt(CompareFeerateDiagram(old_diagram, new_diagram))); BOOST_CHECK(std::is_lt(CompareChunks(old_chunks, new_chunks)));
BOOST_CHECK(std::is_gt(CompareFeerateDiagram(new_diagram, old_diagram))); BOOST_CHECK(std::is_gt(CompareChunks(new_chunks, old_chunks)));
// Identical diagrams, cannot be strictly better // Identical diagrams, cannot be strictly better
old_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}}; old_chunks = {{950, 300}, {100, 100}};
new_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}}; new_chunks = {{950, 300}, {100, 100}};
BOOST_CHECK(std::is_eq(CompareFeerateDiagram(old_diagram, new_diagram))); BOOST_CHECK(std::is_eq(CompareChunks(old_chunks, new_chunks)));
BOOST_CHECK(std::is_eq(CompareFeerateDiagram(new_diagram, old_diagram))); BOOST_CHECK(std::is_eq(CompareChunks(new_chunks, old_chunks)));
// Same aggregate fee, but different total size (trigger single tail fee check step) // Same aggregate fee, but different total size (trigger single tail fee check step)
old_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 399}}; old_chunks = {{950, 300}, {100, 99}};
new_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}}; new_chunks = {{950, 300}, {100, 100}};
// No change in evaluation when tail check needed. // No change in evaluation when tail check needed.
BOOST_CHECK(std::is_gt(CompareFeerateDiagram(old_diagram, new_diagram))); BOOST_CHECK(std::is_gt(CompareChunks(old_chunks, new_chunks)));
BOOST_CHECK(std::is_lt(CompareFeerateDiagram(new_diagram, old_diagram))); BOOST_CHECK(std::is_lt(CompareChunks(new_chunks, old_chunks)));
// Trigger multiple tail fee check steps // Trigger multiple tail fee check steps
old_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 399}}; old_chunks = {{950, 300}, {100, 99}};
new_diagram = {FeeFrac{0, 0}, FeeFrac{950, 300}, FeeFrac{1050, 400}, FeeFrac{1050, 401}, FeeFrac{1050, 402}}; new_chunks = {{950, 300}, {100, 100}, {0, 1}, {0, 1}};
BOOST_CHECK(std::is_gt(CompareFeerateDiagram(old_diagram, new_diagram))); BOOST_CHECK(std::is_gt(CompareChunks(old_chunks, new_chunks)));
BOOST_CHECK(std::is_lt(CompareFeerateDiagram(new_diagram, old_diagram))); BOOST_CHECK(std::is_lt(CompareChunks(new_chunks, old_chunks)));
// Multiple tail fee check steps, unordered result // 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}}; new_chunks = {{950, 300}, {100, 100}, {0, 1}, {0, 1}, {1, 1}};
BOOST_CHECK(CompareFeerateDiagram(old_diagram, new_diagram) == std::partial_ordering::unordered); BOOST_CHECK(CompareChunks(old_chunks, new_chunks) == std::partial_ordering::unordered);
BOOST_CHECK(CompareFeerateDiagram(new_diagram, old_diagram) == std::partial_ordering::unordered); BOOST_CHECK(CompareChunks(new_chunks, old_chunks) == std::partial_ordering::unordered);
} }
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()
+2 -4
View File
@@ -1280,7 +1280,7 @@ std::optional<std::string> CTxMemPool::CheckConflictTopology(const setEntries& d
return std::nullopt; 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); 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 // No topology restrictions post-chunking; sort
std::sort(old_chunks.begin(), old_chunks.end(), std::greater()); std::sort(old_chunks.begin(), old_chunks.end(), std::greater());
std::vector<FeeFrac> old_diagram = BuildDiagramFromChunks(old_chunks);
std::vector<FeeFrac> new_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 // No topology restrictions post-chunking; sort
std::sort(new_chunks.begin(), new_chunks.end(), std::greater()); std::sort(new_chunks.begin(), new_chunks.end(), std::greater());
std::vector<FeeFrac> new_diagram = BuildDiagramFromChunks(new_chunks); return std::make_pair(old_chunks, new_chunks);
return std::make_pair(old_diagram, new_diagram);
} }
+2 -2
View File
@@ -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. * clusters that would be affected by a potential replacement transaction.
* (replacement_fees, replacement_vsize) values are gathered from a * (replacement_fees, replacement_vsize) values are gathered from a
* proposed set of replacement transactions that are considered as a single * 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 * @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 * @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 /* Check that all direct conflicts are in a cluster size of two or less. Each
* direct conflict may be in a separate cluster. * direct conflict may be in a separate cluster.
+13 -27
View File
@@ -7,39 +7,26 @@
#include <array> #include <array>
#include <vector> #include <vector>
std::vector<FeeFrac> BuildDiagramFromChunks(const Span<const FeeFrac> chunks) std::partial_ordering CompareChunks(Span<const FeeFrac> chunks0, Span<const FeeFrac> chunks1)
{
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)
{ {
/** Array to allow indexed access to input diagrams. */ /** 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. */ /** 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. */ /** Whether the corresponding input is strictly better than the other at least in one place. */
bool better_somewhere[2] = {false, false}; bool better_somewhere[2] = {false, false};
/** Get the first unprocessed point in diagram number dia. */ /** 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. */ /** Get the last processed point in diagram number dia. */
const auto prev_point = [&](int dia) { return dias[dia][next_index[dia] - 1]; }; const auto prev_point = [&](int dia) { return accum[dia]; };
/** Move to the next point in diagram number dia. */
// Diagrams should be non-empty, and first elements zero in size and fee const auto advance = [&](int dia) { accum[dia] += chunk[dia][next_index[dia]++]; };
Assert(!dia0.empty() && !dia1.empty());
Assert(prev_point(0).IsEmpty());
Assert(prev_point(1).IsEmpty());
do { do {
bool done_0 = next_index[0] == dias[0].size(); bool done_0 = next_index[0] == chunk[0].size();
bool done_1 = next_index[1] == dias[1].size(); bool done_1 = next_index[1] == chunk[1].size();
if (done_0 && done_1) break; if (done_0 && done_1) break;
// Determine which diagram has the first unprocessed point. If a single side is finished, use the // 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 // 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. // 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 // If P lies above AB, unproc_side is better in P. If P lies below AB, then !unproc_side is
// better in P. // better in P.
if (std::is_gt(cmp)) better_somewhere[unproc_side] = true; if (std::is_gt(cmp)) better_somewhere[unproc_side] = true;
if (std::is_lt(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 both diagrams are better somewhere, they are incomparable.
if (better_somewhere[0] && better_somewhere[1]) return std::partial_ordering::unordered; if (better_somewhere[0] && better_somewhere[1]) return std::partial_ordering::unordered;
} while(true); } while(true);
// Otherwise compare the better_somewhere values. // Otherwise compare the better_somewhere values.
+7 -8
View File
@@ -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) */ /** Compare the feerate diagrams implied by the provided sorted chunks data.
std::vector<FeeFrac> BuildDiagramFromChunks(Span<const FeeFrac> chunks);
/** Compares two feerate diagrams. The shorter one is implicitly
* extended with a horizontal straight line.
* *
* A feerate diagram consists of a list of (fee, size) points with the property that size * The implied diagram for each starts at (0, 0), then contains for each chunk the cumulative fee
* is strictly increasing and that the first entry is (0, 0). * and size up to that chunk, and then extends infinitely to the right with a horizontal line.
*
* 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 #endif // BITCOIN_UTIL_FEEFRAC_H