[tests] Add tests for Wallet::create_tx()

This commit is contained in:
Alekos Filini 2020-08-10 17:16:47 +02:00
parent 9e5023670e
commit 53b5f23fb2
No known key found for this signature in database
GPG Key ID: 5E8AFC3034FDFA4F
5 changed files with 680 additions and 19 deletions

View File

@ -404,6 +404,64 @@ impl BatchDatabase for MemoryDatabase {
}
}
#[cfg(test)]
impl MemoryDatabase {
// Artificially insert a tx in the database, as if we had found it with a `sync`
pub fn received_tx(
&mut self,
tx_meta: testutils::TestIncomingTx,
current_height: Option<u32>,
) -> bitcoin::Txid {
use std::str::FromStr;
let tx = Transaction {
version: 1,
lock_time: 0,
input: vec![],
output: tx_meta
.output
.iter()
.map(|out_meta| bitcoin::TxOut {
value: out_meta.value,
script_pubkey: bitcoin::Address::from_str(&out_meta.to_address)
.unwrap()
.script_pubkey(),
})
.collect(),
};
let txid = tx.txid();
let height = tx_meta
.min_confirmations
.map(|conf| current_height.unwrap().checked_sub(conf as u32).unwrap());
let tx_details = TransactionDetails {
transaction: Some(tx.clone()),
txid,
timestamp: 0,
height,
received: 0,
sent: 0,
fees: 0,
};
self.set_tx(&tx_details).unwrap();
for (vout, out) in tx.output.iter().enumerate() {
self.set_utxo(&UTXO {
txout: out.clone(),
outpoint: OutPoint {
txid,
vout: vout as u32,
},
is_internal: false,
})
.unwrap();
}
txid
}
}
#[cfg(test)]
mod test {
use super::MemoryDatabase;

View File

@ -8,6 +8,7 @@ pub enum Error {
Generic(String),
ScriptDoesntHaveAddressForm,
SendAllMultipleOutputs,
NoAddressees,
OutputBelowDustLimit(usize),
InsufficientFunds,
InvalidAddressNetwork(Address),

View File

@ -127,6 +127,10 @@ where
&self,
builder: TxBuilder<Cs>,
) -> Result<(PSBT, TransactionDetails), Error> {
if builder.addressees.is_empty() {
return Err(Error::NoAddressees);
}
// TODO: fetch both internal and external policies
let policy = self.descriptor.extract_policy()?.unwrap();
if policy.requires_path() && builder.policy_path.is_none() {
@ -137,14 +141,18 @@ where
debug!("requirements: {:?}", requirements);
let version = match builder.version {
tx_builder::Version(0) => return Err(Error::Generic("Invalid version `0`".into())),
tx_builder::Version(1) if requirements.csv.is_some() => {
Some(tx_builder::Version(0)) => {
return Err(Error::Generic("Invalid version `0`".into()))
}
Some(tx_builder::Version(1)) if requirements.csv.is_some() => {
return Err(Error::Generic(
"TxBuilder requested version `1`, but at least `2` is needed to use OP_CSV"
.into(),
))
}
tx_builder::Version(x) => x,
Some(tx_builder::Version(x)) => x,
None if requirements.csv.is_some() => 2,
_ => 1,
};
let lock_time = match builder.locktime {
@ -219,6 +227,14 @@ where
.max_satisfaction_weight(),
);
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(),
));
}
let (available_utxos, use_all_utxos) = self.get_available_utxos(
builder.change_policy,
&builder.utxos,
@ -335,7 +351,24 @@ where
}
}
self.add_hd_keypaths(&mut psbt)?;
// 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)?
{
let (desc, _) = self.get_descriptor_for(script_type);
psbt_output.hd_keypaths = desc.get_hd_keypaths(child)?;
}
}
let transaction_details = TransactionDetails {
transaction: None,
@ -353,7 +386,7 @@ where
// TODO: define an enum for signing errors
pub fn sign(&self, mut psbt: PSBT, assume_height: Option<u32>) -> Result<(PSBT, bool), Error> {
// this helps us doing our job later
self.add_hd_keypaths(&mut psbt)?;
self.add_input_hd_keypaths(&mut psbt)?;
let tx = &psbt.global.unsigned_tx;
@ -701,7 +734,7 @@ where
}
}
fn add_hd_keypaths(&self, psbt: &mut PSBT) -> Result<(), Error> {
fn add_input_hd_keypaths(&self, psbt: &mut PSBT) -> Result<(), Error> {
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());
@ -787,6 +820,8 @@ where
}
}
// 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
if run_setup {
maybe_await!(self.client.setup(
None,
@ -818,8 +853,11 @@ where
mod test {
use bitcoin::Network;
use miniscript::Descriptor;
use crate::database::memory::MemoryDatabase;
use crate::database::Database;
use crate::descriptor::ExtendedDescriptor;
use crate::types::ScriptType;
use super::*;
@ -913,4 +951,518 @@ mod test {
.unwrap()
.is_some());
}
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>,
(ExtendedDescriptor, Option<ExtendedDescriptor>),
bitcoin::Txid,
) {
let descriptors = testutils!(@descriptors (descriptor));
let wallet: OfflineWallet<_> = Wallet::new_offline(
&descriptors.0.to_string(),
None,
Network::Regtest,
MemoryDatabase::new(),
)
.unwrap();
let txid = wallet.database.borrow_mut().received_tx(
testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
},
None,
);
(wallet, descriptors, txid)
}
#[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
);
}
#[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());
}
}

View File

@ -19,7 +19,7 @@ pub struct TxBuilder<Cs: CoinSelectionAlgorithm> {
pub(crate) ordering: TxOrdering,
pub(crate) locktime: Option<u32>,
pub(crate) rbf: Option<u32>,
pub(crate) version: Version,
pub(crate) version: Option<Version>,
pub(crate) change_policy: ChangeSpendPolicy,
pub(crate) force_non_witness_utxo: bool,
pub(crate) coin_selection: Cs,
@ -108,7 +108,7 @@ impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
}
pub fn version(mut self, version: u32) -> Self {
self.version = Version(version);
self.version = Some(Version(version));
self
}
@ -152,7 +152,7 @@ impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
pub enum TxOrdering {
Shuffle,
Untouched,
@ -193,7 +193,7 @@ impl TxOrdering {
}
// Helper type that wraps u32 and has a default value of 1
#[derive(Debug)]
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
pub(crate) struct Version(pub(crate) u32);
impl Default for Version {
@ -202,7 +202,7 @@ impl Default for Version {
}
}
#[derive(Debug)]
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
pub enum ChangeSpendPolicy {
ChangeAllowed,
OnlyChange,
@ -245,6 +245,11 @@ mod test {
use super::*;
#[test]
fn test_output_ordering_default_shuffle() {
assert_eq!(TxOrdering::default(), TxOrdering::Shuffle);
}
#[test]
fn test_output_ordering_untouched() {
let original_tx = ordering_test_tx!();
@ -301,4 +306,57 @@ mod test {
assert_eq!(tx.output[1].script_pubkey, From::from(vec![0xAA]));
assert_eq!(tx.output[2].script_pubkey, From::from(vec![0xAA, 0xEE]));
}
fn get_test_utxos() -> Vec<UTXO> {
vec![
UTXO {
outpoint: OutPoint {
txid: Default::default(),
vout: 0,
},
txout: Default::default(),
is_internal: false,
},
UTXO {
outpoint: OutPoint {
txid: Default::default(),
vout: 1,
},
txout: Default::default(),
is_internal: true,
},
]
}
#[test]
fn test_change_spend_policy_default() {
let change_spend_policy = ChangeSpendPolicy::default();
let filtered = change_spend_policy.filter_utxos(get_test_utxos().into_iter());
assert_eq!(filtered.len(), 2);
}
#[test]
fn test_change_spend_policy_no_internal() {
let change_spend_policy = ChangeSpendPolicy::ChangeForbidden;
let filtered = change_spend_policy.filter_utxos(get_test_utxos().into_iter());
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].is_internal, false);
}
#[test]
fn test_change_spend_policy_only_internal() {
let change_spend_policy = ChangeSpendPolicy::OnlyChange;
let filtered = change_spend_policy.filter_utxos(get_test_utxos().into_iter());
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].is_internal, true);
}
#[test]
fn test_default_tx_version_1() {
let version = Version::default();
assert_eq!(version.0, 1);
}
}

View File

@ -10,7 +10,6 @@ use std::env;
use std::ops::Deref;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Mutex;
use std::time::Duration;
#[allow(unused_imports)]
@ -26,12 +25,6 @@ pub use bitcoincore_rpc::{Auth, Client as RpcClient, RpcApi};
pub use electrum_client::{Client as ElectrumClient, ElectrumApi};
lazy_static! {
static ref SYNC_TESTS_MUTEX: Mutex<()> = Mutex::new(());
}
pub fn test_init() {}
// TODO: we currently only support env vars, we could also parse a toml file
fn get_auth() -> Auth {
match env::var("MAGICAL_RPC_AUTH").as_ref().map(String::as_ref) {
@ -217,7 +210,6 @@ macro_rules! testutils {
}).unwrap();
internal = Some(string_internal.try_into().unwrap());
)*
(external, internal)