mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-12-24 23:19:34 -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_restart_bob_after_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_timelock_not_expired,
|
||||
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
|
||||
steps:
|
||||
|
@ -7,9 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [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
|
||||
|
||||
- A changelog file.
|
||||
- Automatic resume of unfinished swaps for the `asb` upon startup.
|
||||
Unfinished swaps from earlier versions will be skipped.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -1552,6 +1552,15 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.7"
|
||||
@ -2547,7 +2556,7 @@ checksum = "32d3ebd75ac2679c2af3a92246639f9fcc8a442ee420719cc4fe195b98dd5fa3"
|
||||
dependencies = [
|
||||
"bytes 1.0.1",
|
||||
"heck",
|
||||
"itertools",
|
||||
"itertools 0.9.0",
|
||||
"log 0.4.14",
|
||||
"multimap",
|
||||
"petgraph",
|
||||
@ -2564,7 +2573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "169a15f3008ecb5160cba7d37bcd690a7601b6d30cfb87a117d45e59d52af5d4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
"itertools 0.9.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@ -3522,6 +3531,7 @@ dependencies = [
|
||||
"futures",
|
||||
"get-port",
|
||||
"hyper 0.14.5",
|
||||
"itertools 0.10.0",
|
||||
"libp2p",
|
||||
"libp2p-async-await",
|
||||
"miniscript",
|
||||
|
@ -8,9 +8,12 @@ status = [
|
||||
"test (macos-latest)",
|
||||
"docker_tests (happy_path)",
|
||||
"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 (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)",
|
||||
"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"
|
||||
ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", features = ["libsecp_compat", "serde"] }
|
||||
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-async-await = { git = "https://github.com/comit-network/rust-libp2p-async-await" }
|
||||
miniscript = { version = "5", features = ["serde"] }
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::asb::Rate;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FixedRate(Rate);
|
||||
|
||||
impl FixedRate {
|
||||
|
@ -130,7 +130,7 @@ async fn main() -> Result<()> {
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use structopt::StructOpt;
|
||||
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::env::{Config, GetConfig};
|
||||
use swap::network::quote::BidQuote;
|
||||
@ -81,9 +81,9 @@ async fn main() -> Result<()> {
|
||||
|
||||
match args.cmd {
|
||||
Command::BuyXmr {
|
||||
connect_params:
|
||||
AliceConnectParams {
|
||||
peer_id: alice_peer_id,
|
||||
alice_peer_id,
|
||||
alice_multi_addr:
|
||||
AliceMultiaddress {
|
||||
multiaddr: alice_addr,
|
||||
},
|
||||
monero_params:
|
||||
@ -131,9 +131,12 @@ async fn main() -> Result<()> {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let swap_id = Uuid::new_v4();
|
||||
db.insert_peer_id(swap_id, alice_peer_id).await?;
|
||||
|
||||
let swap = Builder::new(
|
||||
db,
|
||||
Uuid::new_v4(),
|
||||
swap_id,
|
||||
bitcoin_wallet.clone(),
|
||||
Arc::new(monero_wallet),
|
||||
env_config,
|
||||
@ -158,7 +161,7 @@ async fn main() -> Result<()> {
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
@ -167,9 +170,8 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
Command::Resume {
|
||||
swap_id,
|
||||
connect_params:
|
||||
AliceConnectParams {
|
||||
peer_id: alice_peer_id,
|
||||
alice_multi_addr:
|
||||
AliceMultiaddress {
|
||||
multiaddr: alice_addr,
|
||||
},
|
||||
monero_params:
|
||||
@ -189,6 +191,7 @@ async fn main() -> Result<()> {
|
||||
init_monero_wallet(data_dir, monero_daemon_host, env_config).await?;
|
||||
let bitcoin_wallet = Arc::new(bitcoin_wallet);
|
||||
|
||||
let alice_peer_id = db.get_peer_id(swap_id)?;
|
||||
let mut swarm = swarm::new::<Behaviour>(&seed)?;
|
||||
swarm.add_address(alice_peer_id, alice_addr);
|
||||
|
||||
|
@ -37,8 +37,15 @@ pub struct Arguments {
|
||||
pub enum Command {
|
||||
/// Start a XMR for BTC swap
|
||||
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)]
|
||||
connect_params: AliceConnectParams,
|
||||
alice_multi_addr: AliceMultiaddress,
|
||||
|
||||
#[structopt(long = "electrum-rpc",
|
||||
help = "Provide the Bitcoin Electrum RPC URL",
|
||||
@ -60,7 +67,7 @@ pub enum Command {
|
||||
swap_id: Uuid,
|
||||
|
||||
#[structopt(flatten)]
|
||||
connect_params: AliceConnectParams,
|
||||
alice_multi_addr: AliceMultiaddress,
|
||||
|
||||
#[structopt(long = "electrum-rpc",
|
||||
help = "Provide the Bitcoin Electrum RPC URL",
|
||||
@ -108,14 +115,7 @@ pub enum Command {
|
||||
}
|
||||
|
||||
#[derive(structopt::StructOpt, Debug)]
|
||||
pub struct AliceConnectParams {
|
||||
#[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,
|
||||
|
||||
pub struct AliceMultiaddress {
|
||||
#[structopt(
|
||||
long = "seller-addr",
|
||||
default_value = DEFAULT_ALICE_MULTIADDR,
|
||||
|
@ -2,10 +2,13 @@ pub use alice::Alice;
|
||||
pub use bob::Bob;
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use itertools::Itertools;
|
||||
use libp2p::PeerId;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use uuid::Uuid;
|
||||
|
||||
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 {
|
||||
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> {
|
||||
match self {
|
||||
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 {
|
||||
pub fn open(path: &Path) -> Result<Self> {
|
||||
@ -56,22 +77,51 @@ impl Database {
|
||||
let db =
|
||||
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<()> {
|
||||
let key = serialize(&swap_id)?;
|
||||
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))
|
||||
.context("Could not write in the DB")?
|
||||
.context("Stored swap somehow changed, aborting saving")?;
|
||||
|
||||
// TODO: see if this can be done through sled config
|
||||
self.0
|
||||
self.swaps
|
||||
.flush_async()
|
||||
.await
|
||||
.map(|_| ())
|
||||
@ -82,7 +132,7 @@ impl Database {
|
||||
let key = serialize(&swap_id)?;
|
||||
|
||||
let encoded = self
|
||||
.0
|
||||
.swaps
|
||||
.get(&key)?
|
||||
.ok_or_else(|| anyhow!("Swap with id {} not found in database", swap_id))?;
|
||||
|
||||
@ -90,22 +140,42 @@ impl Database {
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
pub fn all(&self) -> Result<Vec<(Uuid, Swap)>> {
|
||||
self.0
|
||||
.iter()
|
||||
.map(|item| match item {
|
||||
Ok((key, value)) => {
|
||||
let swap_id = deserialize::<Uuid>(&key);
|
||||
let swap = deserialize::<Swap>(&value).context("Failed to deserialize swap");
|
||||
pub fn all_alice(&self) -> Result<Vec<(Uuid, Alice)>> {
|
||||
self.all_alice_iter().collect()
|
||||
}
|
||||
|
||||
match (swap_id, swap) {
|
||||
(Ok(swap_id), Ok(swap)) => Ok((swap_id, swap)),
|
||||
(Ok(_), Err(err)) => Err(err),
|
||||
_ => bail!("Failed to deserialize swap"),
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err).context("Failed to retrieve swap from DB"),
|
||||
})
|
||||
fn all_alice_iter(&self) -> impl Iterator<Item = Result<(Uuid, Alice)>> {
|
||||
self.all_swaps_iter().map(|item| {
|
||||
let (swap_id, swap) = item?;
|
||||
Ok((swap_id, swap.try_into_alice()?))
|
||||
})
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
@ -187,26 +257,106 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn can_fetch_all_keys() {
|
||||
async fn all_swaps_as_alice() {
|
||||
let db_dir = tempfile::tempdir().unwrap();
|
||||
let db = Database::open(db_dir.path()).unwrap();
|
||||
|
||||
let state_1 = Swap::Alice(Alice::Done(AliceEndState::BtcPunished));
|
||||
let swap_id_1 = Uuid::new_v4();
|
||||
db.insert_latest_state(swap_id_1, state_1.clone())
|
||||
let alice_state = Alice::Done(AliceEndState::BtcPunished);
|
||||
let alice_swap = Swap::Alice(alice_state.clone());
|
||||
let alice_swap_id = Uuid::new_v4();
|
||||
db.insert_latest_state(alice_swap_id, alice_swap)
|
||||
.await
|
||||
.expect("Failed to save second state");
|
||||
.expect("Failed to save alice state 1");
|
||||
|
||||
let state_2 = Swap::Bob(Bob::Done(BobEndState::SafelyAborted));
|
||||
let swap_id_2 = Uuid::new_v4();
|
||||
db.insert_latest_state(swap_id_2, state_2.clone())
|
||||
let alice_swaps = db.all_alice().unwrap();
|
||||
assert_eq!(alice_swaps.len(), 1);
|
||||
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
|
||||
.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!(swaps.contains(&(swap_id_1, state_1)));
|
||||
assert!(swaps.contains(&(swap_id_2, state_2)));
|
||||
assert_eq!(err.downcast_ref::<NotAlice>().unwrap(), &NotAlice);
|
||||
}
|
||||
|
||||
#[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::monero;
|
||||
use crate::monero::monero_private_key;
|
||||
use crate::monero::{monero_private_key, TransferProof};
|
||||
use crate::protocol::alice;
|
||||
use crate::protocol::alice::AliceState;
|
||||
use ::bitcoin::hashes::core::fmt::Display;
|
||||
@ -18,29 +18,45 @@ pub enum Alice {
|
||||
BtcLocked {
|
||||
state3: alice::State3,
|
||||
},
|
||||
XmrLockTransactionSent {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
state3: alice::State3,
|
||||
},
|
||||
XmrLocked {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
state3: alice::State3,
|
||||
},
|
||||
XmrLockTransferProofSent {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
state3: alice::State3,
|
||||
},
|
||||
EncSigLearned {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
encrypted_signature: EncryptedSignature,
|
||||
state3: alice::State3,
|
||||
},
|
||||
CancelTimelockExpired {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
state3: alice::State3,
|
||||
},
|
||||
BtcCancelled {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
state3: alice::State3,
|
||||
},
|
||||
BtcPunishable {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
state3: alice::State3,
|
||||
},
|
||||
BtcRefunded {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
state3: alice::State3,
|
||||
#[serde(with = "monero_private_key")]
|
||||
spend_key: monero::PrivateKey,
|
||||
@ -65,54 +81,82 @@ impl From<&AliceState> for Alice {
|
||||
AliceState::BtcLocked { state3 } => Alice::BtcLocked {
|
||||
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 {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => Alice::XmrLocked {
|
||||
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(),
|
||||
},
|
||||
AliceState::EncSigLearned {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
encrypted_signature,
|
||||
} => Alice::EncSigLearned {
|
||||
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||
transfer_proof: transfer_proof.clone(),
|
||||
state3: state3.as_ref().clone(),
|
||||
encrypted_signature: *encrypted_signature.clone(),
|
||||
},
|
||||
AliceState::BtcRedeemed => Alice::Done(AliceEndState::BtcRedeemed),
|
||||
AliceState::BtcCancelled {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
..
|
||||
} => Alice::BtcCancelled {
|
||||
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||
transfer_proof: transfer_proof.clone(),
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::BtcRefunded {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
spend_key,
|
||||
state3,
|
||||
} => Alice::BtcRefunded {
|
||||
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||
transfer_proof: transfer_proof.clone(),
|
||||
spend_key: *spend_key,
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::BtcPunishable {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
..
|
||||
} => Alice::BtcPunishable {
|
||||
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||
transfer_proof: transfer_proof.clone(),
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::XmrRefunded => Alice::Done(AliceEndState::XmrRefunded),
|
||||
AliceState::CancelTimelockExpired {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => Alice::CancelTimelockExpired {
|
||||
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||
transfer_proof: transfer_proof.clone(),
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::BtcPunished => Alice::Done(AliceEndState::BtcPunished),
|
||||
@ -130,50 +174,80 @@ impl From<Alice> for AliceState {
|
||||
Alice::BtcLocked { state3 } => AliceState::BtcLocked {
|
||||
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 {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => AliceState::XmrLocked {
|
||||
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),
|
||||
},
|
||||
Alice::EncSigLearned {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: state,
|
||||
encrypted_signature,
|
||||
} => AliceState::EncSigLearned {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: Box::new(state),
|
||||
encrypted_signature: Box::new(encrypted_signature),
|
||||
},
|
||||
Alice::CancelTimelockExpired {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => AliceState::CancelTimelockExpired {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: Box::new(state3),
|
||||
},
|
||||
Alice::BtcCancelled {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => AliceState::BtcCancelled {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: Box::new(state3),
|
||||
},
|
||||
|
||||
Alice::BtcPunishable {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => AliceState::BtcPunishable {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: Box::new(state3),
|
||||
},
|
||||
Alice::BtcRefunded {
|
||||
monero_wallet_restore_blockheight,
|
||||
state3,
|
||||
transfer_proof,
|
||||
spend_key,
|
||||
state3,
|
||||
} => AliceState::BtcRefunded {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
spend_key,
|
||||
state3: Box::new(state3),
|
||||
},
|
||||
@ -192,7 +266,11 @@ impl Display for Alice {
|
||||
match self {
|
||||
Alice::Started { .. } => write!(f, "Started"),
|
||||
Alice::BtcLocked { .. } => f.write_str("Bitcoin locked"),
|
||||
Alice::XmrLockTransactionSent { .. } => f.write_str("Monero lock transaction sent"),
|
||||
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::BtcCancelled { .. } => f.write_str("Bitcoin cancel transaction published"),
|
||||
Alice::BtcPunishable { .. } => f.write_str("Bitcoin punishable"),
|
||||
|
@ -84,6 +84,43 @@ where
|
||||
// terminate forever.
|
||||
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 {
|
||||
tokio::select! {
|
||||
swarm_event = self.swarm.next_event() => {
|
||||
@ -264,8 +301,18 @@ where
|
||||
swap_id,
|
||||
};
|
||||
|
||||
if let Err(error) = self.swap_sender.send(swap).await {
|
||||
tracing::warn!(%swap_id, "Swap cannot be spawned: {}", error);
|
||||
// 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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,32 +22,48 @@ pub enum AliceState {
|
||||
BtcLocked {
|
||||
state3: Box<State3>,
|
||||
},
|
||||
XmrLockTransactionSent {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
state3: Box<State3>,
|
||||
},
|
||||
XmrLocked {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
state3: Box<State3>,
|
||||
},
|
||||
XmrLockTransferProofSent {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
state3: Box<State3>,
|
||||
},
|
||||
EncSigLearned {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
encrypted_signature: Box<bitcoin::EncryptedSignature>,
|
||||
state3: Box<State3>,
|
||||
},
|
||||
BtcRedeemed,
|
||||
BtcCancelled {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
state3: Box<State3>,
|
||||
},
|
||||
BtcRefunded {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
spend_key: monero::PrivateKey,
|
||||
state3: Box<State3>,
|
||||
},
|
||||
BtcPunishable {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
state3: Box<State3>,
|
||||
},
|
||||
XmrRefunded,
|
||||
CancelTimelockExpired {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
state3: Box<State3>,
|
||||
},
|
||||
BtcPunished,
|
||||
@ -59,7 +75,11 @@ impl fmt::Display for AliceState {
|
||||
match self {
|
||||
AliceState::Started { .. } => write!(f, "started"),
|
||||
AliceState::BtcLocked { .. } => write!(f, "btc is locked"),
|
||||
AliceState::XmrLockTransactionSent { .. } => write!(f, "xmr lock transaction sent"),
|
||||
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::BtcRedeemed => write!(f, "btc is redeemed"),
|
||||
AliceState::BtcCancelled { .. } => write!(f, "btc is cancelled"),
|
||||
|
@ -5,39 +5,25 @@ use crate::env::Config;
|
||||
use crate::protocol::alice;
|
||||
use crate::protocol::alice::event_loop::EventLoopHandle;
|
||||
use crate::protocol::alice::AliceState;
|
||||
use crate::protocol::alice::AliceState::XmrLockTransferProofSent;
|
||||
use crate::{bitcoin, database, monero};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use tokio::select;
|
||||
use tokio::time::timeout;
|
||||
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> {
|
||||
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(
|
||||
mut swap: alice::Swap,
|
||||
is_target_state: fn(&AliceState) -> bool,
|
||||
exit_early: fn(&AliceState) -> bool,
|
||||
) -> Result<AliceState> {
|
||||
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,
|
||||
&mut swap.event_loop_handle,
|
||||
@ -89,38 +75,74 @@ async fn next_state(
|
||||
}
|
||||
}
|
||||
AliceState::BtcLocked { state3 } => {
|
||||
// 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.
|
||||
let monero_wallet_restore_blockheight = monero_wallet.block_height().await?;
|
||||
match state3.expired_timelocks(bitcoin_wallet).await? {
|
||||
ExpiredTimelocks::None => {
|
||||
// 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.
|
||||
let monero_wallet_restore_blockheight = monero_wallet.block_height().await?;
|
||||
|
||||
let transfer_proof = monero_wallet
|
||||
.transfer(state3.lock_xmr_transfer_request())
|
||||
.await?;
|
||||
let transfer_proof = monero_wallet
|
||||
.transfer(state3.lock_xmr_transfer_request())
|
||||
.await?;
|
||||
|
||||
monero_wallet
|
||||
.watch_for_transfer(state3.lock_xmr_watch_request(transfer_proof.clone(), 1))
|
||||
.await?;
|
||||
|
||||
// TODO: Waiting for XMR confirmations should be done in a separate
|
||||
// state! We have to record that Alice has already sent the transaction.
|
||||
// Otherwise Alice might publish the lock tx twice!
|
||||
|
||||
event_loop_handle
|
||||
.send_transfer_proof(transfer_proof.clone())
|
||||
.await?;
|
||||
|
||||
monero_wallet
|
||||
.watch_for_transfer(state3.lock_xmr_watch_request(transfer_proof, 10))
|
||||
.await?;
|
||||
|
||||
AliceState::XmrLocked {
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
AliceState::XmrLockTransactionSent {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
}
|
||||
}
|
||||
_ => AliceState::SafelyAborted,
|
||||
}
|
||||
}
|
||||
AliceState::XmrLocked {
|
||||
state3,
|
||||
AliceState::XmrLockTransactionSent {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => match state3.expired_timelocks(bitcoin_wallet).await? {
|
||||
ExpiredTimelocks::None => {
|
||||
monero_wallet
|
||||
.watch_for_transfer(state3.lock_xmr_watch_request(transfer_proof.clone(), 1))
|
||||
.await?;
|
||||
|
||||
AliceState::XmrLocked {
|
||||
monero_wallet_restore_blockheight,
|
||||
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
|
||||
.send_transfer_proof(transfer_proof.clone())
|
||||
.await?;
|
||||
|
||||
XmrLockTransferProofSent {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
}
|
||||
}
|
||||
_ => AliceState::CancelTimelockExpired {
|
||||
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;
|
||||
|
||||
@ -128,32 +150,36 @@ async fn next_state(
|
||||
ExpiredTimelocks::None => {
|
||||
select! {
|
||||
_ = tx_lock_status.wait_until_confirmed_with(state3.cancel_timelock) => {
|
||||
AliceState::CancelTimelockExpired {
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
AliceState::CancelTimelockExpired {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
}
|
||||
}
|
||||
enc_sig = event_loop_handle.recv_encrypted_signature() => {
|
||||
tracing::info!("Received encrypted signature");
|
||||
|
||||
AliceState::EncSigLearned {
|
||||
state3,
|
||||
encrypted_signature: Box::new(enc_sig?),
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
encrypted_signature: Box::new(enc_sig?),
|
||||
state3,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => AliceState::CancelTimelockExpired {
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
},
|
||||
}
|
||||
}
|
||||
AliceState::EncSigLearned {
|
||||
state3,
|
||||
encrypted_signature,
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
encrypted_signature,
|
||||
state3,
|
||||
} => match state3.expired_timelocks(bitcoin_wallet).await? {
|
||||
ExpiredTimelocks::None => {
|
||||
let tx_lock_status = bitcoin_wallet.subscribe_to(state3.tx_lock.clone()).await;
|
||||
@ -172,8 +198,9 @@ async fn next_state(
|
||||
.await?;
|
||||
|
||||
AliceState::CancelTimelockExpired {
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -184,20 +211,23 @@ async fn next_state(
|
||||
.await?;
|
||||
|
||||
AliceState::CancelTimelockExpired {
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => AliceState::CancelTimelockExpired {
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
},
|
||||
},
|
||||
AliceState::CancelTimelockExpired {
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => {
|
||||
let transaction = state3.signed_cancel_transaction()?;
|
||||
|
||||
@ -219,13 +249,15 @@ async fn next_state(
|
||||
}
|
||||
|
||||
AliceState::BtcCancelled {
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
}
|
||||
}
|
||||
AliceState::BtcCancelled {
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => {
|
||||
let tx_refund_status = bitcoin_wallet.subscribe_to(state3.tx_refund()).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)?;
|
||||
|
||||
AliceState::BtcRefunded {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
spend_key,
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
}
|
||||
}
|
||||
_ = tx_cancel_status.wait_until_confirmed_with(state3.punish_timelock) => {
|
||||
AliceState::BtcPunishable {
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
AliceState::BtcRefunded {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
spend_key,
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
} => {
|
||||
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
|
||||
.create_from(spend_key, view_key, monero_wallet_restore_blockheight)
|
||||
.await?;
|
||||
@ -265,8 +306,9 @@ async fn next_state(
|
||||
AliceState::XmrRefunded
|
||||
}
|
||||
AliceState::BtcPunishable {
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => {
|
||||
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)?;
|
||||
|
||||
AliceState::BtcRefunded {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
spend_key,
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -314,3 +357,13 @@ async fn next_state(
|
||||
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::EncSigSent(state4) => state4.cancel(),
|
||||
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.",
|
||||
swap_id,
|
||||
state
|
||||
|
@ -24,7 +24,13 @@ pub async fn refund(
|
||||
BobState::EncSigSent(state4) => state4.cancel(),
|
||||
BobState::CancelTimelockExpired(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.",
|
||||
swap_id,
|
||||
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;
|
||||
|
||||
// Ensure Bob's timelock is expired
|
||||
// Ensure cancel timelock is expired
|
||||
if let BobState::BtcLocked(state3) = bob_swap.state.clone() {
|
||||
bob_swap
|
||||
.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::{Container, Docker, RunArgs};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time::interval;
|
||||
use tokio::time::{interval, timeout};
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use url::Url;
|
||||
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) {
|
||||
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 {
|
||||
env_config: Config,
|
||||
|
||||
btc_amount: bitcoin::Amount,
|
||||
xmr_amount: monero::Amount,
|
||||
|
||||
alice_seed: Seed,
|
||||
alice_db_path: PathBuf,
|
||||
alice_listen_address: Multiaddr,
|
||||
|
||||
alice_starting_balances: StartingBalances,
|
||||
alice_bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
alice_monero_wallet: Arc<monero::Wallet>,
|
||||
alice_swap_handle: mpsc::Receiver<Swap>,
|
||||
alice_handle: AliceApplicationHandle,
|
||||
|
||||
bob_params: BobParams,
|
||||
bob_starting_balances: StartingBalances,
|
||||
@ -105,11 +122,30 @@ pub struct TestContext {
|
||||
}
|
||||
|
||||
impl TestContext {
|
||||
pub async fn alice_next_swap(&mut self) -> alice::Swap {
|
||||
self.alice_swap_handle.recv().await.unwrap()
|
||||
pub async fn restart_alice(&mut self) {
|
||||
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 swap = self
|
||||
@ -123,13 +159,13 @@ impl TestContext {
|
||||
|
||||
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(
|
||||
&mut self,
|
||||
join_handle: BobEventLoopJoinHandle,
|
||||
) -> (bob::Swap, BobEventLoopJoinHandle) {
|
||||
join_handle: BobApplicationHandle,
|
||||
) -> (bob::Swap, BobApplicationHandle) {
|
||||
join_handle.abort();
|
||||
|
||||
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());
|
||||
|
||||
(swap, BobEventLoopJoinHandle(join_handle))
|
||||
(swap, BobApplicationHandle(join_handle))
|
||||
}
|
||||
|
||||
pub async fn assert_alice_redeemed(&mut self, state: AliceState) {
|
||||
@ -462,20 +498,12 @@ where
|
||||
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
|
||||
.electrs
|
||||
.get_host_port(harness::electrs::RPC_PORT)
|
||||
.expect("Could not map electrs rpc port");
|
||||
|
||||
let alice_seed = Seed::random().unwrap();
|
||||
let bob_seed = Seed::random().unwrap();
|
||||
|
||||
let (alice_bitcoin_wallet, alice_monero_wallet) = init_test_wallets(
|
||||
MONERO_WALLET_NAME_ALICE,
|
||||
containers.bitcoind_url.clone(),
|
||||
@ -483,16 +511,27 @@ where
|
||||
alice_starting_balances.clone(),
|
||||
tempdir().unwrap().path(),
|
||||
electrs_rpc_port,
|
||||
alice_seed,
|
||||
&alice_seed,
|
||||
env_config,
|
||||
)
|
||||
.await;
|
||||
|
||||
let db_path = tempdir().unwrap();
|
||||
let alice_db = Arc::new(Database::open(db_path.path()).unwrap());
|
||||
let alice_listen_port = get_port().expect("Failed to find a free port");
|
||||
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 {
|
||||
xmr: monero::Amount::ZERO,
|
||||
btc: btc_amount * 10,
|
||||
@ -505,47 +544,34 @@ where
|
||||
bob_starting_balances.clone(),
|
||||
tempdir().unwrap().path(),
|
||||
electrs_rpc_port,
|
||||
bob_seed,
|
||||
&bob_seed,
|
||||
env_config,
|
||||
)
|
||||
.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 {
|
||||
seed: Seed::random().unwrap(),
|
||||
db_path: tempdir().unwrap().path().to_path_buf(),
|
||||
swap_id: Uuid::new_v4(),
|
||||
bitcoin_wallet: bob_bitcoin_wallet.clone(),
|
||||
monero_wallet: bob_monero_wallet.clone(),
|
||||
alice_address: alice_listen_address,
|
||||
alice_peer_id,
|
||||
alice_address: alice_listen_address.clone(),
|
||||
alice_peer_id: alice_handle.peer_id,
|
||||
env_config,
|
||||
};
|
||||
|
||||
let test = TestContext {
|
||||
env_config,
|
||||
btc_amount,
|
||||
xmr_amount,
|
||||
alice_seed,
|
||||
alice_db_path,
|
||||
alice_listen_address,
|
||||
alice_starting_balances,
|
||||
alice_bitcoin_wallet,
|
||||
alice_monero_wallet,
|
||||
alice_swap_handle,
|
||||
alice_handle,
|
||||
bob_params,
|
||||
bob_starting_balances,
|
||||
bob_bitcoin_wallet,
|
||||
@ -555,6 +581,36 @@ where
|
||||
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 {
|
||||
use rand::distributions::Alphanumeric;
|
||||
use rand::{thread_rng, Rng};
|
||||
@ -714,7 +770,7 @@ async fn init_test_wallets(
|
||||
starting_balances: StartingBalances,
|
||||
datadir: &Path,
|
||||
electrum_rpc_port: u16,
|
||||
seed: Seed,
|
||||
seed: &Seed,
|
||||
env_config: Config,
|
||||
) -> (Arc<bitcoin::Wallet>, Arc<monero::Wallet>) {
|
||||
monero
|
||||
@ -790,8 +846,8 @@ struct Containers<'a> {
|
||||
pub mod alice_run_until {
|
||||
use swap::protocol::alice::AliceState;
|
||||
|
||||
pub fn is_xmr_locked(state: &AliceState) -> bool {
|
||||
matches!(state, AliceState::XmrLocked { .. })
|
||||
pub fn is_xmr_lock_transaction_sent(state: &AliceState) -> bool {
|
||||
matches!(state, AliceState::XmrLockTransactionSent { .. })
|
||||
}
|
||||
|
||||
pub fn is_encsig_learned(state: &AliceState) -> bool {
|
||||
@ -835,7 +891,7 @@ pub struct FastCancelConfig;
|
||||
impl GetConfig for FastCancelConfig {
|
||||
fn get_config() -> Config {
|
||||
Config {
|
||||
bitcoin_cancel_timelock: CancelTimelock::new(1),
|
||||
bitcoin_cancel_timelock: CancelTimelock::new(10),
|
||||
..env::Regtest::get_config()
|
||||
}
|
||||
}
|
||||
@ -846,8 +902,8 @@ pub struct FastPunishConfig;
|
||||
impl GetConfig for FastPunishConfig {
|
||||
fn get_config() -> Config {
|
||||
Config {
|
||||
bitcoin_cancel_timelock: CancelTimelock::new(1),
|
||||
bitcoin_punish_timelock: PunishTimelock::new(1),
|
||||
bitcoin_cancel_timelock: CancelTimelock::new(10),
|
||||
bitcoin_punish_timelock: PunishTimelock::new(10),
|
||||
..env::Regtest::get_config()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user