Merge rust-bitcoin/rust-bitcoin#1627: Add `Weight` and `FeeRate` newtypes

70cf4515db Add `Weight` and `FeeRate` newtypes (Martin Habovstiak)

Pull request description:

  Use of general-purpose integers is often error-prone and annoying. We're working towards improving it by introducing newtypes.

  This adds newtypes for weight and fee rate to make fee computation easier and more readable. Note however that this dosn't change the type for individual parts of the transaction since computing the total weight is not as simple as summing them up and we want to avoid such confusion.

  Part of #630
  Replaces #1607 (I want to get this in quickly and don't want to be blocked on DanGould's availability.)

ACKs for top commit:
  apoelstra:
    ACK 70cf4515db
  tcharding:
    ACK 70cf4515db

Tree-SHA512: ab9cc9f554a52ab0109ff23565b3e2cb2d3f609b557457b4afd8763e3e1b418aecbb3d22733e33304e858ecf900904a1af6e6fdc16dc21483b9ef84f56f103b2
This commit is contained in:
Andrew Poelstra 2023-02-09 23:59:47 +00:00
commit b6387db47f
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
7 changed files with 363 additions and 23 deletions

View File

@ -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();
}

View File

@ -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());

View File

@ -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<Self> {
// 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> {
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> {
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<FeeRate> for u64 {
fn from(value: FeeRate) -> Self {
value.to_sat_per_kwu()
}
}
/// Computes ceiling so that fee computation is conservative.
impl Mul<FeeRate> 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<Weight> for FeeRate {
type Output = Amount;
fn mul(self, rhs: Weight) -> Self::Output {
rhs * self
}
}
impl Div<Weight> 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);

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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<Self> {
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> {
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> {
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> {
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> {
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<Weight> 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<u64> for Weight {
type Output = Weight;
fn mul(self, rhs: u64) -> Self::Output {
Weight(self.0 * rhs)
}
}
impl Mul<Weight> for u64 {
type Output = Weight;
fn mul(self, rhs: Weight) -> Self::Output {
Weight(self * rhs.0)
}
}
impl MulAssign<u64> for Weight {
fn mul_assign(&mut self, rhs: u64) {
self.0 *= rhs
}
}
impl Div<u64> for Weight {
type Output = Weight;
fn div(self, rhs: u64) -> Self::Output {
Weight(self.0 / rhs)
}
}
impl DivAssign<u64> for Weight {
fn div_assign(&mut self, rhs: u64) {
self.0 /= rhs
}
}
impl core::iter::Sum for Weight {
fn sum<I>(iter: I) -> Self where I: Iterator<Item = Self> {
Weight(iter.map(Weight::to_wu).sum())
}
}
impl<'a> core::iter::Sum<&'a Weight> for Weight {
fn sum<I>(iter: I) -> Self where I: Iterator<Item = &'a Weight> {
iter.cloned().sum()
}
}
crate::parse::impl_parse_str_through_int!(Weight);

View File

@ -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<str>, $to $(, $fn)?);
$crate::parse::impl_tryfrom_str_through_int_single!(&str, $to $(, $fn)?; alloc::string::String, $to $(, $fn)?; alloc::boxed::Box<str>, $to $(, $fn)?);
impl core::str::FromStr for $to {
type Err = $crate::error::ParseIntError;