diff --git a/bitcoin/fuzz/fuzz_targets/deserialize_transaction.rs b/bitcoin/fuzz/fuzz_targets/deserialize_transaction.rs index 05489d82..14afa09c 100644 --- a/bitcoin/fuzz/fuzz_targets/deserialize_transaction.rs +++ b/bitcoin/fuzz/fuzz_targets/deserialize_transaction.rs @@ -8,7 +8,7 @@ fn do_test(data: &[u8]) { let ser = bitcoin::consensus::encode::serialize(&tx); assert_eq!(&ser[..], data); let len = ser.len(); - let calculated_weight = tx.weight(); + let calculated_weight = tx.weight().to_wu() as usize; for input in &mut tx.input { input.witness = bitcoin::blockdata::witness::Witness::default(); } diff --git a/bitcoin/src/blockdata/block.rs b/bitcoin/src/blockdata/block.rs index 120b6896..947598af 100644 --- a/bitcoin/src/blockdata/block.rs +++ b/bitcoin/src/blockdata/block.rs @@ -19,12 +19,12 @@ use crate::hashes::{Hash, HashEngine}; use crate::hash_types::{Wtxid, TxMerkleNode, WitnessMerkleNode, WitnessCommitment}; use crate::consensus::{encode, Encodable, Decodable}; use crate::blockdata::transaction::Transaction; -use crate::blockdata::constants::WITNESS_SCALE_FACTOR; use crate::blockdata::script; use crate::pow::{CompactTarget, Target, Work}; use crate::VarInt; use crate::internal_macros::impl_consensus_encoding; use crate::io; +use super::Weight; pub use crate::hash_types::BlockHash; @@ -302,9 +302,9 @@ impl Block { } /// Returns the weight of the block. - pub fn weight(&self) -> usize { - let base_weight = WITNESS_SCALE_FACTOR * self.base_size(); - let txs_weight: usize = self.txdata.iter().map(Transaction::weight).sum(); + pub fn weight(&self) -> Weight { + let base_weight = Weight::from_non_witness_data_size(self.base_size() as u64); + let txs_weight: Weight = self.txdata.iter().map(Transaction::weight).sum(); base_weight + txs_weight } @@ -470,7 +470,7 @@ mod tests { assert_eq!(real_decode.size(), some_block.len()); assert_eq!(real_decode.strippedsize(), some_block.len()); - assert_eq!(real_decode.weight(), some_block.len() * 4); + assert_eq!(real_decode.weight(), Weight::from_non_witness_data_size(some_block.len() as u64)); // should be also ok for a non-witness block as commitment is optional in that case assert!(real_decode.check_witness_commitment()); @@ -505,7 +505,7 @@ mod tests { assert_eq!(real_decode.size(), segwit_block.len()); assert_eq!(real_decode.strippedsize(), 4283); - assert_eq!(real_decode.weight(), 17168); + assert_eq!(real_decode.weight(), Weight::from_wu(17168)); assert!(real_decode.check_witness_commitment()); diff --git a/bitcoin/src/blockdata/fee_rate.rs b/bitcoin/src/blockdata/fee_rate.rs new file mode 100644 index 00000000..223b88e2 --- /dev/null +++ b/bitcoin/src/blockdata/fee_rate.rs @@ -0,0 +1,136 @@ +//! Implements `FeeRate` and assoctiated features. + +use core::fmt; +use core::ops::{Mul, Div}; +use crate::Amount; +use super::Weight; + +/// Represents fee rate. +/// +/// This is an integer newtype representing fee rate in `sat/kwu`. It provides protection against mixing +/// up the types as well as basic formatting features. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] +#[cfg_attr(feature = "serde", serde(transparent))] +pub struct FeeRate(u64); + +impl FeeRate { + /// 0 sat/kwu. + /// + /// Equivalent to [`MIN`](Self::MIN), may better express intent in some contexts. + pub const ZERO: FeeRate = FeeRate(0); + + /// Minimum possible value (0 sat/kwu). + /// + /// Equivalent to [`ZERO`](Self::ZERO), may better express intent in some contexts. + pub const MIN: FeeRate = FeeRate(u64::min_value()); + + /// Maximum possible value. + pub const MAX: FeeRate = FeeRate(u64::max_value()); + + /// Minimum fee rate required to broadcast a transaction. + /// + /// The value matches the default Bitcoin Core policy at the time of library release. + pub const BROADCAST_MIN: FeeRate = FeeRate::from_sat_per_vb_unchecked(1); + + /// Fee rate used to compute dust amount. + pub const DUST: FeeRate = FeeRate::from_sat_per_vb_unchecked(3); + + /// Constructs `FeeRate` from satoshis per 1000 weight units. + pub const fn from_sat_per_kwu(sat_kwu: u64) -> Self { + FeeRate(sat_kwu) + } + + /// Constructs `FeeRate` from satoshis per virtual bytes. + /// + /// # Errors + /// + /// Returns `None` on arithmetic overflow. + pub fn from_sat_per_vb(sat_vb: u64) -> Option { + // 1 vb == 4 wu + // 1 sat/vb == 1/4 sat/wu + // sat_vb sat/vb * 1000 / 4 == sat/kwu + Some(FeeRate(sat_vb.checked_mul(1000 / 4)?)) + } + + /// Constructs `FeeRate` from satoshis per virtual bytes without overflow check. + pub const fn from_sat_per_vb_unchecked(sat_vb: u64) -> Self { + FeeRate(sat_vb * (1000 / 4)) + } + + /// Returns raw fee rate. + /// + /// Can be used instead of `into()` to avoid inference issues. + pub const fn to_sat_per_kwu(self) -> u64 { + self.0 + } + + /// Converts to sat/vB rounding down. + pub const fn to_sat_per_vb_floor(self) -> u64 { + self.0 / (1000 / 4) + } + + /// Converts to sat/vB rounding up. + pub const fn to_sat_per_vb_ceil(self) -> u64 { + (self.0 + (1000 / 4 - 1)) / (1000 / 4) + } + + /// Checked multiplication. + /// + /// Computes `self * rhs` returning `None` if overflow occurred. + pub fn checked_mul(self, rhs: u64) -> Option { + self.0.checked_mul(rhs).map(Self) + } + + /// Checked division. + /// + /// Computes `self / rhs` returning `None` if `rhs == 0`. + pub fn checked_div(self, rhs: u64) -> Option { + self.0.checked_div(rhs).map(Self) + } +} + +/// Alternative will display the unit. +impl fmt::Display for FeeRate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if f.alternate() { + write!(f, "{} sat/kwu", self.0) + } else { + fmt::Display::fmt(&self.0, f) + } + } +} + +impl From for u64 { + fn from(value: FeeRate) -> Self { + value.to_sat_per_kwu() + } +} + +/// Computes ceiling so that fee computation is conservative. +impl Mul for Weight { + type Output = Amount; + + fn mul(self, rhs: FeeRate) -> Self::Output { + Amount::from_sat((rhs.to_sat_per_kwu() * self.to_wu() + 999) / 1000) + } +} + +impl Mul for FeeRate { + type Output = Amount; + + fn mul(self, rhs: Weight) -> Self::Output { + rhs * self + } +} + +impl Div for Amount { + type Output = FeeRate; + + fn div(self, rhs: Weight) -> Self::Output { + FeeRate(self.to_sat() * 1000 / rhs.to_wu()) + } +} + +crate::parse::impl_parse_str_through_int!(FeeRate); diff --git a/bitcoin/src/blockdata/mod.rs b/bitcoin/src/blockdata/mod.rs index 2edb7883..ade1bf34 100644 --- a/bitcoin/src/blockdata/mod.rs +++ b/bitcoin/src/blockdata/mod.rs @@ -14,3 +14,8 @@ pub mod script; pub mod transaction; pub mod block; pub mod witness; +pub mod weight; +pub mod fee_rate; + +pub use weight::Weight; +pub use fee_rate::FeeRate; diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index 620c41d3..cc385066 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -34,6 +34,7 @@ use crate::hash_types::{Sighash, Txid, Wtxid}; use crate::VarInt; use crate::internal_macros::impl_consensus_encoding; use crate::parse::impl_parse_str_through_int; +use super::Weight; #[cfg(doc)] use crate::sighash::{EcdsaSighashType, SchnorrSighashType}; @@ -839,8 +840,8 @@ impl Transaction { /// API. The unsigned transaction encoded within PSBT is always a non-segwit transaction /// and can therefore avoid this ambiguity. #[inline] - pub fn weight(&self) -> usize { - self.scaled_size(WITNESS_SCALE_FACTOR) + pub fn weight(&self) -> Weight { + Weight::from_wu(self.scaled_size(WITNESS_SCALE_FACTOR) as u64) } /// Returns the regular byte-wise consensus-serialized size of this transaction. @@ -860,8 +861,8 @@ impl Transaction { /// [`policy`]: ../policy/mod.rs.html #[inline] pub fn vsize(&self) -> usize { - let weight = self.weight(); - (weight + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR + // No overflow because it's computed from data in memory + self.weight().to_vbytes_ceil() as usize } /// Returns the size of this transaction excluding the witness data. @@ -1259,7 +1260,7 @@ mod tests { "a6eab3c14ab5272a58a5ba91505ba1a4b6d7a3a9fcbd187b6cd99a7b6d548cb7".to_string()); assert_eq!(format!("{:x}", realtx.wtxid()), "a6eab3c14ab5272a58a5ba91505ba1a4b6d7a3a9fcbd187b6cd99a7b6d548cb7".to_string()); - assert_eq!(realtx.weight(), tx_bytes.len()*WITNESS_SCALE_FACTOR); + assert_eq!(realtx.weight().to_wu() as usize, tx_bytes.len()*WITNESS_SCALE_FACTOR); assert_eq!(realtx.size(), tx_bytes.len()); assert_eq!(realtx.vsize(), tx_bytes.len()); assert_eq!(realtx.strippedsize(), tx_bytes.len()); @@ -1293,7 +1294,7 @@ mod tests { "f5864806e3565c34d1b41e716f72609d00b55ea5eac5b924c9719a842ef42206".to_string()); assert_eq!(format!("{:x}", realtx.wtxid()), "80b7d8a82d5d5bf92905b06f2014dd699e03837ca172e3a59d51426ebbe3e7f5".to_string()); - const EXPECTED_WEIGHT: usize = 442; + const EXPECTED_WEIGHT: Weight = Weight::from_wu(442); assert_eq!(realtx.weight(), EXPECTED_WEIGHT); assert_eq!(realtx.size(), tx_bytes.len()); assert_eq!(realtx.vsize(), 111); @@ -1302,12 +1303,12 @@ mod tests { // weight = WITNESS_SCALE_FACTOR * stripped_size + witness_size // then, // stripped_size = (weight - size) / (WITNESS_SCALE_FACTOR - 1) - let expected_strippedsize = (EXPECTED_WEIGHT - tx_bytes.len()) / (WITNESS_SCALE_FACTOR - 1); + let expected_strippedsize = (EXPECTED_WEIGHT.to_wu() as usize - tx_bytes.len()) / (WITNESS_SCALE_FACTOR - 1); assert_eq!(realtx.strippedsize(), expected_strippedsize); // Construct a transaction without the witness data. let mut tx_without_witness = realtx; tx_without_witness.input.iter_mut().for_each(|input| input.witness.clear()); - assert_eq!(tx_without_witness.weight(), expected_strippedsize*WITNESS_SCALE_FACTOR); + assert_eq!(tx_without_witness.weight().to_wu() as usize, expected_strippedsize*WITNESS_SCALE_FACTOR); assert_eq!(tx_without_witness.size(), expected_strippedsize); assert_eq!(tx_without_witness.vsize(), expected_strippedsize); assert_eq!(tx_without_witness.strippedsize(), expected_strippedsize); @@ -1412,7 +1413,7 @@ mod tests { assert_eq!(format!("{:x}", tx.wtxid()), "d6ac4a5e61657c4c604dcde855a1db74ec6b3e54f32695d72c5e11c7761ea1b4"); assert_eq!(format!("{:x}", tx.txid()), "9652aa62b0e748caeec40c4cb7bc17c6792435cc3dfe447dd1ca24f912a1c6ec"); - assert_eq!(tx.weight(), 2718); + assert_eq!(tx.weight(), Weight::from_wu(2718)); // non-segwit tx from my mempool let tx_bytes = hex!( @@ -1444,7 +1445,7 @@ mod tests { fn test_segwit_tx_decode() { let tx_bytes = hex!("010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff3603da1b0e00045503bd5704c7dd8a0d0ced13bb5785010800000000000a636b706f6f6c122f4e696e6a61506f6f6c2f5345475749542fffffffff02b4e5a212000000001976a914876fbb82ec05caa6af7a3b5e5a983aae6c6cc6d688ac0000000000000000266a24aa21a9edf91c46b49eb8a29089980f02ee6b57e7d63d33b18b4fddac2bcd7db2a39837040120000000000000000000000000000000000000000000000000000000000000000000000000"); let tx: Transaction = deserialize(&tx_bytes).unwrap(); - assert_eq!(tx.weight(), 780); + assert_eq!(tx.weight(), Weight::from_wu(780)); serde_round_trip!(tx); let consensus_encoded = serialize(&tx); @@ -1603,7 +1604,7 @@ mod tests { (false, "0100000001c336895d9fa674f8b1e294fd006b1ac8266939161600e04788c515089991b50a030000006a47304402204213769e823984b31dcb7104f2c99279e74249eacd4246dabcf2575f85b365aa02200c3ee89c84344ae326b637101a92448664a8d39a009c8ad5d147c752cbe112970121028b1b44b4903c9103c07d5a23e3c7cf7aeb0ba45ddbd2cfdce469ab197381f195fdffffff040000000000000000536a4c5058325bb7b7251cf9e36cac35d691bd37431eeea426d42cbdecca4db20794f9a4030e6cb5211fabf887642bcad98c9994430facb712da8ae5e12c9ae5ff314127d33665000bb26c0067000bb0bf00322a50c300000000000017a9145ca04fdc0a6d2f4e3f67cfeb97e438bb6287725f8750c30000000000001976a91423086a767de0143523e818d4273ddfe6d9e4bbcc88acc8465003000000001976a914c95cbacc416f757c65c942f9b6b8a20038b9b12988ac00000000"), ]; - let empty_transaction_size = Transaction { + let empty_transaction_weight = Transaction { version: 0, lock_time: absolute::LockTime::ZERO, input: vec![], @@ -1620,12 +1621,12 @@ mod tests { let tx: Transaction = deserialize(Vec::from_hex(tx).unwrap().as_slice()).unwrap(); // The empty tx size doesn't include the segwit marker (`0001`), so, in case of segwit txs, // we have to manually add it ourselves - let segwit_marker_size = if *is_segwit { 2 } else { 0 }; - let calculated_size = empty_transaction_size - + segwit_marker_size + let segwit_marker_weight = if *is_segwit { 2 } else { 0 }; + let calculated_size = empty_transaction_weight.to_wu() as usize + + segwit_marker_weight + tx.input.iter().fold(0, |sum, i| sum + txin_weight(i)) + tx.output.iter().fold(0, |sum, o| sum + o.weight()); - assert_eq!(calculated_size, tx.weight()); + assert_eq!(calculated_size, tx.weight().to_wu() as usize); } } } diff --git a/bitcoin/src/blockdata/weight.rs b/bitcoin/src/blockdata/weight.rs new file mode 100644 index 00000000..ac529090 --- /dev/null +++ b/bitcoin/src/blockdata/weight.rs @@ -0,0 +1,198 @@ +//! Implements `Weight` and associated features. + +use core::fmt; +use core::ops::{Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign}; + +/// Represents block weight - the weight of a transaction or block. +/// +/// This is an integer newtype representing weigth in `wu`. It provides protection against mixing +/// up the types as well as basic formatting features. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] +#[cfg_attr(feature = "serde", serde(transparent))] +pub struct Weight(u64); + +impl Weight { + /// 0 wu. + /// + /// Equivalent to [`MIN`](Self::MIN), may better express intent in some contexts. + pub const ZERO: Weight = Weight(0); + + /// Minimum possible value (0 wu). + /// + /// Equivalent to [`ZERO`](Self::ZERO), may better express intent in some contexts. + pub const MIN: Weight = Weight(u64::min_value()); + + /// Maximum possible value. + pub const MAX: Weight = Weight(u64::max_value()); + + /// Directly constructs `Weight` from weight units. + pub const fn from_wu(wu: u64) -> Self { + Weight(wu) + } + + /// Constructs `Weight` from virtual bytes. + /// + /// # Errors + /// + /// Returns `None` on overflow. + pub fn from_vb(vb: u64) -> Option { + vb.checked_mul(4).map(Weight::from_wu) + } + + /// Constructs `Weight` from virtual bytes without overflow check. + pub const fn from_vb_unchecked(vb: u64) -> Self { + Weight::from_wu(vb * 4) + } + + /// Constructs `Weight` from witness size. + pub const fn from_witness_data_size(witness_size: u64) -> Self { + Weight(witness_size) + } + + /// Constructs `Weight` from non-witness size. + pub const fn from_non_witness_data_size(non_witness_size: u64) -> Self { + Weight(non_witness_size * 4) + } + + /// Returns raw weight units. + /// + /// Can be used instead of `into()` to avoid inference issues. + pub const fn to_wu(self) -> u64 { + self.0 + } + + /// Converts to vB rounding down. + pub const fn to_vbytes_floor(self) -> u64 { + self.0 / 4 + } + + /// Converts to vB rounding up. + pub const fn to_vbytes_ceil(self) -> u64 { + (self.0 + 3) / 4 + } + + /// Checked addition. + /// + /// Computes `self + rhs` returning `None` if overflow occurred. + pub fn checked_add(self, rhs: Self) -> Option { + self.0.checked_add(rhs.0).map(Self) + } + + /// Checked subtraction. + /// + /// Computes `self - rhs` returning `None` if overflow occurred. + pub fn checked_sub(self, rhs: Self) -> Option { + self.0.checked_add(rhs.0).map(Self) + } + + /// Checked multiplication. + /// + /// Computes `self * rhs` returning `None` if overflow occurred. + pub fn checked_mul(self, rhs: u64) -> Option { + self.0.checked_mul(rhs).map(Self) + } + + /// Checked division. + /// + /// Computes `self / rhs` returning `None` if `rhs == 0`. + pub fn checked_div(self, rhs: u64) -> Option { + self.0.checked_div(rhs).map(Self) + } +} + +/// Alternative will display the unit. +impl fmt::Display for Weight { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if f.alternate() { + write!(f, "{} wu", self.0) + } else { + fmt::Display::fmt(&self.0, f) + } + } +} + +impl From for u64 { + fn from(value: Weight) -> Self { + value.to_wu() + } +} + +impl Add for Weight { + type Output = Weight; + + fn add(self, rhs: Weight) -> Self::Output { + Weight(self.0 + rhs.0) + } +} + +impl AddAssign for Weight { + fn add_assign(&mut self, rhs: Self) { + self.0 += rhs.0 + } +} + +impl Sub for Weight { + type Output = Weight; + + fn sub(self, rhs: Weight) -> Self::Output { + Weight(self.0 - rhs.0) + } +} + +impl SubAssign for Weight { + fn sub_assign(&mut self, rhs: Self) { + self.0 -= rhs.0 + } +} + +impl Mul for Weight { + type Output = Weight; + + fn mul(self, rhs: u64) -> Self::Output { + Weight(self.0 * rhs) + } +} + +impl Mul for u64 { + type Output = Weight; + + fn mul(self, rhs: Weight) -> Self::Output { + Weight(self * rhs.0) + } +} + +impl MulAssign for Weight { + fn mul_assign(&mut self, rhs: u64) { + self.0 *= rhs + } +} + +impl Div for Weight { + type Output = Weight; + + fn div(self, rhs: u64) -> Self::Output { + Weight(self.0 / rhs) + } +} + +impl DivAssign for Weight { + fn div_assign(&mut self, rhs: u64) { + self.0 /= rhs + } +} + +impl core::iter::Sum for Weight { + fn sum(iter: I) -> Self where I: Iterator { + Weight(iter.map(Weight::to_wu).sum()) + } +} + +impl<'a> core::iter::Sum<&'a Weight> for Weight { + fn sum(iter: I) -> Self where I: Iterator { + iter.cloned().sum() + } +} + +crate::parse::impl_parse_str_through_int!(Weight); diff --git a/bitcoin/src/parse.rs b/bitcoin/src/parse.rs index 67f3dfd2..07ddae82 100644 --- a/bitcoin/src/parse.rs +++ b/bitcoin/src/parse.rs @@ -116,7 +116,7 @@ pub(crate) use impl_tryfrom_str_through_int_single; /// The `Error` type is `ParseIntError` macro_rules! impl_parse_str_through_int { ($to:ident $(, $fn:ident)?) => { - $crate::parse::impl_tryfrom_str_through_int_single!(&str, $to $(, $fn)?; String, $to $(, $fn)?; Box, $to $(, $fn)?); + $crate::parse::impl_tryfrom_str_through_int_single!(&str, $to $(, $fn)?; alloc::string::String, $to $(, $fn)?; alloc::boxed::Box, $to $(, $fn)?); impl core::str::FromStr for $to { type Err = $crate::error::ParseIntError;