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-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-02-07 23:22:28 +01:00
pub mod utils ;
2020-08-12 12:51:50 +02:00
use signer ::{ Signer , SignersContainer } ;
2020-08-07 11:23:01 +02:00
use tx_builder ::TxBuilder ;
2020-08-12 12:51:50 +02:00
use utils ::{ After , FeeRate , IsDust , Older } ;
2020-08-06 11:12:15 +02:00
2020-08-25 16:07:26 +02:00
use crate ::blockchain ::{ Blockchain , OfflineBlockchain , OnlineBlockchain , 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-02-07 23:22:28 +01:00
use crate ::error ::Error ;
2020-08-12 12:51:50 +02:00
use crate ::psbt ::PSBTUtils ;
// use crate::psbt::{utils::PSBTUtils, PSBTSatisfier, PSBTSigner};
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-05-03 16:15:11 +02:00
pub type OfflineWallet < D > = Wallet < OfflineBlockchain , D > ;
pub struct Wallet < B : Blockchain , 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-02-07 23:22:28 +01:00
network : Network ,
2020-05-06 17:17:14 +02:00
current_height : Option < u32 > ,
2020-08-06 10:44:40 +02:00
client : 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-05-03 16:15:11 +02:00
B : Blockchain ,
2020-02-07 23:22:28 +01:00
D : BatchDatabase ,
{
pub fn new_offline (
2020-02-15 21:27:51 +01:00
descriptor : & str ,
change_descriptor : Option < & str > ,
2020-02-07 23:22:28 +01:00
network : Network ,
2020-02-15 21:27:51 +01:00
mut database : D ,
) -> Result < Self , Error > {
database . check_descriptor_checksum (
ScriptType ::External ,
get_checksum ( descriptor ) ? . as_bytes ( ) ,
) ? ;
2020-08-12 12:51:50 +02:00
let ( descriptor , keymap ) = ExtendedDescriptor ::parse_secret ( descriptor ) ? ;
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 ) = > {
database . check_descriptor_checksum (
ScriptType ::Internal ,
get_checksum ( desc ) ? . as_bytes ( ) ,
) ? ;
2020-02-17 14:22:53 +01:00
2020-08-12 12:51:50 +02:00
let ( change_descriptor , change_keymap ) = ExtendedDescriptor ::parse_secret ( desc ) ? ;
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-02-07 23:22:28 +01:00
network ,
2020-05-06 17:17:14 +02:00
current_height : None ,
2020-08-06 10:44:40 +02:00
client : B ::offline ( ) ,
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
}
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 )
}
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
}
pub fn list_unspent ( & self ) -> Result < Vec < UTXO > , Error > {
self . database . borrow ( ) . iter_utxos ( )
}
pub fn list_transactions ( & self , include_raw : bool ) -> Result < Vec < TransactionDetails > , Error > {
self . database . borrow ( ) . iter_txs ( include_raw )
}
pub fn get_balance ( & self ) -> Result < u64 , Error > {
Ok ( self
. list_unspent ( ) ?
. iter ( )
. fold ( 0 , | sum , i | sum + i . txout . value ) )
}
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-10 17:16:47 +02:00
if builder . addressees . is_empty ( ) {
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 =
policy . get_requirements ( 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 ,
( Some ( rbf ) , _ ) if rbf > = 0xFFFFFFFE = > return Err ( Error ::Generic ( " Cannot enable RBF with anumber >= 0xFFFFFFFE " . into ( ) ) ) ,
( 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-07 11:23:01 +02:00
let fee_rate = builder . fee_rate . unwrap_or_default ( ) . as_sat_vb ( ) ;
2020-08-06 13:09:39 +02:00
if builder . send_all & & builder . addressees . 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 ;
let calc_fee_bytes = | wu | ( wu as f32 ) * fee_rate / 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-08-06 13:09:39 +02:00
for ( index , ( address , satoshi ) ) in builder . addressees . iter ( ) . enumerate ( ) {
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-08-10 10:49:34 +02:00
// TODO: proper checks for testnet/regtest p2sh/p2pkh
if address . network ! = self . network & & self . network ! = Network ::Regtest {
return Err ( Error ::InvalidAddressNetwork ( address . clone ( ) ) ) ;
2020-08-06 13:09:39 +02:00
} else if self . is_mine ( & address . script_pubkey ( ) ) ? {
2020-02-07 23:22:28 +01:00
received + = value ;
}
let new_out = TxOut {
script_pubkey : address . script_pubkey ( ) ,
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 ,
total_amount ,
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 ;
let change_val = total_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
builder . ordering . modify_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 ,
sent : total_amount ,
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-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 {
return Err ( Error ::FeeRateTooLow ( required_feerate ) ) ;
}
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 ,
total_amount ,
fee_amount ,
} = builder . coin_selection . coin_select (
available_utxos ,
use_all_utxos ,
new_feerate . as_sat_vb ( ) ,
fee_difference
. checked_sub ( removed_change_output . value )
. unwrap_or ( 0 ) ,
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 ) ;
tx . input . extend_from_slice ( & mut txin ) ;
details . sent + = total_amount ;
total_amount
} 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
builder . ordering . modify_tx ( & mut tx ) ;
// 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 ( (
txin . previous_output . clone ( ) ,
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-02-17 14:22:53 +01:00
// TODO: define an enum for signing errors
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-12 12:51:50 +02:00
for index in 0 .. psbt . inputs . len ( ) {
self . signers . sign ( & mut psbt , index ) ? ;
if self . change_descriptor . is_some ( ) {
self . change_signers . sign ( & mut psbt , index ) ? ;
2020-02-07 23:22:28 +01:00
}
}
// attempt to finalize
2020-05-17 18:01:52 +02:00
let finalized = self . finalize_psbt ( & mut psbt , assume_height ) ? ;
2020-02-07 23:22:28 +01:00
Ok ( ( psbt , finalized ) )
}
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-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-05-17 18:01:52 +02:00
pub fn finalize_psbt (
& self ,
psbt : & mut PSBT ,
assume_height : Option < u32 > ,
) -> Result < bool , Error > {
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 ) ?
. and_then ( | tx | Some ( tx . height . unwrap_or ( std ::u32 ::MAX ) ) ) ;
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 ) ;
return Ok ( false ) ;
} ;
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 ) ;
return Ok ( false ) ;
}
}
}
// 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 ) ;
}
Ok ( true )
}
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-02-07 23:22:28 +01:00
let desc = 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-02-07 23:22:28 +01:00
} ;
desc
}
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-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 ( ) ,
Some ( vec ) = > vec . into_iter ( ) . collect ( ) ,
} ;
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-05-03 16:15:11 +02:00
B : OnlineBlockchain ,
2020-02-07 23:22:28 +01:00
D : BatchDatabase ,
{
2020-07-20 15:51:57 +02:00
#[ maybe_async ]
2020-07-15 18:49:24 +02:00
pub fn new (
2020-02-15 21:27:51 +01:00
descriptor : & str ,
change_descriptor : Option < & str > ,
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 ) ;
wallet . client = 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-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 ( )
. get_script_pubkey_from_path ( ScriptType ::Internal , 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 ::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 {
maybe_await! ( self . client . setup (
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 {
maybe_await! ( self . client . sync (
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-08-08 12:06:40 +02:00
pub fn client ( & self ) -> & B {
& self . client
}
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-08-06 10:44:40 +02:00
maybe_await! ( self . client . 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
fn get_test_wpkh ( ) -> & 'static str {
" wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW) "
}
fn get_test_single_sig_csv ( ) -> & 'static str {
// and(pk(Alice),older(6))
" wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(6))) "
}
fn get_test_single_sig_cltv ( ) -> & 'static str {
// and(pk(Alice),after(100000))
" wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(100000))) "
}
fn get_funded_wallet (
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 " ) ]
fn test_create_tx_empty_addressees ( ) {
let ( wallet , _ , _ ) = get_funded_wallet ( get_test_wpkh ( ) ) ;
wallet
. create_tx ( TxBuilder ::from_addressees ( vec! [ ] ) . version ( 0 ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr , 25_000 ) ] ) . version ( 0 ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr , 25_000 ) ] ) . version ( 1 ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr , 25_000 ) ] ) . version ( 42 ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr , 25_000 ) ] ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr , 25_000 ) ] ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr , 25_000 ) ] ) . nlocktime ( 630_000 ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr , 25_000 ) ] ) . nlocktime ( 630_000 ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr , 25_000 ) ] ) . nlocktime ( 50000 ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr , 25_000 ) ] ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr , 25_000 ) ] ) . enable_rbf ( ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr , 25_000 ) ] ) . enable_rbf_with_sequence ( 3 ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr , 25_000 ) ] ) )
. unwrap ( ) ;
assert_eq! ( psbt . global . unsigned_tx . input [ 0 ] . sequence , 0xFFFFFFFE ) ;
}
#[ test ]
#[ should_panic(expected = " Cannot enable RBF with anumber >= 0xFFFFFFFE " ) ]
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 (
TxBuilder ::from_addressees ( vec! [ ( addr , 25_000 ) ] )
. 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 (
TxBuilder ::from_addressees ( vec! [ ( addr , 25_000 ) ] )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr , 25_000 ) ] ) )
. 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 (
TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 25_000 ) ] ) . do_not_spend_change ( ) ,
)
. 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 (
TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 25_000 ) , ( addr , 10_000 ) ] ) . send_all ( ) ,
)
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 0 ) ] ) . send_all ( ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 0 ) ] ) . send_all ( ) )
. 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 (
TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 0 ) ] )
. 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 (
TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 25_000 ) ] )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 49_800 ) ] ) )
. 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 (
TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 0 ) ] )
. send_all ( )
. fee_rate ( super ::utils ::FeeRate ::from_sat_per_vb ( 453.0 ) ) ,
)
. 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 (
TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 30_000 ) , ( addr . clone ( ) , 10_000 ) ] )
. ordering ( super ::tx_builder ::TxOrdering ::BIP69Lexicographic ) ,
)
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 30_000 ) ] ) )
. 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 (
TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 30_000 ) ] )
. 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 ;
let ( wallet , _ , _ ) = get_funded_wallet ( " wpkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*) " ) ;
let addr = wallet . get_new_address ( ) . unwrap ( ) ;
let ( psbt , _ ) = wallet
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 0 ) ] ) . send_all ( ) )
. 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 ;
let ( wallet , descriptors , _ ) = get_funded_wallet ( " wpkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*) " ) ;
// cache some addresses
wallet . get_new_address ( ) . unwrap ( ) ;
let addr = testutils! ( @ external descriptors , 5 ) ;
let ( psbt , _ ) = wallet
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 0 ) ] ) . send_all ( ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 0 ) ] ) . send_all ( ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 0 ) ] ) . send_all ( ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 0 ) ] ) . send_all ( ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 0 ) ] ) . send_all ( ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 0 ) ] ) . send_all ( ) )
. 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 (
TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 0 ) ] )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr , 25_000 ) ] ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr , 25_000 ) ] ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr , 25_000 ) ] ) . enable_rbf ( ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 25_000 ) ] ) . 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 ( ) ;
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 (
TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 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 ( ) ;
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 (
TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 0 ) ] )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 45_000 ) ] ) . 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 ( ) ;
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 (
TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 0 ) ] )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 45_000 ) ] ) . enable_rbf ( ) )
. 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
. create_tx ( TxBuilder ::from_addressees ( vec! [ ( addr . clone ( ) , 45_000 ) ] ) . 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 ( ) ;
// 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-06 18:11:07 +02:00
}