diff --git a/swap/src/alice/steps.rs b/swap/src/alice/steps.rs index 18e73efd..ad436966 100644 --- a/swap/src/alice/steps.rs +++ b/swap/src/alice/steps.rs @@ -9,7 +9,6 @@ use futures::{ pin_mut, }; use libp2p::request_response::ResponseChannel; - use rand::rngs::OsRng; use sha2::Sha256; use std::{sync::Arc, time::Duration}; @@ -20,8 +19,8 @@ use xmr_btc::{ alice::State3, bitcoin::{ poll_until_block_height_is_gte, BlockHeight, BroadcastSignedTransaction, - EncryptedSignature, GetRawTransaction, TransactionBlockHeight, TxCancel, TxLock, TxRefund, - WaitForTransactionFinality, WatchForRawTransaction, + EncryptedSignature, GetBlockHeight, GetRawTransaction, Timelock, TransactionBlockHeight, + TxCancel, TxLock, TxRefund, WaitForTransactionFinality, WatchForRawTransaction, }, config::Config, cross_curve_dleq, @@ -207,12 +206,12 @@ pub async fn publish_cancel_transaction( tx_lock: TxLock, a: bitcoin::SecretKey, B: bitcoin::PublicKey, - cancel_timelock: u32, + cancel_timelock: Timelock, tx_cancel_sig_bob: bitcoin::Signature, bitcoin_wallet: Arc, ) -> Result where - W: GetRawTransaction + TransactionBlockHeight + BlockHeight + BroadcastSignedTransaction, + W: GetRawTransaction + TransactionBlockHeight + GetBlockHeight + BroadcastSignedTransaction, { // First wait for cancel timelock to expire let tx_lock_height = bitcoin_wallet @@ -253,13 +252,13 @@ where pub async fn wait_for_bitcoin_refund( tx_cancel: &TxCancel, - cancel_tx_height: u32, - punish_timelock: u32, + cancel_tx_height: BlockHeight, + punish_timelock: Timelock, refund_address: &bitcoin::Address, bitcoin_wallet: Arc, ) -> Result<(bitcoin::TxRefund, Option)> where - W: BlockHeight + WatchForRawTransaction, + W: GetBlockHeight + WatchForRawTransaction, { let punish_timelock_expired = poll_until_block_height_is_gte(bitcoin_wallet.as_ref(), cancel_tx_height + punish_timelock); @@ -306,9 +305,9 @@ pub fn extract_monero_private_key( pub fn build_bitcoin_punish_transaction( tx_lock: &TxLock, - cancel_timelock: u32, + cancel_timelock: Timelock, punish_address: &bitcoin::Address, - punish_timelock: u32, + punish_timelock: Timelock, tx_punish_sig_bob: bitcoin::Signature, a: bitcoin::SecretKey, B: bitcoin::PublicKey, diff --git a/swap/src/bitcoin.rs b/swap/src/bitcoin.rs index 5dfc8edd..972140eb 100644 --- a/swap/src/bitcoin.rs +++ b/swap/src/bitcoin.rs @@ -8,7 +8,7 @@ use std::time::Duration; use tokio::time::interval; use xmr_btc::{ bitcoin::{ - BlockHeight, BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock, + BroadcastSignedTransaction, BuildTxLockPsbt, GetBlockHeight, SignTxLock, TransactionBlockHeight, WatchForRawTransaction, }, config::Config, @@ -132,25 +132,27 @@ impl GetRawTransaction for Wallet { } #[async_trait] -impl BlockHeight for Wallet { - async fn block_height(&self) -> u32 { - (|| async { Ok(self.inner.client.getblockcount().await?) }) +impl GetBlockHeight for Wallet { + async fn get_block_height(&self) -> BlockHeight { + let height = (|| async { Ok(self.inner.client.getblockcount().await?) }) .retry(ConstantBackoff::new(Duration::from_secs(1))) .await - .expect("transient errors to be retried") + .expect("transient errors to be retried"); + + BlockHeight::new(height) } } #[async_trait] impl TransactionBlockHeight for Wallet { - async fn transaction_block_height(&self, txid: Txid) -> u32 { + async fn transaction_block_height(&self, txid: Txid) -> BlockHeight { #[derive(Debug)] enum Error { Io, NotYetMined, } - (|| async { + let height = (|| async { let block_height = self .inner .transaction_block_height(txid) @@ -164,7 +166,9 @@ impl TransactionBlockHeight for Wallet { }) .retry(ConstantBackoff::new(Duration::from_secs(1))) .await - .expect("transient errors to be retried") + .expect("transient errors to be retried"); + + BlockHeight::new(height) } } diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index 52418c51..29abc24a 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -29,7 +29,8 @@ use tokio::{sync::Mutex, time::timeout}; use tracing::{error, info}; pub mod message; use crate::bitcoin::{ - current_epoch, wait_for_cancel_timelock_to_expire, BlockHeight, TransactionBlockHeight, + current_epoch, wait_for_cancel_timelock_to_expire, GetBlockHeight, Timelock, + TransactionBlockHeight, }; pub use message::{Message, Message0, Message1, Message2}; @@ -90,7 +91,7 @@ pub fn action_generator( ) -> GenBoxed where N: ReceiveBitcoinRedeemEncsig + Send + 'static, - B: bitcoin::BlockHeight + B: bitcoin::GetBlockHeight + bitcoin::TransactionBlockHeight + bitcoin::WatchForRawTransaction + Send @@ -412,8 +413,8 @@ impl State { rng: &mut R, btc: bitcoin::Amount, xmr: monero::Amount, - cancel_timelock: u32, - punish_timelock: u32, + cancel_timelock: Timelock, + punish_timelock: Timelock, redeem_address: bitcoin::Address, punish_address: bitcoin::Address, ) -> Self { @@ -443,8 +444,8 @@ pub struct State0 { #[serde(with = "::bitcoin::util::amount::serde::as_sat")] pub btc: bitcoin::Amount, pub xmr: monero::Amount, - pub cancel_timelock: u32, - pub punish_timelock: u32, + pub cancel_timelock: Timelock, + pub punish_timelock: Timelock, pub redeem_address: bitcoin::Address, pub punish_address: bitcoin::Address, } @@ -457,8 +458,8 @@ impl State0 { v_a: monero::PrivateViewKey, btc: bitcoin::Amount, xmr: monero::Amount, - cancel_timelock: u32, - punish_timelock: u32, + cancel_timelock: Timelock, + punish_timelock: Timelock, redeem_address: bitcoin::Address, punish_address: bitcoin::Address, ) -> Self { @@ -532,8 +533,8 @@ pub struct State1 { #[serde(with = "::bitcoin::util::amount::serde::as_sat")] btc: bitcoin::Amount, xmr: monero::Amount, - cancel_timelock: u32, - punish_timelock: u32, + cancel_timelock: Timelock, + punish_timelock: Timelock, refund_address: bitcoin::Address, redeem_address: bitcoin::Address, punish_address: bitcoin::Address, @@ -571,8 +572,8 @@ pub struct State2 { #[serde(with = "::bitcoin::util::amount::serde::as_sat")] btc: bitcoin::Amount, xmr: monero::Amount, - cancel_timelock: u32, - punish_timelock: u32, + cancel_timelock: Timelock, + punish_timelock: Timelock, refund_address: bitcoin::Address, redeem_address: bitcoin::Address, punish_address: bitcoin::Address, @@ -640,8 +641,8 @@ pub struct State3 { #[serde(with = "::bitcoin::util::amount::serde::as_sat")] pub btc: bitcoin::Amount, pub xmr: monero::Amount, - pub cancel_timelock: u32, - pub punish_timelock: u32, + pub cancel_timelock: Timelock, + pub punish_timelock: Timelock, pub refund_address: bitcoin::Address, pub redeem_address: bitcoin::Address, pub punish_address: bitcoin::Address, @@ -684,7 +685,7 @@ impl State3 { pub async fn wait_for_cancel_timelock_to_expire(&self, bitcoin_wallet: &W) -> Result<()> where - W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, + W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight, { wait_for_cancel_timelock_to_expire( bitcoin_wallet, @@ -696,7 +697,7 @@ impl State3 { pub async fn expired_timelocks(&self, bitcoin_wallet: &W) -> Result where - W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, + W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight, { current_epoch( bitcoin_wallet, @@ -719,8 +720,8 @@ pub struct State4 { #[serde(with = "::bitcoin::util::amount::serde::as_sat")] btc: bitcoin::Amount, xmr: monero::Amount, - cancel_timelock: u32, - punish_timelock: u32, + cancel_timelock: Timelock, + punish_timelock: Timelock, refund_address: bitcoin::Address, redeem_address: bitcoin::Address, punish_address: bitcoin::Address, @@ -816,8 +817,8 @@ pub struct State5 { #[serde(with = "::bitcoin::util::amount::serde::as_sat")] btc: bitcoin::Amount, xmr: monero::Amount, - cancel_timelock: u32, - punish_timelock: u32, + cancel_timelock: Timelock, + punish_timelock: Timelock, refund_address: bitcoin::Address, redeem_address: bitcoin::Address, punish_address: bitcoin::Address, @@ -905,8 +906,8 @@ pub struct State6 { #[serde(with = "::bitcoin::util::amount::serde::as_sat")] btc: bitcoin::Amount, xmr: monero::Amount, - cancel_timelock: u32, - punish_timelock: u32, + cancel_timelock: Timelock, + punish_timelock: Timelock, refund_address: bitcoin::Address, redeem_address: bitcoin::Address, punish_address: bitcoin::Address, diff --git a/xmr-btc/src/bitcoin.rs b/xmr-btc/src/bitcoin.rs index cbf22800..7b3eb3ce 100644 --- a/xmr-btc/src/bitcoin.rs +++ b/xmr-btc/src/bitcoin.rs @@ -1,6 +1,7 @@ +mod timelocks; pub mod transactions; -use crate::config::Config; +use crate::{config::Config, ExpiredTimelocks}; use anyhow::{anyhow, bail, Result}; use async_trait::async_trait; use bitcoin::hashes::{hex::ToHex, Hash}; @@ -11,9 +12,9 @@ use serde::{Deserialize, Serialize}; use sha2::Sha256; use std::str::FromStr; -use crate::ExpiredTimelocks; pub use bitcoin::{util::psbt::PartiallySignedTransaction, *}; pub use ecdsa_fun::{adaptor::EncryptedSignature, fun::Scalar, Signature}; +pub use timelocks::*; pub use transactions::{TxCancel, TxLock, TxPunish, TxRedeem, TxRefund}; // TODO: Configurable tx-fee (note: parties have to agree prior to swapping) @@ -201,18 +202,18 @@ pub trait WaitForTransactionFinality { } #[async_trait] -pub trait BlockHeight { - async fn block_height(&self) -> u32; +pub trait GetBlockHeight { + async fn get_block_height(&self) -> BlockHeight; } #[async_trait] pub trait TransactionBlockHeight { - async fn transaction_block_height(&self, txid: Txid) -> u32; + async fn transaction_block_height(&self, txid: Txid) -> BlockHeight; } #[async_trait] pub trait WaitForBlockHeight { - async fn wait_for_block_height(&self, height: u32); + async fn wait_for_block_height(&self, height: BlockHeight); } #[async_trait] @@ -236,25 +237,25 @@ pub fn recover(S: PublicKey, sig: Signature, encsig: EncryptedSignature) -> Resu Ok(s) } -pub async fn poll_until_block_height_is_gte(client: &B, target: u32) +pub async fn poll_until_block_height_is_gte(client: &B, target: BlockHeight) where - B: BlockHeight, + B: GetBlockHeight, { - while client.block_height().await < target { + while client.get_block_height().await < target { tokio::time::delay_for(std::time::Duration::from_secs(1)).await; } } pub async fn current_epoch( bitcoin_wallet: &W, - cancel_timelock: u32, - punish_timelock: u32, + cancel_timelock: Timelock, + punish_timelock: Timelock, lock_tx_id: ::bitcoin::Txid, ) -> anyhow::Result where - W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, + W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight, { - let current_block_height = bitcoin_wallet.block_height().await; + let current_block_height = bitcoin_wallet.get_block_height().await; let lock_tx_height = bitcoin_wallet.transaction_block_height(lock_tx_id).await; let cancel_timelock_height = lock_tx_height + cancel_timelock; let punish_timelock_height = cancel_timelock_height + punish_timelock; @@ -271,11 +272,11 @@ where pub async fn wait_for_cancel_timelock_to_expire( bitcoin_wallet: &W, - cancel_timelock: u32, + cancel_timelock: Timelock, lock_tx_id: ::bitcoin::Txid, ) -> Result<()> where - W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, + W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight, { let tx_lock_height = bitcoin_wallet.transaction_block_height(lock_tx_id).await; diff --git a/xmr-btc/src/bitcoin/timelocks.rs b/xmr-btc/src/bitcoin/timelocks.rs new file mode 100644 index 00000000..0f594afb --- /dev/null +++ b/xmr-btc/src/bitcoin/timelocks.rs @@ -0,0 +1,49 @@ +use serde::{Deserialize, Serialize}; +use std::ops::Add; + +/// 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 +/// mined. +#[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[serde(transparent)] +pub struct Timelock(u32); + +impl Timelock { + pub const fn new(number_of_blocks: u32) -> Self { + Self(number_of_blocks) + } +} + +impl From for u32 { + fn from(timelock: Timelock) -> Self { + timelock.0 + } +} + +/// Represent a block height, or block number, expressed in absolute block +/// count. E.g. The transaction was included in block #655123, 655123 block +/// after the genesis block. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)] +#[serde(transparent)] +pub struct BlockHeight(u32); + +impl From for u32 { + fn from(height: BlockHeight) -> Self { + height.0 + } +} + +impl BlockHeight { + pub const fn new(block_height: u32) -> Self { + Self(block_height) + } +} + +impl Add for BlockHeight { + type Output = BlockHeight; + + fn add(self, rhs: Timelock) -> Self::Output { + BlockHeight(self.0 + rhs.0) + } +} diff --git a/xmr-btc/src/bitcoin/transactions.rs b/xmr-btc/src/bitcoin/transactions.rs index e9b789ad..b15ba8c3 100644 --- a/xmr-btc/src/bitcoin/transactions.rs +++ b/xmr-btc/src/bitcoin/transactions.rs @@ -1,6 +1,6 @@ use crate::bitcoin::{ build_shared_output_descriptor, verify_sig, BuildTxLockPsbt, Network, OutPoint, PublicKey, - Txid, TX_FEE, + Timelock, Txid, TX_FEE, }; use anyhow::{bail, Context, Result}; use bitcoin::{ @@ -228,13 +228,13 @@ pub struct TxCancel { } impl TxCancel { - pub fn new(tx_lock: &TxLock, cancel_timelock: u32, A: PublicKey, B: PublicKey) -> Self { + pub fn new(tx_lock: &TxLock, cancel_timelock: Timelock, A: PublicKey, B: PublicKey) -> Self { let cancel_output_descriptor = build_shared_output_descriptor(A.0, B.0); let tx_in = TxIn { previous_output: tx_lock.as_outpoint(), script_sig: Default::default(), - sequence: cancel_timelock, + sequence: cancel_timelock.into(), witness: Vec::new(), }; @@ -316,14 +316,14 @@ impl TxCancel { fn build_spend_transaction( &self, spend_address: &Address, - sequence: Option, + sequence: Option, ) -> Transaction { let previous_output = self.as_outpoint(); let tx_in = TxIn { previous_output, script_sig: Default::default(), - sequence: sequence.unwrap_or(0xFFFF_FFFF), + sequence: sequence.map(Into::into).unwrap_or(0xFFFF_FFFF), witness: Vec::new(), }; @@ -450,7 +450,7 @@ pub struct TxPunish { } impl TxPunish { - pub fn new(tx_cancel: &TxCancel, punish_address: &Address, punish_timelock: u32) -> Self { + pub fn new(tx_cancel: &TxCancel, punish_address: &Address, punish_timelock: Timelock) -> Self { let tx_punish = tx_cancel.build_spend_transaction(punish_address, Some(punish_timelock)); let digest = SigHashCache::new(&tx_punish).signature_hash( diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index f879de28..7fa9569e 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -35,8 +35,8 @@ use tracing::error; pub mod message; use crate::{ bitcoin::{ - current_epoch, wait_for_cancel_timelock_to_expire, BlockHeight, GetRawTransaction, Network, - TransactionBlockHeight, + current_epoch, wait_for_cancel_timelock_to_expire, GetBlockHeight, GetRawTransaction, + Network, Timelock, TransactionBlockHeight, }, monero::{CreateWalletForOutput, WatchForTransfer}, }; @@ -95,7 +95,7 @@ pub fn action_generator( where N: ReceiveTransferProof + Send + 'static, M: monero::WatchForTransfer + Send + Sync + 'static, - B: bitcoin::BlockHeight + B: bitcoin::GetBlockHeight + bitcoin::TransactionBlockHeight + bitcoin::WatchForRawTransaction + Send @@ -357,8 +357,8 @@ pub struct State0 { #[serde(with = "::bitcoin::util::amount::serde::as_sat")] btc: bitcoin::Amount, xmr: monero::Amount, - cancel_timelock: u32, - punish_timelock: u32, + cancel_timelock: Timelock, + punish_timelock: Timelock, refund_address: bitcoin::Address, } @@ -367,8 +367,8 @@ impl State0 { rng: &mut R, btc: bitcoin::Amount, xmr: monero::Amount, - cancel_timelock: u32, - punish_timelock: u32, + cancel_timelock: Timelock, + punish_timelock: Timelock, refund_address: bitcoin::Address, ) -> Self { let b = bitcoin::SecretKey::new_random(rng); @@ -448,8 +448,8 @@ pub struct State1 { #[serde(with = "::bitcoin::util::amount::serde::as_sat")] btc: bitcoin::Amount, xmr: monero::Amount, - cancel_timelock: u32, - punish_timelock: u32, + cancel_timelock: Timelock, + punish_timelock: Timelock, refund_address: bitcoin::Address, redeem_address: bitcoin::Address, punish_address: bitcoin::Address, @@ -507,8 +507,8 @@ pub struct State2 { #[serde(with = "::bitcoin::util::amount::serde::as_sat")] btc: bitcoin::Amount, pub xmr: monero::Amount, - pub cancel_timelock: u32, - pub punish_timelock: u32, + pub cancel_timelock: Timelock, + pub punish_timelock: Timelock, pub refund_address: bitcoin::Address, pub redeem_address: bitcoin::Address, pub punish_address: bitcoin::Address, @@ -574,8 +574,8 @@ pub struct State3 { #[serde(with = "::bitcoin::util::amount::serde::as_sat")] btc: bitcoin::Amount, xmr: monero::Amount, - pub cancel_timelock: u32, - punish_timelock: u32, + pub cancel_timelock: Timelock, + punish_timelock: Timelock, pub refund_address: bitcoin::Address, redeem_address: bitcoin::Address, punish_address: bitcoin::Address, @@ -626,7 +626,7 @@ impl State3 { pub async fn wait_for_cancel_timelock_to_expire(&self, bitcoin_wallet: &W) -> Result<()> where - W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, + W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight, { wait_for_cancel_timelock_to_expire( bitcoin_wallet, @@ -663,7 +663,7 @@ impl State3 { pub async fn current_epoch(&self, bitcoin_wallet: &W) -> Result where - W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, + W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight, { current_epoch( bitcoin_wallet, @@ -686,8 +686,8 @@ pub struct State4 { #[serde(with = "::bitcoin::util::amount::serde::as_sat")] btc: bitcoin::Amount, xmr: monero::Amount, - pub cancel_timelock: u32, - punish_timelock: u32, + pub cancel_timelock: Timelock, + punish_timelock: Timelock, pub refund_address: bitcoin::Address, pub redeem_address: bitcoin::Address, punish_address: bitcoin::Address, @@ -795,7 +795,7 @@ impl State4 { pub async fn wait_for_cancel_timelock_to_expire(&self, bitcoin_wallet: &W) -> Result<()> where - W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, + W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight, { wait_for_cancel_timelock_to_expire( bitcoin_wallet, @@ -807,7 +807,7 @@ impl State4 { pub async fn expired_timelock(&self, bitcoin_wallet: &W) -> Result where - W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, + W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight, { current_epoch( bitcoin_wallet, @@ -879,8 +879,8 @@ pub struct State5 { #[serde(with = "::bitcoin::util::amount::serde::as_sat")] btc: bitcoin::Amount, xmr: monero::Amount, - cancel_timelock: u32, - punish_timelock: u32, + cancel_timelock: Timelock, + punish_timelock: Timelock, refund_address: bitcoin::Address, pub redeem_address: bitcoin::Address, punish_address: bitcoin::Address, diff --git a/xmr-btc/src/config.rs b/xmr-btc/src/config.rs index eb8c728b..5715270e 100644 --- a/xmr-btc/src/config.rs +++ b/xmr-btc/src/config.rs @@ -1,3 +1,4 @@ +use crate::bitcoin::Timelock; use conquer_once::Lazy; use std::time::Duration; @@ -7,8 +8,8 @@ pub struct Config { pub bitcoin_finality_confirmations: u32, pub bitcoin_avg_block_time: Duration, pub monero_max_finality_time: Duration, - pub bitcoin_cancel_timelock: u32, - pub bitcoin_punish_timelock: u32, + pub bitcoin_cancel_timelock: Timelock, + pub bitcoin_punish_timelock: Timelock, pub bitcoin_network: ::bitcoin::Network, } @@ -59,8 +60,8 @@ mod mainnet { pub static MONERO_AVG_BLOCK_TIME: Lazy = Lazy::new(|| Duration::from_secs(2 * 60)); // Set to 12 hours, arbitrary value to be reviewed properly - pub static BITCOIN_CANCEL_TIMELOCK: u32 = 72; - pub static BITCOIN_PUNISH_TIMELOCK: u32 = 72; + pub static BITCOIN_CANCEL_TIMELOCK: Timelock = Timelock::new(72); + pub static BITCOIN_PUNISH_TIMELOCK: Timelock = Timelock::new(72); } mod regtest { @@ -77,7 +78,7 @@ mod regtest { pub static MONERO_AVG_BLOCK_TIME: Lazy = Lazy::new(|| Duration::from_secs(60)); - pub static BITCOIN_CANCEL_TIMELOCK: u32 = 50; + pub static BITCOIN_CANCEL_TIMELOCK: Timelock = Timelock::new(50); - pub static BITCOIN_PUNISH_TIMELOCK: u32 = 50; + pub static BITCOIN_PUNISH_TIMELOCK: Timelock = Timelock::new(50); }