From d69d62822d448c7ef33df85f441bbb8c5faec7b7 Mon Sep 17 00:00:00 2001 From: yancy Date: Tue, 28 Nov 2023 21:43:15 +0100 Subject: [PATCH] Add effective_value method The effective_value method is useful for coin selection algorithms. By providing this effective value method, the effective value of each output can be known during the coin selection process. --- bitcoin/src/blockdata/transaction.rs | 64 +++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index 2e15f30a..414cab22 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -19,11 +19,11 @@ use internals::write_err; use io::{Read, Write}; use super::Weight; -use crate::blockdata::fee_rate::FeeRate; use crate::blockdata::locktime::absolute::{self, Height, Time}; use crate::blockdata::locktime::relative; use crate::blockdata::script::{Script, ScriptBuf}; use crate::blockdata::witness::Witness; +use crate::blockdata::FeeRate; use crate::consensus::{encode, Decodable, Encodable}; use crate::internal_macros::{impl_consensus_encoding, impl_hashencode}; use crate::parse::impl_parse_str_from_int_infallible; @@ -31,7 +31,7 @@ use crate::prelude::*; #[cfg(doc)] use crate::sighash::{EcdsaSighashType, TapSighashType}; use crate::string::FromHexStr; -use crate::{Amount, VarInt}; +use crate::{Amount, SignedAmount, VarInt}; #[rustfmt::skip] // Keep public re-exports separate. #[cfg(feature = "bitcoinconsensus")] @@ -226,6 +226,12 @@ pub struct TxIn { } impl TxIn { + /// Returns the input base weight. + /// + /// Base weight excludes the witness and script. + const BASE_WEIGHT: Weight = + Weight::from_vb_unwrap(OutPoint::SIZE as u64 + Sequence::SIZE as u64); + /// Returns true if this input enables the [`absolute::LockTime`] (aka `nLockTime`) of its /// [`Transaction`]. /// @@ -1168,6 +1174,29 @@ impl From<&Transaction> for Wtxid { fn from(tx: &Transaction) -> Wtxid { tx.wtxid() } } +/// Computes the value of an output accounting for the cost of spending it. +/// +/// The effective value is the value of an output value minus the amount to spend it. That is, the +/// effective_value can be calculated as: value - (fee_rate * weight). +/// +/// Note: the effective value of a [`Transaction`] may increase less than the effective value of +/// a [`TxOut`] when adding another [`TxOut`] to the transaction. This happens when the new +/// [`TxOut`] added causes the output length `VarInt` to increase its encoding length. +/// +/// # Arguments +/// +/// * `fee_rate` - the fee rate of the transaction being created. +/// * `satisfaction_weight` - satisfied spending conditions weight. +pub fn effective_value( + fee_rate: FeeRate, + satisfaction_weight: Weight, + value: Amount, +) -> Option { + let weight = satisfaction_weight.checked_add(TxIn::BASE_WEIGHT)?; + let signed_input_fee = fee_rate.checked_mul_by_weight(weight)?.to_signed().ok()?; + value.to_signed().ok()?.checked_sub(signed_input_fee) +} + /// Predicts the weight of a to-be-constructed transaction. /// /// This function computes the weight of a transaction which is not fully known. All that is needed @@ -1989,6 +2018,37 @@ mod tests { assert!(result.is_err()); } + #[test] + fn effective_value_happy_path() { + let value = Amount::from_str("1 cBTC").unwrap(); + let fee_rate = FeeRate::from_sat_per_kwu(10); + let satisfaction_weight = Weight::from_wu(204); + let effective_value = effective_value(fee_rate, satisfaction_weight, value).unwrap(); + + // 10 sat/kwu * (204wu + BASE_WEIGHT) = 4 sats + let expected_fee = SignedAmount::from_str("4 sats").unwrap(); + let expected_effective_value = value.to_signed().unwrap() - expected_fee; + assert_eq!(effective_value, expected_effective_value); + } + + #[test] + fn effective_value_fee_rate_does_not_overflow() { + let eff_value = effective_value(FeeRate::MAX, Weight::ZERO, Amount::ZERO); + assert!(eff_value.is_none()); + } + + #[test] + fn effective_value_weight_does_not_overflow() { + let eff_value = effective_value(FeeRate::ZERO, Weight::MAX, Amount::ZERO); + assert!(eff_value.is_none()); + } + + #[test] + fn effective_value_value_does_not_overflow() { + let eff_value = effective_value(FeeRate::ZERO, Weight::ZERO, Amount::MAX); + assert!(eff_value.is_none()); + } + #[test] fn txin_txout_weight() { // [(is_segwit, tx_hex, expected_weight)]