2020-08-31 11:26:36 +02:00
// Magical Bitcoin Library
// Written in 2020 by
// Alekos Filini <alekos.filini@gmail.com>
//
// Copyright (c) 2020 Magical Bitcoin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
2020-09-04 16:29:25 +02:00
//! Wallet
//!
//! This module defines the [`Wallet`] structure.
2020-02-07 23:22:28 +01:00
use std ::cell ::RefCell ;
2020-08-08 12:06:40 +02:00
use std ::collections ::HashMap ;
2020-05-03 16:15:11 +02:00
use std ::collections ::{ BTreeMap , HashSet } ;
2020-08-13 16:51:27 +02:00
use std ::ops ::{ Deref , DerefMut } ;
2020-08-12 12:51:50 +02:00
use std ::sync ::Arc ;
2020-02-07 23:22:28 +01:00
use bitcoin ::consensus ::encode ::serialize ;
2020-08-12 12:51:50 +02:00
use bitcoin ::util ::bip32 ::ChildNumber ;
2020-02-07 23:22:28 +01:00
use bitcoin ::util ::psbt ::PartiallySignedTransaction as PSBT ;
2020-08-12 12:51:50 +02:00
use bitcoin ::{ Address , Network , OutPoint , Script , SigHashType , Transaction , TxOut , Txid } ;
2020-02-07 23:22:28 +01:00
2020-08-12 12:51:50 +02:00
use miniscript ::descriptor ::DescriptorPublicKey ;
2020-02-07 23:22:28 +01:00
#[ allow(unused_imports) ]
use log ::{ debug , error , info , trace } ;
2020-08-15 23:21:13 +02:00
pub mod address_validator ;
2020-08-06 16:56:41 +02:00
pub mod coin_selection ;
2020-08-07 10:19:06 +02:00
pub mod export ;
2020-08-13 16:51:27 +02:00
mod rbf ;
2020-08-12 12:51:50 +02:00
pub mod signer ;
2020-08-06 11:12:15 +02:00
pub mod time ;
2020-08-06 13:09:39 +02:00
pub mod tx_builder ;
2020-08-31 10:49:44 +02:00
pub ( crate ) mod utils ;
pub use utils ::IsDust ;
2020-02-07 23:22:28 +01:00
2020-08-15 23:21:13 +02:00
use address_validator ::AddressValidator ;
2020-08-17 12:10:51 +02:00
use signer ::{ Signer , SignerId , SignerOrdering , SignersContainer } ;
2020-08-07 11:23:01 +02:00
use tx_builder ::TxBuilder ;
2020-08-31 10:49:44 +02:00
use utils ::{ After , Older } ;
2020-08-06 11:12:15 +02:00
2020-09-09 18:17:49 +02:00
use crate ::blockchain ::{ Blockchain , BlockchainMarker , OfflineBlockchain , Progress } ;
2020-05-03 16:15:11 +02:00
use crate ::database ::{ BatchDatabase , BatchOperations , DatabaseUtils } ;
2020-08-12 12:51:50 +02:00
use crate ::descriptor ::{
get_checksum , DescriptorMeta , DescriptorScripts , ExtendedDescriptor , ExtractPolicy , Policy ,
2020-09-18 16:31:03 +02:00
ToWalletDescriptor ,
2020-08-12 12:51:50 +02:00
} ;
2020-02-07 23:22:28 +01:00
use crate ::error ::Error ;
2020-08-12 12:51:50 +02:00
use crate ::psbt ::PSBTUtils ;
2020-02-07 23:22:28 +01:00
use crate ::types ::* ;
2020-08-06 18:11:07 +02:00
const CACHE_ADDR_BATCH_SIZE : u32 = 100 ;
2020-09-04 16:29:25 +02:00
/// Type alias for a [`Wallet`] that uses [`OfflineBlockchain`]
2020-05-03 16:15:11 +02:00
pub type OfflineWallet < D > = Wallet < OfflineBlockchain , D > ;
2020-09-04 16:29:25 +02:00
/// A Bitcoin wallet
///
/// A wallet takes descriptors, a [`database`](crate::database) and a
/// [`blockchain`](crate::blockchain) and implements the basic functions that a Bitcoin wallets
/// needs to operate, like [generating addresses](Wallet::get_new_address), [returning the balance](Wallet::get_balance),
/// [creating transactions](Wallet::create_tx), etc.
///
/// A wallet can be either "online" if the [`blockchain`](crate::blockchain) type provided
2020-09-09 18:17:49 +02:00
/// implements [`Blockchain`], or "offline" [`OfflineBlockchain`] is used. Offline wallets only expose
2020-09-04 16:29:25 +02:00
/// methods that don't need any interaction with the blockchain to work.
2020-09-09 18:17:49 +02:00
pub struct Wallet < B : BlockchainMarker , D : BatchDatabase > {
2020-02-07 23:22:28 +01:00
descriptor : ExtendedDescriptor ,
change_descriptor : Option < ExtendedDescriptor > ,
2020-08-12 12:51:50 +02:00
signers : Arc < SignersContainer < DescriptorPublicKey > > ,
change_signers : Arc < SignersContainer < DescriptorPublicKey > > ,
2020-08-15 23:21:13 +02:00
address_validators : Vec < Arc < Box < dyn AddressValidator > > > ,
2020-02-07 23:22:28 +01:00
network : Network ,
2020-05-06 17:17:14 +02:00
current_height : Option < u32 > ,
2020-09-09 18:17:49 +02:00
client : Option < B > ,
2020-02-17 14:22:53 +01:00
database : RefCell < D > ,
2020-02-07 23:22:28 +01:00
}
// offline actions, always available
2020-05-03 16:15:11 +02:00
impl < B , D > Wallet < B , D >
2020-02-07 23:22:28 +01:00
where
2020-09-09 18:17:49 +02:00
B : BlockchainMarker ,
2020-02-07 23:22:28 +01:00
D : BatchDatabase ,
{
2020-09-04 16:29:25 +02:00
/// Create a new "offline" wallet
2020-09-18 16:31:03 +02:00
pub fn new_offline < E : ToWalletDescriptor > (
descriptor : E ,
change_descriptor : Option < E > ,
2020-02-07 23:22:28 +01:00
network : Network ,
2020-02-15 21:27:51 +01:00
mut database : D ,
) -> Result < Self , Error > {
2020-09-21 15:44:07 +02:00
let ( descriptor , keymap ) = descriptor . to_wallet_descriptor ( network ) ? ;
2020-02-15 21:27:51 +01:00
database . check_descriptor_checksum (
ScriptType ::External ,
2020-09-18 16:31:03 +02:00
get_checksum ( & descriptor . to_string ( ) ) ? . as_bytes ( ) ,
2020-02-15 21:27:51 +01:00
) ? ;
2020-08-12 12:51:50 +02:00
let signers = Arc ::new ( SignersContainer ::from ( keymap ) ) ;
let ( change_descriptor , change_signers ) = match change_descriptor {
2020-02-15 21:27:51 +01:00
Some ( desc ) = > {
2020-09-21 15:44:07 +02:00
let ( change_descriptor , change_keymap ) = desc . to_wallet_descriptor ( network ) ? ;
2020-02-15 21:27:51 +01:00
database . check_descriptor_checksum (
ScriptType ::Internal ,
2020-09-18 16:31:03 +02:00
get_checksum ( & change_descriptor . to_string ( ) ) ? . as_bytes ( ) ,
2020-02-15 21:27:51 +01:00
) ? ;
2020-02-17 14:22:53 +01:00
2020-08-12 12:51:50 +02:00
let change_signers = Arc ::new ( SignersContainer ::from ( change_keymap ) ) ;
// if !parsed.same_structure(descriptor.as_ref()) {
// return Err(Error::DifferentDescriptorStructure);
// }
2020-02-17 14:22:53 +01:00
2020-08-12 12:51:50 +02:00
( Some ( change_descriptor ) , change_signers )
2020-02-15 21:27:51 +01:00
}
2020-08-12 12:51:50 +02:00
None = > ( None , Arc ::new ( SignersContainer ::new ( ) ) ) ,
2020-02-15 21:27:51 +01:00
} ;
Ok ( Wallet {
2020-02-07 23:22:28 +01:00
descriptor ,
change_descriptor ,
2020-08-12 12:51:50 +02:00
signers ,
change_signers ,
2020-08-15 23:21:13 +02:00
address_validators : Vec ::new ( ) ,
2020-08-12 12:51:50 +02:00
2020-02-07 23:22:28 +01:00
network ,
2020-05-06 17:17:14 +02:00
current_height : None ,
2020-09-09 18:17:49 +02:00
client : None ,
2020-02-07 23:22:28 +01:00
database : RefCell ::new ( database ) ,
2020-02-15 21:27:51 +01:00
} )
2020-02-07 23:22:28 +01:00
}
2020-09-04 16:29:25 +02:00
/// Return a newly generated address using the external descriptor
2020-02-07 23:22:28 +01:00
pub fn get_new_address ( & self ) -> Result < Address , Error > {
2020-08-06 18:11:07 +02:00
let index = self . fetch_and_increment_index ( ScriptType ::External ) ? ;
2020-02-07 23:22:28 +01:00
self . descriptor
2020-08-12 12:51:50 +02:00
. derive ( & [ ChildNumber ::from_normal_idx ( index ) . unwrap ( ) ] )
2020-02-07 23:22:28 +01:00
. address ( self . network )
. ok_or ( Error ::ScriptDoesntHaveAddressForm )
}
2020-09-04 16:29:25 +02:00
/// Return whether or not a `script` is part of this wallet (either internal or external)
2020-02-07 23:22:28 +01:00
pub fn is_mine ( & self , script : & Script ) -> Result < bool , Error > {
2020-05-03 16:15:11 +02:00
self . database . borrow ( ) . is_mine ( script )
2020-02-07 23:22:28 +01:00
}
2020-09-04 16:29:25 +02:00
/// Return the list of unspent outputs of this wallet
///
/// Note that this methods only operate on the internal database, which first needs to be
/// [`Wallet::sync`] manually.
2020-02-07 23:22:28 +01:00
pub fn list_unspent ( & self ) -> Result < Vec < UTXO > , Error > {
self . database . borrow ( ) . iter_utxos ( )
}
2020-09-04 16:29:25 +02:00
/// Return the list of 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 methods only operate on the internal database, which first needs to be
/// [`Wallet::sync`] manually.
2020-02-07 23:22:28 +01:00
pub fn list_transactions ( & self , include_raw : bool ) -> Result < Vec < TransactionDetails > , Error > {
self . database . borrow ( ) . iter_txs ( include_raw )
}
2020-09-04 16:29:25 +02:00
/// Return the balance, meaning the sum of this wallet's unspent outputs' values
///
/// Note that this methods only operate on the internal database, which first needs to be
/// [`Wallet::sync`] manually.
2020-02-07 23:22:28 +01:00
pub fn get_balance ( & self ) -> Result < u64 , Error > {
Ok ( self
. list_unspent ( ) ?
. iter ( )
. fold ( 0 , | sum , i | sum + i . txout . value ) )
}
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 ,
script_type : ScriptType ,
id : SignerId < DescriptorPublicKey > ,
2020-08-17 12:10:51 +02:00
ordering : SignerOrdering ,
2020-08-15 23:21:13 +02:00
signer : Arc < Box < dyn Signer > > ,
) {
let signers = match script_type {
ScriptType ::External = > Arc ::make_mut ( & mut self . signers ) ,
ScriptType ::Internal = > Arc ::make_mut ( & mut self . change_signers ) ,
} ;
2020-08-17 12:10:51 +02:00
signers . add_external ( id , ordering , signer ) ;
2020-08-15 23:21:13 +02:00
}
2020-09-04 16:29:25 +02:00
/// Add an address validator
///
/// See [the `address_validator` module](address_validator) for an example.
2020-08-15 23:21:13 +02:00
pub fn add_address_validator ( & mut self , validator : Arc < Box < dyn AddressValidator > > ) {
self . address_validators . push ( validator ) ;
}
2020-09-04 16:29:25 +02:00
/// Create a new transaction following the options specified in the `builder`
///
/// ## Example
///
/// ```no_run
/// # 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: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
/// let (psbt, details) = wallet.create_tx(
/// TxBuilder::with_recipients(vec![(to_address.script_pubkey(), 50_000)])
/// )?;
/// // sign and broadcast ...
2020-09-14 14:25:38 +02:00
/// # Ok::<(), bdk::Error>(())
2020-09-04 16:29:25 +02:00
/// ```
2020-08-06 16:56:41 +02:00
pub fn create_tx < Cs : coin_selection ::CoinSelectionAlgorithm > (
& self ,
builder : TxBuilder < Cs > ,
) -> Result < ( PSBT , TransactionDetails ) , Error > {
2020-08-31 10:49:44 +02:00
if builder . recipients . is_empty ( ) {
2020-08-10 17:16:47 +02:00
return Err ( Error ::NoAddressees ) ;
}
2020-08-06 16:56:41 +02:00
// TODO: fetch both internal and external policies
2020-08-12 12:51:50 +02:00
let policy = self
. descriptor
. extract_policy ( Arc ::clone ( & self . signers ) ) ?
. unwrap ( ) ;
2020-08-06 13:09:39 +02:00
if policy . requires_path ( ) & & builder . policy_path . is_none ( ) {
2020-02-07 23:22:28 +01:00
return Err ( Error ::SpendingPolicyRequired ) ;
}
2020-08-06 13:09:39 +02:00
let requirements =
2020-09-04 11:44:49 +02:00
policy . get_condition ( builder . policy_path . as_ref ( ) . unwrap_or ( & BTreeMap ::new ( ) ) ) ? ;
2020-02-07 23:22:28 +01:00
debug! ( " requirements: {:?} " , requirements ) ;
2020-08-07 16:30:19 +02:00
let version = match builder . version {
2020-08-10 17:16:47 +02:00
Some ( tx_builder ::Version ( 0 ) ) = > {
return Err ( Error ::Generic ( " Invalid version `0` " . into ( ) ) )
}
Some ( tx_builder ::Version ( 1 ) ) if requirements . csv . is_some ( ) = > {
2020-08-07 16:30:19 +02:00
return Err ( Error ::Generic (
" TxBuilder requested version `1`, but at least `2` is needed to use OP_CSV "
. into ( ) ,
) )
}
2020-08-10 17:16:47 +02:00
Some ( tx_builder ::Version ( x ) ) = > x ,
None if requirements . csv . is_some ( ) = > 2 ,
_ = > 1 ,
2020-08-07 16:30:19 +02:00
} ;
let lock_time = match builder . locktime {
None = > requirements . timelock . unwrap_or ( 0 ) ,
Some ( x ) if requirements . timelock . is_none ( ) = > x ,
Some ( x ) if requirements . timelock . unwrap ( ) < = x = > x ,
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 ( builder . rbf , requirements . csv ) {
( None , Some ( csv ) ) = > csv ,
( Some ( rbf ) , Some ( csv ) ) if rbf < csv = > return Err ( Error ::Generic ( format! ( " Cannot enable RBF with nSequence ` {} `, since at least ` {} ` is required to spend with OP_CSV " , rbf , csv ) ) ) ,
( None , _ ) if requirements . timelock . is_some ( ) = > 0xFFFFFFFE ,
2020-09-04 15:45:11 +02:00
( Some ( rbf ) , _ ) if rbf > = 0xFFFFFFFE = > return Err ( Error ::Generic ( " Cannot enable RBF with a nSequence >= 0xFFFFFFFE " . into ( ) ) ) ,
2020-08-07 16:30:19 +02:00
( Some ( rbf ) , _ ) = > rbf ,
( None , _ ) = > 0xFFFFFFFF ,
} ;
2020-02-07 23:22:28 +01:00
let mut tx = Transaction {
2020-08-07 16:30:19 +02:00
version ,
lock_time ,
2020-02-07 23:22:28 +01:00
input : vec ! [ ] ,
output : vec ! [ ] ,
} ;
2020-08-31 10:49:44 +02:00
let fee_rate = builder . fee_rate . unwrap_or_default ( ) ;
if builder . send_all & & builder . recipients . len ( ) ! = 1 {
2020-02-07 23:22:28 +01:00
return Err ( Error ::SendAllMultipleOutputs ) ;
}
// we keep it as a float while we accumulate it, and only round it at the end
2020-08-06 16:56:41 +02:00
let mut fee_amount : f32 = 0.0 ;
2020-02-07 23:22:28 +01:00
let mut outgoing : u64 = 0 ;
let mut received : u64 = 0 ;
2020-08-31 10:49:44 +02:00
let calc_fee_bytes = | wu | ( wu as f32 ) * fee_rate . as_sat_vb ( ) / 4.0 ;
2020-08-06 16:56:41 +02:00
fee_amount + = calc_fee_bytes ( tx . get_weight ( ) ) ;
2020-02-07 23:22:28 +01:00
2020-09-04 15:45:11 +02:00
for ( index , ( script_pubkey , satoshi ) ) in builder . recipients . iter ( ) . enumerate ( ) {
2020-08-06 13:09:39 +02:00
let value = match builder . send_all {
2020-02-07 23:22:28 +01:00
true = > 0 ,
false if satoshi . is_dust ( ) = > return Err ( Error ::OutputBelowDustLimit ( index ) ) ,
false = > * satoshi ,
} ;
2020-09-04 15:45:11 +02:00
if self . is_mine ( script_pubkey ) ? {
2020-02-07 23:22:28 +01:00
received + = value ;
}
let new_out = TxOut {
2020-09-04 15:45:11 +02:00
script_pubkey : script_pubkey . clone ( ) ,
2020-02-07 23:22:28 +01:00
value ,
} ;
2020-08-06 16:56:41 +02:00
fee_amount + = calc_fee_bytes ( serialize ( & new_out ) . len ( ) * 4 ) ;
2020-02-07 23:22:28 +01:00
tx . output . push ( new_out ) ;
outgoing + = value ;
}
2020-08-06 16:56:41 +02:00
// TODO: use the right weight instead of the maximum, and only fall-back to it if the
// script is unknown in the database
let input_witness_weight = std ::cmp ::max (
2020-08-12 12:51:50 +02:00
self . get_descriptor_for_script_type ( ScriptType ::Internal )
2020-08-06 18:11:07 +02:00
. 0
2020-08-06 16:56:41 +02:00
. max_satisfaction_weight ( ) ,
2020-08-12 12:51:50 +02:00
self . get_descriptor_for_script_type ( ScriptType ::External )
2020-08-06 18:11:07 +02:00
. 0
2020-08-06 16:56:41 +02:00
. max_satisfaction_weight ( ) ,
) ;
2020-02-07 23:22:28 +01:00
2020-08-10 17:16:47 +02:00
if builder . change_policy ! = tx_builder ::ChangeSpendPolicy ::ChangeAllowed
& & self . change_descriptor . is_none ( )
{
return Err ( Error ::Generic (
" The `change_policy` can be set only if the wallet has a change_descriptor " . into ( ) ,
) ) ;
}
2020-08-07 19:40:13 +02:00
let ( available_utxos , use_all_utxos ) = self . get_available_utxos (
builder . change_policy ,
& builder . utxos ,
& builder . unspendable ,
builder . send_all ,
) ? ;
2020-08-06 16:56:41 +02:00
let coin_selection ::CoinSelectionResult {
txin ,
2020-08-31 10:49:44 +02:00
selected_amount ,
2020-08-06 16:56:41 +02:00
mut fee_amount ,
} = builder . coin_selection . coin_select (
2020-02-07 23:22:28 +01:00
available_utxos ,
use_all_utxos ,
fee_rate ,
outgoing ,
input_witness_weight ,
2020-08-06 16:56:41 +02:00
fee_amount ,
2020-02-07 23:22:28 +01:00
) ? ;
2020-08-06 16:56:41 +02:00
let ( mut txin , prev_script_pubkeys ) : ( Vec < _ > , Vec < _ > ) = txin . into_iter ( ) . unzip ( ) ;
2020-08-08 12:06:40 +02:00
// map that allows us to lookup the prev_script_pubkey for a given previous_output
let prev_script_pubkeys = txin
. iter ( )
. zip ( prev_script_pubkeys . into_iter ( ) )
. map ( | ( txin , script ) | ( txin . previous_output , script ) )
. collect ::< HashMap < _ , _ > > ( ) ;
2020-08-06 16:56:41 +02:00
txin . iter_mut ( ) . for_each ( | i | i . sequence = n_sequence ) ;
tx . input = txin ;
2020-02-07 23:22:28 +01:00
// prepare the change output
2020-08-06 13:09:39 +02:00
let change_output = match builder . send_all {
2020-02-07 23:22:28 +01:00
true = > None ,
false = > {
let change_script = self . get_change_address ( ) ? ;
let change_output = TxOut {
script_pubkey : change_script ,
value : 0 ,
} ;
// take the change into account for fees
2020-08-06 16:56:41 +02:00
fee_amount + = calc_fee_bytes ( serialize ( & change_output ) . len ( ) * 4 ) ;
2020-02-07 23:22:28 +01:00
Some ( change_output )
}
} ;
2020-08-10 10:49:34 +02:00
let mut fee_amount = fee_amount . ceil ( ) as u64 ;
2020-08-31 10:49:44 +02:00
let change_val = selected_amount - outgoing - fee_amount ;
2020-08-06 13:09:39 +02:00
if ! builder . send_all & & ! change_val . is_dust ( ) {
2020-02-07 23:22:28 +01:00
let mut change_output = change_output . unwrap ( ) ;
change_output . value = change_val ;
received + = change_val ;
tx . output . push ( change_output ) ;
2020-08-06 13:09:39 +02:00
} else if builder . send_all & & ! change_val . is_dust ( ) {
2020-02-07 23:22:28 +01:00
// there's only one output, send everything to it
tx . output [ 0 ] . value = change_val ;
// send_all to our address
if self . is_mine ( & tx . output [ 0 ] . script_pubkey ) ? {
received = change_val ;
}
2020-08-10 10:49:34 +02:00
} else if ! builder . send_all & & change_val . is_dust ( ) {
// skip the change output because it's dust, this adds up to the fees
fee_amount + = change_val ;
2020-08-06 13:09:39 +02:00
} else if builder . send_all {
2020-02-07 23:22:28 +01:00
// send_all but the only output would be below dust limit
return Err ( Error ::InsufficientFunds ) ; // TODO: or OutputBelowDustLimit?
}
2020-08-07 15:35:14 +02:00
// sort input/outputs according to the chosen algorithm
2020-08-31 10:49:44 +02:00
builder . ordering . sort_tx ( & mut tx ) ;
2020-02-07 23:22:28 +01:00
let txid = tx . txid ( ) ;
2020-08-13 16:51:27 +02:00
let psbt = self . complete_transaction ( tx , prev_script_pubkeys , builder ) ? ;
2020-02-07 23:22:28 +01:00
2020-08-13 16:51:27 +02:00
let transaction_details = TransactionDetails {
transaction : None ,
txid ,
timestamp : time ::get_timestamp ( ) ,
received ,
2020-08-31 10:49:44 +02:00
sent : selected_amount ,
2020-08-13 16:51:27 +02:00
fees : fee_amount ,
height : None ,
} ;
2020-08-08 12:06:40 +02:00
2020-08-13 16:51:27 +02:00
Ok ( ( psbt , transaction_details ) )
}
2020-02-07 23:22:28 +01:00
2020-09-04 16:29:25 +02:00
/// Bump the fee of a transaction following the options specified in the `builder`
///
/// Return an error if the transaction is already confirmed or doesn't explicitly signal RBF.
///
/// **NOTE**: if the original transaction was made with [`TxBuilder::send_all`], the same
/// option must be enabled when bumping its fees to correctly reduce the only output's value to
/// increase the fees.
///
/// ## Example
///
/// ```no_run
/// # 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: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
/// let txid = Txid::from_str("faff0a466b70f5d5f92bd757a92c1371d4838bdd5bc53a06764e2488e51ce8f8").unwrap();
/// let (psbt, details) = wallet.bump_fee(
/// &txid,
/// TxBuilder::new().fee_rate(FeeRate::from_sat_per_vb(5.0)),
/// )?;
/// // sign and broadcast ...
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
// TODO: option to force addition of an extra output? seems bad for privacy to update the
// change
pub fn bump_fee < Cs : coin_selection ::CoinSelectionAlgorithm > (
& self ,
txid : & Txid ,
builder : TxBuilder < Cs > ,
) -> Result < ( PSBT , TransactionDetails ) , Error > {
let mut details = match self . database . borrow ( ) . get_tx ( & txid , true ) ? {
None = > return Err ( Error ::TransactionNotFound ) ,
Some ( tx ) if tx . transaction . is_none ( ) = > return Err ( Error ::TransactionNotFound ) ,
Some ( tx ) if tx . height . is_some ( ) = > return Err ( Error ::TransactionConfirmed ) ,
Some ( tx ) = > tx ,
} ;
let mut tx = details . transaction . take ( ) . unwrap ( ) ;
if ! tx . input . iter ( ) . any ( | txin | txin . sequence < = 0xFFFFFFFD ) {
return Err ( Error ::IrreplaceableTransaction ) ;
}
2020-02-07 23:22:28 +01:00
2020-08-13 16:51:27 +02:00
// the new tx must "pay for its bandwidth"
let vbytes = tx . get_weight ( ) as f32 / 4.0 ;
let required_feerate = FeeRate ::from_sat_per_vb ( details . fees as f32 / vbytes + 1.0 ) ;
let new_feerate = builder . fee_rate . unwrap_or_default ( ) ;
2020-02-07 23:22:28 +01:00
2020-08-13 16:51:27 +02:00
if new_feerate < required_feerate {
2020-08-31 10:49:44 +02:00
return Err ( Error ::FeeRateTooLow {
required : required_feerate ,
} ) ;
2020-08-13 16:51:27 +02:00
}
let mut fee_difference =
( new_feerate . as_sat_vb ( ) * tx . get_weight ( ) as f32 / 4.0 ) . ceil ( ) as u64 - details . fees ;
2020-08-06 16:56:41 +02:00
2020-08-13 16:51:27 +02:00
if builder . send_all & & tx . output . len ( ) > 1 {
return Err ( Error ::SendAllMultipleOutputs ) ;
}
// find the index of the output that we can update. either the change or the only one if
// it's `send_all`
let updatable_output = if builder . send_all {
0
} else {
let mut change_output = None ;
for ( index , txout ) in tx . output . iter ( ) . enumerate ( ) {
// look for an output that we know and that has the right ScriptType. We use
// `get_deget_descriptor_for` to find what's the ScriptType for `Internal`
// addresses really is, because if there's no change_descriptor it's actually equal
// to "External"
2020-08-12 12:51:50 +02:00
let ( _ , change_type ) = self . get_descriptor_for_script_type ( ScriptType ::Internal ) ;
2020-08-13 16:51:27 +02:00
match self
. database
. borrow ( )
. get_path_from_script_pubkey ( & txout . script_pubkey ) ?
{
Some ( ( script_type , _ ) ) if script_type = = change_type = > {
change_output = Some ( index ) ;
break ;
}
_ = > { }
2020-08-06 16:56:41 +02:00
}
2020-06-30 14:01:38 +02:00
}
2020-02-07 23:22:28 +01:00
2020-08-13 16:51:27 +02:00
// we need a change output, add one here and take into account the extra fees for it
if change_output . is_none ( ) {
let change_script = self . get_change_address ( ) ? ;
let change_txout = TxOut {
script_pubkey : change_script ,
value : 0 ,
} ;
fee_difference + =
( serialize ( & change_txout ) . len ( ) as f32 * new_feerate . as_sat_vb ( ) ) . ceil ( ) as u64 ;
tx . output . push ( change_txout ) ;
2020-08-10 17:16:47 +02:00
2020-08-13 16:51:27 +02:00
change_output = Some ( tx . output . len ( ) - 1 ) ;
}
change_output . unwrap ( )
} ;
// if `builder.utxos` is Some(_) we have to add inputs and we skip down to the last branch
match tx . output [ updatable_output ]
. value
. checked_sub ( fee_difference )
2020-08-10 17:16:47 +02:00
{
2020-08-13 16:51:27 +02:00
Some ( new_value ) if ! new_value . is_dust ( ) & & builder . utxos . is_none ( ) = > {
// try to reduce the "updatable output" amount
tx . output [ updatable_output ] . value = new_value ;
if self . is_mine ( & tx . output [ updatable_output ] . script_pubkey ) ? {
details . received - = fee_difference ;
}
details . fees + = fee_difference ;
2020-08-10 17:16:47 +02:00
}
2020-08-13 16:51:27 +02:00
_ if builder . send_all & & builder . utxos . is_none ( ) = > {
// if the tx is "send_all" it doesn't make sense to either remove the only output
// or add more inputs
return Err ( Error ::InsufficientFunds ) ;
}
_ = > {
// initially always remove the change output
let mut removed_change_output = tx . output . remove ( updatable_output ) ;
if self . is_mine ( & removed_change_output . script_pubkey ) ? {
details . received - = removed_change_output . value ;
}
2020-08-06 16:56:41 +02:00
2020-08-13 16:51:27 +02:00
// we want to add more inputs if:
// - builder.utxos tells us to do so
// - the removed change value is lower than the fee_difference we want to add
let needs_more_inputs =
builder . utxos . is_some ( ) | | removed_change_output . value < = fee_difference ;
let added_amount = if needs_more_inputs {
// TODO: use the right weight instead of the maximum, and only fall-back to it if the
// script is unknown in the database
let input_witness_weight = std ::cmp ::max (
2020-08-12 12:51:50 +02:00
self . get_descriptor_for_script_type ( ScriptType ::Internal )
2020-08-13 16:51:27 +02:00
. 0
. max_satisfaction_weight ( ) ,
2020-08-12 12:51:50 +02:00
self . get_descriptor_for_script_type ( ScriptType ::External )
2020-08-13 16:51:27 +02:00
. 0
. max_satisfaction_weight ( ) ,
) ;
let ( available_utxos , use_all_utxos ) = self . get_available_utxos (
builder . change_policy ,
& builder . utxos ,
& builder . unspendable ,
false ,
) ? ;
let available_utxos = rbf ::filter_available (
self . database . borrow ( ) . deref ( ) ,
available_utxos . into_iter ( ) ,
) ? ;
let coin_selection ::CoinSelectionResult {
txin ,
2020-08-31 10:49:44 +02:00
selected_amount ,
2020-08-13 16:51:27 +02:00
fee_amount ,
} = builder . coin_selection . coin_select (
available_utxos ,
use_all_utxos ,
2020-08-31 10:49:44 +02:00
new_feerate ,
2020-10-07 14:18:50 -07:00
fee_difference . saturating_sub ( removed_change_output . value ) ,
2020-08-13 16:51:27 +02:00
input_witness_weight ,
0.0 ,
) ? ;
fee_difference + = fee_amount . ceil ( ) as u64 ;
// add the new inputs
let ( mut txin , _ ) : ( Vec < _ > , Vec < _ > ) = txin . into_iter ( ) . unzip ( ) ;
// TODO: use tx_builder.sequence ??
// copy the n_sequence from the inputs that were already in the transaction
txin . iter_mut ( )
. for_each ( | i | i . sequence = tx . input [ 0 ] . sequence ) ;
2020-10-07 14:18:50 -07:00
tx . input . extend_from_slice ( & txin ) ;
2020-08-13 16:51:27 +02:00
2020-08-31 10:49:44 +02:00
details . sent + = selected_amount ;
selected_amount
2020-08-13 16:51:27 +02:00
} else {
// otherwise just remove the output and add 0 new coins
0
} ;
match ( removed_change_output . value + added_amount ) . checked_sub ( fee_difference ) {
None = > return Err ( Error ::InsufficientFunds ) ,
Some ( new_value ) if new_value . is_dust ( ) = > {
// the change would be dust, add that to fees
details . fees + = fee_difference + new_value ;
}
Some ( new_value ) = > {
// add the change back
removed_change_output . value = new_value ;
tx . output . push ( removed_change_output ) ;
details . received + = new_value ;
details . fees + = fee_difference ;
}
}
}
2020-02-07 23:22:28 +01:00
} ;
2020-08-13 16:51:27 +02:00
// clear witnesses
for input in & mut tx . input {
input . script_sig = Script ::default ( ) ;
input . witness = vec! [ ] ;
}
// sort input/outputs according to the chosen algorithm
2020-08-31 10:49:44 +02:00
builder . ordering . sort_tx ( & mut tx ) ;
2020-08-13 16:51:27 +02:00
// TODO: check that we are not replacing more than 100 txs from mempool
details . txid = tx . txid ( ) ;
details . timestamp = time ::get_timestamp ( ) ;
let prev_script_pubkeys = tx
. input
. iter ( )
. map ( | txin | {
Ok ( (
2020-10-07 14:18:50 -07:00
txin . previous_output ,
2020-08-13 16:51:27 +02:00
self . database
. borrow ( )
. get_previous_output ( & txin . previous_output ) ? ,
) )
} )
. collect ::< Result < Vec < _ > , Error > > ( ) ?
. into_iter ( )
. filter_map ( | ( outpoint , txout ) | match txout {
Some ( txout ) = > Some ( ( outpoint , txout . script_pubkey ) ) ,
None = > None ,
} )
. collect ( ) ;
let psbt = self . complete_transaction ( tx , prev_script_pubkeys , builder ) ? ;
Ok ( ( psbt , details ) )
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`]
///
/// ## Example
///
/// ```no_run
/// # 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: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
/// # let (psbt, _) = wallet.create_tx(TxBuilder::new())?;
/// let (signed_psbt, finalized) = wallet.sign(psbt, None)?;
2020-09-14 14:25:38 +02:00
/// # Ok::<(), bdk::Error>(())
2020-05-06 17:17:14 +02:00
pub fn sign ( & self , mut psbt : PSBT , assume_height : Option < u32 > ) -> Result < ( PSBT , bool ) , Error > {
2020-02-17 14:22:53 +01:00
// this helps us doing our job later
2020-08-10 17:16:47 +02:00
self . add_input_hd_keypaths ( & mut psbt ) ? ;
2020-02-17 14:22:53 +01:00
2020-08-17 12:10:51 +02:00
for signer in self
. signers
. signers ( )
. iter ( )
. chain ( self . change_signers . signers ( ) . iter ( ) )
{
2020-08-17 23:50:50 +02:00
if signer . sign_whole_tx ( ) {
signer . sign ( & mut psbt , None ) ? ;
} else {
for index in 0 .. psbt . inputs . len ( ) {
signer . sign ( & mut psbt , Some ( index ) ) ? ;
}
2020-02-07 23:22:28 +01:00
}
}
// attempt to finalize
2020-08-31 10:49:44 +02:00
self . finalize_psbt ( psbt , assume_height )
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
2020-02-07 23:22:28 +01:00
pub fn policies ( & self , script_type : ScriptType ) -> Result < Option < Policy > , Error > {
match ( script_type , self . change_descriptor . as_ref ( ) ) {
2020-08-12 12:51:50 +02:00
( ScriptType ::External , _ ) = > {
Ok ( self . descriptor . extract_policy ( Arc ::clone ( & self . signers ) ) ? )
}
2020-02-07 23:22:28 +01:00
( ScriptType ::Internal , None ) = > Ok ( None ) ,
2020-08-12 12:51:50 +02:00
( ScriptType ::Internal , Some ( desc ) ) = > {
Ok ( desc . extract_policy ( Arc ::clone ( & self . change_signers ) ) ? )
}
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
2020-05-10 17:42:02 +02:00
pub fn public_descriptor (
& self ,
script_type : ScriptType ,
) -> Result < Option < ExtendedDescriptor > , Error > {
match ( script_type , self . change_descriptor . as_ref ( ) ) {
2020-08-12 12:51:50 +02:00
( ScriptType ::External , _ ) = > Ok ( Some ( self . descriptor . clone ( ) ) ) ,
2020-05-10 17:42:02 +02:00
( ScriptType ::Internal , None ) = > Ok ( None ) ,
2020-08-12 12:51:50 +02:00
( ScriptType ::Internal , Some ( desc ) ) = > Ok ( Some ( desc . clone ( ) ) ) ,
2020-05-10 17:42:02 +02:00
}
}
2020-09-04 16:29:25 +02:00
/// Try to finalize a PSBT
2020-05-17 18:01:52 +02:00
pub fn finalize_psbt (
& self ,
2020-08-31 10:49:44 +02:00
mut psbt : PSBT ,
2020-05-17 18:01:52 +02:00
assume_height : Option < u32 > ,
2020-08-31 10:49:44 +02:00
) -> Result < ( PSBT , bool ) , Error > {
2020-05-17 18:01:52 +02:00
let mut tx = psbt . global . unsigned_tx . clone ( ) ;
2020-08-12 12:51:50 +02:00
for ( n , ( input , psbt_input ) ) in tx . input . iter_mut ( ) . zip ( psbt . inputs . iter ( ) ) . enumerate ( ) {
2020-05-17 18:01:52 +02:00
// if the height is None in the database it means it's still unconfirmed, so consider
// that as a very high value
let create_height = self
. database
. borrow ( )
. get_tx ( & input . previous_output . txid , false ) ?
2020-10-07 14:18:50 -07:00
. map ( | tx | tx . height . unwrap_or ( std ::u32 ::MAX ) ) ;
2020-05-17 18:01:52 +02:00
let current_height = assume_height . or ( self . current_height ) ;
debug! (
" Input #{} - {}, using `create_height` = {:?}, `current_height` = {:?} " ,
n , input . previous_output , create_height , current_height
) ;
2020-08-12 12:51:50 +02:00
// - Try to derive the descriptor by looking at the txout. If it's in our database, we
// know exactly which `script_type` 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 `hd_keypaths`,
// `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 = if let Some ( desc ) = psbt
. get_utxo_for ( n )
. map ( | txout | self . get_descriptor_for_txout ( & txout ) )
. transpose ( ) ?
. flatten ( )
{
desc
} else if let Some ( desc ) = self
. descriptor
. derive_from_psbt_input ( psbt_input , psbt . get_utxo_for ( n ) )
{
desc
} else if let Some ( desc ) = self
. change_descriptor
. as_ref ( )
. and_then ( | desc | desc . derive_from_psbt_input ( psbt_input , psbt . get_utxo_for ( n ) ) )
{
desc
} else {
debug! ( " Couldn't find the right derived descriptor for input {} " , n ) ;
2020-08-31 10:49:44 +02:00
return Ok ( ( psbt , false ) ) ;
2020-08-12 12:51:50 +02:00
} ;
2020-05-17 18:01:52 +02:00
2020-08-12 12:51:50 +02:00
match desc . satisfy (
input ,
(
psbt_input . clone ( ) ,
After ::new ( current_height , false ) ,
Older ::new ( current_height , create_height , false ) ,
) ,
) {
2020-05-17 18:01:52 +02:00
Ok ( _ ) = > continue ,
Err ( e ) = > {
debug! ( " satisfy error {:?} for input {} " , e , n ) ;
2020-08-31 10:49:44 +02:00
return Ok ( ( psbt , false ) ) ;
2020-05-17 18:01:52 +02:00
}
}
}
// consume tx to extract its input's script_sig and witnesses and move them into the psbt
for ( input , psbt_input ) in tx . input . into_iter ( ) . zip ( psbt . inputs . iter_mut ( ) ) {
psbt_input . final_script_sig = Some ( input . script_sig ) ;
psbt_input . final_script_witness = Some ( input . witness ) ;
}
2020-08-31 10:49:44 +02:00
Ok ( ( psbt , true ) )
2020-05-17 18:01:52 +02:00
}
2020-02-07 23:22:28 +01:00
// Internals
2020-08-12 12:51:50 +02:00
fn get_descriptor_for_script_type (
& self ,
script_type : ScriptType ,
) -> ( & ExtendedDescriptor , ScriptType ) {
2020-10-07 14:18:50 -07:00
match script_type {
2020-08-06 18:11:07 +02:00
ScriptType ::Internal if self . change_descriptor . is_some ( ) = > (
self . change_descriptor . as_ref ( ) . unwrap ( ) ,
ScriptType ::Internal ,
) ,
_ = > ( & self . descriptor , ScriptType ::External ) ,
2020-10-07 14:18:50 -07:00
}
2020-02-07 23:22:28 +01:00
}
2020-08-12 12:51:50 +02:00
fn get_descriptor_for_txout ( & self , txout : & TxOut ) -> Result < Option < ExtendedDescriptor > , Error > {
Ok ( self
. database
. borrow ( )
. get_path_from_script_pubkey ( & txout . script_pubkey ) ?
. map ( | ( script_type , child ) | ( self . get_descriptor_for_script_type ( script_type ) . 0 , child ) )
. map ( | ( desc , child ) | desc . derive ( & [ ChildNumber ::from_normal_idx ( child ) . unwrap ( ) ] ) ) )
2020-02-07 23:22:28 +01:00
}
fn get_change_address ( & self ) -> Result < Script , Error > {
2020-08-12 12:51:50 +02:00
let ( desc , script_type ) = self . get_descriptor_for_script_type ( ScriptType ::Internal ) ;
2020-08-06 18:11:07 +02:00
let index = self . fetch_and_increment_index ( script_type ) ? ;
2020-08-12 12:51:50 +02:00
Ok ( desc
. derive ( & [ ChildNumber ::from_normal_idx ( index ) . unwrap ( ) ] )
. script_pubkey ( ) )
2020-08-06 18:11:07 +02:00
}
fn fetch_and_increment_index ( & self , script_type : ScriptType ) -> Result < u32 , Error > {
2020-08-12 12:51:50 +02:00
let ( descriptor , script_type ) = self . get_descriptor_for_script_type ( script_type ) ;
2020-08-06 18:11:07 +02:00
let index = match descriptor . is_fixed ( ) {
true = > 0 ,
false = > self
. database
. borrow_mut ( )
. increment_last_index ( script_type ) ? ,
2020-02-07 23:22:28 +01:00
} ;
2020-08-06 18:11:07 +02:00
if self
2020-02-07 23:22:28 +01:00
. database
2020-08-06 18:11:07 +02:00
. borrow ( )
. get_script_pubkey_from_path ( script_type , index ) ?
. is_none ( )
{
self . cache_addresses ( script_type , index , CACHE_ADDR_BATCH_SIZE ) ? ;
}
2020-02-07 23:22:28 +01:00
2020-08-15 23:21:13 +02:00
let hd_keypaths = descriptor . get_hd_keypaths ( index ) ? ;
let script = descriptor
. derive ( & [ ChildNumber ::from_normal_idx ( index ) . unwrap ( ) ] )
. script_pubkey ( ) ;
for validator in & self . address_validators {
validator . validate ( script_type , & hd_keypaths , & script ) ? ;
}
2020-08-06 18:11:07 +02:00
Ok ( index )
}
fn cache_addresses (
& self ,
script_type : ScriptType ,
from : u32 ,
mut count : u32 ,
) -> Result < ( ) , Error > {
2020-08-12 12:51:50 +02:00
let ( descriptor , script_type ) = self . get_descriptor_for_script_type ( script_type ) ;
2020-08-06 18:11:07 +02:00
if descriptor . is_fixed ( ) {
if from > 0 {
return Ok ( ( ) ) ;
}
count = 1 ;
}
let mut address_batch = self . database . borrow ( ) . begin_batch ( ) ;
let start_time = time ::Instant ::new ( ) ;
for i in from .. ( from + count ) {
address_batch . set_script_pubkey (
2020-08-12 12:51:50 +02:00
& descriptor
. derive ( & [ ChildNumber ::from_normal_idx ( i ) . unwrap ( ) ] )
. script_pubkey ( ) ,
2020-08-06 18:11:07 +02:00
script_type ,
i ,
) ? ;
}
info! (
" Derivation of {} addresses from {} took {} ms " ,
count ,
from ,
start_time . elapsed ( ) . as_millis ( )
) ;
self . database . borrow_mut ( ) . commit_batch ( address_batch ) ? ;
Ok ( ( ) )
2020-02-07 23:22:28 +01:00
}
fn get_available_utxos (
& self ,
2020-08-07 19:40:13 +02:00
change_policy : tx_builder ::ChangeSpendPolicy ,
2020-02-07 23:22:28 +01:00
utxo : & Option < Vec < OutPoint > > ,
unspendable : & Option < Vec < OutPoint > > ,
send_all : bool ,
) -> Result < ( Vec < UTXO > , bool ) , Error > {
let unspendable_set = match unspendable {
None = > HashSet ::new ( ) ,
2020-10-07 14:18:50 -07:00
Some ( vec ) = > vec . iter ( ) . collect ( ) ,
2020-02-07 23:22:28 +01:00
} ;
match utxo {
// with manual coin selection we always want to spend all the selected utxos, no matter
// what (even if they are marked as unspendable)
Some ( raw_utxos ) = > {
2020-08-07 19:40:13 +02:00
let full_utxos = raw_utxos
2020-02-07 23:22:28 +01:00
. iter ( )
2020-08-07 19:40:13 +02:00
. map ( | u | self . database . borrow ( ) . get_utxo ( & u ) )
. collect ::< Result < Vec < _ > , _ > > ( ) ? ;
2020-02-07 23:22:28 +01:00
if ! full_utxos . iter ( ) . all ( | u | u . is_some ( ) ) {
return Err ( Error ::UnknownUTXO ) ;
}
Ok ( ( full_utxos . into_iter ( ) . map ( | x | x . unwrap ( ) ) . collect ( ) , true ) )
}
2020-08-07 19:40:13 +02:00
// otherwise limit ourselves to the spendable utxos for the selected policy, and the `send_all` setting
None = > {
let utxos = self . list_unspent ( ) ? . into_iter ( ) ;
let utxos = change_policy . filter_utxos ( utxos ) . into_iter ( ) ;
Ok ( (
utxos
. filter ( | u | ! unspendable_set . contains ( & u . outpoint ) )
. collect ( ) ,
send_all ,
) )
}
2020-02-07 23:22:28 +01:00
}
}
2020-08-13 16:51:27 +02:00
fn complete_transaction < Cs : coin_selection ::CoinSelectionAlgorithm > (
& self ,
tx : Transaction ,
prev_script_pubkeys : HashMap < OutPoint , Script > ,
builder : TxBuilder < Cs > ,
) -> Result < PSBT , Error > {
let mut psbt = PSBT ::from_unsigned_tx ( tx ) ? ;
// add metadata for the inputs
for ( psbt_input , input ) in psbt
. inputs
. iter_mut ( )
. zip ( psbt . global . unsigned_tx . input . iter ( ) )
{
let prev_script = match prev_script_pubkeys . get ( & input . previous_output ) {
Some ( prev_script ) = > prev_script ,
None = > continue ,
} ;
// Add sighash, default is obviously "ALL"
psbt_input . sighash_type = builder . sighash . or ( Some ( SigHashType ::All ) ) ;
// Try to find the prev_script in our db to figure out if this is internal or external,
// and the derivation index
let ( script_type , child ) = match self
. database
. borrow ( )
. get_path_from_script_pubkey ( & prev_script ) ?
{
Some ( x ) = > x ,
None = > continue ,
} ;
2020-08-12 12:51:50 +02:00
let ( desc , _ ) = self . get_descriptor_for_script_type ( script_type ) ;
2020-08-13 16:51:27 +02:00
psbt_input . hd_keypaths = desc . get_hd_keypaths ( child ) ? ;
2020-08-12 12:51:50 +02:00
let derived_descriptor = desc . derive ( & [ ChildNumber ::from_normal_idx ( child ) . unwrap ( ) ] ) ;
2020-08-13 16:51:27 +02:00
psbt_input . redeem_script = derived_descriptor . psbt_redeem_script ( ) ;
psbt_input . witness_script = derived_descriptor . psbt_witness_script ( ) ;
let prev_output = input . previous_output ;
if let Some ( prev_tx ) = self . database . borrow ( ) . get_raw_tx ( & prev_output . txid ) ? {
if derived_descriptor . is_witness ( ) {
psbt_input . witness_utxo =
Some ( prev_tx . output [ prev_output . vout as usize ] . clone ( ) ) ;
}
if ! derived_descriptor . is_witness ( ) | | builder . force_non_witness_utxo {
psbt_input . non_witness_utxo = Some ( prev_tx ) ;
}
}
}
// probably redundant but it doesn't hurt...
self . add_input_hd_keypaths ( & mut psbt ) ? ;
// add metadata for the outputs
for ( psbt_output , tx_output ) in psbt
. outputs
. iter_mut ( )
. zip ( psbt . global . unsigned_tx . output . iter ( ) )
{
if let Some ( ( script_type , child ) ) = self
. database
. borrow ( )
. get_path_from_script_pubkey ( & tx_output . script_pubkey ) ?
{
2020-08-12 12:51:50 +02:00
let ( desc , _ ) = self . get_descriptor_for_script_type ( script_type ) ;
2020-08-13 16:51:27 +02:00
psbt_output . hd_keypaths = desc . get_hd_keypaths ( child ) ? ;
}
}
Ok ( psbt )
}
2020-08-10 17:16:47 +02:00
fn add_input_hd_keypaths ( & self , psbt : & mut PSBT ) -> Result < ( ) , Error > {
2020-06-30 14:01:38 +02:00
let mut input_utxos = Vec ::with_capacity ( psbt . inputs . len ( ) ) ;
for n in 0 .. psbt . inputs . len ( ) {
input_utxos . push ( psbt . get_utxo_for ( n ) . clone ( ) ) ;
}
// try to add hd_keypaths if we've already seen the output
for ( psbt_input , out ) in psbt . inputs . iter_mut ( ) . zip ( input_utxos . iter ( ) ) {
if let Some ( out ) = out {
2020-08-06 18:11:07 +02:00
if let Some ( ( script_type , child ) ) = self
2020-06-30 14:01:38 +02:00
. database
. borrow ( )
2020-08-06 18:11:07 +02:00
. get_path_from_script_pubkey ( & out . script_pubkey ) ?
{
debug! ( " Found descriptor {:?}/{} " , script_type , child ) ;
// merge hd_keypaths
2020-08-12 12:51:50 +02:00
let ( desc , _ ) = self . get_descriptor_for_script_type ( script_type ) ;
2020-08-06 18:11:07 +02:00
let mut hd_keypaths = desc . get_hd_keypaths ( child ) ? ;
psbt_input . hd_keypaths . append ( & mut hd_keypaths ) ;
}
2020-06-30 14:01:38 +02:00
}
}
Ok ( ( ) )
}
2020-02-07 23:22:28 +01:00
}
2020-05-03 16:15:11 +02:00
impl < B , D > Wallet < B , D >
2020-02-07 23:22:28 +01:00
where
2020-09-09 18:17:49 +02:00
B : Blockchain ,
2020-02-07 23:22:28 +01:00
D : BatchDatabase ,
{
2020-09-04 16:29:25 +02:00
/// Create a new "online" wallet
2020-07-20 15:51:57 +02:00
#[ maybe_async ]
2020-09-18 16:31:03 +02:00
pub fn new < E : ToWalletDescriptor > (
descriptor : E ,
change_descriptor : Option < E > ,
2020-02-07 23:22:28 +01:00
network : Network ,
2020-08-06 10:44:40 +02:00
database : D ,
client : B ,
2020-02-15 21:27:51 +01:00
) -> Result < Self , Error > {
2020-08-06 10:44:40 +02:00
let mut wallet = Self ::new_offline ( descriptor , change_descriptor , network , database ) ? ;
2020-02-15 21:27:51 +01:00
2020-08-06 10:44:40 +02:00
wallet . current_height = Some ( maybe_await! ( client . get_height ( ) ) ? as u32 ) ;
2020-09-09 18:17:49 +02:00
wallet . client = Some ( client ) ;
2020-05-06 17:17:14 +02:00
2020-08-06 10:44:40 +02:00
Ok ( wallet )
2020-02-07 23:22:28 +01:00
}
2020-09-04 16:29:25 +02:00
/// Sync the internal database with the blockchain
2020-07-20 15:51:57 +02:00
#[ maybe_async ]
2020-08-25 16:07:26 +02:00
pub fn sync < P : 'static + Progress > (
& self ,
progress_update : P ,
max_address_param : Option < u32 > ,
) -> Result < ( ) , Error > {
2020-08-06 18:11:07 +02:00
debug! ( " Begin sync... " ) ;
2020-02-07 23:22:28 +01:00
2020-08-10 10:49:34 +02:00
let mut run_setup = false ;
2020-08-06 18:11:07 +02:00
let max_address = match self . descriptor . is_fixed ( ) {
true = > 0 ,
false = > max_address_param . unwrap_or ( CACHE_ADDR_BATCH_SIZE ) ,
} ;
if self
2020-02-07 23:22:28 +01:00
. database
. borrow ( )
2020-08-06 18:11:07 +02:00
. get_script_pubkey_from_path ( ScriptType ::External , max_address ) ?
. is_none ( )
{
2020-08-10 10:49:34 +02:00
run_setup = true ;
2020-08-06 18:11:07 +02:00
self . cache_addresses ( ScriptType ::External , 0 , max_address ) ? ;
}
2020-02-07 23:22:28 +01:00
2020-08-06 18:11:07 +02:00
if let Some ( change_descriptor ) = & self . change_descriptor {
let max_address = match change_descriptor . is_fixed ( ) {
true = > 0 ,
false = > max_address_param . unwrap_or ( CACHE_ADDR_BATCH_SIZE ) ,
} ;
2020-02-07 23:22:28 +01:00
2020-08-06 18:11:07 +02:00
if self
. database
. borrow ( )
2020-10-07 14:18:50 -07:00
. get_script_pubkey_from_path ( ScriptType ::Internal , max_address . saturating_sub ( 1 ) ) ?
2020-08-06 18:11:07 +02:00
. is_none ( )
{
2020-08-10 10:49:34 +02:00
run_setup = true ;
2020-08-06 18:11:07 +02:00
self . cache_addresses ( ScriptType ::Internal , 0 , max_address ) ? ;
2020-02-07 23:22:28 +01:00
}
}
2020-08-10 17:16:47 +02:00
// TODO: what if i generate an address first and cache some addresses?
// TODO: we should sync if generating an address triggers a new batch to be stored
2020-08-10 10:49:34 +02:00
if run_setup {
2020-09-09 18:17:49 +02:00
maybe_await! ( self . client . as_ref ( ) . ok_or ( Error ::OfflineClient ) ? . setup (
2020-08-10 10:49:34 +02:00
None ,
self . database . borrow_mut ( ) . deref_mut ( ) ,
2020-08-25 16:07:26 +02:00
progress_update ,
2020-08-10 10:49:34 +02:00
) )
} else {
2020-09-09 18:17:49 +02:00
maybe_await! ( self . client . as_ref ( ) . ok_or ( Error ::OfflineClient ) ? . sync (
2020-08-10 10:49:34 +02:00
None ,
self . database . borrow_mut ( ) . deref_mut ( ) ,
2020-08-25 16:07:26 +02:00
progress_update ,
2020-08-10 10:49:34 +02:00
) )
}
2020-02-07 23:22:28 +01:00
}
2020-09-04 16:29:25 +02:00
/// Return a reference to the internal blockchain client
2020-09-09 18:17:49 +02:00
pub fn client ( & self ) -> Option < & B > {
self . client . as_ref ( )
2020-08-08 12:06:40 +02:00
}
2020-09-04 16:29:25 +02:00
/// Broadcast a transaction to the network
2020-07-20 15:51:57 +02:00
#[ maybe_async ]
2020-07-15 18:49:24 +02:00
pub fn broadcast ( & self , tx : Transaction ) -> Result < Txid , Error > {
2020-09-09 18:17:49 +02:00
maybe_await! ( self
. client
. as_ref ( )
. ok_or ( Error ::OfflineClient ) ?
. broadcast ( & tx ) ) ? ;
2020-02-07 23:22:28 +01:00
2020-05-17 18:01:52 +02:00
Ok ( tx . txid ( ) )
2020-02-07 23:22:28 +01:00
}
}
2020-08-06 18:11:07 +02:00
#[ cfg(test) ]
mod test {
2020-08-12 12:51:50 +02:00
use std ::str ::FromStr ;
2020-08-06 18:11:07 +02:00
2020-08-12 12:51:50 +02:00
use bitcoin ::Network ;
2020-08-10 17:16:47 +02:00
2020-08-06 18:11:07 +02:00
use crate ::database ::memory ::MemoryDatabase ;
use crate ::database ::Database ;
use crate ::types ::ScriptType ;
use super ::* ;
#[ test ]
fn test_cache_addresses_fixed ( ) {
let db = MemoryDatabase ::new ( ) ;
let wallet : OfflineWallet < _ > = Wallet ::new_offline (
" wpkh(L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6) " ,
None ,
Network ::Testnet ,
db ,
)
. unwrap ( ) ;
assert_eq! (
wallet . get_new_address ( ) . unwrap ( ) . to_string ( ) ,
" tb1qj08ys4ct2hzzc2hcz6h2hgrvlmsjynaw43s835 "
) ;
assert_eq! (
wallet . get_new_address ( ) . unwrap ( ) . to_string ( ) ,
" tb1qj08ys4ct2hzzc2hcz6h2hgrvlmsjynaw43s835 "
) ;
assert! ( wallet
. database
. borrow_mut ( )
. get_script_pubkey_from_path ( ScriptType ::External , 0 )
. unwrap ( )
. is_some ( ) ) ;
assert! ( wallet
. database
. borrow_mut ( )
. get_script_pubkey_from_path ( ScriptType ::Internal , 0 )
. unwrap ( )
. is_none ( ) ) ;
}
#[ test ]
fn test_cache_addresses ( ) {
let db = MemoryDatabase ::new ( ) ;
let wallet : OfflineWallet < _ > = Wallet ::new_offline ( " wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*) " , None , Network ::Testnet , db ) . unwrap ( ) ;
assert_eq! (
wallet . get_new_address ( ) . unwrap ( ) . to_string ( ) ,
" tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a "
) ;
assert_eq! (
wallet . get_new_address ( ) . unwrap ( ) . to_string ( ) ,
" tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7 "
) ;
assert! ( wallet
. database
. borrow_mut ( )
. get_script_pubkey_from_path ( ScriptType ::External , CACHE_ADDR_BATCH_SIZE - 1 )
. unwrap ( )
. is_some ( ) ) ;
assert! ( wallet
. database
. borrow_mut ( )
. get_script_pubkey_from_path ( ScriptType ::External , CACHE_ADDR_BATCH_SIZE )
. unwrap ( )
. is_none ( ) ) ;
}
#[ test ]
fn test_cache_addresses_refill ( ) {
let db = MemoryDatabase ::new ( ) ;
let wallet : OfflineWallet < _ > = Wallet ::new_offline ( " wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*) " , None , Network ::Testnet , db ) . unwrap ( ) ;
assert_eq! (
wallet . get_new_address ( ) . unwrap ( ) . to_string ( ) ,
" tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a "
) ;
assert! ( wallet
. database
. borrow_mut ( )
. get_script_pubkey_from_path ( ScriptType ::External , CACHE_ADDR_BATCH_SIZE - 1 )
. unwrap ( )
. is_some ( ) ) ;
for _ in 0 .. CACHE_ADDR_BATCH_SIZE {
wallet . get_new_address ( ) . unwrap ( ) ;
}
assert! ( wallet
. database
. borrow_mut ( )
. get_script_pubkey_from_path ( ScriptType ::External , CACHE_ADDR_BATCH_SIZE * 2 - 1 )
. unwrap ( )
. is_some ( ) ) ;
}
2020-08-10 17:16:47 +02:00
2020-08-15 23:21:13 +02:00
pub ( crate ) fn get_test_wpkh ( ) -> & 'static str {
2020-08-10 17:16:47 +02:00
" wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW) "
}
2020-08-15 23:21:13 +02:00
pub ( crate ) fn get_test_single_sig_csv ( ) -> & 'static str {
2020-08-10 17:16:47 +02:00
// and(pk(Alice),older(6))
" wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(6))) "
}
2020-08-15 23:21:13 +02:00
pub ( crate ) fn get_test_single_sig_cltv ( ) -> & 'static str {
2020-08-10 17:16:47 +02:00
// and(pk(Alice),after(100000))
" wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(100000))) "
}
2020-08-15 23:21:13 +02:00
pub ( crate ) fn get_funded_wallet (
2020-08-10 17:16:47 +02:00
descriptor : & str ,
) -> (
OfflineWallet < MemoryDatabase > ,
2020-08-12 12:51:50 +02:00
( String , Option < String > ) ,
2020-08-10 17:16:47 +02:00
bitcoin ::Txid ,
) {
let descriptors = testutils! ( @ descriptors ( descriptor ) ) ;
let wallet : OfflineWallet < _ > = Wallet ::new_offline (
2020-08-12 12:51:50 +02:00
& descriptors . 0 ,
2020-08-10 17:16:47 +02:00
None ,
Network ::Regtest ,
MemoryDatabase ::new ( ) ,
)
. unwrap ( ) ;
let txid = wallet . database . borrow_mut ( ) . received_tx (
testutils! {
2020-08-13 16:51:27 +02:00
@ tx ( ( @ external descriptors , 0 ) = > 50_000 ) ( @ confirmations 1 )
2020-08-10 17:16:47 +02:00
} ,
2020-08-13 16:51:27 +02:00
Some ( 100 ) ,
2020-08-10 17:16:47 +02:00
) ;
( wallet , descriptors , txid )
}
2020-08-13 16:51:27 +02:00
macro_rules ! assert_fee_rate {
( $tx :expr , $fees :expr , $fee_rate :expr $( , @ dust_change $( $dust_change :expr ) * ) * $( , @ add_signature $( $add_signature :expr ) * ) * ) = > ( {
let mut tx = $tx . clone ( ) ;
$(
$( $add_signature ) *
for txin in & mut tx . input {
txin . witness . push ( [ 0x00 ; 108 ] . to_vec ( ) ) ; // fake signature
}
) *
#[ allow(unused_mut) ]
#[ allow(unused_assignments) ]
let mut dust_change = false ;
$(
$( $dust_change ) *
dust_change = true ;
) *
let tx_fee_rate = $fees as f32 / ( tx . get_weight ( ) as f32 / 4.0 ) ;
let fee_rate = $fee_rate . as_sat_vb ( ) ;
if ! dust_change {
assert! ( ( tx_fee_rate - fee_rate ) . abs ( ) < 0.5 , format! ( " Expected fee rate of {} , the tx has {} " , fee_rate , tx_fee_rate ) ) ;
} else {
assert! ( tx_fee_rate > = fee_rate , format! ( " Expected fee rate of at least {} , the tx has {} " , fee_rate , tx_fee_rate ) ) ;
}
} ) ;
}
2020-08-10 17:16:47 +02:00
#[ test ]
#[ should_panic(expected = " NoAddressees " ) ]
2020-08-31 10:49:44 +02:00
fn test_create_tx_empty_recipients ( ) {
2020-08-10 17:16:47 +02:00
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
wallet
2020-08-31 10:49:44 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ ] ) . version ( 0 ) )
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
}
#[ test ]
#[ should_panic(expected = " Invalid version `0` " ) ]
fn test_create_tx_version_0 ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 25_000 ) ] ) . version ( 0 ) )
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
}
#[ test ]
#[ should_panic(
expected = " TxBuilder requested version `1`, but at least `2` is needed to use OP_CSV "
) ]
fn test_create_tx_version_1_csv ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_single_sig_csv ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 25_000 ) ] ) . version ( 1 ) )
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
}
#[ test ]
fn test_create_tx_custom_version ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 25_000 ) ] ) . version ( 42 ) )
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
assert_eq! ( psbt . global . unsigned_tx . version , 42 ) ;
}
#[ test ]
fn test_create_tx_default_locktime ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ (
addr . script_pubkey ( ) ,
25_000 ,
) ] ) )
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
assert_eq! ( psbt . global . unsigned_tx . lock_time , 0 ) ;
}
#[ test ]
fn test_create_tx_default_locktime_cltv ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_single_sig_cltv ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ (
addr . script_pubkey ( ) ,
25_000 ,
) ] ) )
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
assert_eq! ( psbt . global . unsigned_tx . lock_time , 100_000 ) ;
}
#[ test ]
fn test_create_tx_custom_locktime ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx (
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 25_000 ) ] ) . nlocktime ( 630_000 ) ,
)
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
assert_eq! ( psbt . global . unsigned_tx . lock_time , 630_000 ) ;
}
#[ test ]
fn test_create_tx_custom_locktime_compatible_with_cltv ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_single_sig_cltv ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx (
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 25_000 ) ] ) . nlocktime ( 630_000 ) ,
)
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
assert_eq! ( psbt . global . unsigned_tx . lock_time , 630_000 ) ;
}
#[ test ]
#[ should_panic(
expected = " TxBuilder requested timelock of `50000`, but at least `100000` is required to spend from this script "
) ]
fn test_create_tx_custom_locktime_incompatible_with_cltv ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_single_sig_cltv ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
wallet
2020-09-04 15:45:11 +02:00
. create_tx (
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 25_000 ) ] ) . nlocktime ( 50000 ) ,
)
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
}
#[ test ]
fn test_create_tx_no_rbf_csv ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_single_sig_csv ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ (
addr . script_pubkey ( ) ,
25_000 ,
) ] ) )
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
assert_eq! ( psbt . global . unsigned_tx . input [ 0 ] . sequence , 6 ) ;
}
#[ test ]
fn test_create_tx_with_default_rbf_csv ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_single_sig_csv ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx (
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 25_000 ) ] ) . enable_rbf ( ) ,
)
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
assert_eq! ( psbt . global . unsigned_tx . input [ 0 ] . sequence , 0xFFFFFFFD ) ;
}
#[ test ]
#[ should_panic(
expected = " Cannot enable RBF with nSequence `3`, since at least `6` is required to spend with OP_CSV "
) ]
fn test_create_tx_with_custom_rbf_csv ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_single_sig_csv ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
wallet
2020-09-04 15:45:11 +02:00
. create_tx (
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 25_000 ) ] )
. enable_rbf_with_sequence ( 3 ) ,
)
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
}
#[ test ]
fn test_create_tx_no_rbf_cltv ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_single_sig_cltv ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ (
addr . script_pubkey ( ) ,
25_000 ,
) ] ) )
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
assert_eq! ( psbt . global . unsigned_tx . input [ 0 ] . sequence , 0xFFFFFFFE ) ;
}
#[ test ]
2020-09-04 15:45:11 +02:00
#[ should_panic(expected = " Cannot enable RBF with a nSequence >= 0xFFFFFFFE " ) ]
2020-08-10 17:16:47 +02:00
fn test_create_tx_invalid_rbf_sequence ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
wallet
. create_tx (
2020-09-04 15:45:11 +02:00
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 25_000 ) ] )
2020-08-10 17:16:47 +02:00
. enable_rbf_with_sequence ( 0xFFFFFFFE ) ,
)
. unwrap ( ) ;
}
#[ test ]
fn test_create_tx_custom_rbf_sequence ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
. create_tx (
2020-09-04 15:45:11 +02:00
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 25_000 ) ] )
2020-08-10 17:16:47 +02:00
. enable_rbf_with_sequence ( 0xDEADBEEF ) ,
)
. unwrap ( ) ;
assert_eq! ( psbt . global . unsigned_tx . input [ 0 ] . sequence , 0xDEADBEEF ) ;
}
#[ test ]
fn test_create_tx_default_sequence ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ (
addr . script_pubkey ( ) ,
25_000 ,
) ] ) )
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
assert_eq! ( psbt . global . unsigned_tx . input [ 0 ] . sequence , 0xFFFFFFFF ) ;
}
#[ test ]
#[ should_panic(
expected = " The `change_policy` can be set only if the wallet has a change_descriptor "
) ]
fn test_create_tx_change_policy_no_internal ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
wallet
. create_tx (
2020-09-04 15:45:11 +02:00
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 25_000 ) ] )
. do_not_spend_change ( ) ,
2020-08-10 17:16:47 +02:00
)
. unwrap ( ) ;
}
#[ test ]
#[ should_panic(expected = " SendAllMultipleOutputs " ) ]
fn test_create_tx_send_all_multiple_outputs ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
wallet
. create_tx (
2020-09-04 15:45:11 +02:00
TxBuilder ::with_recipients ( vec! [
( addr . script_pubkey ( ) , 25_000 ) ,
( addr . script_pubkey ( ) , 10_000 ) ,
] )
. send_all ( ) ,
2020-08-10 17:16:47 +02:00
)
. unwrap ( ) ;
}
#[ test ]
fn test_create_tx_send_all ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , details ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 0 ) ] ) . send_all ( ) )
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
assert_eq! ( psbt . global . unsigned_tx . output . len ( ) , 1 ) ;
assert_eq! (
psbt . global . unsigned_tx . output [ 0 ] . value ,
50_000 - details . fees
) ;
}
2020-08-13 16:51:27 +02:00
#[ test ]
fn test_create_tx_default_fee_rate ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , details ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 0 ) ] ) . send_all ( ) )
2020-08-13 16:51:27 +02:00
. unwrap ( ) ;
assert_fee_rate! ( psbt . extract_tx ( ) , details . fees , FeeRate ::default ( ) , @ add_signature ) ;
}
#[ test ]
fn test_create_tx_custom_fee_rate ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , details ) = wallet
. create_tx (
2020-09-04 15:45:11 +02:00
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 0 ) ] )
2020-08-13 16:51:27 +02:00
. fee_rate ( FeeRate ::from_sat_per_vb ( 5.0 ) )
. send_all ( ) ,
)
. unwrap ( ) ;
assert_fee_rate! ( psbt . extract_tx ( ) , details . fees , FeeRate ::from_sat_per_vb ( 5.0 ) , @ add_signature ) ;
}
2020-08-10 17:16:47 +02:00
#[ test ]
fn test_create_tx_add_change ( ) {
use super ::tx_builder ::TxOrdering ;
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , details ) = wallet
. create_tx (
2020-09-04 15:45:11 +02:00
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 25_000 ) ] )
2020-08-10 17:16:47 +02:00
. ordering ( TxOrdering ::Untouched ) ,
)
. unwrap ( ) ;
assert_eq! ( psbt . global . unsigned_tx . output . len ( ) , 2 ) ;
assert_eq! ( psbt . global . unsigned_tx . output [ 0 ] . value , 25_000 ) ;
assert_eq! (
psbt . global . unsigned_tx . output [ 1 ] . value ,
25_000 - details . fees
) ;
}
#[ test ]
fn test_create_tx_skip_change_dust ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ (
addr . script_pubkey ( ) ,
49_800 ,
) ] ) )
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
assert_eq! ( psbt . global . unsigned_tx . output . len ( ) , 1 ) ;
assert_eq! ( psbt . global . unsigned_tx . output [ 0 ] . value , 49_800 ) ;
}
#[ test ]
#[ should_panic(expected = " InsufficientFunds " ) ]
fn test_create_tx_send_all_dust_amount ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
// very high fee rate, so that the only output would be below dust
wallet
. create_tx (
2020-09-04 15:45:11 +02:00
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 0 ) ] )
2020-08-10 17:16:47 +02:00
. send_all ( )
2020-08-31 10:49:44 +02:00
. fee_rate ( crate ::FeeRate ::from_sat_per_vb ( 453.0 ) ) ,
2020-08-10 17:16:47 +02:00
)
. unwrap ( ) ;
}
#[ test ]
fn test_create_tx_ordering_respected ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , details ) = wallet
. create_tx (
2020-09-04 15:45:11 +02:00
TxBuilder ::with_recipients ( vec! [
( addr . script_pubkey ( ) , 30_000 ) ,
( addr . script_pubkey ( ) , 10_000 ) ,
] )
. ordering ( super ::tx_builder ::TxOrdering ::BIP69Lexicographic ) ,
2020-08-10 17:16:47 +02:00
)
. unwrap ( ) ;
assert_eq! ( psbt . global . unsigned_tx . output . len ( ) , 3 ) ;
assert_eq! (
psbt . global . unsigned_tx . output [ 0 ] . value ,
10_000 - details . fees
) ;
assert_eq! ( psbt . global . unsigned_tx . output [ 1 ] . value , 10_000 ) ;
assert_eq! ( psbt . global . unsigned_tx . output [ 2 ] . value , 30_000 ) ;
}
#[ test ]
fn test_create_tx_default_sighash ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ (
addr . script_pubkey ( ) ,
30_000 ,
) ] ) )
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
assert_eq! ( psbt . inputs [ 0 ] . sighash_type , Some ( bitcoin ::SigHashType ::All ) ) ;
}
#[ test ]
fn test_create_tx_custom_sighash ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
. create_tx (
2020-09-04 15:45:11 +02:00
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 30_000 ) ] )
2020-08-10 17:16:47 +02:00
. sighash ( bitcoin ::SigHashType ::Single ) ,
)
. unwrap ( ) ;
assert_eq! (
psbt . inputs [ 0 ] . sighash_type ,
Some ( bitcoin ::SigHashType ::Single )
) ;
}
#[ test ]
fn test_create_tx_input_hd_keypaths ( ) {
use bitcoin ::util ::bip32 ::{ DerivationPath , Fingerprint } ;
use std ::str ::FromStr ;
2020-09-21 15:44:07 +02:00
let ( wallet , _ , _ ) = get_funded_wallet ( " wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*) " ) ;
2020-08-10 17:16:47 +02:00
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 0 ) ] ) . send_all ( ) )
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
assert_eq! ( psbt . inputs [ 0 ] . hd_keypaths . len ( ) , 1 ) ;
assert_eq! (
psbt . inputs [ 0 ] . hd_keypaths . values ( ) . nth ( 0 ) . unwrap ( ) ,
& (
Fingerprint ::from_str ( " d34db33f " ) . unwrap ( ) ,
DerivationPath ::from_str ( " m/44'/0'/0'/0/0 " ) . unwrap ( )
)
) ;
}
#[ test ]
fn test_create_tx_output_hd_keypaths ( ) {
use bitcoin ::util ::bip32 ::{ DerivationPath , Fingerprint } ;
use std ::str ::FromStr ;
2020-09-21 15:44:07 +02:00
let ( wallet , descriptors , _ ) = get_funded_wallet ( " wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*) " ) ;
2020-08-10 17:16:47 +02:00
// cache some addresses
wallet . get_new_address ( ) . unwrap ( ) ;
let addr = testutils! ( @ external descriptors , 5 ) ;
let ( psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 0 ) ] ) . send_all ( ) )
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
assert_eq! ( psbt . outputs [ 0 ] . hd_keypaths . len ( ) , 1 ) ;
assert_eq! (
psbt . outputs [ 0 ] . hd_keypaths . values ( ) . nth ( 0 ) . unwrap ( ) ,
& (
Fingerprint ::from_str ( " d34db33f " ) . unwrap ( ) ,
DerivationPath ::from_str ( " m/44'/0'/0'/0/5 " ) . unwrap ( )
)
) ;
}
#[ test ]
fn test_create_tx_set_redeem_script_p2sh ( ) {
use bitcoin ::hashes ::hex ::FromHex ;
let ( wallet , _ , _ ) =
get_funded_wallet ( " sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)) " ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 0 ) ] ) . send_all ( ) )
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
assert_eq! (
psbt . inputs [ 0 ] . redeem_script ,
Some ( Script ::from (
Vec ::< u8 > ::from_hex (
" 21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac "
)
. unwrap ( )
) )
) ;
assert_eq! ( psbt . inputs [ 0 ] . witness_script , None ) ;
}
#[ test ]
fn test_create_tx_set_witness_script_p2wsh ( ) {
use bitcoin ::hashes ::hex ::FromHex ;
let ( wallet , _ , _ ) =
get_funded_wallet ( " wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)) " ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 0 ) ] ) . send_all ( ) )
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
assert_eq! ( psbt . inputs [ 0 ] . redeem_script , None ) ;
assert_eq! (
psbt . inputs [ 0 ] . witness_script ,
Some ( Script ::from (
Vec ::< u8 > ::from_hex (
" 21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac "
)
. unwrap ( )
) )
) ;
}
#[ test ]
fn test_create_tx_set_redeem_witness_script_p2wsh_p2sh ( ) {
use bitcoin ::hashes ::hex ::FromHex ;
let ( wallet , _ , _ ) =
get_funded_wallet ( " sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))) " ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 0 ) ] ) . send_all ( ) )
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
let script = Script ::from (
Vec ::< u8 > ::from_hex (
" 21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac " ,
)
. unwrap ( ) ,
) ;
assert_eq! ( psbt . inputs [ 0 ] . redeem_script , Some ( script . to_v0_p2wsh ( ) ) ) ;
assert_eq! ( psbt . inputs [ 0 ] . witness_script , Some ( script ) ) ;
}
#[ test ]
fn test_create_tx_non_witness_utxo ( ) {
let ( wallet , _ , _ ) =
get_funded_wallet ( " sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)) " ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 0 ) ] ) . send_all ( ) )
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
assert! ( psbt . inputs [ 0 ] . non_witness_utxo . is_some ( ) ) ;
assert! ( psbt . inputs [ 0 ] . witness_utxo . is_none ( ) ) ;
}
#[ test ]
fn test_create_tx_only_witness_utxo ( ) {
let ( wallet , _ , _ ) =
get_funded_wallet ( " wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)) " ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 0 ) ] ) . send_all ( ) )
2020-08-10 17:16:47 +02:00
. unwrap ( ) ;
assert! ( psbt . inputs [ 0 ] . non_witness_utxo . is_none ( ) ) ;
assert! ( psbt . inputs [ 0 ] . witness_utxo . is_some ( ) ) ;
}
#[ test ]
fn test_create_tx_both_non_witness_utxo_and_witness_utxo ( ) {
let ( wallet , _ , _ ) =
get_funded_wallet ( " wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)) " ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
. create_tx (
2020-09-04 15:45:11 +02:00
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 0 ) ] )
2020-08-10 17:16:47 +02:00
. force_non_witness_utxo ( )
. send_all ( ) ,
)
. unwrap ( ) ;
assert! ( psbt . inputs [ 0 ] . non_witness_utxo . is_some ( ) ) ;
assert! ( psbt . inputs [ 0 ] . witness_utxo . is_some ( ) ) ;
}
2020-08-13 16:51:27 +02:00
#[ test ]
#[ should_panic(expected = " IrreplaceableTransaction " ) ]
fn test_bump_fee_irreplaceable_tx ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , mut details ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ (
addr . script_pubkey ( ) ,
25_000 ,
) ] ) )
2020-08-13 16:51:27 +02:00
. unwrap ( ) ;
let tx = psbt . extract_tx ( ) ;
let txid = tx . txid ( ) ;
// skip saving the utxos, we know they can't be used anyways
details . transaction = Some ( tx ) ;
wallet . database . borrow_mut ( ) . set_tx ( & details ) . unwrap ( ) ;
wallet . bump_fee ( & txid , TxBuilder ::new ( ) ) . unwrap ( ) ;
}
#[ test ]
#[ should_panic(expected = " TransactionConfirmed " ) ]
fn test_bump_fee_confirmed_tx ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , mut details ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ (
addr . script_pubkey ( ) ,
25_000 ,
) ] ) )
2020-08-13 16:51:27 +02:00
. unwrap ( ) ;
let tx = psbt . extract_tx ( ) ;
let txid = tx . txid ( ) ;
// skip saving the utxos, we know they can't be used anyways
details . transaction = Some ( tx ) ;
details . height = Some ( 42 ) ;
wallet . database . borrow_mut ( ) . set_tx ( & details ) . unwrap ( ) ;
wallet . bump_fee ( & txid , TxBuilder ::new ( ) ) . unwrap ( ) ;
}
#[ test ]
#[ should_panic(expected = " FeeRateTooLow " ) ]
fn test_bump_fee_low_fee_rate ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , mut details ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx (
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 25_000 ) ] ) . enable_rbf ( ) ,
)
2020-08-13 16:51:27 +02:00
. unwrap ( ) ;
let tx = psbt . extract_tx ( ) ;
let txid = tx . txid ( ) ;
// skip saving the utxos, we know they can't be used anyways
details . transaction = Some ( tx ) ;
wallet . database . borrow_mut ( ) . set_tx ( & details ) . unwrap ( ) ;
wallet
. bump_fee (
& txid ,
TxBuilder ::new ( ) . fee_rate ( FeeRate ::from_sat_per_vb ( 1.0 ) ) ,
)
. unwrap ( ) ;
}
#[ test ]
fn test_bump_fee_reduce_change ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = Address ::from_str ( " 2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX " ) . unwrap ( ) ;
let ( psbt , mut original_details ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx (
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 25_000 ) ] ) . enable_rbf ( ) ,
)
2020-08-13 16:51:27 +02:00
. unwrap ( ) ;
let mut tx = psbt . extract_tx ( ) ;
let txid = tx . txid ( ) ;
// skip saving the new utxos, we know they can't be used anyways
for txin in & mut tx . input {
txin . witness . push ( [ 0x00 ; 108 ] . to_vec ( ) ) ; // fake signature
wallet
. database
. borrow_mut ( )
. del_utxo ( & txin . previous_output )
. unwrap ( ) ;
}
original_details . transaction = Some ( tx ) ;
wallet
. database
. borrow_mut ( )
. set_tx ( & original_details )
. unwrap ( ) ;
let ( psbt , details ) = wallet
. bump_fee (
& txid ,
TxBuilder ::new ( ) . fee_rate ( FeeRate ::from_sat_per_vb ( 2.5 ) ) ,
)
. unwrap ( ) ;
assert_eq! ( details . sent , original_details . sent ) ;
assert_eq! (
details . received + details . fees ,
original_details . received + original_details . fees
) ;
assert! ( details . fees > original_details . fees ) ;
let tx = & psbt . global . unsigned_tx ;
assert_eq! ( tx . output . len ( ) , 2 ) ;
assert_eq! (
tx . output
. iter ( )
. find ( | txout | txout . script_pubkey = = addr . script_pubkey ( ) )
. unwrap ( )
. value ,
25_000
) ;
assert_eq! (
tx . output
. iter ( )
. find ( | txout | txout . script_pubkey ! = addr . script_pubkey ( ) )
. unwrap ( )
. value ,
details . received
) ;
assert_fee_rate! ( psbt . extract_tx ( ) , details . fees , FeeRate ::from_sat_per_vb ( 2.5 ) , @ add_signature ) ;
}
#[ test ]
fn test_bump_fee_reduce_send_all ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let addr = Address ::from_str ( " 2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX " ) . unwrap ( ) ;
let ( psbt , mut original_details ) = wallet
. create_tx (
2020-09-04 15:45:11 +02:00
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 0 ) ] )
2020-08-13 16:51:27 +02:00
. send_all ( )
. enable_rbf ( ) ,
)
. unwrap ( ) ;
let mut tx = psbt . extract_tx ( ) ;
let txid = tx . txid ( ) ;
for txin in & mut tx . input {
txin . witness . push ( [ 0x00 ; 108 ] . to_vec ( ) ) ; // fake signature
wallet
. database
. borrow_mut ( )
. del_utxo ( & txin . previous_output )
. unwrap ( ) ;
}
original_details . transaction = Some ( tx ) ;
wallet
. database
. borrow_mut ( )
. set_tx ( & original_details )
. unwrap ( ) ;
let ( psbt , details ) = wallet
. bump_fee (
& txid ,
TxBuilder ::new ( )
. send_all ( )
. fee_rate ( FeeRate ::from_sat_per_vb ( 2.5 ) ) ,
)
. unwrap ( ) ;
assert_eq! ( details . sent , original_details . sent ) ;
assert! ( details . fees > original_details . fees ) ;
let tx = & psbt . global . unsigned_tx ;
assert_eq! ( tx . output . len ( ) , 1 ) ;
assert_eq! ( tx . output [ 0 ] . value + details . fees , details . sent ) ;
assert_fee_rate! ( psbt . extract_tx ( ) , details . fees , FeeRate ::from_sat_per_vb ( 2.5 ) , @ add_signature ) ;
}
#[ test ]
#[ should_panic(expected = " InsufficientFunds " ) ]
fn test_bump_fee_remove_send_all_output ( ) {
let ( wallet , descriptors , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
// receive an extra tx, to make sure that in case of "send_all" we get an error and it
// doesn't try to pick more inputs
let incoming_txid = wallet . database . borrow_mut ( ) . received_tx (
testutils! ( @ tx ( ( @ external descriptors , 0 ) = > 25_000 ) ( @ confirmations 1 ) ) ,
Some ( 100 ) ,
) ;
let addr = Address ::from_str ( " 2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX " ) . unwrap ( ) ;
let ( psbt , mut original_details ) = wallet
. create_tx (
2020-09-04 15:45:11 +02:00
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 0 ) ] )
2020-08-13 16:51:27 +02:00
. utxos ( vec! [ OutPoint {
txid : incoming_txid ,
vout : 0 ,
} ] )
. send_all ( )
. enable_rbf ( ) ,
)
. unwrap ( ) ;
let mut tx = psbt . extract_tx ( ) ;
let txid = tx . txid ( ) ;
for txin in & mut tx . input {
txin . witness . push ( [ 0x00 ; 108 ] . to_vec ( ) ) ; // fake signature
wallet
. database
. borrow_mut ( )
. del_utxo ( & txin . previous_output )
. unwrap ( ) ;
}
original_details . transaction = Some ( tx ) ;
wallet
. database
. borrow_mut ( )
. set_tx ( & original_details )
. unwrap ( ) ;
assert_eq! ( original_details . sent , 25_000 ) ;
wallet
. bump_fee (
& txid ,
TxBuilder ::new ( )
. send_all ( )
. fee_rate ( FeeRate ::from_sat_per_vb ( 225.0 ) ) ,
)
. unwrap ( ) ;
}
#[ test ]
fn test_bump_fee_add_input ( ) {
let ( wallet , descriptors , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
wallet . database . borrow_mut ( ) . received_tx (
testutils! ( @ tx ( ( @ external descriptors , 0 ) = > 25_000 ) ( @ confirmations 1 ) ) ,
Some ( 100 ) ,
) ;
let addr = Address ::from_str ( " 2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX " ) . unwrap ( ) ;
let ( psbt , mut original_details ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx (
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 45_000 ) ] ) . enable_rbf ( ) ,
)
2020-08-13 16:51:27 +02:00
. unwrap ( ) ;
let mut tx = psbt . extract_tx ( ) ;
let txid = tx . txid ( ) ;
// skip saving the new utxos, we know they can't be used anyways
for txin in & mut tx . input {
txin . witness . push ( [ 0x00 ; 108 ] . to_vec ( ) ) ; // fake signature
wallet
. database
. borrow_mut ( )
. del_utxo ( & txin . previous_output )
. unwrap ( ) ;
}
original_details . transaction = Some ( tx ) ;
wallet
. database
. borrow_mut ( )
. set_tx ( & original_details )
. unwrap ( ) ;
let ( psbt , details ) = wallet
. bump_fee (
& txid ,
TxBuilder ::new ( ) . fee_rate ( FeeRate ::from_sat_per_vb ( 50.0 ) ) ,
)
. unwrap ( ) ;
assert_eq! ( details . sent , original_details . sent + 25_000 ) ;
assert_eq! ( details . fees + details . received , 30_000 ) ;
let tx = & psbt . global . unsigned_tx ;
assert_eq! ( tx . input . len ( ) , 2 ) ;
assert_eq! ( tx . output . len ( ) , 2 ) ;
assert_eq! (
tx . output
. iter ( )
. find ( | txout | txout . script_pubkey = = addr . script_pubkey ( ) )
. unwrap ( )
. value ,
45_000
) ;
assert_eq! (
tx . output
. iter ( )
. find ( | txout | txout . script_pubkey ! = addr . script_pubkey ( ) )
. unwrap ( )
. value ,
details . received
) ;
assert_fee_rate! ( psbt . extract_tx ( ) , details . fees , FeeRate ::from_sat_per_vb ( 50.0 ) , @ add_signature ) ;
}
#[ test ]
fn test_bump_fee_no_change_add_input_and_change ( ) {
let ( wallet , descriptors , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let incoming_txid = wallet . database . borrow_mut ( ) . received_tx (
testutils! ( @ tx ( ( @ external descriptors , 0 ) = > 25_000 ) ( @ confirmations 1 ) ) ,
Some ( 100 ) ,
) ;
let addr = Address ::from_str ( " 2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX " ) . unwrap ( ) ;
let ( psbt , mut original_details ) = wallet
. create_tx (
2020-09-04 15:45:11 +02:00
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 0 ) ] )
2020-08-13 16:51:27 +02:00
. send_all ( )
. add_utxo ( OutPoint {
txid : incoming_txid ,
vout : 0 ,
} )
. enable_rbf ( ) ,
)
. unwrap ( ) ;
let mut tx = psbt . extract_tx ( ) ;
let txid = tx . txid ( ) ;
// skip saving the new utxos, we know they can't be used anyways
for txin in & mut tx . input {
txin . witness . push ( [ 0x00 ; 108 ] . to_vec ( ) ) ; // fake signature
wallet
. database
. borrow_mut ( )
. del_utxo ( & txin . previous_output )
. unwrap ( ) ;
}
original_details . transaction = Some ( tx ) ;
wallet
. database
. borrow_mut ( )
. set_tx ( & original_details )
. unwrap ( ) ;
// NOTE: we don't set "send_all" here. so we have a transaction with only one input, but
// here we are allowed to add more, and we will also have to add a change
let ( psbt , details ) = wallet
. bump_fee (
& txid ,
TxBuilder ::new ( ) . fee_rate ( FeeRate ::from_sat_per_vb ( 50.0 ) ) ,
)
. unwrap ( ) ;
let original_send_all_amount = original_details . sent - original_details . fees ;
assert_eq! ( details . sent , original_details . sent + 50_000 ) ;
assert_eq! (
details . received ,
75_000 - original_send_all_amount - details . fees
) ;
let tx = & psbt . global . unsigned_tx ;
assert_eq! ( tx . input . len ( ) , 2 ) ;
assert_eq! ( tx . output . len ( ) , 2 ) ;
assert_eq! (
tx . output
. iter ( )
. find ( | txout | txout . script_pubkey = = addr . script_pubkey ( ) )
. unwrap ( )
. value ,
original_send_all_amount
) ;
assert_eq! (
tx . output
. iter ( )
. find ( | txout | txout . script_pubkey ! = addr . script_pubkey ( ) )
. unwrap ( )
. value ,
75_000 - original_send_all_amount - details . fees
) ;
assert_fee_rate! ( psbt . extract_tx ( ) , details . fees , FeeRate ::from_sat_per_vb ( 50.0 ) , @ add_signature ) ;
}
#[ test ]
fn test_bump_fee_add_input_change_dust ( ) {
let ( wallet , descriptors , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
wallet . database . borrow_mut ( ) . received_tx (
testutils! ( @ tx ( ( @ external descriptors , 0 ) = > 25_000 ) ( @ confirmations 1 ) ) ,
Some ( 100 ) ,
) ;
let addr = Address ::from_str ( " 2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX " ) . unwrap ( ) ;
let ( psbt , mut original_details ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx (
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 45_000 ) ] ) . enable_rbf ( ) ,
)
2020-08-13 16:51:27 +02:00
. unwrap ( ) ;
let mut tx = psbt . extract_tx ( ) ;
assert_eq! ( tx . input . len ( ) , 1 ) ;
assert_eq! ( tx . output . len ( ) , 2 ) ;
let txid = tx . txid ( ) ;
// skip saving the new utxos, we know they can't be used anyways
for txin in & mut tx . input {
txin . witness . push ( [ 0x00 ; 108 ] . to_vec ( ) ) ; // fake signature
wallet
. database
. borrow_mut ( )
. del_utxo ( & txin . previous_output )
. unwrap ( ) ;
}
original_details . transaction = Some ( tx ) ;
wallet
. database
. borrow_mut ( )
. set_tx ( & original_details )
. unwrap ( ) ;
let ( psbt , details ) = wallet
. bump_fee (
& txid ,
TxBuilder ::new ( ) . fee_rate ( FeeRate ::from_sat_per_vb ( 140.0 ) ) ,
)
. unwrap ( ) ;
assert_eq! ( original_details . received , 5_000 - original_details . fees ) ;
assert_eq! ( details . sent , original_details . sent + 25_000 ) ;
assert_eq! ( details . fees , 30_000 ) ;
assert_eq! ( details . received , 0 ) ;
let tx = & psbt . global . unsigned_tx ;
assert_eq! ( tx . input . len ( ) , 2 ) ;
assert_eq! ( tx . output . len ( ) , 1 ) ;
assert_eq! (
tx . output
. iter ( )
. find ( | txout | txout . script_pubkey = = addr . script_pubkey ( ) )
. unwrap ( )
. value ,
45_000
) ;
assert_fee_rate! ( psbt . extract_tx ( ) , details . fees , FeeRate ::from_sat_per_vb ( 140.0 ) , @ dust_change , @ add_signature ) ;
}
#[ test ]
fn test_bump_fee_force_add_input ( ) {
let ( wallet , descriptors , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
let incoming_txid = wallet . database . borrow_mut ( ) . received_tx (
testutils! ( @ tx ( ( @ external descriptors , 0 ) = > 25_000 ) ( @ confirmations 1 ) ) ,
Some ( 100 ) ,
) ;
let addr = Address ::from_str ( " 2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX " ) . unwrap ( ) ;
let ( psbt , mut original_details ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx (
TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 45_000 ) ] ) . enable_rbf ( ) ,
)
2020-08-13 16:51:27 +02:00
. unwrap ( ) ;
let mut tx = psbt . extract_tx ( ) ;
let txid = tx . txid ( ) ;
// skip saving the new utxos, we know they can't be used anyways
for txin in & mut tx . input {
txin . witness . push ( [ 0x00 ; 108 ] . to_vec ( ) ) ; // fake signature
wallet
. database
. borrow_mut ( )
. del_utxo ( & txin . previous_output )
. unwrap ( ) ;
}
original_details . transaction = Some ( tx ) ;
wallet
. database
. borrow_mut ( )
. set_tx ( & original_details )
. unwrap ( ) ;
// the new fee_rate is low enough that just reducing the change would be fine, but we force
// the addition of an extra input with `add_utxo()`
let ( psbt , details ) = wallet
. bump_fee (
& txid ,
TxBuilder ::new ( )
. add_utxo ( OutPoint {
txid : incoming_txid ,
vout : 0 ,
} )
. fee_rate ( FeeRate ::from_sat_per_vb ( 5.0 ) ) ,
)
. unwrap ( ) ;
assert_eq! ( details . sent , original_details . sent + 25_000 ) ;
assert_eq! ( details . fees + details . received , 30_000 ) ;
let tx = & psbt . global . unsigned_tx ;
assert_eq! ( tx . input . len ( ) , 2 ) ;
assert_eq! ( tx . output . len ( ) , 2 ) ;
assert_eq! (
tx . output
. iter ( )
. find ( | txout | txout . script_pubkey = = addr . script_pubkey ( ) )
. unwrap ( )
. value ,
45_000
) ;
assert_eq! (
tx . output
. iter ( )
. find ( | txout | txout . script_pubkey ! = addr . script_pubkey ( ) )
. unwrap ( )
. value ,
details . received
) ;
assert_fee_rate! ( psbt . extract_tx ( ) , details . fees , FeeRate ::from_sat_per_vb ( 5.0 ) , @ add_signature ) ;
}
2020-08-30 20:34:51 +02:00
#[ test ]
fn test_sign_single_xprv ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( " wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*) " ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 0 ) ] ) . send_all ( ) )
2020-08-30 20:34:51 +02:00
. unwrap ( ) ;
let ( signed_psbt , finalized ) = wallet . sign ( psbt , None ) . unwrap ( ) ;
assert_eq! ( finalized , true ) ;
2020-09-29 18:18:50 +02:00
let extracted = signed_psbt . extract_tx ( ) ;
assert_eq! ( extracted . input [ 0 ] . witness . len ( ) , 2 ) ;
}
#[ test ]
fn test_sign_single_xprv_bip44_path ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( " wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/44'/0'/0'/0/*) " ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
. create_tx ( TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 0 ) ] ) . send_all ( ) )
. unwrap ( ) ;
let ( signed_psbt , finalized ) = wallet . sign ( psbt , None ) . unwrap ( ) ;
assert_eq! ( finalized , true ) ;
2020-08-30 20:34:51 +02:00
let extracted = signed_psbt . extract_tx ( ) ;
assert_eq! ( extracted . input [ 0 ] . witness . len ( ) , 2 ) ;
}
2020-09-16 17:31:43 +02:00
#[ test ]
fn test_sign_single_xprv_sh_wpkh ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( " sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)) " ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
. create_tx ( TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 0 ) ] ) . send_all ( ) )
. unwrap ( ) ;
let ( signed_psbt , finalized ) = wallet . sign ( psbt , None ) . unwrap ( ) ;
assert_eq! ( finalized , true ) ;
let extracted = signed_psbt . extract_tx ( ) ;
assert_eq! ( extracted . input [ 0 ] . witness . len ( ) , 2 ) ;
}
2020-08-30 20:34:51 +02:00
#[ test ]
fn test_sign_single_wif ( ) {
let ( wallet , _ , _ ) =
get_funded_wallet ( " wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW) " ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 0 ) ] ) . send_all ( ) )
2020-08-30 20:34:51 +02:00
. unwrap ( ) ;
let ( signed_psbt , finalized ) = wallet . sign ( psbt , None ) . unwrap ( ) ;
assert_eq! ( finalized , true ) ;
let extracted = signed_psbt . extract_tx ( ) ;
assert_eq! ( extracted . input [ 0 ] . witness . len ( ) , 2 ) ;
}
#[ test ]
fn test_sign_single_xprv_no_hd_keypaths ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( " wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*) " ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( mut psbt , _ ) = wallet
2020-09-04 15:45:11 +02:00
. create_tx ( TxBuilder ::with_recipients ( vec! [ ( addr . script_pubkey ( ) , 0 ) ] ) . send_all ( ) )
2020-08-30 20:34:51 +02:00
. unwrap ( ) ;
psbt . inputs [ 0 ] . hd_keypaths . clear ( ) ;
assert_eq! ( psbt . inputs [ 0 ] . hd_keypaths . len ( ) , 0 ) ;
let ( signed_psbt , finalized ) = wallet . sign ( psbt , None ) . unwrap ( ) ;
assert_eq! ( finalized , true ) ;
let extracted = signed_psbt . extract_tx ( ) ;
assert_eq! ( extracted . input [ 0 ] . witness . len ( ) , 2 ) ;
}
2020-08-06 18:11:07 +02:00
}