diff --git a/swap/src/bitcoin.rs b/swap/src/bitcoin.rs index e7b9b2bf..e7236a67 100644 --- a/swap/src/bitcoin.rs +++ b/swap/src/bitcoin.rs @@ -37,11 +37,6 @@ use serde::{Deserialize, Serialize}; use sha2::Sha256; use std::str::FromStr; -pub use crate::bitcoin::cancel::ESTIMATED_WEIGHT as TX_CANCEL_ESTIMATED_WEIGHT; -pub use crate::bitcoin::punish::ESTIMATED_WEIGHT as TX_PUNISH_ESTIMATED_WEIGHT; -pub use crate::bitcoin::redeem::ESTIMATED_WEIGHT as TX_REDEEM_ESTIMATED_WEIGHT; -pub use crate::bitcoin::refund::ESTIMATED_WEIGHT as TX_REFUND_ESTIMATED_WEIGHT; - #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] pub struct SecretKey { inner: Scalar, @@ -259,6 +254,12 @@ pub struct NotThreeWitnesses(usize); #[cfg(test)] mod tests { use super::*; + use crate::bitcoin::wallet::EstimateFeeRate; + use crate::env::{GetConfig, Regtest}; + use crate::protocol::{alice, bob}; + use bdk::FeeRate; + use rand::rngs::OsRng; + use uuid::Uuid; #[test] fn lock_confirmations_le_to_cancel_timelock_no_timelock_expired() { @@ -304,4 +305,120 @@ mod tests { assert_eq!(expired_timelock, ExpiredTimelocks::Punish) } + + struct StaticFeeRate {} + impl EstimateFeeRate for StaticFeeRate { + fn estimate_feerate(&self, _target_block: usize) -> Result { + Ok(FeeRate::default_min_relay_fee()) + } + + fn min_relay_fee(&self) -> Result { + Ok(bitcoin::Amount::from_sat(1_000)) + } + } + + #[tokio::test] + async fn calculate_transaction_weights() { + let alice_wallet = Wallet::new_funded(Amount::ONE_BTC.as_sat(), StaticFeeRate {}); + let bob_wallet = Wallet::new_funded(Amount::ONE_BTC.as_sat(), StaticFeeRate {}); + let spending_fee = Amount::from_sat(1_000); + let btc_amount = Amount::from_sat(500_000); + let xmr_amount = crate::monero::Amount::from_piconero(10000); + + let tx_redeem_fee = alice_wallet.estimate_fee(TxRedeem::weight()).await.unwrap(); + let tx_punish_fee = alice_wallet.estimate_fee(TxPunish::weight()).await.unwrap(); + let redeem_address = alice_wallet.new_address().await.unwrap(); + let punish_address = alice_wallet.new_address().await.unwrap(); + + let config = Regtest::get_config(); + let alice_state0 = alice::State0::new( + btc_amount, + xmr_amount, + config, + redeem_address, + punish_address, + tx_redeem_fee, + tx_punish_fee, + &mut OsRng, + ) + .unwrap(); + + let bob_state0 = bob::State0::new( + Uuid::new_v4(), + &mut OsRng, + btc_amount, + xmr_amount, + config.bitcoin_cancel_timelock, + config.bitcoin_punish_timelock, + bob_wallet.new_address().await.unwrap(), + config.monero_finality_confirmations, + spending_fee, + spending_fee, + ); + + let message0 = bob_state0.next_message(); + + let (_, alice_state1) = alice_state0.receive(message0).unwrap(); + let alice_message1 = alice_state1.next_message(); + + let bob_state1 = bob_state0 + .receive(&bob_wallet, alice_message1) + .await + .unwrap(); + let bob_message2 = bob_state1.next_message(); + + let alice_state2 = alice_state1.receive(bob_message2).unwrap(); + let alice_message3 = alice_state2.next_message(); + + let bob_state2 = bob_state1.receive(alice_message3).unwrap(); + let bob_message4 = bob_state2.next_message(); + + let alice_state3 = alice_state2.receive(bob_message4).unwrap(); + + let (bob_state3, _tx_lock) = bob_state2.lock_btc().await.unwrap(); + let bob_state4 = bob_state3.xmr_locked(monero_rpc::wallet::BlockHeight { height: 0 }); + let encrypted_signature = bob_state4.tx_redeem_encsig(); + let bob_state6 = bob_state4.cancel(); + + let cancel_transaction = alice_state3.signed_cancel_transaction().unwrap(); + let punish_transaction = alice_state3.signed_punish_transaction().unwrap(); + let redeem_transaction = alice_state3 + .signed_redeem_transaction(encrypted_signature) + .unwrap(); + let refund_transaction = bob_state6.signed_refund_transaction().unwrap(); + + assert_weight( + redeem_transaction.get_weight(), + TxRedeem::weight(), + "TxRedeem", + ); + assert_weight( + cancel_transaction.get_weight(), + TxCancel::weight(), + "TxCancel", + ); + assert_weight( + punish_transaction.get_weight(), + TxPunish::weight(), + "TxPunish", + ); + assert_weight( + refund_transaction.get_weight(), + TxRefund::weight(), + "TxRefund", + ); + } + + // Weights fluctuate -+2 wu assuming because of the length of the signatures. + fn assert_weight(is_weight: usize, expected_weight: usize, tx_name: &str) { + assert!( + is_weight + 1 == expected_weight + || is_weight + 2 == expected_weight + || is_weight == expected_weight, + "{} to have weight {}, but was {}", + tx_name, + expected_weight, + is_weight + ) + } } diff --git a/swap/src/bitcoin/cancel.rs b/swap/src/bitcoin/cancel.rs index 8b182a1a..85cc233a 100644 --- a/swap/src/bitcoin/cancel.rs +++ b/swap/src/bitcoin/cancel.rs @@ -13,10 +13,6 @@ use std::cmp::Ordering; use std::collections::HashMap; use std::ops::Add; -// Taken from https://mempool.space/testnet/tx/8da32d6cada4903c7043563c50cacce656a8be9e02f233201996739df5368b1f -// The weight might fluctuate slightly in reality. -pub const ESTIMATED_WEIGHT: usize = 485; - /// Represent a timelock, expressed in relative block height as defined in /// [BIP68](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki). /// E.g. The timelock expires 10 blocks after the reference transaction is @@ -243,6 +239,10 @@ impl TxCancel { output: vec![tx_out], } } + + pub fn weight() -> usize { + 596 + } } impl Watchable for TxCancel { diff --git a/swap/src/bitcoin/punish.rs b/swap/src/bitcoin/punish.rs index 5dced7c4..674d93a2 100644 --- a/swap/src/bitcoin/punish.rs +++ b/swap/src/bitcoin/punish.rs @@ -7,10 +7,6 @@ use bdk::bitcoin::Script; use miniscript::{Descriptor, DescriptorTrait}; use std::collections::HashMap; -// Taken from https://mempool.space/testnet/tx/ed4d60bc1fd172feca444ed3d06cccb90346b9098c2d28d2d034dac66f608f68 -// The weight might fluctuate slightly in reality. -pub const ESTIMATED_WEIGHT: usize = 547; - #[derive(Debug)] pub struct TxPunish { inner: Transaction, @@ -77,6 +73,10 @@ impl TxPunish { Ok(tx_punish) } + + pub fn weight() -> usize { + 548 + } } impl Watchable for TxPunish { diff --git a/swap/src/bitcoin/redeem.rs b/swap/src/bitcoin/redeem.rs index 6a5662c5..39c237fb 100644 --- a/swap/src/bitcoin/redeem.rs +++ b/swap/src/bitcoin/redeem.rs @@ -15,10 +15,6 @@ use miniscript::{Descriptor, DescriptorTrait}; use sha2::Sha256; use std::collections::HashMap; -// Taken from https://mempool.space/testnet/tx/80c265f5c3862075aab2bd8c94e261b092bc13a34294c6fb4071717fbf46c801 -// The weight might fluctuate slightly in reality. -pub const ESTIMATED_WEIGHT: usize = 547; - #[derive(Clone, Debug)] pub struct TxRedeem { inner: Transaction, @@ -137,6 +133,15 @@ impl TxRedeem { Ok(sig) } + + pub fn weight() -> usize { + 548 + } + + #[cfg(test)] + pub fn inner(&self) -> Transaction { + self.inner.clone() + } } impl Watchable for TxRedeem { diff --git a/swap/src/bitcoin/refund.rs b/swap/src/bitcoin/refund.rs index 5b6af9b1..a0f3dc64 100644 --- a/swap/src/bitcoin/refund.rs +++ b/swap/src/bitcoin/refund.rs @@ -11,10 +11,6 @@ use ecdsa_fun::Signature; use miniscript::{Descriptor, DescriptorTrait}; use std::collections::HashMap; -// Taken from https://mempool.space/testnet/tx/10aef570973bcf524b1a6e8d2eaf3bc1522e776381fc7520fd8987fba96e5424 -// The weight might fluctuate slightly in reality. -pub const ESTIMATED_WEIGHT: usize = 547; - #[derive(Debug)] pub struct TxRefund { inner: Transaction, @@ -141,6 +137,10 @@ impl TxRefund { Ok(sig) } + + pub fn weight() -> usize { + 548 + } } impl Watchable for TxRefund { diff --git a/swap/src/protocol/alice/event_loop.rs b/swap/src/protocol/alice/event_loop.rs index fb2e0879..fc38bb42 100644 --- a/swap/src/protocol/alice/event_loop.rs +++ b/swap/src/protocol/alice/event_loop.rs @@ -1,5 +1,4 @@ use crate::asb::Rate; -use crate::bitcoin::TX_PUNISH_ESTIMATED_WEIGHT; use crate::database::Database; use crate::env::Config; use crate::monero::BalanceTooLow; @@ -165,12 +164,24 @@ where // TODO: This should be cleaned up. let tx_redeem_fee = self.bitcoin_wallet .estimate_fee(bitcoin::TxRedeem::weight()) - .await.unwrap(); + .await; let tx_punish_fee = self.bitcoin_wallet - .estimate_fee(TX_PUNISH_ESTIMATED_WEIGHT) - .await.unwrap(); - let redeem_address = self.bitcoin_wallet.new_address().await.unwrap(); - let punish_address = self.bitcoin_wallet.new_address().await.unwrap(); + .estimate_fee(bitcoin::TxPunish::weight()) + .await; + let redeem_address = self.bitcoin_wallet.new_address().await; + let punish_address = self.bitcoin_wallet.new_address().await; + + let (tx_redeem_fee, tx_punish_fee, redeem_address, punish_address) = match ( + tx_redeem_fee, + tx_punish_fee, + redeem_address, + punish_address, + ) { + (Ok(tx_redeem_fee), Ok(tx_punish_fee), Ok(redeem_address), Ok(punish_address)) => { + (tx_redeem_fee, tx_punish_fee, redeem_address, punish_address) + } + _ => { continue; } + }; let state0 = match State0::new( btc, diff --git a/swap/src/protocol/alice/state.rs b/swap/src/protocol/alice/state.rs index 1ebc512f..b091ebb4 100644 --- a/swap/src/protocol/alice/state.rs +++ b/swap/src/protocol/alice/state.rs @@ -113,6 +113,7 @@ pub struct State0 { } impl State0 { + #[allow(clippy::too_many_arguments)] pub fn new( btc: bitcoin::Amount, xmr: monero::Amount, diff --git a/swap/src/protocol/bob/refund.rs b/swap/src/protocol/bob/refund.rs index cb00ce57..82168eb0 100644 --- a/swap/src/protocol/bob/refund.rs +++ b/swap/src/protocol/bob/refund.rs @@ -45,7 +45,7 @@ pub async fn refund( } }; - state6.refund_btc(bitcoin_wallet.as_ref()).await?; + state6.publish_refund_btc(bitcoin_wallet.as_ref()).await?; let state = BobState::BtcRefunded(state6); let db_state = state.clone().into(); diff --git a/swap/src/protocol/bob/state.rs b/swap/src/protocol/bob/state.rs index 68979f0b..758a0f93 100644 --- a/swap/src/protocol/bob/state.rs +++ b/swap/src/protocol/bob/state.rs @@ -646,7 +646,15 @@ impl State6 { Ok(tx_id) } - pub async fn refund_btc(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result<()> { + pub async fn publish_refund_btc(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result<()> { + let signed_tx_refund = self.signed_refund_transaction()?; + let (_, subscription) = bitcoin_wallet.broadcast(signed_tx_refund, "refund").await?; + subscription.wait_until_final().await?; + + Ok(()) + } + + pub fn signed_refund_transaction(&self) -> Result { let tx_cancel = bitcoin::TxCancel::new( &self.tx_lock, self.cancel_timelock, @@ -665,12 +673,7 @@ impl State6 { let signed_tx_refund = tx_refund.add_signatures((self.A, sig_a), (self.b.public(), sig_b))?; - - let (_, subscription) = bitcoin_wallet.broadcast(signed_tx_refund, "refund").await?; - - subscription.wait_until_final().await?; - - Ok(()) + Ok(signed_tx_refund) } pub fn tx_lock_id(&self) -> bitcoin::Txid { diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 12900e52..dffe3a8d 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -1,4 +1,4 @@ -use crate::bitcoin::ExpiredTimelocks; +use crate::bitcoin::{ExpiredTimelocks, TxCancel, TxRefund}; use crate::database::Swap; use crate::env::Config; use crate::protocol::bob; @@ -66,12 +66,8 @@ async fn next_state( Ok(match state { BobState::Started { btc_amount } => { let bitcoin_refund_address = bitcoin_wallet.new_address().await?; - let tx_refund_fee = bitcoin_wallet - .estimate_fee(bitcoin::TX_REFUND_ESTIMATED_WEIGHT) - .await?; - let tx_cancel_fee = bitcoin_wallet - .estimate_fee(bitcoin::TX_CANCEL_ESTIMATED_WEIGHT) - .await?; + let tx_refund_fee = bitcoin_wallet.estimate_fee(TxRefund::weight()).await?; + let tx_cancel_fee = bitcoin_wallet.estimate_fee(TxCancel::weight()).await?; let state2 = request_price_and_setup( swap_id, @@ -255,7 +251,7 @@ async fn next_state( ); } ExpiredTimelocks::Cancel => { - state.refund_btc(bitcoin_wallet).await?; + state.publish_refund_btc(bitcoin_wallet).await?; BobState::BtcRefunded(state) } ExpiredTimelocks::Punish => BobState::BtcPunished { diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index 4b9f988c..9dd5cbb8 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -14,10 +14,7 @@ use std::fmt; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::Duration; -use swap::bitcoin::{ - CancelTimelock, PunishTimelock, TX_CANCEL_ESTIMATED_WEIGHT, TX_PUNISH_ESTIMATED_WEIGHT, - TX_REDEEM_ESTIMATED_WEIGHT, TX_REFUND_ESTIMATED_WEIGHT, -}; +use swap::bitcoin::{CancelTimelock, PunishTimelock, TxCancel, TxPunish, TxRedeem, TxRefund}; use swap::database::Database; use swap::env::{Config, GetConfig}; use swap::network::swarm; @@ -637,12 +634,12 @@ impl TestContext { let cancel_fee = self .alice_bitcoin_wallet - .estimate_fee(TX_CANCEL_ESTIMATED_WEIGHT) + .estimate_fee(TxCancel::weight()) .await .expect("To estimate fee correctly"); let refund_fee = self .alice_bitcoin_wallet - .estimate_fee(TX_REFUND_ESTIMATED_WEIGHT) + .estimate_fee(TxRefund::weight()) .await .expect("To estimate fee correctly"); @@ -685,7 +682,7 @@ impl TestContext { async fn alice_redeemed_btc_balance(&self) -> bitcoin::Amount { let fee = self .alice_bitcoin_wallet - .estimate_fee(TX_REDEEM_ESTIMATED_WEIGHT) + .estimate_fee(TxRedeem::weight()) .await .expect("To estimate fee correctly"); self.alice_starting_balances.btc + self.btc_amount - fee @@ -728,12 +725,12 @@ impl TestContext { async fn alice_punished_btc_balance(&self) -> bitcoin::Amount { let cancel_fee = self .alice_bitcoin_wallet - .estimate_fee(TX_CANCEL_ESTIMATED_WEIGHT) + .estimate_fee(TxCancel::weight()) .await .expect("To estimate fee correctly"); let punish_fee = self .alice_bitcoin_wallet - .estimate_fee(TX_PUNISH_ESTIMATED_WEIGHT) + .estimate_fee(TxPunish::weight()) .await .expect("To estimate fee correctly"); self.alice_starting_balances.btc + self.btc_amount - cancel_fee - punish_fee