Skip to content

Commit 2a07c46

Browse files
committed
Merge bitcoin/bitcoin#29757: feefrac: avoid explicitly computing diagram; compare based on chunks
b22901d 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 bitcoin/bitcoin@b22901d Tree-SHA512: ca37bdf61d9a9cb5435f4da73e97ead33bf65828ad9af49b87336b1ece70db8ced1c21f517fc6eb6d616311c91f3da75ecae6b9bd42547133e3a3c5320b7816d
2 parents 50729c0 + b22901d commit 2a07c46

File tree

9 files changed

+140
-190
lines changed

9 files changed

+140
-190
lines changed

src/policy/rbf.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -191,13 +191,13 @@ std::optional<std::pair<DiagramCheckError, std::string>> ImprovesFeerateDiagram(
191191
int64_t replacement_vsize)
192192
{
193193
// Require that the replacement strictly improves the mempool's feerate diagram.
194-
const auto diagram_results{pool.CalculateFeerateDiagramsForRBF(replacement_fees, replacement_vsize, direct_conflicts, all_conflicts)};
194+
const auto chunk_results{pool.CalculateChunksForRBF(replacement_fees, replacement_vsize, direct_conflicts, all_conflicts)};
195195

196-
if (!diagram_results.has_value()) {
197-
return std::make_pair(DiagramCheckError::UNCALCULABLE, util::ErrorString(diagram_results).original);
196+
if (!chunk_results.has_value()) {
197+
return std::make_pair(DiagramCheckError::UNCALCULABLE, util::ErrorString(chunk_results).original);
198198
}
199199

200-
if (!std::is_gt(CompareFeerateDiagram(diagram_results.value().second, diagram_results.value().first))) {
200+
if (!std::is_gt(CompareChunks(chunk_results.value().second, chunk_results.value().first))) {
201201
return std::make_pair(DiagramCheckError::FAILURE, "insufficient feerate: does not improve feerate diagram");
202202
}
203203
return std::nullopt;

src/test/feefrac_tests.cpp

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -82,43 +82,4 @@ BOOST_AUTO_TEST_CASE(feefrac_operators)
8282

8383
}
8484

85-
BOOST_AUTO_TEST_CASE(build_diagram_test)
86-
{
87-
FeeFrac p1{1000, 100};
88-
FeeFrac empty{0, 0};
89-
FeeFrac zero_fee{0, 1};
90-
FeeFrac oversized_1{4611686000000, 4000000};
91-
FeeFrac oversized_2{184467440000000, 100000};
92-
93-
// Diagram-building will reorder chunks
94-
std::vector<FeeFrac> chunks;
95-
chunks.push_back(p1);
96-
chunks.push_back(zero_fee);
97-
chunks.push_back(empty);
98-
chunks.push_back(oversized_1);
99-
chunks.push_back(oversized_2);
100-
101-
// Caller in charge of sorting chunks in case chunk size limit
102-
// differs from cluster size limit
103-
std::sort(chunks.begin(), chunks.end(), [](const FeeFrac& a, const FeeFrac& b) { return a > b; });
104-
105-
// Chunks are now sorted in reverse order (largest first)
106-
BOOST_CHECK(chunks[0] == empty); // empty is considered "highest" fee
107-
BOOST_CHECK(chunks[1] == oversized_2);
108-
BOOST_CHECK(chunks[2] == oversized_1);
109-
BOOST_CHECK(chunks[3] == p1);
110-
BOOST_CHECK(chunks[4] == zero_fee);
111-
112-
std::vector<FeeFrac> generated_diagram{BuildDiagramFromChunks(chunks)};
113-
BOOST_CHECK(generated_diagram.size() == 1 + chunks.size());
114-
115-
// Prepended with an empty, then the chunks summed in order as above
116-
BOOST_CHECK(generated_diagram[0] == empty);
117-
BOOST_CHECK(generated_diagram[1] == empty);
118-
BOOST_CHECK(generated_diagram[2] == oversized_2);
119-
BOOST_CHECK(generated_diagram[3] == oversized_2 + oversized_1);
120-
BOOST_CHECK(generated_diagram[4] == oversized_2 + oversized_1 + p1);
121-
BOOST_CHECK(generated_diagram[5] == oversized_2 + oversized_1 + p1 + zero_fee);
122-
}
123-
12485
BOOST_AUTO_TEST_SUITE_END()

src/test/fuzz/feeratediagram.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,20 @@
1616

1717
namespace {
1818

19+
/** Takes the pre-computed and topologically-valid chunks and generates a fee diagram which starts at FeeFrac of (0, 0) */
20+
std::vector<FeeFrac> BuildDiagramFromChunks(const Span<const FeeFrac> chunks)
21+
{
22+
std::vector<FeeFrac> diagram;
23+
diagram.reserve(chunks.size() + 1);
24+
25+
diagram.emplace_back(0, 0);
26+
for (auto& chunk : chunks) {
27+
diagram.emplace_back(diagram.back() + chunk);
28+
}
29+
return diagram;
30+
}
31+
32+
1933
/** Evaluate a diagram at a specific size, returning the fee as a fraction.
2034
*
2135
* Fees in diagram cannot exceed 2^32, as the returned evaluation could overflow
@@ -103,7 +117,7 @@ FUZZ_TARGET(build_and_compare_feerate_diagram)
103117
assert(diagram1.front() == empty);
104118
assert(diagram2.front() == empty);
105119

106-
auto real = CompareFeerateDiagram(diagram1, diagram2);
120+
auto real = CompareChunks(chunks1, chunks2);
107121
auto sim = CompareDiagrams(diagram1, diagram2);
108122
assert(real == sim);
109123

src/test/fuzz/rbf.cpp

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,6 @@ FUZZ_TARGET(rbf, .init = initialize_rbf)
8282
}
8383
}
8484

85-
void CheckDiagramConcave(std::vector<FeeFrac>& diagram)
86-
{
87-
// Diagrams are in monotonically-decreasing feerate order.
88-
FeeFrac last_chunk = diagram.front();
89-
for (size_t i = 1; i<diagram.size(); ++i) {
90-
FeeFrac next_chunk = diagram[i] - diagram[i-1];
91-
assert(next_chunk <= last_chunk);
92-
last_chunk = next_chunk;
93-
}
94-
}
95-
9685
FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
9786
{
9887
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
@@ -107,7 +96,7 @@ FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
10796
std::vector<CTransaction> mempool_txs;
10897
size_t iter{0};
10998

110-
int64_t replacement_vsize = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(1, 1000000);
99+
int32_t replacement_vsize = fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(1, 1000000);
111100

112101
// Keep track of the total vsize of CTxMemPoolEntry's being added to the mempool to avoid overflow
113102
// 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)
167156
pool.CalculateDescendants(txiter, all_conflicts);
168157
}
169158

170-
// Calculate the feerate diagrams for a replacement.
159+
// Calculate the chunks for a replacement.
171160
CAmount replacement_fees = ConsumeMoney(fuzzed_data_provider);
172-
auto calc_results{pool.CalculateFeerateDiagramsForRBF(replacement_fees, replacement_vsize, direct_conflicts, all_conflicts)};
161+
auto calc_results{pool.CalculateChunksForRBF(replacement_fees, replacement_vsize, direct_conflicts, all_conflicts)};
173162

174163
if (calc_results.has_value()) {
175-
// Sanity checks on the diagrams.
164+
// Sanity checks on the chunks.
176165

177-
// Diagrams start at 0.
178-
assert(calc_results->first.front().size == 0);
179-
assert(calc_results->first.front().fee == 0);
180-
assert(calc_results->second.front().size == 0);
181-
assert(calc_results->second.front().fee == 0);
182-
183-
CheckDiagramConcave(calc_results->first);
184-
CheckDiagramConcave(calc_results->second);
166+
// Feerates are monotonically decreasing.
167+
FeeFrac first_sum;
168+
for (size_t i = 0; i < calc_results->first.size(); ++i) {
169+
first_sum += calc_results->first[i];
170+
if (i) assert(!(calc_results->first[i - 1] << calc_results->first[i]));
171+
}
172+
FeeFrac second_sum;
173+
for (size_t i = 0; i < calc_results->second.size(); ++i) {
174+
second_sum += calc_results->second[i];
175+
if (i) assert(!(calc_results->second[i - 1] << calc_results->second[i]));
176+
}
185177

186-
CAmount replaced_fee{0};
187-
int64_t replaced_size{0};
178+
FeeFrac replaced;
188179
for (auto txiter : all_conflicts) {
189-
replaced_fee += txiter->GetModifiedFee();
190-
replaced_size += txiter->GetTxSize();
180+
replaced.fee += txiter->GetModifiedFee();
181+
replaced.size += txiter->GetTxSize();
191182
}
192-
// The total fee of the new diagram should be the total fee of the old
193-
// diagram - replaced_fee + replacement_fees
194-
assert(calc_results->first.back().fee - replaced_fee + replacement_fees == calc_results->second.back().fee);
195-
assert(calc_results->first.back().size - replaced_size + replacement_vsize == calc_results->second.back().size);
183+
// The total fee & size of the new diagram minus replaced fee & size should be the total
184+
// fee & size of the old diagram minus replacement fee & size.
185+
assert((first_sum - replaced) == (second_sum - FeeFrac{replacement_fees, replacement_vsize}));
196186
}
197187

198188
// If internals report error, wrapper should too
@@ -201,10 +191,12 @@ FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
201191
assert(err_tuple.value().first == DiagramCheckError::UNCALCULABLE);
202192
} else {
203193
// Diagram check succeeded
194+
auto old_sum = std::accumulate(calc_results->first.begin(), calc_results->first.end(), FeeFrac{});
195+
auto new_sum = std::accumulate(calc_results->second.begin(), calc_results->second.end(), FeeFrac{});
204196
if (!err_tuple.has_value()) {
205197
// New diagram's final fee should always match or exceed old diagram's
206-
assert(calc_results->first.back().fee <= calc_results->second.back().fee);
207-
} else if (calc_results->first.back().fee > calc_results->second.back().fee) {
198+
assert(old_sum.fee <= new_sum.fee);
199+
} else if (old_sum.fee > new_sum.fee) {
208200
// Or it failed, and if old diagram had higher fees, it should be a failure
209201
assert(err_tuple.value().first == DiagramCheckError::FAILURE);
210202
}

0 commit comments

Comments
 (0)