diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..ef4e9ab7 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,451 @@ +extern crate bitcoin; +extern crate bitcoin_wallet; +extern crate bitcoincore_rpc; + +use dirs::home_dir; +use std::io; +use std::iter::repeat; +use std::path::PathBuf; +use std::sync::{Arc, Once, RwLock}; + +use bitcoin::hashes::{hash160::Hash as Hash160, hex::ToHex}; +use bitcoin::Amount; +use bitcoin_wallet::mnemonic; +use bitcoincore_rpc::{Auth, Client, Error, RpcApi}; + +pub mod wallet_sync; +use wallet_sync::{Wallet, WalletSyncAddressAmount}; + +pub mod contracts; +use contracts::{read_locktime_from_contract, SwapCoin}; + +pub mod error; +pub mod maker_protocol; +pub mod messages; +pub mod offerbook_sync; +pub mod taker_protocol; +pub mod watchtower_client; +pub mod watchtower_protocol; + +static INIT: Once = Once::new(); + +/// Setup function that will only run once, even if called multiple times. +pub fn setup_logger() { + INIT.call_once(|| { + env_logger::Builder::from_env( + env_logger::Env::default() + .default_filter_or("teleport=info,main=info") + .default_write_style_or("always"), + ) + .init(); + }); +} + +pub fn get_bitcoin_rpc() -> Result { + //TODO put all this in a config file + const RPC_CREDENTIALS: Option<(&str, &str)> = Some(("regtestrpcuser", "regtestrpcpass")); + //Some(("btcrpcuser", "btcrpcpass")); + //None; // use Bitcoin Core cookie-based authentication + + let auth = match RPC_CREDENTIALS { + Some((user, pass)) => Auth::UserPass(user.to_string(), pass.to_string()), + None => { + //TODO this currently only works for Linux and regtest, + // also support other OSes (Windows, MacOS...) and networks + let data_dir = home_dir().unwrap().join(".bitcoin"); + Auth::CookieFile(data_dir.join("regtest").join(".cookie")) + } + }; + let rpc = Client::new( + "http://localhost:18443/wallet/teleport" + //"http://localhost:18332/wallet/teleport" + .to_string(), + auth, + )?; + rpc.get_blockchain_info()?; + Ok(rpc) +} + +pub fn generate_wallet(wallet_file_name: &PathBuf) -> std::io::Result<()> { + let rpc = match get_bitcoin_rpc() { + Ok(rpc) => rpc, + Err(error) => { + log::error!(target: "main", "error connecting to bitcoin node: {:?}", error); + return Ok(()); + } + }; + + println!("input seed phrase extension (or leave blank for none): "); + let mut extension = String::new(); + io::stdin().read_line(&mut extension)?; + extension = extension.trim().to_string(); + + let mnemonic = + mnemonic::Mnemonic::new_random(bitcoin_wallet::account::MasterKeyEntropy::Sufficient) + .unwrap(); + + println!("Write down this seed phrase =\n{}", mnemonic.to_string()); + + if !extension.trim().is_empty() { + println!("And this extension =\n\"{}\"", extension); + } + + println!( + "\nThis seed phrase is NOT enough to backup all coins in your wallet\n\ + The teleport wallet file is needed to backup swapcoins" + ); + + Wallet::save_new_wallet_file(&wallet_file_name, mnemonic.to_string(), extension).unwrap(); + + let w = match Wallet::load_wallet_from_file(&wallet_file_name, WalletSyncAddressAmount::Normal) + { + Ok(w) => w, + Err(error) => panic!("error loading wallet file: {:?}", error), + }; + println!("Importing addresses into Core. . ."); + w.import_initial_addresses( + &rpc, + &w.get_hd_wallet_descriptors(&rpc) + .unwrap() + .iter() + .collect::>(), + &Vec::<_>::new(), + ) + .unwrap(); + Ok(()) +} + +pub fn recover_wallet(wallet_file_name: &PathBuf) -> std::io::Result<()> { + println!("input seed phrase: "); + let mut seed_phrase = String::new(); + io::stdin().read_line(&mut seed_phrase)?; + seed_phrase = seed_phrase.trim().to_string(); + + if let Err(e) = mnemonic::Mnemonic::from_str(&seed_phrase) { + println!("invalid seed phrase: {:?}", e); + return Ok(()); + } + + println!("input seed phrase extension (or leave blank for none): "); + let mut extension = String::new(); + io::stdin().read_line(&mut extension)?; + extension = extension.trim().to_string(); + + Wallet::save_new_wallet_file(&wallet_file_name, seed_phrase, extension).unwrap(); + Ok(()) +} + +pub fn display_wallet_balance(wallet_file_name: &PathBuf, long_form: Option) { + let mut wallet = + match Wallet::load_wallet_from_file(wallet_file_name, WalletSyncAddressAmount::Normal) { + Ok(w) => w, + Err(error) => { + log::error!(target: "main", "error loading wallet file: {:?}", error); + return; + } + }; + let rpc = match get_bitcoin_rpc() { + Ok(rpc) => rpc, + Err(error) => { + log::error!(target: "main", "error connecting to bitcoin node: {:?}", error); + return; + } + }; + wallet.startup_sync(&rpc).unwrap(); + + let long_form = long_form.unwrap_or(false); + + let mut utxos = wallet.list_unspent_from_wallet(&rpc).unwrap(); + utxos.sort_by(|a, b| b.confirmations.cmp(&a.confirmations)); + let utxo_count = utxos.len(); + let balance: Amount = utxos.iter().fold(Amount::ZERO, |acc, u| acc + u.amount); + println!("= spendable wallet balance ="); + println!( + "{:16} {:24} {:^8} {:<7} value", + "coin", "address", "type", "conf", + ); + for utxo in utxos { + let txid = utxo.txid.to_hex(); + let addr = utxo.address.unwrap().to_string(); + #[rustfmt::skip] + println!( + "{}{}{}:{} {}{}{} {:^8} {:<7} {}", + if long_form { &txid } else {&txid[0..6] }, + if long_form { "" } else { ".." }, + if long_form { &"" } else { &txid[58..64] }, + utxo.vout, + if long_form { &addr } else { &addr[0..10] }, + if long_form { "" } else { "...." }, + if long_form { &"" } else { &addr[addr.len() - 10..addr.len()] }, + if utxo.witness_script.is_some() { + "swapcoin" + } else { + if utxo.descriptor.is_some() { "seed" } else { "timelock" } + }, + utxo.confirmations, + utxo.amount + ); + } + println!("coin count = {}", utxo_count); + println!("total balance = {}", balance); + + let incomplete_coinswaps = wallet.find_incomplete_coinswaps(&rpc).unwrap(); + if !incomplete_coinswaps.is_empty() { + println!("= incomplete coinswaps ="); + for (hashvalue, (utxo_incoming_swapcoins, utxo_outgoing_swapcoins)) in incomplete_coinswaps + { + let balance: Amount = utxo_incoming_swapcoins + .iter() + .map(|(l, i)| (l, (*i as &dyn SwapCoin))) + .chain( + utxo_outgoing_swapcoins + .iter() + .map(|(l, o)| (l, (*o as &dyn SwapCoin))), + ) + .fold(Amount::ZERO, |acc, us| acc + us.0.amount); + println!( + "{:16} {:8} {:8} {:<15} {:<7} value", + "coin", "type", "preimage", "locktime/blocks", "conf", + ); + + for ((utxo, swapcoin), contract_type) in utxo_incoming_swapcoins + .iter() + .map(|(l, i)| (l, (*i as &dyn SwapCoin))) + .zip(repeat("hashlock")) + .chain( + utxo_outgoing_swapcoins + .iter() + .map(|(l, o)| (l, (*o as &dyn SwapCoin))) + .zip(repeat("timelock")), + ) + { + let txid = utxo.txid.to_hex(); + + #[rustfmt::skip] + println!("{}{}{}:{} {:8} {:8} {:^15} {:<7} {}", + if long_form { &txid } else {&txid[0..6] }, + if long_form { "" } else { ".." }, + if long_form { &"" } else { &txid[58..64] }, + utxo.vout, + contract_type, + if swapcoin.is_hash_preimage_known() { "known" } else { "unknown" }, + read_locktime_from_contract(&swapcoin.get_contract_redeemscript()) + .expect("unable to read locktime from contract"), + utxo.confirmations, + utxo.amount + ); + } + println!( + "hashvalue = {}\ntotal balance = {}", + &hashvalue.to_hex()[..], + balance + ); + } + } + + let (_incoming_contract_utxos, mut outgoing_contract_utxos) = + wallet.find_live_contract_unspents(&rpc).unwrap(); + if !outgoing_contract_utxos.is_empty() { + outgoing_contract_utxos.sort_by(|a, b| b.1.confirmations.cmp(&a.1.confirmations)); + println!("= live timelocked contracts ="); + println!( + "{:16} {:8} {:<7} {:<8} {:6}", + "coin", "timelock", "conf", "locked?", "value" + ); + for (outgoing_swapcoin, utxo) in outgoing_contract_utxos { + let txid = utxo.txid.to_hex(); + let timelock = + read_locktime_from_contract(&outgoing_swapcoin.contract_redeemscript).unwrap(); + #[rustfmt::skip] + println!("{}{}{}:{} {:<8} {:<7} {:<8} {}", + if long_form { &txid } else {&txid[0..6] }, + if long_form { "" } else { ".." }, + if long_form { &"" } else { &txid[58..64] }, + utxo.vout, + timelock, + utxo.confirmations, + if utxo.confirmations >= timelock.into() { "unlocked" } else { "locked" }, + utxo.amount + ); + } + } +} + +pub fn display_wallet_keys(wallet_file_name: &PathBuf) { + let wallet = + match Wallet::load_wallet_from_file(wallet_file_name, WalletSyncAddressAmount::Normal) { + Ok(w) => w, + Err(error) => { + log::error!(target: "main", "error loading wallet file: {:?}", error); + return; + } + }; + wallet.print_wallet_key_data(); +} + +pub fn print_receive_invoice(wallet_file_name: &PathBuf) { + let mut wallet = + match Wallet::load_wallet_from_file(wallet_file_name, WalletSyncAddressAmount::Normal) { + Ok(w) => w, + Err(error) => { + log::error!(target: "main", "error loading wallet file: {:?}", error); + return; + } + }; + let rpc = match get_bitcoin_rpc() { + Ok(rpc) => rpc, + Err(error) => { + log::error!(target: "main", "error connecting to bitcoin node: {:?}", error); + return; + } + }; + wallet.startup_sync(&rpc).unwrap(); + + let addr = match wallet.get_next_external_address(&rpc) { + Ok(a) => a, + Err(error) => { + println!("error: {:?}", error); + return; + } + }; + println!("{}", addr); +} + +pub fn run_maker( + wallet_file_name: &PathBuf, + sync_amount: WalletSyncAddressAmount, + port: u16, + special_behavior: Option, + kill_flag: Option>>, +) { + let rpc = match get_bitcoin_rpc() { + Ok(rpc) => rpc, + Err(error) => { + log::error!(target: "main", "error connecting to bitcoin node: {:?}", error); + return; + } + }; + let mut wallet = match Wallet::load_wallet_from_file(wallet_file_name, sync_amount) { + Ok(w) => w, + Err(error) => { + log::error!(target: "main", "error loading wallet file: {:?}", error); + return; + } + }; + wallet.startup_sync(&rpc).unwrap(); + + let rpc_ptr = Arc::new(rpc); + let wallet_ptr = Arc::new(RwLock::new(wallet)); + let config = maker_protocol::MakerConfig { + port, + rpc_ping_interval: 30, + watchtower_ping_interval: 300, + maker_behavior: match special_behavior.unwrap_or(String::new()).as_str() { + "closeonsignsenderscontracttx" => { + maker_protocol::MakerBehavior::CloseOnSignSendersContractTx + } + _ => maker_protocol::MakerBehavior::Normal, + }, + kill_flag: if kill_flag.is_none() { + Arc::new(RwLock::new(false)) + } else { + kill_flag.unwrap().clone() + }, + }; + maker_protocol::start_maker(rpc_ptr, wallet_ptr, config); +} + +pub fn run_taker(wallet_file_name: &PathBuf, sync_amount: WalletSyncAddressAmount) { + let rpc = match get_bitcoin_rpc() { + Ok(rpc) => rpc, + Err(error) => { + log::error!(target: "main", "error connecting to bitcoin node: {:?}", error); + return; + } + }; + let mut wallet = match Wallet::load_wallet_from_file(wallet_file_name, sync_amount) { + Ok(w) => w, + Err(error) => { + log::error!(target: "main", "error loading wallet file: {:?}", error); + return; + } + }; + wallet.startup_sync(&rpc).unwrap(); + taker_protocol::start_taker(&rpc, &mut wallet); +} + +pub fn recover_from_incomplete_coinswap( + wallet_file_name: &PathBuf, + hashvalue: Hash160, + dont_broadcast: bool, +) { + let mut wallet = + match Wallet::load_wallet_from_file(wallet_file_name, WalletSyncAddressAmount::Normal) { + Ok(w) => w, + Err(error) => { + log::error!(target: "main", "error loading wallet file: {:?}", error); + return; + } + }; + let rpc = match get_bitcoin_rpc() { + Ok(rpc) => rpc, + Err(error) => { + log::error!(target: "main", "error connecting to bitcoin node: {:?}", error); + return; + } + }; + wallet.startup_sync(&rpc).unwrap(); + + let incomplete_coinswaps = wallet.find_incomplete_coinswaps(&rpc).unwrap(); + let incomplete_coinswap = incomplete_coinswaps.get(&hashvalue); + if incomplete_coinswap.is_none() { + log::error!(target: "main", "hashvalue not refering to incomplete coinswap, run \ + `wallet-balance` to see list of incomplete coinswaps"); + return; + } + let incomplete_coinswap = incomplete_coinswap.unwrap(); + + for (ii, outgoing_swapcoin) in incomplete_coinswap.1.iter().enumerate() { + wallet + .import_redeemscript( + &rpc, + &outgoing_swapcoin.1.contract_redeemscript, + wallet_sync::CoreAddressLabelType::Wallet, + ) + .unwrap(); + + let signed_contract_tx = outgoing_swapcoin.1.get_fully_signed_contract_tx(); + if dont_broadcast { + let txhex = bitcoin::consensus::encode::serialize_hex(&signed_contract_tx); + println!("contract_tx_{} = \n{}", ii, txhex); + let accepted = rpc + .test_mempool_accept(&[txhex.clone()]) + .unwrap() + .iter() + .any(|tma| tma.allowed); + assert!(accepted); + } else { + let txid = rpc.send_raw_transaction(&signed_contract_tx).unwrap(); + println!("broadcasted {}", txid); + } + } +} + +pub fn run_watchtower(kill_flag: Option>>) { + let rpc = match get_bitcoin_rpc() { + Ok(rpc) => rpc, + Err(error) => { + log::error!(target: "main", "error connecting to bitcoin node: {:?}", error); + return; + } + }; + + watchtower_protocol::start_watchtower( + &rpc, + if kill_flag.is_none() { + Arc::new(RwLock::new(false)) + } else { + kill_flag.unwrap().clone() + }, + ); +} diff --git a/src/main.rs b/src/main.rs index e904feed..421ed3dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,437 +1,13 @@ -extern crate bitcoin; -extern crate bitcoin_wallet; -extern crate bitcoincore_rpc; - -use dirs::home_dir; -use std::io; -use std::iter::repeat; -use std::path::PathBuf; -use std::sync::Once; -use std::sync::{Arc, RwLock}; - use bitcoin::consensus::encode::deserialize; -use bitcoin::hashes::{hash160::Hash as Hash160, hex::FromHex, hex::ToHex}; -use bitcoin::{Amount, Transaction}; -use bitcoin_wallet::mnemonic; -use bitcoincore_rpc::{Auth, Client, Error, RpcApi}; +use bitcoin::hashes::{hash160::Hash as Hash160, hex::FromHex}; +use bitcoin::Transaction; +use std::path::PathBuf; use structopt::StructOpt; -mod wallet_sync; -use wallet_sync::Wallet; - -mod contracts; -use contracts::{read_locktime_from_contract, SwapCoin}; - -mod error; -mod maker_protocol; -mod messages; -mod offerbook_sync; -mod taker_protocol; -mod watchtower_protocol; - -mod watchtower_client; -use watchtower_client::ContractInfo; - -fn generate_wallet(wallet_file_name: &PathBuf) -> std::io::Result<()> { - let rpc = match get_bitcoin_rpc() { - Ok(rpc) => rpc, - Err(error) => { - log::error!(target: "main", "error connecting to bitcoin node: {:?}", error); - return Ok(()); - } - }; - - println!("input seed phrase extension (or leave blank for none): "); - let mut extension = String::new(); - io::stdin().read_line(&mut extension)?; - extension = extension.trim().to_string(); - - let mnemonic = - mnemonic::Mnemonic::new_random(bitcoin_wallet::account::MasterKeyEntropy::Sufficient) - .unwrap(); - - println!("Write down this seed phrase =\n{}", mnemonic.to_string()); - - if !extension.trim().is_empty() { - println!("And this extension =\n\"{}\"", extension); - } - - println!( - "\nThis seed phrase is NOT enough to backup all coins in your wallet\n\ - The teleport wallet file is needed to backup swapcoins" - ); - - Wallet::save_new_wallet_file(&wallet_file_name, mnemonic.to_string(), extension).unwrap(); - - let w = match Wallet::load_wallet_from_file(&wallet_file_name) { - Ok(w) => w, - Err(error) => panic!("error loading wallet file: {:?}", error), - }; - println!("Importing addresses into Core. . ."); - w.import_initial_addresses( - &rpc, - &w.get_hd_wallet_descriptors(&rpc) - .unwrap() - .iter() - .collect::>(), - &Vec::<_>::new(), - ) - .unwrap(); - Ok(()) -} - -fn recover_wallet(wallet_file_name: &PathBuf) -> std::io::Result<()> { - println!("input seed phrase: "); - let mut seed_phrase = String::new(); - io::stdin().read_line(&mut seed_phrase)?; - seed_phrase = seed_phrase.trim().to_string(); - - if let Err(e) = mnemonic::Mnemonic::from_str(&seed_phrase) { - println!("invalid seed phrase: {:?}", e); - return Ok(()); - } - - println!("input seed phrase extension (or leave blank for none): "); - let mut extension = String::new(); - io::stdin().read_line(&mut extension)?; - extension = extension.trim().to_string(); - - Wallet::save_new_wallet_file(&wallet_file_name, seed_phrase, extension).unwrap(); - Ok(()) -} - -fn get_bitcoin_rpc() -> Result { - //TODO put all this in a config file - const RPC_CREDENTIALS: Option<(&str, &str)> = Some(("regtestrpcuser", "regtestrpcpass")); - //Some(("btcrpcuser", "btcrpcpass")); - //None; // use Bitcoin Core cookie-based authentication - - let auth = match RPC_CREDENTIALS { - Some((user, pass)) => Auth::UserPass(user.to_string(), pass.to_string()), - None => { - //TODO this currently only works for Linux and regtest, - // also support other OSes (Windows, MacOS...) and networks - let data_dir = home_dir().unwrap().join(".bitcoin"); - Auth::CookieFile(data_dir.join("regtest").join(".cookie")) - } - }; - let rpc = Client::new( - "http://localhost:18443/wallet/teleport" - //"http://localhost:18332/wallet/teleport" - .to_string(), - auth, - )?; - rpc.get_blockchain_info()?; - Ok(rpc) -} - -fn display_wallet_balance(wallet_file_name: &PathBuf, long_form: Option) { - let mut wallet = match Wallet::load_wallet_from_file(wallet_file_name) { - Ok(w) => w, - Err(error) => { - log::error!(target: "main", "error loading wallet file: {:?}", error); - return; - } - }; - let rpc = match get_bitcoin_rpc() { - Ok(rpc) => rpc, - Err(error) => { - log::error!(target: "main", "error connecting to bitcoin node: {:?}", error); - return; - } - }; - wallet.startup_sync(&rpc).unwrap(); - - let long_form = long_form.unwrap_or(false); - - let mut utxos = wallet.list_unspent_from_wallet(&rpc).unwrap(); - utxos.sort_by(|a, b| b.confirmations.cmp(&a.confirmations)); - let utxo_count = utxos.len(); - let balance: Amount = utxos.iter().fold(Amount::ZERO, |acc, u| acc + u.amount); - println!("= spendable wallet balance ="); - println!( - "{:16} {:24} {:^8} {:<7} value", - "coin", "address", "type", "conf", - ); - for utxo in utxos { - let txid = utxo.txid.to_hex(); - let addr = utxo.address.unwrap().to_string(); - #[rustfmt::skip] - println!( - "{}{}{}:{} {}{}{} {:^8} {:<7} {}", - if long_form { &txid } else {&txid[0..6] }, - if long_form { "" } else { ".." }, - if long_form { &"" } else { &txid[58..64] }, - utxo.vout, - if long_form { &addr } else { &addr[0..10] }, - if long_form { "" } else { "...." }, - if long_form { &"" } else { &addr[addr.len() - 10..addr.len()] }, - if utxo.witness_script.is_some() { - "swapcoin" - } else { - if utxo.descriptor.is_some() { "seed" } else { "timelock" } - }, - utxo.confirmations, - utxo.amount - ); - } - println!("coin count = {}", utxo_count); - println!("total balance = {}", balance); - - let incomplete_coinswaps = wallet.find_incomplete_coinswaps(&rpc).unwrap(); - if !incomplete_coinswaps.is_empty() { - println!("= incomplete coinswaps ="); - for (hashvalue, (utxo_incoming_swapcoins, utxo_outgoing_swapcoins)) in incomplete_coinswaps - { - let balance: Amount = utxo_incoming_swapcoins - .iter() - .map(|(l, i)| (l, (*i as &dyn SwapCoin))) - .chain( - utxo_outgoing_swapcoins - .iter() - .map(|(l, o)| (l, (*o as &dyn SwapCoin))), - ) - .fold(Amount::ZERO, |acc, us| acc + us.0.amount); - println!( - "{:16} {:8} {:8} {:<15} {:<7} value", - "coin", "type", "preimage", "locktime/blocks", "conf", - ); - - for ((utxo, swapcoin), contract_type) in utxo_incoming_swapcoins - .iter() - .map(|(l, i)| (l, (*i as &dyn SwapCoin))) - .zip(repeat("hashlock")) - .chain( - utxo_outgoing_swapcoins - .iter() - .map(|(l, o)| (l, (*o as &dyn SwapCoin))) - .zip(repeat("timelock")), - ) - { - let txid = utxo.txid.to_hex(); - - #[rustfmt::skip] - println!("{}{}{}:{} {:8} {:8} {:^15} {:<7} {}", - if long_form { &txid } else {&txid[0..6] }, - if long_form { "" } else { ".." }, - if long_form { &"" } else { &txid[58..64] }, - utxo.vout, - contract_type, - if swapcoin.is_hash_preimage_known() { "known" } else { "unknown" }, - read_locktime_from_contract(&swapcoin.get_contract_redeemscript()) - .expect("unable to read locktime from contract"), - utxo.confirmations, - utxo.amount - ); - } - println!( - "hashvalue = {}\ntotal balance = {}", - &hashvalue.to_hex()[..], - balance - ); - } - } - - let (_incoming_contract_utxos, mut outgoing_contract_utxos) = - wallet.find_live_contract_unspents(&rpc).unwrap(); - if !outgoing_contract_utxos.is_empty() { - outgoing_contract_utxos.sort_by(|a, b| b.1.confirmations.cmp(&a.1.confirmations)); - println!("= live timelocked contracts ="); - println!( - "{:16} {:8} {:<7} {:<8} {:6}", - "coin", "timelock", "conf", "locked?", "value" - ); - for (outgoing_swapcoin, utxo) in outgoing_contract_utxos { - let txid = utxo.txid.to_hex(); - let timelock = - read_locktime_from_contract(&outgoing_swapcoin.contract_redeemscript).unwrap(); - #[rustfmt::skip] - println!("{}{}{}:{} {:<8} {:<7} {:<8} {}", - if long_form { &txid } else {&txid[0..6] }, - if long_form { "" } else { ".." }, - if long_form { &"" } else { &txid[58..64] }, - utxo.vout, - timelock, - utxo.confirmations, - if utxo.confirmations >= timelock.into() { "unlocked" } else { "locked" }, - utxo.amount - ); - } - } -} - -fn display_wallet_keys(wallet_file_name: &PathBuf) { - let wallet = match Wallet::load_wallet_from_file(wallet_file_name) { - Ok(w) => w, - Err(error) => { - log::error!(target: "main", "error loading wallet file: {:?}", error); - return; - } - }; - wallet.print_wallet_key_data(); -} - -fn print_receive_invoice(wallet_file_name: &PathBuf) { - let mut wallet = match Wallet::load_wallet_from_file(wallet_file_name) { - Ok(w) => w, - Err(error) => { - log::error!(target: "main", "error loading wallet file: {:?}", error); - return; - } - }; - let rpc = match get_bitcoin_rpc() { - Ok(rpc) => rpc, - Err(error) => { - log::error!(target: "main", "error connecting to bitcoin node: {:?}", error); - return; - } - }; - wallet.startup_sync(&rpc).unwrap(); - - let addr = match wallet.get_next_external_address(&rpc) { - Ok(a) => a, - Err(error) => { - println!("error: {:?}", error); - return; - } - }; - println!("{}", addr); -} - -fn run_maker(wallet_file_name: &PathBuf, port: u16, special_behavior: Option) { - let rpc = match get_bitcoin_rpc() { - Ok(rpc) => rpc, - Err(error) => { - log::error!(target: "main", "error connecting to bitcoin node: {:?}", error); - return; - } - }; - let mut wallet = match Wallet::load_wallet_from_file(wallet_file_name) { - Ok(w) => w, - Err(error) => { - log::error!(target: "main", "error loading wallet file: {:?}", error); - return; - } - }; - wallet.startup_sync(&rpc).unwrap(); - - let rpc_ptr = Arc::new(rpc); - let wallet_ptr = Arc::new(RwLock::new(wallet)); - let config = maker_protocol::MakerConfig { - port, - rpc_ping_interval: 30, - watchtower_ping_interval: 300, - maker_behavior: match special_behavior.unwrap_or(String::new()).as_str() { - "closeonsignsenderscontracttx" => { - maker_protocol::MakerBehavior::CloseOnSignSendersContractTx - } - _ => maker_protocol::MakerBehavior::Normal, - }, - }; - maker_protocol::start_maker(rpc_ptr, wallet_ptr, config); -} - -fn run_taker(wallet_file_name: &PathBuf) { - let rpc = match get_bitcoin_rpc() { - Ok(rpc) => rpc, - Err(error) => { - log::error!(target: "main", "error connecting to bitcoin node: {:?}", error); - return; - } - }; - let mut wallet = match Wallet::load_wallet_from_file(wallet_file_name) { - Ok(w) => w, - Err(error) => { - log::error!(target: "main", "error loading wallet file: {:?}", error); - return; - } - }; - wallet.startup_sync(&rpc).unwrap(); - taker_protocol::start_taker(&rpc, &mut wallet); -} - -fn recover_from_incomplete_coinswap( - wallet_file_name: &PathBuf, - hashvalue: Hash160, - dont_broadcast: bool, -) { - let mut wallet = match Wallet::load_wallet_from_file(wallet_file_name) { - Ok(w) => w, - Err(error) => { - log::error!(target: "main", "error loading wallet file: {:?}", error); - return; - } - }; - let rpc = match get_bitcoin_rpc() { - Ok(rpc) => rpc, - Err(error) => { - log::error!(target: "main", "error connecting to bitcoin node: {:?}", error); - return; - } - }; - wallet.startup_sync(&rpc).unwrap(); - - let incomplete_coinswaps = wallet.find_incomplete_coinswaps(&rpc).unwrap(); - let incomplete_coinswap = incomplete_coinswaps.get(&hashvalue); - if incomplete_coinswap.is_none() { - log::error!(target: "main", "hashvalue not refering to incomplete coinswap, run \ - `wallet-balance` to see list of incomplete coinswaps"); - return; - } - let incomplete_coinswap = incomplete_coinswap.unwrap(); - - for (ii, outgoing_swapcoin) in incomplete_coinswap.1.iter().enumerate() { - wallet - .import_redeemscript( - &rpc, - &outgoing_swapcoin.1.contract_redeemscript, - wallet_sync::CoreAddressLabelType::Wallet, - ) - .unwrap(); - - let signed_contract_tx = outgoing_swapcoin.1.get_fully_signed_contract_tx(); - if dont_broadcast { - let txhex = bitcoin::consensus::encode::serialize_hex(&signed_contract_tx); - println!("contract_tx_{} = \n{}", ii, txhex); - let accepted = rpc - .test_mempool_accept(&[txhex.clone()]) - .unwrap() - .iter() - .any(|tma| tma.allowed); - assert!(accepted); - } else { - let txid = rpc.send_raw_transaction(&signed_contract_tx).unwrap(); - println!("broadcasted {}", txid); - } - } -} - -fn run_watchtower() { - let rpc = match get_bitcoin_rpc() { - Ok(rpc) => rpc, - Err(error) => { - log::error!(target: "main", "error connecting to bitcoin node: {:?}", error); - return; - } - }; - - watchtower_protocol::start_watchtower(&rpc); -} - -static INIT: Once = Once::new(); - -/// Setup function that will only run once, even if called multiple times. -fn setup_logger() { - INIT.call_once(|| { - env_logger::Builder::from_env( - env_logger::Env::default() - .default_filter_or("teleport=info,main=info") - .default_write_style_or("always"), - ) - .init(); - }); -} +use teleport; +use teleport::wallet_sync::WalletSyncAddressAmount; +use teleport::watchtower_client::ContractInfo; #[derive(Debug, StructOpt)] #[structopt(name = "teleport", about = "A tool for CoinSwap")] @@ -496,58 +72,65 @@ enum Subcommand { } fn main() -> Result<(), Box> { - setup_logger(); - + teleport::setup_logger(); let args = ArgsWithWalletFile::from_args(); match args.subcommand { Subcommand::GenerateWallet => { - generate_wallet(&args.wallet_file_name)?; + teleport::generate_wallet(&args.wallet_file_name)?; } Subcommand::RecoverWallet => { - recover_wallet(&args.wallet_file_name)?; + teleport::recover_wallet(&args.wallet_file_name)?; } Subcommand::WalletBalance { long_form } => { - display_wallet_balance(&args.wallet_file_name, long_form); + teleport::display_wallet_balance(&args.wallet_file_name, long_form); } Subcommand::DisplayWalletKeys => { - display_wallet_keys(&args.wallet_file_name); + teleport::display_wallet_keys(&args.wallet_file_name); } Subcommand::GetReceiveInvoice => { - print_receive_invoice(&args.wallet_file_name); + teleport::print_receive_invoice(&args.wallet_file_name); } Subcommand::RunMaker { port, special_behavior, } => { - run_maker( + teleport::run_maker( &args.wallet_file_name, + WalletSyncAddressAmount::Normal, port.unwrap_or(6102), special_behavior, + None, ); } Subcommand::CoinswapSend => { - run_taker(&args.wallet_file_name); + teleport::run_taker(&args.wallet_file_name, WalletSyncAddressAmount::Normal); } Subcommand::RecoverFromIncompleteCoinswap { hashvalue, dont_broadcast, } => { - recover_from_incomplete_coinswap( + teleport::recover_from_incomplete_coinswap( &args.wallet_file_name, hashvalue, dont_broadcast.unwrap_or(false), ); } Subcommand::RunWatchtower => { - run_watchtower(); + teleport::run_watchtower(None); } Subcommand::TestWatchtowerClient { mut contract_transactions_hex, } => { if contract_transactions_hex.is_empty() { // https://bitcoin.stackexchange.com/questions/68811/what-is-the-absolute-smallest-size-of-the-data-bytes-that-a-blockchain-transac - contract_transactions_hex = vec![String::from("0200000000010100000000000000000000000000000000000000000000000000000000000000000000000000fdffffff010100000000000000160014ffffffffffffffffffffffffffffffffffffffff022102000000000000000000000000000000000000000000000000000000000000000147304402207777777777777777777777777777777777777777777777777777777777777777022055555555555555555555555555555555555555555555555555555555555555550100000000")]; + contract_transactions_hex = + vec![String::from(concat!("020000000001010000000000000", + "0000000000000000000000000000000000000000000000000000000000000fdffffff010100000000", + "000000160014ffffffffffffffffffffffffffffffffffffffff02210200000000000000000000000", + "000000000000000000000000000000000000000014730440220777777777777777777777777777777", + "777777777777777777777777777777777702205555555555555555555555555555555555555555555", + "5555555555555555555550100000000"))]; } let contract_txes = contract_transactions_hex .iter() @@ -564,204 +147,9 @@ fn main() -> Result<(), Box> { contract_tx: contract_tx.clone(), }) .collect::>(); - watchtower_client::test_watchtower_client(contracts_to_watch); + teleport::watchtower_client::test_watchtower_client(contracts_to_watch); } } Ok(()) } - -#[cfg(test)] -mod test { - use bitcoin::util::amount::Amount; - use serde_json::Value; - use std::{thread, time}; - use tokio::io::AsyncWriteExt; - - use std::str::FromStr; - - use super::*; - - static TAKER: &str = "tests/taker-wallet"; - static MAKER1: &str = "tests/maker-wallet-1"; - static MAKER2: &str = "tests/maker-wallet-2"; - - // Helper function to create new wallet - fn create_wallet_and_import(rpc: &Client, filename: PathBuf) -> Wallet { - let mnemonic = - mnemonic::Mnemonic::new_random(bitcoin_wallet::account::MasterKeyEntropy::Sufficient) - .unwrap(); - - Wallet::save_new_wallet_file(&filename, mnemonic.to_string(), "".to_string()).unwrap(); - - let wallet = Wallet::load_wallet_from_file(filename).unwrap(); - // import intital addresses to core - wallet - .import_initial_addresses( - &rpc, - &wallet - .get_hd_wallet_descriptors(&rpc) - .unwrap() - .iter() - .collect::>(), - &Vec::<_>::new(), - ) - .unwrap(); - - wallet - } - - pub fn generate_1_block(rpc: &Client) { - rpc.generate_to_address(1, &rpc.get_new_address(None, None).unwrap()) - .unwrap(); - } - - async fn send_test_kill_signal(addr: &str) { - { - let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap(); - let (_, mut writer) = stream.split(); - - writer.write_all(b"kill").await.unwrap(); - } - thread::sleep(time::Duration::from_secs(5)); - } - - // This test requires a bitcoin regtest node running in local machine with a - // wallet name `teleport` loaded and have enough balance to execute transactions. - #[tokio::test] - async fn test_standard_coin_swap() { - setup_logger(); - - let rpc = get_bitcoin_rpc().unwrap(); - - // unlock all utxos to avoid "insufficient fund" error - rpc.call::("lockunspent", &[Value::Bool(true)]) - .unwrap(); - - // create taker wallet - let mut taker = create_wallet_and_import(&rpc, TAKER.into()); - - // create maker1 wallet - let mut maker1 = create_wallet_and_import(&rpc, MAKER1.into()); - - // create maker2 wallet - let mut maker2 = create_wallet_and_import(&rpc, MAKER2.into()); - - // Check files are created - assert!(std::path::Path::new(TAKER).exists()); - assert!(std::path::Path::new(MAKER1).exists()); - assert!(std::path::Path::new(MAKER2).exists()); - - // Create 3 taker and maker address and send 0.05 btc to each - for _ in 0..3 { - let taker_address = taker.get_next_external_address(&rpc).unwrap(); - let maker1_address = maker1.get_next_external_address(&rpc).unwrap(); - let maker2_address = maker2.get_next_external_address(&rpc).unwrap(); - - rpc.send_to_address( - &taker_address, - Amount::from_btc(0.05).unwrap(), - None, - None, - None, - None, - None, - None, - ) - .unwrap(); - rpc.send_to_address( - &maker1_address, - Amount::from_btc(0.05).unwrap(), - None, - None, - None, - None, - None, - None, - ) - .unwrap(); - rpc.send_to_address( - &maker2_address, - Amount::from_btc(0.05).unwrap(), - None, - None, - None, - None, - None, - None, - ) - .unwrap(); - } - - generate_1_block(&rpc); - - // Check inital wallet assertions - assert_eq!(taker.get_external_index(), 3); - assert_eq!(maker1.get_external_index(), 3); - assert_eq!(maker2.get_external_index(), 3); - - assert_eq!(taker.list_unspent_from_wallet(&rpc).unwrap().len(), 3); - assert_eq!(maker1.list_unspent_from_wallet(&rpc).unwrap().len(), 3); - assert_eq!(maker2.list_unspent_from_wallet(&rpc).unwrap().len(), 3); - - assert_eq!(taker.lock_all_nonwallet_unspents(&rpc).unwrap(), ()); - assert_eq!(maker1.lock_all_nonwallet_unspents(&rpc).unwrap(), ()); - assert_eq!(maker2.lock_all_nonwallet_unspents(&rpc).unwrap(), ()); - - // Start watchtower, makers and taker to execute a coinswap - let watchtower_thread = thread::spawn(|| { - run_watchtower(); //which listens on port 6103 - }); - - let maker1_thread = thread::spawn(|| { - run_maker(&PathBuf::from_str(MAKER1).unwrap(), 6102, None); - }); - - let maker2_thread = thread::spawn(|| { - run_maker(&PathBuf::from_str(MAKER2).unwrap(), 16102, None); - }); - - let taker_thread = thread::spawn(|| { - // Wait and then start the taker - thread::sleep(time::Duration::from_secs(5)); - run_taker(&PathBuf::from_str(TAKER).unwrap()); - }); - - taker_thread.join().unwrap(); - - send_test_kill_signal("127.0.0.1:6102").await; - send_test_kill_signal("127.0.0.1:16102").await; - send_test_kill_signal("127.0.0.1:6103").await; - - maker1_thread.join().unwrap(); - maker2_thread.join().unwrap(); - watchtower_thread.join().unwrap(); - - // Recreate the wallet - let taker = Wallet::load_wallet_from_file(&TAKER).unwrap(); - let maker1 = Wallet::load_wallet_from_file(&MAKER1).unwrap(); - let maker2 = Wallet::load_wallet_from_file(&MAKER2).unwrap(); - - // Check assertions - assert_eq!(taker.get_swap_coins_count(), 6); - assert_eq!(maker1.get_swap_coins_count(), 6); - assert_eq!(maker2.get_swap_coins_count(), 6); - - let rpc = get_bitcoin_rpc().unwrap(); - - let utxos = taker.list_unspent_from_wallet(&rpc).unwrap(); - let balance: Amount = utxos.iter().fold(Amount::ZERO, |acc, u| acc + u.amount); - assert_eq!(utxos.len(), 6); - assert!(balance < Amount::from_btc(0.15).unwrap()); - - let utxos = maker1.list_unspent_from_wallet(&rpc).unwrap(); - let balance: Amount = utxos.iter().fold(Amount::ZERO, |acc, u| acc + u.amount); - assert_eq!(utxos.len(), 6); - assert!(balance > Amount::from_btc(0.15).unwrap()); - - let utxos = maker2.list_unspent_from_wallet(&rpc).unwrap(); - let balance: Amount = utxos.iter().fold(Amount::ZERO, |acc, u| acc + u.amount); - assert_eq!(utxos.len(), 6); - assert!(balance > Amount::from_btc(0.15).unwrap()); - } -} diff --git a/src/maker_protocol.rs b/src/maker_protocol.rs index 18fb4954..0f6192cf 100644 --- a/src/maker_protocol.rs +++ b/src/maker_protocol.rs @@ -41,12 +41,13 @@ pub enum MakerBehavior { CloseOnSignSendersContractTx, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct MakerConfig { pub port: u16, pub rpc_ping_interval: u64, pub watchtower_ping_interval: u64, pub maker_behavior: MakerBehavior, + pub kill_flag: Arc>, } #[tokio::main] @@ -99,6 +100,9 @@ async fn run( let (server_loop_comms_tx, mut server_loop_comms_rx) = mpsc::channel::(100); let mut accepting_clients = true; let mut last_watchtowers_ping = Instant::now(); + + let my_kill_flag = config.kill_flag.clone(); + loop { let (mut socket, addr) = select! { new_client = listener.accept() => new_client?, @@ -129,6 +133,9 @@ async fn run( }; log::debug!("Ping: RPC = {}{}", rpc_ping_success, debug_msg); accepting_clients = rpc_ping_success && watchtowers_ping_success; + if *my_kill_flag.read().unwrap() { + break Err(Error::Protocol("kill flag is true")); + } continue; }, }; @@ -146,6 +153,7 @@ async fn run( let client_rpc = Arc::clone(&rpc); let client_wallet = Arc::clone(&wallet); let server_loop_comms_tx = server_loop_comms_tx.clone(); + let maker_behavior = config.maker_behavior; tokio::spawn(async move { let (socket_reader, mut socket_writer) = socket.split(); @@ -184,15 +192,6 @@ async fn run( break; } }; - #[cfg(test)] - if line == "kill".to_string() { - server_loop_comms_tx - .send(Error::Protocol("kill signal")) - .await - .unwrap(); - log::info!("Kill signal received, stopping maker...."); - break; - } line = line.trim_end().to_string(); let message_result = handle_message( @@ -201,7 +200,7 @@ async fn run( Arc::clone(&client_rpc), Arc::clone(&client_wallet), addr, - config.maker_behavior, + maker_behavior, ) .await; match message_result { diff --git a/src/taker_protocol.rs b/src/taker_protocol.rs index c6adfcd3..99b91204 100644 --- a/src/taker_protocol.rs +++ b/src/taker_protocol.rs @@ -37,9 +37,6 @@ use crate::messages::{ SignSendersContractTx, SwapCoinPrivateKey, TakerHello, TakerToMakerMessage, PREIMAGE_LEN, }; -#[cfg(test)] -use crate::get_bitcoin_rpc; - use crate::offerbook_sync::{sync_offerbook, OfferAddress}; use crate::wallet_sync::{ generate_keypair, CoreAddressLabelType, IncomingSwapCoin, OutgoingSwapCoin, Wallet, @@ -816,8 +813,6 @@ async fn wait_for_funding_tx_confirmation( } } sleep(Duration::from_millis(1000)).await; - #[cfg(test)] - crate::test::generate_1_block(&get_bitcoin_rpc().unwrap()); } } diff --git a/src/wallet_sync.rs b/src/wallet_sync.rs index b00721cd..639e5b86 100644 --- a/src/wallet_sync.rs +++ b/src/wallet_sync.rs @@ -62,11 +62,6 @@ pub const NETWORK: Network = Network::Regtest; //not configurable for now const DERIVATION_PATH: &str = "m/84'/1'/0'"; const WALLET_FILE_VERSION: u32 = 0; -#[cfg(not(test))] -const INITIAL_ADDRESS_IMPORT_COUNT: usize = 5000; -#[cfg(test)] -const INITIAL_ADDRESS_IMPORT_COUNT: usize = 6; - //TODO the wallet file format is probably best handled with sqlite #[derive(serde::Serialize, serde::Deserialize)] @@ -84,10 +79,16 @@ pub struct Wallet { master_key: ExtendedPrivKey, wallet_file_name: String, external_index: u32, + initial_address_import_count: usize, incoming_swap_coins: HashMap, outgoing_swap_coins: HashMap, } +pub enum WalletSyncAddressAmount { + Normal, + Testing, +} + pub enum CoreAddressLabelType { Wallet, WatchOnlySwapCoin, @@ -348,7 +349,10 @@ impl Wallet { .map_err(|e| io::Error::from(e))?) } - pub fn load_wallet_from_file>(wallet_file_name: P) -> Result { + pub fn load_wallet_from_file>( + wallet_file_name: P, + sync_amount: WalletSyncAddressAmount, + ) -> Result { let wallet_file_name = wallet_file_name .as_ref() .as_os_str() @@ -372,6 +376,10 @@ impl Wallet { master_key: xprv, wallet_file_name, external_index: wallet_file_data.external_index, + initial_address_import_count: match sync_amount { + WalletSyncAddressAmount::Normal => 5000, + WalletSyncAddressAmount::Testing => 6, + }, incoming_swap_coins: wallet_file_data .incoming_swap_coins .iter() @@ -395,7 +403,6 @@ impl Wallet { Ok(()) } - #[cfg(test)] pub fn get_external_index(&self) -> u32 { self.external_index } @@ -448,7 +455,6 @@ impl Wallet { .insert(coin.get_multisig_redeemscript(), coin); } - #[cfg(test)] pub fn get_swap_coins_count(&self) -> usize { self.incoming_swap_coins.len() + self.outgoing_swap_coins.len() } @@ -503,7 +509,7 @@ impl Wallet { fn is_xpub_descriptor_imported(&self, rpc: &Client, descriptor: &str) -> Result { let first_addr = rpc.derive_addresses(&descriptor, Some([0, 0]))?[0].clone(); - let last_index = (INITIAL_ADDRESS_IMPORT_COUNT - 1) as u32; + let last_index = (self.initial_address_import_count - 1) as u32; let last_addr = rpc.derive_addresses(&descriptor, Some([last_index, last_index]))?[0].clone(); @@ -566,6 +572,9 @@ impl Wallet { hd_descriptors_to_import: &[&String], swapcoin_descriptors_to_import: &[String], ) -> Result<(), Error> { + log::debug!(target: "wallet", + "import_initial_addresses with initial_address_import_count = {}", + self.initial_address_import_count); let address_label = self.get_core_wallet_label(); let import_requests = hd_descriptors_to_import @@ -573,7 +582,7 @@ impl Wallet { .map(|desc| ImportMultiRequest { timestamp: ImportMultiRescanSince::Now, descriptor: Some(desc), - range: Some((0, INITIAL_ADDRESS_IMPORT_COUNT - 1)), + range: Some((0, self.initial_address_import_count - 1)), watchonly: Some(true), label: Some(&address_label), ..Default::default() @@ -607,7 +616,6 @@ impl Wallet { pub fn startup_sync(&mut self, rpc: &Client) -> Result<(), Error> { //TODO many of these unwraps to be replaced with proper error handling - let hd_descriptors = self.get_hd_wallet_descriptors(rpc)?; let hd_descriptors_to_import = hd_descriptors .iter() @@ -659,7 +667,7 @@ impl Wallet { .map(|d| { json!( {"desc": d, - "range": INITIAL_ADDRESS_IMPORT_COUNT-1}) + "range": self.initial_address_import_count-1}) }) .chain(swapcoin_descriptors_to_import.iter().map(|d| json!(d))) .collect::>(); diff --git a/src/watchtower_protocol.rs b/src/watchtower_protocol.rs index 9af78793..e56f3e5f 100644 --- a/src/watchtower_protocol.rs +++ b/src/watchtower_protocol.rs @@ -1,6 +1,7 @@ use std::collections::HashSet; use std::iter::FromIterator; use std::net::Ipv4Addr; +use std::sync::{Arc, RwLock}; use std::time::Duration; use tokio::io::BufReader; @@ -65,15 +66,15 @@ pub enum WatchtowerToMakerMessage { //pub async fn #[tokio::main] -pub async fn start_watchtower(rpc: &Client) { - match run(rpc).await { +pub async fn start_watchtower(rpc: &Client, kill_flag: Arc>) { + match run(rpc, kill_flag).await { Ok(_o) => log::info!("watchtower ended without error"), Err(e) => log::info!("watchtower ended with err {:?}", e), }; } //TODO i think rpc doesnt need to be wrapped in Arc, because its not used in the spawned task -async fn run(rpc: &Client) -> Result<(), Error> { +async fn run(rpc: &Client, kill_flag: Arc>) -> Result<(), Error> { //TODO port number in config file let port = 6103; let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, port)).await?; @@ -121,6 +122,9 @@ async fn run(rpc: &Client) -> Result<(), Error> { &mut last_checked_block_height); accepting_clients = r.is_ok(); log::info!("Timeout Branch, Accepting Clients @ {}", port); + if *kill_flag.read().unwrap() { + break Err(Error::Protocol("kill flag is true")); + } continue; }, }; @@ -164,15 +168,6 @@ async fn run(rpc: &Client) -> Result<(), Error> { break; } }; - #[cfg(test)] - if line == "kill".to_string() { - server_loop_err_comms_tx - .send(Error::Protocol("kill signal")) - .await - .unwrap(); - log::info!("Kill signal received, stopping watchtower...."); - break; - } line = line.trim_end().to_string(); let message_result = handle_message(line, &watched_txes_comms_tx).await; diff --git a/tests/maker-wallet-1 b/tests/maker-wallet-1 deleted file mode 100644 index c83e3435..00000000 --- a/tests/maker-wallet-1 +++ /dev/null @@ -1 +0,0 @@ -{"version":0,"seedphrase":"coconut flock minute struggle mistake review face disease wreck pencil despair shadow","extension":"","external_index":3,"swap_coins":[{"my_privkey":"69c389994c3d4d38a5d503e6aec7184ca6740f66ce22d0716709ba47647f1d24","other_pubkey":"0355a3f1f6db8ece5e51e28f63b4c75c150634e511ccfecc8781387b0742ce6880","other_privkey":null,"contract_tx":{"version":2,"lock_time":0,"input":[{"previous_output":"834f56b05d5679e173a9c0a34f0cfe2e156eebd77f796c781bd2b2601afacefd:0","script_sig":"","sequence":0,"witness":[]}],"output":[{"value":62524,"script_pubkey":"0020f393d97d25c7fdfe3882a7dd00990965351bbf46833bbe5f6b95dadfa0e288ad"}]},"contract_redeemscript":"827ca914ea9e4cf47dbde043d19aee56626cfd04b8c0036787632103df8a315ace6644e6eef9d693d0174f0312a4d83c679ceeed58152f4373b232a7012051672103ddd328991ea9623d446fa1a54a17514ff12690cb12a9414b9a4fb62dbfd3808800012368b2757b88ac","funding_amount":63524,"others_contract_sig":"3044022050215ec619a6a6e07085b6e662d91eb7b607d7a9db615a0598b5ae54749b207602205760212494ab0bc9e02e146ee53fd798e88d0cb0ff00822bb4d1e82a1d3bd9bb","hash_preimage":null},{"my_privkey":"89325a384b78145195819b39f8009a16a1f12fe69bf80405fbc004c2ec6eae9a","other_pubkey":"027851ef01457372b3be93eafa5226d9dde9b56c5106bd86ab17f991c4ce29100c","other_privkey":"806cbf54f998e0628d73ab8940e1906828a3daf862e3db1f23b4434861bc3c36","contract_tx":{"version":2,"lock_time":0,"input":[{"previous_output":"9c5aa7be4052ea601b6968dec58b305bc2920518636185e226b2d55615c468d3:1","script_sig":"","sequence":0,"witness":[]}],"output":[{"value":299409,"script_pubkey":"0020f0803d588723e1db72b7a4267f40332a29ca20e77119631f8ef8c7623eda1e52"}]},"contract_redeemscript":"827ca914ea9e4cf47dbde043d19aee56626cfd04b8c00367876321029a39fbe3d3317aeeb5bc192c80daabfa247f359d69f53d47c167738099e65052012051672102d4a648b7471407f296763b5f0c127eee4e8ea30298f9f3fd4d468cf59f5cf1a800012868b2757b88ac","funding_amount":300409,"others_contract_sig":"304402203a8a39f3b10f16d449a4f69c645477f77469ed89046974a013a810498c50a64e022013069b0d2c83da76e9076110e1462cb119c8c9c9696eec494d976f94def213e8","hash_preimage":[113,213,255,157,128,32,173,191,68,183,26,13,137,60,210,94,212,31,142,201,14,36,142,152,233,244,164,234,212,22,220,237]},{"my_privkey":"03bbc30aef69bf3e3f2f4b09986dfb92db74685aa96210fa0632f0240fc40988","other_pubkey":"029abfc94e136c3a5a0dec6c8f6342f28914aadeda0652c8933684eae732d977a0","other_privkey":"85ae9f78b03707313aad20d2d49a0d2c6dc7d264a6b76d8fcdd64af9ee780bcd","contract_tx":{"version":2,"lock_time":0,"input":[{"previous_output":"1b9c5f8a98eaae17340935a159775cb1f7bfffb7d51a16cd4ab438b49d2429d9:0","script_sig":"","sequence":0,"witness":[]}],"output":[{"value":184638,"script_pubkey":"0020ef75aaecaa14f10a0d47dc8903f812b910c8f515aec0be1a40ae5b0aa78c32c3"}]},"contract_redeemscript":"827ca914ea9e4cf47dbde043d19aee56626cfd04b8c003678763210385997fae7f71c5365f12fb5935b2e414490f1687d8af7e11652b8d586286453d012051672102d4f13371cf7cdae594e8c0cc0923466da1e949b9d1598ee45ab63a29c5a10afc00012868b2757b88ac","funding_amount":185638,"others_contract_sig":"3045022100c500a5f0dabc11b5ef00e7d8d86698967db056e0861a8e846faef14b594c599e02204c14e69f0012aaccdb3b8e9f530e92c4a5ad1ca8393a8b9972004dfe5e95f579","hash_preimage":[113,213,255,157,128,32,173,191,68,183,26,13,137,60,210,94,212,31,142,201,14,36,142,152,233,244,164,234,212,22,220,237]},{"my_privkey":"0d1c9959c6f98fc9f592b9dcc7711876516fcefc7d52e10a49999b56274e1c96","other_pubkey":"037e14ea6666edfde1ae5fbfb22cf14270c585b204376093fd4d43dfec6fc7bf52","other_privkey":"1a17f6bec5b8efb9cc90885d89d935c806ca0504eeea6a7318e6ef07296baa8a","contract_tx":{"version":2,"lock_time":0,"input":[{"previous_output":"89a5833234654e73141f618f36e9b8761ebfb7620d72399102dc96d6fb952c69:1","script_sig":"","sequence":0,"witness":[]}],"output":[{"value":12953,"script_pubkey":"0020c71f42050d8d5871f072686bbf224e17221e35b19bc0ecd187ee01867ba26286"}]},"contract_redeemscript":"827ca914ea9e4cf47dbde043d19aee56626cfd04b8c00367876321020e441b4a42260cbe77e0966e637e4425e96abcdf56c551409efd22b808ef5b570120516721023d26a4dc6cf36b4ab830670575cf3e48bb46f39f5085a4de3a6601e12b2c9c6e00012868b2757b88ac","funding_amount":13953,"others_contract_sig":"3045022100edc3f048be820a0e198fe4c1bccd26f7cdf597eb2325598cf992075a85d8b1f5022079b9e7fb0bf6aaa7a3c72875e10d3559eb342ef30bd934091c80653f3bf8ca94","hash_preimage":[113,213,255,157,128,32,173,191,68,183,26,13,137,60,210,94,212,31,142,201,14,36,142,152,233,244,164,234,212,22,220,237]},{"my_privkey":"bf1091d959a74b93d1bf67dc13a612dc0995645f8fa5a39bab49dfc5c481c849","other_pubkey":"03ca14a3385ed820d965a128dbb1c01889ee7124fc78fc9bd81d78d9d814e9d258","other_privkey":null,"contract_tx":{"version":2,"lock_time":0,"input":[{"previous_output":"48fbb026e886b62510af51bbf4c0c98db74cafa6b3a1b2b79d2a6b6f4497e571:0","script_sig":"","sequence":0,"witness":[]}],"output":[{"value":103744,"script_pubkey":"0020746a10f5297b39178110147cd7a8556155ed9cb84ddbb95efebb8ccd49cb29a6"}]},"contract_redeemscript":"827ca914ea9e4cf47dbde043d19aee56626cfd04b8c0036787632103b957cb3dd4e948b20ddd373b69a99cc930338e6176c1e3c39f14a538fe2c1cca01205167210235195f96ed80e09ed173f3341adfecff2daeb2477bc838e42dc58701f8323ca000012368b2757b88ac","funding_amount":104744,"others_contract_sig":"3045022100a25c69fd3f0dbe77216e0da82595f40d5d623d4554f0003f21de0b992c02d33302202c78000103e2b0d4bac44a63e86c5596cf7ec6081163bf644aae97dc2a736167","hash_preimage":null},{"my_privkey":"adb75fc65c79acf0b7d0f5bdd81f472dfc40642ddd954085c28196905247b2a1","other_pubkey":"02dee44ca83956ec3515ec765856f5564891d189a1b41b05f35600edf28a89e5ba","other_privkey":null,"contract_tx":{"version":2,"lock_time":0,"input":[{"previous_output":"b3c6b06bca9d34adbe1c1db1dee49bda7081cd548f49a6df8bbec4d20b273f22:0","script_sig":"","sequence":0,"witness":[]}],"output":[{"value":320732,"script_pubkey":"002043375d594bac41249ceb5adf920a1f8e00da8405d454e7a2668edc861dc3b22b"}]},"contract_redeemscript":"827ca914ea9e4cf47dbde043d19aee56626cfd04b8c0036787632102426bbace45f850c3d417108bd4b9bf90c1915fb01eff3f1c807bdf6a294387db01205167210341891ac13fac292c782fa36cb1e23102130cc93f73ae2b9a1cbb613b773b097100012368b2757b88ac","funding_amount":321732,"others_contract_sig":"3045022100dcb7577cc885afa638f10bc5c5b5cef87039e76a6772cfc64f25e8124a9f277402202cdd8f7ac1dcf71bcd6661a685559b3fa753611fddadf745113e16232b00f85e","hash_preimage":null}],"prevout_to_contract_map":{"89a5833234654e73141f618f36e9b8761ebfb7620d72399102dc96d6fb952c69:1":"0020c71f42050d8d5871f072686bbf224e17221e35b19bc0ecd187ee01867ba26286","9c5aa7be4052ea601b6968dec58b305bc2920518636185e226b2d55615c468d3:1":"0020f0803d588723e1db72b7a4267f40332a29ca20e77119631f8ef8c7623eda1e52","1b9c5f8a98eaae17340935a159775cb1f7bfffb7d51a16cd4ab438b49d2429d9:0":"0020ef75aaecaa14f10a0d47dc8903f812b910c8f515aec0be1a40ae5b0aa78c32c3"}} \ No newline at end of file diff --git a/tests/maker-wallet-2 b/tests/maker-wallet-2 deleted file mode 100644 index d22c4f04..00000000 --- a/tests/maker-wallet-2 +++ /dev/null @@ -1 +0,0 @@ -{"version":0,"seedphrase":"object frown rate screen gap news neither open charge fish bottom people","extension":"","external_index":3,"swap_coins":[{"my_privkey":"2aed2223f52ca6d6e76ed1f6216221f0723ca0d277c7400eaf15e7ac76589945","other_pubkey":"0217a15cd4724fe456b69c76b979c895dc6960411bd6eb874e3de0987fd01c077c","other_privkey":"bf1091d959a74b93d1bf67dc13a612dc0995645f8fa5a39bab49dfc5c481c849","contract_tx":{"version":2,"lock_time":0,"input":[{"previous_output":"48fbb026e886b62510af51bbf4c0c98db74cafa6b3a1b2b79d2a6b6f4497e571:0","script_sig":"","sequence":0,"witness":[]}],"output":[{"value":103744,"script_pubkey":"0020746a10f5297b39178110147cd7a8556155ed9cb84ddbb95efebb8ccd49cb29a6"}]},"contract_redeemscript":"827ca914ea9e4cf47dbde043d19aee56626cfd04b8c0036787632103b957cb3dd4e948b20ddd373b69a99cc930338e6176c1e3c39f14a538fe2c1cca01205167210235195f96ed80e09ed173f3341adfecff2daeb2477bc838e42dc58701f8323ca000012368b2757b88ac","funding_amount":104744,"others_contract_sig":"304402201e4d19d95bbeb8ef942cfaf626c11d27723c2ef983003683833d4377588c3d91022051eac39262edf2d763771bfbc59f6938f1e060f61f61ac0e064aebba632fcb65","hash_preimage":[113,213,255,157,128,32,173,191,68,183,26,13,137,60,210,94,212,31,142,201,14,36,142,152,233,244,164,234,212,22,220,237]},{"my_privkey":"f34990982a5fd80e1eeff2d9fb03b9b45fe6a9a4890d8592d032f037fd91180e","other_pubkey":"0342daa9d2b0240074ef518bd5b3bd94d6982eb27bb96ecbbfa9d392abaa65d496","other_privkey":null,"contract_tx":{"version":2,"lock_time":0,"input":[{"previous_output":"fe8a5c3dd45cf4fff9e4a535300b36fcbafbcaefc4df8476f6c3e716a3efd69e:1","script_sig":"","sequence":0,"witness":[]}],"output":[{"value":78465,"script_pubkey":"0020b9aaa8d09644ccb68c27fdc3b13da6d73e0902f2be1200bbff64709c824d4e5d"}]},"contract_redeemscript":"827ca914ea9e4cf47dbde043d19aee56626cfd04b8c0036787632102b9fd4718f50d3e50d298c83d31454fb30df4542846fd01ad42a6a953951cafc90120516721037fdd30f2c4299d9b4a61888b56c136ad4a184e50ec693164c404f74b5dde8f7600011e68b2757b88ac","funding_amount":79465,"others_contract_sig":"3044022072879fceba0b24ef64d7f07381543d781d7b22b54de29e82eeed4dfc8fad859c02201447a7fb21c893f6f5cc0731292b3e6d2f87564ff9a9b8e12020e6d4656a3211","hash_preimage":null},{"my_privkey":"48760aa0a6887ae7549760e13effb21aa4f1bd3a6563a517952db230b02bd530","other_pubkey":"0202d1871243fa80f6444099bb8648f8003d394c03d7dbd456b32f2fee7d32a47a","other_privkey":"adb75fc65c79acf0b7d0f5bdd81f472dfc40642ddd954085c28196905247b2a1","contract_tx":{"version":2,"lock_time":0,"input":[{"previous_output":"b3c6b06bca9d34adbe1c1db1dee49bda7081cd548f49a6df8bbec4d20b273f22:0","script_sig":"","sequence":0,"witness":[]}],"output":[{"value":320732,"script_pubkey":"002043375d594bac41249ceb5adf920a1f8e00da8405d454e7a2668edc861dc3b22b"}]},"contract_redeemscript":"827ca914ea9e4cf47dbde043d19aee56626cfd04b8c0036787632102426bbace45f850c3d417108bd4b9bf90c1915fb01eff3f1c807bdf6a294387db01205167210341891ac13fac292c782fa36cb1e23102130cc93f73ae2b9a1cbb613b773b097100012368b2757b88ac","funding_amount":321732,"others_contract_sig":"3045022100debeff4c872db0fda34d8d46329908211397263636bf6c22f16e2060d04a641202201e318a714497192bd498e52c8db01903795a2465b6e1e1d3fc63eca628385b8b","hash_preimage":[113,213,255,157,128,32,173,191,68,183,26,13,137,60,210,94,212,31,142,201,14,36,142,152,233,244,164,234,212,22,220,237]},{"my_privkey":"e877167a1dbf1a78654b6d1acecd23c742cfa7f91e9d9b34d2b23642b85575af","other_pubkey":"03e71eae89d3a3a3aa7bc2b96e6c70166fd1dd0e2a04e10cfa1df700eba89f9302","other_privkey":null,"contract_tx":{"version":2,"lock_time":0,"input":[{"previous_output":"d643bcedabfd22d69e6da33e220fbca6718f90236d2a4255fbc0f41072481555:1","script_sig":"","sequence":0,"witness":[]}],"output":[{"value":19540,"script_pubkey":"0020652e9af6e10e67fa98b7573e4bcd7ee3d9fd47e7824547a10e789ad2e764a384"}]},"contract_redeemscript":"827ca914ea9e4cf47dbde043d19aee56626cfd04b8c00367876321037ee9c19397b6fb27bf5d6d2d2c7b41bf732969fdf2a0ade71aa66b402e7d42c40120516721035bb6efc995d1a7758b0e2d6205414d5485e1696a5e026bc4fdf20fb59c1fa05e00011e68b2757b88ac","funding_amount":20540,"others_contract_sig":"304402206df389c68cd7628813aa8be1fb8bf92072c35325a2f39606694427d41e1b53c5022076ed5163b7aba07cde098448d81d2083d423e106622c094128f722896a419853","hash_preimage":null},{"my_privkey":"17870d17c408f2ddd5c4fcdd0dded58c4d7ed2a319573d4e653fde3bbc9dc0aa","other_pubkey":"0322fabfbaf35a95e0f234e0756a250a848aa1f5d3711a2897113d76f5ed320084","other_privkey":null,"contract_tx":{"version":2,"lock_time":0,"input":[{"previous_output":"cb91220bd130eb2fbf3917ee63f80f1eab9782a20d838fcdf8b078c398fe3035:0","script_sig":"","sequence":0,"witness":[]}],"output":[{"value":378995,"script_pubkey":"0020adde8ea0f03b47db4fb9aa53222f42e81016a1936302ccf69cff97fe7a418c03"}]},"contract_redeemscript":"827ca914ea9e4cf47dbde043d19aee56626cfd04b8c0036787632102c051da7ef8005f7ac65f66d1c661e40eab5cd1f88358afbddfd2f754937d90d30120516721028bbf40839cc1f70c54b5f2a8c0294ee11fd2d7649ff77e0cb0a950b954b8cb1000011e68b2757b88ac","funding_amount":379995,"others_contract_sig":"30440220679fcbaf8d210b3aafd38c4b76e0a158e932e7756143c4e61e76d3f5f781cc6e02201279d2c4364811d7bc18c94dff2c9d093178001e8238aceb8b5d080a63c256a7","hash_preimage":null},{"my_privkey":"4ab095d0609d3ef82d32d7d9c048f78e8bfd4624e5809a82a73381c510e8a6c7","other_pubkey":"03c6d55e2ebb0503e467dfbb146ee16da00855e4f7b66fdaf6d3866017f57767ce","other_privkey":"69c389994c3d4d38a5d503e6aec7184ca6740f66ce22d0716709ba47647f1d24","contract_tx":{"version":2,"lock_time":0,"input":[{"previous_output":"834f56b05d5679e173a9c0a34f0cfe2e156eebd77f796c781bd2b2601afacefd:0","script_sig":"","sequence":0,"witness":[]}],"output":[{"value":62524,"script_pubkey":"0020f393d97d25c7fdfe3882a7dd00990965351bbf46833bbe5f6b95dadfa0e288ad"}]},"contract_redeemscript":"827ca914ea9e4cf47dbde043d19aee56626cfd04b8c0036787632103df8a315ace6644e6eef9d693d0174f0312a4d83c679ceeed58152f4373b232a7012051672103ddd328991ea9623d446fa1a54a17514ff12690cb12a9414b9a4fb62dbfd3808800012368b2757b88ac","funding_amount":63524,"others_contract_sig":"30440220522bf02c00c1fe8e1136c690897b4ce38f6e94335c9978d95d9372fe5102651902204435b1aa52557458c11d82023486a2aef18ff7d731e1d5fca8117e6214ed7010","hash_preimage":[113,213,255,157,128,32,173,191,68,183,26,13,137,60,210,94,212,31,142,201,14,36,142,152,233,244,164,234,212,22,220,237]}],"prevout_to_contract_map":{"834f56b05d5679e173a9c0a34f0cfe2e156eebd77f796c781bd2b2601afacefd:0":"0020f393d97d25c7fdfe3882a7dd00990965351bbf46833bbe5f6b95dadfa0e288ad","b3c6b06bca9d34adbe1c1db1dee49bda7081cd548f49a6df8bbec4d20b273f22:0":"002043375d594bac41249ceb5adf920a1f8e00da8405d454e7a2668edc861dc3b22b","48fbb026e886b62510af51bbf4c0c98db74cafa6b3a1b2b79d2a6b6f4497e571:0":"0020746a10f5297b39178110147cd7a8556155ed9cb84ddbb95efebb8ccd49cb29a6"}} \ No newline at end of file diff --git a/tests/taker-wallet b/tests/taker-wallet deleted file mode 100644 index 335ad849..00000000 --- a/tests/taker-wallet +++ /dev/null @@ -1 +0,0 @@ -{"version":0,"seedphrase":"popular fun bomb steak know solution era rib found sweet clog sorry","extension":"","external_index":3,"swap_coins":[{"my_privkey":"a5d9cd18a5dd2997da97b7ece863d3d18a74884c78664c48f8215c2c8968a487","other_pubkey":"029853beadd66e0fc4591a238f0801f32a91dfb21b91517d9e64f1ba15f77371f9","other_privkey":"17870d17c408f2ddd5c4fcdd0dded58c4d7ed2a319573d4e653fde3bbc9dc0aa","contract_tx":{"version":2,"lock_time":0,"input":[{"previous_output":"cb91220bd130eb2fbf3917ee63f80f1eab9782a20d838fcdf8b078c398fe3035:0","script_sig":"","sequence":0,"witness":[]}],"output":[{"value":378995,"script_pubkey":"0020adde8ea0f03b47db4fb9aa53222f42e81016a1936302ccf69cff97fe7a418c03"}]},"contract_redeemscript":"827ca914ea9e4cf47dbde043d19aee56626cfd04b8c0036787632102c051da7ef8005f7ac65f66d1c661e40eab5cd1f88358afbddfd2f754937d90d30120516721028bbf40839cc1f70c54b5f2a8c0294ee11fd2d7649ff77e0cb0a950b954b8cb1000011e68b2757b88ac","funding_amount":379995,"others_contract_sig":"3045022100c62553bc3a5a8280ff10c0627feda7405b3833e36aece571bf1238c770545bd9022066d469254d38fefc224033586b5903cb55ba2d22d39e90a222f954c5251a142e","hash_preimage":[113,213,255,157,128,32,173,191,68,183,26,13,137,60,210,94,212,31,142,201,14,36,142,152,233,244,164,234,212,22,220,237]},{"my_privkey":"9378b6e9e10b08e94c00c38adc09328d4653f30a83eae0fae24b44d07a51f3a0","other_pubkey":"033e5537129be584d294d50a16f2852aed93058a273f9e8839e2f85e83b78876ac","other_privkey":"f34990982a5fd80e1eeff2d9fb03b9b45fe6a9a4890d8592d032f037fd91180e","contract_tx":{"version":2,"lock_time":0,"input":[{"previous_output":"fe8a5c3dd45cf4fff9e4a535300b36fcbafbcaefc4df8476f6c3e716a3efd69e:1","script_sig":"","sequence":0,"witness":[]}],"output":[{"value":78465,"script_pubkey":"0020b9aaa8d09644ccb68c27fdc3b13da6d73e0902f2be1200bbff64709c824d4e5d"}]},"contract_redeemscript":"827ca914ea9e4cf47dbde043d19aee56626cfd04b8c0036787632102b9fd4718f50d3e50d298c83d31454fb30df4542846fd01ad42a6a953951cafc90120516721037fdd30f2c4299d9b4a61888b56c136ad4a184e50ec693164c404f74b5dde8f7600011e68b2757b88ac","funding_amount":79465,"others_contract_sig":"304402204531c3d749e6288f44997f6b10e817298312937406a602c75b7da22987f5bece0220197ad60990ca73acdc760816960278aede32addb9d99bd899d55d7f4f02f97b4","hash_preimage":[113,213,255,157,128,32,173,191,68,183,26,13,137,60,210,94,212,31,142,201,14,36,142,152,233,244,164,234,212,22,220,237]},{"my_privkey":"5e3dca8d25a766431d9d94e55b690a9e207241c8be3e0887e3efdd0b22f65997","other_pubkey":"03a07255a6facdc77e6c4cfc7bc2afe0236d4b5a25a405b988b75a61c84e1ccf51","other_privkey":"e877167a1dbf1a78654b6d1acecd23c742cfa7f91e9d9b34d2b23642b85575af","contract_tx":{"version":2,"lock_time":0,"input":[{"previous_output":"d643bcedabfd22d69e6da33e220fbca6718f90236d2a4255fbc0f41072481555:1","script_sig":"","sequence":0,"witness":[]}],"output":[{"value":19540,"script_pubkey":"0020652e9af6e10e67fa98b7573e4bcd7ee3d9fd47e7824547a10e789ad2e764a384"}]},"contract_redeemscript":"827ca914ea9e4cf47dbde043d19aee56626cfd04b8c00367876321037ee9c19397b6fb27bf5d6d2d2c7b41bf732969fdf2a0ade71aa66b402e7d42c40120516721035bb6efc995d1a7758b0e2d6205414d5485e1696a5e026bc4fdf20fb59c1fa05e00011e68b2757b88ac","funding_amount":20540,"others_contract_sig":"3045022100dd22baef46d7e3187b76d7fae77ff65da66c5e884142ac965c64bd1ff0a621bd02205e47405f98e18ef1372cf255e8d01522bf2b2127318767ddbb9a7f9f6e3e63da","hash_preimage":[113,213,255,157,128,32,173,191,68,183,26,13,137,60,210,94,212,31,142,201,14,36,142,152,233,244,164,234,212,22,220,237]}],"prevout_to_contract_map":{}} \ No newline at end of file diff --git a/tests/test_standard_coinswap.rs b/tests/test_standard_coinswap.rs new file mode 100644 index 00000000..24bb3038 --- /dev/null +++ b/tests/test_standard_coinswap.rs @@ -0,0 +1,226 @@ +use bitcoin::util::amount::Amount; +use bitcoin_wallet::mnemonic; +use bitcoincore_rpc::{Client, RpcApi}; + +use teleport::wallet_sync::{Wallet, WalletSyncAddressAmount}; + +use serde_json::Value; + +use std::path::PathBuf; +use std::sync::{Arc, RwLock}; +use std::{thread, time}; + +use std::str::FromStr; + +static TAKER: &str = "tests/taker-wallet"; +static MAKER1: &str = "tests/maker-wallet-1"; +static MAKER2: &str = "tests/maker-wallet-2"; + +// Helper function to create new wallet +fn create_wallet_and_import(rpc: &Client, filename: PathBuf) -> Wallet { + let mnemonic = + mnemonic::Mnemonic::new_random(bitcoin_wallet::account::MasterKeyEntropy::Sufficient) + .unwrap(); + + Wallet::save_new_wallet_file(&filename, mnemonic.to_string(), "".to_string()).unwrap(); + + let wallet = Wallet::load_wallet_from_file(filename, WalletSyncAddressAmount::Testing).unwrap(); + // import intital addresses to core + wallet + .import_initial_addresses( + &rpc, + &wallet + .get_hd_wallet_descriptors(&rpc) + .unwrap() + .iter() + .collect::>(), + &Vec::<_>::new(), + ) + .unwrap(); + + wallet +} + +pub fn generate_1_block(rpc: &Client) { + rpc.generate_to_address(1, &rpc.get_new_address(None, None).unwrap()) + .unwrap(); +} + +// This test requires a bitcoin regtest node running in local machine with a +// wallet name `teleport` loaded and have enough balance to execute transactions. +#[tokio::test] +async fn test_standard_coinswap() { + teleport::setup_logger(); + + let rpc = teleport::get_bitcoin_rpc().unwrap(); + + // unlock all utxos to avoid "insufficient fund" error + rpc.call::("lockunspent", &[Value::Bool(true)]) + .unwrap(); + + // create taker wallet + let mut taker_wallet = create_wallet_and_import(&rpc, TAKER.into()); + + // create maker1 wallet + let mut maker1_wallet = create_wallet_and_import(&rpc, MAKER1.into()); + + // create maker2 wallet + let mut maker2_wallet = create_wallet_and_import(&rpc, MAKER2.into()); + + // Check files are created + assert!(std::path::Path::new(TAKER).exists()); + assert!(std::path::Path::new(MAKER1).exists()); + assert!(std::path::Path::new(MAKER2).exists()); + + // Create 3 taker and maker address and send 0.05 btc to each + for _ in 0..3 { + let taker_address = taker_wallet.get_next_external_address(&rpc).unwrap(); + let maker1_address = maker1_wallet.get_next_external_address(&rpc).unwrap(); + let maker2_address = maker2_wallet.get_next_external_address(&rpc).unwrap(); + + rpc.send_to_address( + &taker_address, + Amount::from_btc(0.05).unwrap(), + None, + None, + None, + None, + None, + None, + ) + .unwrap(); + rpc.send_to_address( + &maker1_address, + Amount::from_btc(0.05).unwrap(), + None, + None, + None, + None, + None, + None, + ) + .unwrap(); + rpc.send_to_address( + &maker2_address, + Amount::from_btc(0.05).unwrap(), + None, + None, + None, + None, + None, + None, + ) + .unwrap(); + } + + generate_1_block(&rpc); + + // Check inital wallet assertions + assert_eq!(taker_wallet.get_external_index(), 3); + assert_eq!(maker1_wallet.get_external_index(), 3); + assert_eq!(maker2_wallet.get_external_index(), 3); + + assert_eq!( + taker_wallet.list_unspent_from_wallet(&rpc).unwrap().len(), + 3 + ); + assert_eq!( + maker1_wallet.list_unspent_from_wallet(&rpc).unwrap().len(), + 3 + ); + assert_eq!( + maker2_wallet.list_unspent_from_wallet(&rpc).unwrap().len(), + 3 + ); + + assert_eq!(taker_wallet.lock_all_nonwallet_unspents(&rpc).unwrap(), ()); + assert_eq!(maker1_wallet.lock_all_nonwallet_unspents(&rpc).unwrap(), ()); + assert_eq!(maker2_wallet.lock_all_nonwallet_unspents(&rpc).unwrap(), ()); + + let kill_flag = Arc::new(RwLock::new(false)); + + // Start watchtower, makers and taker to execute a coinswap + let kill_flag_watchtower = kill_flag.clone(); + let watchtower_thread = thread::spawn(|| { + teleport::run_watchtower(Some(kill_flag_watchtower)); + }); + + let kill_flag_maker1 = kill_flag.clone(); + let maker1_thread = thread::spawn(|| { + teleport::run_maker( + &PathBuf::from_str(MAKER1).unwrap(), + WalletSyncAddressAmount::Testing, + 6102, + None, + Some(kill_flag_maker1), + ); + }); + + let kill_flag_maker2 = kill_flag.clone(); + let maker2_thread = thread::spawn(|| { + teleport::run_maker( + &PathBuf::from_str(MAKER2).unwrap(), + WalletSyncAddressAmount::Testing, + 16102, + None, + Some(kill_flag_maker2), + ); + }); + + let taker_thread = thread::spawn(|| { + // Wait and then start the taker + thread::sleep(time::Duration::from_secs(5)); + teleport::run_taker( + &PathBuf::from_str(TAKER).unwrap(), + WalletSyncAddressAmount::Testing, + ); + }); + + let kill_flag_block_creation_thread = kill_flag.clone(); + let rpc_ptr = Arc::new(rpc); + let block_creation_thread = thread::spawn(move || { + while !*kill_flag_block_creation_thread.read().unwrap() { + thread::sleep(time::Duration::from_secs(5)); + generate_1_block(&rpc_ptr); + println!("created block"); + } + println!("ending block creation thread"); + }); + + taker_thread.join().unwrap(); + *kill_flag.write().unwrap() = true; + maker1_thread.join().unwrap(); + maker2_thread.join().unwrap(); + watchtower_thread.join().unwrap(); + block_creation_thread.join().unwrap(); + + // Recreate the wallet + let taker_wallet = + Wallet::load_wallet_from_file(&TAKER, WalletSyncAddressAmount::Testing).unwrap(); + let maker1_wallet = + Wallet::load_wallet_from_file(&MAKER1, WalletSyncAddressAmount::Testing).unwrap(); + let maker2_wallet = + Wallet::load_wallet_from_file(&MAKER2, WalletSyncAddressAmount::Testing).unwrap(); + + // Check assertions + assert_eq!(taker_wallet.get_swap_coins_count(), 6); + assert_eq!(maker1_wallet.get_swap_coins_count(), 6); + assert_eq!(maker2_wallet.get_swap_coins_count(), 6); + + let rpc = teleport::get_bitcoin_rpc().unwrap(); + + let utxos = taker_wallet.list_unspent_from_wallet(&rpc).unwrap(); + let balance: Amount = utxos.iter().fold(Amount::ZERO, |acc, u| acc + u.amount); + assert_eq!(utxos.len(), 6); + assert!(balance < Amount::from_btc(0.15).unwrap()); + + let utxos = maker1_wallet.list_unspent_from_wallet(&rpc).unwrap(); + let balance: Amount = utxos.iter().fold(Amount::ZERO, |acc, u| acc + u.amount); + assert_eq!(utxos.len(), 6); + assert!(balance > Amount::from_btc(0.15).unwrap()); + + let utxos = maker2_wallet.list_unspent_from_wallet(&rpc).unwrap(); + let balance: Amount = utxos.iter().fold(Amount::ZERO, |acc, u| acc + u.amount); + assert_eq!(utxos.len(), 6); + assert!(balance > Amount::from_btc(0.15).unwrap()); +}