bdk/src/wallet/mod.rs

1768 lines
68 KiB
Rust
Raw Normal View History

// Bitcoin Dev Kit
// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
2020-08-31 11:26:36 +02:00
//
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
2020-08-31 11:26:36 +02:00
//
// 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.
2020-08-31 11:26:36 +02:00
2020-09-04 16:29:25 +02:00
//! Wallet
//!
//! This module defines the [`Wallet`] structure.
2023-01-10 15:10:02 +11:00
use crate::collections::{BTreeMap, HashMap, HashSet};
use alloc::{
boxed::Box,
string::{String, ToString},
sync::Arc,
vec::Vec,
};
pub use bdk_chain::keychain::Balance;
2023-02-15 12:23:59 +11:00
use bdk_chain::{
chain_graph,
keychain::{KeychainChangeSet, KeychainScan, KeychainTracker},
sparse_chain, BlockId, ConfirmationTime, IntoOwned,
};
use bitcoin::secp256k1::Secp256k1;
2023-01-10 15:10:02 +11:00
use core::fmt;
use core::ops::Deref;
2020-02-07 23:22:28 +01:00
use bitcoin::consensus::encode::serialize;
2022-10-25 11:15:43 +02:00
use bitcoin::util::psbt;
2022-04-26 15:11:22 +02:00
use bitcoin::{
Address, BlockHash, EcdsaSighashType, LockTime, Network, OutPoint, SchnorrSighashType, Script,
Sequence, Transaction, TxOut, Txid, Witness,
2022-04-26 15:11:22 +02:00
};
2020-02-07 23:22:28 +01:00
2022-10-25 11:15:43 +02:00
use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier};
2020-02-07 23:22:28 +01:00
#[allow(unused_imports)]
use log::{debug, error, info, trace};
pub mod coin_selection;
pub mod export;
pub mod signer;
pub mod tx_builder;
2020-08-31 10:49:44 +02:00
pub(crate) mod utils;
#[cfg(feature = "hardware-signer")]
2022-08-29 16:16:56 +02:00
#[cfg_attr(docsrs, doc(cfg(feature = "hardware-signer")))]
pub mod hardwaresigner;
2023-02-15 12:23:59 +11:00
pub mod persist;
2020-08-31 10:49:44 +02:00
pub use utils::IsDust;
2020-02-07 23:22:28 +01:00
#[allow(deprecated)]
use coin_selection::DefaultCoinSelectionAlgorithm;
use signer::{SignOptions, SignerOrdering, SignersContainer, TransactionSigner};
use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams};
2022-10-25 11:15:43 +02:00
use utils::{check_nsequence_rbf, After, Older, SecpCtx};
use crate::descriptor::policy::BuildSatisfaction;
use crate::descriptor::{
calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorMeta,
2023-02-15 12:23:59 +11:00
ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils,
};
2022-10-25 11:15:43 +02:00
use crate::error::{Error, MiniscriptPsbtError};
use crate::psbt::PsbtUtils;
2021-05-06 15:55:58 +02:00
use crate::signer::SignerError;
2020-02-07 23:22:28 +01:00
use crate::types::*;
use crate::wallet::coin_selection::Excess::{Change, NoChange};
2020-02-07 23:22:28 +01:00
const COINBASE_MATURITY: u32 = 100;
2020-09-04 16:29:25 +02:00
/// A Bitcoin wallet
///
/// The `Wallet` struct acts as a way of coherently interfacing with output descriptors and related transactions.
/// Its main components are:
2020-09-04 16:29:25 +02:00
///
/// 1. output *descriptors* from which it can derive addresses.
/// 2. A [`Database`] where it tracks transactions and utxos related to the descriptors.
/// 3. [`signer`]s that can contribute signatures to addresses instantiated from the descriptors.
///
/// [`Database`]: crate::database::Database
/// [`signer`]: crate::signer
#[derive(Debug)]
2023-02-15 12:23:59 +11:00
pub struct Wallet<D = ()> {
signers: Arc<SignersContainer>,
change_signers: Arc<SignersContainer>,
keychain_tracker: KeychainTracker<KeychainKind, ConfirmationTime>,
2023-02-15 12:23:59 +11:00
persist: persist::Persist<D>,
2020-02-07 23:22:28 +01:00
network: Network,
2020-11-16 22:07:38 +01:00
secp: SecpCtx,
2020-02-07 23:22:28 +01:00
}
2023-02-15 12:23:59 +11:00
/// The update to a [`Wallet`] used in [Wallet::apply_update]. This is usually returned from blockchain data sources.
/// The type parameter `T` indicates the kind of transaction contained in the update. It's usually a [`bitcoin::Transaction`].
pub type Update<T> = KeychainScan<KeychainKind, ConfirmationTime, T>;
/// Error indicating that something was wrong with an [`Update<T>`].
pub type UpdateError = chain_graph::UpdateError<ConfirmationTime>;
/// The changeset produced internally by applying an update
pub(crate) type ChangeSet = KeychainChangeSet<KeychainKind, ConfirmationTime, Transaction>;
/// The address index selection strategy to use to derived an address from the wallet's external
/// descriptor. See [`Wallet::get_address`]. If you're unsure which one to use use `WalletIndex::New`.
#[derive(Debug)]
pub enum AddressIndex {
/// Return a new address after incrementing the current descriptor index.
New,
/// Return the address for the current descriptor index if it has not been used in a received
/// transaction. Otherwise return a new address as with [`AddressIndex::New`].
///
/// Use with caution, if the wallet has not yet detected an address has been used it could
/// return an already used address. This function is primarily meant for situations where the
/// caller is untrusted; for example when deriving donation addresses on-demand for a public
/// web page.
LastUnused,
/// Return the address for a specific descriptor index. Does not change the current descriptor
/// index used by `AddressIndex::New` and `AddressIndex::LastUsed`.
///
/// Use with caution, if an index is given that is less than the current descriptor index
/// then the returned address may have already been used.
Peek(u32),
}
2023-01-19 15:03:37 -05:00
/// A derived address and the index it was found at.
2021-05-17 16:31:18 -04:00
/// For convenience this automatically derefs to `Address`
2022-10-25 11:15:43 +02:00
#[derive(Debug, PartialEq, Eq)]
2021-05-17 16:31:18 -04:00
pub struct AddressInfo {
/// Child index of this address
pub index: u32,
/// Address
pub address: Address,
2022-03-10 06:22:02 +05:30
/// Type of keychain
pub keychain: KeychainKind,
2021-05-17 16:31:18 -04:00
}
impl Deref for AddressInfo {
type Target = Address;
fn deref(&self) -> &Self::Target {
&self.address
}
}
impl fmt::Display for AddressInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.address)
}
}
impl Wallet {
2023-02-15 12:23:59 +11:00
/// Creates a wallet that does not persist data.
pub fn new_no_persist<E: IntoWalletDescriptor>(
descriptor: E,
change_descriptor: Option<E>,
network: Network,
) -> Result<Self, crate::descriptor::DescriptorError> {
Self::new(descriptor, change_descriptor, (), network).map_err(|e| match e {
NewError::Descriptor(e) => e,
NewError::Persist(_) => unreachable!("no persistence so it can't fail"),
})
}
}
#[derive(Debug)]
/// Error returned from [`Wallet::new`]
pub enum NewError<P> {
/// There was problem with the descriptors passed in
Descriptor(crate::descriptor::DescriptorError),
/// We were unable to load the wallet's data from the persistance backend
Persist(P),
}
impl<P> core::fmt::Display for NewError<P>
where
P: core::fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NewError::Descriptor(e) => e.fmt(f),
NewError::Persist(e) => {
write!(f, "failed to load wallet from persistance backend: {}", e)
}
}
}
}
#[cfg(feautre = "std")]
impl<P: core::fmt::Display> std::error::Error for NewError<P> {}
impl<D> Wallet<D> {
/// Create a wallet from a `descriptor` (and an optional `change_descriptor`) and load related
/// transaction data from `db`.
pub fn new<E: IntoWalletDescriptor>(
descriptor: E,
change_descriptor: Option<E>,
2023-02-15 12:23:59 +11:00
mut db: D,
network: Network,
2023-02-15 12:23:59 +11:00
) -> Result<Self, NewError<D::LoadError>>
where
D: persist::Backend,
{
let secp = Secp256k1::new();
let mut keychain_tracker = KeychainTracker::default();
2023-02-15 12:23:59 +11:00
let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, &secp, network)
.map_err(NewError::Descriptor)?;
keychain_tracker
.txout_index
.add_keychain(KeychainKind::External, descriptor.clone());
let signers = Arc::new(SignersContainer::build(keymap, &descriptor, &secp));
let change_signers = match change_descriptor {
Some(desc) => {
let (change_descriptor, change_keymap) =
2023-02-15 12:23:59 +11:00
into_wallet_descriptor_checked(desc, &secp, network)
.map_err(NewError::Descriptor)?;
let change_signers = Arc::new(SignersContainer::build(
change_keymap,
&change_descriptor,
&secp,
));
keychain_tracker
.txout_index
.add_keychain(KeychainKind::Internal, change_descriptor);
change_signers
}
None => Arc::new(SignersContainer::new()),
};
2023-02-15 12:23:59 +11:00
db.load_into_keychain_tracker(&mut keychain_tracker)
.map_err(NewError::Persist)?;
let persist = persist::Persist::new(db);
Ok(Wallet {
signers,
change_signers,
network,
2023-02-15 12:23:59 +11:00
persist,
secp,
keychain_tracker,
})
}
/// Get the Bitcoin network the wallet is using.
pub fn network(&self) -> Network {
self.network
}
/// Iterator over all keychains in this wallet
pub fn keychanins(&self) -> &BTreeMap<KeychainKind, ExtendedDescriptor> {
self.keychain_tracker.txout_index.keychains()
}
/// Return a derived address using the external descriptor, see [`AddressIndex`] for
/// available address index selection strategies. If none of the keys in the descriptor are derivable
2022-08-05 11:39:50 -04:00
/// (i.e. does not end with /*) then the same address will always be returned for any [`AddressIndex`].
2023-02-15 12:23:59 +11:00
pub fn get_address(&mut self, address_index: AddressIndex) -> AddressInfo
where
D: persist::Backend,
{
self._get_address(address_index, KeychainKind::External)
}
/// Return a derived address using the internal (change) descriptor.
///
/// If the wallet doesn't have an internal descriptor it will use the external descriptor.
///
/// see [`AddressIndex`] for available address index selection strategies. If none of the keys
2022-08-05 11:39:50 -04:00
/// in the descriptor are derivable (i.e. does not end with /*) then the same address will always
/// be returned for any [`AddressIndex`].
2023-02-15 12:23:59 +11:00
pub fn get_internal_address(&mut self, address_index: AddressIndex) -> AddressInfo
where
D: persist::Backend,
{
self._get_address(address_index, KeychainKind::Internal)
}
2023-02-15 12:23:59 +11:00
fn _get_address(&mut self, address_index: AddressIndex, keychain: KeychainKind) -> AddressInfo
where
D: persist::Backend,
{
let keychain = self.map_keychain(keychain);
let txout_index = &mut self.keychain_tracker.txout_index;
let (index, spk) = match address_index {
AddressIndex::New => {
let ((index, spk), changeset) = txout_index.reveal_next_spk(&keychain);
let spk = spk.clone();
self.persist.stage(changeset.into());
self.persist.commit().expect("TODO");
(index, spk)
}
AddressIndex::LastUnused => {
let index = txout_index.last_revealed_index(&keychain);
match index {
Some(index) if !txout_index.is_used(&(keychain, index)) => (
index,
txout_index
.spk_at_index(&(keychain, index))
.expect("must exist")
.clone(),
),
_ => return self._get_address(AddressIndex::New, keychain),
}
}
AddressIndex::Peek(index) => txout_index
.spks_of_keychain(&keychain)
.take(index as usize + 1)
.last()
.unwrap(),
};
let info = AddressInfo {
index,
address: Address::from_script(&spk, self.network)
.expect("descriptor must have address form"),
keychain,
};
info
}
2020-09-04 16:29:25 +02:00
/// Return whether or not a `script` is part of this wallet (either internal or external)
pub fn is_mine(&self, script: &Script) -> bool {
self.keychain_tracker
.txout_index
.index_of_spk(script)
.is_some()
2020-02-07 23:22:28 +01:00
}
/// Finds how the wallet derived the script pubkey `spk`.
///
/// Will only return `Some(_)` if the wallet has given out the spk.
pub fn derivation_of_spk(&self, spk: &Script) -> Option<(KeychainKind, u32)> {
self.keychain_tracker.txout_index.index_of_spk(spk).copied()
}
2020-09-04 16:29:25 +02:00
/// Return the list of unspent outputs of this wallet
///
/// Note that this method only operates on the internal database, which first needs to be
2020-09-04 16:29:25 +02:00
/// [`Wallet::sync`] manually.
pub fn list_unspent(&self) -> Vec<LocalUtxo> {
self.keychain_tracker
.full_utxos()
.map(|(&(keychain, derivation_index), utxo)| LocalUtxo {
outpoint: utxo.outpoint,
txout: utxo.txout,
keychain: keychain.clone(),
is_spent: false,
derivation_index,
confirmation_time: utxo.chain_position,
})
.collect()
}
2023-02-15 12:23:59 +11:00
/// Get all the checkpoints the wallet is currently storing indexed by height.
pub fn checkpoints(&self) -> &BTreeMap<u32, BlockHash> {
self.keychain_tracker.chain().checkpoints()
}
/// Returns the latest checkpoint.
pub fn latest_checkpoint(&self) -> Option<BlockId> {
self.keychain_tracker.chain().latest_checkpoint()
}
2023-02-15 12:23:59 +11:00
/// Returns a iterators of all the script pubkeys for the `Internal` and External` variants in `KeychainKind`.
///
/// This is inteded to be used when doing a full scan of your addresses (e.g. after restoring
/// from seed words). You pass the `BTreeMap` of iterators to a blockchain data source (e.g.
/// electrum server) which will go through each address until it reaches a *stop grap*.
///
/// Note carefully that iterators go over **all** script pubkeys on the keychains (not what
/// script pubkeys the wallet is storing internally).
pub fn spks_of_all_keychains(
&self,
) -> BTreeMap<KeychainKind, impl Iterator<Item = (u32, Script)> + Clone> {
self.keychain_tracker.txout_index.spks_of_all_keychains()
2020-02-07 23:22:28 +01:00
}
2023-02-15 12:23:59 +11:00
/// Gets an iterator over all the script pubkeys in a single keychain.
///
/// See [`spks_of_all_keychains`] for more documentation
///
/// [`spks_of_all_keychains`]: Self::spks_of_all_keychains
pub fn spks_of_keychain(
&self,
keychain: KeychainKind,
) -> impl Iterator<Item = (u32, Script)> + Clone {
self.keychain_tracker
.txout_index
.spks_of_keychain(&keychain)
}
/// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the
/// wallet's database.
pub fn get_utxo(&self, op: OutPoint) -> Option<LocalUtxo> {
self.keychain_tracker
.full_utxos()
.find_map(|(&(keychain, derivation_index), txo)| {
if op == txo.outpoint {
Some(LocalUtxo {
outpoint: txo.outpoint,
txout: txo.txout,
keychain,
is_spent: txo.spent_by.is_none(),
derivation_index,
confirmation_time: txo.chain_position,
})
} else {
None
}
})
}
/// Return a single transactions made and received by the wallet
///
/// Optionally fill the [`TransactionDetails::transaction`] field with the raw transaction if
/// `include_raw` is `true`.
///
/// Note that this method only operates on the internal database, which first needs to be
/// [`Wallet::sync`] manually.
pub fn get_tx(&self, txid: Txid, include_raw: bool) -> Option<TransactionDetails> {
let (&confirmation_time, tx) = self.keychain_tracker.chain_graph().get_tx_in_chain(txid)?;
let graph = self.keychain_tracker.graph();
let txout_index = &self.keychain_tracker.txout_index;
let received = tx
.output
.iter()
.map(|txout| {
if txout_index.index_of_spk(&txout.script_pubkey).is_some() {
txout.value
} else {
0
}
})
.sum();
let sent = tx
.input
.iter()
.map(|txin| {
if let Some((_, txout)) = txout_index.txout(txin.previous_output) {
txout.value
} else {
0
}
})
.sum();
let inputs = tx
.input
.iter()
.map(|txin| {
graph
.get_txout(txin.previous_output)
.map(|txout| txout.value)
})
.sum::<Option<u64>>();
let outputs = tx.output.iter().map(|txout| txout.value).sum();
let fee = inputs.map(|inputs| inputs.saturating_sub(outputs));
Some(TransactionDetails {
transaction: if include_raw { Some(tx.clone()) } else { None },
txid,
received,
sent,
fee,
confirmation_time,
})
}
2023-02-15 12:23:59 +11:00
/// Add a new checkpoint to the wallet's internal view of the chain.
/// This stages but does not [`commit`] the change.
///
/// Returns whether anything changed with the insertion (e.g. `false` if checkpoint was already
/// there).
///
/// [`commit`]: Self::commit
pub fn insert_checkpoint(
&mut self,
block_id: BlockId,
) -> Result<bool, sparse_chain::InsertCheckpointError> {
2023-02-15 12:23:59 +11:00
let changeset = self.keychain_tracker.insert_checkpoint(block_id)?;
let changed = changeset.is_empty();
self.persist.stage(changeset);
Ok(changed)
}
2023-02-15 12:23:59 +11:00
/// Add a transaction to the wallet's internal view of the chain.
/// This stages but does not [`commit`] the change.
///
/// There are a number reasons `tx` could be rejected with an `Err(_)`. The most important one
/// is that the transaction is at a height that is greater than [`latest_checkpoint`]. Therefore
/// you should use [`insert_checkpoint`] to insert new checkpoints before manually inserting new
/// transactions.
///
/// Returns whether anything changed with the transaction insertion (e.g. `false` if the
/// transaction was already inserted at the same position).
///
/// [`commit`]: Self::commit
/// [`latest_checkpoint`]: Self::latest_checkpoint
/// [`insert_checkpoint`]: Self::insert_checkpoint
pub fn insert_tx(
&mut self,
tx: Transaction,
position: ConfirmationTime,
) -> Result<bool, chain_graph::InsertTxError<ConfirmationTime>> {
2023-02-15 12:23:59 +11:00
let changeset = self.keychain_tracker.insert_tx(tx, position)?;
let changed = changeset.is_empty();
self.persist.stage(changeset);
Ok(changed)
}
#[deprecated(note = "use Wallet::transactions instead")]
/// Deprecated. use `Wallet::transactions` instead.
pub fn list_transactions(&self, include_raw: bool) -> Vec<TransactionDetails> {
self.keychain_tracker
.chain()
.txids()
.map(|&(_, txid)| self.get_tx(txid, include_raw).expect("must exist"))
.collect()
}
/// Iterate over the transactions in the wallet in order of ascending confirmation time with
/// unconfirmed transactions last.
pub fn transactions(
&self,
) -> impl DoubleEndedIterator<Item = (ConfirmationTime, &Transaction)> + '_ {
self.keychain_tracker
2023-02-15 12:23:59 +11:00
.chain_graph()
.transactions_in_chain()
.map(|(pos, tx)| (*pos, tx))
2020-02-07 23:22:28 +01:00
}
/// Return the balance, separated into available, trusted-pending, untrusted-pending and immature
/// values.
2020-09-04 16:29:25 +02:00
///
2022-08-05 11:39:50 -04:00
/// Note that this method only operates on the internal database, which first needs to be
2020-09-04 16:29:25 +02:00
/// [`Wallet::sync`] manually.
pub fn get_balance(&self) -> Balance {
self.keychain_tracker.balance(|keychain| match keychain {
KeychainKind::External => false,
KeychainKind::Internal => true,
})
2020-02-07 23:22:28 +01:00
}
2020-09-04 16:29:25 +02:00
/// Add an external signer
///
/// See [the `signer` module](signer) for an example.
2020-08-15 23:21:13 +02:00
pub fn add_signer(
&mut self,
keychain: KeychainKind,
ordering: SignerOrdering,
signer: Arc<dyn TransactionSigner>,
2020-08-15 23:21:13 +02:00
) {
let signers = match keychain {
KeychainKind::External => Arc::make_mut(&mut self.signers),
KeychainKind::Internal => Arc::make_mut(&mut self.change_signers),
2020-08-15 23:21:13 +02:00
};
signers.add_external(signer.id(&self.secp), ordering, signer);
2020-08-15 23:21:13 +02:00
}
2022-03-09 18:38:11 +01:00
/// Get the signers
///
/// ## Example
///
/// ```
/// # use bdk::{Wallet, KeychainKind};
/// # use bdk::bitcoin::Network;
/// # use bdk::database::MemoryDatabase;
2023-02-15 12:23:59 +11:00
/// let wallet = Wallet::new_no_persist("wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*)", None, Network::Testnet, MemoryDatabase::new())?;
2022-03-09 18:38:11 +01:00
/// for secret_key in wallet.get_signers(KeychainKind::External).signers().iter().filter_map(|s| s.descriptor_secret_key()) {
/// // secret_key: tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*
/// println!("secret_key: {}", secret_key);
/// }
///
/// Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn get_signers(&self, keychain: KeychainKind) -> Arc<SignersContainer> {
match keychain {
KeychainKind::External => Arc::clone(&self.signers),
KeychainKind::Internal => Arc::clone(&self.change_signers),
}
}
/// Start building a transaction.
///
/// This returns a blank [`TxBuilder`] from which you can specify the parameters for the transaction.
2020-09-04 16:29:25 +02:00
///
/// ## Example
///
/// ```
2020-09-04 16:29:25 +02:00
/// # use std::str::FromStr;
/// # use bitcoin::*;
2020-09-14 14:25:38 +02:00
/// # use bdk::*;
/// # use bdk::database::*;
2020-09-04 16:29:25 +02:00
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
/// # let wallet = doctest_wallet!();
2020-09-04 16:29:25 +02:00
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
/// let (psbt, details) = {
/// let mut builder = wallet.build_tx();
/// builder
/// .add_recipient(to_address.script_pubkey(), 50_000);
/// builder.finish()?
/// };
///
2020-09-04 16:29:25 +02:00
/// // sign and broadcast ...
2020-09-14 14:25:38 +02:00
/// # Ok::<(), bdk::Error>(())
2020-09-04 16:29:25 +02:00
/// ```
///
/// [`TxBuilder`]: crate::TxBuilder
2023-02-15 12:23:59 +11:00
pub fn build_tx(&mut self) -> TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, CreateTx> {
TxBuilder {
2023-01-10 15:10:02 +11:00
wallet: alloc::rc::Rc::new(core::cell::RefCell::new(self)),
params: TxParams::default(),
coin_selection: DefaultCoinSelectionAlgorithm::default(),
phantom: core::marker::PhantomData,
}
}
pub(crate) fn create_tx<Cs: coin_selection::CoinSelectionAlgorithm>(
&mut self,
coin_selection: Cs,
params: TxParams,
2023-02-15 12:23:59 +11:00
) -> Result<(psbt::PartiallySignedTransaction, TransactionDetails), Error>
where
D: persist::Backend,
{
let external_descriptor = self
.keychain_tracker
.txout_index
.keychains()
.get(&KeychainKind::External)
.expect("must exist");
let internal_descriptor = self
.keychain_tracker
.txout_index
.keychains()
.get(&KeychainKind::Internal);
let external_policy = external_descriptor
.extract_policy(&self.signers, BuildSatisfaction::None, &self.secp)?
.unwrap();
let internal_policy = internal_descriptor
.as_ref()
.map(|desc| {
Ok::<_, Error>(
desc.extract_policy(&self.change_signers, BuildSatisfaction::None, &self.secp)?
.unwrap(),
)
})
.transpose()?;
// The policy allows spending external outputs, but it requires a policy path that hasn't been
// provided
if params.change_policy != tx_builder::ChangeSpendPolicy::OnlyChange
&& external_policy.requires_path()
&& params.external_policy_path.is_none()
{
return Err(Error::SpendingPolicyRequired(KeychainKind::External));
};
// Same for the internal_policy path, if present
if let Some(internal_policy) = &internal_policy {
if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeForbidden
&& internal_policy.requires_path()
&& params.internal_policy_path.is_none()
{
return Err(Error::SpendingPolicyRequired(KeychainKind::Internal));
};
2020-02-07 23:22:28 +01:00
}
let external_requirements = external_policy.get_condition(
params
.external_policy_path
.as_ref()
.unwrap_or(&BTreeMap::new()),
)?;
let internal_requirements = internal_policy
.map(|policy| {
Ok::<_, Error>(
policy.get_condition(
params
.internal_policy_path
.as_ref()
.unwrap_or(&BTreeMap::new()),
)?,
)
})
.transpose()?;
let requirements =
external_requirements.merge(&internal_requirements.unwrap_or_default())?;
debug!("Policy requirements: {:?}", requirements);
2020-02-07 23:22:28 +01:00
let version = match params.version {
Some(tx_builder::Version(0)) => {
return Err(Error::Generic("Invalid version `0`".into()))
}
Some(tx_builder::Version(1)) if requirements.csv.is_some() => {
return Err(Error::Generic(
"TxBuilder requested version `1`, but at least `2` is needed to use OP_CSV"
.into(),
))
}
Some(tx_builder::Version(x)) => x,
None if requirements.csv.is_some() => 2,
_ => 1,
};
// We use a match here instead of a map_or_else as it's way more readable :)
let current_height = match params.current_height {
// If they didn't tell us the current height, we assume it's the latest sync height.
None => self
.keychain_tracker
.chain()
.latest_checkpoint()
.and_then(|cp| cp.height.into())
.map(|height| LockTime::from_height(height).expect("Invalid height")),
h => h,
};
let lock_time = match params.locktime {
// When no nLockTime is specified, we try to prevent fee sniping, if possible
None => {
// Fee sniping can be partially prevented by setting the timelock
// to current_height. If we don't know the current_height,
// we default to 0.
2022-10-25 11:15:43 +02:00
let fee_sniping_height = current_height.unwrap_or(LockTime::ZERO);
// We choose the biggest between the required nlocktime and the fee sniping
// height
2022-10-25 11:15:43 +02:00
match requirements.timelock {
// No requirement, just use the fee_sniping_height
None => fee_sniping_height,
// There's a block-based requirement, but the value is lower than the fee_sniping_height
Some(value @ LockTime::Blocks(_)) if value < fee_sniping_height => fee_sniping_height,
// There's a time-based requirement or a block-based requirement greater
// than the fee_sniping_height use that value
Some(value) => value,
}
}
// Specific nLockTime required and we have no constraints, so just set to that value
Some(x) if requirements.timelock.is_none() => x,
// Specific nLockTime required and it's compatible with the constraints
2022-10-25 11:15:43 +02:00
Some(x) if requirements.timelock.unwrap().is_same_unit(x) && x >= requirements.timelock.unwrap() => x,
// Invalid nLockTime required
2022-10-25 11:15:43 +02:00
Some(x) => return Err(Error::Generic(format!("TxBuilder requested timelock of `{:?}`, but at least `{:?}` is required to spend from this script", x, requirements.timelock.unwrap())))
};
let n_sequence = match (params.rbf, requirements.csv) {
// No RBF or CSV but there's an nLockTime, so the nSequence cannot be final
2022-10-25 11:15:43 +02:00
(None, None) if lock_time != LockTime::ZERO => Sequence::ENABLE_LOCKTIME_NO_RBF,
// No RBF, CSV or nLockTime, make the transaction final
2022-10-25 11:15:43 +02:00
(None, None) => Sequence::MAX,
// No RBF requested, use the value from CSV. Note that this value is by definition
// non-final, so even if a timelock is enabled this nSequence is fine, hence why we
// don't bother checking for it here. The same is true for all the other branches below
(None, Some(csv)) => csv,
// RBF with a specific value but that value is too high
2022-10-25 11:15:43 +02:00
(Some(tx_builder::RbfValue::Value(rbf)), _) if !rbf.is_rbf() => {
return Err(Error::Generic(
"Cannot enable RBF with a nSequence >= 0xFFFFFFFE".into(),
))
}
// RBF with a specific value requested, but the value is incompatible with CSV
(Some(tx_builder::RbfValue::Value(rbf)), Some(csv))
if !check_nsequence_rbf(rbf, csv) =>
{
return Err(Error::Generic(format!(
2022-10-25 11:15:43 +02:00
"Cannot enable RBF with nSequence `{:?}` given a required OP_CSV of `{:?}`",
rbf, csv
)))
}
// RBF enabled with the default value with CSV also enabled. CSV takes precedence
(Some(tx_builder::RbfValue::Default), Some(csv)) => csv,
// Valid RBF, either default or with a specific value. We ignore the `CSV` value
// because we've already checked it before
(Some(rbf), _) => rbf.get_value(),
};
let (fee_rate, mut fee_amount) = match params
.fee_policy
.as_ref()
.unwrap_or(&FeePolicy::FeeRate(FeeRate::default()))
{
//FIXME: see https://github.com/bitcoindevkit/bdk/issues/256
FeePolicy::FeeAmount(fee) => {
if let Some(previous_fee) = params.bumping_fee {
if *fee < previous_fee.absolute {
return Err(Error::FeeTooLow {
required: previous_fee.absolute,
});
}
}
(FeeRate::from_sat_per_vb(0.0), *fee)
}
FeePolicy::FeeRate(rate) => {
if let Some(previous_fee) = params.bumping_fee {
let required_feerate = FeeRate::from_sat_per_vb(previous_fee.rate + 1.0);
if *rate < required_feerate {
return Err(Error::FeeRateTooLow {
required: required_feerate,
});
}
}
(*rate, 0)
}
};
2020-02-07 23:22:28 +01:00
let mut tx = Transaction {
version,
2022-10-25 11:15:43 +02:00
lock_time: lock_time.into(),
2020-02-07 23:22:28 +01:00
input: vec![],
output: vec![],
};
if params.manually_selected_only && params.utxos.is_empty() {
return Err(Error::NoUtxosSelected);
2020-02-07 23:22:28 +01:00
}
// we keep it as a float while we accumulate it, and only round it at the end
let mut outgoing: u64 = 0;
let mut received: u64 = 0;
let recipients = params.recipients.iter().map(|(r, v)| (r, *v));
for (index, (script_pubkey, value)) in recipients.enumerate() {
if !params.allow_dust
&& value.is_dust(script_pubkey)
&& !script_pubkey.is_provably_unspendable()
{
return Err(Error::OutputBelowDustLimit(index));
}
2020-02-07 23:22:28 +01:00
if self.is_mine(script_pubkey) {
2020-02-07 23:22:28 +01:00
received += value;
}
let new_out = TxOut {
script_pubkey: script_pubkey.clone(),
2020-02-07 23:22:28 +01:00
value,
};
tx.output.push(new_out);
outgoing += value;
}
fee_amount += fee_rate.fee_wu(tx.weight());
// Segwit transactions' header is 2WU larger than legacy txs' header,
// as they contain a witness marker (1WU) and a witness flag (1WU) (see BIP144).
// At this point we really don't know if the resulting transaction will be segwit
// or legacy, so we just add this 2WU to the fee_amount - overshooting the fee amount
// is better than undershooting it.
// If we pass a fee_amount that is slightly higher than the final fee_amount, we
// end up with a transaction with a slightly higher fee rate than the requested one.
// If, instead, we undershoot, we may end up with a feerate lower than the requested one
// - we might come up with non broadcastable txs!
fee_amount += fee_rate.fee_wu(2);
if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeAllowed
&& internal_descriptor.is_none()
{
return Err(Error::Generic(
"The `change_policy` can be set only if the wallet has a change_descriptor".into(),
));
}
let (required_utxos, optional_utxos) = self.preselect_utxos(
params.change_policy,
&params.unspendable,
params.utxos.clone(),
params.drain_wallet,
params.manually_selected_only,
params.bumping_fee.is_some(), // we mandate confirmed transactions if we're bumping the fee
2022-10-25 11:15:43 +02:00
current_height.map(LockTime::to_consensus_u32),
);
// get drain script
let drain_script = match params.drain_to {
Some(ref drain_recipient) => drain_recipient.clone(),
2023-02-15 12:23:59 +11:00
None => {
let change_keychain = self.map_keychain(KeychainKind::Internal);
let ((index, spk), changeset) = self
.keychain_tracker
.txout_index
.next_unused_spk(&change_keychain);
let spk = spk.clone();
self.keychain_tracker
.txout_index
.mark_used(&change_keychain, index);
self.persist.stage(changeset.into());
self.persist.commit().expect("TODO");
spk
}
};
let coin_selection = coin_selection.coin_select(
required_utxos,
2020-10-26 14:12:46 -04:00
optional_utxos,
2020-02-07 23:22:28 +01:00
fee_rate,
outgoing + fee_amount,
&drain_script,
2020-02-07 23:22:28 +01:00
)?;
fee_amount += coin_selection.fee_amount;
let excess = &coin_selection.excess;
tx.input = coin_selection
.selected
.iter()
.map(|u| bitcoin::TxIn {
previous_output: u.outpoint(),
script_sig: Script::default(),
sequence: n_sequence,
witness: Witness::new(),
})
.collect();
2020-02-07 23:22:28 +01:00
if tx.output.is_empty() {
// Uh oh, our transaction has no outputs.
// We allow this when:
// - We have a drain_to address and the utxos we must spend (this happens,
// for example, when we RBF)
// - We have a drain_to address and drain_wallet set
// Otherwise, we don't know who we should send the funds to, and how much
// we should send!
if params.drain_to.is_some() && (params.drain_wallet || !params.utxos.is_empty()) {
if let NoChange {
dust_threshold,
remaining_amount,
change_fee,
} = excess
{
return Err(Error::InsufficientFunds {
needed: *dust_threshold,
available: remaining_amount.saturating_sub(*change_fee),
});
}
} else {
return Err(Error::NoRecipients);
2020-02-07 23:22:28 +01:00
}
}
match excess {
NoChange {
remaining_amount, ..
} => fee_amount += remaining_amount,
Change { amount, fee } => {
if self.is_mine(&drain_script) {
received += amount;
}
fee_amount += fee;
// create drain output
let drain_output = TxOut {
value: *amount,
script_pubkey: drain_script,
};
// TODO: We should pay attention when adding a new output: this might increase
// the lenght of the "number of vouts" parameter by 2 bytes, potentially making
// our feerate too low
tx.output.push(drain_output);
}
};
// sort input/outputs according to the chosen algorithm
params.ordering.sort_tx(&mut tx);
2020-02-07 23:22:28 +01:00
let txid = tx.txid();
let sent = coin_selection.local_selected_amount();
let psbt = self.complete_transaction(tx, coin_selection.selected, params)?;
2020-02-07 23:22:28 +01:00
2020-08-13 16:51:27 +02:00
let transaction_details = TransactionDetails {
transaction: None,
txid,
confirmation_time: ConfirmationTime::Unconfirmed,
2020-08-13 16:51:27 +02:00
received,
sent,
fee: Some(fee_amount),
2020-08-13 16:51:27 +02:00
};
2020-08-13 16:51:27 +02:00
Ok((psbt, transaction_details))
}
2020-02-07 23:22:28 +01:00
/// Bump the fee of a transaction previously created with this wallet.
2020-09-04 16:29:25 +02:00
///
/// Returns an error if the transaction is already confirmed or doesn't explicitly signal
2021-11-23 13:40:58 -05:00
/// *replace by fee* (RBF). If the transaction can be fee bumped then it returns a [`TxBuilder`]
/// pre-populated with the inputs and outputs of the original transaction.
2020-09-04 16:29:25 +02:00
///
/// ## Example
///
/// ```no_run
/// # // TODO: remove norun -- bumping fee seems to need the tx in the wallet database first.
2020-09-04 16:29:25 +02:00
/// # use std::str::FromStr;
/// # use bitcoin::*;
2020-09-14 14:25:38 +02:00
/// # use bdk::*;
/// # use bdk::database::*;
2020-09-04 16:29:25 +02:00
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
/// # let wallet = doctest_wallet!();
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
/// let (mut psbt, _) = {
/// let mut builder = wallet.build_tx();
/// builder
/// .add_recipient(to_address.script_pubkey(), 50_000)
/// .enable_rbf();
/// builder.finish()?
/// };
/// let _ = wallet.sign(&mut psbt, SignOptions::default())?;
/// let tx = psbt.extract_tx();
/// // broadcast tx but it's taking too long to confirm so we want to bump the fee
/// let (mut psbt, _) = {
/// let mut builder = wallet.build_fee_bump(tx.txid())?;
/// builder
/// .fee_rate(FeeRate::from_sat_per_vb(5.0));
/// builder.finish()?
/// };
///
/// let _ = wallet.sign(&mut psbt, SignOptions::default())?;
/// let fee_bumped_tx = psbt.extract_tx();
/// // broadcast fee_bumped_tx to replace original
2020-09-14 14:25:38 +02:00
/// # Ok::<(), bdk::Error>(())
2020-09-04 16:29:25 +02:00
/// ```
2020-08-13 16:51:27 +02:00
// TODO: support for merging multiple transactions while bumping the fees
pub fn build_fee_bump(
&mut self,
txid: Txid,
2023-02-15 12:23:59 +11:00
) -> Result<TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, BumpFee>, Error> {
let graph = self.keychain_tracker.graph();
let txout_index = &self.keychain_tracker.txout_index;
let tx_and_height = self.keychain_tracker.chain_graph().get_tx_in_chain(txid);
let mut tx = match tx_and_height {
2020-08-13 16:51:27 +02:00
None => return Err(Error::TransactionNotFound),
Some((ConfirmationTime::Confirmed { .. }, _tx)) => {
return Err(Error::TransactionConfirmed)
}
Some((_, tx)) => tx.clone(),
2020-08-13 16:51:27 +02:00
};
2022-10-25 11:15:43 +02:00
if !tx
.input
.iter()
.any(|txin| txin.sequence.to_consensus_u32() <= 0xFFFFFFFD)
{
2020-08-13 16:51:27 +02:00
return Err(Error::IrreplaceableTransaction);
}
2020-02-07 23:22:28 +01:00
2023-02-15 12:23:59 +11:00
let fee = graph.calculate_fee(&tx).ok_or(Error::FeeRateUnavailable)?;
if fee < 0 {
// It's available but it's wrong so let's say it's unavailable
return Err(Error::FeeRateUnavailable)?;
}
let fee = fee as u64;
let feerate = FeeRate::from_wu(fee, tx.weight());
2020-08-13 16:51:27 +02:00
2020-10-16 14:27:50 +02:00
// remove the inputs from the tx and process them
let original_txin = tx.input.drain(..).collect::<Vec<_>>();
let original_utxos = original_txin
2020-10-16 14:27:50 +02:00
.iter()
.map(|txin| -> Result<_, Error> {
let (&confirmation_time, prev_tx) = self
.keychain_tracker
.chain_graph()
.get_tx_in_chain(txin.previous_output.txid)
.ok_or(Error::UnknownUtxo)?;
let txout = &prev_tx.output[txin.previous_output.vout as usize];
2020-08-13 16:51:27 +02:00
let weighted_utxo = match txout_index.index_of_spk(&txout.script_pubkey) {
Some(&(keychain, derivation_index)) => {
let satisfaction_weight = self
.get_descriptor_for_keychain(keychain)
.max_satisfaction_weight()
.unwrap();
WeightedUtxo {
utxo: Utxo::Local(LocalUtxo {
outpoint: txin.previous_output,
txout: txout.clone(),
keychain,
is_spent: true,
derivation_index,
confirmation_time,
}),
satisfaction_weight,
}
}
2020-10-16 14:27:50 +02:00
None => {
let satisfaction_weight =
2020-10-16 14:27:50 +02:00
serialize(&txin.script_sig).len() * 4 + serialize(&txin.witness).len();
WeightedUtxo {
satisfaction_weight,
utxo: Utxo::Foreign {
outpoint: txin.previous_output,
psbt_input: Box::new(psbt::Input {
witness_utxo: Some(txout.clone()),
non_witness_utxo: Some(prev_tx.clone()),
..Default::default()
}),
},
}
2020-08-13 16:51:27 +02:00
}
2020-10-16 14:27:50 +02:00
};
Ok(weighted_utxo)
})
.collect::<Result<Vec<_>, _>>()?;
2020-10-16 14:27:50 +02:00
if tx.output.len() > 1 {
let mut change_index = None;
for (index, txout) in tx.output.iter().enumerate() {
let change_type = self.map_keychain(KeychainKind::Internal);
match txout_index.index_of_spk(&txout.script_pubkey) {
Some(&(keychain, _)) if keychain == change_type => change_index = Some(index),
_ => {}
}
2020-10-16 14:27:50 +02:00
}
if let Some(change_index) = change_index {
tx.output.remove(change_index);
}
2020-08-13 16:51:27 +02:00
}
let params = TxParams {
// TODO: figure out what rbf option should be?
version: Some(tx_builder::Version(tx.version)),
recipients: tx
.output
.into_iter()
.map(|txout| (txout.script_pubkey, txout.value))
.collect(),
utxos: original_utxos,
bumping_fee: Some(tx_builder::PreviousFee {
absolute: fee,
rate: feerate.as_sat_per_vb(),
}),
..Default::default()
};
2020-08-13 16:51:27 +02:00
Ok(TxBuilder {
2023-01-10 15:10:02 +11:00
wallet: alloc::rc::Rc::new(core::cell::RefCell::new(self)),
params,
coin_selection: DefaultCoinSelectionAlgorithm::default(),
phantom: core::marker::PhantomData,
})
2020-02-07 23:22:28 +01:00
}
2020-09-04 16:29:25 +02:00
/// Sign a transaction with all the wallet's signers, in the order specified by every signer's
/// [`SignerOrdering`]. This function returns the `Result` type with an encapsulated `bool` that has the value true if the PSBT was finalized, or false otherwise.
2020-09-04 16:29:25 +02:00
///
/// The [`SignOptions`] can be used to tweak the behavior of the software signers, and the way
/// the transaction is finalized at the end. Note that it can't be guaranteed that *every*
/// signers will follow the options, but the "software signers" (WIF keys and `xprv`) defined
/// in this library will.
///
2020-09-04 16:29:25 +02:00
/// ## Example
///
/// ```
2020-09-04 16:29:25 +02:00
/// # use std::str::FromStr;
/// # use bitcoin::*;
2020-09-14 14:25:38 +02:00
/// # use bdk::*;
/// # use bdk::database::*;
2020-09-04 16:29:25 +02:00
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
/// # let wallet = doctest_wallet!();
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
/// let (mut psbt, _) = {
/// let mut builder = wallet.build_tx();
/// builder.add_recipient(to_address.script_pubkey(), 50_000);
/// builder.finish()?
/// };
/// let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
/// assert!(finalized, "we should have signed all the inputs");
2020-09-14 14:25:38 +02:00
/// # Ok::<(), bdk::Error>(())
pub fn sign(
&self,
psbt: &mut psbt::PartiallySignedTransaction,
sign_options: SignOptions,
) -> Result<bool, Error> {
2022-10-25 11:15:43 +02:00
// This adds all the PSBT metadata for the inputs, which will help us later figure out how
// to derive our keys
self.update_psbt_with_descriptor(psbt)?;
2020-02-17 14:22:53 +01:00
// If we aren't allowed to use `witness_utxo`, ensure that every input (except p2tr and finalized ones)
// has the `non_witness_utxo`
2021-05-06 11:35:58 +02:00
if !sign_options.trust_witness_utxo
&& psbt
.inputs
.iter()
.filter(|i| i.final_script_witness.is_none() && i.final_script_sig.is_none())
.filter(|i| i.tap_internal_key.is_none() && i.tap_merkle_root.is_none())
.any(|i| i.non_witness_utxo.is_none())
2021-05-06 11:35:58 +02:00
{
return Err(Error::Signer(signer::SignerError::MissingNonWitnessUtxo));
}
// If the user hasn't explicitly opted-in, refuse to sign the transaction unless every input
// is using `SIGHASH_ALL` or `SIGHASH_DEFAULT` for taproot
if !sign_options.allow_all_sighashes
&& !psbt.inputs.iter().all(|i| {
i.sighash_type.is_none()
|| i.sighash_type == Some(EcdsaSighashType::All.into())
|| i.sighash_type == Some(SchnorrSighashType::All.into())
|| i.sighash_type == Some(SchnorrSighashType::Default.into())
})
{
return Err(Error::Signer(signer::SignerError::NonStandardSighash));
}
for signer in self
.signers
.signers()
.iter()
.chain(self.change_signers.signers().iter())
{
signer.sign_transaction(psbt, &sign_options, &self.secp)?;
2020-02-07 23:22:28 +01:00
}
// attempt to finalize
2022-06-04 12:42:52 +07:00
if sign_options.try_finalize {
self.finalize_psbt(psbt, sign_options)
} else {
Ok(false)
}
2020-02-07 23:22:28 +01:00
}
2020-09-04 16:29:25 +02:00
/// Return the spending policies for the wallet's descriptor
pub fn policies(&self, keychain: KeychainKind) -> Result<Option<Policy>, Error> {
let signers = match keychain {
KeychainKind::External => &self.signers,
KeychainKind::Internal => &self.change_signers,
};
match self.public_descriptor(keychain) {
Some(desc) => Ok(desc.extract_policy(signers, BuildSatisfaction::None, &self.secp)?),
None => Ok(None),
2020-02-07 23:22:28 +01:00
}
}
2020-09-04 16:29:25 +02:00
/// Return the "public" version of the wallet's descriptor, meaning a new descriptor that has
/// the same structure but with every secret key removed
///
/// This can be used to build a watch-only version of a wallet
pub fn public_descriptor(&self, keychain: KeychainKind) -> Option<&ExtendedDescriptor> {
self.keychain_tracker.txout_index.keychains().get(&keychain)
}
2022-09-15 14:32:10 +02:00
/// Finalize a PSBT, i.e., for each input determine if sufficient data is available to pass
/// validation and construct the respective `scriptSig` or `scriptWitness`. Please refer to
/// [BIP174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#Input_Finalizer)
/// for further information.
///
/// Returns `true` if the PSBT could be finalized, and `false` otherwise.
///
/// The [`SignOptions`] can be used to tweak the behavior of the finalizer.
pub fn finalize_psbt(
&self,
psbt: &mut psbt::PartiallySignedTransaction,
sign_options: SignOptions,
) -> Result<bool, Error> {
let tx = &psbt.unsigned_tx;
let mut finished = true;
for (n, input) in tx.input.iter().enumerate() {
2021-05-06 15:55:58 +02:00
let psbt_input = &psbt
.inputs
.get(n)
.ok_or(Error::Signer(SignerError::InputIndexOutOfRange))?;
if psbt_input.final_script_sig.is_some() || psbt_input.final_script_witness.is_some() {
continue;
}
let confirmation_height = self
.keychain_tracker
.chain()
.tx_position(input.previous_output.txid)
.map(|conftime| match conftime {
&ConfirmationTime::Confirmed { height, .. } => height,
ConfirmationTime::Unconfirmed => u32::MAX,
});
let last_sync_height = self
.keychain_tracker
.chain()
.latest_checkpoint()
.map(|block_id| block_id.height);
let current_height = sign_options.assume_height.or(last_sync_height);
debug!(
"Input #{} - {}, using `confirmation_height` = {:?}, `current_height` = {:?}",
n, input.previous_output, confirmation_height, current_height
);
// - Try to derive the descriptor by looking at the txout. If it's in our database, we
// know exactly which `keychain` to use, and which derivation index it is
// - If that fails, try to derive it by looking at the psbt input: the complete logic
// is in `src/descriptor/mod.rs`, but it will basically look at `bip32_derivation`,
// `redeem_script` and `witness_script` to determine the right derivation
// - If that also fails, it will try it on the internal descriptor, if present
let desc = psbt
.get_utxo_for(n)
.map(|txout| self.get_descriptor_for_txout(&txout))
.flatten()
.or_else(|| {
self.keychain_tracker
.txout_index
.keychains()
.iter()
.find_map(|(_, desc)| {
desc.derive_from_psbt_input(
psbt_input,
psbt.get_utxo_for(n),
&self.secp,
)
})
});
match desc {
Some(desc) => {
let mut tmp_input = bitcoin::TxIn::default();
match desc.satisfy(
&mut tmp_input,
(
PsbtInputSatisfier::new(psbt, n),
After::new(current_height, false),
Older::new(current_height, confirmation_height, false),
),
) {
Ok(_) => {
let psbt_input = &mut psbt.inputs[n];
psbt_input.final_script_sig = Some(tmp_input.script_sig);
psbt_input.final_script_witness = Some(tmp_input.witness);
2022-05-29 10:53:37 +07:00
if sign_options.remove_partial_sigs {
psbt_input.partial_sigs.clear();
}
}
Err(e) => {
debug!("satisfy error {:?} for input {}", e, n);
finished = false
}
}
}
None => finished = false,
}
}
Ok(finished)
}
2020-12-11 14:10:11 -08:00
/// Return the secp256k1 context used for all signing operations
2020-11-16 22:07:38 +01:00
pub fn secp_ctx(&self) -> &SecpCtx {
&self.secp
}
2021-11-23 13:40:58 -05:00
/// Returns the descriptor used to create addresses for a particular `keychain`.
pub fn get_descriptor_for_keychain(&self, keychain: KeychainKind) -> &ExtendedDescriptor {
self.public_descriptor(self.map_keychain(keychain))
.expect("we mapped it to external if it doesn't exist")
}
/// The derivation index of this wallet. It will return `None` if it has not derived any addresses.
/// Otherwise, it will return the index of the highest address it has derived.
pub fn derivation_index(&self, keychain: KeychainKind) -> Option<u32> {
self.keychain_tracker
.txout_index
.last_revealed_index(&keychain)
2020-02-07 23:22:28 +01:00
}
/// The index of the next address that you would get if you were to ask the wallet for a new address
pub fn next_derivation_index(&self, keychain: KeychainKind) -> u32 {
self.keychain_tracker.txout_index.next_index(&keychain).0
2020-02-07 23:22:28 +01:00
}
/// Informs the wallet that you no longer intend to broadcast a tx that was built from it.
///
/// This frees up the change address used when creating the tx for use in future transactions.
///
// TODO: Make this free up reserved utxos when that's implemented
pub fn cancel_tx(&mut self, tx: &Transaction) {
let txout_index = &mut self.keychain_tracker.txout_index;
for txout in &tx.output {
if let Some(&(keychain, index)) = txout_index.index_of_spk(&txout.script_pubkey) {
// NOTE: unmark_used will **not** make something unused if it has actually been used
// by a tx in the tracker. It only removes the superficial marking.
txout_index.unmark_used(&keychain, index);
}
}
}
fn map_keychain(&self, keychain: KeychainKind) -> KeychainKind {
if keychain == KeychainKind::Internal
&& self.public_descriptor(KeychainKind::Internal).is_none()
{
return KeychainKind::External;
} else {
keychain
}
}
fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option<DerivedDescriptor> {
let &(keychain, child) = self
.keychain_tracker
.txout_index
.index_of_spk(&txout.script_pubkey)?;
let descriptor = self.get_descriptor_for_keychain(keychain);
Some(descriptor.at_derivation_index(child))
2020-02-07 23:22:28 +01:00
}
fn get_available_utxos(&self) -> Vec<(LocalUtxo, usize)> {
self.list_unspent()
.into_iter()
.map(|utxo| {
let keychain = utxo.keychain;
(
utxo,
self.get_descriptor_for_keychain(keychain)
.max_satisfaction_weight()
.unwrap(),
)
})
.collect()
}
/// Given the options returns the list of utxos that must be used to form the
/// transaction and any further that may be used if needed.
#[allow(clippy::type_complexity)]
#[allow(clippy::too_many_arguments)]
fn preselect_utxos(
&self,
change_policy: tx_builder::ChangeSpendPolicy,
unspendable: &HashSet<OutPoint>,
manually_selected: Vec<WeightedUtxo>,
must_use_all_available: bool,
manual_only: bool,
must_only_use_confirmed_tx: bool,
current_height: Option<u32>,
) -> (Vec<WeightedUtxo>, Vec<WeightedUtxo>) {
// must_spend <- manually selected utxos
// may_spend <- all other available utxos
let mut may_spend = self.get_available_utxos();
may_spend.retain(|may_spend| {
!manually_selected
.iter()
.any(|manually_selected| manually_selected.utxo.outpoint() == may_spend.0.outpoint)
});
let mut must_spend = manually_selected;
// NOTE: we are intentionally ignoring `unspendable` here. i.e manual
// selection overrides unspendable.
if manual_only {
return (must_spend, vec![]);
2020-02-07 23:22:28 +01:00
}
let satisfies_confirmed = may_spend
.iter()
.map(|u| {
let txid = u.0.outpoint.txid;
let tx = self.keychain_tracker.chain_graph().get_tx_in_chain(txid);
match tx {
// We don't have the tx in the db for some reason,
// so we can't know for sure if it's mature or not.
// We prefer not to spend it.
None => false,
Some((confirmation_time, tx)) => {
// Whether the UTXO is mature and, if needed, confirmed
let mut spendable = true;
if must_only_use_confirmed_tx && !confirmation_time.is_confirmed() {
return false;
}
if tx.is_coin_base() {
debug_assert!(
confirmation_time.is_confirmed(),
"coinbase must always be confirmed"
);
if let Some(current_height) = current_height {
match confirmation_time {
ConfirmationTime::Confirmed { height, .. } => {
// https://github.com/bitcoin/bitcoin/blob/c5e67be03bb06a5d7885c55db1f016fbf2333fe3/src/validation.cpp#L373-L375
spendable &= (current_height.saturating_sub(*height))
>= COINBASE_MATURITY;
}
ConfirmationTime::Unconfirmed => spendable = false,
}
}
}
spendable
}
}
})
.collect::<Vec<_>>();
let mut i = 0;
may_spend.retain(|u| {
let retain = change_policy.is_satisfied_by(&u.0)
&& !unspendable.contains(&u.0.outpoint)
&& satisfies_confirmed[i];
i += 1;
retain
});
let mut may_spend = may_spend
.into_iter()
.map(|(local_utxo, satisfaction_weight)| WeightedUtxo {
satisfaction_weight,
utxo: Utxo::Local(local_utxo),
})
.collect();
if must_use_all_available {
must_spend.append(&mut may_spend);
}
(must_spend, may_spend)
2020-02-07 23:22:28 +01:00
}
fn complete_transaction(
2020-08-13 16:51:27 +02:00
&self,
tx: Transaction,
selected: Vec<Utxo>,
params: TxParams,
) -> Result<psbt::PartiallySignedTransaction, Error> {
let mut psbt = psbt::PartiallySignedTransaction::from_unsigned_tx(tx)?;
if params.add_global_xpubs {
let all_xpubs = self
.keychanins()
.iter()
.flat_map(|(_, desc)| desc.get_extended_keys())
.collect::<Vec<_>>();
for xpub in all_xpubs {
let origin = match xpub.origin {
Some(origin) => origin,
None if xpub.xkey.depth == 0 => {
(xpub.root_fingerprint(&self.secp), vec![].into())
}
_ => return Err(Error::MissingKeyOrigin(xpub.xkey.to_string())),
};
psbt.xpub.insert(xpub.xkey, origin);
}
}
let mut lookup_output = selected
.into_iter()
.map(|utxo| (utxo.outpoint(), utxo))
.collect::<HashMap<_, _>>();
2020-08-13 16:51:27 +02:00
// add metadata for the inputs
for (psbt_input, input) in psbt.inputs.iter_mut().zip(psbt.unsigned_tx.input.iter()) {
let utxo = match lookup_output.remove(&input.previous_output) {
Some(utxo) => utxo,
2020-08-13 16:51:27 +02:00
None => continue,
};
match utxo {
Utxo::Local(utxo) => {
*psbt_input =
match self.get_psbt_input(utxo, params.sighash, params.only_witness_utxo) {
Ok(psbt_input) => psbt_input,
Err(e) => match e {
Error::UnknownUtxo => psbt::Input {
sighash_type: params.sighash,
..psbt::Input::default()
},
_ => return Err(e),
2021-03-15 21:50:51 -04:00
},
}
2020-08-13 16:51:27 +02:00
}
Utxo::Foreign {
psbt_input: foreign_psbt_input,
outpoint,
} => {
2022-04-28 15:39:31 +02:00
let is_taproot = foreign_psbt_input
.witness_utxo
.as_ref()
.map(|txout| txout.script_pubkey.is_v1_p2tr())
.unwrap_or(false);
if !is_taproot
&& !params.only_witness_utxo
&& foreign_psbt_input.non_witness_utxo.is_none()
{
return Err(Error::Generic(format!(
"Missing non_witness_utxo on foreign utxo {}",
outpoint
)));
}
*psbt_input = *foreign_psbt_input;
2020-08-13 16:51:27 +02:00
}
}
}
2022-10-25 11:15:43 +02:00
self.update_psbt_with_descriptor(&mut psbt)?;
2020-08-13 16:51:27 +02:00
Ok(psbt)
}
2021-03-15 21:50:51 -04:00
/// get the corresponding PSBT Input for a LocalUtxo
pub fn get_psbt_input(
&self,
utxo: LocalUtxo,
sighash_type: Option<psbt::PsbtSighashType>,
only_witness_utxo: bool,
) -> Result<psbt::Input, Error> {
2021-03-15 21:50:51 -04:00
// Try to find the prev_script in our db to figure out if this is internal or external,
// and the derivation index
let &(keychain, child) = self
.keychain_tracker
.txout_index
.index_of_spk(&utxo.txout.script_pubkey)
.ok_or(Error::UnknownUtxo)?;
2021-03-15 21:50:51 -04:00
let mut psbt_input = psbt::Input {
2021-03-15 21:50:51 -04:00
sighash_type,
..psbt::Input::default()
2021-03-15 21:50:51 -04:00
};
let desc = self.get_descriptor_for_keychain(keychain);
2022-10-25 11:15:43 +02:00
let derived_descriptor = desc.at_derivation_index(child);
2021-03-15 21:50:51 -04:00
2022-10-25 11:15:43 +02:00
psbt_input
.update_with_descriptor_unchecked(&derived_descriptor)
.map_err(MiniscriptPsbtError::Conversion)?;
2021-03-15 21:50:51 -04:00
let prev_output = utxo.outpoint;
if let Some(prev_tx) = self.keychain_tracker.graph().get_tx(prev_output.txid) {
if desc.is_witness() || desc.is_taproot() {
2021-03-15 21:50:51 -04:00
psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone());
}
if !desc.is_taproot() && (!desc.is_witness() || !only_witness_utxo) {
psbt_input.non_witness_utxo = Some(prev_tx.clone());
2021-03-15 21:50:51 -04:00
}
}
Ok(psbt_input)
}
2022-10-25 11:15:43 +02:00
fn update_psbt_with_descriptor(
&self,
psbt: &mut psbt::PartiallySignedTransaction,
) -> Result<(), Error> {
2022-10-25 11:15:43 +02:00
// We need to borrow `psbt` mutably within the loops, so we have to allocate a vec for all
// the input utxos and outputs
//
// Clippy complains that the collect is not required, but that's wrong
#[allow(clippy::needless_collect)]
let utxos = (0..psbt.inputs.len())
.filter_map(|i| psbt.get_utxo_for(i).map(|utxo| (true, i, utxo)))
.chain(
psbt.unsigned_tx
.output
.iter()
.enumerate()
.map(|(i, out)| (false, i, out.clone())),
)
.collect::<Vec<_>>();
2020-06-30 14:01:38 +02:00
2022-10-25 11:15:43 +02:00
// Try to figure out the keychain and derivation for every input and output
for (is_input, index, out) in utxos.into_iter() {
if let Some(&(keychain, child)) = self
.keychain_tracker
.txout_index
.index_of_spk(&out.script_pubkey)
2022-10-25 11:15:43 +02:00
{
debug!(
"Found descriptor for input #{} {:?}/{}",
index, keychain, child
);
let desc = self.get_descriptor_for_keychain(keychain);
let desc = desc.at_derivation_index(child);
if is_input {
psbt.update_input_with_descriptor(index, &desc)
.map_err(MiniscriptPsbtError::UtxoUpdate)?;
} else {
psbt.update_output_with_descriptor(index, &desc)
.map_err(MiniscriptPsbtError::OutputUpdate)?;
}
2020-06-30 14:01:38 +02:00
}
}
Ok(())
}
2022-03-09 18:38:11 +01:00
/// Return the checksum of the public descriptor associated to `keychain`
///
/// Internally calls [`Self::get_descriptor_for_keychain`] to fetch the right descriptor
pub fn descriptor_checksum(&self, keychain: KeychainKind) -> String {
self.get_descriptor_for_keychain(keychain)
.to_string()
2022-05-03 12:41:22 +02:00
.split_once('#')
2022-03-09 18:38:11 +01:00
.unwrap()
.1
2022-03-09 18:38:11 +01:00
.to_string()
}
2023-02-15 12:23:59 +11:00
/// Applies an update to the wallet and stages the changes (but does not [`commit`] them).
///
/// Usually you create an `update` by interacting with some blockchain data source and inserting
/// transactions related to your wallet into it.
///
/// [`commit`]: Self::commit
pub fn apply_udpate<Tx>(&mut self, update: Update<Tx>) -> Result<(), UpdateError>
where
D: persist::Backend,
Tx: IntoOwned<Transaction> + Clone,
{
let changeset = self.keychain_tracker.apply_update(update)?;
self.persist.stage(changeset);
Ok(())
}
/// Commits all curently [`staged`] changed to the persistence backend returning and error when this fails.
///
/// [`staged`]: Self::staged
pub fn commit(&mut self) -> Result<(), D::WriteError>
where
D: persist::Backend,
{
self.persist.commit()
}
/// Returns the changes that will be staged with the next call to [`commit`].
///
/// [`commit`]: Self::commit
pub fn staged(&self) -> &ChangeSet {
self.persist.staged()
}
2020-02-07 23:22:28 +01:00
}
/// Deterministically generate a unique name given the descriptors defining the wallet
///
/// Compatible with [`wallet_name_from_descriptor`]
pub fn wallet_name_from_descriptor<T>(
descriptor: T,
change_descriptor: Option<T>,
network: Network,
secp: &SecpCtx,
) -> Result<String, Error>
where
T: IntoWalletDescriptor,
{
//TODO check descriptors contains only public keys
let descriptor = descriptor
.into_wallet_descriptor(secp, network)?
.0
.to_string();
let mut wallet_name = calc_checksum(&descriptor[..descriptor.find('#').unwrap()])?;
if let Some(change_descriptor) = change_descriptor {
let change_descriptor = change_descriptor
.into_wallet_descriptor(secp, network)?
.0
.to_string();
wallet_name.push_str(
calc_checksum(&change_descriptor[..change_descriptor.find('#').unwrap()])?.as_str(),
);
}
Ok(wallet_name)
}