Skip to content

Commit 0ff8d82

Browse files
committed
Make FeeRate from sat constructors infallible
We now have constructors that take an arbitrary size fee rate (`Amount`). The `from_sat_per_foo` constructors can be made infallible by taking a `u32` instead of `u64`. This makes the API more ergonomic but limits the fee rate to just under 42 BTC which is plenty. Note we just delete the `from_sat_per_vb_u32` function because it is unreleased, in the past we had `from_sat_per_vb_unchecked` so we could put that back in if we wanted to be a bit more kind to downstream. Can be done later, we likely want to go over the public API before release and add a few things back in that we forgot to deprecate or could not for some reason during dev. Fuzz with a new function that consumes a `u32`.
1 parent 3b0286b commit 0ff8d82

File tree

11 files changed

+120
-128
lines changed

11 files changed

+120
-128
lines changed

bitcoin/src/blockdata/script/tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ fn default_dust_value() {
751751
assert!(script_p2wpkh.is_p2wpkh());
752752
assert_eq!(script_p2wpkh.minimal_non_dust(), Amount::from_sat_u32(294));
753753
assert_eq!(
754-
script_p2wpkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb_u32(6)),
754+
script_p2wpkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb(6)),
755755
Some(Amount::from_sat_u32(588))
756756
);
757757

@@ -765,7 +765,7 @@ fn default_dust_value() {
765765
assert!(script_p2pkh.is_p2pkh());
766766
assert_eq!(script_p2pkh.minimal_non_dust(), Amount::from_sat_u32(546));
767767
assert_eq!(
768-
script_p2pkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb_u32(6)),
768+
script_p2pkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb(6)),
769769
Some(Amount::from_sat_u32(1092))
770770
);
771771
}

bitcoin/src/blockdata/transaction.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1689,7 +1689,7 @@ mod tests {
16891689
#[test]
16901690
fn effective_value_happy_path() {
16911691
let value = "1 cBTC".parse::<Amount>().unwrap();
1692-
let fee_rate = FeeRate::from_sat_per_kwu(10).unwrap();
1692+
let fee_rate = FeeRate::from_sat_per_kwu(10);
16931693
let effective_value = effective_value(fee_rate, InputWeightPrediction::P2WPKH_MAX, value);
16941694

16951695
// 10 sat/kwu * 272 wu = 3 sats (rounding up)

bitcoin/src/psbt/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ impl Psbt {
127127
/// 1000 sats/vByte. 25k sats/vByte is obviously a mistake at this point.
128128
///
129129
/// [`extract_tx`]: Psbt::extract_tx
130-
pub const DEFAULT_MAX_FEE_RATE: FeeRate = FeeRate::from_sat_per_vb_u32(25_000);
130+
pub const DEFAULT_MAX_FEE_RATE: FeeRate = FeeRate::from_sat_per_vb(25_000);
131131

132132
/// An alias for [`extract_tx_fee_rate_limit`].
133133
///
@@ -1437,7 +1437,7 @@ mod tests {
14371437
// Large fee rate errors if we pass in 1 sat/vb so just use this to get the error fee rate returned.
14381438
let error_fee_rate = psbt
14391439
.clone()
1440-
.extract_tx_with_fee_rate_limit(FeeRate::from_sat_per_vb_u32(1))
1440+
.extract_tx_with_fee_rate_limit(FeeRate::from_sat_per_vb(1))
14411441
.map_err(|e| match e {
14421442
ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate,
14431443
_ => panic!(""),
@@ -1475,7 +1475,7 @@ mod tests {
14751475
ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate,
14761476
_ => panic!(""),
14771477
}),
1478-
Err(FeeRate::from_sat_per_kwu(6250003).unwrap()) // 6250000 is 25k sat/vbyte
1478+
Err(FeeRate::from_sat_per_kwu(6250003)) // 6250000 is 25k sat/vbyte
14791479
);
14801480

14811481
// Lowering the input satoshis by 1 lowers the sat/kwu by 3

fuzz/fuzz_targets/bitcoin/deserialize_script.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use bitcoin::address::Address;
22
use bitcoin::consensus::encode;
33
use bitcoin::script::{self, ScriptExt as _};
44
use bitcoin::{FeeRate, Network};
5-
use bitcoin_fuzz::fuzz_utils::{consume_random_bytes, consume_u64};
5+
use bitcoin_fuzz::fuzz_utils::{consume_random_bytes, consume_u32};
66
use honggfuzz::fuzz;
77

88
fn do_test(data: &[u8]) {
@@ -17,7 +17,7 @@ fn do_test(data: &[u8]) {
1717
let _ = script.count_sigops_legacy();
1818
let _ = script.minimal_non_dust();
1919

20-
let fee_rate = FeeRate::from_sat_per_kwu(consume_u64(&mut new_data)).unwrap();
20+
let fee_rate = FeeRate::from_sat_per_kwu(consume_u32(&mut new_data));
2121
let _ = script.minimal_non_dust_custom(fee_rate);
2222

2323
let mut b = script::Builder::new();

fuzz/src/fuzz_utils.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,16 @@ pub fn consume_u64(data: &mut &[u8]) -> u64 {
3535
u64_bytes[7],
3636
])
3737
}
38+
39+
#[allow(dead_code)]
40+
pub fn consume_u32(data: &mut &[u8]) -> u32 {
41+
// We need at least 4 bytes to read a u32
42+
if data.len() < 4 {
43+
return 0;
44+
}
45+
46+
let (u32_bytes, rest) = data.split_at(4);
47+
*data = rest;
48+
49+
u32::from_le_bytes([u32_bytes[0], u32_bytes[1], u32_bytes[2], u32_bytes[3]])
50+
}

units/src/amount/tests.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -270,13 +270,13 @@ fn amount_checked_div_by_weight_ceil() {
270270
let weight = Weight::from_kwu(1).unwrap();
271271
let fee_rate = sat(1).div_by_weight_ceil(weight).unwrap();
272272
// 1 sats / 1,000 wu = 1 sats/kwu
273-
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1).unwrap());
273+
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1));
274274

275275
let weight = Weight::from_wu(381);
276276
let fee_rate = sat(329).div_by_weight_ceil(weight).unwrap();
277277
// 329 sats / 381 wu = 863.5 sats/kwu
278278
// round up to 864
279-
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(864).unwrap());
279+
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(864));
280280

281281
let fee_rate = Amount::ONE_SAT.div_by_weight_ceil(Weight::ZERO);
282282
assert!(fee_rate.is_error());
@@ -288,13 +288,13 @@ fn amount_checked_div_by_weight_floor() {
288288
let weight = Weight::from_kwu(1).unwrap();
289289
let fee_rate = sat(1).div_by_weight_floor(weight).unwrap();
290290
// 1 sats / 1,000 wu = 1 sats/kwu
291-
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1).unwrap());
291+
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1));
292292

293293
let weight = Weight::from_wu(381);
294294
let fee_rate = sat(329).div_by_weight_floor(weight).unwrap();
295295
// 329 sats / 381 wu = 863.5 sats/kwu
296296
// round down to 863
297-
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(863).unwrap());
297+
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(863));
298298

299299
let fee_rate = Amount::ONE_SAT.div_by_weight_floor(Weight::ZERO);
300300
assert!(fee_rate.is_error());
@@ -304,7 +304,7 @@ fn amount_checked_div_by_weight_floor() {
304304
#[test]
305305
fn amount_checked_div_by_fee_rate() {
306306
let amount = sat(1000);
307-
let fee_rate = FeeRate::from_sat_per_kwu(2).unwrap();
307+
let fee_rate = FeeRate::from_sat_per_kwu(2);
308308

309309
// Test floor division
310310
let weight = amount.div_by_fee_rate_floor(fee_rate).unwrap();
@@ -317,20 +317,20 @@ fn amount_checked_div_by_fee_rate() {
317317

318318
// Test truncation behavior
319319
let amount = sat(1000);
320-
let fee_rate = FeeRate::from_sat_per_kwu(3).unwrap();
320+
let fee_rate = FeeRate::from_sat_per_kwu(3);
321321
let floor_weight = amount.div_by_fee_rate_floor(fee_rate).unwrap();
322322
let ceil_weight = amount.div_by_fee_rate_ceil(fee_rate).unwrap();
323323
assert_eq!(floor_weight, Weight::from_wu(333_333));
324324
assert_eq!(ceil_weight, Weight::from_wu(333_334));
325325

326326
// Test division by zero
327-
let zero_fee_rate = FeeRate::from_sat_per_kwu(0).unwrap();
327+
let zero_fee_rate = FeeRate::from_sat_per_kwu(0);
328328
assert!(amount.div_by_fee_rate_floor(zero_fee_rate).is_error());
329329
assert!(amount.div_by_fee_rate_ceil(zero_fee_rate).is_error());
330330

331331
// Test with maximum amount
332332
let max_amount = Amount::MAX;
333-
let small_fee_rate = FeeRate::from_sat_per_kwu(1).unwrap();
333+
let small_fee_rate = FeeRate::from_sat_per_kwu(1);
334334
let weight = max_amount.div_by_fee_rate_floor(small_fee_rate).unwrap();
335335
// 21_000_000_0000_0000 sats / (1 sat/kwu) = 2_100_000_000_000_000_000 wu
336336
assert_eq!(weight, Weight::from_wu(2_100_000_000_000_000_000));

units/src/amount/unsigned.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ impl Amount {
439439
/// let amount = Amount::from_sat(10)?;
440440
/// let weight = Weight::from_wu(300);
441441
/// let fee_rate = amount.div_by_weight_ceil(weight).expect("valid fee rate");
442-
/// assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(34).expect("valid fee rate"));
442+
/// assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(34));
443443
/// # Ok::<_, amount::OutOfRangeError>(())
444444
/// ```
445445
pub const fn div_by_weight_ceil(self, weight: Weight) -> NumOpResult<FeeRate> {

units/src/fee.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -190,12 +190,12 @@ mod tests {
190190
#[test]
191191
fn fee_rate_div_by_weight() {
192192
let fee_rate = (Amount::from_sat_u32(329) / Weight::from_wu(381)).unwrap();
193-
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(863).unwrap());
193+
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(863));
194194
}
195195

196196
#[test]
197197
fn fee_wu() {
198-
let fee_rate = FeeRate::from_sat_per_vb(2).unwrap();
198+
let fee_rate = FeeRate::from_sat_per_vb(2);
199199
let weight = Weight::from_vb(3).unwrap();
200200
assert_eq!(fee_rate.to_fee(weight), Amount::from_sat_u32(6));
201201
}
@@ -204,19 +204,19 @@ mod tests {
204204
fn weight_mul() {
205205
let weight = Weight::from_vb(10).unwrap();
206206
let fee: Amount =
207-
FeeRate::from_sat_per_vb(10).unwrap().mul_by_weight(weight).expect("expected Amount");
207+
FeeRate::from_sat_per_vb(10).mul_by_weight(weight).expect("expected Amount");
208208
assert_eq!(Amount::from_sat_u32(100), fee);
209209

210-
let fee = FeeRate::from_sat_per_kwu(10).unwrap().mul_by_weight(Weight::MAX);
210+
let fee = FeeRate::from_sat_per_kwu(10).mul_by_weight(Weight::MAX);
211211
assert!(fee.is_error());
212212

213213
let weight = Weight::from_vb(3).unwrap();
214-
let fee_rate = FeeRate::from_sat_per_vb(3).unwrap();
214+
let fee_rate = FeeRate::from_sat_per_vb(3);
215215
let fee = fee_rate.mul_by_weight(weight).unwrap();
216216
assert_eq!(Amount::from_sat_u32(9), fee);
217217

218218
let weight = Weight::from_wu(381);
219-
let fee_rate = FeeRate::from_sat_per_kwu(864).unwrap();
219+
let fee_rate = FeeRate::from_sat_per_kwu(864);
220220
let fee = weight.mul_by_fee_rate(fee_rate).unwrap();
221221
// 381 * 0.864 yields 329.18.
222222
// The result is then rounded up to 330.
@@ -226,7 +226,7 @@ mod tests {
226226
#[test]
227227
#[allow(clippy::op_ref)]
228228
fn multiply() {
229-
let two = FeeRate::from_sat_per_vb(2).unwrap();
229+
let two = FeeRate::from_sat_per_vb(2);
230230
let three = Weight::from_vb(3).unwrap();
231231
let six = Amount::from_sat_u32(6);
232232

@@ -243,9 +243,9 @@ mod tests {
243243
fn amount_div_by_fee_rate() {
244244
// Test exact division
245245
let amount = Amount::from_sat_u32(1000);
246-
let fee_rate = FeeRate::from_sat_per_kwu(2).unwrap();
247-
let weight = (amount / fee_rate).unwrap();
248-
assert_eq!(weight, Weight::from_wu(500_000));
246+
let fee_rate = FeeRate::from_sat_per_kwu(2);
247+
let weight = amount / fee_rate;
248+
assert_eq!(weight.unwrap(), Weight::from_wu(500_000));
249249

250250
// Test reference division
251251
let weight_ref = (&amount / fee_rate).unwrap();
@@ -257,19 +257,19 @@ mod tests {
257257

258258
// Test truncation behavior
259259
let amount = Amount::from_sat_u32(1000);
260-
let fee_rate = FeeRate::from_sat_per_kwu(3).unwrap();
261-
let weight = (amount / fee_rate).unwrap();
260+
let fee_rate = FeeRate::from_sat_per_kwu(3);
261+
let weight = amount / fee_rate;
262262
// 1000 * 1000 = 1,000,000 msats
263263
// 1,000,000 / 3 = 333,333.33... wu
264264
// Should truncate down to 333,333 wu
265-
assert_eq!(weight, Weight::from_wu(333_333));
265+
assert_eq!(weight.unwrap(), Weight::from_wu(333_333));
266266

267267
// Verify that ceiling division gives different result
268268
let ceil_weight = amount.div_by_fee_rate_ceil(fee_rate).unwrap();
269269
assert_eq!(ceil_weight, Weight::from_wu(333_334));
270270

271271
// Test that division by zero returns None
272-
let zero_rate = FeeRate::from_sat_per_kwu(0).unwrap();
272+
let zero_rate = FeeRate::from_sat_per_kwu(0);
273273
assert!(amount.div_by_fee_rate_floor(zero_rate).is_error());
274274
assert!(amount.div_by_fee_rate_ceil(zero_rate).is_error());
275275
}

0 commit comments

Comments
 (0)