Implement `wait_for_transaction_finality`

pull/53/head
Franck Royer 4 years ago
parent 65e910e1c1
commit 765482b0aa
No known key found for this signature in database
GPG Key ID: A82ED75A8DFC50A4

55
Cargo.lock generated

@ -124,6 +124,17 @@ dependencies = [
"syn 1.0.48",
]
[[package]]
name = "async-recursion"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5444eec77a9ec2bfe4524139e09195862e981400c4358d3b760cae634e4c4ee"
dependencies = [
"proc-macro2 1.0.24",
"quote 1.0.7",
"syn 1.0.48",
]
[[package]]
name = "async-trait"
version = "0.1.41"
@ -261,6 +272,26 @@ dependencies = [
"url",
]
[[package]]
name = "bitcoin-harness"
version = "0.1.0"
source = "git+https://github.com/d4nte/bitcoin-harness-rs?branch=access-wallet-client#2ceccaa89bd994c2bc49e23a11cd8af647afc31b"
dependencies = [
"base64 0.12.3",
"bitcoin",
"bitcoincore-rpc-json",
"futures",
"hex 0.4.2",
"reqwest",
"serde",
"serde_json",
"testcontainers",
"thiserror",
"tokio",
"tracing",
"url",
]
[[package]]
name = "bitcoin_hashes"
version = "0.7.6"
@ -534,6 +565,21 @@ dependencies = [
"bitflags",
]
[[package]]
name = "conquer-once"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cb45322099323eefa1b48850ce6c148f5b510894c531e038539f6370c887214"
dependencies = [
"conquer-util",
]
[[package]]
name = "conquer-util"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e763eef8846b13b380f37dfecda401770b0ca4e56e95170237bd7c25c7db3582"
[[package]]
name = "const_fn"
version = "0.4.3"
@ -1975,10 +2021,8 @@ name = "monero-harness"
version = "0.1.0"
dependencies = [
"anyhow",
"curve25519-dalek 2.1.0",
"digest_auth",
"futures",
"monero",
"port_check",
"rand 0.7.3",
"reqwest",
@ -3339,12 +3383,14 @@ name = "swap"
version = "0.1.0"
dependencies = [
"anyhow",
"async-recursion",
"async-trait",
"atty",
"backoff",
"base64 0.12.3",
"bitcoin",
"bitcoin-harness",
"bitcoin-harness 0.1.0 (git+https://github.com/d4nte/bitcoin-harness-rs?branch=access-wallet-client)",
"conquer-once",
"derivative",
"ecdsa_fun",
"futures",
@ -4121,7 +4167,8 @@ dependencies = [
"backoff",
"base64 0.12.3",
"bitcoin",
"bitcoin-harness",
"bitcoin-harness 0.1.0 (git+https://github.com/coblox/bitcoin-harness-rs?rev=3be644cd9512c157d3337a189298b8257ed54d04)",
"conquer-once",
"cross-curve-dleq",
"curve25519-dalek 2.1.0",
"ecdsa_fun",

@ -13,7 +13,7 @@ atty = "0.2"
backoff = { version = "0.2", features = ["tokio"] }
base64 = "0.12"
bitcoin = { version = "0.23", features = ["rand", "use-serde"] } # TODO: Upgrade other crates in this repo to use this version.
bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "3be644cd9512c157d3337a189298b8257ed54d04" }
bitcoin-harness = { git = "https://github.com/d4nte/bitcoin-harness-rs", branch = "access-wallet-client" }
conquer-once = "0.3"
derivative = "2"
ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", rev = "510d48ef6a2b19805f7f5c70c598e5b03f668e7a", features = ["libsecp_compat", "serde", "serialization"] }

@ -15,6 +15,7 @@ use libp2p::request_response::ResponseChannel;
use sha2::Sha256;
use std::{sync::Arc, time::Duration};
use tokio::time::timeout;
use tracing::trace;
use xmr_btc::{
alice,
alice::{State0, State3},
@ -23,13 +24,11 @@ use xmr_btc::{
EncryptedSignature, GetRawTransaction, TransactionBlockHeight, TxCancel, TxLock, TxRefund,
WaitForTransactionFinality, WatchForRawTransaction,
},
config::Config,
cross_curve_dleq,
monero::Transfer,
};
// For each step, we are giving Bob 10 minutes to act.
static BOB_TIME_TO_ACT: Lazy<Duration> = Lazy::new(|| Duration::from_secs(10 * 60));
// The maximum we assume we need to wait from the moment the monero transaction
// is mined to the moment it reaches finality. We set 15 confirmations for now
// (based on Kraken). 1.5 multiplier in case the blockchain is slower than
@ -44,8 +43,10 @@ pub async fn negotiate(
v_a: monero::PrivateViewKey,
swarm: &mut Swarm,
bitcoin_wallet: Arc<bitcoin::Wallet>,
config: Config,
) -> Result<(ResponseChannel<AliceToBob>, State3)> {
let event = timeout(*BOB_TIME_TO_ACT, swarm.next())
trace!("Starting negotiate");
let event = timeout(config.bob_time_to_act, swarm.next())
.await
.context("Failed to receive dial connection from Bob")?;
match event {
@ -53,7 +54,7 @@ pub async fn negotiate(
other => bail!("Unexpected event received: {:?}", other),
}
let event = timeout(*BOB_TIME_TO_ACT, swarm.next())
let event = timeout(config.bob_time_to_act, swarm.next())
.await
.context("Failed to receive amounts from Bob")?;
let (btc, channel) = match event {
@ -89,7 +90,7 @@ pub async fn negotiate(
// TODO(Franck): Understand why this is needed.
swarm.set_state0(state0.clone());
let event = timeout(*BOB_TIME_TO_ACT, swarm.next())
let event = timeout(config.bob_time_to_act, swarm.next())
.await
.context("Failed to receive message 0 from Bob")?;
let message0 = match event {
@ -99,7 +100,7 @@ pub async fn negotiate(
let state1 = state0.receive(message0)?;
let event = timeout(*BOB_TIME_TO_ACT, swarm.next())
let event = timeout(config.bob_time_to_act, swarm.next())
.await
.context("Failed to receive message 1 from Bob")?;
let (msg, channel) = match event {
@ -112,7 +113,7 @@ pub async fn negotiate(
let message1 = state2.next_message();
swarm.send_message1(channel, message1);
let event = timeout(*BOB_TIME_TO_ACT, swarm.next())
let event = timeout(config.bob_time_to_act, swarm.next())
.await
.context("Failed to receive message 2 from Bob")?;
let (msg, channel) = match event {
@ -128,13 +129,14 @@ pub async fn negotiate(
pub async fn wait_for_locked_bitcoin<W>(
lock_bitcoin_txid: bitcoin::Txid,
bitcoin_wallet: Arc<W>,
config: Config,
) -> Result<()>
where
W: WatchForRawTransaction + WaitForTransactionFinality,
{
// We assume we will see Bob's transaction in the mempool first.
timeout(
*BOB_TIME_TO_ACT,
config.bob_time_to_act,
bitcoin_wallet.watch_for_raw_transaction(lock_bitcoin_txid),
)
.await
@ -142,7 +144,7 @@ where
// // We saw the transaction in the mempool, waiting for it to be confirmed.
// bitcoin_wallet
// .wait_for_transaction_finality(lock_bitcoin_txid)
// .wait_for_transaction_finality(lock_bitcoin_txid, config)
// .await;
Ok(())
@ -225,17 +227,18 @@ pub fn build_bitcoin_redeem_transaction(
pub async fn publish_bitcoin_redeem_transaction<W>(
redeem_tx: bitcoin::Transaction,
bitcoin_wallet: Arc<W>,
config: Config,
) -> Result<()>
where
W: BroadcastSignedTransaction + WaitForTransactionFinality,
{
let _tx_id = bitcoin_wallet
let tx_id = bitcoin_wallet
.broadcast_signed_transaction(redeem_tx)
.await?;
// // TODO(Franck): Not sure if we wait for finality here or just mined
// bitcoin_wallet.wait_for_transaction_finality(tx_id).await;
Ok(())
bitcoin_wallet
.wait_for_transaction_finality(tx_id, config)
.await
}
pub async fn publish_cancel_transaction<W>(
@ -364,6 +367,7 @@ pub fn build_bitcoin_punish_transaction(
pub async fn publish_bitcoin_punish_transaction<W>(
punish_tx: bitcoin::Transaction,
bitcoin_wallet: Arc<W>,
config: Config,
) -> Result<bitcoin::Txid>
where
W: BroadcastSignedTransaction + WaitForTransactionFinality,
@ -372,8 +376,9 @@ where
.broadcast_signed_transaction(punish_tx)
.await?;
// todo: enable this once trait is implemented
// bitcoin_wallet.wait_for_transaction_finality(txid).await;
bitcoin_wallet
.wait_for_transaction_finality(txid, config)
.await?;
Ok(txid)
}

@ -28,6 +28,7 @@ use std::sync::Arc;
use xmr_btc::{
alice::State3,
bitcoin::{TransactionBlockHeight, TxCancel, TxRefund, WatchForRawTransaction},
config::Config,
cross_curve_dleq,
monero::CreateWalletForOutput,
};
@ -92,6 +93,7 @@ pub async fn swap(
mut swarm: Swarm,
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
monero_wallet: Arc<crate::monero::Wallet>,
config: Config,
) -> Result<AliceState> {
match state {
AliceState::Started {
@ -100,8 +102,16 @@ pub async fn swap(
s_a,
v_a,
} => {
let (channel, state3) =
negotiate(amounts, a, s_a, v_a, &mut swarm, bitcoin_wallet.clone()).await?;
let (channel, state3) = negotiate(
amounts,
a,
s_a,
v_a,
&mut swarm,
bitcoin_wallet.clone(),
config,
)
.await?;
swap(
AliceState::Negotiated {
@ -112,6 +122,7 @@ pub async fn swap(
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
@ -120,7 +131,8 @@ pub async fn swap(
channel,
amounts,
} => {
let _ = wait_for_locked_bitcoin(state3.tx_lock.txid(), bitcoin_wallet.clone()).await?;
let _ = wait_for_locked_bitcoin(state3.tx_lock.txid(), bitcoin_wallet.clone(), config)
.await?;
swap(
AliceState::BtcLocked {
@ -131,6 +143,7 @@ pub async fn swap(
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
@ -153,6 +166,7 @@ pub async fn swap(
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
@ -169,6 +183,7 @@ pub async fn swap(
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
@ -178,6 +193,7 @@ pub async fn swap(
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
@ -202,6 +218,7 @@ pub async fn swap(
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await;
}
@ -210,13 +227,15 @@ pub async fn swap(
// TODO(Franck): Error handling is delicate here.
// If Bob sees this transaction he can redeem Monero
// e.g. If the Bitcoin node is down then the user needs to take action.
publish_bitcoin_redeem_transaction(signed_tx_redeem, bitcoin_wallet.clone()).await?;
publish_bitcoin_redeem_transaction(signed_tx_redeem, bitcoin_wallet.clone(), config)
.await?;
swap(
AliceState::BtcRedeemed,
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
@ -236,6 +255,7 @@ pub async fn swap(
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
@ -261,6 +281,7 @@ pub async fn swap(
swarm,
bitcoin_wallet.clone(),
monero_wallet,
config,
)
.await
}
@ -274,6 +295,7 @@ pub async fn swap(
swarm,
bitcoin_wallet.clone(),
monero_wallet,
config,
)
.await
}
@ -310,8 +332,11 @@ pub async fn swap(
state3.B.clone(),
)?;
let punish_tx_finalised =
publish_bitcoin_punish_transaction(signed_tx_punish, bitcoin_wallet.clone());
let punish_tx_finalised = publish_bitcoin_punish_transaction(
signed_tx_punish,
bitcoin_wallet.clone(),
config,
);
let refund_tx_seen = bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid());
@ -325,6 +350,7 @@ pub async fn swap(
swarm,
bitcoin_wallet.clone(),
monero_wallet,
config,
)
.await
}
@ -338,6 +364,7 @@ pub async fn swap(
swarm,
bitcoin_wallet.clone(),
monero_wallet,
config,
)
.await
}

@ -6,9 +6,13 @@ use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _
use bitcoin::util::psbt::PartiallySignedTransaction;
use bitcoin_harness::bitcoind_rpc::PsbtBase64;
use reqwest::Url;
use xmr_btc::bitcoin::{
BlockHeight, BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock, TransactionBlockHeight,
WatchForRawTransaction,
use tokio::time::interval;
use xmr_btc::{
bitcoin::{
BlockHeight, BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock,
TransactionBlockHeight, WatchForRawTransaction,
},
config::Config,
};
pub use ::bitcoin::{Address, Transaction};
@ -148,7 +152,23 @@ impl TransactionBlockHeight for Wallet {
#[async_trait]
impl WaitForTransactionFinality for Wallet {
async fn wait_for_transaction_finality(&self, _txid: Txid) {
todo!()
async fn wait_for_transaction_finality(&self, txid: Txid, config: Config) -> Result<()> {
// TODO(Franck): This assumes that bitcoind runs with txindex=1
// Divide by 4 to not check too often yet still be aware of the new block early
// on.
let mut interval = interval(config.bitcoin_avg_block_time / 4);
loop {
let tx = self.0.client.get_raw_transaction_verbose(txid).await?;
if let Some(confirmations) = tx.confirmations {
if confirmations >= config.bitcoin_finality_confirmations {
break;
}
}
interval.tick().await;
}
Ok(())
}
}

@ -202,11 +202,13 @@ async fn happy_path_recursive_executor() {
};
let alice_swarm =
alice::new_swarm(alice_multiaddr.clone(), alice_transport, alice_behaviour).unwrap();
let config = xmr_btc::config::Config::regtest();
let alice_swap = alice::swap::swap(
alice_state,
alice_swarm,
alice_btc_wallet.clone(),
alice_xmr_wallet.clone(),
config,
);
let bob_db_dir = tempdir().unwrap();

@ -10,6 +10,7 @@ edition = "2018"
anyhow = "1"
async-trait = "0.1"
bitcoin = { version = "0.23", features = ["rand", "serde"] }
conquer-once = "0.3"
cross-curve-dleq = { git = "https://github.com/comit-network/cross-curve-dleq", rev = "a19608734da1e8803cb4c806022483df4e7d5588", features = ["serde"] }
curve25519-dalek = "2"
ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", rev = "510d48ef6a2b19805f7f5c70c598e5b03f668e7a", features = ["libsecp_compat", "serde", "serialization"] }

@ -1,5 +1,6 @@
pub mod transactions;
use crate::config::Config;
use anyhow::{anyhow, bail, Result};
use async_trait::async_trait;
use bitcoin::hashes::{hex::ToHex, Hash};
@ -188,7 +189,7 @@ pub trait WatchForRawTransaction {
#[async_trait]
pub trait WaitForTransactionFinality {
async fn wait_for_transaction_finality(&self, txid: Txid);
async fn wait_for_transaction_finality(&self, txid: Txid, config: Config) -> Result<()>;
}
#[async_trait]

@ -0,0 +1,49 @@
use conquer_once::Lazy;
use std::time::Duration;
#[derive(Debug, Copy, Clone)]
pub struct Config {
pub bob_time_to_act: Duration,
pub bitcoin_finality_confirmations: u32,
pub bitcoin_avg_block_time: Duration,
}
impl Config {
pub fn mainnet() -> Self {
Self {
bob_time_to_act: *mainnet::BOB_TIME_TO_ACT,
bitcoin_finality_confirmations: mainnet::BITCOIN_FINALITY_CONFIRMATIONS,
bitcoin_avg_block_time: *mainnet::BITCOIN_AVG_BLOCK_TIME,
}
}
pub fn regtest() -> Self {
Self {
bob_time_to_act: *regtest::BOB_TIME_TO_ACT,
bitcoin_finality_confirmations: regtest::BITCOIN_FINALITY_CONFIRMATIONS,
bitcoin_avg_block_time: *regtest::BITCOIN_AVG_BLOCK_TIME,
}
}
}
mod mainnet {
use super::*;
// For each step, we are giving Bob 10 minutes to act.
pub static BOB_TIME_TO_ACT: Lazy<Duration> = Lazy::new(|| Duration::from_secs(10 * 60));
pub static BITCOIN_FINALITY_CONFIRMATIONS: u32 = 3;
pub static BITCOIN_AVG_BLOCK_TIME: Lazy<Duration> = Lazy::new(|| Duration::from_secs(10 * 60));
}
mod regtest {
use super::*;
// In test, set to 5 seconds to fail fast
pub static BOB_TIME_TO_ACT: Lazy<Duration> = Lazy::new(|| Duration::from_secs(5));
pub static BITCOIN_FINALITY_CONFIRMATIONS: u32 = 1;
pub static BITCOIN_AVG_BLOCK_TIME: Lazy<Duration> = Lazy::new(|| Duration::from_secs(1));
}

@ -48,6 +48,7 @@ mod utils {
pub mod alice;
pub mod bitcoin;
pub mod bob;
pub mod config;
pub mod monero;
pub mod serde;
pub mod transport;

Loading…
Cancel
Save