Skip to content

Commit 1fee075

Browse files
committed
Merge rust-bitcoin#4616: units: Improve the fee calculation functions
0ff8d82 Make FeeRate from sat constructors infallible (Tobin C. Harding) 3b0286b Return NumOpResult for FeeRate and Weight (Tobin C. Harding) 15065b7 Return NumOpResult when calculating fee on Amount (Tobin C. Harding) 75106e6 Remove checked_ prefix from fee functions (Tobin C. Harding) Pull request description: This was rust-bitcoin@14dc950 from rust-bitcoin#4610. Remove the `checked_` prefix from `fee` functions then make them all return `NumOpResult`. ACKs for top commit: apoelstra: ACK 0ff8d82; successfully ran local tests; nice! Tree-SHA512: 62f2af6701f2a0e17b9f0a0ee132ca4a9289fe00e22c94f746602ed3c1f3758bb22323b224362cb659432ff5297391f3106ee460e9a8f65a39f904d72bc98aeb
2 parents 9852732 + 0ff8d82 commit 1fee075

File tree

12 files changed

+226
-255
lines changed

12 files changed

+226
-255
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
@@ -1685,7 +1685,7 @@ mod tests {
16851685
#[test]
16861686
fn effective_value_happy_path() {
16871687
let value = "1 cBTC".parse::<Amount>().unwrap();
1688-
let fee_rate = FeeRate::from_sat_per_kwu(10).unwrap();
1688+
let fee_rate = FeeRate::from_sat_per_kwu(10);
16891689
let effective_value = effective_value(fee_rate, InputWeightPrediction::P2WPKH_MAX, value);
16901690

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

bitcoin/src/psbt/mod.rs

Lines changed: 27 additions & 26 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
///
@@ -1348,17 +1348,6 @@ mod tests {
13481348
use crate::witness::Witness;
13491349
use crate::Sequence;
13501350

1351-
/// Fee rate in sat/kwu for a high-fee PSBT with an input=5_000_000_000_000, output=1000
1352-
const ABSURD_FEE_RATE: FeeRate = match FeeRate::from_sat_per_kwu(15_060_240_960_843) {
1353-
Some(fee_rate) => fee_rate,
1354-
None => panic!("unreachable - no unwrap in Rust 1.63 in const"),
1355-
};
1356-
const JUST_BELOW_ABSURD_FEE_RATE: FeeRate = match FeeRate::from_sat_per_kwu(15_060_240_960_842)
1357-
{
1358-
Some(fee_rate) => fee_rate,
1359-
None => panic!("unreachable - no unwrap in Rust 1.63 in const"),
1360-
};
1361-
13621351
#[track_caller]
13631352
pub fn hex_psbt(s: &str) -> Result<Psbt, crate::psbt::error::Error> {
13641353
let r = Vec::from_hex(s);
@@ -1442,39 +1431,51 @@ mod tests {
14421431

14431432
#[test]
14441433
fn psbt_high_fee_checks() {
1445-
let psbt = psbt_with_values(5_000_000_000_000, 1000);
1434+
let psbt = psbt_with_values(Amount::MAX.to_sat(), 1000);
1435+
1436+
// We cannot create an expected fee rate to test against because `FeeRate::from_sat_per_mvb` is private.
1437+
// Large fee rate errors if we pass in 1 sat/vb so just use this to get the error fee rate returned.
1438+
let error_fee_rate = psbt
1439+
.clone()
1440+
.extract_tx_with_fee_rate_limit(FeeRate::from_sat_per_vb(1))
1441+
.map_err(|e| match e {
1442+
ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate,
1443+
_ => panic!(""),
1444+
})
1445+
.unwrap_err();
1446+
1447+
// In `internal_extract_tx_with_fee_rate_limit` when we do fee / weight
1448+
// we manually saturate to `FeeRate::MAX`.
1449+
assert!(psbt.clone().extract_tx_with_fee_rate_limit(FeeRate::MAX).is_ok());
1450+
1451+
// These error because the fee rate is above the limit as expected.
14461452
assert_eq!(
14471453
psbt.clone().extract_tx().map_err(|e| match e {
14481454
ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate,
14491455
_ => panic!(""),
14501456
}),
1451-
Err(ABSURD_FEE_RATE)
1457+
Err(error_fee_rate)
14521458
);
14531459
assert_eq!(
14541460
psbt.clone().extract_tx_fee_rate_limit().map_err(|e| match e {
14551461
ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate,
14561462
_ => panic!(""),
14571463
}),
1458-
Err(ABSURD_FEE_RATE)
1459-
);
1460-
assert_eq!(
1461-
psbt.clone().extract_tx_with_fee_rate_limit(JUST_BELOW_ABSURD_FEE_RATE).map_err(|e| {
1462-
match e {
1463-
ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate,
1464-
_ => panic!(""),
1465-
}
1466-
}),
1467-
Err(ABSURD_FEE_RATE)
1464+
Err(error_fee_rate)
14681465
);
1469-
assert!(psbt.extract_tx_with_fee_rate_limit(ABSURD_FEE_RATE).is_ok());
1466+
1467+
// No one is using an ~50 BTC fee so if we can handle this
1468+
// then the `FeeRate` restrictions are fine for PSBT usage.
1469+
let psbt = psbt_with_values(Amount::from_btc_u16(50).to_sat(), 1000); // fee = 50 BTC - 1000 sats
1470+
assert!(psbt.extract_tx_with_fee_rate_limit(FeeRate::MAX).is_ok());
14701471

14711472
// Testing that extract_tx will error at 25k sat/vbyte (6250000 sat/kwu)
14721473
assert_eq!(
14731474
psbt_with_values(2076001, 1000).extract_tx().map_err(|e| match e {
14741475
ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate,
14751476
_ => panic!(""),
14761477
}),
1477-
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
14781479
);
14791480

14801481
// 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: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -268,70 +268,70 @@ fn positive_sub() {
268268
#[test]
269269
fn amount_checked_div_by_weight_ceil() {
270270
let weight = Weight::from_kwu(1).unwrap();
271-
let fee_rate = sat(1).checked_div_by_weight_ceil(weight).unwrap();
271+
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);
276-
let fee_rate = sat(329).checked_div_by_weight_ceil(weight).unwrap();
276+
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

281-
let fee_rate = Amount::ONE_SAT.checked_div_by_weight_ceil(Weight::ZERO);
282-
assert!(fee_rate.is_none());
281+
let fee_rate = Amount::ONE_SAT.div_by_weight_ceil(Weight::ZERO);
282+
assert!(fee_rate.is_error());
283283
}
284284

285285
#[cfg(feature = "alloc")]
286286
#[test]
287287
fn amount_checked_div_by_weight_floor() {
288288
let weight = Weight::from_kwu(1).unwrap();
289-
let fee_rate = sat(1).checked_div_by_weight_floor(weight).unwrap();
289+
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);
294-
let fee_rate = sat(329).checked_div_by_weight_floor(weight).unwrap();
294+
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

299-
let fee_rate = Amount::ONE_SAT.checked_div_by_weight_floor(Weight::ZERO);
300-
assert!(fee_rate.is_none());
299+
let fee_rate = Amount::ONE_SAT.div_by_weight_floor(Weight::ZERO);
300+
assert!(fee_rate.is_error());
301301
}
302302

303303
#[cfg(feature = "alloc")]
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
310-
let weight = amount.checked_div_by_fee_rate_floor(fee_rate).unwrap();
310+
let weight = amount.div_by_fee_rate_floor(fee_rate).unwrap();
311311
// 1000 sats / (2 sats/kwu) = 500,000 wu
312312
assert_eq!(weight, Weight::from_wu(500_000));
313313

314314
// Test ceiling division
315-
let weight = amount.checked_div_by_fee_rate_ceil(fee_rate).unwrap();
315+
let weight = amount.div_by_fee_rate_ceil(fee_rate).unwrap();
316316
assert_eq!(weight, Weight::from_wu(500_000)); // Same result for exact division
317317

318318
// Test truncation behavior
319319
let amount = sat(1000);
320-
let fee_rate = FeeRate::from_sat_per_kwu(3).unwrap();
321-
let floor_weight = amount.checked_div_by_fee_rate_floor(fee_rate).unwrap();
322-
let ceil_weight = amount.checked_div_by_fee_rate_ceil(fee_rate).unwrap();
320+
let fee_rate = FeeRate::from_sat_per_kwu(3);
321+
let floor_weight = amount.div_by_fee_rate_floor(fee_rate).unwrap();
322+
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();
328-
assert!(amount.checked_div_by_fee_rate_floor(zero_fee_rate).is_none());
329-
assert!(amount.checked_div_by_fee_rate_ceil(zero_fee_rate).is_none());
327+
let zero_fee_rate = FeeRate::from_sat_per_kwu(0);
328+
assert!(amount.div_by_fee_rate_floor(zero_fee_rate).is_error());
329+
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();
334-
let weight = max_amount.checked_div_by_fee_rate_floor(small_fee_rate).unwrap();
333+
let small_fee_rate = FeeRate::from_sat_per_kwu(1);
334+
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));
337337
}

0 commit comments

Comments
 (0)