From 305907fc428b4a2e8fa0e3ca3ff54561a99934bc Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Thu, 2 Nov 2023 23:21:23 -0600 Subject: [PATCH] integration: broadcast withdrawal --- devenv/integration/bin/test | 2 +- romeo/tests/tests/mod.rs | 1 + romeo/tests/tests/withdrawal.rs | 89 +++++++++++++++++++++++++++++++ sbtc-cli/src/commands/withdraw.rs | 55 ++++++++----------- sbtc-cli/src/main.rs | 25 +++++---- 5 files changed, 130 insertions(+), 42 deletions(-) create mode 100644 romeo/tests/tests/withdrawal.rs diff --git a/devenv/integration/bin/test b/devenv/integration/bin/test index f5ef976c..b6197927 100755 --- a/devenv/integration/bin/test +++ b/devenv/integration/bin/test @@ -9,7 +9,7 @@ project_name() { echo "$filter" } -filters=("test(deposit)") +filters=("test(deposit)" "test(withdrawal)") filter_union="" ids=() projects=() diff --git a/romeo/tests/tests/mod.rs b/romeo/tests/tests/mod.rs index b992645d..a68ddb23 100644 --- a/romeo/tests/tests/mod.rs +++ b/romeo/tests/tests/mod.rs @@ -1,6 +1,7 @@ pub mod bitcoin_client; pub mod deposit; pub mod stacks_client; +pub mod withdrawal; use std::ops::Index; diff --git a/romeo/tests/tests/withdrawal.rs b/romeo/tests/tests/withdrawal.rs new file mode 100644 index 00000000..7083888d --- /dev/null +++ b/romeo/tests/tests/withdrawal.rs @@ -0,0 +1,89 @@ +use std::{thread::sleep, time::Duration}; + +use bdk::{ + bitcoin::{psbt::serialize::Serialize, PrivateKey}, + blockchain::{ + ConfigurableBlockchain, ElectrumBlockchain, ElectrumBlockchainConfig, + }, + database::MemoryDatabase, + template::P2Wpkh, + SyncOptions, Wallet, +}; +use reqwest::blocking::Client; +use sbtc_cli::commands::{ + broadcast::{broadcast_tx, BroadcastArgs}, + withdraw::{build_withdrawal_tx, WithdrawalArgs}, +}; + +use super::{ + bitcoin_client::{electrs_url, generate_blocks}, + KeyType::*, + WALLETS, +}; + +#[test] +fn broadcast_withdrawal() { + let client = Client::new(); + { + generate_blocks(1, &client, WALLETS[0][P2wpkh].address); + generate_blocks(1, &client, WALLETS[1][P2wpkh].address); + // pads blocks to get rewards. + generate_blocks(100, &client, WALLETS[0][P2wpkh].address); + }; + + let electrum_url = electrs_url(); + + // suboptimal, replace once we have better events. + { + let blockchain = + ElectrumBlockchain::from_config(&ElectrumBlockchainConfig { + url: electrum_url.clone().into(), + socks5: None, + retry: 3, + timeout: Some(10), + stop_gap: 10, + validate_domain: false, + }) + .unwrap(); + + let private_key = PrivateKey::from_wif(WALLETS[0][P2wpkh].wif).unwrap(); + + let wallet = Wallet::new( + P2Wpkh(private_key), + Some(P2Wpkh(private_key)), + bdk::bitcoin::Network::Regtest, + MemoryDatabase::default(), + ) + .unwrap(); + + loop { + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + let balance = wallet.get_balance().unwrap(); + if balance.confirmed != 0 { + break; + } + sleep(Duration::from_millis(1_000)); + } + } + + // amount random [1000,2000) + // fee random [1000,2000) + let args = WithdrawalArgs { + node_url: electrs_url(), + network: bdk::bitcoin::Network::Regtest, + wif: WALLETS[1][P2wpkh].wif.into(), + drawee_wif: WALLETS[1][Stacks].wif.into(), + payee_address: WALLETS[1][P2wpkh].address.into(), + amount: 2000, + fulfillment_fee: 2000, + sbtc_wallet: WALLETS[0][P2tr].address.into(), + }; + + let tx = build_withdrawal_tx(&args).unwrap(); + + broadcast_tx(&BroadcastArgs { + node_url: electrum_url, + tx: hex::encode(tx.serialize()), + }) + .unwrap(); +} diff --git a/sbtc-cli/src/commands/withdraw.rs b/sbtc-cli/src/commands/withdraw.rs index 774dce2f..0f431b4a 100644 --- a/sbtc-cli/src/commands/withdraw.rs +++ b/sbtc-cli/src/commands/withdraw.rs @@ -1,8 +1,8 @@ -use std::{io::stdout, str::FromStr}; +use std::str::FromStr; use bdk::{ bitcoin::{ - psbt::serialize::Serialize, Address as BitcoinAddress, + blockdata::transaction::Transaction, Address as BitcoinAddress, Network as BitcoinNetwork, PrivateKey, }, blockchain::{ @@ -15,45 +15,45 @@ use bdk::{ use clap::Parser; use url::Url; -use crate::commands::utils::TransactionData; - #[derive(Parser, Debug, Clone)] pub struct WithdrawalArgs { /// Where to broadcast the transaction #[clap(short('u'), long)] - node_url: Url, + pub node_url: Url, /// Bitcoin network where the deposit will be broadcasted to #[clap(short, long)] - network: BitcoinNetwork, + pub network: BitcoinNetwork, /// WIF of the Bitcoin P2WPKH address that will broadcast and pay for the /// withdrawal request #[clap(short, long)] - wif: String, + pub wif: String, /// WIF of the Stacks address that owns sBTC to be withdrawn #[clap(short, long)] - drawee_wif: String, + pub drawee_wif: String, /// Bitcoin address that will receive BTC #[clap(short('b'), long)] - payee_address: String, + pub payee_address: String, /// The amount of sats to withdraw #[clap(short, long)] - amount: u64, + pub amount: u64, /// The amount of sats to send for the fulfillment fee #[clap(short, long)] - fulfillment_fee: u64, + pub fulfillment_fee: u64, /// Bitcoin address of the sbtc wallet #[clap(short, long)] - sbtc_wallet: String, + pub sbtc_wallet: String, } -pub fn build_withdrawal_tx(withdrawal: &WithdrawalArgs) -> anyhow::Result<()> { +pub fn build_withdrawal_tx( + withdrawal: &WithdrawalArgs, +) -> anyhow::Result { let private_key = PrivateKey::from_wif(&withdrawal.wif)?; let blockchain = @@ -82,23 +82,14 @@ pub fn build_withdrawal_tx(withdrawal: &WithdrawalArgs) -> anyhow::Result<()> { let sbtc_wallet_bitcoin_address = BitcoinAddress::from_str(&withdrawal.sbtc_wallet)?; - let tx = sbtc_core::operations::op_return::withdrawal_request::build_withdrawal_tx( - &wallet, - withdrawal.network, - drawee_stacks_private_key, - payee_bitcoin_address, - sbtc_wallet_bitcoin_address, - withdrawal.amount, - withdrawal.fulfillment_fee, - )?; - - serde_json::to_writer_pretty( - stdout(), - &TransactionData { - id: tx.txid().to_string(), - hex: hex::encode(tx.serialize()), - }, - )?; - - Ok(()) + sbtc_core::operations::op_return::withdrawal_request::build_withdrawal_tx( + &wallet, + withdrawal.network, + drawee_stacks_private_key, + payee_bitcoin_address, + sbtc_wallet_bitcoin_address, + withdrawal.amount, + withdrawal.fulfillment_fee, + ) + .map_err(|e| e.into()) } diff --git a/sbtc-cli/src/main.rs b/sbtc-cli/src/main.rs index 5a163375..51d0660f 100644 --- a/sbtc-cli/src/main.rs +++ b/sbtc-cli/src/main.rs @@ -7,7 +7,7 @@ //! and interact with the Bitcoin and Stacks networks. use std::io::stdout; -use bdk::bitcoin::psbt::serialize::Serialize; +use bdk::bitcoin::{psbt::serialize::Serialize, Transaction}; use clap::{Parser, Subcommand}; use sbtc_cli::commands::{ broadcast::{broadcast_tx, BroadcastArgs}, @@ -31,23 +31,30 @@ enum Command { GenerateFrom(GenerateArgs), } +fn to_stdout_pretty(txn: Transaction) -> serde_json::Result<()> { + serde_json::to_writer_pretty( + stdout(), + &utils::TransactionData { + id: txn.txid().to_string(), + hex: hex::encode(txn.serialize()), + }, + ) +} + fn main() -> Result<(), anyhow::Error> { let args = Cli::parse(); match args.command { Command::Deposit(deposit_args) => build_deposit_tx(&deposit_args) .and_then(|t| { - serde_json::to_writer_pretty( - stdout(), - &utils::TransactionData { - id: t.txid().to_string(), - hex: hex::encode(t.serialize()), - }, - )?; + to_stdout_pretty(t)?; Ok(()) }), Command::Withdraw(withdrawal_args) => { - build_withdrawal_tx(&withdrawal_args) + build_withdrawal_tx(&withdrawal_args).and_then(|t| { + to_stdout_pretty(t)?; + Ok(()) + }) } Command::Broadcast(broadcast_args) => broadcast_tx(&broadcast_args), Command::GenerateFrom(generate_args) => generate(&generate_args),