mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-12-25 07:29:32 -05:00
Merge #376
376: ASB resumes unfinished swaps after startup r=da-kami a=da-kami Fixes #374 - [x] Save Bob peer-id in database for Alice - [x] Alice: Wait for `10` Monero confirmations in `BtcRefunded` instead of `XmrLocked` (requires extending the RPC to distinguish locked / unlocked balance) - [x] Save Alice peer-id in database for Bob ~~(+ multiaddress and remove params from resume)~~ - [ ] ~~Refactor Bob in test setup (handle event-loop in test setup similar to Alice)~~ I decided against refactoring Bob in the test setup, because eventually we might still want to add concurrent swap tests with multiple Bobs. The refactoring I had in mind would not allow such kind of tests. Generally, the current state of the changes already contains enough added value to open the PR :) Follow ups out of scope - [ ] Parametrize database with role (Alice / Bob) and remove all the (currently useless) mapping between DB and protocol types. - [ ] Alice: Wait for transfer proof ack before transitioning to new `XmrLocked` Co-authored-by: Daniel Karzel <daniel@comit.network>
This commit is contained in:
commit
d144405182
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@ -110,10 +110,13 @@ jobs:
|
|||||||
happy_path,
|
happy_path,
|
||||||
happy_path_restart_bob_after_xmr_locked,
|
happy_path_restart_bob_after_xmr_locked,
|
||||||
happy_path_restart_bob_before_xmr_locked,
|
happy_path_restart_bob_before_xmr_locked,
|
||||||
|
happy_path_restart_alice_after_xmr_locked,
|
||||||
bob_refunds_using_cancel_and_refund_command,
|
bob_refunds_using_cancel_and_refund_command,
|
||||||
bob_refunds_using_cancel_and_refund_command_timelock_not_expired,
|
bob_refunds_using_cancel_and_refund_command_timelock_not_expired,
|
||||||
bob_refunds_using_cancel_and_refund_command_timelock_not_expired_force,
|
bob_refunds_using_cancel_and_refund_command_timelock_not_expired_force,
|
||||||
punish
|
punish,
|
||||||
|
alice_punishes_after_restart_punish_timelock_expired,
|
||||||
|
alice_refunds_after_restart_bob_refunded
|
||||||
]
|
]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
@ -7,9 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The `resume` command of the `swap` CLI no longer require the `--seller-peer-id` parameter.
|
||||||
|
This information is now saved in the database.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- A changelog file.
|
- A changelog file.
|
||||||
|
- Automatic resume of unfinished swaps for the `asb` upon startup.
|
||||||
|
Unfinished swaps from earlier versions will be skipped.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -1552,6 +1552,15 @@ dependencies = [
|
|||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "0.4.7"
|
version = "0.4.7"
|
||||||
@ -2547,7 +2556,7 @@ checksum = "32d3ebd75ac2679c2af3a92246639f9fcc8a442ee420719cc4fe195b98dd5fa3"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes 1.0.1",
|
"bytes 1.0.1",
|
||||||
"heck",
|
"heck",
|
||||||
"itertools",
|
"itertools 0.9.0",
|
||||||
"log 0.4.14",
|
"log 0.4.14",
|
||||||
"multimap",
|
"multimap",
|
||||||
"petgraph",
|
"petgraph",
|
||||||
@ -2564,7 +2573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "169a15f3008ecb5160cba7d37bcd690a7601b6d30cfb87a117d45e59d52af5d4"
|
checksum = "169a15f3008ecb5160cba7d37bcd690a7601b6d30cfb87a117d45e59d52af5d4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"itertools",
|
"itertools 0.9.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
@ -3522,6 +3531,7 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"get-port",
|
"get-port",
|
||||||
"hyper 0.14.5",
|
"hyper 0.14.5",
|
||||||
|
"itertools 0.10.0",
|
||||||
"libp2p",
|
"libp2p",
|
||||||
"libp2p-async-await",
|
"libp2p-async-await",
|
||||||
"miniscript",
|
"miniscript",
|
||||||
|
@ -8,9 +8,12 @@ status = [
|
|||||||
"test (macos-latest)",
|
"test (macos-latest)",
|
||||||
"docker_tests (happy_path)",
|
"docker_tests (happy_path)",
|
||||||
"docker_tests (happy_path_restart_bob_after_xmr_locked)",
|
"docker_tests (happy_path_restart_bob_after_xmr_locked)",
|
||||||
|
"docker_tests (happy_path_restart_alice_after_xmr_locked)",
|
||||||
"docker_tests (happy_path_restart_bob_before_xmr_locked)",
|
"docker_tests (happy_path_restart_bob_before_xmr_locked)",
|
||||||
"docker_tests (bob_refunds_using_cancel_and_refund_command)",
|
"docker_tests (bob_refunds_using_cancel_and_refund_command)",
|
||||||
"docker_tests (bob_refunds_using_cancel_and_refund_command_timelock_not_expired_force)",
|
"docker_tests (bob_refunds_using_cancel_and_refund_command_timelock_not_expired_force)",
|
||||||
"docker_tests (bob_refunds_using_cancel_and_refund_command_timelock_not_expired)",
|
"docker_tests (bob_refunds_using_cancel_and_refund_command_timelock_not_expired)",
|
||||||
"docker_tests (punish)"
|
"docker_tests (punish)",
|
||||||
|
"docker_tests (alice_punishes_after_restart_punish_timelock_expired)",
|
||||||
|
"docker_tests (alice_refunds_after_restart_bob_refunded)"
|
||||||
]
|
]
|
||||||
|
@ -25,6 +25,7 @@ dialoguer = "0.8"
|
|||||||
directories-next = "2"
|
directories-next = "2"
|
||||||
ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", features = ["libsecp_compat", "serde"] }
|
ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", features = ["libsecp_compat", "serde"] }
|
||||||
futures = { version = "0.3", default-features = false }
|
futures = { version = "0.3", default-features = false }
|
||||||
|
itertools = "0.10"
|
||||||
libp2p = { version = "0.36", default-features = false, features = ["tcp-tokio", "yamux", "mplex", "dns-tokio", "noise", "request-response"] }
|
libp2p = { version = "0.36", default-features = false, features = ["tcp-tokio", "yamux", "mplex", "dns-tokio", "noise", "request-response"] }
|
||||||
libp2p-async-await = { git = "https://github.com/comit-network/rust-libp2p-async-await" }
|
libp2p-async-await = { git = "https://github.com/comit-network/rust-libp2p-async-await" }
|
||||||
miniscript = { version = "5", features = ["serde"] }
|
miniscript = { version = "5", features = ["serde"] }
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::asb::Rate;
|
use crate::asb::Rate;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct FixedRate(Rate);
|
pub struct FixedRate(Rate);
|
||||||
|
|
||||||
impl FixedRate {
|
impl FixedRate {
|
||||||
|
@ -130,7 +130,7 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
table.add_row(row!["SWAP ID", "STATE"]);
|
table.add_row(row!["SWAP ID", "STATE"]);
|
||||||
|
|
||||||
for (swap_id, state) in db.all()? {
|
for (swap_id, state) in db.all_alice()? {
|
||||||
table.add_row(row![swap_id, state]);
|
table.add_row(row![swap_id, state]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ use std::sync::Arc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use swap::bitcoin::{Amount, TxLock};
|
use swap::bitcoin::{Amount, TxLock};
|
||||||
use swap::cli::command::{AliceConnectParams, Arguments, Command, Data, MoneroParams};
|
use swap::cli::command::{AliceMultiaddress, Arguments, Command, Data, MoneroParams};
|
||||||
use swap::database::Database;
|
use swap::database::Database;
|
||||||
use swap::env::{Config, GetConfig};
|
use swap::env::{Config, GetConfig};
|
||||||
use swap::network::quote::BidQuote;
|
use swap::network::quote::BidQuote;
|
||||||
@ -81,9 +81,9 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
match args.cmd {
|
match args.cmd {
|
||||||
Command::BuyXmr {
|
Command::BuyXmr {
|
||||||
connect_params:
|
alice_peer_id,
|
||||||
AliceConnectParams {
|
alice_multi_addr:
|
||||||
peer_id: alice_peer_id,
|
AliceMultiaddress {
|
||||||
multiaddr: alice_addr,
|
multiaddr: alice_addr,
|
||||||
},
|
},
|
||||||
monero_params:
|
monero_params:
|
||||||
@ -131,9 +131,12 @@ async fn main() -> Result<()> {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let swap_id = Uuid::new_v4();
|
||||||
|
db.insert_peer_id(swap_id, alice_peer_id).await?;
|
||||||
|
|
||||||
let swap = Builder::new(
|
let swap = Builder::new(
|
||||||
db,
|
db,
|
||||||
Uuid::new_v4(),
|
swap_id,
|
||||||
bitcoin_wallet.clone(),
|
bitcoin_wallet.clone(),
|
||||||
Arc::new(monero_wallet),
|
Arc::new(monero_wallet),
|
||||||
env_config,
|
env_config,
|
||||||
@ -158,7 +161,7 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
table.add_row(row!["SWAP ID", "STATE"]);
|
table.add_row(row!["SWAP ID", "STATE"]);
|
||||||
|
|
||||||
for (swap_id, state) in db.all()? {
|
for (swap_id, state) in db.all_bob()? {
|
||||||
table.add_row(row![swap_id, state]);
|
table.add_row(row![swap_id, state]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,9 +170,8 @@ async fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
Command::Resume {
|
Command::Resume {
|
||||||
swap_id,
|
swap_id,
|
||||||
connect_params:
|
alice_multi_addr:
|
||||||
AliceConnectParams {
|
AliceMultiaddress {
|
||||||
peer_id: alice_peer_id,
|
|
||||||
multiaddr: alice_addr,
|
multiaddr: alice_addr,
|
||||||
},
|
},
|
||||||
monero_params:
|
monero_params:
|
||||||
@ -189,6 +191,7 @@ async fn main() -> Result<()> {
|
|||||||
init_monero_wallet(data_dir, monero_daemon_host, env_config).await?;
|
init_monero_wallet(data_dir, monero_daemon_host, env_config).await?;
|
||||||
let bitcoin_wallet = Arc::new(bitcoin_wallet);
|
let bitcoin_wallet = Arc::new(bitcoin_wallet);
|
||||||
|
|
||||||
|
let alice_peer_id = db.get_peer_id(swap_id)?;
|
||||||
let mut swarm = swarm::new::<Behaviour>(&seed)?;
|
let mut swarm = swarm::new::<Behaviour>(&seed)?;
|
||||||
swarm.add_address(alice_peer_id, alice_addr);
|
swarm.add_address(alice_peer_id, alice_addr);
|
||||||
|
|
||||||
|
@ -37,8 +37,15 @@ pub struct Arguments {
|
|||||||
pub enum Command {
|
pub enum Command {
|
||||||
/// Start a XMR for BTC swap
|
/// Start a XMR for BTC swap
|
||||||
BuyXmr {
|
BuyXmr {
|
||||||
|
#[structopt(
|
||||||
|
long = "seller-peer-id",
|
||||||
|
default_value = DEFAULT_ALICE_PEER_ID,
|
||||||
|
help = "The peer id of a specific swap partner can be optionally provided"
|
||||||
|
)]
|
||||||
|
alice_peer_id: PeerId,
|
||||||
|
|
||||||
#[structopt(flatten)]
|
#[structopt(flatten)]
|
||||||
connect_params: AliceConnectParams,
|
alice_multi_addr: AliceMultiaddress,
|
||||||
|
|
||||||
#[structopt(long = "electrum-rpc",
|
#[structopt(long = "electrum-rpc",
|
||||||
help = "Provide the Bitcoin Electrum RPC URL",
|
help = "Provide the Bitcoin Electrum RPC URL",
|
||||||
@ -60,7 +67,7 @@ pub enum Command {
|
|||||||
swap_id: Uuid,
|
swap_id: Uuid,
|
||||||
|
|
||||||
#[structopt(flatten)]
|
#[structopt(flatten)]
|
||||||
connect_params: AliceConnectParams,
|
alice_multi_addr: AliceMultiaddress,
|
||||||
|
|
||||||
#[structopt(long = "electrum-rpc",
|
#[structopt(long = "electrum-rpc",
|
||||||
help = "Provide the Bitcoin Electrum RPC URL",
|
help = "Provide the Bitcoin Electrum RPC URL",
|
||||||
@ -108,14 +115,7 @@ pub enum Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(structopt::StructOpt, Debug)]
|
#[derive(structopt::StructOpt, Debug)]
|
||||||
pub struct AliceConnectParams {
|
pub struct AliceMultiaddress {
|
||||||
#[structopt(
|
|
||||||
long = "seller-peer-id",
|
|
||||||
default_value = DEFAULT_ALICE_PEER_ID,
|
|
||||||
help = "The peer id of a specific swap partner can be optionally provided"
|
|
||||||
)]
|
|
||||||
pub peer_id: PeerId,
|
|
||||||
|
|
||||||
#[structopt(
|
#[structopt(
|
||||||
long = "seller-addr",
|
long = "seller-addr",
|
||||||
default_value = DEFAULT_ALICE_MULTIADDR,
|
default_value = DEFAULT_ALICE_MULTIADDR,
|
||||||
|
@ -2,10 +2,13 @@ pub use alice::Alice;
|
|||||||
pub use bob::Bob;
|
pub use bob::Bob;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use libp2p::PeerId;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::str::FromStr;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
mod alice;
|
mod alice;
|
||||||
@ -38,16 +41,34 @@ impl Display for Swap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq)]
|
||||||
|
#[error("Not in the role of Alice")]
|
||||||
|
struct NotAlice;
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq)]
|
||||||
|
#[error("Not in the role of Bob")]
|
||||||
|
struct NotBob;
|
||||||
|
|
||||||
impl Swap {
|
impl Swap {
|
||||||
|
pub fn try_into_alice(self) -> Result<Alice> {
|
||||||
|
match self {
|
||||||
|
Swap::Alice(alice) => Ok(alice),
|
||||||
|
Swap::Bob(_) => bail!(NotAlice),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn try_into_bob(self) -> Result<Bob> {
|
pub fn try_into_bob(self) -> Result<Bob> {
|
||||||
match self {
|
match self {
|
||||||
Swap::Bob(bob) => Ok(bob),
|
Swap::Bob(bob) => Ok(bob),
|
||||||
Swap::Alice(_) => bail!("Swap instance is not Bob"),
|
Swap::Alice(_) => bail!(NotBob),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Database(sled::Db);
|
pub struct Database {
|
||||||
|
swaps: sled::Tree,
|
||||||
|
peers: sled::Tree,
|
||||||
|
}
|
||||||
|
|
||||||
impl Database {
|
impl Database {
|
||||||
pub fn open(path: &Path) -> Result<Self> {
|
pub fn open(path: &Path) -> Result<Self> {
|
||||||
@ -56,22 +77,51 @@ impl Database {
|
|||||||
let db =
|
let db =
|
||||||
sled::open(path).with_context(|| format!("Could not open the DB at {:?}", path))?;
|
sled::open(path).with_context(|| format!("Could not open the DB at {:?}", path))?;
|
||||||
|
|
||||||
Ok(Database(db))
|
let swaps = db.open_tree("swaps")?;
|
||||||
|
let peers = db.open_tree("peers")?;
|
||||||
|
|
||||||
|
Ok(Database { swaps, peers })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn insert_peer_id(&self, swap_id: Uuid, peer_id: PeerId) -> Result<()> {
|
||||||
|
let peer_id_str = peer_id.to_string();
|
||||||
|
|
||||||
|
let key = serialize(&swap_id)?;
|
||||||
|
let value = serialize(&peer_id_str).context("Could not serialize peer-id")?;
|
||||||
|
|
||||||
|
self.peers.insert(key, value)?;
|
||||||
|
|
||||||
|
self.peers
|
||||||
|
.flush_async()
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.context("Could not flush db")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_peer_id(&self, swap_id: Uuid) -> Result<PeerId> {
|
||||||
|
let key = serialize(&swap_id)?;
|
||||||
|
|
||||||
|
let encoded = self
|
||||||
|
.peers
|
||||||
|
.get(&key)?
|
||||||
|
.ok_or_else(|| anyhow!("No peer-id found for swap id {} in database", swap_id))?;
|
||||||
|
|
||||||
|
let peer_id: String = deserialize(&encoded).context("Could not deserialize peer-id")?;
|
||||||
|
Ok(PeerId::from_str(peer_id.as_str())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn insert_latest_state(&self, swap_id: Uuid, state: Swap) -> Result<()> {
|
pub async fn insert_latest_state(&self, swap_id: Uuid, state: Swap) -> Result<()> {
|
||||||
let key = serialize(&swap_id)?;
|
let key = serialize(&swap_id)?;
|
||||||
let new_value = serialize(&state).context("Could not serialize new state value")?;
|
let new_value = serialize(&state).context("Could not serialize new state value")?;
|
||||||
|
|
||||||
let old_value = self.0.get(&key)?;
|
let old_value = self.swaps.get(&key)?;
|
||||||
|
|
||||||
self.0
|
self.swaps
|
||||||
.compare_and_swap(key, old_value, Some(new_value))
|
.compare_and_swap(key, old_value, Some(new_value))
|
||||||
.context("Could not write in the DB")?
|
.context("Could not write in the DB")?
|
||||||
.context("Stored swap somehow changed, aborting saving")?;
|
.context("Stored swap somehow changed, aborting saving")?;
|
||||||
|
|
||||||
// TODO: see if this can be done through sled config
|
self.swaps
|
||||||
self.0
|
|
||||||
.flush_async()
|
.flush_async()
|
||||||
.await
|
.await
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
@ -82,7 +132,7 @@ impl Database {
|
|||||||
let key = serialize(&swap_id)?;
|
let key = serialize(&swap_id)?;
|
||||||
|
|
||||||
let encoded = self
|
let encoded = self
|
||||||
.0
|
.swaps
|
||||||
.get(&key)?
|
.get(&key)?
|
||||||
.ok_or_else(|| anyhow!("Swap with id {} not found in database", swap_id))?;
|
.ok_or_else(|| anyhow!("Swap with id {} not found in database", swap_id))?;
|
||||||
|
|
||||||
@ -90,22 +140,42 @@ impl Database {
|
|||||||
Ok(state)
|
Ok(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn all(&self) -> Result<Vec<(Uuid, Swap)>> {
|
pub fn all_alice(&self) -> Result<Vec<(Uuid, Alice)>> {
|
||||||
self.0
|
self.all_alice_iter().collect()
|
||||||
.iter()
|
}
|
||||||
.map(|item| match item {
|
|
||||||
Ok((key, value)) => {
|
|
||||||
let swap_id = deserialize::<Uuid>(&key);
|
|
||||||
let swap = deserialize::<Swap>(&value).context("Failed to deserialize swap");
|
|
||||||
|
|
||||||
match (swap_id, swap) {
|
fn all_alice_iter(&self) -> impl Iterator<Item = Result<(Uuid, Alice)>> {
|
||||||
(Ok(swap_id), Ok(swap)) => Ok((swap_id, swap)),
|
self.all_swaps_iter().map(|item| {
|
||||||
(Ok(_), Err(err)) => Err(err),
|
let (swap_id, swap) = item?;
|
||||||
_ => bail!("Failed to deserialize swap"),
|
Ok((swap_id, swap.try_into_alice()?))
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => Err(err).context("Failed to retrieve swap from DB"),
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn all_bob(&self) -> Result<Vec<(Uuid, Bob)>> {
|
||||||
|
self.all_bob_iter().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn all_bob_iter(&self) -> impl Iterator<Item = Result<(Uuid, Bob)>> {
|
||||||
|
self.all_swaps_iter().map(|item| {
|
||||||
|
let (swap_id, swap) = item?;
|
||||||
|
Ok((swap_id, swap.try_into_bob()?))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn all_swaps_iter(&self) -> impl Iterator<Item = Result<(Uuid, Swap)>> {
|
||||||
|
self.swaps.iter().map(|item| {
|
||||||
|
let (key, value) = item.context("Failed to retrieve swap from DB")?;
|
||||||
|
|
||||||
|
let swap_id = deserialize::<Uuid>(&key)?;
|
||||||
|
let swap = deserialize::<Swap>(&value).context("Failed to deserialize swap")?;
|
||||||
|
|
||||||
|
Ok((swap_id, swap))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unfinished_alice(&self) -> Result<Vec<(Uuid, Alice)>> {
|
||||||
|
self.all_alice_iter()
|
||||||
|
.filter_ok(|(_swap_id, alice)| !matches!(alice, Alice::Done(_)))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -187,26 +257,106 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn can_fetch_all_keys() {
|
async fn all_swaps_as_alice() {
|
||||||
let db_dir = tempfile::tempdir().unwrap();
|
let db_dir = tempfile::tempdir().unwrap();
|
||||||
let db = Database::open(db_dir.path()).unwrap();
|
let db = Database::open(db_dir.path()).unwrap();
|
||||||
|
|
||||||
let state_1 = Swap::Alice(Alice::Done(AliceEndState::BtcPunished));
|
let alice_state = Alice::Done(AliceEndState::BtcPunished);
|
||||||
let swap_id_1 = Uuid::new_v4();
|
let alice_swap = Swap::Alice(alice_state.clone());
|
||||||
db.insert_latest_state(swap_id_1, state_1.clone())
|
let alice_swap_id = Uuid::new_v4();
|
||||||
|
db.insert_latest_state(alice_swap_id, alice_swap)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to save second state");
|
.expect("Failed to save alice state 1");
|
||||||
|
|
||||||
let state_2 = Swap::Bob(Bob::Done(BobEndState::SafelyAborted));
|
let alice_swaps = db.all_alice().unwrap();
|
||||||
let swap_id_2 = Uuid::new_v4();
|
assert_eq!(alice_swaps.len(), 1);
|
||||||
db.insert_latest_state(swap_id_2, state_2.clone())
|
assert!(alice_swaps.contains(&(alice_swap_id, alice_state)));
|
||||||
|
|
||||||
|
let bob_state = Bob::Done(BobEndState::SafelyAborted);
|
||||||
|
let bob_swap = Swap::Bob(bob_state);
|
||||||
|
let bob_swap_id = Uuid::new_v4();
|
||||||
|
db.insert_latest_state(bob_swap_id, bob_swap)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to save first state");
|
.expect("Failed to save bob state 1");
|
||||||
|
|
||||||
let swaps = db.all().unwrap();
|
let err = db.all_alice().unwrap_err();
|
||||||
|
|
||||||
assert_eq!(swaps.len(), 2);
|
assert_eq!(err.downcast_ref::<NotAlice>().unwrap(), &NotAlice);
|
||||||
assert!(swaps.contains(&(swap_id_1, state_1)));
|
}
|
||||||
assert!(swaps.contains(&(swap_id_2, state_2)));
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn all_swaps_as_bob() {
|
||||||
|
let db_dir = tempfile::tempdir().unwrap();
|
||||||
|
let db = Database::open(db_dir.path()).unwrap();
|
||||||
|
|
||||||
|
let bob_state = Bob::Done(BobEndState::SafelyAborted);
|
||||||
|
let bob_swap = Swap::Bob(bob_state.clone());
|
||||||
|
let bob_swap_id = Uuid::new_v4();
|
||||||
|
db.insert_latest_state(bob_swap_id, bob_swap)
|
||||||
|
.await
|
||||||
|
.expect("Failed to save bob state 1");
|
||||||
|
|
||||||
|
let bob_swaps = db.all_bob().unwrap();
|
||||||
|
assert_eq!(bob_swaps.len(), 1);
|
||||||
|
assert!(bob_swaps.contains(&(bob_swap_id, bob_state)));
|
||||||
|
|
||||||
|
let alice_state = Alice::Done(AliceEndState::BtcPunished);
|
||||||
|
let alice_swap = Swap::Alice(alice_state);
|
||||||
|
let alice_swap_id = Uuid::new_v4();
|
||||||
|
db.insert_latest_state(alice_swap_id, alice_swap)
|
||||||
|
.await
|
||||||
|
.expect("Failed to save alice state 1");
|
||||||
|
|
||||||
|
let err = db.all_bob().unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(err.downcast_ref::<NotBob>().unwrap(), &NotBob);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn can_save_swap_state_and_peer_id_with_same_swap_id() -> Result<()> {
|
||||||
|
let db_dir = tempfile::tempdir().unwrap();
|
||||||
|
let db = Database::open(db_dir.path()).unwrap();
|
||||||
|
|
||||||
|
let alice_id = Uuid::new_v4();
|
||||||
|
let alice_state = Alice::Done(AliceEndState::BtcPunished);
|
||||||
|
let alice_swap = Swap::Alice(alice_state);
|
||||||
|
let peer_id = PeerId::random();
|
||||||
|
|
||||||
|
db.insert_latest_state(alice_id, alice_swap.clone()).await?;
|
||||||
|
db.insert_peer_id(alice_id, peer_id).await?;
|
||||||
|
|
||||||
|
let loaded_swap = db.get_state(alice_id)?;
|
||||||
|
let loaded_peer_id = db.get_peer_id(alice_id)?;
|
||||||
|
|
||||||
|
assert_eq!(alice_swap, loaded_swap);
|
||||||
|
assert_eq!(peer_id, loaded_peer_id);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_reopen_db() -> Result<()> {
|
||||||
|
let db_dir = tempfile::tempdir().unwrap();
|
||||||
|
let alice_id = Uuid::new_v4();
|
||||||
|
let alice_state = Alice::Done(AliceEndState::BtcPunished);
|
||||||
|
let alice_swap = Swap::Alice(alice_state);
|
||||||
|
|
||||||
|
let peer_id = PeerId::random();
|
||||||
|
|
||||||
|
{
|
||||||
|
let db = Database::open(db_dir.path()).unwrap();
|
||||||
|
db.insert_latest_state(alice_id, alice_swap.clone()).await?;
|
||||||
|
db.insert_peer_id(alice_id, peer_id).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let db = Database::open(db_dir.path()).unwrap();
|
||||||
|
|
||||||
|
let loaded_swap = db.get_state(alice_id)?;
|
||||||
|
let loaded_peer_id = db.get_peer_id(alice_id)?;
|
||||||
|
|
||||||
|
assert_eq!(alice_swap, loaded_swap);
|
||||||
|
assert_eq!(peer_id, loaded_peer_id);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::bitcoin::EncryptedSignature;
|
use crate::bitcoin::EncryptedSignature;
|
||||||
use crate::monero;
|
use crate::monero;
|
||||||
use crate::monero::monero_private_key;
|
use crate::monero::{monero_private_key, TransferProof};
|
||||||
use crate::protocol::alice;
|
use crate::protocol::alice;
|
||||||
use crate::protocol::alice::AliceState;
|
use crate::protocol::alice::AliceState;
|
||||||
use ::bitcoin::hashes::core::fmt::Display;
|
use ::bitcoin::hashes::core::fmt::Display;
|
||||||
@ -18,29 +18,45 @@ pub enum Alice {
|
|||||||
BtcLocked {
|
BtcLocked {
|
||||||
state3: alice::State3,
|
state3: alice::State3,
|
||||||
},
|
},
|
||||||
|
XmrLockTransactionSent {
|
||||||
|
monero_wallet_restore_blockheight: BlockHeight,
|
||||||
|
transfer_proof: TransferProof,
|
||||||
|
state3: alice::State3,
|
||||||
|
},
|
||||||
XmrLocked {
|
XmrLocked {
|
||||||
monero_wallet_restore_blockheight: BlockHeight,
|
monero_wallet_restore_blockheight: BlockHeight,
|
||||||
|
transfer_proof: TransferProof,
|
||||||
|
state3: alice::State3,
|
||||||
|
},
|
||||||
|
XmrLockTransferProofSent {
|
||||||
|
monero_wallet_restore_blockheight: BlockHeight,
|
||||||
|
transfer_proof: TransferProof,
|
||||||
state3: alice::State3,
|
state3: alice::State3,
|
||||||
},
|
},
|
||||||
EncSigLearned {
|
EncSigLearned {
|
||||||
monero_wallet_restore_blockheight: BlockHeight,
|
monero_wallet_restore_blockheight: BlockHeight,
|
||||||
|
transfer_proof: TransferProof,
|
||||||
encrypted_signature: EncryptedSignature,
|
encrypted_signature: EncryptedSignature,
|
||||||
state3: alice::State3,
|
state3: alice::State3,
|
||||||
},
|
},
|
||||||
CancelTimelockExpired {
|
CancelTimelockExpired {
|
||||||
monero_wallet_restore_blockheight: BlockHeight,
|
monero_wallet_restore_blockheight: BlockHeight,
|
||||||
|
transfer_proof: TransferProof,
|
||||||
state3: alice::State3,
|
state3: alice::State3,
|
||||||
},
|
},
|
||||||
BtcCancelled {
|
BtcCancelled {
|
||||||
monero_wallet_restore_blockheight: BlockHeight,
|
monero_wallet_restore_blockheight: BlockHeight,
|
||||||
|
transfer_proof: TransferProof,
|
||||||
state3: alice::State3,
|
state3: alice::State3,
|
||||||
},
|
},
|
||||||
BtcPunishable {
|
BtcPunishable {
|
||||||
monero_wallet_restore_blockheight: BlockHeight,
|
monero_wallet_restore_blockheight: BlockHeight,
|
||||||
|
transfer_proof: TransferProof,
|
||||||
state3: alice::State3,
|
state3: alice::State3,
|
||||||
},
|
},
|
||||||
BtcRefunded {
|
BtcRefunded {
|
||||||
monero_wallet_restore_blockheight: BlockHeight,
|
monero_wallet_restore_blockheight: BlockHeight,
|
||||||
|
transfer_proof: TransferProof,
|
||||||
state3: alice::State3,
|
state3: alice::State3,
|
||||||
#[serde(with = "monero_private_key")]
|
#[serde(with = "monero_private_key")]
|
||||||
spend_key: monero::PrivateKey,
|
spend_key: monero::PrivateKey,
|
||||||
@ -65,54 +81,82 @@ impl From<&AliceState> for Alice {
|
|||||||
AliceState::BtcLocked { state3 } => Alice::BtcLocked {
|
AliceState::BtcLocked { state3 } => Alice::BtcLocked {
|
||||||
state3: state3.as_ref().clone(),
|
state3: state3.as_ref().clone(),
|
||||||
},
|
},
|
||||||
|
AliceState::XmrLockTransactionSent {
|
||||||
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
|
} => Alice::XmrLockTransactionSent {
|
||||||
|
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof: transfer_proof.clone(),
|
||||||
|
state3: state3.as_ref().clone(),
|
||||||
|
},
|
||||||
AliceState::XmrLocked {
|
AliceState::XmrLocked {
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
state3,
|
state3,
|
||||||
} => Alice::XmrLocked {
|
} => Alice::XmrLocked {
|
||||||
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof: transfer_proof.clone(),
|
||||||
|
state3: state3.as_ref().clone(),
|
||||||
|
},
|
||||||
|
AliceState::XmrLockTransferProofSent {
|
||||||
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
|
} => Alice::XmrLockTransferProofSent {
|
||||||
|
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof: transfer_proof.clone(),
|
||||||
state3: state3.as_ref().clone(),
|
state3: state3.as_ref().clone(),
|
||||||
},
|
},
|
||||||
AliceState::EncSigLearned {
|
AliceState::EncSigLearned {
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
state3,
|
state3,
|
||||||
encrypted_signature,
|
encrypted_signature,
|
||||||
} => Alice::EncSigLearned {
|
} => Alice::EncSigLearned {
|
||||||
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof: transfer_proof.clone(),
|
||||||
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 {
|
AliceState::BtcCancelled {
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
state3,
|
state3,
|
||||||
..
|
|
||||||
} => Alice::BtcCancelled {
|
} => Alice::BtcCancelled {
|
||||||
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof: transfer_proof.clone(),
|
||||||
state3: state3.as_ref().clone(),
|
state3: state3.as_ref().clone(),
|
||||||
},
|
},
|
||||||
AliceState::BtcRefunded {
|
AliceState::BtcRefunded {
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
spend_key,
|
spend_key,
|
||||||
state3,
|
state3,
|
||||||
} => Alice::BtcRefunded {
|
} => Alice::BtcRefunded {
|
||||||
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof: transfer_proof.clone(),
|
||||||
spend_key: *spend_key,
|
spend_key: *spend_key,
|
||||||
state3: state3.as_ref().clone(),
|
state3: state3.as_ref().clone(),
|
||||||
},
|
},
|
||||||
AliceState::BtcPunishable {
|
AliceState::BtcPunishable {
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
state3,
|
state3,
|
||||||
..
|
|
||||||
} => Alice::BtcPunishable {
|
} => Alice::BtcPunishable {
|
||||||
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof: transfer_proof.clone(),
|
||||||
state3: state3.as_ref().clone(),
|
state3: state3.as_ref().clone(),
|
||||||
},
|
},
|
||||||
AliceState::XmrRefunded => Alice::Done(AliceEndState::XmrRefunded),
|
AliceState::XmrRefunded => Alice::Done(AliceEndState::XmrRefunded),
|
||||||
AliceState::CancelTimelockExpired {
|
AliceState::CancelTimelockExpired {
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
state3,
|
state3,
|
||||||
} => Alice::CancelTimelockExpired {
|
} => Alice::CancelTimelockExpired {
|
||||||
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof: transfer_proof.clone(),
|
||||||
state3: state3.as_ref().clone(),
|
state3: state3.as_ref().clone(),
|
||||||
},
|
},
|
||||||
AliceState::BtcPunished => Alice::Done(AliceEndState::BtcPunished),
|
AliceState::BtcPunished => Alice::Done(AliceEndState::BtcPunished),
|
||||||
@ -130,50 +174,80 @@ impl From<Alice> for AliceState {
|
|||||||
Alice::BtcLocked { state3 } => AliceState::BtcLocked {
|
Alice::BtcLocked { state3 } => AliceState::BtcLocked {
|
||||||
state3: Box::new(state3),
|
state3: Box::new(state3),
|
||||||
},
|
},
|
||||||
|
Alice::XmrLockTransactionSent {
|
||||||
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
|
} => AliceState::XmrLockTransactionSent {
|
||||||
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3: Box::new(state3),
|
||||||
|
},
|
||||||
Alice::XmrLocked {
|
Alice::XmrLocked {
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
state3,
|
state3,
|
||||||
} => AliceState::XmrLocked {
|
} => AliceState::XmrLocked {
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3: Box::new(state3),
|
||||||
|
},
|
||||||
|
Alice::XmrLockTransferProofSent {
|
||||||
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
|
} => AliceState::XmrLockTransferProofSent {
|
||||||
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
state3: Box::new(state3),
|
state3: Box::new(state3),
|
||||||
},
|
},
|
||||||
Alice::EncSigLearned {
|
Alice::EncSigLearned {
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
state3: state,
|
state3: state,
|
||||||
encrypted_signature,
|
encrypted_signature,
|
||||||
} => AliceState::EncSigLearned {
|
} => AliceState::EncSigLearned {
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
state3: Box::new(state),
|
state3: Box::new(state),
|
||||||
encrypted_signature: Box::new(encrypted_signature),
|
encrypted_signature: Box::new(encrypted_signature),
|
||||||
},
|
},
|
||||||
Alice::CancelTimelockExpired {
|
Alice::CancelTimelockExpired {
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
state3,
|
state3,
|
||||||
} => AliceState::CancelTimelockExpired {
|
} => AliceState::CancelTimelockExpired {
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
state3: Box::new(state3),
|
state3: Box::new(state3),
|
||||||
},
|
},
|
||||||
Alice::BtcCancelled {
|
Alice::BtcCancelled {
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
state3,
|
state3,
|
||||||
} => AliceState::BtcCancelled {
|
} => AliceState::BtcCancelled {
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
state3: Box::new(state3),
|
state3: Box::new(state3),
|
||||||
},
|
},
|
||||||
|
|
||||||
Alice::BtcPunishable {
|
Alice::BtcPunishable {
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
state3,
|
state3,
|
||||||
} => AliceState::BtcPunishable {
|
} => AliceState::BtcPunishable {
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
state3: Box::new(state3),
|
state3: Box::new(state3),
|
||||||
},
|
},
|
||||||
Alice::BtcRefunded {
|
Alice::BtcRefunded {
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
state3,
|
transfer_proof,
|
||||||
spend_key,
|
spend_key,
|
||||||
|
state3,
|
||||||
} => AliceState::BtcRefunded {
|
} => AliceState::BtcRefunded {
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
spend_key,
|
spend_key,
|
||||||
state3: Box::new(state3),
|
state3: Box::new(state3),
|
||||||
},
|
},
|
||||||
@ -192,7 +266,11 @@ impl Display for Alice {
|
|||||||
match self {
|
match self {
|
||||||
Alice::Started { .. } => write!(f, "Started"),
|
Alice::Started { .. } => write!(f, "Started"),
|
||||||
Alice::BtcLocked { .. } => f.write_str("Bitcoin locked"),
|
Alice::BtcLocked { .. } => f.write_str("Bitcoin locked"),
|
||||||
|
Alice::XmrLockTransactionSent { .. } => f.write_str("Monero lock transaction sent"),
|
||||||
Alice::XmrLocked { .. } => f.write_str("Monero locked"),
|
Alice::XmrLocked { .. } => f.write_str("Monero locked"),
|
||||||
|
Alice::XmrLockTransferProofSent { .. } => {
|
||||||
|
f.write_str("Monero lock transfer proof sent")
|
||||||
|
}
|
||||||
Alice::CancelTimelockExpired { .. } => f.write_str("Cancel timelock is expired"),
|
Alice::CancelTimelockExpired { .. } => f.write_str("Cancel timelock is expired"),
|
||||||
Alice::BtcCancelled { .. } => f.write_str("Bitcoin cancel transaction published"),
|
Alice::BtcCancelled { .. } => f.write_str("Bitcoin cancel transaction published"),
|
||||||
Alice::BtcPunishable { .. } => f.write_str("Bitcoin punishable"),
|
Alice::BtcPunishable { .. } => f.write_str("Bitcoin punishable"),
|
||||||
|
@ -84,6 +84,43 @@ where
|
|||||||
// terminate forever.
|
// terminate forever.
|
||||||
self.send_transfer_proof.push(future::pending().boxed());
|
self.send_transfer_proof.push(future::pending().boxed());
|
||||||
|
|
||||||
|
let unfinished_swaps = match self.db.unfinished_alice() {
|
||||||
|
Ok(unfinished_swaps) => unfinished_swaps,
|
||||||
|
Err(_) => {
|
||||||
|
tracing::error!("Failed to load unfinished swaps");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (swap_id, state) in unfinished_swaps {
|
||||||
|
let peer_id = match self.db.get_peer_id(swap_id) {
|
||||||
|
Ok(peer_id) => peer_id,
|
||||||
|
Err(_) => {
|
||||||
|
tracing::warn!(%swap_id, "Resuming swap skipped because no peer-id found for swap in database");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let handle = self.new_handle(peer_id);
|
||||||
|
|
||||||
|
let swap = Swap {
|
||||||
|
event_loop_handle: handle,
|
||||||
|
bitcoin_wallet: self.bitcoin_wallet.clone(),
|
||||||
|
monero_wallet: self.monero_wallet.clone(),
|
||||||
|
env_config: self.env_config,
|
||||||
|
db: self.db.clone(),
|
||||||
|
state: state.into(),
|
||||||
|
swap_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.swap_sender.send(swap).await {
|
||||||
|
Ok(_) => tracing::info!(%swap_id, "Resuming swap"),
|
||||||
|
Err(_) => {
|
||||||
|
tracing::warn!(%swap_id, "Failed to resume swap because receiver has been dropped")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
swarm_event = self.swarm.next_event() => {
|
swarm_event = self.swarm.next_event() => {
|
||||||
@ -264,10 +301,20 @@ where
|
|||||||
swap_id,
|
swap_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Consider adding separate components for start/rsume of swaps
|
||||||
|
|
||||||
|
// swaps save peer id so we can resume
|
||||||
|
match self.db.insert_peer_id(swap_id, bob_peer_id).await {
|
||||||
|
Ok(_) => {
|
||||||
if let Err(error) = self.swap_sender.send(swap).await {
|
if let Err(error) = self.swap_sender.send(swap).await {
|
||||||
tracing::warn!(%swap_id, "Swap cannot be spawned: {}", error);
|
tracing::warn!(%swap_id, "Swap cannot be spawned: {}", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Err(error) => {
|
||||||
|
tracing::warn!(%swap_id, "Unable to save peer-id, swap cannot be spawned: {}", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new [`EventLoopHandle`] that is scoped for communication with
|
/// Create a new [`EventLoopHandle`] that is scoped for communication with
|
||||||
/// the given peer.
|
/// the given peer.
|
||||||
|
@ -22,32 +22,48 @@ pub enum AliceState {
|
|||||||
BtcLocked {
|
BtcLocked {
|
||||||
state3: Box<State3>,
|
state3: Box<State3>,
|
||||||
},
|
},
|
||||||
|
XmrLockTransactionSent {
|
||||||
|
monero_wallet_restore_blockheight: BlockHeight,
|
||||||
|
transfer_proof: TransferProof,
|
||||||
|
state3: Box<State3>,
|
||||||
|
},
|
||||||
XmrLocked {
|
XmrLocked {
|
||||||
monero_wallet_restore_blockheight: BlockHeight,
|
monero_wallet_restore_blockheight: BlockHeight,
|
||||||
|
transfer_proof: TransferProof,
|
||||||
|
state3: Box<State3>,
|
||||||
|
},
|
||||||
|
XmrLockTransferProofSent {
|
||||||
|
monero_wallet_restore_blockheight: BlockHeight,
|
||||||
|
transfer_proof: TransferProof,
|
||||||
state3: Box<State3>,
|
state3: Box<State3>,
|
||||||
},
|
},
|
||||||
EncSigLearned {
|
EncSigLearned {
|
||||||
monero_wallet_restore_blockheight: BlockHeight,
|
monero_wallet_restore_blockheight: BlockHeight,
|
||||||
|
transfer_proof: TransferProof,
|
||||||
encrypted_signature: Box<bitcoin::EncryptedSignature>,
|
encrypted_signature: Box<bitcoin::EncryptedSignature>,
|
||||||
state3: Box<State3>,
|
state3: Box<State3>,
|
||||||
},
|
},
|
||||||
BtcRedeemed,
|
BtcRedeemed,
|
||||||
BtcCancelled {
|
BtcCancelled {
|
||||||
monero_wallet_restore_blockheight: BlockHeight,
|
monero_wallet_restore_blockheight: BlockHeight,
|
||||||
|
transfer_proof: TransferProof,
|
||||||
state3: Box<State3>,
|
state3: Box<State3>,
|
||||||
},
|
},
|
||||||
BtcRefunded {
|
BtcRefunded {
|
||||||
monero_wallet_restore_blockheight: BlockHeight,
|
monero_wallet_restore_blockheight: BlockHeight,
|
||||||
|
transfer_proof: TransferProof,
|
||||||
spend_key: monero::PrivateKey,
|
spend_key: monero::PrivateKey,
|
||||||
state3: Box<State3>,
|
state3: Box<State3>,
|
||||||
},
|
},
|
||||||
BtcPunishable {
|
BtcPunishable {
|
||||||
monero_wallet_restore_blockheight: BlockHeight,
|
monero_wallet_restore_blockheight: BlockHeight,
|
||||||
|
transfer_proof: TransferProof,
|
||||||
state3: Box<State3>,
|
state3: Box<State3>,
|
||||||
},
|
},
|
||||||
XmrRefunded,
|
XmrRefunded,
|
||||||
CancelTimelockExpired {
|
CancelTimelockExpired {
|
||||||
monero_wallet_restore_blockheight: BlockHeight,
|
monero_wallet_restore_blockheight: BlockHeight,
|
||||||
|
transfer_proof: TransferProof,
|
||||||
state3: Box<State3>,
|
state3: Box<State3>,
|
||||||
},
|
},
|
||||||
BtcPunished,
|
BtcPunished,
|
||||||
@ -59,7 +75,11 @@ impl fmt::Display for AliceState {
|
|||||||
match self {
|
match self {
|
||||||
AliceState::Started { .. } => write!(f, "started"),
|
AliceState::Started { .. } => write!(f, "started"),
|
||||||
AliceState::BtcLocked { .. } => write!(f, "btc is locked"),
|
AliceState::BtcLocked { .. } => write!(f, "btc is locked"),
|
||||||
|
AliceState::XmrLockTransactionSent { .. } => write!(f, "xmr lock transaction sent"),
|
||||||
AliceState::XmrLocked { .. } => write!(f, "xmr is locked"),
|
AliceState::XmrLocked { .. } => write!(f, "xmr is locked"),
|
||||||
|
AliceState::XmrLockTransferProofSent { .. } => {
|
||||||
|
write!(f, "xmr lock transfer proof sent")
|
||||||
|
}
|
||||||
AliceState::EncSigLearned { .. } => write!(f, "encrypted signature is learned"),
|
AliceState::EncSigLearned { .. } => write!(f, "encrypted signature is learned"),
|
||||||
AliceState::BtcRedeemed => write!(f, "btc is redeemed"),
|
AliceState::BtcRedeemed => write!(f, "btc is redeemed"),
|
||||||
AliceState::BtcCancelled { .. } => write!(f, "btc is cancelled"),
|
AliceState::BtcCancelled { .. } => write!(f, "btc is cancelled"),
|
||||||
|
@ -5,39 +5,25 @@ use crate::env::Config;
|
|||||||
use crate::protocol::alice;
|
use crate::protocol::alice;
|
||||||
use crate::protocol::alice::event_loop::EventLoopHandle;
|
use crate::protocol::alice::event_loop::EventLoopHandle;
|
||||||
use crate::protocol::alice::AliceState;
|
use crate::protocol::alice::AliceState;
|
||||||
|
use crate::protocol::alice::AliceState::XmrLockTransferProofSent;
|
||||||
use crate::{bitcoin, database, monero};
|
use crate::{bitcoin, database, monero};
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use rand::{CryptoRng, RngCore};
|
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
trait Rng: RngCore + CryptoRng + Send {}
|
|
||||||
|
|
||||||
impl<T> Rng for T where T: RngCore + CryptoRng + Send {}
|
|
||||||
|
|
||||||
pub fn is_complete(state: &AliceState) -> bool {
|
|
||||||
matches!(
|
|
||||||
state,
|
|
||||||
AliceState::XmrRefunded
|
|
||||||
| AliceState::BtcRedeemed
|
|
||||||
| AliceState::BtcPunished
|
|
||||||
| AliceState::SafelyAborted
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run(swap: alice::Swap) -> Result<AliceState> {
|
pub async fn run(swap: alice::Swap) -> Result<AliceState> {
|
||||||
run_until(swap, is_complete).await
|
run_until(swap, |_| false).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(name = "swap", skip(swap,is_target_state), fields(id = %swap.swap_id))]
|
#[tracing::instrument(name = "swap", skip(swap,exit_early), fields(id = %swap.swap_id))]
|
||||||
pub async fn run_until(
|
pub async fn run_until(
|
||||||
mut swap: alice::Swap,
|
mut swap: alice::Swap,
|
||||||
is_target_state: fn(&AliceState) -> bool,
|
exit_early: fn(&AliceState) -> bool,
|
||||||
) -> Result<AliceState> {
|
) -> Result<AliceState> {
|
||||||
let mut current_state = swap.state;
|
let mut current_state = swap.state;
|
||||||
|
|
||||||
while !is_target_state(¤t_state) {
|
while !is_complete(¤t_state) && !exit_early(¤t_state) {
|
||||||
current_state = next_state(
|
current_state = next_state(
|
||||||
current_state,
|
current_state,
|
||||||
&mut swap.event_loop_handle,
|
&mut swap.event_loop_handle,
|
||||||
@ -89,6 +75,8 @@ async fn next_state(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
AliceState::BtcLocked { state3 } => {
|
AliceState::BtcLocked { state3 } => {
|
||||||
|
match state3.expired_timelocks(bitcoin_wallet).await? {
|
||||||
|
ExpiredTimelocks::None => {
|
||||||
// Record the current monero wallet block height so we don't have to scan from
|
// Record the current monero wallet block height so we don't have to scan from
|
||||||
// block 0 for scenarios where we create a refund wallet.
|
// block 0 for scenarios where we create a refund wallet.
|
||||||
let monero_wallet_restore_blockheight = monero_wallet.block_height().await?;
|
let monero_wallet_restore_blockheight = monero_wallet.block_height().await?;
|
||||||
@ -97,30 +85,64 @@ async fn next_state(
|
|||||||
.transfer(state3.lock_xmr_transfer_request())
|
.transfer(state3.lock_xmr_transfer_request())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
AliceState::XmrLockTransactionSent {
|
||||||
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => AliceState::SafelyAborted,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AliceState::XmrLockTransactionSent {
|
||||||
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
|
} => match state3.expired_timelocks(bitcoin_wallet).await? {
|
||||||
|
ExpiredTimelocks::None => {
|
||||||
monero_wallet
|
monero_wallet
|
||||||
.watch_for_transfer(state3.lock_xmr_watch_request(transfer_proof.clone(), 1))
|
.watch_for_transfer(state3.lock_xmr_watch_request(transfer_proof.clone(), 1))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// TODO: Waiting for XMR confirmations should be done in a separate
|
AliceState::XmrLocked {
|
||||||
// state! We have to record that Alice has already sent the transaction.
|
monero_wallet_restore_blockheight,
|
||||||
// Otherwise Alice might publish the lock tx twice!
|
transfer_proof,
|
||||||
|
state3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => AliceState::CancelTimelockExpired {
|
||||||
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
AliceState::XmrLocked {
|
||||||
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
|
} => match state3.expired_timelocks(bitcoin_wallet).await? {
|
||||||
|
ExpiredTimelocks::None => {
|
||||||
event_loop_handle
|
event_loop_handle
|
||||||
.send_transfer_proof(transfer_proof.clone())
|
.send_transfer_proof(transfer_proof.clone())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
monero_wallet
|
XmrLockTransferProofSent {
|
||||||
.watch_for_transfer(state3.lock_xmr_watch_request(transfer_proof, 10))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
AliceState::XmrLocked {
|
|
||||||
state3,
|
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AliceState::XmrLocked {
|
_ => AliceState::CancelTimelockExpired {
|
||||||
state3,
|
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AliceState::XmrLockTransferProofSent {
|
||||||
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
} => {
|
} => {
|
||||||
let tx_lock_status = bitcoin_wallet.subscribe_to(state3.tx_lock.clone()).await;
|
let tx_lock_status = bitcoin_wallet.subscribe_to(state3.tx_lock.clone()).await;
|
||||||
|
|
||||||
@ -129,31 +151,35 @@ async fn next_state(
|
|||||||
select! {
|
select! {
|
||||||
_ = tx_lock_status.wait_until_confirmed_with(state3.cancel_timelock) => {
|
_ = tx_lock_status.wait_until_confirmed_with(state3.cancel_timelock) => {
|
||||||
AliceState::CancelTimelockExpired {
|
AliceState::CancelTimelockExpired {
|
||||||
state3,
|
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
enc_sig = event_loop_handle.recv_encrypted_signature() => {
|
enc_sig = event_loop_handle.recv_encrypted_signature() => {
|
||||||
tracing::info!("Received encrypted signature");
|
tracing::info!("Received encrypted signature");
|
||||||
|
|
||||||
AliceState::EncSigLearned {
|
AliceState::EncSigLearned {
|
||||||
state3,
|
|
||||||
encrypted_signature: Box::new(enc_sig?),
|
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
encrypted_signature: Box::new(enc_sig?),
|
||||||
|
state3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => AliceState::CancelTimelockExpired {
|
_ => AliceState::CancelTimelockExpired {
|
||||||
state3,
|
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AliceState::EncSigLearned {
|
AliceState::EncSigLearned {
|
||||||
state3,
|
|
||||||
encrypted_signature,
|
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
encrypted_signature,
|
||||||
|
state3,
|
||||||
} => match state3.expired_timelocks(bitcoin_wallet).await? {
|
} => match state3.expired_timelocks(bitcoin_wallet).await? {
|
||||||
ExpiredTimelocks::None => {
|
ExpiredTimelocks::None => {
|
||||||
let tx_lock_status = bitcoin_wallet.subscribe_to(state3.tx_lock.clone()).await;
|
let tx_lock_status = bitcoin_wallet.subscribe_to(state3.tx_lock.clone()).await;
|
||||||
@ -172,8 +198,9 @@ async fn next_state(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
AliceState::CancelTimelockExpired {
|
AliceState::CancelTimelockExpired {
|
||||||
state3,
|
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -184,20 +211,23 @@ async fn next_state(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
AliceState::CancelTimelockExpired {
|
AliceState::CancelTimelockExpired {
|
||||||
state3,
|
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => AliceState::CancelTimelockExpired {
|
_ => AliceState::CancelTimelockExpired {
|
||||||
state3,
|
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
AliceState::CancelTimelockExpired {
|
AliceState::CancelTimelockExpired {
|
||||||
state3,
|
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
} => {
|
} => {
|
||||||
let transaction = state3.signed_cancel_transaction()?;
|
let transaction = state3.signed_cancel_transaction()?;
|
||||||
|
|
||||||
@ -219,13 +249,15 @@ async fn next_state(
|
|||||||
}
|
}
|
||||||
|
|
||||||
AliceState::BtcCancelled {
|
AliceState::BtcCancelled {
|
||||||
state3,
|
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AliceState::BtcCancelled {
|
AliceState::BtcCancelled {
|
||||||
state3,
|
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
} => {
|
} => {
|
||||||
let tx_refund_status = bitcoin_wallet.subscribe_to(state3.tx_refund()).await;
|
let tx_refund_status = bitcoin_wallet.subscribe_to(state3.tx_refund()).await;
|
||||||
let tx_cancel_status = bitcoin_wallet.subscribe_to(state3.tx_cancel()).await;
|
let tx_cancel_status = bitcoin_wallet.subscribe_to(state3.tx_cancel()).await;
|
||||||
@ -238,26 +270,35 @@ async fn next_state(
|
|||||||
let spend_key = state3.extract_monero_private_key(published_refund_tx)?;
|
let spend_key = state3.extract_monero_private_key(published_refund_tx)?;
|
||||||
|
|
||||||
AliceState::BtcRefunded {
|
AliceState::BtcRefunded {
|
||||||
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
spend_key,
|
spend_key,
|
||||||
state3,
|
state3,
|
||||||
monero_wallet_restore_blockheight,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = tx_cancel_status.wait_until_confirmed_with(state3.punish_timelock) => {
|
_ = tx_cancel_status.wait_until_confirmed_with(state3.punish_timelock) => {
|
||||||
AliceState::BtcPunishable {
|
AliceState::BtcPunishable {
|
||||||
state3,
|
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AliceState::BtcRefunded {
|
AliceState::BtcRefunded {
|
||||||
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
spend_key,
|
spend_key,
|
||||||
state3,
|
state3,
|
||||||
monero_wallet_restore_blockheight,
|
|
||||||
} => {
|
} => {
|
||||||
let view_key = state3.v;
|
let view_key = state3.v;
|
||||||
|
|
||||||
|
// Ensure that the XMR to be refunded are spendable by awaiting 10 confirmations
|
||||||
|
// on the lock transaction
|
||||||
|
monero_wallet
|
||||||
|
.watch_for_transfer(state3.lock_xmr_watch_request(transfer_proof, 10))
|
||||||
|
.await?;
|
||||||
|
|
||||||
monero_wallet
|
monero_wallet
|
||||||
.create_from(spend_key, view_key, monero_wallet_restore_blockheight)
|
.create_from(spend_key, view_key, monero_wallet_restore_blockheight)
|
||||||
.await?;
|
.await?;
|
||||||
@ -265,8 +306,9 @@ async fn next_state(
|
|||||||
AliceState::XmrRefunded
|
AliceState::XmrRefunded
|
||||||
}
|
}
|
||||||
AliceState::BtcPunishable {
|
AliceState::BtcPunishable {
|
||||||
state3,
|
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
|
state3,
|
||||||
} => {
|
} => {
|
||||||
let signed_tx_punish = state3.signed_punish_transaction()?;
|
let signed_tx_punish = state3.signed_punish_transaction()?;
|
||||||
|
|
||||||
@ -301,9 +343,10 @@ async fn next_state(
|
|||||||
let spend_key = state3.extract_monero_private_key(published_refund_tx)?;
|
let spend_key = state3.extract_monero_private_key(published_refund_tx)?;
|
||||||
|
|
||||||
AliceState::BtcRefunded {
|
AliceState::BtcRefunded {
|
||||||
|
monero_wallet_restore_blockheight,
|
||||||
|
transfer_proof,
|
||||||
spend_key,
|
spend_key,
|
||||||
state3,
|
state3,
|
||||||
monero_wallet_restore_blockheight,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -314,3 +357,13 @@ async fn next_state(
|
|||||||
AliceState::SafelyAborted => AliceState::SafelyAborted,
|
AliceState::SafelyAborted => AliceState::SafelyAborted,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_complete(state: &AliceState) -> bool {
|
||||||
|
matches!(
|
||||||
|
state,
|
||||||
|
AliceState::XmrRefunded
|
||||||
|
| AliceState::BtcRedeemed
|
||||||
|
| AliceState::BtcPunished
|
||||||
|
| AliceState::SafelyAborted
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -26,7 +26,14 @@ pub async fn cancel(
|
|||||||
BobState::XmrLocked(state4) => state4.cancel(),
|
BobState::XmrLocked(state4) => state4.cancel(),
|
||||||
BobState::EncSigSent(state4) => state4.cancel(),
|
BobState::EncSigSent(state4) => state4.cancel(),
|
||||||
BobState::CancelTimelockExpired(state6) => state6,
|
BobState::CancelTimelockExpired(state6) => state6,
|
||||||
_ => bail!(
|
BobState::Started { .. }
|
||||||
|
| BobState::ExecutionSetupDone(_)
|
||||||
|
| BobState::BtcRedeemed(_)
|
||||||
|
| BobState::BtcCancelled(_)
|
||||||
|
| BobState::BtcRefunded(_)
|
||||||
|
| BobState::XmrRedeemed { .. }
|
||||||
|
| BobState::BtcPunished { .. }
|
||||||
|
| BobState::SafelyAborted => bail!(
|
||||||
"Cannot cancel swap {} because it is in state {} which is not refundable.",
|
"Cannot cancel swap {} because it is in state {} which is not refundable.",
|
||||||
swap_id,
|
swap_id,
|
||||||
state
|
state
|
||||||
|
@ -24,7 +24,13 @@ pub async fn refund(
|
|||||||
BobState::EncSigSent(state4) => state4.cancel(),
|
BobState::EncSigSent(state4) => state4.cancel(),
|
||||||
BobState::CancelTimelockExpired(state6) => state6,
|
BobState::CancelTimelockExpired(state6) => state6,
|
||||||
BobState::BtcCancelled(state6) => state6,
|
BobState::BtcCancelled(state6) => state6,
|
||||||
_ => bail!(
|
BobState::Started { .. }
|
||||||
|
| BobState::ExecutionSetupDone(_)
|
||||||
|
| BobState::BtcRedeemed(_)
|
||||||
|
| BobState::BtcRefunded(_)
|
||||||
|
| BobState::XmrRedeemed { .. }
|
||||||
|
| BobState::BtcPunished { .. }
|
||||||
|
| BobState::SafelyAborted => bail!(
|
||||||
"Cannot refund swap {} because it is in state {} which is not refundable.",
|
"Cannot refund swap {} because it is in state {} which is not refundable.",
|
||||||
swap_id,
|
swap_id,
|
||||||
state
|
state
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
pub mod harness;
|
||||||
|
|
||||||
|
use harness::alice_run_until::is_xmr_lock_transaction_sent;
|
||||||
|
use harness::bob_run_until::is_btc_locked;
|
||||||
|
use harness::FastPunishConfig;
|
||||||
|
use swap::protocol::alice::AliceState;
|
||||||
|
use swap::protocol::bob::BobState;
|
||||||
|
use swap::protocol::{alice, bob};
|
||||||
|
|
||||||
|
/// 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_after_restart_if_punish_timelock_expired() {
|
||||||
|
harness::setup_test(FastPunishConfig, |mut ctx| async move {
|
||||||
|
let (bob_swap, bob_join_handle) = ctx.bob_swap().await;
|
||||||
|
let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked));
|
||||||
|
|
||||||
|
let alice_swap = ctx.alice_next_swap().await;
|
||||||
|
let alice_bitcoin_wallet = alice_swap.bitcoin_wallet.clone();
|
||||||
|
|
||||||
|
let alice_swap = tokio::spawn(alice::run_until(alice_swap, is_xmr_lock_transaction_sent));
|
||||||
|
|
||||||
|
let bob_state = bob_swap.await??;
|
||||||
|
assert!(matches!(bob_state, BobState::BtcLocked { .. }));
|
||||||
|
|
||||||
|
let alice_state = alice_swap.await??;
|
||||||
|
|
||||||
|
// Ensure punish timelock is expired
|
||||||
|
if let AliceState::XmrLockTransactionSent { state3, .. } = alice_state {
|
||||||
|
alice_bitcoin_wallet
|
||||||
|
.subscribe_to(state3.tx_lock)
|
||||||
|
.await
|
||||||
|
.wait_until_confirmed_with(state3.punish_timelock)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
panic!(
|
||||||
|
"\
|
||||||
|
Alice in unexpected state {}",
|
||||||
|
alice_state
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.restart_alice().await;
|
||||||
|
let alice_swap = ctx.alice_next_swap().await;
|
||||||
|
let alice_swap = tokio::spawn(alice::run(alice_swap));
|
||||||
|
|
||||||
|
let alice_state = alice_swap.await??;
|
||||||
|
ctx.assert_alice_punished(alice_state).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?;
|
||||||
|
|
||||||
|
ctx.assert_bob_punished(bob_state).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
38
swap/tests/alice_refunds_after_restart_bob_refunded.rs
Normal file
38
swap/tests/alice_refunds_after_restart_bob_refunded.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
pub mod harness;
|
||||||
|
|
||||||
|
use harness::alice_run_until::is_xmr_lock_transaction_sent;
|
||||||
|
use harness::FastCancelConfig;
|
||||||
|
use swap::protocol::alice::AliceState;
|
||||||
|
use swap::protocol::{alice, bob};
|
||||||
|
|
||||||
|
/// Bob locks Btc and Alice locks Xmr. Alice does not act so Bob refunds.
|
||||||
|
/// Eventually Alice comes back online and refunds as well.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn alice_refunds_after_restart_if_bob_already_refunded() {
|
||||||
|
harness::setup_test(FastCancelConfig, |mut ctx| async move {
|
||||||
|
let (bob_swap, _) = ctx.bob_swap().await;
|
||||||
|
let bob_swap = tokio::spawn(bob::run(bob_swap));
|
||||||
|
|
||||||
|
let alice_swap = ctx.alice_next_swap().await;
|
||||||
|
let alice_swap = tokio::spawn(alice::run_until(alice_swap, is_xmr_lock_transaction_sent));
|
||||||
|
|
||||||
|
let bob_state = bob_swap.await??;
|
||||||
|
ctx.assert_bob_refunded(bob_state).await;
|
||||||
|
|
||||||
|
let alice_state = alice_swap.await??;
|
||||||
|
assert!(matches!(
|
||||||
|
alice_state,
|
||||||
|
AliceState::XmrLockTransactionSent { .. }
|
||||||
|
));
|
||||||
|
|
||||||
|
ctx.restart_alice().await;
|
||||||
|
let alice_swap = ctx.alice_next_swap().await;
|
||||||
|
let alice_swap = tokio::spawn(alice::run(alice_swap));
|
||||||
|
|
||||||
|
let alice_state = alice_swap.await??;
|
||||||
|
ctx.assert_alice_refunded(alice_state).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
@ -19,7 +19,7 @@ async fn given_bob_manually_refunds_after_btc_locked_bob_refunds() {
|
|||||||
|
|
||||||
let (bob_swap, bob_join_handle) = 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;
|
||||||
|
|
||||||
// Ensure Bob's timelock is expired
|
// Ensure cancel timelock is expired
|
||||||
if let BobState::BtcLocked(state3) = bob_swap.state.clone() {
|
if let BobState::BtcLocked(state3) = bob_swap.state.clone() {
|
||||||
bob_swap
|
bob_swap
|
||||||
.bitcoin_wallet
|
.bitcoin_wallet
|
||||||
|
40
swap/tests/happy_path_restart_alice_after_xmr_locked.rs
Normal file
40
swap/tests/happy_path_restart_alice_after_xmr_locked.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
pub mod harness;
|
||||||
|
|
||||||
|
use harness::alice_run_until::is_xmr_lock_transaction_sent;
|
||||||
|
use harness::SlowCancelConfig;
|
||||||
|
use swap::protocol::alice::AliceState;
|
||||||
|
use swap::protocol::{alice, bob};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn given_alice_restarts_after_xmr_is_locked_resume_swap() {
|
||||||
|
harness::setup_test(SlowCancelConfig, |mut ctx| async move {
|
||||||
|
let (bob_swap, _) = ctx.bob_swap().await;
|
||||||
|
let bob_swap = tokio::spawn(bob::run(bob_swap));
|
||||||
|
|
||||||
|
let alice_swap = ctx.alice_next_swap().await;
|
||||||
|
let alice_swap = tokio::spawn(alice::run_until(alice_swap, is_xmr_lock_transaction_sent));
|
||||||
|
|
||||||
|
let alice_state = alice_swap.await??;
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
alice_state,
|
||||||
|
AliceState::XmrLockTransactionSent { .. }
|
||||||
|
));
|
||||||
|
|
||||||
|
ctx.restart_alice().await;
|
||||||
|
let alice_swap = ctx.alice_next_swap().await;
|
||||||
|
assert!(matches!(
|
||||||
|
alice_swap.state,
|
||||||
|
AliceState::XmrLockTransactionSent { .. }
|
||||||
|
));
|
||||||
|
|
||||||
|
let alice_state = alice::run(alice_swap).await?;
|
||||||
|
ctx.assert_alice_redeemed(alice_state).await;
|
||||||
|
|
||||||
|
let bob_state = bob_swap.await??;
|
||||||
|
ctx.assert_bob_redeemed(bob_state).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
@ -29,8 +29,9 @@ use tempfile::tempdir;
|
|||||||
use testcontainers::clients::Cli;
|
use testcontainers::clients::Cli;
|
||||||
use testcontainers::{Container, Docker, RunArgs};
|
use testcontainers::{Container, Docker, RunArgs};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
use tokio::sync::mpsc::Receiver;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use tokio::time::interval;
|
use tokio::time::{interval, timeout};
|
||||||
use tracing_subscriber::util::SubscriberInitExt;
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@ -79,24 +80,40 @@ impl BobParams {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BobEventLoopJoinHandle(JoinHandle<()>);
|
pub struct BobApplicationHandle(JoinHandle<()>);
|
||||||
|
|
||||||
impl BobEventLoopJoinHandle {
|
impl BobApplicationHandle {
|
||||||
pub fn abort(&self) {
|
pub fn abort(&self) {
|
||||||
self.0.abort()
|
self.0.abort()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AliceEventLoopJoinHandle(JoinHandle<()>);
|
pub struct AliceApplicationHandle {
|
||||||
|
handle: JoinHandle<()>,
|
||||||
|
peer_id: PeerId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AliceApplicationHandle {
|
||||||
|
pub fn abort(&self) {
|
||||||
|
self.handle.abort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TestContext {
|
pub struct TestContext {
|
||||||
|
env_config: Config,
|
||||||
|
|
||||||
btc_amount: bitcoin::Amount,
|
btc_amount: bitcoin::Amount,
|
||||||
xmr_amount: monero::Amount,
|
xmr_amount: monero::Amount,
|
||||||
|
|
||||||
|
alice_seed: Seed,
|
||||||
|
alice_db_path: PathBuf,
|
||||||
|
alice_listen_address: Multiaddr,
|
||||||
|
|
||||||
alice_starting_balances: StartingBalances,
|
alice_starting_balances: StartingBalances,
|
||||||
alice_bitcoin_wallet: Arc<bitcoin::Wallet>,
|
alice_bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||||
alice_monero_wallet: Arc<monero::Wallet>,
|
alice_monero_wallet: Arc<monero::Wallet>,
|
||||||
alice_swap_handle: mpsc::Receiver<Swap>,
|
alice_swap_handle: mpsc::Receiver<Swap>,
|
||||||
|
alice_handle: AliceApplicationHandle,
|
||||||
|
|
||||||
bob_params: BobParams,
|
bob_params: BobParams,
|
||||||
bob_starting_balances: StartingBalances,
|
bob_starting_balances: StartingBalances,
|
||||||
@ -105,11 +122,30 @@ pub struct TestContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TestContext {
|
impl TestContext {
|
||||||
pub async fn alice_next_swap(&mut self) -> alice::Swap {
|
pub async fn restart_alice(&mut self) {
|
||||||
self.alice_swap_handle.recv().await.unwrap()
|
self.alice_handle.abort();
|
||||||
|
|
||||||
|
let (alice_handle, alice_swap_handle) = start_alice(
|
||||||
|
&self.alice_seed,
|
||||||
|
self.alice_db_path.clone(),
|
||||||
|
self.alice_listen_address.clone(),
|
||||||
|
self.env_config,
|
||||||
|
self.alice_bitcoin_wallet.clone(),
|
||||||
|
self.alice_monero_wallet.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.alice_handle = alice_handle;
|
||||||
|
self.alice_swap_handle = alice_swap_handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn bob_swap(&mut self) -> (bob::Swap, BobEventLoopJoinHandle) {
|
pub async fn alice_next_swap(&mut self) -> alice::Swap {
|
||||||
|
timeout(Duration::from_secs(10), self.alice_swap_handle.recv())
|
||||||
|
.await
|
||||||
|
.expect("No Alice swap within 10 seconds, aborting because this test is waiting for a swap forever...")
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn bob_swap(&mut self) -> (bob::Swap, BobApplicationHandle) {
|
||||||
let (event_loop, event_loop_handle) = self.bob_params.new_eventloop().unwrap();
|
let (event_loop, event_loop_handle) = self.bob_params.new_eventloop().unwrap();
|
||||||
|
|
||||||
let swap = self
|
let swap = self
|
||||||
@ -123,13 +159,13 @@ impl TestContext {
|
|||||||
|
|
||||||
let join_handle = tokio::spawn(event_loop.run());
|
let join_handle = tokio::spawn(event_loop.run());
|
||||||
|
|
||||||
(swap, BobEventLoopJoinHandle(join_handle))
|
(swap, BobApplicationHandle(join_handle))
|
||||||
}
|
}
|
||||||
|
|
||||||
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: BobApplicationHandle,
|
||||||
) -> (bob::Swap, BobEventLoopJoinHandle) {
|
) -> (bob::Swap, BobApplicationHandle) {
|
||||||
join_handle.abort();
|
join_handle.abort();
|
||||||
|
|
||||||
let (event_loop, event_loop_handle) = self.bob_params.new_eventloop().unwrap();
|
let (event_loop, event_loop_handle) = self.bob_params.new_eventloop().unwrap();
|
||||||
@ -144,7 +180,7 @@ impl TestContext {
|
|||||||
|
|
||||||
let join_handle = tokio::spawn(event_loop.run());
|
let join_handle = tokio::spawn(event_loop.run());
|
||||||
|
|
||||||
(swap, BobEventLoopJoinHandle(join_handle))
|
(swap, BobApplicationHandle(join_handle))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn assert_alice_redeemed(&mut self, state: AliceState) {
|
pub async fn assert_alice_redeemed(&mut self, state: AliceState) {
|
||||||
@ -462,20 +498,12 @@ where
|
|||||||
btc: bitcoin::Amount::ZERO,
|
btc: bitcoin::Amount::ZERO,
|
||||||
};
|
};
|
||||||
|
|
||||||
let port = get_port().expect("Failed to find a free port");
|
|
||||||
|
|
||||||
let alice_listen_address: Multiaddr = format!("/ip4/127.0.0.1/tcp/{}", port)
|
|
||||||
.parse()
|
|
||||||
.expect("failed to parse Alice's address");
|
|
||||||
|
|
||||||
let electrs_rpc_port = containers
|
let electrs_rpc_port = containers
|
||||||
.electrs
|
.electrs
|
||||||
.get_host_port(harness::electrs::RPC_PORT)
|
.get_host_port(harness::electrs::RPC_PORT)
|
||||||
.expect("Could not map electrs rpc port");
|
.expect("Could not map electrs rpc port");
|
||||||
|
|
||||||
let alice_seed = Seed::random().unwrap();
|
let alice_seed = Seed::random().unwrap();
|
||||||
let bob_seed = Seed::random().unwrap();
|
|
||||||
|
|
||||||
let (alice_bitcoin_wallet, alice_monero_wallet) = init_test_wallets(
|
let (alice_bitcoin_wallet, alice_monero_wallet) = init_test_wallets(
|
||||||
MONERO_WALLET_NAME_ALICE,
|
MONERO_WALLET_NAME_ALICE,
|
||||||
containers.bitcoind_url.clone(),
|
containers.bitcoind_url.clone(),
|
||||||
@ -483,16 +511,27 @@ where
|
|||||||
alice_starting_balances.clone(),
|
alice_starting_balances.clone(),
|
||||||
tempdir().unwrap().path(),
|
tempdir().unwrap().path(),
|
||||||
electrs_rpc_port,
|
electrs_rpc_port,
|
||||||
alice_seed,
|
&alice_seed,
|
||||||
env_config,
|
env_config,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let db_path = tempdir().unwrap();
|
let alice_listen_port = get_port().expect("Failed to find a free port");
|
||||||
let alice_db = Arc::new(Database::open(db_path.path()).unwrap());
|
let alice_listen_address: Multiaddr = format!("/ip4/127.0.0.1/tcp/{}", alice_listen_port)
|
||||||
|
.parse()
|
||||||
|
.expect("failed to parse Alice's address");
|
||||||
|
|
||||||
let alice_seed = Seed::random().unwrap();
|
let alice_db_path = tempdir().unwrap().into_path();
|
||||||
|
let (alice_handle, alice_swap_handle) = start_alice(
|
||||||
|
&alice_seed,
|
||||||
|
alice_db_path.clone(),
|
||||||
|
alice_listen_address.clone(),
|
||||||
|
env_config,
|
||||||
|
alice_bitcoin_wallet.clone(),
|
||||||
|
alice_monero_wallet.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let bob_seed = Seed::random().unwrap();
|
||||||
let bob_starting_balances = StartingBalances {
|
let bob_starting_balances = StartingBalances {
|
||||||
xmr: monero::Amount::ZERO,
|
xmr: monero::Amount::ZERO,
|
||||||
btc: btc_amount * 10,
|
btc: btc_amount * 10,
|
||||||
@ -505,47 +544,34 @@ where
|
|||||||
bob_starting_balances.clone(),
|
bob_starting_balances.clone(),
|
||||||
tempdir().unwrap().path(),
|
tempdir().unwrap().path(),
|
||||||
electrs_rpc_port,
|
electrs_rpc_port,
|
||||||
bob_seed,
|
&bob_seed,
|
||||||
env_config,
|
env_config,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let mut alice_swarm = swarm::new::<alice::Behaviour>(&alice_seed).unwrap();
|
|
||||||
Swarm::listen_on(&mut alice_swarm, alice_listen_address.clone()).unwrap();
|
|
||||||
|
|
||||||
let (alice_event_loop, alice_swap_handle) = alice::EventLoop::new(
|
|
||||||
alice_swarm,
|
|
||||||
env_config,
|
|
||||||
alice_bitcoin_wallet.clone(),
|
|
||||||
alice_monero_wallet.clone(),
|
|
||||||
alice_db,
|
|
||||||
FixedRate::default(),
|
|
||||||
bitcoin::Amount::ONE_BTC,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let alice_peer_id = alice_event_loop.peer_id();
|
|
||||||
|
|
||||||
tokio::spawn(alice_event_loop.run());
|
|
||||||
|
|
||||||
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(),
|
||||||
swap_id: Uuid::new_v4(),
|
swap_id: Uuid::new_v4(),
|
||||||
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_listen_address,
|
alice_address: alice_listen_address.clone(),
|
||||||
alice_peer_id,
|
alice_peer_id: alice_handle.peer_id,
|
||||||
env_config,
|
env_config,
|
||||||
};
|
};
|
||||||
|
|
||||||
let test = TestContext {
|
let test = TestContext {
|
||||||
|
env_config,
|
||||||
btc_amount,
|
btc_amount,
|
||||||
xmr_amount,
|
xmr_amount,
|
||||||
|
alice_seed,
|
||||||
|
alice_db_path,
|
||||||
|
alice_listen_address,
|
||||||
alice_starting_balances,
|
alice_starting_balances,
|
||||||
alice_bitcoin_wallet,
|
alice_bitcoin_wallet,
|
||||||
alice_monero_wallet,
|
alice_monero_wallet,
|
||||||
alice_swap_handle,
|
alice_swap_handle,
|
||||||
|
alice_handle,
|
||||||
bob_params,
|
bob_params,
|
||||||
bob_starting_balances,
|
bob_starting_balances,
|
||||||
bob_bitcoin_wallet,
|
bob_bitcoin_wallet,
|
||||||
@ -555,6 +581,36 @@ where
|
|||||||
testfn(test).await.unwrap()
|
testfn(test).await.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn start_alice(
|
||||||
|
seed: &Seed,
|
||||||
|
db_path: PathBuf,
|
||||||
|
listen_address: Multiaddr,
|
||||||
|
env_config: Config,
|
||||||
|
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||||
|
monero_wallet: Arc<monero::Wallet>,
|
||||||
|
) -> (AliceApplicationHandle, Receiver<alice::Swap>) {
|
||||||
|
let db = Arc::new(Database::open(db_path.as_path()).unwrap());
|
||||||
|
|
||||||
|
let mut swarm = swarm::new::<alice::Behaviour>(&seed).unwrap();
|
||||||
|
Swarm::listen_on(&mut swarm, listen_address).unwrap();
|
||||||
|
|
||||||
|
let (event_loop, swap_handle) = alice::EventLoop::new(
|
||||||
|
swarm,
|
||||||
|
env_config,
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
db,
|
||||||
|
FixedRate::default(),
|
||||||
|
bitcoin::Amount::ONE_BTC,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let peer_id = event_loop.peer_id();
|
||||||
|
let handle = tokio::spawn(event_loop.run());
|
||||||
|
|
||||||
|
(AliceApplicationHandle { handle, peer_id }, swap_handle)
|
||||||
|
}
|
||||||
|
|
||||||
fn random_prefix() -> String {
|
fn random_prefix() -> String {
|
||||||
use rand::distributions::Alphanumeric;
|
use rand::distributions::Alphanumeric;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
@ -714,7 +770,7 @@ async fn init_test_wallets(
|
|||||||
starting_balances: StartingBalances,
|
starting_balances: StartingBalances,
|
||||||
datadir: &Path,
|
datadir: &Path,
|
||||||
electrum_rpc_port: u16,
|
electrum_rpc_port: u16,
|
||||||
seed: Seed,
|
seed: &Seed,
|
||||||
env_config: Config,
|
env_config: Config,
|
||||||
) -> (Arc<bitcoin::Wallet>, Arc<monero::Wallet>) {
|
) -> (Arc<bitcoin::Wallet>, Arc<monero::Wallet>) {
|
||||||
monero
|
monero
|
||||||
@ -790,8 +846,8 @@ struct Containers<'a> {
|
|||||||
pub mod alice_run_until {
|
pub mod alice_run_until {
|
||||||
use swap::protocol::alice::AliceState;
|
use swap::protocol::alice::AliceState;
|
||||||
|
|
||||||
pub fn is_xmr_locked(state: &AliceState) -> bool {
|
pub fn is_xmr_lock_transaction_sent(state: &AliceState) -> bool {
|
||||||
matches!(state, AliceState::XmrLocked { .. })
|
matches!(state, AliceState::XmrLockTransactionSent { .. })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_encsig_learned(state: &AliceState) -> bool {
|
pub fn is_encsig_learned(state: &AliceState) -> bool {
|
||||||
@ -835,7 +891,7 @@ pub struct FastCancelConfig;
|
|||||||
impl GetConfig for FastCancelConfig {
|
impl GetConfig for FastCancelConfig {
|
||||||
fn get_config() -> Config {
|
fn get_config() -> Config {
|
||||||
Config {
|
Config {
|
||||||
bitcoin_cancel_timelock: CancelTimelock::new(1),
|
bitcoin_cancel_timelock: CancelTimelock::new(10),
|
||||||
..env::Regtest::get_config()
|
..env::Regtest::get_config()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -846,8 +902,8 @@ pub struct FastPunishConfig;
|
|||||||
impl GetConfig for FastPunishConfig {
|
impl GetConfig for FastPunishConfig {
|
||||||
fn get_config() -> Config {
|
fn get_config() -> Config {
|
||||||
Config {
|
Config {
|
||||||
bitcoin_cancel_timelock: CancelTimelock::new(1),
|
bitcoin_cancel_timelock: CancelTimelock::new(10),
|
||||||
bitcoin_punish_timelock: PunishTimelock::new(1),
|
bitcoin_punish_timelock: PunishTimelock::new(10),
|
||||||
..env::Regtest::get_config()
|
..env::Regtest::get_config()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user