mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-10-01 01:45:40 -04:00
Merge #147
147: Refactor prod code after test refactoring r=da-kami a=da-kami
Follow up of https://github.com/comit-network/xmr-btc-swap/pull/144
Took a bit more than expected, but this is really neat now!
The commits should be well-contained for reviewing, but 00835baa15
is quite big.
I changed the abstraction on the way - out methods are finally named `run` and `run_until` which both take a swap - which makes way more sense I think :)
Also had to change the abstraction layers in `testutils` and introduced `Test` which specifies the swap amounts (that would usually come from the commandline and should not live in the factory as they are irrelevant for resumed swaps).
Co-authored-by: Daniel Karzel <daniel@comit.network>
This commit is contained in:
commit
05669c749c
244
swap/src/main.rs
244
swap/src/main.rs
@ -14,20 +14,16 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::cli::{Command, Options, Resume};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use libp2p::{core::Multiaddr, PeerId};
|
||||
use anyhow::{Context, Result};
|
||||
use prettytable::{row, Table};
|
||||
use rand::rngs::OsRng;
|
||||
use std::sync::Arc;
|
||||
use structopt::StructOpt;
|
||||
use swap::{
|
||||
bitcoin,
|
||||
config::Config,
|
||||
database::{Database, Swap},
|
||||
monero, network,
|
||||
network::transport::build,
|
||||
protocol::{alice, alice::AliceState, bob, bob::BobState},
|
||||
seed::Seed,
|
||||
database::Database,
|
||||
monero,
|
||||
protocol::{alice, bob, bob::SwapFactory, StartingBalances},
|
||||
trace::init_tracing,
|
||||
SwapAmounts,
|
||||
};
|
||||
@ -51,8 +47,7 @@ async fn main() -> Result<()> {
|
||||
opt.data_dir
|
||||
);
|
||||
let data_dir = std::path::Path::new(opt.data_dir.as_str()).to_path_buf();
|
||||
let db =
|
||||
Database::open(data_dir.join("database").as_path()).context("Could not open database")?;
|
||||
let db_path = data_dir.join("database");
|
||||
|
||||
let seed = swap::config::seed::Seed::from_file_or_generate(&data_dir)
|
||||
.expect("Could not retrieve/initialize seed")
|
||||
@ -67,7 +62,12 @@ async fn main() -> Result<()> {
|
||||
send_monero,
|
||||
receive_bitcoin,
|
||||
} => {
|
||||
let (bitcoin_wallet, monero_wallet) = setup_wallets(
|
||||
let swap_amounts = SwapAmounts {
|
||||
xmr: send_monero,
|
||||
btc: receive_bitcoin,
|
||||
};
|
||||
|
||||
let (bitcoin_wallet, monero_wallet, starting_balances) = setup_wallets(
|
||||
bitcoind_url,
|
||||
bitcoin_wallet_name.as_str(),
|
||||
monero_wallet_rpc_url,
|
||||
@ -75,50 +75,28 @@ async fn main() -> Result<()> {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let amounts = SwapAmounts {
|
||||
btc: receive_bitcoin,
|
||||
xmr: send_monero,
|
||||
};
|
||||
|
||||
let alice_state = {
|
||||
let rng = &mut OsRng;
|
||||
let a = bitcoin::SecretKey::new_random(rng);
|
||||
let s_a = cross_curve_dleq::Scalar::random(rng);
|
||||
let v_a = monero::PrivateViewKey::new_random(rng);
|
||||
let redeem_address = bitcoin_wallet.as_ref().new_address().await?;
|
||||
let punish_address = redeem_address.clone();
|
||||
let state0 = alice::state::State0::new(
|
||||
a,
|
||||
s_a,
|
||||
v_a,
|
||||
amounts.btc,
|
||||
amounts.xmr,
|
||||
config.bitcoin_cancel_timelock,
|
||||
config.bitcoin_punish_timelock,
|
||||
redeem_address,
|
||||
punish_address,
|
||||
);
|
||||
|
||||
AliceState::Started { amounts, state0 }
|
||||
};
|
||||
|
||||
let swap_id = Uuid::new_v4();
|
||||
|
||||
info!(
|
||||
"Swap sending {} and receiving {} started with ID {}",
|
||||
send_monero, receive_bitcoin, swap_id
|
||||
);
|
||||
|
||||
alice_swap(
|
||||
let alice_factory = alice::SwapFactory::new(
|
||||
seed,
|
||||
config,
|
||||
swap_id,
|
||||
alice_state,
|
||||
listen_addr,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
db,
|
||||
seed,
|
||||
starting_balances,
|
||||
db_path,
|
||||
listen_addr,
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
let (swap, mut event_loop) = alice_factory.new_swap_as_alice(swap_amounts).await?;
|
||||
|
||||
tokio::spawn(async move { event_loop.run().await });
|
||||
alice::run(swap).await?;
|
||||
}
|
||||
Command::BuyXmr {
|
||||
alice_peer_id,
|
||||
@ -129,7 +107,12 @@ async fn main() -> Result<()> {
|
||||
send_bitcoin,
|
||||
receive_monero,
|
||||
} => {
|
||||
let (bitcoin_wallet, monero_wallet) = setup_wallets(
|
||||
let swap_amounts = SwapAmounts {
|
||||
btc: send_bitcoin,
|
||||
xmr: receive_monero,
|
||||
};
|
||||
|
||||
let (bitcoin_wallet, monero_wallet, starting_balances) = setup_wallets(
|
||||
bitcoind_url,
|
||||
bitcoin_wallet_name.as_str(),
|
||||
monero_wallet_rpc_url,
|
||||
@ -137,47 +120,36 @@ async fn main() -> Result<()> {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let refund_address = bitcoin_wallet.new_address().await?;
|
||||
let state0 = bob::state::State0::new(
|
||||
&mut OsRng,
|
||||
send_bitcoin,
|
||||
receive_monero,
|
||||
config.bitcoin_cancel_timelock,
|
||||
config.bitcoin_punish_timelock,
|
||||
refund_address,
|
||||
config.monero_finality_confirmations,
|
||||
);
|
||||
|
||||
let amounts = SwapAmounts {
|
||||
btc: send_bitcoin,
|
||||
xmr: receive_monero,
|
||||
};
|
||||
|
||||
let bob_state = BobState::Started { state0, amounts };
|
||||
|
||||
let swap_id = Uuid::new_v4();
|
||||
|
||||
info!(
|
||||
"Swap sending {} and receiving {} started with ID {}",
|
||||
send_bitcoin, receive_monero, swap_id
|
||||
);
|
||||
|
||||
bob_swap(
|
||||
let bob_factory = SwapFactory::new(
|
||||
seed,
|
||||
db_path,
|
||||
swap_id,
|
||||
bob_state,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
db,
|
||||
alice_peer_id,
|
||||
config,
|
||||
starting_balances,
|
||||
alice_addr,
|
||||
seed,
|
||||
)
|
||||
.await?;
|
||||
alice_peer_id,
|
||||
);
|
||||
let (swap, event_loop) = bob_factory.new_swap_as_bob(swap_amounts).await?;
|
||||
|
||||
tokio::spawn(async move { event_loop.run().await });
|
||||
bob::run(swap).await?;
|
||||
}
|
||||
Command::History => {
|
||||
let mut table = Table::new();
|
||||
|
||||
table.add_row(row!["SWAP ID", "STATE"]);
|
||||
|
||||
let db = Database::open(db_path.as_path()).context("Could not open database")?;
|
||||
|
||||
for (swap_id, state) in db.all()? {
|
||||
table.add_row(row![swap_id, state]);
|
||||
}
|
||||
@ -192,30 +164,29 @@ async fn main() -> Result<()> {
|
||||
monero_wallet_rpc_url,
|
||||
listen_addr,
|
||||
}) => {
|
||||
let db_state = if let Swap::Alice(db_state) = db.get_state(swap_id)? {
|
||||
db_state
|
||||
} else {
|
||||
bail!("Swap {} is not sell xmr.", swap_id)
|
||||
};
|
||||
|
||||
let (bitcoin_wallet, monero_wallet) = setup_wallets(
|
||||
let (bitcoin_wallet, monero_wallet, starting_balances) = setup_wallets(
|
||||
bitcoind_url,
|
||||
bitcoin_wallet_name.as_str(),
|
||||
monero_wallet_rpc_url,
|
||||
config,
|
||||
)
|
||||
.await?;
|
||||
alice_swap(
|
||||
|
||||
let alice_factory = alice::SwapFactory::new(
|
||||
seed,
|
||||
config,
|
||||
swap_id,
|
||||
db_state.into(),
|
||||
listen_addr,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
db,
|
||||
seed,
|
||||
starting_balances,
|
||||
db_path,
|
||||
listen_addr,
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
let (swap, mut event_loop) = alice_factory.recover_alice_from_db().await?;
|
||||
|
||||
tokio::spawn(async move { event_loop.run().await });
|
||||
alice::run(swap).await?;
|
||||
}
|
||||
Command::Resume(Resume::BuyXmr {
|
||||
swap_id,
|
||||
@ -225,30 +196,29 @@ async fn main() -> Result<()> {
|
||||
alice_peer_id,
|
||||
alice_addr,
|
||||
}) => {
|
||||
let db_state = if let Swap::Bob(db_state) = db.get_state(swap_id)? {
|
||||
db_state
|
||||
} else {
|
||||
bail!("Swap {} is not buy xmr.", swap_id)
|
||||
};
|
||||
|
||||
let (bitcoin_wallet, monero_wallet) = setup_wallets(
|
||||
let (bitcoin_wallet, monero_wallet, starting_balances) = setup_wallets(
|
||||
bitcoind_url,
|
||||
bitcoin_wallet_name.as_str(),
|
||||
monero_wallet_rpc_url,
|
||||
config,
|
||||
)
|
||||
.await?;
|
||||
bob_swap(
|
||||
|
||||
let bob_factory = SwapFactory::new(
|
||||
seed,
|
||||
db_path,
|
||||
swap_id,
|
||||
db_state.into(),
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
db,
|
||||
alice_peer_id,
|
||||
config,
|
||||
starting_balances,
|
||||
alice_addr,
|
||||
seed,
|
||||
)
|
||||
.await?;
|
||||
alice_peer_id,
|
||||
);
|
||||
let (swap, event_loop) = bob_factory.recover_bob_from_db().await?;
|
||||
|
||||
tokio::spawn(async move { event_loop.run().await });
|
||||
bob::run(swap).await?;
|
||||
}
|
||||
};
|
||||
|
||||
@ -260,7 +230,11 @@ async fn setup_wallets(
|
||||
bitcoin_wallet_name: &str,
|
||||
monero_wallet_rpc_url: url::Url,
|
||||
config: Config,
|
||||
) -> Result<(Arc<swap::bitcoin::Wallet>, Arc<swap::monero::Wallet>)> {
|
||||
) -> Result<(
|
||||
Arc<swap::bitcoin::Wallet>,
|
||||
Arc<swap::monero::Wallet>,
|
||||
StartingBalances,
|
||||
)> {
|
||||
let bitcoin_wallet =
|
||||
swap::bitcoin::Wallet::new(bitcoin_wallet_name, bitcoind_url, config.bitcoin_network)
|
||||
.await?;
|
||||
@ -279,68 +253,10 @@ async fn setup_wallets(
|
||||
);
|
||||
let monero_wallet = Arc::new(monero_wallet);
|
||||
|
||||
Ok((bitcoin_wallet, monero_wallet))
|
||||
}
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn alice_swap(
|
||||
swap_id: Uuid,
|
||||
state: AliceState,
|
||||
listen_addr: Multiaddr,
|
||||
bitcoin_wallet: Arc<swap::bitcoin::Wallet>,
|
||||
monero_wallet: Arc<swap::monero::Wallet>,
|
||||
config: Config,
|
||||
db: Database,
|
||||
seed: Seed,
|
||||
) -> Result<AliceState> {
|
||||
let alice_behaviour = alice::Behaviour::new(network::Seed::new(seed));
|
||||
let alice_peer_id = alice_behaviour.peer_id();
|
||||
info!("Own Peer-ID: {}", alice_peer_id);
|
||||
let alice_transport = build(alice_behaviour.identity())?;
|
||||
|
||||
let (mut event_loop, handle) =
|
||||
alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen_addr)?;
|
||||
|
||||
let swap = alice::swap::swap(
|
||||
state,
|
||||
handle,
|
||||
bitcoin_wallet.clone(),
|
||||
monero_wallet.clone(),
|
||||
config,
|
||||
swap_id,
|
||||
db,
|
||||
);
|
||||
|
||||
tokio::spawn(async move { event_loop.run().await });
|
||||
swap.await
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn bob_swap(
|
||||
swap_id: Uuid,
|
||||
state: BobState,
|
||||
bitcoin_wallet: Arc<swap::bitcoin::Wallet>,
|
||||
monero_wallet: Arc<swap::monero::Wallet>,
|
||||
db: Database,
|
||||
alice_peer_id: PeerId,
|
||||
alice_addr: Multiaddr,
|
||||
seed: Seed,
|
||||
) -> Result<BobState> {
|
||||
let bob_behaviour = bob::Behaviour::new(network::Seed::new(seed));
|
||||
let bob_transport = build(bob_behaviour.identity())?;
|
||||
|
||||
let (event_loop, handle) =
|
||||
bob::event_loop::EventLoop::new(bob_transport, bob_behaviour, alice_peer_id, alice_addr)?;
|
||||
|
||||
let swap = bob::swap::swap(
|
||||
state,
|
||||
handle,
|
||||
db,
|
||||
bitcoin_wallet.clone(),
|
||||
monero_wallet.clone(),
|
||||
OsRng,
|
||||
swap_id,
|
||||
);
|
||||
|
||||
tokio::spawn(event_loop.run());
|
||||
swap.await
|
||||
let starting_balances = StartingBalances {
|
||||
btc: bitcoin_balance,
|
||||
xmr: monero_balance,
|
||||
};
|
||||
|
||||
Ok((bitcoin_wallet, monero_wallet, starting_balances))
|
||||
}
|
||||
|
@ -1,2 +1,8 @@
|
||||
pub mod alice;
|
||||
pub mod bob;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StartingBalances {
|
||||
pub xmr: crate::monero::Amount,
|
||||
pub btc: bitcoin::Amount,
|
||||
}
|
||||
|
@ -1,19 +1,15 @@
|
||||
//! Run an XMR/BTC swap in the role of Alice.
|
||||
//! Alice holds XMR and wishes receive BTC.
|
||||
use anyhow::Result;
|
||||
use libp2p::{
|
||||
core::{identity::Keypair, Multiaddr},
|
||||
request_response::ResponseChannel,
|
||||
NetworkBehaviour, PeerId,
|
||||
};
|
||||
use anyhow::{bail, Result};
|
||||
use libp2p::{request_response::ResponseChannel, NetworkBehaviour, PeerId};
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::{
|
||||
bitcoin, database, monero,
|
||||
network::{
|
||||
peer_tracker::{self, PeerTracker},
|
||||
request_response::AliceToBob,
|
||||
transport::SwapTransport,
|
||||
Seed, TokioExecutor,
|
||||
Seed as NetworkSeed,
|
||||
},
|
||||
protocol::bob,
|
||||
SwapAmounts,
|
||||
@ -26,8 +22,16 @@ pub use self::{
|
||||
message1::Message1,
|
||||
message2::Message2,
|
||||
state::*,
|
||||
swap::{run_until, swap},
|
||||
swap::{run, run_until},
|
||||
};
|
||||
use crate::{
|
||||
config::Config, database::Database, network::transport::build, protocol::StartingBalances,
|
||||
seed::Seed,
|
||||
};
|
||||
use libp2p::{core::Multiaddr, identity::Keypair};
|
||||
use rand::rngs::OsRng;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use uuid::Uuid;
|
||||
|
||||
mod amounts;
|
||||
pub mod event_loop;
|
||||
@ -39,29 +43,173 @@ pub mod state;
|
||||
mod steps;
|
||||
pub mod swap;
|
||||
|
||||
pub type Swarm = libp2p::Swarm<Behaviour>;
|
||||
pub struct Swap {
|
||||
pub state: AliceState,
|
||||
pub event_loop_handle: EventLoopHandle,
|
||||
pub bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
pub monero_wallet: Arc<monero::Wallet>,
|
||||
pub config: Config,
|
||||
pub swap_id: Uuid,
|
||||
pub db: Database,
|
||||
}
|
||||
|
||||
pub fn new_swarm(
|
||||
pub struct SwapFactory {
|
||||
swap_id: Uuid,
|
||||
identity: Keypair,
|
||||
peer_id: PeerId,
|
||||
db_path: PathBuf,
|
||||
config: Config,
|
||||
|
||||
listen_address: Multiaddr,
|
||||
|
||||
pub bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
pub monero_wallet: Arc<monero::Wallet>,
|
||||
pub starting_balances: StartingBalances,
|
||||
}
|
||||
|
||||
impl SwapFactory {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn new(
|
||||
seed: Seed,
|
||||
config: Config,
|
||||
swap_id: Uuid,
|
||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
monero_wallet: Arc<monero::Wallet>,
|
||||
starting_balances: StartingBalances,
|
||||
db_path: PathBuf,
|
||||
listen_address: Multiaddr,
|
||||
) -> Self {
|
||||
let network_seed = NetworkSeed::new(seed);
|
||||
let identity = network_seed.derive_libp2p_identity();
|
||||
let peer_id = PeerId::from(identity.public());
|
||||
|
||||
Self {
|
||||
swap_id,
|
||||
identity,
|
||||
peer_id,
|
||||
db_path,
|
||||
config,
|
||||
listen_address,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
starting_balances,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn new_swap_as_alice(&self, swap_amounts: SwapAmounts) -> Result<(Swap, EventLoop)> {
|
||||
let initial_state = init_alice_state(
|
||||
swap_amounts.btc,
|
||||
swap_amounts.xmr,
|
||||
self.bitcoin_wallet.clone(),
|
||||
self.config,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (event_loop, event_loop_handle) = init_alice_event_loop(
|
||||
self.listen_address.clone(),
|
||||
self.identity.clone(),
|
||||
self.peer_id.clone(),
|
||||
)?;
|
||||
|
||||
let db = Database::open(self.db_path.as_path())?;
|
||||
|
||||
Ok((
|
||||
Swap {
|
||||
event_loop_handle,
|
||||
bitcoin_wallet: self.bitcoin_wallet.clone(),
|
||||
monero_wallet: self.monero_wallet.clone(),
|
||||
config: self.config,
|
||||
db,
|
||||
state: initial_state,
|
||||
swap_id: self.swap_id,
|
||||
},
|
||||
event_loop,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn recover_alice_from_db(&self) -> Result<(Swap, EventLoop)> {
|
||||
// reopen the existing database
|
||||
let db = Database::open(self.db_path.clone().as_path())?;
|
||||
|
||||
let resume_state = if let database::Swap::Alice(state) = db.get_state(self.swap_id)? {
|
||||
state.into()
|
||||
} else {
|
||||
bail!(
|
||||
"Trying to load swap with id {} for the wrong direction.",
|
||||
self.swap_id
|
||||
)
|
||||
};
|
||||
|
||||
let (event_loop, event_loop_handle) = init_alice_event_loop(
|
||||
self.listen_address.clone(),
|
||||
self.identity.clone(),
|
||||
self.peer_id.clone(),
|
||||
)?;
|
||||
|
||||
Ok((
|
||||
Swap {
|
||||
state: resume_state,
|
||||
event_loop_handle,
|
||||
bitcoin_wallet: self.bitcoin_wallet.clone(),
|
||||
monero_wallet: self.monero_wallet.clone(),
|
||||
config: self.config,
|
||||
swap_id: self.swap_id,
|
||||
db,
|
||||
},
|
||||
event_loop,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn peer_id(&self) -> PeerId {
|
||||
self.peer_id.clone()
|
||||
}
|
||||
|
||||
pub fn listen_address(&self) -> Multiaddr {
|
||||
self.listen_address.clone()
|
||||
}
|
||||
}
|
||||
|
||||
async fn init_alice_state(
|
||||
btc_to_swap: bitcoin::Amount,
|
||||
xmr_to_swap: monero::Amount,
|
||||
alice_btc_wallet: Arc<bitcoin::Wallet>,
|
||||
config: Config,
|
||||
) -> Result<AliceState> {
|
||||
let rng = &mut OsRng;
|
||||
|
||||
let amounts = SwapAmounts {
|
||||
btc: btc_to_swap,
|
||||
xmr: xmr_to_swap,
|
||||
};
|
||||
|
||||
let a = bitcoin::SecretKey::new_random(rng);
|
||||
let s_a = cross_curve_dleq::Scalar::random(rng);
|
||||
let v_a = monero::PrivateViewKey::new_random(rng);
|
||||
let redeem_address = alice_btc_wallet.as_ref().new_address().await?;
|
||||
let punish_address = redeem_address.clone();
|
||||
let state0 = State0::new(
|
||||
a,
|
||||
s_a,
|
||||
v_a,
|
||||
amounts.btc,
|
||||
amounts.xmr,
|
||||
config.bitcoin_cancel_timelock,
|
||||
config.bitcoin_punish_timelock,
|
||||
redeem_address,
|
||||
punish_address,
|
||||
);
|
||||
|
||||
Ok(AliceState::Started { amounts, state0 })
|
||||
}
|
||||
|
||||
fn init_alice_event_loop(
|
||||
listen: Multiaddr,
|
||||
transport: SwapTransport,
|
||||
behaviour: Behaviour,
|
||||
) -> Result<Swarm> {
|
||||
use anyhow::Context as _;
|
||||
|
||||
let local_peer_id = behaviour.peer_id();
|
||||
|
||||
let mut swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, local_peer_id.clone())
|
||||
.executor(Box::new(TokioExecutor {
|
||||
handle: tokio::runtime::Handle::current(),
|
||||
}))
|
||||
.build();
|
||||
|
||||
Swarm::listen_on(&mut swarm, listen.clone())
|
||||
.with_context(|| format!("Address is not supported: {:#}", listen))?;
|
||||
|
||||
tracing::info!("Initialized swarm: {}", local_peer_id);
|
||||
|
||||
Ok(swarm)
|
||||
identity: Keypair,
|
||||
peer_id: PeerId,
|
||||
) -> Result<(EventLoop, EventLoopHandle)> {
|
||||
let alice_behaviour = Behaviour::default();
|
||||
let alice_transport = build(identity)?;
|
||||
EventLoop::new(alice_transport, alice_behaviour, listen, peer_id)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -138,7 +286,7 @@ impl From<message3::OutEvent> for OutEvent {
|
||||
}
|
||||
|
||||
/// A `NetworkBehaviour` that represents an XMR/BTC swap node as Alice.
|
||||
#[derive(NetworkBehaviour)]
|
||||
#[derive(NetworkBehaviour, Default)]
|
||||
#[behaviour(out_event = "OutEvent", event_process = false)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Behaviour {
|
||||
@ -148,33 +296,9 @@ pub struct Behaviour {
|
||||
message1: message1::Behaviour,
|
||||
message2: message2::Behaviour,
|
||||
message3: message3::Behaviour,
|
||||
#[behaviour(ignore)]
|
||||
identity: Keypair,
|
||||
}
|
||||
|
||||
impl Behaviour {
|
||||
pub fn new(seed: Seed) -> Self {
|
||||
let identity = seed.derive_libp2p_identity();
|
||||
|
||||
Self {
|
||||
pt: PeerTracker::default(),
|
||||
amounts: Amounts::default(),
|
||||
message0: message0::Behaviour::default(),
|
||||
message1: message1::Behaviour::default(),
|
||||
message2: message2::Behaviour::default(),
|
||||
message3: message3::Behaviour::default(),
|
||||
identity,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn identity(&self) -> Keypair {
|
||||
self.identity.clone()
|
||||
}
|
||||
|
||||
pub fn peer_id(&self) -> PeerId {
|
||||
PeerId::from(self.identity.public())
|
||||
}
|
||||
|
||||
/// Alice always sends her messages as a response to a request from Bob.
|
||||
pub fn send_amounts(&mut self, channel: ResponseChannel<AliceToBob>, amounts: SwapAmounts) {
|
||||
let msg = AliceToBob::Amounts(amounts);
|
||||
|
@ -148,10 +148,9 @@ impl EventLoop {
|
||||
transport: SwapTransport,
|
||||
behaviour: Behaviour,
|
||||
listen: Multiaddr,
|
||||
peer_id: PeerId,
|
||||
) -> Result<(Self, EventLoopHandle)> {
|
||||
let local_peer_id = behaviour.peer_id();
|
||||
|
||||
let mut swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, local_peer_id)
|
||||
let mut swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, peer_id)
|
||||
.executor(Box::new(TokioExecutor {
|
||||
handle: tokio::runtime::Handle::current(),
|
||||
}))
|
||||
@ -249,8 +248,4 @@ impl EventLoop {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peer_id(&self) -> PeerId {
|
||||
self.swarm.peer_id()
|
||||
}
|
||||
}
|
||||
|
@ -15,18 +15,23 @@ use crate::{
|
||||
bitcoin,
|
||||
bitcoin::{TransactionBlockHeight, WatchForRawTransaction},
|
||||
config::Config,
|
||||
database::{Database, Swap},
|
||||
database,
|
||||
database::Database,
|
||||
monero,
|
||||
monero::CreateWalletForOutput,
|
||||
protocol::alice::{
|
||||
event_loop::EventLoopHandle,
|
||||
steps::{
|
||||
build_bitcoin_punish_transaction, build_bitcoin_redeem_transaction,
|
||||
extract_monero_private_key, lock_xmr, negotiate, publish_bitcoin_punish_transaction,
|
||||
publish_bitcoin_redeem_transaction, publish_cancel_transaction,
|
||||
wait_for_bitcoin_encrypted_signature, wait_for_bitcoin_refund, wait_for_locked_bitcoin,
|
||||
protocol::{
|
||||
alice,
|
||||
alice::{
|
||||
event_loop::EventLoopHandle,
|
||||
steps::{
|
||||
build_bitcoin_punish_transaction, build_bitcoin_redeem_transaction,
|
||||
extract_monero_private_key, lock_xmr, negotiate,
|
||||
publish_bitcoin_punish_transaction, publish_bitcoin_redeem_transaction,
|
||||
publish_cancel_transaction, wait_for_bitcoin_encrypted_signature,
|
||||
wait_for_bitcoin_refund, wait_for_locked_bitcoin,
|
||||
},
|
||||
AliceState,
|
||||
},
|
||||
AliceState,
|
||||
},
|
||||
ExpiredTimelocks,
|
||||
};
|
||||
@ -35,28 +40,6 @@ trait Rng: RngCore + CryptoRng + Send {}
|
||||
|
||||
impl<T> Rng for T where T: RngCore + CryptoRng + Send {}
|
||||
|
||||
pub async fn swap(
|
||||
state: AliceState,
|
||||
event_loop_handle: EventLoopHandle,
|
||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
monero_wallet: Arc<monero::Wallet>,
|
||||
config: Config,
|
||||
swap_id: Uuid,
|
||||
db: Database,
|
||||
) -> Result<AliceState> {
|
||||
run_until(
|
||||
state,
|
||||
is_complete,
|
||||
event_loop_handle,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
swap_id,
|
||||
db,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn is_complete(state: &AliceState) -> bool {
|
||||
matches!(
|
||||
state,
|
||||
@ -81,10 +64,31 @@ pub fn is_encsig_learned(state: &AliceState) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn run(swap: alice::Swap) -> Result<AliceState> {
|
||||
run_until(swap, is_complete).await
|
||||
}
|
||||
|
||||
pub async fn run_until(
|
||||
swap: alice::Swap,
|
||||
is_target_state: fn(&AliceState) -> bool,
|
||||
) -> Result<AliceState> {
|
||||
run_until_internal(
|
||||
swap.state,
|
||||
is_target_state,
|
||||
swap.event_loop_handle,
|
||||
swap.bitcoin_wallet,
|
||||
swap.monero_wallet,
|
||||
swap.config,
|
||||
swap.swap_id,
|
||||
swap.db,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// State machine driver for swap execution
|
||||
#[async_recursion]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn run_until(
|
||||
async fn run_until_internal(
|
||||
state: AliceState,
|
||||
is_target_state: fn(&AliceState) -> bool,
|
||||
mut event_loop_handle: EventLoopHandle,
|
||||
@ -110,9 +114,9 @@ pub async fn run_until(
|
||||
};
|
||||
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
run_until(
|
||||
run_until_internal(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -153,9 +157,9 @@ pub async fn run_until(
|
||||
};
|
||||
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
run_until(
|
||||
run_until_internal(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -194,9 +198,9 @@ pub async fn run_until(
|
||||
};
|
||||
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
run_until(
|
||||
run_until_internal(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -232,9 +236,9 @@ pub async fn run_until(
|
||||
};
|
||||
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
run_until(
|
||||
run_until_internal(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -270,9 +274,9 @@ pub async fn run_until(
|
||||
|
||||
let state = AliceState::CancelTimelockExpired { state3 };
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
return run_until(
|
||||
return run_until_internal(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -298,9 +302,9 @@ pub async fn run_until(
|
||||
|
||||
let state = AliceState::BtcRedeemed;
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
run_until(
|
||||
run_until_internal(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -325,9 +329,9 @@ pub async fn run_until(
|
||||
|
||||
let state = AliceState::BtcCancelled { state3, tx_cancel };
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
run_until(
|
||||
run_until_internal(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -358,10 +362,12 @@ pub async fn run_until(
|
||||
None => {
|
||||
let state = AliceState::BtcPunishable { tx_refund, state3 };
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
swap(
|
||||
|
||||
run_until_internal(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
bitcoin_wallet.clone(),
|
||||
monero_wallet,
|
||||
@ -382,9 +388,9 @@ pub async fn run_until(
|
||||
|
||||
let state = AliceState::BtcRefunded { spend_key, state3 };
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
run_until(
|
||||
run_until_internal(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -407,7 +413,7 @@ pub async fn run_until(
|
||||
|
||||
let state = AliceState::XmrRefunded;
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
Ok(state)
|
||||
}
|
||||
@ -437,9 +443,9 @@ pub async fn run_until(
|
||||
Either::Left(_) => {
|
||||
let state = AliceState::BtcPunished;
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
run_until(
|
||||
run_until_internal(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -461,9 +467,9 @@ pub async fn run_until(
|
||||
)?;
|
||||
let state = AliceState::BtcRefunded { spend_key, state3 };
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
run_until(
|
||||
run_until_internal(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
|
@ -1,19 +1,14 @@
|
||||
//! Run an XMR/BTC swap in the role of Bob.
|
||||
//! Bob holds BTC and wishes receive XMR.
|
||||
use anyhow::Result;
|
||||
use libp2p::{
|
||||
core::{identity::Keypair, Multiaddr},
|
||||
NetworkBehaviour, PeerId,
|
||||
};
|
||||
use anyhow::{bail, Result};
|
||||
use libp2p::{core::Multiaddr, NetworkBehaviour, PeerId};
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::{
|
||||
bitcoin,
|
||||
bitcoin::EncryptedSignature,
|
||||
network::{
|
||||
peer_tracker::{self, PeerTracker},
|
||||
transport::SwapTransport,
|
||||
Seed, TokioExecutor,
|
||||
},
|
||||
database, monero, network,
|
||||
network::peer_tracker::{self, PeerTracker},
|
||||
protocol::{alice, bob},
|
||||
SwapAmounts,
|
||||
};
|
||||
@ -26,8 +21,16 @@ pub use self::{
|
||||
message2::Message2,
|
||||
message3::Message3,
|
||||
state::*,
|
||||
swap::{run_until, swap},
|
||||
swap::{run, run_until},
|
||||
};
|
||||
use crate::{
|
||||
config::Config, database::Database, network::transport::build, protocol::StartingBalances,
|
||||
seed::Seed,
|
||||
};
|
||||
use libp2p::identity::Keypair;
|
||||
use rand::rngs::OsRng;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use uuid::Uuid;
|
||||
|
||||
mod amounts;
|
||||
pub mod event_loop;
|
||||
@ -38,20 +41,169 @@ mod message3;
|
||||
pub mod state;
|
||||
pub mod swap;
|
||||
|
||||
pub type Swarm = libp2p::Swarm<Behaviour>;
|
||||
pub struct Swap {
|
||||
pub state: BobState,
|
||||
pub event_loop_handle: bob::EventLoopHandle,
|
||||
pub db: Database,
|
||||
pub bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
pub monero_wallet: Arc<monero::Wallet>,
|
||||
pub swap_id: Uuid,
|
||||
}
|
||||
|
||||
pub fn new_swarm(transport: SwapTransport, behaviour: Behaviour) -> Result<Swarm> {
|
||||
let local_peer_id = behaviour.peer_id();
|
||||
pub struct SwapFactory {
|
||||
swap_id: Uuid,
|
||||
identity: Keypair,
|
||||
peer_id: PeerId,
|
||||
db_path: PathBuf,
|
||||
config: Config,
|
||||
|
||||
let swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, local_peer_id.clone())
|
||||
.executor(Box::new(TokioExecutor {
|
||||
handle: tokio::runtime::Handle::current(),
|
||||
}))
|
||||
.build();
|
||||
alice_connect_address: Multiaddr,
|
||||
alice_connect_peer_id: PeerId,
|
||||
|
||||
info!("Initialized swarm with identity {}", local_peer_id);
|
||||
pub bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
pub monero_wallet: Arc<monero::Wallet>,
|
||||
pub starting_balances: StartingBalances,
|
||||
}
|
||||
|
||||
Ok(swarm)
|
||||
impl SwapFactory {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
seed: Seed,
|
||||
db_path: PathBuf,
|
||||
swap_id: Uuid,
|
||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
monero_wallet: Arc<monero::Wallet>,
|
||||
config: Config,
|
||||
starting_balances: StartingBalances,
|
||||
alice_connect_address: Multiaddr,
|
||||
alice_connect_peer_id: PeerId,
|
||||
) -> Self {
|
||||
let identity = network::Seed::new(seed).derive_libp2p_identity();
|
||||
let peer_id = identity.public().into_peer_id();
|
||||
|
||||
Self {
|
||||
swap_id,
|
||||
identity,
|
||||
peer_id,
|
||||
db_path,
|
||||
config,
|
||||
alice_connect_address,
|
||||
alice_connect_peer_id,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
starting_balances,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn new_swap_as_bob(
|
||||
&self,
|
||||
swap_amounts: SwapAmounts,
|
||||
) -> Result<(bob::Swap, bob::EventLoop)> {
|
||||
let initial_state = init_bob_state(
|
||||
swap_amounts.btc,
|
||||
swap_amounts.xmr,
|
||||
self.bitcoin_wallet.clone(),
|
||||
self.config,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (event_loop, event_loop_handle) = init_bob_event_loop(
|
||||
self.identity.clone(),
|
||||
self.peer_id.clone(),
|
||||
self.alice_connect_peer_id.clone(),
|
||||
self.alice_connect_address.clone(),
|
||||
)?;
|
||||
|
||||
let db = Database::open(self.db_path.as_path())?;
|
||||
|
||||
Ok((
|
||||
Swap {
|
||||
state: initial_state,
|
||||
event_loop_handle,
|
||||
db,
|
||||
bitcoin_wallet: self.bitcoin_wallet.clone(),
|
||||
monero_wallet: self.monero_wallet.clone(),
|
||||
swap_id: self.swap_id,
|
||||
},
|
||||
event_loop,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn recover_bob_from_db(&self) -> Result<(bob::Swap, bob::EventLoop)> {
|
||||
// reopen the existing database
|
||||
let db = Database::open(self.db_path.clone().as_path())?;
|
||||
|
||||
let resume_state = if let database::Swap::Bob(state) = db.get_state(self.swap_id)? {
|
||||
state.into()
|
||||
} else {
|
||||
bail!(
|
||||
"Trying to load swap with id {} for the wrong direction.",
|
||||
self.swap_id
|
||||
)
|
||||
};
|
||||
|
||||
let (event_loop, event_loop_handle) = init_bob_event_loop(
|
||||
self.identity.clone(),
|
||||
self.peer_id.clone(),
|
||||
self.alice_connect_peer_id.clone(),
|
||||
self.alice_connect_address.clone(),
|
||||
)?;
|
||||
|
||||
Ok((
|
||||
Swap {
|
||||
state: resume_state,
|
||||
event_loop_handle,
|
||||
db,
|
||||
bitcoin_wallet: self.bitcoin_wallet.clone(),
|
||||
monero_wallet: self.monero_wallet.clone(),
|
||||
swap_id: self.swap_id,
|
||||
},
|
||||
event_loop,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn init_bob_state(
|
||||
btc_to_swap: bitcoin::Amount,
|
||||
xmr_to_swap: monero::Amount,
|
||||
bob_btc_wallet: Arc<bitcoin::Wallet>,
|
||||
config: Config,
|
||||
) -> Result<BobState> {
|
||||
let amounts = SwapAmounts {
|
||||
btc: btc_to_swap,
|
||||
xmr: xmr_to_swap,
|
||||
};
|
||||
|
||||
let refund_address = bob_btc_wallet.new_address().await?;
|
||||
let state0 = bob::State0::new(
|
||||
&mut OsRng,
|
||||
btc_to_swap,
|
||||
xmr_to_swap,
|
||||
config.bitcoin_cancel_timelock,
|
||||
config.bitcoin_punish_timelock,
|
||||
refund_address,
|
||||
config.monero_finality_confirmations,
|
||||
);
|
||||
|
||||
Ok(BobState::Started { state0, amounts })
|
||||
}
|
||||
|
||||
fn init_bob_event_loop(
|
||||
identity: Keypair,
|
||||
peer_id: PeerId,
|
||||
alice_peer_id: PeerId,
|
||||
alice_addr: Multiaddr,
|
||||
) -> Result<(bob::event_loop::EventLoop, bob::event_loop::EventLoopHandle)> {
|
||||
let bob_behaviour = bob::Behaviour::default();
|
||||
let bob_transport = build(identity)?;
|
||||
|
||||
bob::event_loop::EventLoop::new(
|
||||
bob_transport,
|
||||
bob_behaviour,
|
||||
peer_id,
|
||||
alice_peer_id,
|
||||
alice_addr,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -115,7 +267,7 @@ impl From<message3::OutEvent> for OutEvent {
|
||||
}
|
||||
|
||||
/// A `NetworkBehaviour` that represents an XMR/BTC swap node as Bob.
|
||||
#[derive(NetworkBehaviour)]
|
||||
#[derive(NetworkBehaviour, Default)]
|
||||
#[behaviour(out_event = "OutEvent", event_process = false)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Behaviour {
|
||||
@ -125,33 +277,9 @@ pub struct Behaviour {
|
||||
message1: message1::Behaviour,
|
||||
message2: message2::Behaviour,
|
||||
message3: message3::Behaviour,
|
||||
#[behaviour(ignore)]
|
||||
identity: Keypair,
|
||||
}
|
||||
|
||||
impl Behaviour {
|
||||
pub fn new(seed: Seed) -> Self {
|
||||
let identity = seed.derive_libp2p_identity();
|
||||
|
||||
Self {
|
||||
pt: PeerTracker::default(),
|
||||
amounts: Amounts::default(),
|
||||
message0: message0::Behaviour::default(),
|
||||
message1: message1::Behaviour::default(),
|
||||
message2: message2::Behaviour::default(),
|
||||
message3: message3::Behaviour::default(),
|
||||
identity,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn identity(&self) -> Keypair {
|
||||
self.identity.clone()
|
||||
}
|
||||
|
||||
pub fn peer_id(&self) -> PeerId {
|
||||
PeerId::from(self.identity.public())
|
||||
}
|
||||
|
||||
/// Sends a message to Alice to get current amounts based on `btc`.
|
||||
pub fn request_amounts(&mut self, alice: PeerId, btc: u64) {
|
||||
let btc = ::bitcoin::Amount::from_sat(btc);
|
||||
@ -189,19 +317,3 @@ impl Behaviour {
|
||||
self.pt.add_address(peer_id, address)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Behaviour {
|
||||
fn default() -> Behaviour {
|
||||
let identity = Keypair::generate_ed25519();
|
||||
|
||||
Self {
|
||||
pt: PeerTracker::default(),
|
||||
amounts: Amounts::default(),
|
||||
message0: message0::Behaviour::default(),
|
||||
message1: message1::Behaviour::default(),
|
||||
message2: message2::Behaviour::default(),
|
||||
message3: message3::Behaviour::default(),
|
||||
identity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -131,12 +131,11 @@ impl EventLoop {
|
||||
pub fn new(
|
||||
transport: SwapTransport,
|
||||
behaviour: Behaviour,
|
||||
peer_id: PeerId,
|
||||
alice_peer_id: PeerId,
|
||||
alice_addr: Multiaddr,
|
||||
) -> Result<(Self, EventLoopHandle)> {
|
||||
let local_peer_id = behaviour.peer_id();
|
||||
|
||||
let mut swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, local_peer_id)
|
||||
let mut swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, peer_id)
|
||||
.executor(Box::new(TokioExecutor {
|
||||
handle: tokio::runtime::Handle::current(),
|
||||
}))
|
||||
|
@ -1,6 +1,6 @@
|
||||
use anyhow::{bail, Result};
|
||||
use async_recursion::async_recursion;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use rand::{rngs::OsRng, CryptoRng, RngCore};
|
||||
use std::sync::Arc;
|
||||
use tokio::select;
|
||||
use tracing::{debug, info};
|
||||
@ -14,33 +14,6 @@ use crate::{
|
||||
ExpiredTimelocks, SwapAmounts,
|
||||
};
|
||||
|
||||
// TODO(Franck): Make this a method on a struct
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn swap<R>(
|
||||
state: BobState,
|
||||
event_loop_handle: EventLoopHandle,
|
||||
db: Database,
|
||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
monero_wallet: Arc<monero::Wallet>,
|
||||
rng: R,
|
||||
swap_id: Uuid,
|
||||
) -> Result<BobState>
|
||||
where
|
||||
R: RngCore + CryptoRng + Send,
|
||||
{
|
||||
run_until(
|
||||
state,
|
||||
is_complete,
|
||||
event_loop_handle,
|
||||
db,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
rng,
|
||||
swap_id,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn is_complete(state: &BobState) -> bool {
|
||||
matches!(
|
||||
state,
|
||||
@ -63,10 +36,32 @@ pub fn is_encsig_sent(state: &BobState) -> bool {
|
||||
matches!(state, BobState::EncSigSent(..))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn run(swap: bob::Swap) -> Result<BobState> {
|
||||
run_until(swap, is_complete).await
|
||||
}
|
||||
|
||||
pub async fn run_until(
|
||||
swap: bob::Swap,
|
||||
is_target_state: fn(&BobState) -> bool,
|
||||
) -> Result<BobState> {
|
||||
run_until_internal(
|
||||
swap.state,
|
||||
is_target_state,
|
||||
swap.event_loop_handle,
|
||||
swap.db,
|
||||
swap.bitcoin_wallet,
|
||||
swap.monero_wallet,
|
||||
OsRng,
|
||||
swap.swap_id,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// State machine driver for swap execution
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[async_recursion]
|
||||
pub async fn run_until<R>(
|
||||
async fn run_until_internal<R>(
|
||||
state: BobState,
|
||||
is_target_state: fn(&BobState) -> bool,
|
||||
mut event_loop_handle: EventLoopHandle,
|
||||
@ -99,7 +94,7 @@ where
|
||||
let state = BobState::Negotiated(state2);
|
||||
let db_state = state.clone().into();
|
||||
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
|
||||
run_until(
|
||||
run_until_internal(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -120,7 +115,7 @@ where
|
||||
let state = BobState::BtcLocked(state3);
|
||||
let db_state = state.clone().into();
|
||||
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
|
||||
run_until(
|
||||
run_until_internal(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -185,7 +180,7 @@ where
|
||||
};
|
||||
let db_state = state.clone().into();
|
||||
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
|
||||
run_until(
|
||||
run_until_internal(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -226,7 +221,7 @@ where
|
||||
};
|
||||
let db_state = state.clone().into();
|
||||
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
|
||||
run_until(
|
||||
run_until_internal(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -261,7 +256,7 @@ where
|
||||
|
||||
let db_state = state.clone().into();
|
||||
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
|
||||
run_until(
|
||||
run_until_internal(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -282,7 +277,7 @@ where
|
||||
};
|
||||
let db_state = state.clone().into();
|
||||
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
|
||||
run_until(
|
||||
run_until_internal(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -307,7 +302,7 @@ where
|
||||
db.insert_latest_state(swap_id, Swap::Bob(state.clone().into()))
|
||||
.await?;
|
||||
|
||||
run_until(
|
||||
run_until_internal(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -336,7 +331,7 @@ where
|
||||
|
||||
let db_state = state.clone().into();
|
||||
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
|
||||
run_until(
|
||||
run_until_internal(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
|
@ -1,4 +1,3 @@
|
||||
use rand::rngs::OsRng;
|
||||
use swap::protocol::{alice, bob};
|
||||
use tokio::join;
|
||||
|
||||
@ -8,33 +7,17 @@ pub mod testutils;
|
||||
|
||||
#[tokio::test]
|
||||
async fn happy_path() {
|
||||
testutils::test(|alice_harness, bob_harness| async move {
|
||||
let alice = alice_harness.new_alice().await;
|
||||
let bob = bob_harness.new_bob().await;
|
||||
testutils::setup_test(|ctx| async move {
|
||||
let alice_swap = ctx.new_swap_as_alice().await;
|
||||
let bob_swap = ctx.new_swap_as_bob().await;
|
||||
|
||||
let alice_swap = alice::swap(
|
||||
alice.state,
|
||||
alice.event_loop_handle,
|
||||
alice.bitcoin_wallet.clone(),
|
||||
alice.monero_wallet.clone(),
|
||||
alice.config,
|
||||
alice.swap_id,
|
||||
alice.db,
|
||||
);
|
||||
let alice = alice::run(alice_swap);
|
||||
|
||||
let bob_swap = bob::swap(
|
||||
bob.state,
|
||||
bob.event_loop_handle,
|
||||
bob.db,
|
||||
bob.bitcoin_wallet.clone(),
|
||||
bob.monero_wallet.clone(),
|
||||
OsRng,
|
||||
bob.swap_id,
|
||||
);
|
||||
let (alice_state, bob_state) = join!(alice_swap, bob_swap);
|
||||
let bob = bob::run(bob_swap);
|
||||
let (alice_state, bob_state) = join!(alice, bob);
|
||||
|
||||
alice_harness.assert_redeemed(alice_state.unwrap()).await;
|
||||
bob_harness.assert_redeemed(bob_state.unwrap()).await;
|
||||
ctx.assert_alice_redeemed(alice_state.unwrap()).await;
|
||||
ctx.assert_bob_redeemed(bob_state.unwrap()).await;
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
@ -1,58 +1,30 @@
|
||||
use rand::rngs::OsRng;
|
||||
use swap::protocol::{alice, alice::AliceState, bob};
|
||||
|
||||
pub mod testutils;
|
||||
|
||||
#[tokio::test]
|
||||
async fn given_alice_restarts_after_encsig_is_learned_resume_swap() {
|
||||
testutils::test(|alice_harness, bob_harness| async move {
|
||||
let alice = alice_harness.new_alice().await;
|
||||
let bob = bob_harness.new_bob().await;
|
||||
testutils::setup_test(|ctx| async move {
|
||||
let alice_swap = ctx.new_swap_as_alice().await;
|
||||
let bob_swap = ctx.new_swap_as_bob().await;
|
||||
|
||||
let bob_swap = bob::swap(
|
||||
bob.state,
|
||||
bob.event_loop_handle,
|
||||
bob.db,
|
||||
bob.bitcoin_wallet.clone(),
|
||||
bob.monero_wallet.clone(),
|
||||
OsRng,
|
||||
bob.swap_id,
|
||||
);
|
||||
let bob_swap_handle = tokio::spawn(bob_swap);
|
||||
let bob = bob::run(bob_swap);
|
||||
let bob_handle = tokio::spawn(bob);
|
||||
|
||||
let alice_state = alice::run_until(
|
||||
alice.state,
|
||||
alice::swap::is_encsig_learned,
|
||||
alice.event_loop_handle,
|
||||
alice.bitcoin_wallet.clone(),
|
||||
alice.monero_wallet.clone(),
|
||||
alice.config,
|
||||
alice.swap_id,
|
||||
alice.db,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let alice_state = alice::run_until(alice_swap, alice::swap::is_encsig_learned)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(matches!(alice_state, AliceState::EncSigLearned {..}));
|
||||
|
||||
let alice = alice_harness.recover_alice_from_db().await;
|
||||
assert!(matches!(alice.state, AliceState::EncSigLearned {..}));
|
||||
let alice_swap = ctx.recover_alice_from_db().await;
|
||||
assert!(matches!(alice_swap.state, AliceState::EncSigLearned {..}));
|
||||
|
||||
let alice_state = alice::swap(
|
||||
alice.state,
|
||||
alice.event_loop_handle,
|
||||
alice.bitcoin_wallet.clone(),
|
||||
alice.monero_wallet.clone(),
|
||||
alice.config,
|
||||
alice.swap_id,
|
||||
alice.db,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let alice_state = alice::run(alice_swap).await.unwrap();
|
||||
|
||||
alice_harness.assert_redeemed(alice_state).await;
|
||||
ctx.assert_alice_redeemed(alice_state).await;
|
||||
|
||||
let bob_state = bob_swap_handle.await.unwrap();
|
||||
bob_harness.assert_redeemed(bob_state.unwrap()).await
|
||||
let bob_state = bob_handle.await.unwrap();
|
||||
ctx.assert_bob_redeemed(bob_state.unwrap()).await
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
@ -1,59 +1,31 @@
|
||||
use rand::rngs::OsRng;
|
||||
use swap::protocol::{alice, bob, bob::BobState};
|
||||
|
||||
pub mod testutils;
|
||||
|
||||
#[tokio::test]
|
||||
async fn given_bob_restarts_after_encsig_is_sent_resume_swap() {
|
||||
testutils::test(|alice_harness, bob_harness| async move {
|
||||
let alice = alice_harness.new_alice().await;
|
||||
let bob = bob_harness.new_bob().await;
|
||||
testutils::setup_test(|ctx| async move {
|
||||
let alice_swap = ctx.new_swap_as_alice().await;
|
||||
let bob_swap = ctx.new_swap_as_bob().await;
|
||||
|
||||
let alice_swap = alice::swap(
|
||||
alice.state,
|
||||
alice.event_loop_handle,
|
||||
alice.bitcoin_wallet.clone(),
|
||||
alice.monero_wallet.clone(),
|
||||
alice.config,
|
||||
alice.swap_id,
|
||||
alice.db,
|
||||
);
|
||||
let alice_swap_handle = tokio::spawn(alice_swap);
|
||||
let alice = alice::run(alice_swap);
|
||||
let alice_handle = tokio::spawn(alice);
|
||||
|
||||
let bob_state = bob::run_until(
|
||||
bob.state,
|
||||
bob::swap::is_encsig_sent,
|
||||
bob.event_loop_handle,
|
||||
bob.db,
|
||||
bob.bitcoin_wallet.clone(),
|
||||
bob.monero_wallet.clone(),
|
||||
OsRng,
|
||||
bob.swap_id,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let bob_state = bob::run_until(bob_swap, bob::swap::is_encsig_sent)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(matches!(bob_state, BobState::EncSigSent {..}));
|
||||
|
||||
let bob = bob_harness.recover_bob_from_db().await;
|
||||
assert!(matches!(bob.state, BobState::EncSigSent {..}));
|
||||
let bob_swap = ctx.recover_bob_from_db().await;
|
||||
assert!(matches!(bob_swap.state, BobState::EncSigSent {..}));
|
||||
|
||||
let bob_state = bob::swap(
|
||||
bob.state,
|
||||
bob.event_loop_handle,
|
||||
bob.db,
|
||||
bob.bitcoin_wallet.clone(),
|
||||
bob.monero_wallet.clone(),
|
||||
OsRng,
|
||||
bob.swap_id,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let bob_state = bob::run(bob_swap).await.unwrap();
|
||||
|
||||
bob_harness.assert_redeemed(bob_state).await;
|
||||
ctx.assert_bob_redeemed(bob_state).await;
|
||||
|
||||
let alice_state = alice_swap_handle.await.unwrap();
|
||||
alice_harness.assert_redeemed(alice_state.unwrap()).await;
|
||||
let alice_state = alice_handle.await.unwrap();
|
||||
ctx.assert_alice_redeemed(alice_state.unwrap()).await;
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
@ -1,59 +1,32 @@
|
||||
use rand::rngs::OsRng;
|
||||
use swap::protocol::{alice, bob, bob::BobState};
|
||||
use swap::protocol::{
|
||||
alice, bob,
|
||||
bob::{swap::is_xmr_locked, BobState},
|
||||
};
|
||||
|
||||
pub mod testutils;
|
||||
|
||||
#[tokio::test]
|
||||
async fn given_bob_restarts_after_xmr_is_locked_resume_swap() {
|
||||
testutils::test(|alice_harness, bob_harness| async move {
|
||||
let alice = alice_harness.new_alice().await;
|
||||
let bob = bob_harness.new_bob().await;
|
||||
testutils::setup_test(|ctx| async move {
|
||||
let alice_swap = ctx.new_swap_as_alice().await;
|
||||
let bob_swap = ctx.new_swap_as_bob().await;
|
||||
|
||||
let alice_swap = alice::swap(
|
||||
alice.state,
|
||||
alice.event_loop_handle,
|
||||
alice.bitcoin_wallet.clone(),
|
||||
alice.monero_wallet.clone(),
|
||||
alice.config,
|
||||
alice.swap_id,
|
||||
alice.db,
|
||||
);
|
||||
let alice_swap_handle = tokio::spawn(alice_swap);
|
||||
let alice_handle = alice::run(alice_swap);
|
||||
let alice_swap_handle = tokio::spawn(alice_handle);
|
||||
|
||||
let bob_state = bob::run_until(
|
||||
bob.state,
|
||||
bob::swap::is_xmr_locked,
|
||||
bob.event_loop_handle,
|
||||
bob.db,
|
||||
bob.bitcoin_wallet.clone(),
|
||||
bob.monero_wallet.clone(),
|
||||
OsRng,
|
||||
bob.swap_id,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let bob_state = bob::run_until(bob_swap, is_xmr_locked).await.unwrap();
|
||||
|
||||
assert!(matches!(bob_state, BobState::XmrLocked {..}));
|
||||
|
||||
let bob = bob_harness.recover_bob_from_db().await;
|
||||
assert!(matches!(bob.state, BobState::XmrLocked {..}));
|
||||
let bob_swap = ctx.recover_bob_from_db().await;
|
||||
assert!(matches!(bob_swap.state, BobState::XmrLocked {..}));
|
||||
|
||||
let bob_state = bob::swap(
|
||||
bob.state,
|
||||
bob.event_loop_handle,
|
||||
bob.db,
|
||||
bob.bitcoin_wallet.clone(),
|
||||
bob.monero_wallet.clone(),
|
||||
OsRng,
|
||||
bob.swap_id,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let bob_state = bob::run(bob_swap).await.unwrap();
|
||||
|
||||
bob_harness.assert_redeemed(bob_state).await;
|
||||
ctx.assert_bob_redeemed(bob_state).await;
|
||||
|
||||
let alice_state = alice_swap_handle.await.unwrap();
|
||||
alice_harness.assert_redeemed(alice_state.unwrap()).await;
|
||||
ctx.assert_alice_redeemed(alice_state.unwrap()).await;
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
use rand::rngs::OsRng;
|
||||
use swap::protocol::{alice, bob, bob::BobState};
|
||||
use swap::protocol::{
|
||||
alice, bob,
|
||||
bob::{swap::is_btc_locked, BobState},
|
||||
};
|
||||
|
||||
pub mod testutils;
|
||||
|
||||
@ -7,57 +9,28 @@ pub mod testutils;
|
||||
/// the encsig and fail to refund or redeem. Alice punishes.
|
||||
#[tokio::test]
|
||||
async fn alice_punishes_if_bob_never_acts_after_fund() {
|
||||
testutils::test(|alice_harness, bob_harness| async move {
|
||||
let alice = alice_harness.new_alice().await;
|
||||
let bob = bob_harness.new_bob().await;
|
||||
testutils::setup_test(|ctx| async move {
|
||||
let alice_swap = ctx.new_swap_as_alice().await;
|
||||
let bob_swap = ctx.new_swap_as_bob().await;
|
||||
|
||||
let alice_swap = alice::swap(
|
||||
alice.state,
|
||||
alice.event_loop_handle,
|
||||
alice.bitcoin_wallet.clone(),
|
||||
alice.monero_wallet.clone(),
|
||||
alice.config,
|
||||
alice.swap_id,
|
||||
alice.db,
|
||||
);
|
||||
let alice_swap_handle = tokio::spawn(alice_swap);
|
||||
let alice = alice::run(alice_swap);
|
||||
let alice_handle = tokio::spawn(alice);
|
||||
|
||||
let bob_state = bob::run_until(
|
||||
bob.state,
|
||||
bob::swap::is_btc_locked,
|
||||
bob.event_loop_handle,
|
||||
bob.db,
|
||||
bob.bitcoin_wallet.clone(),
|
||||
bob.monero_wallet.clone(),
|
||||
OsRng,
|
||||
bob.swap_id,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let bob_state = bob::run_until(bob_swap, is_btc_locked).await.unwrap();
|
||||
|
||||
assert!(matches!(bob_state, BobState::BtcLocked {..}));
|
||||
|
||||
let alice_state = alice_swap_handle.await.unwrap();
|
||||
alice_harness.assert_punished(alice_state.unwrap()).await;
|
||||
let alice_state = alice_handle.await.unwrap();
|
||||
ctx.assert_alice_punished(alice_state.unwrap()).await;
|
||||
|
||||
// Restart Bob after Alice punished to ensure Bob transitions to
|
||||
// punished and does not run indefinitely
|
||||
let bob = bob_harness.recover_bob_from_db().await;
|
||||
assert!(matches!(bob.state, BobState::BtcLocked {..}));
|
||||
let bob_swap = ctx.recover_bob_from_db().await;
|
||||
assert!(matches!(bob_swap.state, BobState::BtcLocked {..}));
|
||||
|
||||
let bob_state = bob::swap(
|
||||
bob.state,
|
||||
bob.event_loop_handle,
|
||||
bob.db,
|
||||
bob.bitcoin_wallet.clone(),
|
||||
bob.monero_wallet.clone(),
|
||||
OsRng,
|
||||
bob.swap_id,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let bob_state = bob::run(bob_swap).await.unwrap();
|
||||
|
||||
bob_harness.assert_punished(bob_state).await;
|
||||
ctx.assert_bob_punished(bob_state).await;
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use rand::rngs::OsRng;
|
||||
use swap::protocol::{alice, alice::AliceState, bob};
|
||||
|
||||
pub mod testutils;
|
||||
@ -7,60 +6,33 @@ pub mod testutils;
|
||||
/// then also refunds.
|
||||
#[tokio::test]
|
||||
async fn given_alice_restarts_after_xmr_is_locked_abort_swap() {
|
||||
testutils::test(|alice_harness, bob_harness| async move {
|
||||
let alice = alice_harness.new_alice().await;
|
||||
let bob = bob_harness.new_bob().await;
|
||||
testutils::setup_test(|ctx| async move {
|
||||
let alice_swap = ctx.new_swap_as_alice().await;
|
||||
let bob_swap = ctx.new_swap_as_bob().await;
|
||||
|
||||
let bob_swap = bob::swap(
|
||||
bob.state,
|
||||
bob.event_loop_handle,
|
||||
bob.db,
|
||||
bob.bitcoin_wallet.clone(),
|
||||
bob.monero_wallet.clone(),
|
||||
OsRng,
|
||||
bob.swap_id,
|
||||
);
|
||||
let bob_swap_handle = tokio::spawn(bob_swap);
|
||||
let bob = bob::run(bob_swap);
|
||||
let bob_handle = tokio::spawn(bob);
|
||||
|
||||
let alice_state = alice::run_until(
|
||||
alice.state,
|
||||
alice::swap::is_xmr_locked,
|
||||
alice.event_loop_handle,
|
||||
alice.bitcoin_wallet.clone(),
|
||||
alice.monero_wallet.clone(),
|
||||
alice.config,
|
||||
alice.swap_id,
|
||||
alice.db,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let alice_state = alice::run_until(alice_swap, alice::swap::is_xmr_locked)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(matches!(alice_state, AliceState::XmrLocked {..}));
|
||||
|
||||
// Alice does not act, Bob refunds
|
||||
let bob_state = bob_swap_handle.await.unwrap();
|
||||
let bob_state = bob_handle.await.unwrap();
|
||||
|
||||
// Once bob has finished Alice is restarted and refunds as well
|
||||
let alice = alice_harness.recover_alice_from_db().await;
|
||||
assert!(matches!(alice.state, AliceState::XmrLocked {..}));
|
||||
let alice_swap = ctx.recover_alice_from_db().await;
|
||||
assert!(matches!(alice_swap.state, AliceState::XmrLocked {..}));
|
||||
|
||||
let alice_state = alice::swap(
|
||||
alice.state,
|
||||
alice.event_loop_handle,
|
||||
alice.bitcoin_wallet.clone(),
|
||||
alice.monero_wallet.clone(),
|
||||
alice.config,
|
||||
alice.swap_id,
|
||||
alice.db,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let alice_state = alice::run(alice_swap).await.unwrap();
|
||||
|
||||
// TODO: The test passes like this, but the assertion should be done after Bob
|
||||
// refunded, not at the end because this can cause side-effects!
|
||||
// We have to properly wait for the refund tx's finality inside the assertion,
|
||||
// which requires storing the refund_tx_id in the the state!
|
||||
bob_harness.assert_refunded(bob_state.unwrap()).await;
|
||||
alice_harness.assert_refunded(alice_state).await;
|
||||
ctx.assert_bob_refunded(bob_state.unwrap()).await;
|
||||
ctx.assert_alice_refunded(alice_state).await;
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
@ -2,17 +2,14 @@ use crate::testutils;
|
||||
use bitcoin_harness::Bitcoind;
|
||||
use futures::Future;
|
||||
use get_port::get_port;
|
||||
use libp2p::{core::Multiaddr, PeerId};
|
||||
use libp2p::core::Multiaddr;
|
||||
use monero_harness::{image, Monero};
|
||||
use rand::rngs::OsRng;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
use swap::{
|
||||
bitcoin,
|
||||
config::Config,
|
||||
database::Database,
|
||||
monero, network,
|
||||
network::transport::build,
|
||||
protocol::{alice, alice::AliceState, bob, bob::BobState},
|
||||
monero,
|
||||
protocol::{alice, alice::AliceState, bob, bob::BobState, StartingBalances},
|
||||
seed::Seed,
|
||||
SwapAmounts,
|
||||
};
|
||||
@ -22,9 +19,291 @@ use tracing_core::dispatcher::DefaultGuard;
|
||||
use tracing_log::LogTracer;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub async fn test<T, F>(testfn: T)
|
||||
pub struct TestContext {
|
||||
swap_amounts: SwapAmounts,
|
||||
|
||||
alice_swap_factory: alice::SwapFactory,
|
||||
bob_swap_factory: bob::SwapFactory,
|
||||
}
|
||||
|
||||
impl TestContext {
|
||||
pub async fn new_swap_as_alice(&self) -> alice::Swap {
|
||||
let (swap, mut event_loop) = self
|
||||
.alice_swap_factory
|
||||
.new_swap_as_alice(self.swap_amounts)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
tokio::spawn(async move { event_loop.run().await });
|
||||
|
||||
swap
|
||||
}
|
||||
|
||||
pub async fn new_swap_as_bob(&self) -> bob::Swap {
|
||||
let (swap, event_loop) = self
|
||||
.bob_swap_factory
|
||||
.new_swap_as_bob(self.swap_amounts)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
tokio::spawn(async move { event_loop.run().await });
|
||||
|
||||
swap
|
||||
}
|
||||
|
||||
pub async fn recover_alice_from_db(&self) -> alice::Swap {
|
||||
let (swap, mut event_loop) = self
|
||||
.alice_swap_factory
|
||||
.recover_alice_from_db()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
tokio::spawn(async move { event_loop.run().await });
|
||||
|
||||
swap
|
||||
}
|
||||
|
||||
pub async fn recover_bob_from_db(&self) -> bob::Swap {
|
||||
let (swap, event_loop) = self.bob_swap_factory.recover_bob_from_db().await.unwrap();
|
||||
|
||||
tokio::spawn(async move { event_loop.run().await });
|
||||
|
||||
swap
|
||||
}
|
||||
|
||||
pub async fn assert_alice_redeemed(&self, state: AliceState) {
|
||||
assert!(matches!(state, AliceState::BtcRedeemed));
|
||||
|
||||
let btc_balance_after_swap = self
|
||||
.alice_swap_factory
|
||||
.bitcoin_wallet
|
||||
.as_ref()
|
||||
.balance()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
btc_balance_after_swap,
|
||||
self.alice_swap_factory.starting_balances.btc + self.swap_amounts.btc
|
||||
- bitcoin::Amount::from_sat(bitcoin::TX_FEE)
|
||||
);
|
||||
|
||||
let xmr_balance_after_swap = self
|
||||
.alice_swap_factory
|
||||
.monero_wallet
|
||||
.as_ref()
|
||||
.get_balance()
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(
|
||||
xmr_balance_after_swap
|
||||
<= self.alice_swap_factory.starting_balances.xmr - self.swap_amounts.xmr
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn assert_alice_refunded(&self, state: AliceState) {
|
||||
assert!(matches!(state, AliceState::XmrRefunded));
|
||||
|
||||
let btc_balance_after_swap = self
|
||||
.alice_swap_factory
|
||||
.bitcoin_wallet
|
||||
.as_ref()
|
||||
.balance()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
btc_balance_after_swap,
|
||||
self.alice_swap_factory.starting_balances.btc
|
||||
);
|
||||
|
||||
// Ensure that Alice's balance is refreshed as we use a newly created wallet
|
||||
self.alice_swap_factory
|
||||
.monero_wallet
|
||||
.as_ref()
|
||||
.inner
|
||||
.refresh()
|
||||
.await
|
||||
.unwrap();
|
||||
let xmr_balance_after_swap = self
|
||||
.alice_swap_factory
|
||||
.monero_wallet
|
||||
.as_ref()
|
||||
.get_balance()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(xmr_balance_after_swap, self.swap_amounts.xmr);
|
||||
}
|
||||
|
||||
pub async fn assert_alice_punished(&self, state: AliceState) {
|
||||
assert!(matches!(state, AliceState::BtcPunished));
|
||||
|
||||
let btc_balance_after_swap = self
|
||||
.alice_swap_factory
|
||||
.bitcoin_wallet
|
||||
.as_ref()
|
||||
.balance()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
btc_balance_after_swap,
|
||||
self.alice_swap_factory.starting_balances.btc + self.swap_amounts.btc
|
||||
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE)
|
||||
);
|
||||
|
||||
let xmr_balance_after_swap = self
|
||||
.alice_swap_factory
|
||||
.monero_wallet
|
||||
.as_ref()
|
||||
.get_balance()
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(
|
||||
xmr_balance_after_swap
|
||||
<= self.alice_swap_factory.starting_balances.xmr - self.swap_amounts.xmr
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn assert_bob_redeemed(&self, state: BobState) {
|
||||
let lock_tx_id = if let BobState::XmrRedeemed { tx_lock_id } = state {
|
||||
tx_lock_id
|
||||
} else {
|
||||
panic!("Bob in unexpected state");
|
||||
};
|
||||
|
||||
let lock_tx_bitcoin_fee = self
|
||||
.bob_swap_factory
|
||||
.bitcoin_wallet
|
||||
.transaction_fee(lock_tx_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let btc_balance_after_swap = self
|
||||
.bob_swap_factory
|
||||
.bitcoin_wallet
|
||||
.as_ref()
|
||||
.balance()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
btc_balance_after_swap,
|
||||
self.bob_swap_factory.starting_balances.btc
|
||||
- self.swap_amounts.btc
|
||||
- lock_tx_bitcoin_fee
|
||||
);
|
||||
|
||||
// Ensure that Bob's balance is refreshed as we use a newly created wallet
|
||||
self.bob_swap_factory
|
||||
.monero_wallet
|
||||
.as_ref()
|
||||
.inner
|
||||
.refresh()
|
||||
.await
|
||||
.unwrap();
|
||||
let xmr_balance_after_swap = self
|
||||
.bob_swap_factory
|
||||
.monero_wallet
|
||||
.as_ref()
|
||||
.get_balance()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
xmr_balance_after_swap,
|
||||
self.bob_swap_factory.starting_balances.xmr + self.swap_amounts.xmr
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn assert_bob_refunded(&self, state: BobState) {
|
||||
let lock_tx_id = if let BobState::BtcRefunded(state4) = state {
|
||||
state4.tx_lock_id()
|
||||
} else {
|
||||
panic!("Bob in unexpected state");
|
||||
};
|
||||
let lock_tx_bitcoin_fee = self
|
||||
.bob_swap_factory
|
||||
.bitcoin_wallet
|
||||
.transaction_fee(lock_tx_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let btc_balance_after_swap = self
|
||||
.bob_swap_factory
|
||||
.bitcoin_wallet
|
||||
.as_ref()
|
||||
.balance()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let alice_submitted_cancel = btc_balance_after_swap
|
||||
== self.bob_swap_factory.starting_balances.btc
|
||||
- lock_tx_bitcoin_fee
|
||||
- bitcoin::Amount::from_sat(bitcoin::TX_FEE);
|
||||
|
||||
let bob_submitted_cancel = btc_balance_after_swap
|
||||
== self.bob_swap_factory.starting_balances.btc
|
||||
- lock_tx_bitcoin_fee
|
||||
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE);
|
||||
|
||||
// The cancel tx can be submitted by both Alice and Bob.
|
||||
// Since we cannot be sure who submitted it we have to assert accordingly
|
||||
assert!(alice_submitted_cancel || bob_submitted_cancel);
|
||||
|
||||
let xmr_balance_after_swap = self
|
||||
.bob_swap_factory
|
||||
.monero_wallet
|
||||
.as_ref()
|
||||
.get_balance()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
xmr_balance_after_swap,
|
||||
self.bob_swap_factory.starting_balances.xmr
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn assert_bob_punished(&self, state: BobState) {
|
||||
let lock_tx_id = if let BobState::BtcPunished { tx_lock_id } = state {
|
||||
tx_lock_id
|
||||
} else {
|
||||
panic!("Bob in unexpected state");
|
||||
};
|
||||
|
||||
let lock_tx_bitcoin_fee = self
|
||||
.bob_swap_factory
|
||||
.bitcoin_wallet
|
||||
.transaction_fee(lock_tx_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let btc_balance_after_swap = self
|
||||
.bob_swap_factory
|
||||
.bitcoin_wallet
|
||||
.as_ref()
|
||||
.balance()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
btc_balance_after_swap,
|
||||
self.bob_swap_factory.starting_balances.btc
|
||||
- self.swap_amounts.btc
|
||||
- lock_tx_bitcoin_fee
|
||||
);
|
||||
|
||||
let xmr_balance_after_swap = self
|
||||
.bob_swap_factory
|
||||
.monero_wallet
|
||||
.as_ref()
|
||||
.get_balance()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
xmr_balance_after_swap,
|
||||
self.bob_swap_factory.starting_balances.xmr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn setup_test<T, F>(testfn: T)
|
||||
where
|
||||
T: Fn(AliceHarness, BobHarness) -> F,
|
||||
T: Fn(TestContext) -> F,
|
||||
F: Future<Output = ()>,
|
||||
{
|
||||
let cli = Cli::default();
|
||||
@ -44,13 +323,31 @@ where
|
||||
xmr: swap_amounts.xmr * 10,
|
||||
btc: bitcoin::Amount::ZERO,
|
||||
};
|
||||
let alice_harness = AliceHarness::new(
|
||||
config,
|
||||
swap_amounts,
|
||||
Uuid::new_v4(),
|
||||
&monero,
|
||||
|
||||
let port = get_port().expect("Failed to find a free port");
|
||||
|
||||
let listen_address: Multiaddr = format!("/ip4/127.0.0.1/tcp/{}", port)
|
||||
.parse()
|
||||
.expect("failed to parse Alice's address");
|
||||
|
||||
let (alice_bitcoin_wallet, alice_monero_wallet) = init_wallets(
|
||||
"alice",
|
||||
&containers.bitcoind,
|
||||
&monero,
|
||||
alice_starting_balances.clone(),
|
||||
config,
|
||||
)
|
||||
.await;
|
||||
|
||||
let alice_swap_factory = alice::SwapFactory::new(
|
||||
Seed::random().unwrap(),
|
||||
config,
|
||||
Uuid::new_v4(),
|
||||
alice_bitcoin_wallet,
|
||||
alice_monero_wallet,
|
||||
alice_starting_balances,
|
||||
tempdir().unwrap().path().to_path_buf(),
|
||||
listen_address,
|
||||
)
|
||||
.await;
|
||||
|
||||
@ -59,397 +356,34 @@ where
|
||||
btc: swap_amounts.btc * 10,
|
||||
};
|
||||
|
||||
let bob_harness = BobHarness::new(
|
||||
config,
|
||||
swap_amounts,
|
||||
Uuid::new_v4(),
|
||||
&monero,
|
||||
let (bob_bitcoin_wallet, bob_monero_wallet) = init_wallets(
|
||||
"bob",
|
||||
&containers.bitcoind,
|
||||
bob_starting_balances,
|
||||
alice_harness.listen_address(),
|
||||
alice_harness.peer_id(),
|
||||
&monero,
|
||||
bob_starting_balances.clone(),
|
||||
config,
|
||||
)
|
||||
.await;
|
||||
|
||||
testfn(alice_harness, bob_harness).await
|
||||
}
|
||||
|
||||
pub struct Alice {
|
||||
pub state: AliceState,
|
||||
pub event_loop_handle: alice::EventLoopHandle,
|
||||
pub bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
pub monero_wallet: Arc<monero::Wallet>,
|
||||
pub config: Config,
|
||||
pub swap_id: Uuid,
|
||||
pub db: Database,
|
||||
}
|
||||
|
||||
pub struct AliceHarness {
|
||||
listen_address: Multiaddr,
|
||||
peer_id: PeerId,
|
||||
|
||||
seed: Seed,
|
||||
db_path: PathBuf,
|
||||
swap_id: Uuid,
|
||||
|
||||
swap_amounts: SwapAmounts,
|
||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
monero_wallet: Arc<monero::Wallet>,
|
||||
config: Config,
|
||||
starting_balances: StartingBalances,
|
||||
}
|
||||
|
||||
impl AliceHarness {
|
||||
async fn new(
|
||||
config: Config,
|
||||
swap_amounts: SwapAmounts,
|
||||
swap_id: Uuid,
|
||||
monero: &Monero,
|
||||
bitcoind: &Bitcoind<'_>,
|
||||
starting_balances: StartingBalances,
|
||||
) -> Self {
|
||||
let port = get_port().expect("Failed to find a free port");
|
||||
|
||||
let listen_address: Multiaddr = format!("/ip4/127.0.0.1/tcp/{}", port)
|
||||
.parse()
|
||||
.expect("failed to parse Alice's address");
|
||||
|
||||
let seed = Seed::random().unwrap();
|
||||
|
||||
let db_path = tempdir().unwrap().path().to_path_buf();
|
||||
|
||||
let (bitcoin_wallet, monero_wallet) =
|
||||
init_wallets("alice", bitcoind, monero, starting_balances.clone(), config).await;
|
||||
|
||||
// TODO: This should be done by changing the production code
|
||||
let network_seed = network::Seed::new(seed);
|
||||
let identity = network_seed.derive_libp2p_identity();
|
||||
let peer_id = PeerId::from(identity.public());
|
||||
|
||||
Self {
|
||||
seed,
|
||||
db_path,
|
||||
listen_address,
|
||||
peer_id,
|
||||
swap_id,
|
||||
swap_amounts,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
starting_balances,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn new_alice(&self) -> Alice {
|
||||
let initial_state = init_alice_state(
|
||||
self.swap_amounts.btc,
|
||||
self.swap_amounts.xmr,
|
||||
self.bitcoin_wallet.clone(),
|
||||
self.config,
|
||||
)
|
||||
.await;
|
||||
|
||||
let (mut event_loop, event_loop_handle) =
|
||||
init_alice_event_loop(self.listen_address.clone(), self.seed);
|
||||
|
||||
tokio::spawn(async move { event_loop.run().await });
|
||||
|
||||
let db = Database::open(self.db_path.as_path()).unwrap();
|
||||
|
||||
Alice {
|
||||
event_loop_handle,
|
||||
bitcoin_wallet: self.bitcoin_wallet.clone(),
|
||||
monero_wallet: self.monero_wallet.clone(),
|
||||
config: self.config,
|
||||
db,
|
||||
state: initial_state,
|
||||
swap_id: self.swap_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn recover_alice_from_db(&self) -> Alice {
|
||||
// TODO: "simulated restart" issues:
|
||||
// - create new wallets instead of reusing (hard because of container
|
||||
// lifetimes)
|
||||
// - consider aborting the old event loop (currently just keeps running)
|
||||
|
||||
// reopen the existing database
|
||||
let db = Database::open(self.db_path.clone().as_path()).unwrap();
|
||||
|
||||
let resume_state =
|
||||
if let swap::database::Swap::Alice(state) = db.get_state(self.swap_id).unwrap() {
|
||||
state.into()
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let (mut event_loop, event_loop_handle) =
|
||||
init_alice_event_loop(self.listen_address.clone(), self.seed);
|
||||
|
||||
tokio::spawn(async move { event_loop.run().await });
|
||||
|
||||
Alice {
|
||||
state: resume_state,
|
||||
event_loop_handle,
|
||||
bitcoin_wallet: self.bitcoin_wallet.clone(),
|
||||
monero_wallet: self.monero_wallet.clone(),
|
||||
config: self.config,
|
||||
swap_id: self.swap_id,
|
||||
db,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn assert_redeemed(&self, state: AliceState) {
|
||||
assert!(matches!(state, AliceState::BtcRedeemed));
|
||||
|
||||
let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap();
|
||||
assert_eq!(
|
||||
btc_balance_after_swap,
|
||||
self.starting_balances.btc + self.swap_amounts.btc
|
||||
- bitcoin::Amount::from_sat(bitcoin::TX_FEE)
|
||||
);
|
||||
|
||||
let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap();
|
||||
assert!(xmr_balance_after_swap <= self.starting_balances.xmr - self.swap_amounts.xmr);
|
||||
}
|
||||
|
||||
pub async fn assert_refunded(&self, state: AliceState) {
|
||||
assert!(matches!(state, AliceState::XmrRefunded));
|
||||
|
||||
let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap();
|
||||
assert_eq!(btc_balance_after_swap, self.starting_balances.btc);
|
||||
|
||||
// Ensure that Alice's balance is refreshed as we use a newly created wallet
|
||||
self.monero_wallet.as_ref().inner.refresh().await.unwrap();
|
||||
let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap();
|
||||
assert_eq!(xmr_balance_after_swap, self.swap_amounts.xmr);
|
||||
}
|
||||
|
||||
pub async fn assert_punished(&self, state: AliceState) {
|
||||
assert!(matches!(state, AliceState::BtcPunished));
|
||||
|
||||
let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap();
|
||||
assert_eq!(
|
||||
btc_balance_after_swap,
|
||||
self.starting_balances.btc + self.swap_amounts.btc
|
||||
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE)
|
||||
);
|
||||
|
||||
let xnr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap();
|
||||
assert!(xnr_balance_after_swap <= self.starting_balances.xmr - self.swap_amounts.xmr);
|
||||
}
|
||||
|
||||
pub fn peer_id(&self) -> PeerId {
|
||||
self.peer_id.clone()
|
||||
}
|
||||
|
||||
pub fn listen_address(&self) -> Multiaddr {
|
||||
self.listen_address.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Bob {
|
||||
pub state: BobState,
|
||||
pub event_loop_handle: bob::EventLoopHandle,
|
||||
pub db: Database,
|
||||
pub bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
pub monero_wallet: Arc<monero::Wallet>,
|
||||
pub swap_id: Uuid,
|
||||
}
|
||||
|
||||
pub struct BobHarness {
|
||||
db_path: PathBuf,
|
||||
swap_id: Uuid,
|
||||
|
||||
swap_amounts: SwapAmounts,
|
||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
monero_wallet: Arc<monero::Wallet>,
|
||||
config: Config,
|
||||
starting_balances: StartingBalances,
|
||||
|
||||
alice_connect_address: Multiaddr,
|
||||
alice_connect_peer_id: PeerId,
|
||||
}
|
||||
|
||||
impl BobHarness {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn new(
|
||||
config: Config,
|
||||
swap_amounts: SwapAmounts,
|
||||
swap_id: Uuid,
|
||||
monero: &Monero,
|
||||
bitcoind: &Bitcoind<'_>,
|
||||
starting_balances: StartingBalances,
|
||||
alice_connect_address: Multiaddr,
|
||||
alice_connect_peer_id: PeerId,
|
||||
) -> Self {
|
||||
let db_path = tempdir().unwrap().path().to_path_buf();
|
||||
|
||||
let (bitcoin_wallet, monero_wallet) =
|
||||
init_wallets("bob", bitcoind, monero, starting_balances.clone(), config).await;
|
||||
|
||||
Self {
|
||||
db_path,
|
||||
swap_id,
|
||||
swap_amounts,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
starting_balances,
|
||||
alice_connect_address,
|
||||
alice_connect_peer_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn new_bob(&self) -> Bob {
|
||||
let initial_state = init_bob_state(
|
||||
self.swap_amounts.btc,
|
||||
self.swap_amounts.xmr,
|
||||
self.bitcoin_wallet.clone(),
|
||||
self.config,
|
||||
)
|
||||
.await;
|
||||
|
||||
let (event_loop, event_loop_handle) = init_bob_event_loop(
|
||||
self.alice_connect_peer_id.clone(),
|
||||
self.alice_connect_address.clone(),
|
||||
);
|
||||
|
||||
tokio::spawn(async move { event_loop.run().await });
|
||||
|
||||
let db = Database::open(self.db_path.as_path()).unwrap();
|
||||
|
||||
Bob {
|
||||
state: initial_state,
|
||||
event_loop_handle,
|
||||
db,
|
||||
bitcoin_wallet: self.bitcoin_wallet.clone(),
|
||||
monero_wallet: self.monero_wallet.clone(),
|
||||
swap_id: self.swap_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn recover_bob_from_db(&self) -> Bob {
|
||||
// TODO: "simulated restart" issues:
|
||||
// - create new wallets instead of reusing (hard because of container
|
||||
// lifetimes)
|
||||
// - consider aborting the old event loop (currently just keeps running)
|
||||
|
||||
// reopen the existing database
|
||||
let db = Database::open(self.db_path.clone().as_path()).unwrap();
|
||||
|
||||
let resume_state =
|
||||
if let swap::database::Swap::Bob(state) = db.get_state(self.swap_id).unwrap() {
|
||||
state.into()
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let (event_loop, event_loop_handle) = init_bob_event_loop(
|
||||
self.alice_connect_peer_id.clone(),
|
||||
self.alice_connect_address.clone(),
|
||||
);
|
||||
|
||||
tokio::spawn(async move { event_loop.run().await });
|
||||
|
||||
Bob {
|
||||
state: resume_state,
|
||||
event_loop_handle,
|
||||
db,
|
||||
bitcoin_wallet: self.bitcoin_wallet.clone(),
|
||||
monero_wallet: self.monero_wallet.clone(),
|
||||
swap_id: self.swap_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn assert_redeemed(&self, state: BobState) {
|
||||
let lock_tx_id = if let BobState::XmrRedeemed { tx_lock_id } = state {
|
||||
tx_lock_id
|
||||
} else {
|
||||
panic!("Bob in unexpected state");
|
||||
};
|
||||
|
||||
let lock_tx_bitcoin_fee = self
|
||||
.bitcoin_wallet
|
||||
.transaction_fee(lock_tx_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap();
|
||||
assert_eq!(
|
||||
btc_balance_after_swap,
|
||||
self.starting_balances.btc - self.swap_amounts.btc - lock_tx_bitcoin_fee
|
||||
);
|
||||
|
||||
// Ensure that Bob's balance is refreshed as we use a newly created wallet
|
||||
self.monero_wallet.as_ref().inner.refresh().await.unwrap();
|
||||
let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap();
|
||||
assert_eq!(
|
||||
xmr_balance_after_swap,
|
||||
self.starting_balances.xmr + self.swap_amounts.xmr
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn assert_refunded(&self, state: BobState) {
|
||||
let lock_tx_id = if let BobState::BtcRefunded(state4) = state {
|
||||
state4.tx_lock_id()
|
||||
} else {
|
||||
panic!("Bob in unexpected state");
|
||||
};
|
||||
let lock_tx_bitcoin_fee = self
|
||||
.bitcoin_wallet
|
||||
.transaction_fee(lock_tx_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap();
|
||||
|
||||
let alice_submitted_cancel = btc_balance_after_swap
|
||||
== self.starting_balances.btc
|
||||
- lock_tx_bitcoin_fee
|
||||
- bitcoin::Amount::from_sat(bitcoin::TX_FEE);
|
||||
|
||||
let bob_submitted_cancel = btc_balance_after_swap
|
||||
== self.starting_balances.btc
|
||||
- lock_tx_bitcoin_fee
|
||||
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE);
|
||||
|
||||
// The cancel tx can be submitted by both Alice and Bob.
|
||||
// Since we cannot be sure who submitted it we have to assert accordingly
|
||||
assert!(alice_submitted_cancel || bob_submitted_cancel);
|
||||
|
||||
let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap();
|
||||
assert_eq!(xmr_balance_after_swap, self.starting_balances.xmr);
|
||||
}
|
||||
|
||||
pub async fn assert_punished(&self, state: BobState) {
|
||||
let lock_tx_id = if let BobState::BtcPunished { tx_lock_id } = state {
|
||||
tx_lock_id
|
||||
} else {
|
||||
panic!("Bob in unexpected state");
|
||||
};
|
||||
|
||||
let lock_tx_bitcoin_fee = self
|
||||
.bitcoin_wallet
|
||||
.transaction_fee(lock_tx_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap();
|
||||
assert_eq!(
|
||||
btc_balance_after_swap,
|
||||
self.starting_balances.btc - self.swap_amounts.btc - lock_tx_bitcoin_fee
|
||||
);
|
||||
|
||||
let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap();
|
||||
assert_eq!(xmr_balance_after_swap, self.starting_balances.xmr);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct StartingBalances {
|
||||
pub xmr: monero::Amount,
|
||||
pub btc: bitcoin::Amount,
|
||||
let bob_swap_factory = bob::SwapFactory::new(
|
||||
Seed::random().unwrap(),
|
||||
tempdir().unwrap().path().to_path_buf(),
|
||||
Uuid::new_v4(),
|
||||
bob_bitcoin_wallet,
|
||||
bob_monero_wallet,
|
||||
config,
|
||||
bob_starting_balances,
|
||||
alice_swap_factory.listen_address(),
|
||||
alice_swap_factory.peer_id(),
|
||||
);
|
||||
|
||||
let test = TestContext {
|
||||
swap_amounts,
|
||||
alice_swap_factory,
|
||||
bob_swap_factory,
|
||||
};
|
||||
|
||||
testfn(test).await
|
||||
}
|
||||
|
||||
async fn init_containers(cli: &Cli) -> (Monero, Containers<'_>) {
|
||||
@ -498,87 +432,6 @@ async fn init_wallets(
|
||||
(btc_wallet, xmr_wallet)
|
||||
}
|
||||
|
||||
async fn init_alice_state(
|
||||
btc_to_swap: bitcoin::Amount,
|
||||
xmr_to_swap: monero::Amount,
|
||||
alice_btc_wallet: Arc<bitcoin::Wallet>,
|
||||
config: Config,
|
||||
) -> AliceState {
|
||||
let rng = &mut OsRng;
|
||||
|
||||
let amounts = SwapAmounts {
|
||||
btc: btc_to_swap,
|
||||
xmr: xmr_to_swap,
|
||||
};
|
||||
|
||||
let a = bitcoin::SecretKey::new_random(rng);
|
||||
let s_a = cross_curve_dleq::Scalar::random(rng);
|
||||
let v_a = monero::PrivateViewKey::new_random(rng);
|
||||
let redeem_address = alice_btc_wallet.as_ref().new_address().await.unwrap();
|
||||
let punish_address = redeem_address.clone();
|
||||
let state0 = alice::State0::new(
|
||||
a,
|
||||
s_a,
|
||||
v_a,
|
||||
amounts.btc,
|
||||
amounts.xmr,
|
||||
config.bitcoin_cancel_timelock,
|
||||
config.bitcoin_punish_timelock,
|
||||
redeem_address,
|
||||
punish_address,
|
||||
);
|
||||
|
||||
AliceState::Started { amounts, state0 }
|
||||
}
|
||||
|
||||
fn init_alice_event_loop(
|
||||
listen: Multiaddr,
|
||||
seed: Seed,
|
||||
) -> (
|
||||
alice::event_loop::EventLoop,
|
||||
alice::event_loop::EventLoopHandle,
|
||||
) {
|
||||
let alice_behaviour = alice::Behaviour::new(network::Seed::new(seed));
|
||||
let alice_transport = build(alice_behaviour.identity()).unwrap();
|
||||
alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen).unwrap()
|
||||
}
|
||||
|
||||
async fn init_bob_state(
|
||||
btc_to_swap: bitcoin::Amount,
|
||||
xmr_to_swap: monero::Amount,
|
||||
bob_btc_wallet: Arc<bitcoin::Wallet>,
|
||||
config: Config,
|
||||
) -> BobState {
|
||||
let amounts = SwapAmounts {
|
||||
btc: btc_to_swap,
|
||||
xmr: xmr_to_swap,
|
||||
};
|
||||
|
||||
let refund_address = bob_btc_wallet.new_address().await.unwrap();
|
||||
let state0 = bob::State0::new(
|
||||
&mut OsRng,
|
||||
btc_to_swap,
|
||||
xmr_to_swap,
|
||||
config.bitcoin_cancel_timelock,
|
||||
config.bitcoin_punish_timelock,
|
||||
refund_address,
|
||||
config.monero_finality_confirmations,
|
||||
);
|
||||
|
||||
BobState::Started { state0, amounts }
|
||||
}
|
||||
|
||||
fn init_bob_event_loop(
|
||||
alice_peer_id: PeerId,
|
||||
alice_addr: Multiaddr,
|
||||
) -> (bob::event_loop::EventLoop, bob::event_loop::EventLoopHandle) {
|
||||
let seed = Seed::random().unwrap();
|
||||
let bob_behaviour = bob::Behaviour::new(network::Seed::new(seed));
|
||||
let bob_transport = build(bob_behaviour.identity()).unwrap();
|
||||
bob::event_loop::EventLoop::new(bob_transport, bob_behaviour, alice_peer_id, alice_addr)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// This is just to keep the containers alive
|
||||
#[allow(dead_code)]
|
||||
struct Containers<'a> {
|
||||
|
Loading…
Reference in New Issue
Block a user