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.
This commit is contained in:
yancy 2023-11-28 21:43:15 +01:00
parent de9f20a620
commit d69d62822d
1 changed files with 62 additions and 2 deletions

View File

@ -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<SignedAmount> {
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)]