Coverage Report

Created: 2025-09-25 19:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/bitcoin/src/test/fuzz/feeratediagram.cpp
Line
Count
Source
1
// Copyright (c) 2023-present The Bitcoin Core developers
2
// Distributed under the MIT software license, see the accompanying
3
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5
#include <cstdint>
6
7
#include <vector>
8
9
#include <util/feefrac.h>
10
#include <policy/rbf.h>
11
12
#include <test/fuzz/fuzz.h>
13
#include <test/fuzz/util.h>
14
15
#include <cassert>
16
17
namespace {
18
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 std::span<const FeeFrac> chunks)
21
0
{
22
0
    std::vector<FeeFrac> diagram;
23
0
    diagram.reserve(chunks.size() + 1);
24
25
0
    diagram.emplace_back(0, 0);
26
0
    for (auto& chunk : chunks) {
27
0
        diagram.emplace_back(diagram.back() + chunk);
28
0
    }
29
0
    return diagram;
30
0
}
31
32
33
/** Evaluate a diagram at a specific size, returning the fee as a fraction.
34
 *
35
 * Fees in diagram cannot exceed 2^32, as the returned evaluation could overflow
36
 * the FeeFrac::fee field in the result. */
37
FeeFrac EvaluateDiagram(int32_t size, std::span<const FeeFrac> diagram)
38
0
{
39
0
    assert(diagram.size() > 0);
40
0
    unsigned not_above = 0;
41
0
    unsigned not_below = diagram.size() - 1;
42
    // If outside the range of diagram, extend begin/end.
43
0
    if (size < diagram[not_above].size) return {diagram[not_above].fee, 1};
44
0
    if (size > diagram[not_below].size) return {diagram[not_below].fee, 1};
45
    // Perform bisection search to locate the diagram segment that size is in.
46
0
    while (not_below > not_above + 1) {
47
0
        unsigned mid = (not_below + not_above) / 2;
48
0
        if (diagram[mid].size <= size) not_above = mid;
49
0
        if (diagram[mid].size >= size) not_below = mid;
50
0
    }
51
    // If the size matches a transition point between segments, return its fee.
52
0
    if (not_below == not_above) return {diagram[not_below].fee, 1};
53
    // Otherwise, interpolate.
54
0
    auto dir_coef = diagram[not_below] - diagram[not_above];
55
0
    assert(dir_coef.size > 0);
56
    // Let A = diagram[not_above] and B = diagram[not_below]
57
0
    const auto& point_a = diagram[not_above];
58
    // We want to return:
59
    //     A.fee + (B.fee - A.fee) / (B.size - A.size) * (size - A.size)
60
    //   = A.fee + dir_coef.fee / dir_coef.size * (size - A.size)
61
    //   = (A.fee * dir_coef.size + dir_coef.fee * (size - A.size)) / dir_coef.size
62
0
    assert(size >= point_a.size);
63
0
    return {point_a.fee * dir_coef.size + dir_coef.fee * (size - point_a.size), dir_coef.size};
64
0
}
65
66
std::weak_ordering CompareFeeFracWithDiagram(const FeeFrac& ff, std::span<const FeeFrac> diagram)
67
0
{
68
0
    return FeeRateCompare(FeeFrac{ff.fee, 1}, EvaluateDiagram(ff.size, diagram));
69
0
}
70
71
std::partial_ordering CompareDiagrams(std::span<const FeeFrac> dia1, std::span<const FeeFrac> dia2)
72
0
{
73
0
    bool all_ge = true;
74
0
    bool all_le = true;
75
0
    for (const auto p1 : dia1) {
76
0
        auto cmp = CompareFeeFracWithDiagram(p1, dia2);
77
0
        if (std::is_lt(cmp)) all_ge = false;
78
0
        if (std::is_gt(cmp)) all_le = false;
79
0
    }
80
0
    for (const auto p2 : dia2) {
81
0
        auto cmp = CompareFeeFracWithDiagram(p2, dia1);
82
0
        if (std::is_lt(cmp)) all_le = false;
83
0
        if (std::is_gt(cmp)) all_ge = false;
84
0
    }
85
0
    if (all_ge && all_le) return std::partial_ordering::equivalent;
86
0
    if (all_ge && !all_le) return std::partial_ordering::greater;
87
0
    if (!all_ge && all_le) return std::partial_ordering::less;
88
0
    return std::partial_ordering::unordered;
89
0
}
90
91
void PopulateChunks(FuzzedDataProvider& fuzzed_data_provider, std::vector<FeeFrac>& chunks)
92
0
{
93
0
    chunks.clear();
94
95
0
    LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 50)
96
0
    {
97
0
        chunks.emplace_back(fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(INT32_MIN>>1, INT32_MAX>>1), fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(1, 1000000));
98
0
    }
99
0
    return;
100
0
}
101
102
} // namespace
103
104
FUZZ_TARGET(build_and_compare_feerate_diagram)
105
0
{
106
    // Generate a random set of chunks
107
0
    FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
108
0
    std::vector<FeeFrac> chunks1, chunks2;
109
0
    FeeFrac empty{0, 0};
110
111
0
    PopulateChunks(fuzzed_data_provider, chunks1);
112
0
    PopulateChunks(fuzzed_data_provider, chunks2);
113
114
0
    std::vector<FeeFrac> diagram1{BuildDiagramFromChunks(chunks1)};
115
0
    std::vector<FeeFrac> diagram2{BuildDiagramFromChunks(chunks2)};
116
117
0
    assert(diagram1.front() == empty);
118
0
    assert(diagram2.front() == empty);
119
120
0
    auto real = CompareChunks(chunks1, chunks2);
121
0
    auto sim = CompareDiagrams(diagram1, diagram2);
122
0
    assert(real == sim);
123
124
    // Do explicit evaluation at up to 1000 points, and verify consistency with the result.
125
0
    LIMITED_WHILE(fuzzed_data_provider.remaining_bytes(), 1000) {
126
0
        int32_t size = fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(0, diagram2.back().size);
127
0
        auto eval1 = EvaluateDiagram(size, diagram1);
128
0
        auto eval2 = EvaluateDiagram(size, diagram2);
129
0
        auto cmp = FeeRateCompare(eval1, eval2);
130
0
        if (std::is_lt(cmp)) assert(!std::is_gt(real));
131
0
        if (std::is_gt(cmp)) assert(!std::is_lt(real));
132
0
    }
133
0
}