Coverage Report

Created: 2025-04-09 20:00

/root/bitcoin/src/test/fuzz/feefrac.cpp
Line
Count
Source (jump to first uncovered line)
1
// Copyright (c) 2024 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 <arith_uint256.h>
6
#include <policy/feerate.h>
7
#include <util/feefrac.h>
8
#include <test/fuzz/FuzzedDataProvider.h>
9
#include <test/fuzz/fuzz.h>
10
#include <test/fuzz/util.h>
11
12
#include <compare>
13
#include <cmath>
14
#include <cstdint>
15
#include <iostream>
16
17
namespace {
18
19
/** The maximum absolute value of an int64_t, as an arith_uint256 (2^63). */
20
const auto MAX_ABS_INT64 = arith_uint256{1} << 63;
21
22
/** Construct an arith_uint256 whose value equals abs(x). */
23
arith_uint256 Abs256(int64_t x)
24
0
{
25
0
    if (x >= 0) {
26
        // For positive numbers, pass through the value.
27
0
        return arith_uint256{static_cast<uint64_t>(x)};
28
0
    } else if (x > std::numeric_limits<int64_t>::min()) {
29
        // For negative numbers, negate first.
30
0
        return arith_uint256{static_cast<uint64_t>(-x)};
31
0
    } else {
32
        // Special case for x == -2^63 (for which -x results in integer overflow).
33
0
        return MAX_ABS_INT64;
34
0
    }
35
0
}
36
37
/** Construct an arith_uint256 whose value equals abs(x), for 96-bit x. */
38
arith_uint256 Abs256(std::pair<int64_t, uint32_t> x)
39
0
{
40
0
    if (x.first >= 0) {
41
        // x.first and x.second are both non-negative; sum their absolute values.
42
0
        return (Abs256(x.first) << 32) + Abs256(x.second);
43
0
    } else {
44
        // x.first is negative and x.second is non-negative; subtract the absolute values.
45
0
        return (Abs256(x.first) << 32) - Abs256(x.second);
46
0
    }
47
0
}
48
49
std::strong_ordering MulCompare(int64_t a1, int64_t a2, int64_t b1, int64_t b2)
50
0
{
51
    // Compute and compare signs.
52
0
    int sign_a = (a1 == 0 ? 0 : a1 < 0 ? -1 : 1) * (a2 == 0 ? 0 : a2 < 0 ? -1 : 1);
53
0
    int sign_b = (b1 == 0 ? 0 : b1 < 0 ? -1 : 1) * (b2 == 0 ? 0 : b2 < 0 ? -1 : 1);
54
0
    if (sign_a != sign_b) return sign_a <=> sign_b;
55
56
    // Compute absolute values of products.
57
0
    auto mul_abs_a = Abs256(a1) * Abs256(a2), mul_abs_b = Abs256(b1) * Abs256(b2);
58
59
    // Compute products of absolute values.
60
0
    if (sign_a < 0) {
61
0
        return mul_abs_b <=> mul_abs_a;
62
0
    } else {
63
0
        return mul_abs_a <=> mul_abs_b;
64
0
    }
65
0
}
66
67
} // namespace
68
69
FUZZ_TARGET(feefrac)
70
0
{
71
0
    FuzzedDataProvider provider(buffer.data(), buffer.size());
72
73
0
    int64_t f1 = provider.ConsumeIntegral<int64_t>();
74
0
    int32_t s1 = provider.ConsumeIntegral<int32_t>();
75
0
    if (s1 == 0) f1 = 0;
76
0
    FeeFrac fr1(f1, s1);
77
0
    assert(fr1.IsEmpty() == (s1 == 0));
78
79
0
    int64_t f2 = provider.ConsumeIntegral<int64_t>();
80
0
    int32_t s2 = provider.ConsumeIntegral<int32_t>();
81
0
    if (s2 == 0) f2 = 0;
82
0
    FeeFrac fr2(f2, s2);
83
0
    assert(fr2.IsEmpty() == (s2 == 0));
84
85
    // Feerate comparisons
86
0
    auto cmp_feerate = MulCompare(f1, s2, f2, s1);
87
0
    assert(FeeRateCompare(fr1, fr2) == cmp_feerate);
88
0
    assert((fr1 << fr2) == std::is_lt(cmp_feerate));
89
0
    assert((fr1 >> fr2) == std::is_gt(cmp_feerate));
90
91
    // Compare with manual invocation of FeeFrac::Mul.
92
0
    auto cmp_mul = FeeFrac::Mul(f1, s2) <=> FeeFrac::Mul(f2, s1);
93
0
    assert(cmp_mul == cmp_feerate);
94
95
    // Same, but using FeeFrac::MulFallback.
96
0
    auto cmp_fallback = FeeFrac::MulFallback(f1, s2) <=> FeeFrac::MulFallback(f2, s1);
97
0
    assert(cmp_fallback == cmp_feerate);
98
99
    // Total order comparisons
100
0
    auto cmp_total = std::is_eq(cmp_feerate) ? (s2 <=> s1) : cmp_feerate;
101
0
    assert((fr1 <=> fr2) == cmp_total);
102
0
    assert((fr1 < fr2) == std::is_lt(cmp_total));
103
0
    assert((fr1 > fr2) == std::is_gt(cmp_total));
104
0
    assert((fr1 <= fr2) == std::is_lteq(cmp_total));
105
0
    assert((fr1 >= fr2) == std::is_gteq(cmp_total));
106
0
    assert((fr1 == fr2) == std::is_eq(cmp_total));
107
0
    assert((fr1 != fr2) == std::is_neq(cmp_total));
108
0
}
109
110
FUZZ_TARGET(feefrac_div_fallback)
111
0
{
112
    // Verify the behavior of FeeFrac::DivFallback over all possible inputs.
113
114
    // Construct a 96-bit signed value num, a positive 31-bit value den, and rounding mode.
115
0
    FuzzedDataProvider provider(buffer.data(), buffer.size());
116
0
    auto num_high = provider.ConsumeIntegral<int64_t>();
117
0
    auto num_low = provider.ConsumeIntegral<uint32_t>();
118
0
    std::pair<int64_t, uint32_t> num{num_high, num_low};
119
0
    auto den = provider.ConsumeIntegralInRange<int32_t>(1, std::numeric_limits<int32_t>::max());
120
0
    auto round_down = provider.ConsumeBool();
121
122
    // Predict the sign of the actual result.
123
0
    bool is_negative = num_high < 0;
124
    // Evaluate absolute value using arith_uint256. If the actual result is negative and we are
125
    // rounding down, or positive and we are rounding up, the absolute value of the quotient is
126
    // the rounded-up quotient of the absolute values.
127
0
    auto num_abs = Abs256(num);
128
0
    auto den_abs = Abs256(den);
129
0
    auto quot_abs = (is_negative == round_down) ?
130
0
        (num_abs + den_abs - 1) / den_abs :
131
0
        num_abs / den_abs;
132
133
    // If the result is not representable by an int64_t, bail out.
134
0
    if ((is_negative && quot_abs > MAX_ABS_INT64) || (!is_negative && quot_abs >= MAX_ABS_INT64)) {
135
0
        return;
136
0
    }
137
138
    // Verify the behavior of FeeFrac::DivFallback.
139
0
    auto res = FeeFrac::DivFallback(num, den, round_down);
140
0
    assert(res == 0 || (res < 0) == is_negative);
141
0
    assert(Abs256(res) == quot_abs);
142
143
    // Compare approximately with floating-point.
144
0
    long double expect = round_down ? std::floor(num_high * 4294967296.0L + num_low) / den
145
0
                                    : std::ceil(num_high * 4294967296.0L + num_low) / den;
146
    // Expect to be accurate within 50 bits of precision, +- 1 sat.
147
0
    if (expect == 0.0L) {
148
0
        assert(res >= -1 && res <= 1);
149
0
    } else if (expect > 0.0L) {
150
0
        assert(res >= expect * 0.999999999999999L - 1.0L);
151
0
        assert(res <= expect * 1.000000000000001L + 1.0L);
152
0
    } else {
153
0
        assert(res >= expect * 1.000000000000001L - 1.0L);
154
0
        assert(res <= expect * 0.999999999999999L + 1.0L);
155
0
    }
156
0
}
157
158
FUZZ_TARGET(feefrac_mul_div)
159
0
{
160
    // Verify the behavior of:
161
    // - The combination of FeeFrac::Mul + FeeFrac::Div.
162
    // - The combination of FeeFrac::MulFallback + FeeFrac::DivFallback.
163
    // - FeeFrac::Evaluate.
164
165
    // Construct a 32-bit signed multiplicand, a 64-bit signed multiplicand, a positive 31-bit
166
    // divisor, and a rounding mode.
167
0
    FuzzedDataProvider provider(buffer.data(), buffer.size());
168
0
    auto mul32 = provider.ConsumeIntegral<int32_t>();
169
0
    auto mul64 = provider.ConsumeIntegral<int64_t>();
170
0
    auto div = provider.ConsumeIntegralInRange<int32_t>(1, std::numeric_limits<int32_t>::max());
171
0
    auto round_down = provider.ConsumeBool();
172
173
    // Predict the sign of the overall result.
174
0
    bool is_negative = ((mul32 < 0) && (mul64 > 0)) || ((mul32 > 0) && (mul64 < 0));
175
    // Evaluate absolute value using arith_uint256. If the actual result is negative and we are
176
    // rounding down or positive and we rounding up, the absolute value of the quotient is the
177
    // rounded-up quotient of the absolute values.
178
0
    auto prod_abs = Abs256(mul32) * Abs256(mul64);
179
0
    auto div_abs = Abs256(div);
180
0
    auto quot_abs = (is_negative == round_down) ?
181
0
        (prod_abs + div_abs - 1) / div_abs :
182
0
        prod_abs / div_abs;
183
184
    // If the result is not representable by an int64_t, bail out.
185
0
    if ((is_negative && quot_abs > MAX_ABS_INT64) || (!is_negative && quot_abs >= MAX_ABS_INT64)) {
186
        // If 0 <= mul32 <= div, then the result is guaranteed to be representable. In the context
187
        // of the Evaluate{Down,Up} calls below, this corresponds to 0 <= at_size <= feefrac.size.
188
0
        assert(mul32 < 0 || mul32 > div);
189
0
        return;
190
0
    }
191
192
    // Verify the behavior of FeeFrac::Mul + FeeFrac::Div.
193
0
    auto res = FeeFrac::Div(FeeFrac::Mul(mul64, mul32), div, round_down);
194
0
    assert(res == 0 || (res < 0) == is_negative);
195
0
    assert(Abs256(res) == quot_abs);
196
197
    // Verify the behavior of FeeFrac::MulFallback + FeeFrac::DivFallback.
198
0
    auto res_fallback = FeeFrac::DivFallback(FeeFrac::MulFallback(mul64, mul32), div, round_down);
199
0
    assert(res == res_fallback);
200
201
    // Compare approximately with floating-point.
202
0
    long double expect = round_down ? std::floor(static_cast<long double>(mul32) * mul64 / div)
203
0
                                    : std::ceil(static_cast<long double>(mul32) * mul64 / div);
204
    // Expect to be accurate within 50 bits of precision, +- 1 sat.
205
0
    if (expect == 0.0L) {
206
0
        assert(res >= -1 && res <= 1);
207
0
    } else if (expect > 0.0L) {
208
0
        assert(res >= expect * 0.999999999999999L - 1.0L);
209
0
        assert(res <= expect * 1.000000000000001L + 1.0L);
210
0
    } else {
211
0
        assert(res >= expect * 1.000000000000001L - 1.0L);
212
0
        assert(res <= expect * 0.999999999999999L + 1.0L);
213
0
    }
214
215
    // Verify the behavior of FeeFrac::Evaluate{Down,Up}.
216
0
    if (mul32 >= 0) {
217
0
        auto res_fee = round_down ?
218
0
            FeeFrac{mul64, div}.EvaluateFeeDown(mul32) :
219
0
            FeeFrac{mul64, div}.EvaluateFeeUp(mul32);
220
0
        assert(res == res_fee);
221
222
        // Compare approximately with CFeeRate.
223
0
        if (mul64 <= std::numeric_limits<int64_t>::max() / 1000 &&
224
0
            mul64 >= std::numeric_limits<int64_t>::min() / 1000 &&
225
0
            quot_abs <= arith_uint256{std::numeric_limits<int64_t>::max() / 1000}) {
226
0
            CFeeRate feerate(mul64, (uint32_t)div);
227
0
            CAmount feerate_fee{feerate.GetFee(mul32)};
228
0
            auto allowed_gap = static_cast<int64_t>(mul32 / 1000 + 3 + round_down);
229
0
            assert(feerate_fee - res_fee >= -allowed_gap);
230
0
            assert(feerate_fee - res_fee <= allowed_gap);
231
0
        }
232
0
    }
233
0
}