mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-12-26 16:09:41 -05:00
Merge #177
177: Nectar r=D4nte a=D4nte Re-work Alice do be the Service Provider (nectar). This means that Alice's event loop runs all the time and processes swap requests. - We currently use a hardcoded fixed rate - The binary for nectar is to be done in follow-up PR - We removed the message acknowledgement feature as it created issues with other work and some work would have been needed to link a ack to the correct swap on Alice side. - Because we removed the acks, we had to remove a number of restart tests. This can be re-introduced at a later stage once a decision on how to best test the restarts can be done - We remove the punish test as it is a nectar (alice) feature and current focus is on the CLI (bob). Co-authored-by: Franck Royer <franck@coblox.tech>
This commit is contained in:
commit
fadbf1638a
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@ -127,16 +127,10 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
test_name: [
|
test_name: [
|
||||||
happy_path,
|
happy_path,
|
||||||
happy_path_restart_alice,
|
|
||||||
happy_path_restart_bob_after_comm,
|
|
||||||
happy_path_restart_bob_after_lock_proof_received,
|
|
||||||
happy_path_restart_bob_before_comm,
|
happy_path_restart_bob_before_comm,
|
||||||
punish,
|
|
||||||
refund_restart_alice_cancelled,
|
|
||||||
refund_restart_alice,
|
|
||||||
bob_refunds_using_cancel_and_refund_command,
|
bob_refunds_using_cancel_and_refund_command,
|
||||||
bob_refunds_using_cancel_and_refund_command_timelock_not_exired,
|
bob_refunds_using_cancel_and_refund_command_timelock_not_expired,
|
||||||
bob_refunds_using_cancel_and_refund_command_timelock_not_exired_force,
|
bob_refunds_using_cancel_and_refund_command_timelock_not_expired_force,
|
||||||
]
|
]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -2885,10 +2885,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust_decimal"
|
name = "rust_decimal"
|
||||||
version = "1.9.0"
|
version = "1.10.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a5c739ba050709eae138f053356d27ff818d71fe54ce5a8d9f4c7a660bfb6684"
|
checksum = "e3e5a94e2006dd60c603d8481c65b665b4b6694f78d23e15869ad10eb883e36e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
@ -3,11 +3,8 @@ status = [
|
|||||||
"build_test (x86_64-unknown-linux-gnu)",
|
"build_test (x86_64-unknown-linux-gnu)",
|
||||||
"build_test (x86_64-apple-darwin)",
|
"build_test (x86_64-apple-darwin)",
|
||||||
"docker_tests (happy_path)",
|
"docker_tests (happy_path)",
|
||||||
"docker_tests (happy_path_restart_alice)",
|
|
||||||
"docker_tests (happy_path_restart_bob_after_comm)",
|
|
||||||
"docker_tests (happy_path_restart_bob_after_lock_proof_received)",
|
|
||||||
"docker_tests (happy_path_restart_bob_before_comm)",
|
"docker_tests (happy_path_restart_bob_before_comm)",
|
||||||
"docker_tests (punish)",
|
"docker_tests (bob_refunds_using_cancel_and_refund_command)",
|
||||||
"docker_tests (refund_restart_alice_cancelled)",
|
"docker_tests (bob_refunds_using_cancel_and_refund_command_timelock_not_expired_force)",
|
||||||
"docker_tests (refund_restart_alice)",
|
"docker_tests (bob_refunds_using_cancel_and_refund_command_timelock_not_expired)"
|
||||||
]
|
]
|
||||||
|
@ -5,6 +5,12 @@ authors = ["CoBloX developers <team@coblox.tech>"]
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "XMR/BTC trustless atomic swaps."
|
description = "XMR/BTC trustless atomic swaps."
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "cli"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "swap"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
async-recursion = "0.3.1"
|
async-recursion = "0.3.1"
|
||||||
@ -34,7 +40,7 @@ pem = "0.8"
|
|||||||
prettytable-rs = "0.8"
|
prettytable-rs = "0.8"
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
reqwest = { version = "0.11", default-features = false }
|
reqwest = { version = "0.11", default-features = false }
|
||||||
rust_decimal = "1.8"
|
rust_decimal = "1.10"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_cbor = "0.11"
|
serde_cbor = "0.11"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
|
@ -12,41 +12,34 @@
|
|||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use crate::{
|
use anyhow::{Context, Result};
|
||||||
|
use log::LevelFilter;
|
||||||
|
use prettytable::{row, Table};
|
||||||
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
use structopt::StructOpt;
|
||||||
|
use swap::{
|
||||||
|
bitcoin,
|
||||||
cli::{Cancel, Command, Options, Refund, Resume},
|
cli::{Cancel, Command, Options, Refund, Resume},
|
||||||
|
config,
|
||||||
config::{
|
config::{
|
||||||
initial_setup, query_user_for_initial_testnet_config, read_config, ConfigNotInitialized,
|
initial_setup, query_user_for_initial_testnet_config, read_config, ConfigNotInitialized,
|
||||||
},
|
},
|
||||||
|
database::Database,
|
||||||
|
execution_params,
|
||||||
execution_params::GetExecutionParams,
|
execution_params::GetExecutionParams,
|
||||||
|
fs::{default_config_path, default_data_dir},
|
||||||
|
monero,
|
||||||
monero::{CreateWallet, OpenWallet},
|
monero::{CreateWallet, OpenWallet},
|
||||||
protocol::bob::cancel::CancelError,
|
protocol::{
|
||||||
|
bob,
|
||||||
|
bob::{cancel::CancelError, Builder},
|
||||||
|
SwapAmounts,
|
||||||
|
},
|
||||||
|
trace::init_tracing,
|
||||||
};
|
};
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use database::Database;
|
|
||||||
use fs::{default_config_path, default_data_dir};
|
|
||||||
use log::LevelFilter;
|
|
||||||
use prettytable::{row, Table};
|
|
||||||
use protocol::{alice, bob, bob::Builder, SwapAmounts};
|
|
||||||
use std::{path::PathBuf, sync::Arc};
|
|
||||||
use structopt::StructOpt;
|
|
||||||
use trace::init_tracing;
|
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub mod bitcoin;
|
|
||||||
pub mod config;
|
|
||||||
pub mod database;
|
|
||||||
pub mod execution_params;
|
|
||||||
pub mod monero;
|
|
||||||
pub mod network;
|
|
||||||
pub mod protocol;
|
|
||||||
pub mod seed;
|
|
||||||
pub mod trace;
|
|
||||||
|
|
||||||
mod cli;
|
|
||||||
mod fs;
|
|
||||||
mod serde_peer_id;
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate prettytable;
|
extern crate prettytable;
|
||||||
|
|
||||||
@ -70,7 +63,7 @@ async fn main() -> Result<()> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let db_path = data_dir.join("database");
|
let db_path = data_dir.join("database");
|
||||||
let seed = config::seed::Seed::from_file_or_generate(&data_dir)
|
let seed = config::Seed::from_file_or_generate(&data_dir)
|
||||||
.expect("Could not retrieve/initialize seed")
|
.expect("Could not retrieve/initialize seed")
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
@ -80,42 +73,6 @@ async fn main() -> Result<()> {
|
|||||||
let execution_params = execution_params::Testnet::get_execution_params();
|
let execution_params = execution_params::Testnet::get_execution_params();
|
||||||
|
|
||||||
match opt.cmd {
|
match opt.cmd {
|
||||||
Command::SellXmr {
|
|
||||||
listen_addr,
|
|
||||||
send_monero,
|
|
||||||
receive_bitcoin,
|
|
||||||
config,
|
|
||||||
} => {
|
|
||||||
let swap_amounts = SwapAmounts {
|
|
||||||
xmr: send_monero,
|
|
||||||
btc: receive_bitcoin,
|
|
||||||
};
|
|
||||||
|
|
||||||
let (bitcoin_wallet, monero_wallet) =
|
|
||||||
init_wallets(config.path, bitcoin_network, monero_network).await?;
|
|
||||||
|
|
||||||
let swap_id = Uuid::new_v4();
|
|
||||||
|
|
||||||
info!(
|
|
||||||
"Swap sending {} and receiving {} started with ID {}",
|
|
||||||
send_monero, receive_bitcoin, swap_id
|
|
||||||
);
|
|
||||||
|
|
||||||
let alice_factory = alice::Builder::new(
|
|
||||||
seed,
|
|
||||||
execution_params,
|
|
||||||
swap_id,
|
|
||||||
Arc::new(bitcoin_wallet),
|
|
||||||
Arc::new(monero_wallet),
|
|
||||||
db_path,
|
|
||||||
listen_addr,
|
|
||||||
);
|
|
||||||
let (swap, mut event_loop) =
|
|
||||||
alice_factory.with_init_params(swap_amounts).build().await?;
|
|
||||||
|
|
||||||
tokio::spawn(async move { event_loop.run().await });
|
|
||||||
alice::run(swap).await?;
|
|
||||||
}
|
|
||||||
Command::BuyXmr {
|
Command::BuyXmr {
|
||||||
alice_peer_id,
|
alice_peer_id,
|
||||||
alice_addr,
|
alice_addr,
|
||||||
@ -167,28 +124,6 @@ async fn main() -> Result<()> {
|
|||||||
// Print the table to stdout
|
// Print the table to stdout
|
||||||
table.printstd();
|
table.printstd();
|
||||||
}
|
}
|
||||||
Command::Resume(Resume::SellXmr {
|
|
||||||
swap_id,
|
|
||||||
listen_addr,
|
|
||||||
config,
|
|
||||||
}) => {
|
|
||||||
let (bitcoin_wallet, monero_wallet) =
|
|
||||||
init_wallets(config.path, bitcoin_network, monero_network).await?;
|
|
||||||
|
|
||||||
let alice_factory = alice::Builder::new(
|
|
||||||
seed,
|
|
||||||
execution_params,
|
|
||||||
swap_id,
|
|
||||||
Arc::new(bitcoin_wallet),
|
|
||||||
Arc::new(monero_wallet),
|
|
||||||
db_path,
|
|
||||||
listen_addr,
|
|
||||||
);
|
|
||||||
let (swap, mut event_loop) = alice_factory.build().await?;
|
|
||||||
|
|
||||||
tokio::spawn(async move { event_loop.run().await });
|
|
||||||
alice::run(swap).await?;
|
|
||||||
}
|
|
||||||
Command::Resume(Resume::BuyXmr {
|
Command::Resume(Resume::BuyXmr {
|
||||||
swap_id,
|
swap_id,
|
||||||
alice_peer_id,
|
alice_peer_id,
|
@ -19,19 +19,6 @@ pub struct Options {
|
|||||||
#[derive(structopt::StructOpt, Debug)]
|
#[derive(structopt::StructOpt, Debug)]
|
||||||
#[structopt(name = "xmr_btc-swap", about = "XMR BTC atomic swap")]
|
#[structopt(name = "xmr_btc-swap", about = "XMR BTC atomic swap")]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
SellXmr {
|
|
||||||
#[structopt(long = "p2p-address", default_value = "/ip4/0.0.0.0/tcp/9876")]
|
|
||||||
listen_addr: Multiaddr,
|
|
||||||
|
|
||||||
#[structopt(long = "send-xmr", help = "Monero amount as floating point nr without denomination (e.g. 125.1)", parse(try_from_str = parse_xmr))]
|
|
||||||
send_monero: monero::Amount,
|
|
||||||
|
|
||||||
#[structopt(long = "receive-btc", help = "Bitcoin amount as floating point nr without denomination (e.g. 1.25)", parse(try_from_str = parse_btc))]
|
|
||||||
receive_bitcoin: bitcoin::Amount,
|
|
||||||
|
|
||||||
#[structopt(flatten)]
|
|
||||||
config: Config,
|
|
||||||
},
|
|
||||||
BuyXmr {
|
BuyXmr {
|
||||||
#[structopt(long = "connect-peer-id")]
|
#[structopt(long = "connect-peer-id")]
|
||||||
alice_peer_id: PeerId,
|
alice_peer_id: PeerId,
|
||||||
@ -56,16 +43,6 @@ pub enum Command {
|
|||||||
|
|
||||||
#[derive(structopt::StructOpt, Debug)]
|
#[derive(structopt::StructOpt, Debug)]
|
||||||
pub enum Resume {
|
pub enum Resume {
|
||||||
SellXmr {
|
|
||||||
#[structopt(long = "swap-id")]
|
|
||||||
swap_id: Uuid,
|
|
||||||
|
|
||||||
#[structopt(long = "listen-address", default_value = "/ip4/127.0.0.1/tcp/9876")]
|
|
||||||
listen_addr: Multiaddr,
|
|
||||||
|
|
||||||
#[structopt(flatten)]
|
|
||||||
config: Config,
|
|
||||||
},
|
|
||||||
BuyXmr {
|
BuyXmr {
|
||||||
#[structopt(long = "swap-id")]
|
#[structopt(long = "swap-id")]
|
||||||
swap_id: Uuid,
|
swap_id: Uuid,
|
||||||
|
@ -13,6 +13,8 @@ use url::Url;
|
|||||||
|
|
||||||
pub mod seed;
|
pub mod seed;
|
||||||
|
|
||||||
|
pub use seed::Seed;
|
||||||
|
|
||||||
const DEFAULT_BITCOIND_TESTNET_URL: &str = "http://127.0.0.1:18332";
|
const DEFAULT_BITCOIND_TESTNET_URL: &str = "http://127.0.0.1:18332";
|
||||||
const DEFAULT_MONERO_WALLET_RPC_TESTNET_URL: &str = "http://127.0.0.1:38083/json_rpc";
|
const DEFAULT_MONERO_WALLET_RPC_TESTNET_URL: &str = "http://127.0.0.1:38083/json_rpc";
|
||||||
|
|
||||||
|
@ -14,10 +14,6 @@ use serde::{Deserialize, Serialize};
|
|||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
pub enum Alice {
|
pub enum Alice {
|
||||||
Started {
|
Started {
|
||||||
amounts: SwapAmounts,
|
|
||||||
state0: alice::State0,
|
|
||||||
},
|
|
||||||
Negotiated {
|
|
||||||
state3: alice::State3,
|
state3: alice::State3,
|
||||||
#[serde(with = "crate::serde_peer_id")]
|
#[serde(with = "crate::serde_peer_id")]
|
||||||
bob_peer_id: PeerId,
|
bob_peer_id: PeerId,
|
||||||
@ -54,11 +50,11 @@ pub enum AliceEndState {
|
|||||||
impl From<&AliceState> for Alice {
|
impl From<&AliceState> for Alice {
|
||||||
fn from(alice_state: &AliceState) -> Self {
|
fn from(alice_state: &AliceState) -> Self {
|
||||||
match alice_state {
|
match alice_state {
|
||||||
AliceState::Negotiated {
|
AliceState::Started {
|
||||||
state3,
|
state3,
|
||||||
bob_peer_id,
|
bob_peer_id,
|
||||||
..
|
..
|
||||||
} => Alice::Negotiated {
|
} => Alice::Started {
|
||||||
state3: state3.as_ref().clone(),
|
state3: state3.as_ref().clone(),
|
||||||
bob_peer_id: *bob_peer_id,
|
bob_peer_id: *bob_peer_id,
|
||||||
},
|
},
|
||||||
@ -76,7 +72,7 @@ impl From<&AliceState> for Alice {
|
|||||||
encrypted_signature,
|
encrypted_signature,
|
||||||
} => Alice::EncSigLearned {
|
} => Alice::EncSigLearned {
|
||||||
state3: state3.as_ref().clone(),
|
state3: state3.as_ref().clone(),
|
||||||
encrypted_signature: encrypted_signature.clone(),
|
encrypted_signature: *encrypted_signature.clone(),
|
||||||
},
|
},
|
||||||
AliceState::BtcRedeemed => Alice::Done(AliceEndState::BtcRedeemed),
|
AliceState::BtcRedeemed => Alice::Done(AliceEndState::BtcRedeemed),
|
||||||
AliceState::BtcCancelled { state3, .. } => Alice::BtcCancelled(state3.as_ref().clone()),
|
AliceState::BtcCancelled { state3, .. } => Alice::BtcCancelled(state3.as_ref().clone()),
|
||||||
@ -93,10 +89,6 @@ impl From<&AliceState> for Alice {
|
|||||||
}
|
}
|
||||||
AliceState::BtcPunished => Alice::Done(AliceEndState::BtcPunished),
|
AliceState::BtcPunished => Alice::Done(AliceEndState::BtcPunished),
|
||||||
AliceState::SafelyAborted => Alice::Done(AliceEndState::SafelyAborted),
|
AliceState::SafelyAborted => Alice::Done(AliceEndState::SafelyAborted),
|
||||||
AliceState::Started { amounts, state0 } => Alice::Started {
|
|
||||||
amounts: *amounts,
|
|
||||||
state0: state0.clone(),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,11 +96,10 @@ impl From<&AliceState> for Alice {
|
|||||||
impl From<Alice> for AliceState {
|
impl From<Alice> for AliceState {
|
||||||
fn from(db_state: Alice) -> Self {
|
fn from(db_state: Alice) -> Self {
|
||||||
match db_state {
|
match db_state {
|
||||||
Alice::Started { amounts, state0 } => AliceState::Started { amounts, state0 },
|
Alice::Started {
|
||||||
Alice::Negotiated {
|
|
||||||
state3,
|
state3,
|
||||||
bob_peer_id,
|
bob_peer_id,
|
||||||
} => AliceState::Negotiated {
|
} => AliceState::Started {
|
||||||
bob_peer_id,
|
bob_peer_id,
|
||||||
amounts: SwapAmounts {
|
amounts: SwapAmounts {
|
||||||
btc: state3.btc,
|
btc: state3.btc,
|
||||||
@ -135,7 +126,7 @@ impl From<Alice> for AliceState {
|
|||||||
encrypted_signature,
|
encrypted_signature,
|
||||||
} => AliceState::EncSigLearned {
|
} => AliceState::EncSigLearned {
|
||||||
state3: Box::new(state),
|
state3: Box::new(state),
|
||||||
encrypted_signature,
|
encrypted_signature: Box::new(encrypted_signature),
|
||||||
},
|
},
|
||||||
Alice::CancelTimelockExpired(state3) => AliceState::CancelTimelockExpired {
|
Alice::CancelTimelockExpired(state3) => AliceState::CancelTimelockExpired {
|
||||||
state3: Box::new(state3),
|
state3: Box::new(state3),
|
||||||
@ -150,7 +141,7 @@ impl From<Alice> for AliceState {
|
|||||||
|
|
||||||
AliceState::BtcCancelled {
|
AliceState::BtcCancelled {
|
||||||
state3: Box::new(state),
|
state3: Box::new(state),
|
||||||
tx_cancel,
|
tx_cancel: Box::new(tx_cancel),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Alice::BtcPunishable(state3) => {
|
Alice::BtcPunishable(state3) => {
|
||||||
@ -186,7 +177,6 @@ impl Display for Alice {
|
|||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Alice::Started { .. } => write!(f, "Started"),
|
Alice::Started { .. } => write!(f, "Started"),
|
||||||
Alice::Negotiated { .. } => f.write_str("Negotiated"),
|
|
||||||
Alice::BtcLocked { .. } => f.write_str("Bitcoin locked"),
|
Alice::BtcLocked { .. } => f.write_str("Bitcoin locked"),
|
||||||
Alice::XmrLocked(_) => f.write_str("Monero locked"),
|
Alice::XmrLocked(_) => f.write_str("Monero locked"),
|
||||||
Alice::CancelTimelockExpired(_) => f.write_str("Cancel timelock is expired"),
|
Alice::CancelTimelockExpired(_) => f.write_str("Cancel timelock is expired"),
|
||||||
|
@ -17,8 +17,11 @@
|
|||||||
)]
|
)]
|
||||||
|
|
||||||
pub mod bitcoin;
|
pub mod bitcoin;
|
||||||
|
pub mod cli;
|
||||||
|
pub mod config;
|
||||||
pub mod database;
|
pub mod database;
|
||||||
pub mod execution_params;
|
pub mod execution_params;
|
||||||
|
pub mod fs;
|
||||||
pub mod monero;
|
pub mod monero;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod protocol;
|
pub mod protocol;
|
||||||
|
@ -15,6 +15,7 @@ use rust_decimal::{
|
|||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
|
convert::TryFrom,
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
ops::{Add, Mul, Sub},
|
ops::{Add, Mul, Sub},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
@ -88,13 +89,22 @@ impl Amount {
|
|||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_monero(amount: f64) -> Result<Self> {
|
||||||
|
let decimal = Decimal::try_from(amount)?;
|
||||||
|
Self::from_decimal(decimal)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_monero(amount: &str) -> Result<Self> {
|
pub fn parse_monero(amount: &str) -> Result<Self> {
|
||||||
let decimal = Decimal::from_str(amount)?;
|
let decimal = Decimal::from_str(amount)?;
|
||||||
|
Self::from_decimal(decimal)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_decimal(amount: Decimal) -> Result<Self> {
|
||||||
let piconeros_dec =
|
let piconeros_dec =
|
||||||
decimal.mul(Decimal::from_u64(PICONERO_OFFSET).expect("constant to fit into u64"));
|
amount.mul(Decimal::from_u64(PICONERO_OFFSET).expect("constant to fit into u64"));
|
||||||
let piconeros = piconeros_dec
|
let piconeros = piconeros_dec
|
||||||
.to_u64()
|
.to_u64()
|
||||||
.ok_or_else(|| OverflowError(amount.to_owned()))?;
|
.ok_or_else(|| OverflowError(amount.to_string()))?;
|
||||||
Ok(Amount(piconeros))
|
Ok(Amount(piconeros))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,10 @@ use libp2p::{
|
|||||||
/// - DNS name resolution
|
/// - DNS name resolution
|
||||||
/// - authentication via noise
|
/// - authentication via noise
|
||||||
/// - multiplexing via yamux or mplex
|
/// - multiplexing via yamux or mplex
|
||||||
pub fn build(id_keys: identity::Keypair) -> Result<SwapTransport> {
|
pub fn build(id_keys: &identity::Keypair) -> Result<SwapTransport> {
|
||||||
use libp2p::tcp::TokioTcpConfig;
|
use libp2p::tcp::TokioTcpConfig;
|
||||||
|
|
||||||
let dh_keys = noise::Keypair::<X25519Spec>::new().into_authentic(&id_keys)?;
|
let dh_keys = noise::Keypair::<X25519Spec>::new().into_authentic(id_keys)?;
|
||||||
let noise = NoiseConfig::xx(dh_keys).into_authenticated();
|
let noise = NoiseConfig::xx(dh_keys).into_authenticated();
|
||||||
|
|
||||||
let tcp = TokioTcpConfig::new().nodelay(true);
|
let tcp = TokioTcpConfig::new().nodelay(true);
|
||||||
|
@ -1,28 +1,16 @@
|
|||||||
//! Run an XMR/BTC swap in the role of Alice.
|
//! Run an XMR/BTC swap in the role of Alice.
|
||||||
//! Alice holds XMR and wishes receive BTC.
|
//! Alice holds XMR and wishes receive BTC.
|
||||||
use crate::{
|
use crate::{
|
||||||
bitcoin, database,
|
bitcoin, database, database::Database, execution_params::ExecutionParams, monero,
|
||||||
database::Database,
|
protocol::SwapAmounts,
|
||||||
execution_params::ExecutionParams,
|
|
||||||
monero,
|
|
||||||
network::{
|
|
||||||
peer_tracker::{self, PeerTracker},
|
|
||||||
transport::build,
|
|
||||||
Seed as NetworkSeed,
|
|
||||||
},
|
|
||||||
protocol::{bob::EncryptedSignature, SwapAmounts},
|
|
||||||
seed::Seed,
|
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Error, Result};
|
use anyhow::{bail, Result};
|
||||||
use libp2p::{
|
use libp2p::{core::Multiaddr, PeerId};
|
||||||
core::Multiaddr, identity::Keypair, request_response::ResponseChannel, NetworkBehaviour, PeerId,
|
use std::sync::Arc;
|
||||||
};
|
|
||||||
use rand::rngs::OsRng;
|
|
||||||
use std::{path::PathBuf, sync::Arc};
|
|
||||||
use tracing::{debug, info};
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
|
behaviour::{Behaviour, OutEvent},
|
||||||
event_loop::{EventLoop, EventLoopHandle},
|
event_loop::{EventLoop, EventLoopHandle},
|
||||||
execution_setup::Message1,
|
execution_setup::Message1,
|
||||||
state::*,
|
state::*,
|
||||||
@ -30,9 +18,9 @@ pub use self::{
|
|||||||
swap_response::*,
|
swap_response::*,
|
||||||
transfer_proof::TransferProof,
|
transfer_proof::TransferProof,
|
||||||
};
|
};
|
||||||
use crate::protocol::bob::SwapRequest;
|
|
||||||
pub use execution_setup::Message3;
|
pub use execution_setup::Message3;
|
||||||
|
|
||||||
|
mod behaviour;
|
||||||
mod encrypted_signature;
|
mod encrypted_signature;
|
||||||
pub mod event_loop;
|
pub mod event_loop;
|
||||||
mod execution_setup;
|
mod execution_setup;
|
||||||
@ -49,16 +37,15 @@ pub struct Swap {
|
|||||||
pub monero_wallet: Arc<monero::Wallet>,
|
pub monero_wallet: Arc<monero::Wallet>,
|
||||||
pub execution_params: ExecutionParams,
|
pub execution_params: ExecutionParams,
|
||||||
pub swap_id: Uuid,
|
pub swap_id: Uuid,
|
||||||
pub db: Database,
|
pub db: Arc<Database>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Builder {
|
pub struct Builder {
|
||||||
swap_id: Uuid,
|
swap_id: Uuid,
|
||||||
identity: Keypair,
|
|
||||||
peer_id: PeerId,
|
peer_id: PeerId,
|
||||||
db_path: PathBuf,
|
db: Arc<Database>,
|
||||||
execution_params: ExecutionParams,
|
execution_params: ExecutionParams,
|
||||||
|
event_loop_handle: EventLoopHandle,
|
||||||
listen_address: Multiaddr,
|
listen_address: Multiaddr,
|
||||||
|
|
||||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||||
@ -69,29 +56,31 @@ pub struct Builder {
|
|||||||
|
|
||||||
enum InitParams {
|
enum InitParams {
|
||||||
None,
|
None,
|
||||||
New { swap_amounts: SwapAmounts },
|
New {
|
||||||
|
swap_amounts: SwapAmounts,
|
||||||
|
bob_peer_id: PeerId,
|
||||||
|
state3: Box<State3>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Builder {
|
impl Builder {
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
seed: Seed,
|
self_peer_id: PeerId,
|
||||||
execution_params: ExecutionParams,
|
execution_params: ExecutionParams,
|
||||||
swap_id: Uuid,
|
swap_id: Uuid,
|
||||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||||
monero_wallet: Arc<monero::Wallet>,
|
monero_wallet: Arc<monero::Wallet>,
|
||||||
db_path: PathBuf,
|
db: Arc<Database>,
|
||||||
listen_address: Multiaddr,
|
listen_address: Multiaddr,
|
||||||
|
event_loop_handle: EventLoopHandle,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let network_seed = NetworkSeed::new(seed);
|
|
||||||
let identity = network_seed.derive_libp2p_identity();
|
|
||||||
let peer_id = PeerId::from(identity.public());
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
swap_id,
|
swap_id,
|
||||||
identity,
|
peer_id: self_peer_id,
|
||||||
peer_id,
|
db,
|
||||||
db_path,
|
|
||||||
execution_params,
|
execution_params,
|
||||||
|
event_loop_handle,
|
||||||
listen_address,
|
listen_address,
|
||||||
bitcoin_wallet,
|
bitcoin_wallet,
|
||||||
monero_wallet,
|
monero_wallet,
|
||||||
@ -99,43 +88,48 @@ impl Builder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_init_params(self, swap_amounts: SwapAmounts) -> Self {
|
pub fn with_init_params(
|
||||||
|
self,
|
||||||
|
swap_amounts: SwapAmounts,
|
||||||
|
bob_peer_id: PeerId,
|
||||||
|
state3: State3,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
init_params: InitParams::New { swap_amounts },
|
init_params: InitParams::New {
|
||||||
|
swap_amounts,
|
||||||
|
bob_peer_id,
|
||||||
|
state3: Box::new(state3),
|
||||||
|
},
|
||||||
..self
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn build(self) -> Result<(Swap, EventLoop)> {
|
pub async fn build(self) -> Result<Swap> {
|
||||||
match self.init_params {
|
match self.init_params {
|
||||||
InitParams::New { swap_amounts } => {
|
InitParams::New {
|
||||||
let initial_state = self
|
swap_amounts,
|
||||||
.make_initial_state(swap_amounts.btc, swap_amounts.xmr)
|
bob_peer_id,
|
||||||
.await?;
|
ref state3,
|
||||||
|
} => {
|
||||||
|
let initial_state = AliceState::Started {
|
||||||
|
amounts: swap_amounts,
|
||||||
|
state3: state3.clone(),
|
||||||
|
bob_peer_id,
|
||||||
|
};
|
||||||
|
|
||||||
let (event_loop, event_loop_handle) = self.init_event_loop()?;
|
Ok(Swap {
|
||||||
|
event_loop_handle: self.event_loop_handle,
|
||||||
let db = Database::open(self.db_path.as_path())?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
Swap {
|
|
||||||
event_loop_handle,
|
|
||||||
bitcoin_wallet: self.bitcoin_wallet,
|
bitcoin_wallet: self.bitcoin_wallet,
|
||||||
monero_wallet: self.monero_wallet,
|
monero_wallet: self.monero_wallet,
|
||||||
execution_params: self.execution_params,
|
execution_params: self.execution_params,
|
||||||
db,
|
db: self.db,
|
||||||
state: initial_state,
|
state: initial_state,
|
||||||
swap_id: self.swap_id,
|
swap_id: self.swap_id,
|
||||||
},
|
})
|
||||||
event_loop,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
InitParams::None => {
|
InitParams::None => {
|
||||||
// reopen the existing database
|
|
||||||
let db = Database::open(self.db_path.as_path())?;
|
|
||||||
|
|
||||||
let resume_state =
|
let resume_state =
|
||||||
if let database::Swap::Alice(state) = db.get_state(self.swap_id)? {
|
if let database::Swap::Alice(state) = self.db.get_state(self.swap_id)? {
|
||||||
state.into()
|
state.into()
|
||||||
} else {
|
} else {
|
||||||
bail!(
|
bail!(
|
||||||
@ -144,20 +138,15 @@ impl Builder {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let (event_loop, event_loop_handle) = self.init_event_loop()?;
|
Ok(Swap {
|
||||||
|
|
||||||
Ok((
|
|
||||||
Swap {
|
|
||||||
state: resume_state,
|
state: resume_state,
|
||||||
event_loop_handle,
|
event_loop_handle: self.event_loop_handle,
|
||||||
bitcoin_wallet: self.bitcoin_wallet,
|
bitcoin_wallet: self.bitcoin_wallet,
|
||||||
monero_wallet: self.monero_wallet,
|
monero_wallet: self.monero_wallet,
|
||||||
execution_params: self.execution_params,
|
execution_params: self.execution_params,
|
||||||
swap_id: self.swap_id,
|
swap_id: self.swap_id,
|
||||||
db,
|
db: self.db,
|
||||||
},
|
})
|
||||||
event_loop,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,154 +158,4 @@ impl Builder {
|
|||||||
pub fn listen_address(&self) -> Multiaddr {
|
pub fn listen_address(&self) -> Multiaddr {
|
||||||
self.listen_address.clone()
|
self.listen_address.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn make_initial_state(
|
|
||||||
&self,
|
|
||||||
btc_to_swap: bitcoin::Amount,
|
|
||||||
xmr_to_swap: monero::Amount,
|
|
||||||
) -> 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 = self.bitcoin_wallet.new_address().await?;
|
|
||||||
let punish_address = redeem_address.clone();
|
|
||||||
let state0 = State0::new(
|
|
||||||
a,
|
|
||||||
s_a,
|
|
||||||
v_a,
|
|
||||||
amounts.btc,
|
|
||||||
amounts.xmr,
|
|
||||||
self.execution_params.bitcoin_cancel_timelock,
|
|
||||||
self.execution_params.bitcoin_punish_timelock,
|
|
||||||
redeem_address,
|
|
||||||
punish_address,
|
|
||||||
rng,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(AliceState::Started { amounts, state0 })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_event_loop(&self) -> Result<(EventLoop, EventLoopHandle)> {
|
|
||||||
let alice_behaviour = Behaviour::default();
|
|
||||||
let alice_transport = build(self.identity.clone())?;
|
|
||||||
EventLoop::new(
|
|
||||||
alice_transport,
|
|
||||||
alice_behaviour,
|
|
||||||
self.listen_address(),
|
|
||||||
self.peer_id,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum OutEvent {
|
|
||||||
ConnectionEstablished(PeerId),
|
|
||||||
SwapRequest {
|
|
||||||
msg: SwapRequest,
|
|
||||||
channel: ResponseChannel<SwapResponse>,
|
|
||||||
},
|
|
||||||
ExecutionSetupDone(Result<Box<State3>>),
|
|
||||||
TransferProofAcknowledged,
|
|
||||||
EncryptedSignature {
|
|
||||||
msg: Box<EncryptedSignature>,
|
|
||||||
channel: ResponseChannel<()>,
|
|
||||||
},
|
|
||||||
ResponseSent, // Same variant is used for all messages as no processing is done
|
|
||||||
Failure(Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<peer_tracker::OutEvent> for OutEvent {
|
|
||||||
fn from(event: peer_tracker::OutEvent) -> Self {
|
|
||||||
match event {
|
|
||||||
peer_tracker::OutEvent::ConnectionEstablished(id) => {
|
|
||||||
OutEvent::ConnectionEstablished(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<swap_response::OutEvent> for OutEvent {
|
|
||||||
fn from(event: swap_response::OutEvent) -> Self {
|
|
||||||
use swap_response::OutEvent::*;
|
|
||||||
match event {
|
|
||||||
MsgReceived { msg, channel } => OutEvent::SwapRequest { msg, channel },
|
|
||||||
ResponseSent => OutEvent::ResponseSent,
|
|
||||||
Failure(err) => OutEvent::Failure(err.context("Swap Request/Response failure")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<execution_setup::OutEvent> for OutEvent {
|
|
||||||
fn from(event: execution_setup::OutEvent) -> Self {
|
|
||||||
match event {
|
|
||||||
execution_setup::OutEvent::Done(res) => OutEvent::ExecutionSetupDone(res.map(Box::new)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<transfer_proof::OutEvent> for OutEvent {
|
|
||||||
fn from(event: transfer_proof::OutEvent) -> Self {
|
|
||||||
use transfer_proof::OutEvent::*;
|
|
||||||
match event {
|
|
||||||
Acknowledged => OutEvent::TransferProofAcknowledged,
|
|
||||||
Failure(err) => OutEvent::Failure(err.context("Failure with Transfer Proof")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<encrypted_signature::OutEvent> for OutEvent {
|
|
||||||
fn from(event: encrypted_signature::OutEvent) -> Self {
|
|
||||||
use encrypted_signature::OutEvent::*;
|
|
||||||
match event {
|
|
||||||
MsgReceived { msg, channel } => OutEvent::EncryptedSignature {
|
|
||||||
msg: Box::new(msg),
|
|
||||||
channel,
|
|
||||||
},
|
|
||||||
AckSent => OutEvent::ResponseSent,
|
|
||||||
Failure(err) => OutEvent::Failure(err.context("Failure with Encrypted Signature")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A `NetworkBehaviour` that represents an XMR/BTC swap node as Alice.
|
|
||||||
#[derive(NetworkBehaviour, Default)]
|
|
||||||
#[behaviour(out_event = "OutEvent", event_process = false)]
|
|
||||||
#[allow(missing_debug_implementations)]
|
|
||||||
pub struct Behaviour {
|
|
||||||
pt: PeerTracker,
|
|
||||||
amounts: swap_response::Behaviour,
|
|
||||||
execution_setup: execution_setup::Behaviour,
|
|
||||||
transfer_proof: transfer_proof::Behaviour,
|
|
||||||
encrypted_signature: encrypted_signature::Behaviour,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Behaviour {
|
|
||||||
/// Alice always sends her messages as a response to a request from Bob.
|
|
||||||
pub fn send_swap_response(
|
|
||||||
&mut self,
|
|
||||||
channel: ResponseChannel<SwapResponse>,
|
|
||||||
swap_response: SwapResponse,
|
|
||||||
) -> Result<()> {
|
|
||||||
self.amounts.send(channel, swap_response)?;
|
|
||||||
info!("Sent swap response");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start_execution_setup(&mut self, bob_peer_id: PeerId, state0: State0) {
|
|
||||||
self.execution_setup.run(bob_peer_id, state0);
|
|
||||||
info!("Start execution setup with {}", bob_peer_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send Transfer Proof to Bob.
|
|
||||||
pub fn send_transfer_proof(&mut self, bob: PeerId, msg: TransferProof) {
|
|
||||||
self.transfer_proof.send(bob, msg);
|
|
||||||
debug!("Sent Transfer Proof");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
143
swap/src/protocol/alice/behaviour.rs
Normal file
143
swap/src/protocol/alice/behaviour.rs
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
use crate::{
|
||||||
|
network::{peer_tracker, peer_tracker::PeerTracker},
|
||||||
|
protocol::{
|
||||||
|
alice::{
|
||||||
|
encrypted_signature, execution_setup, swap_response, transfer_proof, State0, State3,
|
||||||
|
SwapResponse, TransferProof,
|
||||||
|
},
|
||||||
|
bob::{EncryptedSignature, SwapRequest},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use anyhow::{Error, Result};
|
||||||
|
use libp2p::{request_response::ResponseChannel, NetworkBehaviour, PeerId};
|
||||||
|
use tracing::{debug, info};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum OutEvent {
|
||||||
|
ConnectionEstablished(PeerId),
|
||||||
|
SwapRequest {
|
||||||
|
msg: SwapRequest,
|
||||||
|
channel: ResponseChannel<SwapResponse>,
|
||||||
|
bob_peer_id: PeerId,
|
||||||
|
},
|
||||||
|
ExecutionSetupDone {
|
||||||
|
bob_peer_id: PeerId,
|
||||||
|
state3: Box<State3>,
|
||||||
|
},
|
||||||
|
TransferProofAcknowledged,
|
||||||
|
EncryptedSignature {
|
||||||
|
msg: Box<EncryptedSignature>,
|
||||||
|
channel: ResponseChannel<()>,
|
||||||
|
},
|
||||||
|
ResponseSent, // Same variant is used for all messages as no processing is done
|
||||||
|
Failure(Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<peer_tracker::OutEvent> for OutEvent {
|
||||||
|
fn from(event: peer_tracker::OutEvent) -> Self {
|
||||||
|
match event {
|
||||||
|
peer_tracker::OutEvent::ConnectionEstablished(id) => {
|
||||||
|
OutEvent::ConnectionEstablished(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<swap_response::OutEvent> for OutEvent {
|
||||||
|
fn from(event: swap_response::OutEvent) -> Self {
|
||||||
|
use crate::protocol::alice::swap_response::OutEvent::*;
|
||||||
|
match event {
|
||||||
|
MsgReceived {
|
||||||
|
msg,
|
||||||
|
channel,
|
||||||
|
bob_peer_id,
|
||||||
|
} => OutEvent::SwapRequest {
|
||||||
|
msg,
|
||||||
|
channel,
|
||||||
|
bob_peer_id,
|
||||||
|
},
|
||||||
|
ResponseSent => OutEvent::ResponseSent,
|
||||||
|
Failure(err) => OutEvent::Failure(err.context("Swap Request/Response failure")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<execution_setup::OutEvent> for OutEvent {
|
||||||
|
fn from(event: execution_setup::OutEvent) -> Self {
|
||||||
|
use crate::protocol::alice::execution_setup::OutEvent::*;
|
||||||
|
match event {
|
||||||
|
Done {
|
||||||
|
bob_peer_id,
|
||||||
|
state3,
|
||||||
|
} => OutEvent::ExecutionSetupDone {
|
||||||
|
bob_peer_id,
|
||||||
|
state3: Box::new(state3),
|
||||||
|
},
|
||||||
|
Failure(err) => OutEvent::Failure(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<transfer_proof::OutEvent> for OutEvent {
|
||||||
|
fn from(event: transfer_proof::OutEvent) -> Self {
|
||||||
|
use crate::protocol::alice::transfer_proof::OutEvent::*;
|
||||||
|
match event {
|
||||||
|
Acknowledged => OutEvent::TransferProofAcknowledged,
|
||||||
|
Failure(err) => OutEvent::Failure(err.context("Failure with Transfer Proof")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<encrypted_signature::OutEvent> for OutEvent {
|
||||||
|
fn from(event: encrypted_signature::OutEvent) -> Self {
|
||||||
|
use crate::protocol::alice::encrypted_signature::OutEvent::*;
|
||||||
|
match event {
|
||||||
|
MsgReceived { msg, channel } => OutEvent::EncryptedSignature {
|
||||||
|
msg: Box::new(msg),
|
||||||
|
channel,
|
||||||
|
},
|
||||||
|
AckSent => OutEvent::ResponseSent,
|
||||||
|
Failure(err) => OutEvent::Failure(err.context("Failure with Encrypted Signature")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A `NetworkBehaviour` that represents an XMR/BTC swap node as Alice.
|
||||||
|
#[derive(NetworkBehaviour, Default)]
|
||||||
|
#[behaviour(out_event = "OutEvent", event_process = false)]
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct Behaviour {
|
||||||
|
pt: PeerTracker,
|
||||||
|
swap_response: swap_response::Behaviour,
|
||||||
|
execution_setup: execution_setup::Behaviour,
|
||||||
|
transfer_proof: transfer_proof::Behaviour,
|
||||||
|
encrypted_signature: encrypted_signature::Behaviour,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Behaviour {
|
||||||
|
/// Alice always sends her messages as a response to a request from Bob.
|
||||||
|
pub fn send_swap_response(
|
||||||
|
&mut self,
|
||||||
|
channel: ResponseChannel<SwapResponse>,
|
||||||
|
swap_response: SwapResponse,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
self.swap_response.send(channel, swap_response)?;
|
||||||
|
info!("Sent swap response");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_execution_setup(&mut self, bob_peer_id: PeerId, state0: State0) {
|
||||||
|
self.execution_setup.run(bob_peer_id, state0);
|
||||||
|
info!("Start execution setup with {}", bob_peer_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send Transfer Proof to Bob.
|
||||||
|
pub fn send_transfer_proof(&mut self, bob: PeerId, msg: TransferProof) {
|
||||||
|
self.transfer_proof.send(bob, msg);
|
||||||
|
debug!("Sent Transfer Proof");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_encrypted_signature_ack(&mut self, channel: ResponseChannel<()>) -> Result<()> {
|
||||||
|
self.encrypted_signature.send_ack(channel)
|
||||||
|
}
|
||||||
|
}
|
@ -1,121 +1,77 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
|
bitcoin,
|
||||||
|
database::Database,
|
||||||
execution_params::ExecutionParams,
|
execution_params::ExecutionParams,
|
||||||
network::{transport::SwapTransport, TokioExecutor},
|
monero, network,
|
||||||
|
network::{transport, TokioExecutor},
|
||||||
protocol::{
|
protocol::{
|
||||||
alice::{Behaviour, OutEvent, State0, State3, SwapResponse, TransferProof},
|
alice,
|
||||||
|
alice::{Behaviour, Builder, OutEvent, State0, State3, SwapResponse, TransferProof},
|
||||||
bob::{EncryptedSignature, SwapRequest},
|
bob::{EncryptedSignature, SwapRequest},
|
||||||
|
SwapAmounts,
|
||||||
},
|
},
|
||||||
|
seed::Seed,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use libp2p::{
|
use libp2p::{
|
||||||
core::Multiaddr, futures::FutureExt, request_response::ResponseChannel, PeerId, Swarm,
|
core::Multiaddr, futures::FutureExt, request_response::ResponseChannel, PeerId, Swarm,
|
||||||
};
|
};
|
||||||
use tokio::{
|
use rand::rngs::OsRng;
|
||||||
sync::mpsc::{Receiver, Sender},
|
use std::{collections::HashMap, sync::Arc};
|
||||||
time::timeout,
|
use tokio::sync::{broadcast, mpsc};
|
||||||
};
|
use tracing::{debug, error, trace};
|
||||||
use tracing::{error, trace};
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
// TODO: Use dynamic
|
||||||
|
const RATE: u32 = 100;
|
||||||
|
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Channels<T> {
|
pub struct MpscChannels<T> {
|
||||||
sender: Sender<T>,
|
sender: mpsc::Sender<T>,
|
||||||
receiver: Receiver<T>,
|
receiver: mpsc::Receiver<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Channels<T> {
|
impl<T> Default for MpscChannels<T> {
|
||||||
pub fn new() -> Channels<T> {
|
|
||||||
let (sender, receiver) = tokio::sync::mpsc::channel(100);
|
|
||||||
Channels { sender, receiver }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Default for Channels<T> {
|
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
let (sender, receiver) = mpsc::channel(100);
|
||||||
|
MpscChannels { sender, receiver }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct BroadcastChannels<T>
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
{
|
||||||
|
sender: broadcast::Sender<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for BroadcastChannels<T>
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
let (sender, _receiver) = broadcast::channel(100);
|
||||||
|
BroadcastChannels { sender }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EventLoopHandle {
|
pub struct EventLoopHandle {
|
||||||
done_execution_setup: Receiver<Result<State3>>,
|
recv_encrypted_signature: broadcast::Receiver<EncryptedSignature>,
|
||||||
recv_encrypted_signature: Receiver<EncryptedSignature>,
|
send_transfer_proof: mpsc::Sender<(PeerId, TransferProof)>,
|
||||||
recv_swap_request: Receiver<(SwapRequest, ResponseChannel<SwapResponse>)>,
|
|
||||||
conn_established: Receiver<PeerId>,
|
|
||||||
send_swap_response: Sender<(ResponseChannel<SwapResponse>, SwapResponse)>,
|
|
||||||
start_execution_setup: Sender<(PeerId, State0)>,
|
|
||||||
send_transfer_proof: Sender<(PeerId, TransferProof)>,
|
|
||||||
recv_transfer_proof_ack: Receiver<()>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventLoopHandle {
|
impl EventLoopHandle {
|
||||||
pub async fn recv_conn_established(&mut self) -> Result<PeerId> {
|
|
||||||
self.conn_established
|
|
||||||
.recv()
|
|
||||||
.await
|
|
||||||
.ok_or_else(|| anyhow!("Failed to receive connection established from Bob"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn execution_setup(&mut self, bob_peer_id: PeerId, state0: State0) -> Result<State3> {
|
|
||||||
let _ = self
|
|
||||||
.start_execution_setup
|
|
||||||
.send((bob_peer_id, state0))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
self.done_execution_setup
|
|
||||||
.recv()
|
|
||||||
.await
|
|
||||||
.ok_or_else(|| anyhow!("Failed to setup execution with Bob"))?
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn recv_encrypted_signature(&mut self) -> Result<EncryptedSignature> {
|
pub async fn recv_encrypted_signature(&mut self) -> Result<EncryptedSignature> {
|
||||||
self.recv_encrypted_signature
|
self.recv_encrypted_signature
|
||||||
.recv()
|
.recv()
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| anyhow!("Failed to receive Bitcoin encrypted signature from Bob"))
|
.context("Failed to receive Bitcoin encrypted signature from Bob")
|
||||||
}
|
}
|
||||||
|
pub async fn send_transfer_proof(&mut self, bob: PeerId, msg: TransferProof) -> Result<()> {
|
||||||
pub async fn recv_swap_request(
|
|
||||||
&mut self,
|
|
||||||
) -> Result<(SwapRequest, ResponseChannel<SwapResponse>)> {
|
|
||||||
self.recv_swap_request
|
|
||||||
.recv()
|
|
||||||
.await
|
|
||||||
.ok_or_else(|| anyhow!("Failed to receive amounts request from Bob"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_swap_response(
|
|
||||||
&mut self,
|
|
||||||
channel: ResponseChannel<SwapResponse>,
|
|
||||||
swap_response: SwapResponse,
|
|
||||||
) -> Result<()> {
|
|
||||||
let _ = self
|
|
||||||
.send_swap_response
|
|
||||||
.send((channel, swap_response))
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_transfer_proof(
|
|
||||||
&mut self,
|
|
||||||
bob: PeerId,
|
|
||||||
msg: TransferProof,
|
|
||||||
execution_params: ExecutionParams,
|
|
||||||
) -> Result<()> {
|
|
||||||
let _ = self.send_transfer_proof.send((bob, msg)).await?;
|
let _ = self.send_transfer_proof.send((bob, msg)).await?;
|
||||||
|
|
||||||
// TODO: Re-evaluate if these acknowledges are necessary at all.
|
|
||||||
// If we don't use a timeout here and Alice fails to dial Bob she will wait
|
|
||||||
// indefinitely for this acknowledge.
|
|
||||||
if timeout(
|
|
||||||
execution_params.bob_time_to_act,
|
|
||||||
self.recv_transfer_proof_ack.recv(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
error!("Failed to receive transfer proof ack from Bob")
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,65 +79,74 @@ impl EventLoopHandle {
|
|||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct EventLoop {
|
pub struct EventLoop {
|
||||||
swarm: libp2p::Swarm<Behaviour>,
|
swarm: libp2p::Swarm<Behaviour>,
|
||||||
start_execution_setup: Receiver<(PeerId, State0)>,
|
peer_id: PeerId,
|
||||||
done_execution_setup: Sender<Result<State3>>,
|
execution_params: ExecutionParams,
|
||||||
recv_encrypted_signature: Sender<EncryptedSignature>,
|
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||||
recv_swap_request: Sender<(SwapRequest, ResponseChannel<SwapResponse>)>,
|
monero_wallet: Arc<monero::Wallet>,
|
||||||
conn_established: Sender<PeerId>,
|
db: Arc<Database>,
|
||||||
send_swap_response: Receiver<(ResponseChannel<SwapResponse>, SwapResponse)>,
|
listen_address: Multiaddr,
|
||||||
send_transfer_proof: Receiver<(PeerId, TransferProof)>,
|
|
||||||
recv_transfer_proof_ack: Sender<()>,
|
// Amounts agreed upon for swaps currently in the execution setup phase
|
||||||
|
// Note: We can do one execution setup per peer at a given time.
|
||||||
|
swap_amounts: HashMap<PeerId, SwapAmounts>,
|
||||||
|
|
||||||
|
recv_encrypted_signature: broadcast::Sender<EncryptedSignature>,
|
||||||
|
send_transfer_proof: mpsc::Receiver<(PeerId, TransferProof)>,
|
||||||
|
|
||||||
|
// Only used to produce new handles
|
||||||
|
send_transfer_proof_sender: mpsc::Sender<(PeerId, TransferProof)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventLoop {
|
impl EventLoop {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
transport: SwapTransport,
|
listen_address: Multiaddr,
|
||||||
behaviour: Behaviour,
|
seed: Seed,
|
||||||
listen: Multiaddr,
|
execution_params: ExecutionParams,
|
||||||
peer_id: PeerId,
|
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||||
) -> Result<(Self, EventLoopHandle)> {
|
monero_wallet: Arc<monero::Wallet>,
|
||||||
|
db: Arc<Database>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let identity = network::Seed::new(seed).derive_libp2p_identity();
|
||||||
|
let behaviour = Behaviour::default();
|
||||||
|
let transport = transport::build(&identity)?;
|
||||||
|
let peer_id = PeerId::from(identity.public());
|
||||||
|
|
||||||
let mut swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, peer_id)
|
let mut swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, peer_id)
|
||||||
.executor(Box::new(TokioExecutor {
|
.executor(Box::new(TokioExecutor {
|
||||||
handle: tokio::runtime::Handle::current(),
|
handle: tokio::runtime::Handle::current(),
|
||||||
}))
|
}))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Swarm::listen_on(&mut swarm, listen.clone())
|
Swarm::listen_on(&mut swarm, listen_address.clone())
|
||||||
.with_context(|| format!("Address is not supported: {:#}", listen))?;
|
.with_context(|| format!("Address is not supported: {:#}", listen_address))?;
|
||||||
|
|
||||||
let start_execution_setup = Channels::new();
|
let recv_encrypted_signature = BroadcastChannels::default();
|
||||||
let done_execution_setup = Channels::new();
|
let send_transfer_proof = MpscChannels::default();
|
||||||
let recv_encrypted_signature = Channels::new();
|
|
||||||
let request = Channels::new();
|
|
||||||
let conn_established = Channels::new();
|
|
||||||
let send_swap_response = Channels::new();
|
|
||||||
let send_transfer_proof = Channels::new();
|
|
||||||
let recv_transfer_proof_ack = Channels::new();
|
|
||||||
|
|
||||||
let driver = EventLoop {
|
Ok(EventLoop {
|
||||||
swarm,
|
swarm,
|
||||||
start_execution_setup: start_execution_setup.receiver,
|
peer_id,
|
||||||
done_execution_setup: done_execution_setup.sender,
|
execution_params,
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
db,
|
||||||
|
listen_address,
|
||||||
|
swap_amounts: Default::default(),
|
||||||
recv_encrypted_signature: recv_encrypted_signature.sender,
|
recv_encrypted_signature: recv_encrypted_signature.sender,
|
||||||
recv_swap_request: request.sender,
|
|
||||||
conn_established: conn_established.sender,
|
|
||||||
send_swap_response: send_swap_response.receiver,
|
|
||||||
send_transfer_proof: send_transfer_proof.receiver,
|
send_transfer_proof: send_transfer_proof.receiver,
|
||||||
recv_transfer_proof_ack: recv_transfer_proof_ack.sender,
|
send_transfer_proof_sender: send_transfer_proof.sender,
|
||||||
};
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let handle = EventLoopHandle {
|
pub fn new_handle(&self) -> EventLoopHandle {
|
||||||
start_execution_setup: start_execution_setup.sender,
|
EventLoopHandle {
|
||||||
done_execution_setup: done_execution_setup.receiver,
|
recv_encrypted_signature: self.recv_encrypted_signature.subscribe(),
|
||||||
recv_encrypted_signature: recv_encrypted_signature.receiver,
|
send_transfer_proof: self.send_transfer_proof_sender.clone(),
|
||||||
recv_swap_request: request.receiver,
|
}
|
||||||
conn_established: conn_established.receiver,
|
}
|
||||||
send_swap_response: send_swap_response.sender,
|
|
||||||
send_transfer_proof: send_transfer_proof.sender,
|
|
||||||
recv_transfer_proof_ack: recv_transfer_proof_ack.receiver,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((driver, handle))
|
pub fn peer_id(&self) -> PeerId {
|
||||||
|
self.peer_id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(&mut self) {
|
pub async fn run(&mut self) {
|
||||||
@ -190,22 +155,21 @@ impl EventLoop {
|
|||||||
swarm_event = self.swarm.next().fuse() => {
|
swarm_event = self.swarm.next().fuse() => {
|
||||||
match swarm_event {
|
match swarm_event {
|
||||||
OutEvent::ConnectionEstablished(alice) => {
|
OutEvent::ConnectionEstablished(alice) => {
|
||||||
let _ = self.conn_established.send(alice).await;
|
debug!("Connection Established with {}", alice);
|
||||||
}
|
}
|
||||||
OutEvent::SwapRequest { msg, channel } => {
|
OutEvent::SwapRequest { msg, channel, bob_peer_id } => {
|
||||||
let _ = self.recv_swap_request.send((msg, channel)).await;
|
let _ = self.handle_swap_request(msg, channel, bob_peer_id).await;
|
||||||
}
|
}
|
||||||
OutEvent::ExecutionSetupDone(res) => {
|
OutEvent::ExecutionSetupDone{bob_peer_id, state3} => {
|
||||||
let _ = self.done_execution_setup.send(res.map(|state|*state)).await;
|
let _ = self.handle_execution_setup_done(bob_peer_id, *state3).await;
|
||||||
}
|
}
|
||||||
OutEvent::TransferProofAcknowledged => {
|
OutEvent::TransferProofAcknowledged => {
|
||||||
trace!("Bob acknowledged transfer proof");
|
trace!("Bob acknowledged transfer proof");
|
||||||
let _ = self.recv_transfer_proof_ack.send(()).await;
|
|
||||||
}
|
}
|
||||||
OutEvent::EncryptedSignature{ msg, channel } => {
|
OutEvent::EncryptedSignature{ msg, channel } => {
|
||||||
let _ = self.recv_encrypted_signature.send(*msg).await;
|
let _ = self.recv_encrypted_signature.send(*msg);
|
||||||
// Send back empty response so that the request/response protocol completes.
|
// Send back empty response so that the request/response protocol completes.
|
||||||
if let Err(error) = self.swarm.encrypted_signature.send_ack(channel) {
|
if let Err(error) = self.swarm.send_encrypted_signature_ack(channel) {
|
||||||
error!("Failed to send Encrypted Signature ack: {:?}", error);
|
error!("Failed to send Encrypted Signature ack: {:?}", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -215,21 +179,6 @@ impl EventLoop {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
swap_response = self.send_swap_response.recv().fuse() => {
|
|
||||||
if let Some((channel, swap_response)) = swap_response {
|
|
||||||
let _ = self
|
|
||||||
.swarm
|
|
||||||
.send_swap_response(channel, swap_response)
|
|
||||||
.map_err(|err|error!("Failed to send swap response: {:#}", err));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
option = self.start_execution_setup.recv().fuse() => {
|
|
||||||
if let Some((bob_peer_id, state0)) = option {
|
|
||||||
let _ = self
|
|
||||||
.swarm
|
|
||||||
.start_execution_setup(bob_peer_id, state0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
transfer_proof = self.send_transfer_proof.recv().fuse() => {
|
transfer_proof = self.send_transfer_proof.recv().fuse() => {
|
||||||
if let Some((bob_peer_id, msg)) = transfer_proof {
|
if let Some((bob_peer_id, msg)) = transfer_proof {
|
||||||
self.swarm.send_transfer_proof(bob_peer_id, msg);
|
self.swarm.send_transfer_proof(bob_peer_id, msg);
|
||||||
@ -238,4 +187,78 @@ impl EventLoop {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_swap_request(
|
||||||
|
&mut self,
|
||||||
|
swap_request: SwapRequest,
|
||||||
|
channel: ResponseChannel<SwapResponse>,
|
||||||
|
bob_peer_id: PeerId,
|
||||||
|
) -> Result<()> {
|
||||||
|
// 1. Check if acceptable request
|
||||||
|
// 2. Send response
|
||||||
|
|
||||||
|
let btc_amount = swap_request.btc_amount;
|
||||||
|
let xmr_amount = btc_amount.as_btc() * RATE as f64;
|
||||||
|
let xmr_amount = monero::Amount::from_monero(xmr_amount)?;
|
||||||
|
let swap_response = SwapResponse { xmr_amount };
|
||||||
|
|
||||||
|
self.swarm
|
||||||
|
.send_swap_response(channel, swap_response)
|
||||||
|
.context("Failed to send swap response")?;
|
||||||
|
|
||||||
|
// 3. Start setup execution
|
||||||
|
|
||||||
|
let state0 = State0::new(
|
||||||
|
btc_amount,
|
||||||
|
xmr_amount,
|
||||||
|
self.execution_params,
|
||||||
|
self.bitcoin_wallet.as_ref(),
|
||||||
|
&mut OsRng,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// if a node restart during execution setup, the swap is aborted (safely).
|
||||||
|
self.swap_amounts.insert(bob_peer_id, SwapAmounts {
|
||||||
|
btc: btc_amount,
|
||||||
|
xmr: xmr_amount,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.swarm.start_execution_setup(bob_peer_id, state0);
|
||||||
|
// Continues once the execution setup protocol is done
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_execution_setup_done(
|
||||||
|
&mut self,
|
||||||
|
bob_peer_id: PeerId,
|
||||||
|
state3: State3,
|
||||||
|
) -> Result<()> {
|
||||||
|
let swap_id = Uuid::new_v4();
|
||||||
|
let handle = self.new_handle();
|
||||||
|
|
||||||
|
let swap_amounts = self.swap_amounts.remove(&bob_peer_id).ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"execution setup done for an unknown peer id: {}, node restarted in between?",
|
||||||
|
bob_peer_id
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let swap = Builder::new(
|
||||||
|
self.peer_id,
|
||||||
|
self.execution_params,
|
||||||
|
swap_id,
|
||||||
|
self.bitcoin_wallet.clone(),
|
||||||
|
self.monero_wallet.clone(),
|
||||||
|
self.db.clone(),
|
||||||
|
self.listen_address.clone(),
|
||||||
|
handle,
|
||||||
|
)
|
||||||
|
.with_init_params(swap_amounts, bob_peer_id, state3)
|
||||||
|
.build()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
tokio::spawn(async move { alice::run(swap).await });
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use crate::{
|
|||||||
bob::{Message0, Message2, Message4},
|
bob::{Message0, Message2, Message4},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use anyhow::{Context, Error, Result};
|
use anyhow::{Context, Error};
|
||||||
use libp2p::PeerId;
|
use libp2p::PeerId;
|
||||||
use libp2p_async_await::BehaviourOutEvent;
|
use libp2p_async_await::BehaviourOutEvent;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -32,14 +32,18 @@ pub struct Message3 {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum OutEvent {
|
pub enum OutEvent {
|
||||||
Done(Result<State3>),
|
Done { bob_peer_id: PeerId, state3: State3 },
|
||||||
|
Failure(Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BehaviourOutEvent<State3, (), anyhow::Error>> for OutEvent {
|
impl From<BehaviourOutEvent<(PeerId, State3), (), Error>> for OutEvent {
|
||||||
fn from(event: BehaviourOutEvent<State3, (), Error>) -> Self {
|
fn from(event: BehaviourOutEvent<(PeerId, State3), (), Error>) -> Self {
|
||||||
match event {
|
match event {
|
||||||
BehaviourOutEvent::Inbound(_, Ok(State3)) => OutEvent::Done(Ok(State3)),
|
BehaviourOutEvent::Inbound(_, Ok((bob_peer_id, state3))) => OutEvent::Done {
|
||||||
BehaviourOutEvent::Inbound(_, Err(e)) => OutEvent::Done(Err(e)),
|
bob_peer_id,
|
||||||
|
state3,
|
||||||
|
},
|
||||||
|
BehaviourOutEvent::Inbound(_, Err(e)) => OutEvent::Failure(e),
|
||||||
BehaviourOutEvent::Outbound(..) => unreachable!("Alice only supports inbound"),
|
BehaviourOutEvent::Outbound(..) => unreachable!("Alice only supports inbound"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,7 +52,7 @@ impl From<BehaviourOutEvent<State3, (), anyhow::Error>> for OutEvent {
|
|||||||
#[derive(libp2p::NetworkBehaviour)]
|
#[derive(libp2p::NetworkBehaviour)]
|
||||||
#[behaviour(out_event = "OutEvent", event_process = false)]
|
#[behaviour(out_event = "OutEvent", event_process = false)]
|
||||||
pub struct Behaviour {
|
pub struct Behaviour {
|
||||||
inner: libp2p_async_await::Behaviour<State3, (), anyhow::Error>,
|
inner: libp2p_async_await::Behaviour<(PeerId, State3), (), anyhow::Error>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Behaviour {
|
impl Default for Behaviour {
|
||||||
@ -92,7 +96,7 @@ impl Behaviour {
|
|||||||
.context("failed to deserialize message4")?;
|
.context("failed to deserialize message4")?;
|
||||||
let state3 = state2.receive(message4)?;
|
let state3 = state2.receive(message4)?;
|
||||||
|
|
||||||
Ok(state3)
|
Ok((bob, state3))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ use crate::{
|
|||||||
wait_for_cancel_timelock_to_expire, GetBlockHeight, TransactionBlockHeight, TxCancel,
|
wait_for_cancel_timelock_to_expire, GetBlockHeight, TransactionBlockHeight, TxCancel,
|
||||||
TxRefund, WatchForRawTransaction,
|
TxRefund, WatchForRawTransaction,
|
||||||
},
|
},
|
||||||
|
execution_params::ExecutionParams,
|
||||||
monero,
|
monero,
|
||||||
protocol::{
|
protocol::{
|
||||||
alice::{Message1, Message3, TransferProof},
|
alice::{Message1, Message3, TransferProof},
|
||||||
@ -24,10 +25,6 @@ use std::fmt;
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum AliceState {
|
pub enum AliceState {
|
||||||
Started {
|
Started {
|
||||||
amounts: SwapAmounts,
|
|
||||||
state0: State0,
|
|
||||||
},
|
|
||||||
Negotiated {
|
|
||||||
bob_peer_id: PeerId,
|
bob_peer_id: PeerId,
|
||||||
amounts: SwapAmounts,
|
amounts: SwapAmounts,
|
||||||
state3: Box<State3>,
|
state3: Box<State3>,
|
||||||
@ -41,12 +38,12 @@ pub enum AliceState {
|
|||||||
state3: Box<State3>,
|
state3: Box<State3>,
|
||||||
},
|
},
|
||||||
EncSigLearned {
|
EncSigLearned {
|
||||||
encrypted_signature: bitcoin::EncryptedSignature,
|
encrypted_signature: Box<bitcoin::EncryptedSignature>,
|
||||||
state3: Box<State3>,
|
state3: Box<State3>,
|
||||||
},
|
},
|
||||||
BtcRedeemed,
|
BtcRedeemed,
|
||||||
BtcCancelled {
|
BtcCancelled {
|
||||||
tx_cancel: TxCancel,
|
tx_cancel: Box<TxCancel>,
|
||||||
state3: Box<State3>,
|
state3: Box<State3>,
|
||||||
},
|
},
|
||||||
BtcRefunded {
|
BtcRefunded {
|
||||||
@ -69,7 +66,6 @@ impl fmt::Display for AliceState {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
AliceState::Started { .. } => write!(f, "started"),
|
AliceState::Started { .. } => write!(f, "started"),
|
||||||
AliceState::Negotiated { .. } => write!(f, "negotiated"),
|
|
||||||
AliceState::BtcLocked { .. } => write!(f, "btc is locked"),
|
AliceState::BtcLocked { .. } => write!(f, "btc is locked"),
|
||||||
AliceState::XmrLocked { .. } => write!(f, "xmr is locked"),
|
AliceState::XmrLocked { .. } => write!(f, "xmr is locked"),
|
||||||
AliceState::EncSigLearned { .. } => write!(f, "encrypted signature is learned"),
|
AliceState::EncSigLearned { .. } => write!(f, "encrypted signature is learned"),
|
||||||
@ -101,25 +97,24 @@ pub struct State0 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl State0 {
|
impl State0 {
|
||||||
#[allow(clippy::too_many_arguments)]
|
pub async fn new<R>(
|
||||||
pub fn new<R>(
|
|
||||||
a: bitcoin::SecretKey,
|
|
||||||
s_a: cross_curve_dleq::Scalar,
|
|
||||||
v_a: monero::PrivateViewKey,
|
|
||||||
btc: bitcoin::Amount,
|
btc: bitcoin::Amount,
|
||||||
xmr: monero::Amount,
|
xmr: monero::Amount,
|
||||||
cancel_timelock: Timelock,
|
execution_params: ExecutionParams,
|
||||||
punish_timelock: Timelock,
|
bitcoin_wallet: &bitcoin::Wallet,
|
||||||
redeem_address: bitcoin::Address,
|
|
||||||
punish_address: bitcoin::Address,
|
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
) -> Self
|
) -> Result<Self>
|
||||||
where
|
where
|
||||||
R: RngCore + CryptoRng,
|
R: RngCore + CryptoRng,
|
||||||
{
|
{
|
||||||
|
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.new_address().await?;
|
||||||
|
let punish_address = redeem_address.clone();
|
||||||
let dleq_proof_s_a = cross_curve_dleq::Proof::new(rng, &s_a);
|
let dleq_proof_s_a = cross_curve_dleq::Proof::new(rng, &s_a);
|
||||||
|
|
||||||
Self {
|
Ok(Self {
|
||||||
a,
|
a,
|
||||||
s_a,
|
s_a,
|
||||||
v_a,
|
v_a,
|
||||||
@ -128,9 +123,9 @@ impl State0 {
|
|||||||
punish_address,
|
punish_address,
|
||||||
btc,
|
btc,
|
||||||
xmr,
|
xmr,
|
||||||
cancel_timelock,
|
cancel_timelock: execution_params.bitcoin_cancel_timelock,
|
||||||
punish_timelock,
|
punish_timelock: execution_params.bitcoin_punish_timelock,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn receive(self, msg: Message0) -> Result<State1> {
|
pub fn receive(self, msg: Message0) -> Result<State1> {
|
||||||
|
@ -12,7 +12,7 @@ use crate::{
|
|||||||
monero::Transfer,
|
monero::Transfer,
|
||||||
protocol::{
|
protocol::{
|
||||||
alice,
|
alice,
|
||||||
alice::{event_loop::EventLoopHandle, SwapResponse, TransferProof},
|
alice::{event_loop::EventLoopHandle, TransferProof},
|
||||||
SwapAmounts,
|
SwapAmounts,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -26,43 +26,7 @@ use libp2p::PeerId;
|
|||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
use tracing::{info, trace};
|
use tracing::info;
|
||||||
|
|
||||||
pub async fn negotiate(
|
|
||||||
state0: alice::State0,
|
|
||||||
xmr_amount: monero::Amount,
|
|
||||||
event_loop_handle: &mut EventLoopHandle,
|
|
||||||
execution_params: ExecutionParams,
|
|
||||||
) -> Result<(PeerId, alice::State3)> {
|
|
||||||
trace!("Starting negotiate");
|
|
||||||
|
|
||||||
// todo: we can move this out, we dont need to timeout here
|
|
||||||
let bob_peer_id = timeout(
|
|
||||||
execution_params.bob_time_to_act,
|
|
||||||
event_loop_handle.recv_conn_established(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.context("Failed to receive dial connection from Bob")??;
|
|
||||||
|
|
||||||
let event = timeout(
|
|
||||||
execution_params.bob_time_to_act,
|
|
||||||
event_loop_handle.recv_swap_request(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.context("Failed to receive swap request from Bob")??;
|
|
||||||
|
|
||||||
event_loop_handle
|
|
||||||
.send_swap_response(event.1, SwapResponse { xmr_amount })
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let state3 = timeout(
|
|
||||||
execution_params.bob_time_to_act,
|
|
||||||
event_loop_handle.execution_setup(bob_peer_id, state0),
|
|
||||||
)
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
Ok((bob_peer_id, state3))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(Franck): Use helper functions from xmr-btc instead of re-writing them
|
// TODO(Franck): Use helper functions from xmr-btc instead of re-writing them
|
||||||
// here
|
// here
|
||||||
@ -96,7 +60,6 @@ pub async fn lock_xmr<W>(
|
|||||||
state3: alice::State3,
|
state3: alice::State3,
|
||||||
event_loop_handle: &mut EventLoopHandle,
|
event_loop_handle: &mut EventLoopHandle,
|
||||||
monero_wallet: Arc<W>,
|
monero_wallet: Arc<W>,
|
||||||
execution_params: ExecutionParams,
|
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
W: Transfer,
|
W: Transfer,
|
||||||
@ -118,13 +81,9 @@ where
|
|||||||
// Otherwise Alice might publish the lock tx twice!
|
// Otherwise Alice might publish the lock tx twice!
|
||||||
|
|
||||||
event_loop_handle
|
event_loop_handle
|
||||||
.send_transfer_proof(
|
.send_transfer_proof(bob_peer_id, TransferProof {
|
||||||
bob_peer_id,
|
|
||||||
TransferProof {
|
|
||||||
tx_lock_proof: transfer_proof,
|
tx_lock_proof: transfer_proof,
|
||||||
},
|
})
|
||||||
execution_params,
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -17,10 +17,10 @@ use crate::{
|
|||||||
event_loop::EventLoopHandle,
|
event_loop::EventLoopHandle,
|
||||||
steps::{
|
steps::{
|
||||||
build_bitcoin_punish_transaction, build_bitcoin_redeem_transaction,
|
build_bitcoin_punish_transaction, build_bitcoin_redeem_transaction,
|
||||||
extract_monero_private_key, lock_xmr, negotiate,
|
extract_monero_private_key, lock_xmr, publish_bitcoin_punish_transaction,
|
||||||
publish_bitcoin_punish_transaction, publish_bitcoin_redeem_transaction,
|
publish_bitcoin_redeem_transaction, publish_cancel_transaction,
|
||||||
publish_cancel_transaction, wait_for_bitcoin_encrypted_signature,
|
wait_for_bitcoin_encrypted_signature, wait_for_bitcoin_refund,
|
||||||
wait_for_bitcoin_refund, wait_for_locked_bitcoin,
|
wait_for_locked_bitcoin,
|
||||||
},
|
},
|
||||||
AliceState,
|
AliceState,
|
||||||
},
|
},
|
||||||
@ -84,44 +84,14 @@ async fn run_until_internal(
|
|||||||
monero_wallet: Arc<monero::Wallet>,
|
monero_wallet: Arc<monero::Wallet>,
|
||||||
execution_params: ExecutionParams,
|
execution_params: ExecutionParams,
|
||||||
swap_id: Uuid,
|
swap_id: Uuid,
|
||||||
db: Database,
|
db: Arc<Database>,
|
||||||
) -> Result<AliceState> {
|
) -> Result<AliceState> {
|
||||||
info!("Current state:{}", state);
|
info!("Current state:{}", state);
|
||||||
if is_target_state(&state) {
|
if is_target_state(&state) {
|
||||||
Ok(state)
|
Ok(state)
|
||||||
} else {
|
} else {
|
||||||
match state {
|
match state {
|
||||||
AliceState::Started { amounts, state0 } => {
|
AliceState::Started {
|
||||||
let (bob_peer_id, state3) = negotiate(
|
|
||||||
state0,
|
|
||||||
amounts.xmr,
|
|
||||||
&mut event_loop_handle,
|
|
||||||
execution_params,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let state = AliceState::Negotiated {
|
|
||||||
bob_peer_id,
|
|
||||||
amounts,
|
|
||||||
state3: Box::new(state3),
|
|
||||||
};
|
|
||||||
|
|
||||||
let db_state = (&state).into();
|
|
||||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
|
||||||
.await?;
|
|
||||||
run_until_internal(
|
|
||||||
state,
|
|
||||||
is_target_state,
|
|
||||||
event_loop_handle,
|
|
||||||
bitcoin_wallet,
|
|
||||||
monero_wallet,
|
|
||||||
execution_params,
|
|
||||||
swap_id,
|
|
||||||
db,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
AliceState::Negotiated {
|
|
||||||
state3,
|
state3,
|
||||||
bob_peer_id,
|
bob_peer_id,
|
||||||
amounts,
|
amounts,
|
||||||
@ -165,7 +135,6 @@ async fn run_until_internal(
|
|||||||
*state3.clone(),
|
*state3.clone(),
|
||||||
&mut event_loop_handle,
|
&mut event_loop_handle,
|
||||||
monero_wallet.clone(),
|
monero_wallet.clone(),
|
||||||
execution_params,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@ -202,7 +171,7 @@ async fn run_until_internal(
|
|||||||
Either::Left(_) => AliceState::CancelTimelockExpired { state3 },
|
Either::Left(_) => AliceState::CancelTimelockExpired { state3 },
|
||||||
Either::Right((enc_sig, _)) => AliceState::EncSigLearned {
|
Either::Right((enc_sig, _)) => AliceState::EncSigLearned {
|
||||||
state3,
|
state3,
|
||||||
encrypted_signature: enc_sig?,
|
encrypted_signature: Box::new(enc_sig?),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,7 +200,7 @@ async fn run_until_internal(
|
|||||||
let state = match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? {
|
let state = match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? {
|
||||||
ExpiredTimelocks::None => {
|
ExpiredTimelocks::None => {
|
||||||
match build_bitcoin_redeem_transaction(
|
match build_bitcoin_redeem_transaction(
|
||||||
encrypted_signature,
|
*encrypted_signature,
|
||||||
&state3.tx_lock,
|
&state3.tx_lock,
|
||||||
state3.a.clone(),
|
state3.a.clone(),
|
||||||
state3.s_a,
|
state3.s_a,
|
||||||
@ -305,7 +274,10 @@ async fn run_until_internal(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let state = AliceState::BtcCancelled { state3, tx_cancel };
|
let state = AliceState::BtcCancelled {
|
||||||
|
state3,
|
||||||
|
tx_cancel: Box::new(tx_cancel),
|
||||||
|
};
|
||||||
let db_state = (&state).into();
|
let db_state = (&state).into();
|
||||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -9,7 +9,7 @@ use libp2p::{
|
|||||||
ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent,
|
ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent,
|
||||||
RequestResponseMessage, ResponseChannel,
|
RequestResponseMessage, ResponseChannel,
|
||||||
},
|
},
|
||||||
NetworkBehaviour,
|
NetworkBehaviour, PeerId,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@ -20,6 +20,7 @@ pub enum OutEvent {
|
|||||||
MsgReceived {
|
MsgReceived {
|
||||||
msg: SwapRequest,
|
msg: SwapRequest,
|
||||||
channel: ResponseChannel<SwapResponse>,
|
channel: ResponseChannel<SwapResponse>,
|
||||||
|
bob_peer_id: PeerId,
|
||||||
},
|
},
|
||||||
ResponseSent,
|
ResponseSent,
|
||||||
Failure(Error),
|
Failure(Error),
|
||||||
@ -45,6 +46,7 @@ impl From<RequestResponseEvent<SwapRequest, SwapResponse>> for OutEvent {
|
|||||||
OutEvent::MsgReceived {
|
OutEvent::MsgReceived {
|
||||||
msg: request,
|
msg: request,
|
||||||
channel,
|
channel,
|
||||||
|
bob_peer_id: peer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RequestResponseEvent::Message {
|
RequestResponseEvent::Message {
|
||||||
|
@ -168,7 +168,7 @@ impl Builder {
|
|||||||
&self,
|
&self,
|
||||||
) -> Result<(bob::event_loop::EventLoop, bob::event_loop::EventLoopHandle)> {
|
) -> Result<(bob::event_loop::EventLoop, bob::event_loop::EventLoopHandle)> {
|
||||||
let bob_behaviour = bob::Behaviour::default();
|
let bob_behaviour = bob::Behaviour::default();
|
||||||
let bob_transport = build(self.identity.clone())?;
|
let bob_transport = build(&self.identity)?;
|
||||||
|
|
||||||
bob::event_loop::EventLoop::new(
|
bob::event_loop::EventLoop::new(
|
||||||
bob_transport,
|
bob_transport,
|
||||||
|
@ -43,7 +43,6 @@ pub struct EventLoopHandle {
|
|||||||
dial_alice: Sender<()>,
|
dial_alice: Sender<()>,
|
||||||
send_swap_request: Sender<SwapRequest>,
|
send_swap_request: Sender<SwapRequest>,
|
||||||
send_encrypted_signature: Sender<EncryptedSignature>,
|
send_encrypted_signature: Sender<EncryptedSignature>,
|
||||||
recv_encrypted_signature_ack: Receiver<()>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventLoopHandle {
|
impl EventLoopHandle {
|
||||||
@ -95,10 +94,6 @@ impl EventLoopHandle {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.send_encrypted_signature.send(tx_redeem_encsig).await?;
|
self.send_encrypted_signature.send(tx_redeem_encsig).await?;
|
||||||
|
|
||||||
self.recv_encrypted_signature_ack
|
|
||||||
.recv()
|
|
||||||
.await
|
|
||||||
.ok_or_else(|| anyhow!("Failed to receive encrypted signature ack from Alice"))?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,7 +111,6 @@ pub struct EventLoop {
|
|||||||
conn_established: Sender<PeerId>,
|
conn_established: Sender<PeerId>,
|
||||||
send_swap_request: Receiver<SwapRequest>,
|
send_swap_request: Receiver<SwapRequest>,
|
||||||
send_encrypted_signature: Receiver<EncryptedSignature>,
|
send_encrypted_signature: Receiver<EncryptedSignature>,
|
||||||
recv_encrypted_signature_ack: Sender<()>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventLoop {
|
impl EventLoop {
|
||||||
@ -144,7 +138,6 @@ impl EventLoop {
|
|||||||
let conn_established = Channels::new();
|
let conn_established = Channels::new();
|
||||||
let send_swap_request = Channels::new();
|
let send_swap_request = Channels::new();
|
||||||
let send_encrypted_signature = Channels::new();
|
let send_encrypted_signature = Channels::new();
|
||||||
let recv_encrypted_signature_ack = Channels::new();
|
|
||||||
|
|
||||||
let event_loop = EventLoop {
|
let event_loop = EventLoop {
|
||||||
swarm,
|
swarm,
|
||||||
@ -158,7 +151,6 @@ impl EventLoop {
|
|||||||
dial_alice: dial_alice.receiver,
|
dial_alice: dial_alice.receiver,
|
||||||
send_swap_request: send_swap_request.receiver,
|
send_swap_request: send_swap_request.receiver,
|
||||||
send_encrypted_signature: send_encrypted_signature.receiver,
|
send_encrypted_signature: send_encrypted_signature.receiver,
|
||||||
recv_encrypted_signature_ack: recv_encrypted_signature_ack.sender,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let handle = EventLoopHandle {
|
let handle = EventLoopHandle {
|
||||||
@ -170,7 +162,6 @@ impl EventLoop {
|
|||||||
dial_alice: dial_alice.sender,
|
dial_alice: dial_alice.sender,
|
||||||
send_swap_request: send_swap_request.sender,
|
send_swap_request: send_swap_request.sender,
|
||||||
send_encrypted_signature: send_encrypted_signature.sender,
|
send_encrypted_signature: send_encrypted_signature.sender,
|
||||||
recv_encrypted_signature_ack: recv_encrypted_signature_ack.receiver,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((event_loop, handle))
|
Ok((event_loop, handle))
|
||||||
@ -199,7 +190,6 @@ impl EventLoop {
|
|||||||
}
|
}
|
||||||
OutEvent::EncryptedSignatureAcknowledged => {
|
OutEvent::EncryptedSignatureAcknowledged => {
|
||||||
debug!("Alice acknowledged encrypted signature");
|
debug!("Alice acknowledged encrypted signature");
|
||||||
let _ = self.recv_encrypted_signature_ack.send(()).await;
|
|
||||||
}
|
}
|
||||||
OutEvent::ResponseSent => {}
|
OutEvent::ResponseSent => {}
|
||||||
OutEvent::Failure(err) => {
|
OutEvent::Failure(err) => {
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
pub mod testutils;
|
pub mod testutils;
|
||||||
|
|
||||||
use swap::protocol::{alice, bob, bob::BobState};
|
use swap::protocol::{bob, bob::BobState};
|
||||||
use testutils::{bob_run_until::is_btc_locked, FastCancelConfig};
|
use testutils::{bob_run_until::is_btc_locked, FastCancelConfig};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn given_bob_manually_refunds_after_btc_locked_bob_refunds() {
|
async fn given_bob_manually_refunds_after_btc_locked_bob_refunds() {
|
||||||
testutils::setup_test(FastCancelConfig, |mut ctx| async move {
|
testutils::setup_test(FastCancelConfig, |mut ctx| async move {
|
||||||
let (alice_swap, _) = ctx.new_swap_as_alice().await;
|
|
||||||
let (bob_swap, bob_join_handle) = ctx.new_swap_as_bob().await;
|
let (bob_swap, bob_join_handle) = ctx.new_swap_as_bob().await;
|
||||||
|
|
||||||
let alice_handle = alice::run(alice_swap);
|
|
||||||
let alice_swap_handle = tokio::spawn(alice_handle);
|
|
||||||
|
|
||||||
let bob_state = bob::run_until(bob_swap, is_btc_locked).await.unwrap();
|
let bob_state = bob::run_until(bob_swap, is_btc_locked).await.unwrap();
|
||||||
|
|
||||||
assert!(matches!(bob_state, BobState::BtcLocked { .. }));
|
assert!(matches!(bob_state, BobState::BtcLocked { .. }));
|
||||||
@ -29,6 +25,7 @@ async fn given_bob_manually_refunds_after_btc_locked_bob_refunds() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bob manually cancels
|
// Bob manually cancels
|
||||||
|
bob_join_handle.abort();
|
||||||
let (_, state) = bob::cancel(
|
let (_, state) = bob::cancel(
|
||||||
bob_swap.swap_id,
|
bob_swap.swap_id,
|
||||||
bob_swap.state,
|
bob_swap.state,
|
||||||
@ -41,10 +38,11 @@ async fn given_bob_manually_refunds_after_btc_locked_bob_refunds() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(matches!(state, BobState::BtcCancelled { .. }));
|
assert!(matches!(state, BobState::BtcCancelled { .. }));
|
||||||
|
|
||||||
let (bob_swap, _) = ctx.stop_and_resume_bob_from_db(bob_join_handle).await;
|
let (bob_swap, bob_join_handle) = ctx.stop_and_resume_bob_from_db(bob_join_handle).await;
|
||||||
assert!(matches!(bob_swap.state, BobState::BtcCancelled { .. }));
|
assert!(matches!(bob_swap.state, BobState::BtcCancelled { .. }));
|
||||||
|
|
||||||
// Bob manually refunds
|
// Bob manually refunds
|
||||||
|
bob_join_handle.abort();
|
||||||
let bob_state = bob::refund(
|
let bob_state = bob::refund(
|
||||||
bob_swap.swap_id,
|
bob_swap.swap_id,
|
||||||
bob_swap.state,
|
bob_swap.state,
|
||||||
@ -58,9 +56,6 @@ async fn given_bob_manually_refunds_after_btc_locked_bob_refunds() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
ctx.assert_bob_refunded(bob_state).await;
|
ctx.assert_bob_refunded(bob_state).await;
|
||||||
|
|
||||||
let alice_state = alice_swap_handle.await.unwrap().unwrap();
|
|
||||||
ctx.assert_alice_refunded(alice_state).await;
|
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,14 @@
|
|||||||
pub mod testutils;
|
pub mod testutils;
|
||||||
|
|
||||||
use bob::cancel::CancelError;
|
use bob::cancel::CancelError;
|
||||||
use swap::protocol::{alice, bob, bob::BobState};
|
use swap::protocol::{bob, bob::BobState};
|
||||||
use testutils::{bob_run_until::is_btc_locked, SlowCancelConfig};
|
use testutils::{bob_run_until::is_btc_locked, SlowCancelConfig};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn given_bob_manually_cancels_when_timelock_not_expired_errors() {
|
async fn given_bob_manually_cancels_when_timelock_not_expired_errors() {
|
||||||
testutils::setup_test(SlowCancelConfig, |mut ctx| async move {
|
testutils::setup_test(SlowCancelConfig, |mut ctx| async move {
|
||||||
let (alice_swap, _) = ctx.new_swap_as_alice().await;
|
|
||||||
let (bob_swap, bob_join_handle) = ctx.new_swap_as_bob().await;
|
let (bob_swap, bob_join_handle) = ctx.new_swap_as_bob().await;
|
||||||
|
|
||||||
let alice_handle = alice::run(alice_swap);
|
|
||||||
tokio::spawn(alice_handle);
|
|
||||||
|
|
||||||
let bob_state = bob::run_until(bob_swap, is_btc_locked).await.unwrap();
|
let bob_state = bob::run_until(bob_swap, is_btc_locked).await.unwrap();
|
||||||
assert!(matches!(bob_state, BobState::BtcLocked { .. }));
|
assert!(matches!(bob_state, BobState::BtcLocked { .. }));
|
||||||
|
|
@ -1,17 +1,13 @@
|
|||||||
pub mod testutils;
|
pub mod testutils;
|
||||||
|
|
||||||
use swap::protocol::{alice, bob, bob::BobState};
|
use swap::protocol::{bob, bob::BobState};
|
||||||
use testutils::{bob_run_until::is_btc_locked, SlowCancelConfig};
|
use testutils::{bob_run_until::is_btc_locked, SlowCancelConfig};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn given_bob_manually_forces_cancel_when_timelock_not_expired_errors() {
|
async fn given_bob_manually_forces_cancel_when_timelock_not_expired_errors() {
|
||||||
testutils::setup_test(SlowCancelConfig, |mut ctx| async move {
|
testutils::setup_test(SlowCancelConfig, |mut ctx| async move {
|
||||||
let (alice_swap, _) = ctx.new_swap_as_alice().await;
|
|
||||||
let (bob_swap, bob_join_handle) = ctx.new_swap_as_bob().await;
|
let (bob_swap, bob_join_handle) = ctx.new_swap_as_bob().await;
|
||||||
|
|
||||||
let alice_handle = alice::run(alice_swap);
|
|
||||||
tokio::spawn(alice_handle);
|
|
||||||
|
|
||||||
let bob_state = bob::run_until(bob_swap, is_btc_locked).await.unwrap();
|
let bob_state = bob::run_until(bob_swap, is_btc_locked).await.unwrap();
|
||||||
assert!(matches!(bob_state, BobState::BtcLocked { .. }));
|
assert!(matches!(bob_state, BobState::BtcLocked { .. }));
|
||||||
|
|
@ -1,23 +1,18 @@
|
|||||||
pub mod testutils;
|
pub mod testutils;
|
||||||
|
|
||||||
use swap::protocol::{alice, bob};
|
use swap::protocol::bob;
|
||||||
use testutils::SlowCancelConfig;
|
use testutils::SlowCancelConfig;
|
||||||
use tokio::join;
|
|
||||||
|
|
||||||
/// Run the following tests with RUST_MIN_STACK=10000000
|
/// Run the following tests with RUST_MIN_STACK=10000000
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn happy_path() {
|
async fn happy_path() {
|
||||||
testutils::setup_test(SlowCancelConfig, |mut ctx| async move {
|
testutils::setup_test(SlowCancelConfig, |mut ctx| async move {
|
||||||
let (alice_swap, _) = ctx.new_swap_as_alice().await;
|
|
||||||
let (bob_swap, _) = ctx.new_swap_as_bob().await;
|
let (bob_swap, _) = ctx.new_swap_as_bob().await;
|
||||||
|
|
||||||
let alice = alice::run(alice_swap);
|
let bob_state = bob::run(bob_swap).await;
|
||||||
let bob = bob::run(bob_swap);
|
|
||||||
|
|
||||||
let (alice_state, bob_state) = join!(alice, bob);
|
ctx.assert_alice_redeemed().await;
|
||||||
|
|
||||||
ctx.assert_alice_redeemed(alice_state.unwrap()).await;
|
|
||||||
ctx.assert_bob_redeemed(bob_state.unwrap()).await;
|
ctx.assert_bob_redeemed(bob_state.unwrap()).await;
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
pub mod testutils;
|
|
||||||
|
|
||||||
use swap::protocol::{alice, alice::AliceState, bob};
|
|
||||||
use testutils::{alice_run_until::is_encsig_learned, SlowCancelConfig};
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn given_alice_restarts_after_encsig_is_learned_resume_swap() {
|
|
||||||
testutils::setup_test(SlowCancelConfig, |mut ctx| async move {
|
|
||||||
let (alice_swap, alice_join_handle) = ctx.new_swap_as_alice().await;
|
|
||||||
let (bob_swap, _) = ctx.new_swap_as_bob().await;
|
|
||||||
|
|
||||||
let bob = bob::run(bob_swap);
|
|
||||||
let bob_handle = tokio::spawn(bob);
|
|
||||||
|
|
||||||
let alice_state = alice::run_until(alice_swap, is_encsig_learned)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert!(matches!(alice_state, AliceState::EncSigLearned { .. }));
|
|
||||||
|
|
||||||
let alice_swap = ctx.stop_and_resume_alice_from_db(alice_join_handle).await;
|
|
||||||
assert!(matches!(alice_swap.state, AliceState::EncSigLearned { .. }));
|
|
||||||
|
|
||||||
let alice_state = alice::run(alice_swap).await.unwrap();
|
|
||||||
|
|
||||||
ctx.assert_alice_redeemed(alice_state).await;
|
|
||||||
|
|
||||||
let bob_state = bob_handle.await.unwrap();
|
|
||||||
ctx.assert_bob_redeemed(bob_state.unwrap()).await
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
pub mod testutils;
|
|
||||||
|
|
||||||
use swap::protocol::{alice, bob, bob::BobState};
|
|
||||||
use testutils::{bob_run_until::is_encsig_sent, SlowCancelConfig};
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn given_bob_restarts_after_encsig_is_sent_resume_swap() {
|
|
||||||
testutils::setup_test(SlowCancelConfig, |mut ctx| async move {
|
|
||||||
let (alice_swap, _) = ctx.new_swap_as_alice().await;
|
|
||||||
let (bob_swap, bob_join_handle) = ctx.new_swap_as_bob().await;
|
|
||||||
|
|
||||||
let alice = alice::run(alice_swap);
|
|
||||||
let alice_handle = tokio::spawn(alice);
|
|
||||||
|
|
||||||
let bob_state = bob::run_until(bob_swap, is_encsig_sent).await.unwrap();
|
|
||||||
|
|
||||||
assert!(matches!(bob_state, BobState::EncSigSent { .. }));
|
|
||||||
|
|
||||||
let (bob_swap, _) = ctx.stop_and_resume_bob_from_db(bob_join_handle).await;
|
|
||||||
assert!(matches!(bob_swap.state, BobState::EncSigSent { .. }));
|
|
||||||
|
|
||||||
let bob_state = bob::run(bob_swap).await.unwrap();
|
|
||||||
|
|
||||||
ctx.assert_bob_redeemed(bob_state).await;
|
|
||||||
|
|
||||||
let alice_state = alice_handle.await.unwrap();
|
|
||||||
ctx.assert_alice_redeemed(alice_state.unwrap()).await;
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
pub mod testutils;
|
|
||||||
|
|
||||||
use swap::protocol::{alice, bob, bob::BobState};
|
|
||||||
use testutils::{bob_run_until::is_lock_proof_received, SlowCancelConfig};
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn given_bob_restarts_after_lock_proof_received_resume_swap() {
|
|
||||||
testutils::setup_test(SlowCancelConfig, |mut ctx| async move {
|
|
||||||
let (alice_swap, _) = ctx.new_swap_as_alice().await;
|
|
||||||
let (bob_swap, bob_join_handle) = ctx.new_swap_as_bob().await;
|
|
||||||
|
|
||||||
let alice_handle = alice::run(alice_swap);
|
|
||||||
let alice_swap_handle = tokio::spawn(alice_handle);
|
|
||||||
|
|
||||||
let bob_state = bob::run_until(bob_swap, is_lock_proof_received)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(matches!(bob_state, BobState::XmrLockProofReceived { .. }));
|
|
||||||
|
|
||||||
let (bob_swap, _) = ctx.stop_and_resume_bob_from_db(bob_join_handle).await;
|
|
||||||
assert!(matches!(
|
|
||||||
bob_swap.state,
|
|
||||||
BobState::XmrLockProofReceived { .. }
|
|
||||||
));
|
|
||||||
|
|
||||||
let bob_state = bob::run(bob_swap).await.unwrap();
|
|
||||||
|
|
||||||
ctx.assert_bob_redeemed(bob_state).await;
|
|
||||||
|
|
||||||
let alice_state = alice_swap_handle.await.unwrap().unwrap();
|
|
||||||
ctx.assert_alice_redeemed(alice_state).await;
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
@ -1,17 +1,13 @@
|
|||||||
pub mod testutils;
|
pub mod testutils;
|
||||||
|
|
||||||
use swap::protocol::{alice, bob, bob::BobState};
|
use swap::protocol::{bob, bob::BobState};
|
||||||
use testutils::{bob_run_until::is_xmr_locked, SlowCancelConfig};
|
use testutils::{bob_run_until::is_xmr_locked, SlowCancelConfig};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn given_bob_restarts_after_xmr_is_locked_resume_swap() {
|
async fn given_bob_restarts_after_xmr_is_locked_resume_swap() {
|
||||||
testutils::setup_test(SlowCancelConfig, |mut ctx| async move {
|
testutils::setup_test(SlowCancelConfig, |mut ctx| async move {
|
||||||
let (alice_swap, _) = ctx.new_swap_as_alice().await;
|
|
||||||
let (bob_swap, bob_join_handle) = ctx.new_swap_as_bob().await;
|
let (bob_swap, bob_join_handle) = ctx.new_swap_as_bob().await;
|
||||||
|
|
||||||
let alice_handle = alice::run(alice_swap);
|
|
||||||
let alice_swap_handle = tokio::spawn(alice_handle);
|
|
||||||
|
|
||||||
let bob_state = bob::run_until(bob_swap, is_xmr_locked).await.unwrap();
|
let bob_state = bob::run_until(bob_swap, is_xmr_locked).await.unwrap();
|
||||||
|
|
||||||
assert!(matches!(bob_state, BobState::XmrLocked { .. }));
|
assert!(matches!(bob_state, BobState::XmrLocked { .. }));
|
||||||
@ -23,8 +19,7 @@ async fn given_bob_restarts_after_xmr_is_locked_resume_swap() {
|
|||||||
|
|
||||||
ctx.assert_bob_redeemed(bob_state).await;
|
ctx.assert_bob_redeemed(bob_state).await;
|
||||||
|
|
||||||
let alice_state = alice_swap_handle.await.unwrap();
|
ctx.assert_alice_redeemed().await;
|
||||||
ctx.assert_alice_redeemed(alice_state.unwrap()).await;
|
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
pub mod testutils;
|
|
||||||
|
|
||||||
use swap::protocol::{alice, bob, bob::BobState};
|
|
||||||
use testutils::{bob_run_until::is_btc_locked, FastPunishConfig};
|
|
||||||
|
|
||||||
/// Bob locks Btc and Alice locks Xmr. Bob does not act; he fails to send Alice
|
|
||||||
/// the encsig and fail to refund or redeem. Alice punishes.
|
|
||||||
#[tokio::test]
|
|
||||||
async fn alice_punishes_if_bob_never_acts_after_fund() {
|
|
||||||
testutils::setup_test(FastPunishConfig, |mut ctx| async move {
|
|
||||||
let (alice_swap, _) = ctx.new_swap_as_alice().await;
|
|
||||||
let (bob_swap, bob_join_handle) = ctx.new_swap_as_bob().await;
|
|
||||||
|
|
||||||
let alice = alice::run(alice_swap);
|
|
||||||
let alice_handle = tokio::spawn(alice);
|
|
||||||
|
|
||||||
let bob_state = bob::run_until(bob_swap, is_btc_locked).await.unwrap();
|
|
||||||
|
|
||||||
assert!(matches!(bob_state, BobState::BtcLocked { .. }));
|
|
||||||
|
|
||||||
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_swap, _) = ctx.stop_and_resume_bob_from_db(bob_join_handle).await;
|
|
||||||
assert!(matches!(bob_swap.state, BobState::BtcLocked { .. }));
|
|
||||||
|
|
||||||
let bob_state = bob::run(bob_swap).await.unwrap();
|
|
||||||
|
|
||||||
ctx.assert_bob_punished(bob_state).await;
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
pub mod testutils;
|
|
||||||
|
|
||||||
use swap::protocol::{alice, alice::AliceState, bob};
|
|
||||||
use testutils::{alice_run_until::is_xmr_locked, FastCancelConfig};
|
|
||||||
|
|
||||||
/// Bob locks btc and Alice locks xmr. Alice fails to act so Bob refunds. Alice
|
|
||||||
/// then also refunds.
|
|
||||||
#[tokio::test]
|
|
||||||
async fn given_alice_restarts_after_xmr_is_locked_refund_swap() {
|
|
||||||
testutils::setup_test(FastCancelConfig, |mut ctx| async move {
|
|
||||||
let (alice_swap, alice_join_handle) = ctx.new_swap_as_alice().await;
|
|
||||||
let (bob_swap, _) = ctx.new_swap_as_bob().await;
|
|
||||||
|
|
||||||
let bob = bob::run(bob_swap);
|
|
||||||
let bob_handle = tokio::spawn(bob);
|
|
||||||
|
|
||||||
let alice_state = alice::run_until(alice_swap, is_xmr_locked).await.unwrap();
|
|
||||||
assert!(matches!(alice_state, AliceState::XmrLocked { .. }));
|
|
||||||
|
|
||||||
// Alice does not act, Bob refunds
|
|
||||||
let bob_state = bob_handle.await.unwrap();
|
|
||||||
ctx.assert_bob_refunded(bob_state.unwrap()).await;
|
|
||||||
|
|
||||||
// Once bob has finished Alice is restarted and refunds as well
|
|
||||||
let alice_swap = ctx.stop_and_resume_alice_from_db(alice_join_handle).await;
|
|
||||||
assert!(matches!(alice_swap.state, AliceState::XmrLocked { .. }));
|
|
||||||
|
|
||||||
let alice_state = alice::run(alice_swap).await.unwrap();
|
|
||||||
|
|
||||||
ctx.assert_alice_refunded(alice_state).await;
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
pub mod testutils;
|
|
||||||
|
|
||||||
use swap::{
|
|
||||||
execution_params,
|
|
||||||
protocol::{alice, alice::AliceState, bob},
|
|
||||||
};
|
|
||||||
use testutils::alice_run_until::is_encsig_learned;
|
|
||||||
|
|
||||||
/// Bob locks btc and Alice locks xmr. Alice fails to act so Bob refunds. Alice
|
|
||||||
/// is forced to refund even though she learned the secret and would be able to
|
|
||||||
/// redeem had the timelock not expired.
|
|
||||||
#[tokio::test]
|
|
||||||
async fn given_alice_restarts_after_enc_sig_learned_and_bob_already_cancelled_refund_swap() {
|
|
||||||
testutils::setup_test(execution_params::Regtest, |mut ctx| async move {
|
|
||||||
let (alice_swap, alice_join_handle) = ctx.new_swap_as_alice().await;
|
|
||||||
let (bob_swap, _) = ctx.new_swap_as_bob().await;
|
|
||||||
|
|
||||||
let bob = bob::run(bob_swap);
|
|
||||||
let bob_handle = tokio::spawn(bob);
|
|
||||||
|
|
||||||
let alice_state = alice::run_until(alice_swap, is_encsig_learned)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert!(
|
|
||||||
matches!(alice_state, AliceState::EncSigLearned { .. }),
|
|
||||||
"Alice state is not EncSigLearned: {:?}",
|
|
||||||
alice_state
|
|
||||||
);
|
|
||||||
|
|
||||||
// Wait for Bob to refund, because Alice does not act
|
|
||||||
let bob_state = bob_handle.await.unwrap();
|
|
||||||
ctx.assert_bob_refunded(bob_state.unwrap()).await;
|
|
||||||
|
|
||||||
// Once bob has finished Alice is restarted and refunds as well
|
|
||||||
let alice_swap = ctx.stop_and_resume_alice_from_db(alice_join_handle).await;
|
|
||||||
assert!(
|
|
||||||
matches!(alice_swap.state, AliceState::EncSigLearned { .. }),
|
|
||||||
"Alice state is not EncSigLearned: {:?}",
|
|
||||||
alice_state
|
|
||||||
);
|
|
||||||
|
|
||||||
let alice_state = alice::run(alice_swap).await.unwrap();
|
|
||||||
|
|
||||||
ctx.assert_alice_refunded(alice_state).await;
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
@ -8,6 +8,7 @@ use std::{path::PathBuf, sync::Arc};
|
|||||||
use swap::{
|
use swap::{
|
||||||
bitcoin,
|
bitcoin,
|
||||||
bitcoin::Timelock,
|
bitcoin::Timelock,
|
||||||
|
database::{Database, Swap},
|
||||||
execution_params,
|
execution_params,
|
||||||
execution_params::{ExecutionParams, GetExecutionParams},
|
execution_params::{ExecutionParams, GetExecutionParams},
|
||||||
monero,
|
monero,
|
||||||
@ -30,31 +31,10 @@ pub struct StartingBalances {
|
|||||||
struct AliceParams {
|
struct AliceParams {
|
||||||
seed: Seed,
|
seed: Seed,
|
||||||
execution_params: ExecutionParams,
|
execution_params: ExecutionParams,
|
||||||
swap_id: Uuid,
|
db: Arc<Database>,
|
||||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
|
||||||
monero_wallet: Arc<monero::Wallet>,
|
|
||||||
db_path: PathBuf,
|
|
||||||
listen_address: Multiaddr,
|
listen_address: Multiaddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AliceParams {
|
|
||||||
pub fn builder(&self) -> alice::Builder {
|
|
||||||
alice::Builder::new(
|
|
||||||
self.seed,
|
|
||||||
self.execution_params,
|
|
||||||
self.swap_id,
|
|
||||||
self.bitcoin_wallet.clone(),
|
|
||||||
self.monero_wallet.clone(),
|
|
||||||
self.db_path.clone(),
|
|
||||||
self.listen_address.clone(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn peer_id(&self) -> PeerId {
|
|
||||||
self.builder().peer_id()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct BobParams {
|
struct BobParams {
|
||||||
seed: Seed,
|
seed: Seed,
|
||||||
@ -84,6 +64,12 @@ impl BobParams {
|
|||||||
|
|
||||||
pub struct BobEventLoopJoinHandle(JoinHandle<()>);
|
pub struct BobEventLoopJoinHandle(JoinHandle<()>);
|
||||||
|
|
||||||
|
impl BobEventLoopJoinHandle {
|
||||||
|
pub fn abort(&self) {
|
||||||
|
self.0.abort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct AliceEventLoopJoinHandle(JoinHandle<()>);
|
pub struct AliceEventLoopJoinHandle(JoinHandle<()>);
|
||||||
|
|
||||||
pub struct TestContext {
|
pub struct TestContext {
|
||||||
@ -101,20 +87,6 @@ pub struct TestContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TestContext {
|
impl TestContext {
|
||||||
pub async fn new_swap_as_alice(&mut self) -> (alice::Swap, AliceEventLoopJoinHandle) {
|
|
||||||
let (swap, mut event_loop) = self
|
|
||||||
.alice_params
|
|
||||||
.builder()
|
|
||||||
.with_init_params(self.swap_amounts)
|
|
||||||
.build()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let join_handle = tokio::spawn(async move { event_loop.run().await });
|
|
||||||
|
|
||||||
(swap, AliceEventLoopJoinHandle(join_handle))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn new_swap_as_bob(&mut self) -> (bob::Swap, BobEventLoopJoinHandle) {
|
pub async fn new_swap_as_bob(&mut self) -> (bob::Swap, BobEventLoopJoinHandle) {
|
||||||
let (swap, event_loop) = self
|
let (swap, event_loop) = self
|
||||||
.bob_params
|
.bob_params
|
||||||
@ -129,24 +101,11 @@ impl TestContext {
|
|||||||
(swap, BobEventLoopJoinHandle(join_handle))
|
(swap, BobEventLoopJoinHandle(join_handle))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn stop_and_resume_alice_from_db(
|
|
||||||
&mut self,
|
|
||||||
join_handle: AliceEventLoopJoinHandle,
|
|
||||||
) -> alice::Swap {
|
|
||||||
join_handle.0.abort();
|
|
||||||
|
|
||||||
let (swap, mut event_loop) = self.alice_params.builder().build().await.unwrap();
|
|
||||||
|
|
||||||
tokio::spawn(async move { event_loop.run().await });
|
|
||||||
|
|
||||||
swap
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn stop_and_resume_bob_from_db(
|
pub async fn stop_and_resume_bob_from_db(
|
||||||
&mut self,
|
&mut self,
|
||||||
join_handle: BobEventLoopJoinHandle,
|
join_handle: BobEventLoopJoinHandle,
|
||||||
) -> (bob::Swap, BobEventLoopJoinHandle) {
|
) -> (bob::Swap, BobEventLoopJoinHandle) {
|
||||||
join_handle.0.abort();
|
join_handle.abort();
|
||||||
|
|
||||||
let (swap, event_loop) = self.bob_params.builder().build().await.unwrap();
|
let (swap, event_loop) = self.bob_params.builder().build().await.unwrap();
|
||||||
|
|
||||||
@ -155,7 +114,17 @@ impl TestContext {
|
|||||||
(swap, BobEventLoopJoinHandle(join_handle))
|
(swap, BobEventLoopJoinHandle(join_handle))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn assert_alice_redeemed(&self, state: AliceState) {
|
pub async fn assert_alice_redeemed(&self) {
|
||||||
|
let mut states = self.alice_params.db.all().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(states.len(), 1, "Expected only one swap in Alice's db");
|
||||||
|
|
||||||
|
let (_swap_id, state) = states.pop().unwrap();
|
||||||
|
let state = match state {
|
||||||
|
Swap::Alice(state) => state.into(),
|
||||||
|
Swap::Bob(_) => panic!("Bob state in Alice db is unexpected"),
|
||||||
|
};
|
||||||
|
|
||||||
assert!(matches!(state, AliceState::BtcRedeemed));
|
assert!(matches!(state, AliceState::BtcRedeemed));
|
||||||
|
|
||||||
let btc_balance_after_swap = self.alice_bitcoin_wallet.as_ref().balance().await.unwrap();
|
let btc_balance_after_swap = self.alice_bitcoin_wallet.as_ref().balance().await.unwrap();
|
||||||
@ -174,8 +143,22 @@ impl TestContext {
|
|||||||
assert!(xmr_balance_after_swap <= self.alice_starting_balances.xmr - self.swap_amounts.xmr);
|
assert!(xmr_balance_after_swap <= self.alice_starting_balances.xmr - self.swap_amounts.xmr);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn assert_alice_refunded(&self, state: AliceState) {
|
pub async fn assert_alice_refunded(&self) {
|
||||||
assert!(matches!(state, AliceState::XmrRefunded));
|
let mut states = self.alice_params.db.all().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(states.len(), 1, "Expected only one swap in Alice's db");
|
||||||
|
|
||||||
|
let (_swap_id, state) = states.pop().unwrap();
|
||||||
|
let state = match state {
|
||||||
|
Swap::Alice(state) => state.into(),
|
||||||
|
Swap::Bob(_) => panic!("Bob state in Alice db is unexpected"),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
matches!(state, AliceState::XmrRefunded),
|
||||||
|
"Alice state is not XmrRefunded: {}",
|
||||||
|
state
|
||||||
|
);
|
||||||
|
|
||||||
let btc_balance_after_swap = self.alice_bitcoin_wallet.as_ref().balance().await.unwrap();
|
let btc_balance_after_swap = self.alice_bitcoin_wallet.as_ref().balance().await.unwrap();
|
||||||
assert_eq!(btc_balance_after_swap, self.alice_starting_balances.btc);
|
assert_eq!(btc_balance_after_swap, self.alice_starting_balances.btc);
|
||||||
@ -342,13 +325,13 @@ where
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
let db_path = tempdir().unwrap();
|
||||||
|
let alice_db = Arc::new(Database::open(db_path.path()).unwrap());
|
||||||
|
|
||||||
let alice_params = AliceParams {
|
let alice_params = AliceParams {
|
||||||
seed: Seed::random().unwrap(),
|
seed: Seed::random().unwrap(),
|
||||||
execution_params,
|
execution_params,
|
||||||
swap_id: Uuid::new_v4(),
|
db: alice_db.clone(),
|
||||||
bitcoin_wallet: alice_bitcoin_wallet.clone(),
|
|
||||||
monero_wallet: alice_monero_wallet.clone(),
|
|
||||||
db_path: tempdir().unwrap().path().to_path_buf(),
|
|
||||||
listen_address,
|
listen_address,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -365,6 +348,22 @@ where
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
let mut alice_event_loop = alice::EventLoop::new(
|
||||||
|
alice_params.listen_address.clone(),
|
||||||
|
alice_params.seed,
|
||||||
|
alice_params.execution_params,
|
||||||
|
alice_bitcoin_wallet.clone(),
|
||||||
|
alice_monero_wallet.clone(),
|
||||||
|
alice_db,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let alice_peer_id = alice_event_loop.peer_id();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
alice_event_loop.run().await;
|
||||||
|
});
|
||||||
|
|
||||||
let bob_params = BobParams {
|
let bob_params = BobParams {
|
||||||
seed: Seed::random().unwrap(),
|
seed: Seed::random().unwrap(),
|
||||||
db_path: tempdir().unwrap().path().to_path_buf(),
|
db_path: tempdir().unwrap().path().to_path_buf(),
|
||||||
@ -372,7 +371,7 @@ where
|
|||||||
bitcoin_wallet: bob_bitcoin_wallet.clone(),
|
bitcoin_wallet: bob_bitcoin_wallet.clone(),
|
||||||
monero_wallet: bob_monero_wallet.clone(),
|
monero_wallet: bob_monero_wallet.clone(),
|
||||||
alice_address: alice_params.listen_address.clone(),
|
alice_address: alice_params.listen_address.clone(),
|
||||||
alice_peer_id: alice_params.peer_id(),
|
alice_peer_id,
|
||||||
execution_params,
|
execution_params,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -388,7 +387,7 @@ where
|
|||||||
bob_monero_wallet,
|
bob_monero_wallet,
|
||||||
};
|
};
|
||||||
|
|
||||||
testfn(test).await
|
testfn(test).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn init_containers(cli: &Cli) -> (Monero, Containers<'_>) {
|
async fn init_containers(cli: &Cli) -> (Monero, Containers<'_>) {
|
||||||
|
Loading…
Reference in New Issue
Block a user