bdk 0.29.0

A modern, lightweight, descriptor-based wallet library
Documentation
// Bitcoin Dev Kit
// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
//
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
//
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// You may not use this file except in accordance with one or both of these
// licenses.

use std::convert::AsRef;
use std::ops::Sub;

use bitcoin::blockdata::transaction::{OutPoint, Transaction, TxOut};
use bitcoin::{hash_types::Txid, psbt, Weight};

use serde::{Deserialize, Serialize};

/// Types of keychains
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KeychainKind {
    /// External
    External = 0,
    /// Internal, usually used for change outputs
    Internal = 1,
}

impl KeychainKind {
    /// Return [`KeychainKind`] as a byte
    pub fn as_byte(&self) -> u8 {
        match self {
            KeychainKind::External => b'e',
            KeychainKind::Internal => b'i',
        }
    }
}

impl AsRef<[u8]> for KeychainKind {
    fn as_ref(&self) -> &[u8] {
        match self {
            KeychainKind::External => b"e",
            KeychainKind::Internal => b"i",
        }
    }
}

/// Fee rate
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
// Internally stored as satoshi/vbyte
pub struct FeeRate(f32);

impl FeeRate {
    /// Create a new instance checking the value provided
    ///
    /// ## Panics
    ///
    /// Panics if the value is not [normal](https://doc.rust-lang.org/std/primitive.f32.html#method.is_normal) (except if it's a positive zero) or negative.
    fn new_checked(value: f32) -> Self {
        assert!(value.is_normal() || value == 0.0);
        assert!(value.is_sign_positive());

        FeeRate(value)
    }

    /// Create a new instance of [`FeeRate`] given a float fee rate in sats/kwu
    pub fn from_sat_per_kwu(sat_per_kwu: f32) -> Self {
        FeeRate::new_checked(sat_per_kwu / 250.0_f32)
    }

    /// Create a new instance of [`FeeRate`] given a float fee rate in sats/kvb
    pub fn from_sat_per_kvb(sat_per_kvb: f32) -> Self {
        FeeRate::new_checked(sat_per_kvb / 1000.0_f32)
    }

    /// Create a new instance of [`FeeRate`] given a float fee rate in btc/kvbytes
    ///
    /// ## Panics
    ///
    /// Panics if the value is not [normal](https://doc.rust-lang.org/std/primitive.f32.html#method.is_normal) (except if it's a positive zero) or negative.
    pub fn from_btc_per_kvb(btc_per_kvb: f32) -> Self {
        FeeRate::new_checked(btc_per_kvb * 1e5)
    }

    /// Create a new instance of [`FeeRate`] given a float fee rate in satoshi/vbyte
    ///
    /// ## Panics
    ///
    /// Panics if the value is not [normal](https://doc.rust-lang.org/std/primitive.f32.html#method.is_normal) (except if it's a positive zero) or negative.
    pub fn from_sat_per_vb(sat_per_vb: f32) -> Self {
        FeeRate::new_checked(sat_per_vb)
    }

    /// Create a new [`FeeRate`] with the default min relay fee value
    pub const fn default_min_relay_fee() -> Self {
        FeeRate(1.0)
    }

    /// Calculate fee rate from `fee` and weight units (`wu`).
    pub fn from_wu(fee: u64, wu: Weight) -> FeeRate {
        Self::from_vb(fee, wu.to_vbytes_ceil() as usize)
    }

    /// Calculate fee rate from `fee` and `vbytes`.
    pub fn from_vb(fee: u64, vbytes: usize) -> FeeRate {
        let rate = fee as f32 / vbytes as f32;
        Self::from_sat_per_vb(rate)
    }

    /// Return the value as satoshi/vbyte
    pub fn as_sat_per_vb(&self) -> f32 {
        self.0
    }

    /// Calculate absolute fee in Satoshis using size in weight units.
    pub fn fee_wu(&self, wu: Weight) -> u64 {
        self.fee_vb(wu.to_vbytes_ceil() as usize)
    }

    /// Calculate absolute fee in Satoshis using size in virtual bytes.
    pub fn fee_vb(&self, vbytes: usize) -> u64 {
        (self.as_sat_per_vb() * vbytes as f32).ceil() as u64
    }
}

impl std::default::Default for FeeRate {
    fn default() -> Self {
        FeeRate::default_min_relay_fee()
    }
}

impl Sub for FeeRate {
    type Output = Self;

    fn sub(self, other: FeeRate) -> Self::Output {
        FeeRate(self.0 - other.0)
    }
}

/// Trait implemented by types that can be used to measure weight units.
pub trait Vbytes {
    /// Convert weight units to virtual bytes.
    fn vbytes(self) -> usize;
}

impl Vbytes for usize {
    fn vbytes(self) -> usize {
        // ref: https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#transaction-size-calculations
        (self as f32 / 4.0).ceil() as usize
    }
}

/// An unspent output owned by a [`Wallet`].
///
/// [`Wallet`]: crate::Wallet
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
pub struct LocalUtxo {
    /// Reference to a transaction output
    pub outpoint: OutPoint,
    /// Transaction output
    pub txout: TxOut,
    /// Type of keychain
    pub keychain: KeychainKind,
    /// Whether this UTXO is spent or not
    pub is_spent: bool,
}

/// A [`Utxo`] with its `satisfaction_weight`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WeightedUtxo {
    /// The weight of the witness data and `scriptSig` expressed in [weight units]. This is used to
    /// properly maintain the feerate when adding this input to a transaction during coin selection.
    ///
    /// [weight units]: https://en.bitcoin.it/wiki/Weight_units
    pub satisfaction_weight: usize,
    /// The UTXO
    pub utxo: Utxo,
}

#[derive(Debug, Clone, PartialEq, Eq)]
/// An unspent transaction output (UTXO).
pub enum Utxo {
    /// A UTXO owned by the local wallet.
    Local(LocalUtxo),
    /// A UTXO owned by another wallet.
    Foreign {
        /// The location of the output.
        outpoint: OutPoint,
        /// The information about the input we require to add it to a PSBT.
        // Box it to stop the type being too big.
        psbt_input: Box<psbt::Input>,
    },
}

impl Utxo {
    /// Get the location of the UTXO
    pub fn outpoint(&self) -> OutPoint {
        match &self {
            Utxo::Local(local) => local.outpoint,
            Utxo::Foreign { outpoint, .. } => *outpoint,
        }
    }

    /// Get the `TxOut` of the UTXO
    pub fn txout(&self) -> &TxOut {
        match &self {
            Utxo::Local(local) => &local.txout,
            Utxo::Foreign {
                outpoint,
                psbt_input,
            } => {
                if let Some(prev_tx) = &psbt_input.non_witness_utxo {
                    return &prev_tx.output[outpoint.vout as usize];
                }

                if let Some(txout) = &psbt_input.witness_utxo {
                    return txout;
                }

                unreachable!("Foreign UTXOs will always have one of these set")
            }
        }
    }
}

/// A wallet transaction
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct TransactionDetails {
    /// Optional transaction
    pub transaction: Option<Transaction>,
    /// Transaction id
    pub txid: Txid,
    /// Received value (sats)
    /// Sum of owned outputs of this transaction.
    pub received: u64,
    /// Sent value (sats)
    /// Sum of owned inputs of this transaction.
    pub sent: u64,
    /// Fee value (sats) if confirmed.
    /// The availability of the fee depends on the backend. It's never `None` with an Electrum
    /// Server backend, but it could be `None` with a Bitcoin RPC node without txindex that receive
    /// funds while offline.
    pub fee: Option<u64>,
    /// If the transaction is confirmed, contains height and Unix timestamp of the block containing the
    /// transaction, unconfirmed transaction contains `None`.
    pub confirmation_time: Option<BlockTime>,
}

impl PartialOrd for TransactionDetails {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for TransactionDetails {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.confirmation_time
            .cmp(&other.confirmation_time)
            .then_with(|| self.txid.cmp(&other.txid))
    }
}

/// Block height and timestamp of a block
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
pub struct BlockTime {
    /// confirmation block height
    pub height: u32,
    /// confirmation block timestamp
    pub timestamp: u64,
}

impl PartialOrd for BlockTime {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for BlockTime {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.height
            .cmp(&other.height)
            .then_with(|| self.timestamp.cmp(&other.timestamp))
    }
}

/// **DEPRECATED**: Confirmation time of a transaction
///
/// The structure has been renamed to `BlockTime`
#[deprecated(note = "This structure has been renamed to `BlockTime`")]
pub type ConfirmationTime = BlockTime;

impl BlockTime {
    /// Returns `Some` `BlockTime` if both `height` and `timestamp` are `Some`
    pub fn new(height: Option<u32>, timestamp: Option<u64>) -> Option<Self> {
        match (height, timestamp) {
            (Some(height), Some(timestamp)) => Some(BlockTime { height, timestamp }),
            _ => None,
        }
    }
}

/// Balance differentiated in various categories
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)]
pub struct Balance {
    /// All coinbase outputs not yet matured
    pub immature: u64,
    /// Unconfirmed UTXOs generated by a wallet tx
    pub trusted_pending: u64,
    /// Unconfirmed UTXOs received from an external wallet
    pub untrusted_pending: u64,
    /// Confirmed and immediately spendable balance
    pub confirmed: u64,
}

impl Balance {
    /// Get sum of trusted_pending and confirmed coins
    pub fn get_spendable(&self) -> u64 {
        self.confirmed + self.trusted_pending
    }

    /// Get the whole balance visible to the wallet
    pub fn get_total(&self) -> u64 {
        self.confirmed + self.trusted_pending + self.untrusted_pending + self.immature
    }
}

impl std::fmt::Display for Balance {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{{ immature: {}, trusted_pending: {}, untrusted_pending: {}, confirmed: {} }}",
            self.immature, self.trusted_pending, self.untrusted_pending, self.confirmed
        )
    }
}

impl std::ops::Add for Balance {
    type Output = Self;

    fn add(self, other: Self) -> Self {
        Self {
            immature: self.immature + other.immature,
            trusted_pending: self.trusted_pending + other.trusted_pending,
            untrusted_pending: self.untrusted_pending + other.untrusted_pending,
            confirmed: self.confirmed + other.confirmed,
        }
    }
}

impl std::iter::Sum for Balance {
    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
        iter.fold(
            Balance {
                ..Default::default()
            },
            |a, b| a + b,
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use bitcoin::hashes::Hash;

    #[test]
    fn sort_block_time() {
        let block_time_a = BlockTime {
            height: 100,
            timestamp: 100,
        };

        let block_time_b = BlockTime {
            height: 100,
            timestamp: 110,
        };

        let block_time_c = BlockTime {
            height: 0,
            timestamp: 0,
        };

        let mut vec = vec![
            block_time_a.clone(),
            block_time_b.clone(),
            block_time_c.clone(),
        ];
        vec.sort();
        let expected = vec![block_time_c, block_time_a, block_time_b];

        assert_eq!(vec, expected)
    }

    #[test]
    fn sort_tx_details() {
        let block_time_a = BlockTime {
            height: 100,
            timestamp: 100,
        };

        let block_time_b = BlockTime {
            height: 0,
            timestamp: 0,
        };

        let tx_details_a = TransactionDetails {
            transaction: None,
            txid: Txid::all_zeros(),
            received: 0,
            sent: 0,
            fee: None,
            confirmation_time: None,
        };

        let tx_details_b = TransactionDetails {
            transaction: None,
            txid: Txid::all_zeros(),
            received: 0,
            sent: 0,
            fee: None,
            confirmation_time: Some(block_time_a),
        };

        let tx_details_c = TransactionDetails {
            transaction: None,
            txid: Txid::all_zeros(),
            received: 0,
            sent: 0,
            fee: None,
            confirmation_time: Some(block_time_b.clone()),
        };

        let tx_details_d = TransactionDetails {
            transaction: None,
            txid: Txid::from_byte_array([1; Txid::LEN]),
            received: 0,
            sent: 0,
            fee: None,
            confirmation_time: Some(block_time_b),
        };

        let mut vec = vec![
            tx_details_a.clone(),
            tx_details_b.clone(),
            tx_details_c.clone(),
            tx_details_d.clone(),
        ];
        vec.sort();
        let expected = vec![tx_details_a, tx_details_c, tx_details_d, tx_details_b];

        assert_eq!(vec, expected)
    }

    #[test]
    fn can_store_feerate_in_const() {
        const _MIN_RELAY: FeeRate = FeeRate::default_min_relay_fee();
    }

    #[test]
    #[should_panic]
    fn test_invalid_feerate_neg_zero() {
        let _ = FeeRate::from_sat_per_vb(-0.0);
    }

    #[test]
    #[should_panic]
    fn test_invalid_feerate_neg_value() {
        let _ = FeeRate::from_sat_per_vb(-5.0);
    }

    #[test]
    #[should_panic]
    fn test_invalid_feerate_nan() {
        let _ = FeeRate::from_sat_per_vb(f32::NAN);
    }

    #[test]
    #[should_panic]
    fn test_invalid_feerate_inf() {
        let _ = FeeRate::from_sat_per_vb(f32::INFINITY);
    }

    #[test]
    fn test_valid_feerate_pos_zero() {
        let _ = FeeRate::from_sat_per_vb(0.0);
    }

    #[test]
    fn test_fee_from_btc_per_kvb() {
        let fee = FeeRate::from_btc_per_kvb(1e-5);
        assert!((fee.as_sat_per_vb() - 1.0).abs() < f32::EPSILON);
    }

    #[test]
    fn test_fee_from_sat_per_vbyte() {
        let fee = FeeRate::from_sat_per_vb(1.0);
        assert!((fee.as_sat_per_vb() - 1.0).abs() < f32::EPSILON);
    }

    #[test]
    fn test_fee_default_min_relay_fee() {
        let fee = FeeRate::default_min_relay_fee();
        assert!((fee.as_sat_per_vb() - 1.0).abs() < f32::EPSILON);
    }

    #[test]
    fn test_fee_from_sat_per_kvb() {
        let fee = FeeRate::from_sat_per_kvb(1000.0);
        assert!((fee.as_sat_per_vb() - 1.0).abs() < f32::EPSILON);
    }

    #[test]
    fn test_fee_from_sat_per_kwu() {
        let fee = FeeRate::from_sat_per_kwu(250.0);
        assert!((fee.as_sat_per_vb() - 1.0).abs() < f32::EPSILON);
    }
}