mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-12-26 07:59:28 -05:00
Merge #85
85: Database & Swap Resume r=da-kami a=D4nte - Introduce Database for Alice and Bob - Save states in database - Resume from database (broken for Bob, will create test + fix it in follow-up) PR - Tests when alice restarts: Both happy and refund path Co-authored-by: Franck Royer <franck@coblox.tech> Co-authored-by: rishflab <rishflab@hotmail.com> Co-authored-by: Daniel Karzel <daniel@comit.network>
This commit is contained in:
commit
6ef6fc894f
@ -17,15 +17,15 @@ use libp2p::{
|
|||||||
NetworkBehaviour, PeerId,
|
NetworkBehaviour, PeerId,
|
||||||
};
|
};
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
use xmr_btc::{alice::State0, bob};
|
use xmr_btc::bob;
|
||||||
|
|
||||||
mod amounts;
|
mod amounts;
|
||||||
pub mod event_loop;
|
pub mod event_loop;
|
||||||
mod execution;
|
|
||||||
mod message0;
|
mod message0;
|
||||||
mod message1;
|
mod message1;
|
||||||
mod message2;
|
mod message2;
|
||||||
mod message3;
|
mod message3;
|
||||||
|
mod steps;
|
||||||
pub mod swap;
|
pub mod swap;
|
||||||
|
|
||||||
pub type Swarm = libp2p::Swarm<Behaviour>;
|
pub type Swarm = libp2p::Swarm<Behaviour>;
|
||||||
@ -60,7 +60,10 @@ pub enum OutEvent {
|
|||||||
// TODO (Franck): Change this to get both amounts so parties can verify the amounts are
|
// TODO (Franck): Change this to get both amounts so parties can verify the amounts are
|
||||||
// expected early on.
|
// expected early on.
|
||||||
Request(amounts::OutEvent), // Not-uniform with Bob on purpose, ready for adding Xmr event.
|
Request(amounts::OutEvent), // Not-uniform with Bob on purpose, ready for adding Xmr event.
|
||||||
Message0(bob::Message0),
|
Message0 {
|
||||||
|
msg: bob::Message0,
|
||||||
|
channel: ResponseChannel<AliceToBob>,
|
||||||
|
},
|
||||||
Message1 {
|
Message1 {
|
||||||
msg: bob::Message1,
|
msg: bob::Message1,
|
||||||
channel: ResponseChannel<AliceToBob>,
|
channel: ResponseChannel<AliceToBob>,
|
||||||
@ -91,7 +94,7 @@ impl From<amounts::OutEvent> for OutEvent {
|
|||||||
impl From<message0::OutEvent> for OutEvent {
|
impl From<message0::OutEvent> for OutEvent {
|
||||||
fn from(event: message0::OutEvent) -> Self {
|
fn from(event: message0::OutEvent) -> Self {
|
||||||
match event {
|
match event {
|
||||||
message0::OutEvent::Msg(msg) => OutEvent::Message0(msg),
|
message0::OutEvent::Msg { channel, msg } => OutEvent::Message0 { msg, channel },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,20 +139,6 @@ pub struct Behaviour {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Behaviour {
|
impl Behaviour {
|
||||||
pub fn new(state: State0) -> Self {
|
|
||||||
let identity = Keypair::generate_ed25519();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
pt: PeerTracker::default(),
|
|
||||||
amounts: Amounts::default(),
|
|
||||||
message0: Message0::new(state),
|
|
||||||
message1: Message1::default(),
|
|
||||||
message2: Message2::default(),
|
|
||||||
message3: Message3::default(),
|
|
||||||
identity,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn identity(&self) -> Keypair {
|
pub fn identity(&self) -> Keypair {
|
||||||
self.identity.clone()
|
self.identity.clone()
|
||||||
}
|
}
|
||||||
@ -165,6 +154,16 @@ impl Behaviour {
|
|||||||
info!("Sent amounts response");
|
info!("Sent amounts response");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send Message0 to Bob in response to receiving his Message0.
|
||||||
|
pub fn send_message0(
|
||||||
|
&mut self,
|
||||||
|
channel: ResponseChannel<AliceToBob>,
|
||||||
|
msg: xmr_btc::alice::Message0,
|
||||||
|
) {
|
||||||
|
self.message0.send(channel, msg);
|
||||||
|
debug!("Sent Message0");
|
||||||
|
}
|
||||||
|
|
||||||
/// Send Message1 to Bob in response to receiving his Message1.
|
/// Send Message1 to Bob in response to receiving his Message1.
|
||||||
pub fn send_message1(
|
pub fn send_message1(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -185,3 +184,19 @@ impl Behaviour {
|
|||||||
debug!("Sent Message2");
|
debug!("Sent Message2");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Behaviour {
|
||||||
|
fn default() -> Self {
|
||||||
|
let identity = Keypair::generate_ed25519();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
pt: PeerTracker::default(),
|
||||||
|
amounts: Amounts::default(),
|
||||||
|
message0: Message0::default(),
|
||||||
|
message1: Message1::default(),
|
||||||
|
message2: Message2::default(),
|
||||||
|
message3: Message3::default(),
|
||||||
|
identity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -30,13 +30,14 @@ impl<T> Default for Channels<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventLoopHandle {
|
pub struct EventLoopHandle {
|
||||||
msg0: Receiver<bob::Message0>,
|
msg0: Receiver<(bob::Message0, ResponseChannel<AliceToBob>)>,
|
||||||
msg1: Receiver<(bob::Message1, ResponseChannel<AliceToBob>)>,
|
msg1: Receiver<(bob::Message1, ResponseChannel<AliceToBob>)>,
|
||||||
msg2: Receiver<(bob::Message2, ResponseChannel<AliceToBob>)>,
|
msg2: Receiver<(bob::Message2, ResponseChannel<AliceToBob>)>,
|
||||||
msg3: Receiver<bob::Message3>,
|
msg3: Receiver<bob::Message3>,
|
||||||
request: Receiver<crate::alice::amounts::OutEvent>,
|
request: Receiver<crate::alice::amounts::OutEvent>,
|
||||||
conn_established: Receiver<PeerId>,
|
conn_established: Receiver<PeerId>,
|
||||||
send_amounts: Sender<(ResponseChannel<AliceToBob>, SwapAmounts)>,
|
send_amounts: Sender<(ResponseChannel<AliceToBob>, SwapAmounts)>,
|
||||||
|
send_msg0: Sender<(ResponseChannel<AliceToBob>, alice::Message0)>,
|
||||||
send_msg1: Sender<(ResponseChannel<AliceToBob>, alice::Message1)>,
|
send_msg1: Sender<(ResponseChannel<AliceToBob>, alice::Message1)>,
|
||||||
send_msg2: Sender<(ResponseChannel<AliceToBob>, alice::Message2)>,
|
send_msg2: Sender<(ResponseChannel<AliceToBob>, alice::Message2)>,
|
||||||
}
|
}
|
||||||
@ -49,7 +50,7 @@ impl EventLoopHandle {
|
|||||||
.ok_or_else(|| anyhow!("Failed to receive connection established from Bob"))
|
.ok_or_else(|| anyhow!("Failed to receive connection established from Bob"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn recv_message0(&mut self) -> Result<bob::Message0> {
|
pub async fn recv_message0(&mut self) -> Result<(bob::Message0, ResponseChannel<AliceToBob>)> {
|
||||||
self.msg0
|
self.msg0
|
||||||
.recv()
|
.recv()
|
||||||
.await
|
.await
|
||||||
@ -93,6 +94,15 @@ impl EventLoopHandle {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn send_message0(
|
||||||
|
&mut self,
|
||||||
|
channel: ResponseChannel<AliceToBob>,
|
||||||
|
msg: alice::Message0,
|
||||||
|
) -> Result<()> {
|
||||||
|
let _ = self.send_msg0.send((channel, msg)).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn send_message1(
|
pub async fn send_message1(
|
||||||
&mut self,
|
&mut self,
|
||||||
channel: ResponseChannel<AliceToBob>,
|
channel: ResponseChannel<AliceToBob>,
|
||||||
@ -114,13 +124,14 @@ impl EventLoopHandle {
|
|||||||
|
|
||||||
pub struct EventLoop {
|
pub struct EventLoop {
|
||||||
swarm: libp2p::Swarm<Behaviour>,
|
swarm: libp2p::Swarm<Behaviour>,
|
||||||
msg0: Sender<bob::Message0>,
|
msg0: Sender<(bob::Message0, ResponseChannel<AliceToBob>)>,
|
||||||
msg1: Sender<(bob::Message1, ResponseChannel<AliceToBob>)>,
|
msg1: Sender<(bob::Message1, ResponseChannel<AliceToBob>)>,
|
||||||
msg2: Sender<(bob::Message2, ResponseChannel<AliceToBob>)>,
|
msg2: Sender<(bob::Message2, ResponseChannel<AliceToBob>)>,
|
||||||
msg3: Sender<bob::Message3>,
|
msg3: Sender<bob::Message3>,
|
||||||
request: Sender<crate::alice::amounts::OutEvent>,
|
request: Sender<crate::alice::amounts::OutEvent>,
|
||||||
conn_established: Sender<PeerId>,
|
conn_established: Sender<PeerId>,
|
||||||
send_amounts: Receiver<(ResponseChannel<AliceToBob>, SwapAmounts)>,
|
send_amounts: Receiver<(ResponseChannel<AliceToBob>, SwapAmounts)>,
|
||||||
|
send_msg0: Receiver<(ResponseChannel<AliceToBob>, alice::Message0)>,
|
||||||
send_msg1: Receiver<(ResponseChannel<AliceToBob>, alice::Message1)>,
|
send_msg1: Receiver<(ResponseChannel<AliceToBob>, alice::Message1)>,
|
||||||
send_msg2: Receiver<(ResponseChannel<AliceToBob>, alice::Message2)>,
|
send_msg2: Receiver<(ResponseChannel<AliceToBob>, alice::Message2)>,
|
||||||
}
|
}
|
||||||
@ -149,6 +160,7 @@ impl EventLoop {
|
|||||||
let request = Channels::new();
|
let request = Channels::new();
|
||||||
let conn_established = Channels::new();
|
let conn_established = Channels::new();
|
||||||
let send_amounts = Channels::new();
|
let send_amounts = Channels::new();
|
||||||
|
let send_msg0 = Channels::new();
|
||||||
let send_msg1 = Channels::new();
|
let send_msg1 = Channels::new();
|
||||||
let send_msg2 = Channels::new();
|
let send_msg2 = Channels::new();
|
||||||
|
|
||||||
@ -161,6 +173,7 @@ impl EventLoop {
|
|||||||
request: request.sender,
|
request: request.sender,
|
||||||
conn_established: conn_established.sender,
|
conn_established: conn_established.sender,
|
||||||
send_amounts: send_amounts.receiver,
|
send_amounts: send_amounts.receiver,
|
||||||
|
send_msg0: send_msg0.receiver,
|
||||||
send_msg1: send_msg1.receiver,
|
send_msg1: send_msg1.receiver,
|
||||||
send_msg2: send_msg2.receiver,
|
send_msg2: send_msg2.receiver,
|
||||||
};
|
};
|
||||||
@ -173,6 +186,7 @@ impl EventLoop {
|
|||||||
request: request.receiver,
|
request: request.receiver,
|
||||||
conn_established: conn_established.receiver,
|
conn_established: conn_established.receiver,
|
||||||
send_amounts: send_amounts.sender,
|
send_amounts: send_amounts.sender,
|
||||||
|
send_msg0: send_msg0.sender,
|
||||||
send_msg1: send_msg1.sender,
|
send_msg1: send_msg1.sender,
|
||||||
send_msg2: send_msg2.sender,
|
send_msg2: send_msg2.sender,
|
||||||
};
|
};
|
||||||
@ -188,8 +202,8 @@ impl EventLoop {
|
|||||||
OutEvent::ConnectionEstablished(alice) => {
|
OutEvent::ConnectionEstablished(alice) => {
|
||||||
let _ = self.conn_established.send(alice).await;
|
let _ = self.conn_established.send(alice).await;
|
||||||
}
|
}
|
||||||
OutEvent::Message0(msg) => {
|
OutEvent::Message0 { msg, channel } => {
|
||||||
let _ = self.msg0.send(msg).await;
|
let _ = self.msg0.send((msg, channel)).await;
|
||||||
}
|
}
|
||||||
OutEvent::Message1 { msg, channel } => {
|
OutEvent::Message1 { msg, channel } => {
|
||||||
let _ = self.msg1.send((msg, channel)).await;
|
let _ = self.msg1.send((msg, channel)).await;
|
||||||
@ -210,6 +224,11 @@ impl EventLoop {
|
|||||||
self.swarm.send_amounts(channel, amounts);
|
self.swarm.send_amounts(channel, amounts);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
msg0 = self.send_msg0.next().fuse() => {
|
||||||
|
if let Some((channel, msg)) = msg0 {
|
||||||
|
self.swarm.send_message0(channel, msg);
|
||||||
|
}
|
||||||
|
},
|
||||||
msg1 = self.send_msg1.next().fuse() => {
|
msg1 = self.send_msg1.next().fuse() => {
|
||||||
if let Some((channel, msg)) = msg1 {
|
if let Some((channel, msg)) = msg1 {
|
||||||
self.swarm.send_message1(channel, msg);
|
self.swarm.send_message1(channel, msg);
|
||||||
|
@ -6,7 +6,6 @@ use libp2p::{
|
|||||||
swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters},
|
swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters},
|
||||||
NetworkBehaviour,
|
NetworkBehaviour,
|
||||||
};
|
};
|
||||||
use rand::rngs::OsRng;
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
@ -15,11 +14,15 @@ use std::{
|
|||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Message0Protocol, TIMEOUT};
|
use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Message0Protocol, TIMEOUT};
|
||||||
use xmr_btc::{alice::State0, bob};
|
use libp2p::request_response::ResponseChannel;
|
||||||
|
use xmr_btc::bob;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum OutEvent {
|
pub enum OutEvent {
|
||||||
Msg(bob::Message0),
|
Msg {
|
||||||
|
msg: bob::Message0,
|
||||||
|
channel: ResponseChannel<AliceToBob>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A `NetworkBehaviour` that represents send/recv of message 0.
|
/// A `NetworkBehaviour` that represents send/recv of message 0.
|
||||||
@ -30,12 +33,28 @@ pub struct Message0 {
|
|||||||
rr: RequestResponse<Codec<Message0Protocol>>,
|
rr: RequestResponse<Codec<Message0Protocol>>,
|
||||||
#[behaviour(ignore)]
|
#[behaviour(ignore)]
|
||||||
events: VecDeque<OutEvent>,
|
events: VecDeque<OutEvent>,
|
||||||
#[behaviour(ignore)]
|
|
||||||
state: State0,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message0 {
|
impl Message0 {
|
||||||
pub fn new(state: State0) -> Self {
|
pub fn send(&mut self, channel: ResponseChannel<AliceToBob>, msg: xmr_btc::alice::Message0) {
|
||||||
|
let msg = AliceToBob::Message0(msg);
|
||||||
|
self.rr.send_response(channel, msg);
|
||||||
|
}
|
||||||
|
fn poll(
|
||||||
|
&mut self,
|
||||||
|
_: &mut Context<'_>,
|
||||||
|
_: &mut impl PollParameters,
|
||||||
|
) -> Poll<NetworkBehaviourAction<RequestProtocol<Codec<Message0Protocol>>, OutEvent>> {
|
||||||
|
if let Some(event) = self.events.pop_front() {
|
||||||
|
return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Message0 {
|
||||||
|
fn default() -> Self {
|
||||||
let timeout = Duration::from_secs(TIMEOUT);
|
let timeout = Duration::from_secs(TIMEOUT);
|
||||||
let mut config = RequestResponseConfig::default();
|
let mut config = RequestResponseConfig::default();
|
||||||
config.set_request_timeout(timeout);
|
config.set_request_timeout(timeout);
|
||||||
@ -47,21 +66,8 @@ impl Message0 {
|
|||||||
config,
|
config,
|
||||||
),
|
),
|
||||||
events: Default::default(),
|
events: Default::default(),
|
||||||
state,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll(
|
|
||||||
&mut self,
|
|
||||||
_: &mut Context<'_>,
|
|
||||||
_: &mut impl PollParameters,
|
|
||||||
) -> Poll<NetworkBehaviourAction<RequestProtocol<Codec<Message0Protocol>>, OutEvent>> {
|
|
||||||
if let Some(event) = self.events.pop_front() {
|
|
||||||
return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event));
|
|
||||||
}
|
|
||||||
|
|
||||||
Poll::Pending
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NetworkBehaviourEventProcess<RequestResponseEvent<BobToAlice, AliceToBob>> for Message0 {
|
impl NetworkBehaviourEventProcess<RequestResponseEvent<BobToAlice, AliceToBob>> for Message0 {
|
||||||
@ -76,13 +82,7 @@ impl NetworkBehaviourEventProcess<RequestResponseEvent<BobToAlice, AliceToBob>>
|
|||||||
} => {
|
} => {
|
||||||
if let BobToAlice::Message0(msg) = request {
|
if let BobToAlice::Message0(msg) = request {
|
||||||
debug!("Received Message0");
|
debug!("Received Message0");
|
||||||
// TODO(Franck): Move this business logic out of the network behaviour.
|
self.events.push_back(OutEvent::Msg { msg, channel });
|
||||||
let response = AliceToBob::Message0(self.state.next_message(&mut OsRng));
|
|
||||||
|
|
||||||
self.rr.send_response(channel, response);
|
|
||||||
debug!("Sent Message0");
|
|
||||||
|
|
||||||
self.events.push_back(OutEvent::Msg(msg));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RequestResponseEvent::Message {
|
RequestResponseEvent::Message {
|
||||||
|
@ -10,6 +10,7 @@ use futures::{
|
|||||||
};
|
};
|
||||||
use libp2p::request_response::ResponseChannel;
|
use libp2p::request_response::ResponseChannel;
|
||||||
|
|
||||||
|
use rand::rngs::OsRng;
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
@ -59,7 +60,13 @@ pub async fn negotiate(
|
|||||||
.send_amounts(event.channel, amounts)
|
.send_amounts(event.channel, amounts)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let bob_message0 = timeout(config.bob_time_to_act, event_loop_handle.recv_message0()).await??;
|
let (bob_message0, channel) =
|
||||||
|
timeout(config.bob_time_to_act, event_loop_handle.recv_message0()).await??;
|
||||||
|
|
||||||
|
let alice_message0 = state0.next_message(&mut OsRng);
|
||||||
|
event_loop_handle
|
||||||
|
.send_message0(channel, alice_message0)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let state1 = state0.receive(bob_message0)?;
|
let state1 = state0.receive(bob_message0)?;
|
||||||
|
|
||||||
@ -80,6 +87,8 @@ pub async fn negotiate(
|
|||||||
Ok((channel, state3))
|
Ok((channel, state3))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(Franck): Use helper functions from xmr-btc instead of re-writing them
|
||||||
|
// here
|
||||||
pub async fn wait_for_locked_bitcoin<W>(
|
pub async fn wait_for_locked_bitcoin<W>(
|
||||||
lock_bitcoin_txid: bitcoin::Txid,
|
lock_bitcoin_txid: bitcoin::Txid,
|
||||||
bitcoin_wallet: Arc<W>,
|
bitcoin_wallet: Arc<W>,
|
@ -3,18 +3,22 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
alice::{
|
alice::{
|
||||||
event_loop::EventLoopHandle,
|
event_loop::EventLoopHandle,
|
||||||
execution::{
|
steps::{
|
||||||
build_bitcoin_punish_transaction, build_bitcoin_redeem_transaction,
|
build_bitcoin_punish_transaction, build_bitcoin_redeem_transaction,
|
||||||
extract_monero_private_key, lock_xmr, negotiate, publish_bitcoin_punish_transaction,
|
extract_monero_private_key, lock_xmr, negotiate, publish_bitcoin_punish_transaction,
|
||||||
publish_bitcoin_redeem_transaction, publish_cancel_transaction,
|
publish_bitcoin_redeem_transaction, publish_cancel_transaction,
|
||||||
wait_for_bitcoin_encrypted_signature, wait_for_bitcoin_refund, wait_for_locked_bitcoin,
|
wait_for_bitcoin_encrypted_signature, wait_for_bitcoin_refund, wait_for_locked_bitcoin,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
bitcoin,
|
||||||
bitcoin::EncryptedSignature,
|
bitcoin::EncryptedSignature,
|
||||||
network::request_response::AliceToBob,
|
network::request_response::AliceToBob,
|
||||||
|
state,
|
||||||
|
state::{Alice, Swap},
|
||||||
|
storage::Database,
|
||||||
SwapAmounts,
|
SwapAmounts,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::{bail, Result};
|
||||||
use async_recursion::async_recursion;
|
use async_recursion::async_recursion;
|
||||||
use futures::{
|
use futures::{
|
||||||
future::{select, Either},
|
future::{select, Either},
|
||||||
@ -22,8 +26,9 @@ use futures::{
|
|||||||
};
|
};
|
||||||
use libp2p::request_response::ResponseChannel;
|
use libp2p::request_response::ResponseChannel;
|
||||||
use rand::{CryptoRng, RngCore};
|
use rand::{CryptoRng, RngCore};
|
||||||
use std::{fmt, sync::Arc};
|
use std::{convert::TryFrom, fmt, sync::Arc};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
use uuid::Uuid;
|
||||||
use xmr_btc::{
|
use xmr_btc::{
|
||||||
alice::{State0, State3},
|
alice::{State0, State3},
|
||||||
bitcoin::{TransactionBlockHeight, TxCancel, TxRefund, WatchForRawTransaction},
|
bitcoin::{TransactionBlockHeight, TxCancel, TxRefund, WatchForRawTransaction},
|
||||||
@ -36,8 +41,6 @@ trait Rng: RngCore + CryptoRng + Send {}
|
|||||||
|
|
||||||
impl<T> Rng for T where T: RngCore + CryptoRng + Send {}
|
impl<T> Rng for T where T: RngCore + CryptoRng + Send {}
|
||||||
|
|
||||||
// The same data structure is used for swap execution and recovery.
|
|
||||||
// This allows for a seamless transition from a failed swap to recovery.
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
pub enum AliceState {
|
pub enum AliceState {
|
||||||
Started {
|
Started {
|
||||||
@ -45,12 +48,12 @@ pub enum AliceState {
|
|||||||
state0: State0,
|
state0: State0,
|
||||||
},
|
},
|
||||||
Negotiated {
|
Negotiated {
|
||||||
channel: ResponseChannel<AliceToBob>,
|
channel: Option<ResponseChannel<AliceToBob>>,
|
||||||
amounts: SwapAmounts,
|
amounts: SwapAmounts,
|
||||||
state3: State3,
|
state3: State3,
|
||||||
},
|
},
|
||||||
BtcLocked {
|
BtcLocked {
|
||||||
channel: ResponseChannel<AliceToBob>,
|
channel: Option<ResponseChannel<AliceToBob>>,
|
||||||
amounts: SwapAmounts,
|
amounts: SwapAmounts,
|
||||||
state3: State3,
|
state3: State3,
|
||||||
},
|
},
|
||||||
@ -67,8 +70,7 @@ pub enum AliceState {
|
|||||||
tx_cancel: TxCancel,
|
tx_cancel: TxCancel,
|
||||||
},
|
},
|
||||||
BtcRefunded {
|
BtcRefunded {
|
||||||
tx_refund: TxRefund,
|
spend_key: monero::PrivateKey,
|
||||||
published_refund_tx: ::bitcoin::Transaction,
|
|
||||||
state3: State3,
|
state3: State3,
|
||||||
},
|
},
|
||||||
BtcPunishable {
|
BtcPunishable {
|
||||||
@ -76,7 +78,7 @@ pub enum AliceState {
|
|||||||
state3: State3,
|
state3: State3,
|
||||||
},
|
},
|
||||||
XmrRefunded,
|
XmrRefunded,
|
||||||
Cancelling {
|
T1Expired {
|
||||||
state3: State3,
|
state3: State3,
|
||||||
},
|
},
|
||||||
Punished,
|
Punished,
|
||||||
@ -98,7 +100,113 @@ impl fmt::Display for AliceState {
|
|||||||
AliceState::SafelyAborted => write!(f, "safely_aborted"),
|
AliceState::SafelyAborted => write!(f, "safely_aborted"),
|
||||||
AliceState::BtcPunishable { .. } => write!(f, "btc_punishable"),
|
AliceState::BtcPunishable { .. } => write!(f, "btc_punishable"),
|
||||||
AliceState::XmrRefunded => write!(f, "xmr_refunded"),
|
AliceState::XmrRefunded => write!(f, "xmr_refunded"),
|
||||||
AliceState::Cancelling { .. } => write!(f, "cancelling"),
|
AliceState::T1Expired { .. } => write!(f, "t1 is expired"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&AliceState> for state::Alice {
|
||||||
|
fn from(alice_state: &AliceState) -> Self {
|
||||||
|
match alice_state {
|
||||||
|
AliceState::Negotiated { state3, .. } => Alice::Negotiated(state3.clone()),
|
||||||
|
AliceState::BtcLocked { state3, .. } => Alice::BtcLocked(state3.clone()),
|
||||||
|
AliceState::XmrLocked { state3 } => Alice::XmrLocked(state3.clone()),
|
||||||
|
AliceState::EncSignLearned {
|
||||||
|
state3,
|
||||||
|
encrypted_signature,
|
||||||
|
} => Alice::EncSignLearned {
|
||||||
|
state: state3.clone(),
|
||||||
|
encrypted_signature: encrypted_signature.clone(),
|
||||||
|
},
|
||||||
|
AliceState::BtcRedeemed => Alice::SwapComplete,
|
||||||
|
AliceState::BtcCancelled { state3, .. } => Alice::BtcCancelled(state3.clone()),
|
||||||
|
AliceState::BtcRefunded { .. } => Alice::SwapComplete,
|
||||||
|
AliceState::BtcPunishable { state3, .. } => Alice::BtcPunishable(state3.clone()),
|
||||||
|
AliceState::XmrRefunded => Alice::SwapComplete,
|
||||||
|
AliceState::T1Expired { state3 } => Alice::T1Expired(state3.clone()),
|
||||||
|
AliceState::Punished => Alice::SwapComplete,
|
||||||
|
AliceState::SafelyAborted => Alice::SwapComplete,
|
||||||
|
// TODO: Potentially add support to resume swaps that are not Negotiated
|
||||||
|
AliceState::Started { .. } => {
|
||||||
|
panic!("Alice attempted to save swap before being negotiated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<state::Swap> for AliceState {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(db_state: Swap) -> Result<Self, Self::Error> {
|
||||||
|
use AliceState::*;
|
||||||
|
if let Swap::Alice(state) = db_state {
|
||||||
|
let alice_state = match state {
|
||||||
|
Alice::Negotiated(state3) => Negotiated {
|
||||||
|
channel: None,
|
||||||
|
amounts: SwapAmounts {
|
||||||
|
btc: state3.btc,
|
||||||
|
xmr: state3.xmr,
|
||||||
|
},
|
||||||
|
state3,
|
||||||
|
},
|
||||||
|
Alice::BtcLocked(state3) => BtcLocked {
|
||||||
|
channel: None,
|
||||||
|
amounts: SwapAmounts {
|
||||||
|
btc: state3.btc,
|
||||||
|
xmr: state3.xmr,
|
||||||
|
},
|
||||||
|
state3,
|
||||||
|
},
|
||||||
|
Alice::XmrLocked(state3) => XmrLocked { state3 },
|
||||||
|
Alice::BtcRedeemable { .. } => bail!("BtcRedeemable state is unexpected"),
|
||||||
|
Alice::EncSignLearned {
|
||||||
|
state,
|
||||||
|
encrypted_signature,
|
||||||
|
} => EncSignLearned {
|
||||||
|
state3: state,
|
||||||
|
encrypted_signature,
|
||||||
|
},
|
||||||
|
Alice::T1Expired(state3) => AliceState::T1Expired { state3 },
|
||||||
|
Alice::BtcCancelled(state) => {
|
||||||
|
let tx_cancel = bitcoin::TxCancel::new(
|
||||||
|
&state.tx_lock,
|
||||||
|
state.refund_timelock,
|
||||||
|
state.a.public(),
|
||||||
|
state.B,
|
||||||
|
);
|
||||||
|
|
||||||
|
BtcCancelled {
|
||||||
|
state3: state,
|
||||||
|
tx_cancel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Alice::BtcPunishable(state) => {
|
||||||
|
let tx_cancel = bitcoin::TxCancel::new(
|
||||||
|
&state.tx_lock,
|
||||||
|
state.refund_timelock,
|
||||||
|
state.a.public(),
|
||||||
|
state.B,
|
||||||
|
);
|
||||||
|
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &state.refund_address);
|
||||||
|
BtcPunishable {
|
||||||
|
tx_refund,
|
||||||
|
state3: state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Alice::BtcRefunded {
|
||||||
|
state, spend_key, ..
|
||||||
|
} => BtcRefunded {
|
||||||
|
spend_key,
|
||||||
|
state3: state,
|
||||||
|
},
|
||||||
|
Alice::SwapComplete => {
|
||||||
|
// TODO(Franck): Better fine grain
|
||||||
|
AliceState::SafelyAborted
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(alice_state)
|
||||||
|
} else {
|
||||||
|
bail!("Alice swap state expected.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,7 +217,9 @@ pub async fn swap(
|
|||||||
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
||||||
monero_wallet: Arc<crate::monero::Wallet>,
|
monero_wallet: Arc<crate::monero::Wallet>,
|
||||||
config: Config,
|
config: Config,
|
||||||
) -> Result<(AliceState, EventLoopHandle)> {
|
swap_id: Uuid,
|
||||||
|
db: Database,
|
||||||
|
) -> Result<AliceState> {
|
||||||
run_until(
|
run_until(
|
||||||
state,
|
state,
|
||||||
is_complete,
|
is_complete,
|
||||||
@ -117,10 +227,35 @@ pub async fn swap(
|
|||||||
bitcoin_wallet,
|
bitcoin_wallet,
|
||||||
monero_wallet,
|
monero_wallet,
|
||||||
config,
|
config,
|
||||||
|
swap_id,
|
||||||
|
db,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn resume_from_database(
|
||||||
|
event_loop_handle: EventLoopHandle,
|
||||||
|
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
||||||
|
monero_wallet: Arc<crate::monero::Wallet>,
|
||||||
|
config: Config,
|
||||||
|
swap_id: Uuid,
|
||||||
|
db: Database,
|
||||||
|
) -> Result<AliceState> {
|
||||||
|
let db_swap = db.get_state(swap_id)?;
|
||||||
|
let start_state = AliceState::try_from(db_swap)?;
|
||||||
|
let state = swap(
|
||||||
|
start_state,
|
||||||
|
event_loop_handle,
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
config,
|
||||||
|
swap_id,
|
||||||
|
db,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(state)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_complete(state: &AliceState) -> bool {
|
pub fn is_complete(state: &AliceState) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
state,
|
state,
|
||||||
@ -138,8 +273,16 @@ pub fn is_xmr_locked(state: &AliceState) -> bool {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_encsig_learned(state: &AliceState) -> bool {
|
||||||
|
matches!(
|
||||||
|
state,
|
||||||
|
AliceState::EncSignLearned{..}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// State machine driver for swap execution
|
// State machine driver for swap execution
|
||||||
#[async_recursion]
|
#[async_recursion]
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn run_until(
|
pub async fn run_until(
|
||||||
state: AliceState,
|
state: AliceState,
|
||||||
is_target_state: fn(&AliceState) -> bool,
|
is_target_state: fn(&AliceState) -> bool,
|
||||||
@ -147,27 +290,37 @@ pub async fn run_until(
|
|||||||
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
||||||
monero_wallet: Arc<crate::monero::Wallet>,
|
monero_wallet: Arc<crate::monero::Wallet>,
|
||||||
config: Config,
|
config: Config,
|
||||||
) -> Result<(AliceState, EventLoopHandle)> {
|
swap_id: Uuid,
|
||||||
|
db: Database,
|
||||||
|
// TODO: Remove EventLoopHandle!
|
||||||
|
) -> Result<AliceState> {
|
||||||
info!("Current state:{}", state);
|
info!("Current state:{}", state);
|
||||||
if is_target_state(&state) {
|
if is_target_state(&state) {
|
||||||
Ok((state, event_loop_handle))
|
Ok(state)
|
||||||
} else {
|
} else {
|
||||||
match state {
|
match state {
|
||||||
AliceState::Started { amounts, state0 } => {
|
AliceState::Started { amounts, state0 } => {
|
||||||
let (channel, state3) =
|
let (channel, state3) =
|
||||||
negotiate(state0, amounts, &mut event_loop_handle, config).await?;
|
negotiate(state0, amounts, &mut event_loop_handle, config).await?;
|
||||||
|
|
||||||
|
let state = AliceState::Negotiated {
|
||||||
|
channel: Some(channel),
|
||||||
|
amounts,
|
||||||
|
state3,
|
||||||
|
};
|
||||||
|
|
||||||
|
let db_state = (&state).into();
|
||||||
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||||
|
.await?;
|
||||||
run_until(
|
run_until(
|
||||||
AliceState::Negotiated {
|
state,
|
||||||
channel,
|
|
||||||
amounts,
|
|
||||||
state3,
|
|
||||||
},
|
|
||||||
is_target_state,
|
is_target_state,
|
||||||
event_loop_handle,
|
event_loop_handle,
|
||||||
bitcoin_wallet,
|
bitcoin_wallet,
|
||||||
monero_wallet,
|
monero_wallet,
|
||||||
config,
|
config,
|
||||||
|
swap_id,
|
||||||
|
db,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
@ -176,21 +329,41 @@ pub async fn run_until(
|
|||||||
channel,
|
channel,
|
||||||
amounts,
|
amounts,
|
||||||
} => {
|
} => {
|
||||||
let _ =
|
let state = match channel {
|
||||||
wait_for_locked_bitcoin(state3.tx_lock.txid(), bitcoin_wallet.clone(), config)
|
Some(channel) => {
|
||||||
|
let _ = wait_for_locked_bitcoin(
|
||||||
|
state3.tx_lock.txid(),
|
||||||
|
bitcoin_wallet.clone(),
|
||||||
|
config,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
AliceState::BtcLocked {
|
||||||
|
channel: Some(channel),
|
||||||
|
amounts,
|
||||||
|
state3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
tracing::info!("Cannot resume swap from negotiated state, aborting");
|
||||||
|
|
||||||
|
// Alice did not lock Xmr yet
|
||||||
|
AliceState::SafelyAborted
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let db_state = (&state).into();
|
||||||
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||||
|
.await?;
|
||||||
run_until(
|
run_until(
|
||||||
AliceState::BtcLocked {
|
state,
|
||||||
channel,
|
|
||||||
amounts,
|
|
||||||
state3,
|
|
||||||
},
|
|
||||||
is_target_state,
|
is_target_state,
|
||||||
event_loop_handle,
|
event_loop_handle,
|
||||||
bitcoin_wallet,
|
bitcoin_wallet,
|
||||||
monero_wallet,
|
monero_wallet,
|
||||||
config,
|
config,
|
||||||
|
swap_id,
|
||||||
|
db,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
@ -199,77 +372,82 @@ pub async fn run_until(
|
|||||||
amounts,
|
amounts,
|
||||||
state3,
|
state3,
|
||||||
} => {
|
} => {
|
||||||
lock_xmr(
|
let state = match channel {
|
||||||
channel,
|
Some(channel) => {
|
||||||
amounts,
|
lock_xmr(
|
||||||
state3.clone(),
|
channel,
|
||||||
&mut event_loop_handle,
|
amounts,
|
||||||
monero_wallet.clone(),
|
state3.clone(),
|
||||||
)
|
&mut event_loop_handle,
|
||||||
.await?;
|
monero_wallet.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
AliceState::XmrLocked { state3 }
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
tracing::info!("Cannot resume swap from BTC locked state, aborting");
|
||||||
|
|
||||||
|
// Alice did not lock Xmr yet
|
||||||
|
AliceState::SafelyAborted
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let db_state = (&state).into();
|
||||||
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||||
|
.await?;
|
||||||
run_until(
|
run_until(
|
||||||
AliceState::XmrLocked { state3 },
|
state,
|
||||||
is_target_state,
|
is_target_state,
|
||||||
event_loop_handle,
|
event_loop_handle,
|
||||||
bitcoin_wallet,
|
bitcoin_wallet,
|
||||||
monero_wallet,
|
monero_wallet,
|
||||||
config,
|
config,
|
||||||
|
swap_id,
|
||||||
|
db,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
AliceState::XmrLocked { state3 } => {
|
AliceState::XmrLocked { state3 } => {
|
||||||
// todo: match statement and wait for t1 can probably be expressed more cleanly
|
// todo: match statement and wait for t1 can probably be expressed more cleanly
|
||||||
match state3.current_epoch(bitcoin_wallet.as_ref()).await? {
|
let state = match state3.current_epoch(bitcoin_wallet.as_ref()).await? {
|
||||||
Epoch::T0 => {
|
Epoch::T0 => {
|
||||||
let wait_for_enc_sig = wait_for_bitcoin_encrypted_signature(
|
let wait_for_enc_sig = wait_for_bitcoin_encrypted_signature(
|
||||||
&mut event_loop_handle,
|
&mut event_loop_handle,
|
||||||
config.monero_max_finality_time,
|
config.monero_max_finality_time,
|
||||||
);
|
);
|
||||||
let t1_timeout = state3.wait_for_t1(bitcoin_wallet.as_ref());
|
let state3_clone = state3.clone();
|
||||||
|
let t1_timeout = state3_clone.wait_for_t1(bitcoin_wallet.as_ref());
|
||||||
|
|
||||||
tokio::select! {
|
pin_mut!(wait_for_enc_sig);
|
||||||
_ = t1_timeout => {
|
pin_mut!(t1_timeout);
|
||||||
run_until(
|
|
||||||
AliceState::Cancelling { state3 },
|
match select(t1_timeout, wait_for_enc_sig).await {
|
||||||
is_target_state,
|
Either::Left(_) => AliceState::T1Expired { state3 },
|
||||||
event_loop_handle,
|
Either::Right((enc_sig, _)) => AliceState::EncSignLearned {
|
||||||
bitcoin_wallet,
|
state3,
|
||||||
monero_wallet,
|
encrypted_signature: enc_sig?,
|
||||||
config,
|
},
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
enc_sig = wait_for_enc_sig => {
|
|
||||||
run_until(
|
|
||||||
AliceState::EncSignLearned {
|
|
||||||
state3,
|
|
||||||
encrypted_signature: enc_sig?,
|
|
||||||
},
|
|
||||||
is_target_state,
|
|
||||||
event_loop_handle,
|
|
||||||
bitcoin_wallet,
|
|
||||||
monero_wallet,
|
|
||||||
config,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => AliceState::T1Expired { state3 },
|
||||||
run_until(
|
};
|
||||||
AliceState::Cancelling { state3 },
|
|
||||||
is_target_state,
|
|
||||||
event_loop_handle,
|
|
||||||
bitcoin_wallet,
|
|
||||||
monero_wallet,
|
|
||||||
config,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let db_state = (&state).into();
|
||||||
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||||
|
.await?;
|
||||||
|
run_until(
|
||||||
|
state,
|
||||||
|
is_target_state,
|
||||||
|
event_loop_handle,
|
||||||
|
bitcoin_wallet.clone(),
|
||||||
|
monero_wallet,
|
||||||
|
config,
|
||||||
|
swap_id,
|
||||||
|
db,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
AliceState::EncSignLearned {
|
AliceState::EncSignLearned {
|
||||||
state3,
|
state3,
|
||||||
encrypted_signature,
|
encrypted_signature,
|
||||||
@ -284,13 +462,21 @@ pub async fn run_until(
|
|||||||
) {
|
) {
|
||||||
Ok(tx) => tx,
|
Ok(tx) => tx,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
state3.wait_for_t1(bitcoin_wallet.as_ref()).await?;
|
||||||
|
|
||||||
|
let state = AliceState::T1Expired { state3 };
|
||||||
|
let db_state = (&state).into();
|
||||||
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||||
|
.await?;
|
||||||
return run_until(
|
return run_until(
|
||||||
AliceState::Cancelling { state3 },
|
state,
|
||||||
is_target_state,
|
is_target_state,
|
||||||
event_loop_handle,
|
event_loop_handle,
|
||||||
bitcoin_wallet,
|
bitcoin_wallet,
|
||||||
monero_wallet,
|
monero_wallet,
|
||||||
config,
|
config,
|
||||||
|
swap_id,
|
||||||
|
db,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
@ -306,17 +492,23 @@ pub async fn run_until(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let state = AliceState::BtcRedeemed;
|
||||||
|
let db_state = (&state).into();
|
||||||
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||||
|
.await?;
|
||||||
run_until(
|
run_until(
|
||||||
AliceState::BtcRedeemed,
|
state,
|
||||||
is_target_state,
|
is_target_state,
|
||||||
event_loop_handle,
|
event_loop_handle,
|
||||||
bitcoin_wallet,
|
bitcoin_wallet,
|
||||||
monero_wallet,
|
monero_wallet,
|
||||||
config,
|
config,
|
||||||
|
swap_id,
|
||||||
|
db,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
AliceState::Cancelling { state3 } => {
|
AliceState::T1Expired { state3 } => {
|
||||||
let tx_cancel = publish_cancel_transaction(
|
let tx_cancel = publish_cancel_transaction(
|
||||||
state3.tx_lock.clone(),
|
state3.tx_lock.clone(),
|
||||||
state3.a.clone(),
|
state3.a.clone(),
|
||||||
@ -327,13 +519,19 @@ pub async fn run_until(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let state = AliceState::BtcCancelled { state3, tx_cancel };
|
||||||
|
let db_state = (&state).into();
|
||||||
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||||
|
.await?;
|
||||||
run_until(
|
run_until(
|
||||||
AliceState::BtcCancelled { state3, tx_cancel },
|
state,
|
||||||
is_target_state,
|
is_target_state,
|
||||||
event_loop_handle,
|
event_loop_handle,
|
||||||
bitcoin_wallet,
|
bitcoin_wallet,
|
||||||
monero_wallet,
|
monero_wallet,
|
||||||
config,
|
config,
|
||||||
|
swap_id,
|
||||||
|
db,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
@ -354,52 +552,60 @@ pub async fn run_until(
|
|||||||
// TODO(Franck): Review error handling
|
// TODO(Franck): Review error handling
|
||||||
match published_refund_tx {
|
match published_refund_tx {
|
||||||
None => {
|
None => {
|
||||||
run_until(
|
let state = AliceState::BtcPunishable { tx_refund, state3 };
|
||||||
AliceState::BtcPunishable { tx_refund, state3 },
|
let db_state = (&state).into();
|
||||||
is_target_state,
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||||
|
.await?;
|
||||||
|
swap(
|
||||||
|
state,
|
||||||
event_loop_handle,
|
event_loop_handle,
|
||||||
bitcoin_wallet.clone(),
|
bitcoin_wallet.clone(),
|
||||||
monero_wallet,
|
monero_wallet,
|
||||||
config,
|
config,
|
||||||
|
swap_id,
|
||||||
|
db,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
Some(published_refund_tx) => {
|
Some(published_refund_tx) => {
|
||||||
|
let spend_key = extract_monero_private_key(
|
||||||
|
published_refund_tx,
|
||||||
|
tx_refund,
|
||||||
|
state3.s_a,
|
||||||
|
state3.a.clone(),
|
||||||
|
state3.S_b_bitcoin,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let state = AliceState::BtcRefunded { spend_key, state3 };
|
||||||
|
let db_state = (&state).into();
|
||||||
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||||
|
.await?;
|
||||||
run_until(
|
run_until(
|
||||||
AliceState::BtcRefunded {
|
state,
|
||||||
tx_refund,
|
|
||||||
published_refund_tx,
|
|
||||||
state3,
|
|
||||||
},
|
|
||||||
is_target_state,
|
is_target_state,
|
||||||
event_loop_handle,
|
event_loop_handle,
|
||||||
bitcoin_wallet.clone(),
|
bitcoin_wallet.clone(),
|
||||||
monero_wallet,
|
monero_wallet,
|
||||||
config,
|
config,
|
||||||
|
swap_id,
|
||||||
|
db,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AliceState::BtcRefunded {
|
AliceState::BtcRefunded { spend_key, state3 } => {
|
||||||
tx_refund,
|
|
||||||
published_refund_tx,
|
|
||||||
state3,
|
|
||||||
} => {
|
|
||||||
let spend_key = extract_monero_private_key(
|
|
||||||
published_refund_tx,
|
|
||||||
tx_refund,
|
|
||||||
state3.s_a,
|
|
||||||
state3.a.clone(),
|
|
||||||
state3.S_b_bitcoin,
|
|
||||||
)?;
|
|
||||||
let view_key = state3.v;
|
let view_key = state3.v;
|
||||||
|
|
||||||
monero_wallet
|
monero_wallet
|
||||||
.create_and_load_wallet_for_output(spend_key, view_key)
|
.create_and_load_wallet_for_output(spend_key, view_key)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok((AliceState::XmrRefunded, event_loop_handle))
|
let state = AliceState::XmrRefunded;
|
||||||
|
let db_state = (&state).into();
|
||||||
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||||
|
.await?;
|
||||||
|
Ok(state)
|
||||||
}
|
}
|
||||||
AliceState::BtcPunishable { tx_refund, state3 } => {
|
AliceState::BtcPunishable { tx_refund, state3 } => {
|
||||||
let signed_tx_punish = build_bitcoin_punish_transaction(
|
let signed_tx_punish = build_bitcoin_punish_transaction(
|
||||||
@ -425,37 +631,52 @@ pub async fn run_until(
|
|||||||
|
|
||||||
match select(punish_tx_finalised, refund_tx_seen).await {
|
match select(punish_tx_finalised, refund_tx_seen).await {
|
||||||
Either::Left(_) => {
|
Either::Left(_) => {
|
||||||
|
let state = AliceState::Punished;
|
||||||
|
let db_state = (&state).into();
|
||||||
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||||
|
.await?;
|
||||||
run_until(
|
run_until(
|
||||||
AliceState::Punished,
|
state,
|
||||||
is_target_state,
|
is_target_state,
|
||||||
event_loop_handle,
|
event_loop_handle,
|
||||||
bitcoin_wallet.clone(),
|
bitcoin_wallet.clone(),
|
||||||
monero_wallet,
|
monero_wallet,
|
||||||
config,
|
config,
|
||||||
|
swap_id,
|
||||||
|
db,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
Either::Right((published_refund_tx, _)) => {
|
Either::Right((published_refund_tx, _)) => {
|
||||||
|
let spend_key = extract_monero_private_key(
|
||||||
|
published_refund_tx,
|
||||||
|
tx_refund,
|
||||||
|
state3.s_a,
|
||||||
|
state3.a.clone(),
|
||||||
|
state3.S_b_bitcoin,
|
||||||
|
)?;
|
||||||
|
let state = AliceState::BtcRefunded { spend_key, state3 };
|
||||||
|
let db_state = (&state).into();
|
||||||
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||||
|
.await?;
|
||||||
run_until(
|
run_until(
|
||||||
AliceState::BtcRefunded {
|
state,
|
||||||
tx_refund,
|
|
||||||
published_refund_tx,
|
|
||||||
state3,
|
|
||||||
},
|
|
||||||
is_target_state,
|
is_target_state,
|
||||||
event_loop_handle,
|
event_loop_handle,
|
||||||
bitcoin_wallet.clone(),
|
bitcoin_wallet.clone(),
|
||||||
monero_wallet,
|
monero_wallet,
|
||||||
config,
|
config,
|
||||||
|
swap_id,
|
||||||
|
db,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AliceState::XmrRefunded => Ok((AliceState::XmrRefunded, event_loop_handle)),
|
AliceState::XmrRefunded => Ok(AliceState::XmrRefunded),
|
||||||
AliceState::BtcRedeemed => Ok((AliceState::BtcRedeemed, event_loop_handle)),
|
AliceState::BtcRedeemed => Ok(AliceState::BtcRedeemed),
|
||||||
AliceState::Punished => Ok((AliceState::Punished, event_loop_handle)),
|
AliceState::Punished => Ok(AliceState::Punished),
|
||||||
AliceState::SafelyAborted => Ok((AliceState::SafelyAborted, event_loop_handle)),
|
AliceState::SafelyAborted => Ok(AliceState::SafelyAborted),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,7 @@ use std::sync::Arc;
|
|||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use swap::{
|
use swap::{
|
||||||
alice, alice::swap::AliceState, bitcoin, bob, bob::swap::BobState, cli::Options, monero,
|
alice, alice::swap::AliceState, bitcoin, bob, bob::swap::BobState, cli::Options, monero,
|
||||||
network::transport::build, recover::recover, storage::Database, trace::init_tracing,
|
network::transport::build, storage::Database, trace::init_tracing, SwapAmounts,
|
||||||
SwapAmounts,
|
|
||||||
};
|
};
|
||||||
use tracing::{info, log::LevelFilter};
|
use tracing::{info, log::LevelFilter};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@ -98,11 +97,8 @@ async fn main() -> Result<()> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
(
|
(
|
||||||
AliceState::Started {
|
AliceState::Started { amounts, state0 },
|
||||||
amounts,
|
alice::Behaviour::default(),
|
||||||
state0: state0.clone(),
|
|
||||||
},
|
|
||||||
alice::Behaviour::new(state0),
|
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -117,12 +113,20 @@ async fn main() -> Result<()> {
|
|||||||
let (mut event_loop, handle) =
|
let (mut event_loop, handle) =
|
||||||
alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen_addr)?;
|
alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen_addr)?;
|
||||||
|
|
||||||
|
let swap_id = Uuid::new_v4();
|
||||||
|
info!(
|
||||||
|
"Swap sending {} and receiving {} started with ID {}",
|
||||||
|
send_monero, receive_bitcoin, swap_id
|
||||||
|
);
|
||||||
|
|
||||||
let swap = alice::swap::swap(
|
let swap = alice::swap::swap(
|
||||||
alice_state,
|
alice_state,
|
||||||
handle,
|
handle,
|
||||||
bitcoin_wallet.clone(),
|
bitcoin_wallet.clone(),
|
||||||
monero_wallet.clone(),
|
monero_wallet.clone(),
|
||||||
config,
|
config,
|
||||||
|
swap_id,
|
||||||
|
db,
|
||||||
);
|
);
|
||||||
|
|
||||||
let _event_loop = tokio::spawn(async move { event_loop.run().await });
|
let _event_loop = tokio::spawn(async move { event_loop.run().await });
|
||||||
@ -130,7 +134,6 @@ async fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
Options::BuyXmr {
|
Options::BuyXmr {
|
||||||
alice_addr,
|
alice_addr,
|
||||||
alice_peer_id,
|
|
||||||
bitcoind_url,
|
bitcoind_url,
|
||||||
bitcoin_wallet_name,
|
bitcoin_wallet_name,
|
||||||
monero_wallet_rpc_url,
|
monero_wallet_rpc_url,
|
||||||
@ -182,13 +185,18 @@ async fn main() -> Result<()> {
|
|||||||
let bob_state = BobState::Started {
|
let bob_state = BobState::Started {
|
||||||
state0,
|
state0,
|
||||||
amounts,
|
amounts,
|
||||||
peer_id: alice_peer_id,
|
|
||||||
addr: alice_addr,
|
addr: alice_addr,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (event_loop, handle) =
|
let (event_loop, handle) =
|
||||||
bob::event_loop::EventLoop::new(bob_transport, bob_behaviour).unwrap();
|
bob::event_loop::EventLoop::new(bob_transport, bob_behaviour).unwrap();
|
||||||
|
|
||||||
|
let swap_id = Uuid::new_v4();
|
||||||
|
info!(
|
||||||
|
"Swap sending {} and receiving {} started with ID {}",
|
||||||
|
send_bitcoin, receive_monero, swap_id
|
||||||
|
);
|
||||||
|
|
||||||
let swap = bob::swap::swap(
|
let swap = bob::swap::swap(
|
||||||
bob_state,
|
bob_state,
|
||||||
handle,
|
handle,
|
||||||
@ -196,7 +204,7 @@ async fn main() -> Result<()> {
|
|||||||
bitcoin_wallet.clone(),
|
bitcoin_wallet.clone(),
|
||||||
monero_wallet.clone(),
|
monero_wallet.clone(),
|
||||||
OsRng,
|
OsRng,
|
||||||
Uuid::new_v4(),
|
swap_id,
|
||||||
);
|
);
|
||||||
|
|
||||||
let _event_loop = tokio::spawn(async move { event_loop.run().await });
|
let _event_loop = tokio::spawn(async move { event_loop.run().await });
|
||||||
@ -214,24 +222,7 @@ async fn main() -> Result<()> {
|
|||||||
// Print the table to stdout
|
// Print the table to stdout
|
||||||
table.printstd();
|
table.printstd();
|
||||||
}
|
}
|
||||||
Options::Recover {
|
Options::Resume { .. } => todo!("implement this"),
|
||||||
swap_id,
|
|
||||||
bitcoind_url,
|
|
||||||
monerod_url,
|
|
||||||
bitcoin_wallet_name,
|
|
||||||
} => {
|
|
||||||
let state = db.get_state(swap_id)?;
|
|
||||||
let bitcoin_wallet = bitcoin::Wallet::new(
|
|
||||||
bitcoin_wallet_name.as_ref(),
|
|
||||||
bitcoind_url,
|
|
||||||
config.bitcoin_network,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("failed to create bitcoin wallet");
|
|
||||||
let monero_wallet = monero::Wallet::new(monerod_url);
|
|
||||||
|
|
||||||
recover(bitcoin_wallet, monero_wallet, state).await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -20,7 +20,6 @@ use xmr_btc::{
|
|||||||
|
|
||||||
mod amounts;
|
mod amounts;
|
||||||
pub mod event_loop;
|
pub mod event_loop;
|
||||||
mod execution;
|
|
||||||
mod message0;
|
mod message0;
|
||||||
mod message1;
|
mod message1;
|
||||||
mod message2;
|
mod message2;
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
use crate::{bob::event_loop::EventLoopHandle, SwapAmounts};
|
|
||||||
use anyhow::Result;
|
|
||||||
use libp2p::core::Multiaddr;
|
|
||||||
use rand::{CryptoRng, RngCore};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use xmr_btc::bob::State2;
|
|
||||||
|
|
||||||
pub async fn negotiate<R>(
|
|
||||||
state0: xmr_btc::bob::State0,
|
|
||||||
amounts: SwapAmounts,
|
|
||||||
swarm: &mut EventLoopHandle,
|
|
||||||
addr: Multiaddr,
|
|
||||||
mut rng: R,
|
|
||||||
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
|
||||||
) -> Result<State2>
|
|
||||||
where
|
|
||||||
R: RngCore + CryptoRng + Send,
|
|
||||||
{
|
|
||||||
tracing::trace!("Starting negotiate");
|
|
||||||
swarm.dial_alice(addr).await?;
|
|
||||||
|
|
||||||
let alice = swarm.recv_conn_established().await?;
|
|
||||||
|
|
||||||
swarm.request_amounts(alice.clone(), amounts.btc).await?;
|
|
||||||
|
|
||||||
swarm
|
|
||||||
.send_message0(alice.clone(), state0.next_message(&mut rng))
|
|
||||||
.await?;
|
|
||||||
let msg0 = swarm.recv_message0().await?;
|
|
||||||
let state1 = state0.receive(bitcoin_wallet.as_ref(), msg0).await?;
|
|
||||||
|
|
||||||
swarm
|
|
||||||
.send_message1(alice.clone(), state1.next_message())
|
|
||||||
.await?;
|
|
||||||
let msg1 = swarm.recv_message1().await?;
|
|
||||||
let state2 = state1.receive(msg1)?;
|
|
||||||
|
|
||||||
swarm
|
|
||||||
.send_message2(alice.clone(), state2.next_message())
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(state2)
|
|
||||||
}
|
|
@ -1,27 +1,31 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
bob::{event_loop::EventLoopHandle, execution::negotiate},
|
bob::event_loop::EventLoopHandle,
|
||||||
|
state,
|
||||||
|
state::{Bob, Swap},
|
||||||
storage::Database,
|
storage::Database,
|
||||||
SwapAmounts,
|
SwapAmounts,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::{bail, Result};
|
||||||
use async_recursion::async_recursion;
|
use async_recursion::async_recursion;
|
||||||
|
use futures::{
|
||||||
|
future::{select, Either},
|
||||||
|
pin_mut,
|
||||||
|
};
|
||||||
use libp2p::{core::Multiaddr, PeerId};
|
use libp2p::{core::Multiaddr, PeerId};
|
||||||
use rand::{CryptoRng, RngCore};
|
use rand::{CryptoRng, RngCore};
|
||||||
use std::{fmt, sync::Arc};
|
use std::{convert::TryFrom, fmt, sync::Arc};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use xmr_btc::{
|
use xmr_btc::{
|
||||||
bob::{self},
|
bob::{self, State2},
|
||||||
Epoch,
|
Epoch,
|
||||||
};
|
};
|
||||||
|
|
||||||
// The same data structure is used for swap execution and recovery.
|
#[derive(Debug, Clone)]
|
||||||
// This allows for a seamless transition from a failed swap to recovery.
|
|
||||||
pub enum BobState {
|
pub enum BobState {
|
||||||
Started {
|
Started {
|
||||||
state0: bob::State0,
|
state0: bob::State0,
|
||||||
amounts: SwapAmounts,
|
amounts: SwapAmounts,
|
||||||
peer_id: PeerId,
|
|
||||||
addr: Multiaddr,
|
addr: Multiaddr,
|
||||||
},
|
},
|
||||||
Negotiated(bob::State2, PeerId),
|
Negotiated(bob::State2, PeerId),
|
||||||
@ -54,6 +58,49 @@ impl fmt::Display for BobState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<BobState> for state::Bob {
|
||||||
|
fn from(bob_state: BobState) -> Self {
|
||||||
|
match bob_state {
|
||||||
|
BobState::Started { .. } => {
|
||||||
|
// TODO: Do we want to resume just started swaps
|
||||||
|
unimplemented!("Cannot save a swap that has just started")
|
||||||
|
}
|
||||||
|
BobState::Negotiated(state2, peer_id) => Bob::Negotiated { state2, peer_id },
|
||||||
|
BobState::BtcLocked(state3, peer_id) => Bob::BtcLocked { state3, peer_id },
|
||||||
|
BobState::XmrLocked(state4, peer_id) => Bob::XmrLocked { state4, peer_id },
|
||||||
|
BobState::EncSigSent(state4, peer_id) => Bob::EncSigSent { state4, peer_id },
|
||||||
|
BobState::BtcRedeemed(state5) => Bob::BtcRedeemed(state5),
|
||||||
|
BobState::Cancelled(state4) => Bob::BtcCancelled(state4),
|
||||||
|
BobState::BtcRefunded(_)
|
||||||
|
| BobState::XmrRedeemed
|
||||||
|
| BobState::Punished
|
||||||
|
| BobState::SafelyAborted => Bob::SwapComplete,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<state::Swap> for BobState {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(db_state: state::Swap) -> Result<Self, Self::Error> {
|
||||||
|
if let Swap::Bob(state) = db_state {
|
||||||
|
let bob_State = match state {
|
||||||
|
Bob::Negotiated { state2, peer_id } => BobState::Negotiated(state2, peer_id),
|
||||||
|
Bob::BtcLocked { state3, peer_id } => BobState::BtcLocked(state3, peer_id),
|
||||||
|
Bob::XmrLocked { state4, peer_id } => BobState::XmrLocked(state4, peer_id),
|
||||||
|
Bob::EncSigSent { state4, peer_id } => BobState::EncSigSent(state4, peer_id),
|
||||||
|
Bob::BtcRedeemed(state5) => BobState::BtcRedeemed(state5),
|
||||||
|
Bob::BtcCancelled(state4) => BobState::Cancelled(state4),
|
||||||
|
Bob::SwapComplete => BobState::SafelyAborted,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(bob_State)
|
||||||
|
} else {
|
||||||
|
bail!("Bob swap state expected.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn swap<R>(
|
pub async fn swap<R>(
|
||||||
state: BobState,
|
state: BobState,
|
||||||
event_loop_handle: EventLoopHandle,
|
event_loop_handle: EventLoopHandle,
|
||||||
@ -79,6 +126,32 @@ where
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn resume_from_database<R>(
|
||||||
|
event_loop_handle: EventLoopHandle,
|
||||||
|
db: Database,
|
||||||
|
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
||||||
|
monero_wallet: Arc<crate::monero::Wallet>,
|
||||||
|
rng: R,
|
||||||
|
swap_id: Uuid,
|
||||||
|
) -> Result<BobState>
|
||||||
|
where
|
||||||
|
R: RngCore + CryptoRng + Send,
|
||||||
|
{
|
||||||
|
let db_swap = db.get_state(swap_id)?;
|
||||||
|
let start_state = BobState::try_from(db_swap)?;
|
||||||
|
let state = swap(
|
||||||
|
start_state,
|
||||||
|
event_loop_handle,
|
||||||
|
db,
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
rng,
|
||||||
|
swap_id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(state)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_complete(state: &BobState) -> bool {
|
pub fn is_complete(state: &BobState) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
state,
|
state,
|
||||||
@ -97,6 +170,10 @@ pub fn is_xmr_locked(state: &BobState) -> bool {
|
|||||||
matches!(state, BobState::XmrLocked(..))
|
matches!(state, BobState::XmrLocked(..))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_encsig_sent(state: &BobState) -> bool {
|
||||||
|
matches!(state, BobState::EncSigSent(..))
|
||||||
|
}
|
||||||
|
|
||||||
// State machine driver for swap execution
|
// State machine driver for swap execution
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[async_recursion]
|
#[async_recursion]
|
||||||
@ -121,10 +198,9 @@ where
|
|||||||
BobState::Started {
|
BobState::Started {
|
||||||
state0,
|
state0,
|
||||||
amounts,
|
amounts,
|
||||||
peer_id,
|
|
||||||
addr,
|
addr,
|
||||||
} => {
|
} => {
|
||||||
let state2 = negotiate(
|
let (state2, alice_peer_id) = negotiate(
|
||||||
state0,
|
state0,
|
||||||
amounts,
|
amounts,
|
||||||
&mut event_loop_handle,
|
&mut event_loop_handle,
|
||||||
@ -133,8 +209,13 @@ where
|
|||||||
bitcoin_wallet.clone(),
|
bitcoin_wallet.clone(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let state = BobState::Negotiated(state2, alice_peer_id);
|
||||||
|
let db_state = state.clone().into();
|
||||||
|
db.insert_latest_state(swap_id, state::Swap::Bob(db_state))
|
||||||
|
.await?;
|
||||||
run_until(
|
run_until(
|
||||||
BobState::Negotiated(state2, peer_id),
|
state,
|
||||||
is_target_state,
|
is_target_state,
|
||||||
event_loop_handle,
|
event_loop_handle,
|
||||||
db,
|
db,
|
||||||
@ -148,9 +229,13 @@ where
|
|||||||
BobState::Negotiated(state2, alice_peer_id) => {
|
BobState::Negotiated(state2, alice_peer_id) => {
|
||||||
// Alice and Bob have exchanged info
|
// Alice and Bob have exchanged info
|
||||||
let state3 = state2.lock_btc(bitcoin_wallet.as_ref()).await?;
|
let state3 = state2.lock_btc(bitcoin_wallet.as_ref()).await?;
|
||||||
// db.insert_latest_state(state);
|
|
||||||
|
let state = BobState::BtcLocked(state3, alice_peer_id);
|
||||||
|
let db_state = state.clone().into();
|
||||||
|
db.insert_latest_state(swap_id, state::Swap::Bob(db_state))
|
||||||
|
.await?;
|
||||||
run_until(
|
run_until(
|
||||||
BobState::BtcLocked(state3, alice_peer_id),
|
state,
|
||||||
is_target_state,
|
is_target_state,
|
||||||
event_loop_handle,
|
event_loop_handle,
|
||||||
db,
|
db,
|
||||||
@ -170,8 +255,12 @@ where
|
|||||||
.watch_for_lock_xmr(monero_wallet.as_ref(), msg2)
|
.watch_for_lock_xmr(monero_wallet.as_ref(), msg2)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let state = BobState::XmrLocked(state4, alice_peer_id);
|
||||||
|
let db_state = state.clone().into();
|
||||||
|
db.insert_latest_state(swap_id, state::Swap::Bob(db_state))
|
||||||
|
.await?;
|
||||||
run_until(
|
run_until(
|
||||||
BobState::XmrLocked(state4, alice_peer_id),
|
state,
|
||||||
is_target_state,
|
is_target_state,
|
||||||
event_loop_handle,
|
event_loop_handle,
|
||||||
db,
|
db,
|
||||||
@ -194,8 +283,12 @@ where
|
|||||||
.send_message3(alice_peer_id.clone(), tx_redeem_encsig)
|
.send_message3(alice_peer_id.clone(), tx_redeem_encsig)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let state = BobState::EncSigSent(state, alice_peer_id);
|
||||||
|
let db_state = state.clone().into();
|
||||||
|
db.insert_latest_state(swap_id, state::Swap::Bob(db_state))
|
||||||
|
.await?;
|
||||||
run_until(
|
run_until(
|
||||||
BobState::EncSigSent(state, alice_peer_id),
|
state,
|
||||||
is_target_state,
|
is_target_state,
|
||||||
event_loop_handle,
|
event_loop_handle,
|
||||||
db,
|
db,
|
||||||
@ -207,51 +300,55 @@ where
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
BobState::EncSigSent(state, ..) => {
|
BobState::EncSigSent(state, ..) => {
|
||||||
// Watch for redeem
|
let state_clone = state.clone();
|
||||||
let redeem_watcher = state.watch_for_redeem_btc(bitcoin_wallet.as_ref());
|
let redeem_watcher = state_clone.watch_for_redeem_btc(bitcoin_wallet.as_ref());
|
||||||
let t1_timeout = state.wait_for_t1(bitcoin_wallet.as_ref());
|
let t1_timeout = state_clone.wait_for_t1(bitcoin_wallet.as_ref());
|
||||||
|
|
||||||
tokio::select! {
|
pin_mut!(redeem_watcher);
|
||||||
val = redeem_watcher => {
|
pin_mut!(t1_timeout);
|
||||||
run_until(
|
|
||||||
BobState::BtcRedeemed(val?),
|
let state = match select(redeem_watcher, t1_timeout).await {
|
||||||
is_target_state,
|
Either::Left((val, _)) => BobState::BtcRedeemed(val?),
|
||||||
event_loop_handle,
|
Either::Right((..)) => {
|
||||||
db,
|
|
||||||
bitcoin_wallet,
|
|
||||||
monero_wallet,
|
|
||||||
rng,
|
|
||||||
swap_id,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
_ = t1_timeout => {
|
|
||||||
// Check whether TxCancel has been published.
|
// Check whether TxCancel has been published.
|
||||||
// We should not fail if the transaction is already on the blockchain
|
// We should not fail if the transaction is already on the blockchain
|
||||||
if state.check_for_tx_cancel(bitcoin_wallet.as_ref()).await.is_err() {
|
if state
|
||||||
|
.check_for_tx_cancel(bitcoin_wallet.as_ref())
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
state.submit_tx_cancel(bitcoin_wallet.as_ref()).await?;
|
state.submit_tx_cancel(bitcoin_wallet.as_ref()).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
run_until(
|
BobState::Cancelled(state)
|
||||||
BobState::Cancelled(state),
|
|
||||||
is_target_state,
|
|
||||||
event_loop_handle,
|
|
||||||
db,
|
|
||||||
bitcoin_wallet,
|
|
||||||
monero_wallet,
|
|
||||||
rng,
|
|
||||||
swap_id
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
let db_state = state.clone().into();
|
||||||
|
db.insert_latest_state(swap_id, state::Swap::Bob(db_state))
|
||||||
|
.await?;
|
||||||
|
run_until(
|
||||||
|
state,
|
||||||
|
is_target_state,
|
||||||
|
event_loop_handle,
|
||||||
|
db,
|
||||||
|
bitcoin_wallet.clone(),
|
||||||
|
monero_wallet,
|
||||||
|
rng,
|
||||||
|
swap_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
BobState::BtcRedeemed(state) => {
|
BobState::BtcRedeemed(state) => {
|
||||||
// Bob redeems XMR using revealed s_a
|
// Bob redeems XMR using revealed s_a
|
||||||
state.claim_xmr(monero_wallet.as_ref()).await?;
|
state.claim_xmr(monero_wallet.as_ref()).await?;
|
||||||
|
|
||||||
|
let state = BobState::XmrRedeemed;
|
||||||
|
let db_state = state.clone().into();
|
||||||
|
db.insert_latest_state(swap_id, state::Swap::Bob(db_state))
|
||||||
|
.await?;
|
||||||
run_until(
|
run_until(
|
||||||
BobState::XmrRedeemed,
|
state,
|
||||||
is_target_state,
|
is_target_state,
|
||||||
event_loop_handle,
|
event_loop_handle,
|
||||||
db,
|
db,
|
||||||
@ -263,37 +360,31 @@ where
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
BobState::Cancelled(state) => {
|
BobState::Cancelled(state) => {
|
||||||
|
// TODO
|
||||||
// Bob has cancelled the swap
|
// Bob has cancelled the swap
|
||||||
match state.current_epoch(bitcoin_wallet.as_ref()).await? {
|
let state = match state.current_epoch(bitcoin_wallet.as_ref()).await? {
|
||||||
Epoch::T0 => panic!("Cancelled before t1??? Something is really wrong"),
|
Epoch::T0 => panic!("Cancelled before t1??? Something is really wrong"),
|
||||||
Epoch::T1 => {
|
Epoch::T1 => {
|
||||||
state.refund_btc(bitcoin_wallet.as_ref()).await?;
|
state.refund_btc(bitcoin_wallet.as_ref()).await?;
|
||||||
run_until(
|
BobState::BtcRefunded(state)
|
||||||
BobState::BtcRefunded(state),
|
|
||||||
is_target_state,
|
|
||||||
event_loop_handle,
|
|
||||||
db,
|
|
||||||
bitcoin_wallet,
|
|
||||||
monero_wallet,
|
|
||||||
rng,
|
|
||||||
swap_id,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
Epoch::T2 => {
|
Epoch::T2 => BobState::Punished,
|
||||||
run_until(
|
};
|
||||||
BobState::Punished,
|
|
||||||
is_target_state,
|
let db_state = state.clone().into();
|
||||||
event_loop_handle,
|
db.insert_latest_state(swap_id, state::Swap::Bob(db_state))
|
||||||
db,
|
.await?;
|
||||||
bitcoin_wallet,
|
run_until(
|
||||||
monero_wallet,
|
state,
|
||||||
rng,
|
is_target_state,
|
||||||
swap_id,
|
event_loop_handle,
|
||||||
)
|
db,
|
||||||
.await
|
bitcoin_wallet,
|
||||||
}
|
monero_wallet,
|
||||||
}
|
rng,
|
||||||
|
swap_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
BobState::BtcRefunded(state4) => Ok(BobState::BtcRefunded(state4)),
|
BobState::BtcRefunded(state4) => Ok(BobState::BtcRefunded(state4)),
|
||||||
BobState::Punished => Ok(BobState::Punished),
|
BobState::Punished => Ok(BobState::Punished),
|
||||||
@ -302,3 +393,42 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn negotiate<R>(
|
||||||
|
state0: xmr_btc::bob::State0,
|
||||||
|
amounts: SwapAmounts,
|
||||||
|
swarm: &mut EventLoopHandle,
|
||||||
|
addr: Multiaddr,
|
||||||
|
mut rng: R,
|
||||||
|
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
||||||
|
) -> Result<(State2, PeerId)>
|
||||||
|
where
|
||||||
|
R: RngCore + CryptoRng + Send,
|
||||||
|
{
|
||||||
|
tracing::trace!("Starting negotiate");
|
||||||
|
swarm.dial_alice(addr).await?;
|
||||||
|
|
||||||
|
let alice_peer_id = swarm.recv_conn_established().await?;
|
||||||
|
|
||||||
|
swarm
|
||||||
|
.request_amounts(alice_peer_id.clone(), amounts.btc)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
swarm
|
||||||
|
.send_message0(alice_peer_id.clone(), state0.next_message(&mut rng))
|
||||||
|
.await?;
|
||||||
|
let msg0 = swarm.recv_message0().await?;
|
||||||
|
let state1 = state0.receive(bitcoin_wallet.as_ref(), msg0).await?;
|
||||||
|
|
||||||
|
swarm
|
||||||
|
.send_message1(alice_peer_id.clone(), state1.next_message())
|
||||||
|
.await?;
|
||||||
|
let msg1 = swarm.recv_message1().await?;
|
||||||
|
let state2 = state1.receive(msg1)?;
|
||||||
|
|
||||||
|
swarm
|
||||||
|
.send_message2(alice_peer_id.clone(), state2.next_message())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok((state2, alice_peer_id))
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use libp2p::{core::Multiaddr, PeerId};
|
use libp2p::core::Multiaddr;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@ -40,9 +40,6 @@ pub enum Options {
|
|||||||
#[structopt(short = "a", long = "connect-addr")]
|
#[structopt(short = "a", long = "connect-addr")]
|
||||||
alice_addr: Multiaddr,
|
alice_addr: Multiaddr,
|
||||||
|
|
||||||
#[structopt(short = "p", long = "connect-peer-id")]
|
|
||||||
alice_peer_id: PeerId,
|
|
||||||
|
|
||||||
#[structopt(
|
#[structopt(
|
||||||
short = "b",
|
short = "b",
|
||||||
long = "bitcoind",
|
long = "bitcoind",
|
||||||
@ -67,7 +64,7 @@ pub enum Options {
|
|||||||
receive_monero: xmr_btc::monero::Amount,
|
receive_monero: xmr_btc::monero::Amount,
|
||||||
},
|
},
|
||||||
History,
|
History,
|
||||||
Recover {
|
Resume {
|
||||||
#[structopt(required = true)]
|
#[structopt(required = true)]
|
||||||
swap_id: Uuid,
|
swap_id: Uuid,
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use ::serde::{Deserialize, Serialize};
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
|
|
||||||
pub mod alice;
|
pub mod alice;
|
||||||
@ -9,7 +9,7 @@ pub mod bob;
|
|||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod monero;
|
pub mod monero;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod recover;
|
pub mod serde;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod storage;
|
pub mod storage;
|
||||||
pub mod tor;
|
pub mod tor;
|
||||||
@ -31,7 +31,7 @@ pub enum Rsp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// XMR/BTC swap amounts.
|
/// XMR/BTC swap amounts.
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
// TODO(Franck): review necessity of this struct
|
// TODO(Franck): review necessity of this struct
|
||||||
pub struct SwapAmounts {
|
pub struct SwapAmounts {
|
||||||
/// Amount of BTC to swap.
|
/// Amount of BTC to swap.
|
||||||
|
@ -1,463 +0,0 @@
|
|||||||
//! This module is used to attempt to recover an unfinished swap.
|
|
||||||
//!
|
|
||||||
//! Recovery is only supported for certain states and the strategy followed is
|
|
||||||
//! to perform the simplest steps that require no further action from the
|
|
||||||
//! counterparty.
|
|
||||||
//!
|
|
||||||
//! The quality of this module is bad because there is a lot of code
|
|
||||||
//! duplication, both within the module and with respect to
|
|
||||||
//! `xmr_btc/src/{alice,bob}.rs`. In my opinion, a better approach to support
|
|
||||||
//! swap recovery would be through the `action_generator`s themselves, but this
|
|
||||||
//! was deemed too complicated for the time being.
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
bitcoin, monero,
|
|
||||||
monero::CreateWalletForOutput,
|
|
||||||
state::{Alice, Bob, Swap},
|
|
||||||
};
|
|
||||||
use anyhow::Result;
|
|
||||||
use bitcoin_harness::BitcoindRpcApi;
|
|
||||||
use ecdsa_fun::{adaptor::Adaptor, nonce::Deterministic};
|
|
||||||
use futures::{
|
|
||||||
future::{select, Either},
|
|
||||||
pin_mut,
|
|
||||||
};
|
|
||||||
use sha2::Sha256;
|
|
||||||
use tracing::info;
|
|
||||||
use xmr_btc::bitcoin::{
|
|
||||||
poll_until_block_height_is_gte, BroadcastSignedTransaction, TransactionBlockHeight,
|
|
||||||
WatchForRawTransaction,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn recover(
|
|
||||||
bitcoin_wallet: bitcoin::Wallet,
|
|
||||||
monero_wallet: monero::Wallet,
|
|
||||||
state: Swap,
|
|
||||||
) -> Result<()> {
|
|
||||||
match state {
|
|
||||||
Swap::Alice(state) => alice_recover(bitcoin_wallet, monero_wallet, state).await,
|
|
||||||
Swap::Bob(state) => bob_recover(bitcoin_wallet, monero_wallet, state).await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn alice_recover(
|
|
||||||
bitcoin_wallet: bitcoin::Wallet,
|
|
||||||
monero_wallet: monero::Wallet,
|
|
||||||
state: Alice,
|
|
||||||
) -> Result<()> {
|
|
||||||
match state {
|
|
||||||
Alice::Negotiated(_) | Alice::BtcLocked(_) | Alice::SwapComplete => {
|
|
||||||
info!("Nothing to do");
|
|
||||||
}
|
|
||||||
Alice::XmrLocked(state) => {
|
|
||||||
info!("Monero still locked up");
|
|
||||||
|
|
||||||
let tx_cancel = bitcoin::TxCancel::new(
|
|
||||||
&state.tx_lock,
|
|
||||||
state.refund_timelock,
|
|
||||||
state.a.public(),
|
|
||||||
state.B,
|
|
||||||
);
|
|
||||||
|
|
||||||
info!("Checking if the Bitcoin cancel transaction has been published");
|
|
||||||
if bitcoin_wallet
|
|
||||||
.inner
|
|
||||||
.get_raw_transaction(tx_cancel.txid())
|
|
||||||
.await
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
info!("Bitcoin cancel transaction not yet published");
|
|
||||||
|
|
||||||
let tx_lock_height = bitcoin_wallet
|
|
||||||
.transaction_block_height(state.tx_lock.txid())
|
|
||||||
.await;
|
|
||||||
poll_until_block_height_is_gte(
|
|
||||||
&bitcoin_wallet,
|
|
||||||
tx_lock_height + state.refund_timelock,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let sig_a = state.a.sign(tx_cancel.digest());
|
|
||||||
let sig_b = state.tx_cancel_sig_bob.clone();
|
|
||||||
|
|
||||||
let tx_cancel = tx_cancel
|
|
||||||
.clone()
|
|
||||||
.add_signatures(&state.tx_lock, (state.a.public(), sig_a), (state.B, sig_b))
|
|
||||||
.expect("sig_{a,b} to be valid signatures for tx_cancel");
|
|
||||||
|
|
||||||
// TODO: We should not fail if the transaction is already on the blockchain
|
|
||||||
bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(tx_cancel)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Confirmed that Bitcoin cancel transaction is on the blockchain");
|
|
||||||
|
|
||||||
let tx_cancel_height = bitcoin_wallet
|
|
||||||
.transaction_block_height(tx_cancel.txid())
|
|
||||||
.await;
|
|
||||||
let poll_until_bob_can_be_punished = poll_until_block_height_is_gte(
|
|
||||||
&bitcoin_wallet,
|
|
||||||
tx_cancel_height + state.punish_timelock,
|
|
||||||
);
|
|
||||||
pin_mut!(poll_until_bob_can_be_punished);
|
|
||||||
|
|
||||||
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &state.refund_address);
|
|
||||||
|
|
||||||
info!("Waiting for either Bitcoin refund or punish timelock");
|
|
||||||
match select(
|
|
||||||
bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid()),
|
|
||||||
poll_until_bob_can_be_punished,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Either::Left((tx_refund_published, ..)) => {
|
|
||||||
info!("Found Bitcoin refund transaction");
|
|
||||||
|
|
||||||
let s_a = monero::PrivateKey {
|
|
||||||
scalar: state.s_a.into_ed25519(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let tx_refund_sig = tx_refund
|
|
||||||
.extract_signature_by_key(tx_refund_published, state.a.public())?;
|
|
||||||
let tx_refund_encsig = state.a.encsign(state.S_b_bitcoin, tx_refund.digest());
|
|
||||||
|
|
||||||
let s_b = bitcoin::recover(state.S_b_bitcoin, tx_refund_sig, tx_refund_encsig)?;
|
|
||||||
let s_b = monero::PrivateKey::from_scalar(
|
|
||||||
monero::Scalar::from_bytes_mod_order(s_b.to_bytes()),
|
|
||||||
);
|
|
||||||
|
|
||||||
monero_wallet
|
|
||||||
.create_and_load_wallet_for_output(s_a + s_b, state.v)
|
|
||||||
.await?;
|
|
||||||
info!("Successfully refunded monero");
|
|
||||||
}
|
|
||||||
Either::Right(_) => {
|
|
||||||
info!("Punish timelock reached, attempting to punish Bob");
|
|
||||||
|
|
||||||
let tx_punish = bitcoin::TxPunish::new(
|
|
||||||
&tx_cancel,
|
|
||||||
&state.punish_address,
|
|
||||||
state.punish_timelock,
|
|
||||||
);
|
|
||||||
|
|
||||||
let sig_a = state.a.sign(tx_punish.digest());
|
|
||||||
let sig_b = state.tx_punish_sig_bob.clone();
|
|
||||||
|
|
||||||
let sig_tx_punish = tx_punish.add_signatures(
|
|
||||||
&tx_cancel,
|
|
||||||
(state.a.public(), sig_a),
|
|
||||||
(state.B, sig_b),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(sig_tx_punish)
|
|
||||||
.await?;
|
|
||||||
info!("Successfully punished Bob's inactivity by taking bitcoin");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Alice::BtcRedeemable { redeem_tx, state } => {
|
|
||||||
info!("Have the means to redeem the Bitcoin");
|
|
||||||
|
|
||||||
let tx_lock_height = bitcoin_wallet
|
|
||||||
.transaction_block_height(state.tx_lock.txid())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let block_height = bitcoin_wallet.inner.client.getblockcount().await?;
|
|
||||||
let refund_absolute_expiry = tx_lock_height + state.refund_timelock;
|
|
||||||
|
|
||||||
info!("Checking refund timelock");
|
|
||||||
if block_height < refund_absolute_expiry {
|
|
||||||
info!("Safe to redeem");
|
|
||||||
|
|
||||||
bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(redeem_tx)
|
|
||||||
.await?;
|
|
||||||
info!("Successfully redeemed bitcoin");
|
|
||||||
} else {
|
|
||||||
info!("Refund timelock reached");
|
|
||||||
|
|
||||||
let tx_cancel = bitcoin::TxCancel::new(
|
|
||||||
&state.tx_lock,
|
|
||||||
state.refund_timelock,
|
|
||||||
state.a.public(),
|
|
||||||
state.B,
|
|
||||||
);
|
|
||||||
|
|
||||||
info!("Checking if the Bitcoin cancel transaction has been published");
|
|
||||||
if bitcoin_wallet
|
|
||||||
.inner
|
|
||||||
.get_raw_transaction(tx_cancel.txid())
|
|
||||||
.await
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
let sig_a = state.a.sign(tx_cancel.digest());
|
|
||||||
let sig_b = state.tx_cancel_sig_bob.clone();
|
|
||||||
|
|
||||||
let tx_cancel = tx_cancel
|
|
||||||
.clone()
|
|
||||||
.add_signatures(&state.tx_lock, (state.a.public(), sig_a), (state.B, sig_b))
|
|
||||||
.expect("sig_{a,b} to be valid signatures for tx_cancel");
|
|
||||||
|
|
||||||
// TODO: We should not fail if the transaction is already on the blockchain
|
|
||||||
bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(tx_cancel)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Confirmed that Bitcoin cancel transaction is on the blockchain");
|
|
||||||
|
|
||||||
let tx_cancel_height = bitcoin_wallet
|
|
||||||
.transaction_block_height(tx_cancel.txid())
|
|
||||||
.await;
|
|
||||||
let poll_until_bob_can_be_punished = poll_until_block_height_is_gte(
|
|
||||||
&bitcoin_wallet,
|
|
||||||
tx_cancel_height + state.punish_timelock,
|
|
||||||
);
|
|
||||||
pin_mut!(poll_until_bob_can_be_punished);
|
|
||||||
|
|
||||||
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &state.refund_address);
|
|
||||||
|
|
||||||
info!("Waiting for either Bitcoin refund or punish timelock");
|
|
||||||
match select(
|
|
||||||
bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid()),
|
|
||||||
poll_until_bob_can_be_punished,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Either::Left((tx_refund_published, ..)) => {
|
|
||||||
info!("Found Bitcoin refund transaction");
|
|
||||||
|
|
||||||
let s_a = monero::PrivateKey {
|
|
||||||
scalar: state.s_a.into_ed25519(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let tx_refund_sig = tx_refund
|
|
||||||
.extract_signature_by_key(tx_refund_published, state.a.public())?;
|
|
||||||
let tx_refund_encsig =
|
|
||||||
state.a.encsign(state.S_b_bitcoin, tx_refund.digest());
|
|
||||||
|
|
||||||
let s_b =
|
|
||||||
bitcoin::recover(state.S_b_bitcoin, tx_refund_sig, tx_refund_encsig)?;
|
|
||||||
let s_b = monero::PrivateKey::from_scalar(
|
|
||||||
monero::Scalar::from_bytes_mod_order(s_b.to_bytes()),
|
|
||||||
);
|
|
||||||
|
|
||||||
monero_wallet
|
|
||||||
.create_and_load_wallet_for_output(s_a + s_b, state.v)
|
|
||||||
.await?;
|
|
||||||
info!("Successfully refunded monero");
|
|
||||||
}
|
|
||||||
Either::Right(_) => {
|
|
||||||
info!("Punish timelock reached, attempting to punish Bob");
|
|
||||||
|
|
||||||
let tx_punish = bitcoin::TxPunish::new(
|
|
||||||
&tx_cancel,
|
|
||||||
&state.punish_address,
|
|
||||||
state.punish_timelock,
|
|
||||||
);
|
|
||||||
|
|
||||||
let sig_a = state.a.sign(tx_punish.digest());
|
|
||||||
let sig_b = state.tx_punish_sig_bob.clone();
|
|
||||||
|
|
||||||
let sig_tx_punish = tx_punish.add_signatures(
|
|
||||||
&tx_cancel,
|
|
||||||
(state.a.public(), sig_a),
|
|
||||||
(state.B, sig_b),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(sig_tx_punish)
|
|
||||||
.await?;
|
|
||||||
info!("Successfully punished Bob's inactivity by taking bitcoin");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Alice::BtcPunishable(state) => {
|
|
||||||
info!("Punish timelock reached, attempting to punish Bob");
|
|
||||||
|
|
||||||
let tx_cancel = bitcoin::TxCancel::new(
|
|
||||||
&state.tx_lock,
|
|
||||||
state.refund_timelock,
|
|
||||||
state.a.public(),
|
|
||||||
state.B,
|
|
||||||
);
|
|
||||||
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &state.refund_address);
|
|
||||||
|
|
||||||
info!("Checking if Bitcoin has already been refunded");
|
|
||||||
|
|
||||||
// TODO: Protect against transient errors so that we can correctly decide if the
|
|
||||||
// bitcoin has been refunded
|
|
||||||
match bitcoin_wallet
|
|
||||||
.inner
|
|
||||||
.get_raw_transaction(tx_refund.txid())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(tx_refund_published) => {
|
|
||||||
info!("Bitcoin already refunded");
|
|
||||||
|
|
||||||
let s_a = monero::PrivateKey {
|
|
||||||
scalar: state.s_a.into_ed25519(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let tx_refund_sig = tx_refund
|
|
||||||
.extract_signature_by_key(tx_refund_published, state.a.public())?;
|
|
||||||
let tx_refund_encsig = state.a.encsign(state.S_b_bitcoin, tx_refund.digest());
|
|
||||||
|
|
||||||
let s_b = bitcoin::recover(state.S_b_bitcoin, tx_refund_sig, tx_refund_encsig)?;
|
|
||||||
let s_b = monero::PrivateKey::from_scalar(
|
|
||||||
monero::Scalar::from_bytes_mod_order(s_b.to_bytes()),
|
|
||||||
);
|
|
||||||
|
|
||||||
monero_wallet
|
|
||||||
.create_and_load_wallet_for_output(s_a + s_b, state.v)
|
|
||||||
.await?;
|
|
||||||
info!("Successfully refunded monero");
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
info!("Bitcoin not yet refunded");
|
|
||||||
|
|
||||||
let tx_punish = bitcoin::TxPunish::new(
|
|
||||||
&tx_cancel,
|
|
||||||
&state.punish_address,
|
|
||||||
state.punish_timelock,
|
|
||||||
);
|
|
||||||
|
|
||||||
let sig_a = state.a.sign(tx_punish.digest());
|
|
||||||
let sig_b = state.tx_punish_sig_bob.clone();
|
|
||||||
|
|
||||||
let sig_tx_punish = tx_punish.add_signatures(
|
|
||||||
&tx_cancel,
|
|
||||||
(state.a.public(), sig_a),
|
|
||||||
(state.B, sig_b),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(sig_tx_punish)
|
|
||||||
.await?;
|
|
||||||
info!("Successfully punished Bob's inactivity by taking bitcoin");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Alice::BtcRefunded {
|
|
||||||
view_key,
|
|
||||||
spend_key,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
info!("Bitcoin was refunded, attempting to refund monero");
|
|
||||||
|
|
||||||
monero_wallet
|
|
||||||
.create_and_load_wallet_for_output(spend_key, view_key)
|
|
||||||
.await?;
|
|
||||||
info!("Successfully refunded monero");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn bob_recover(
|
|
||||||
bitcoin_wallet: crate::bitcoin::Wallet,
|
|
||||||
monero_wallet: crate::monero::Wallet,
|
|
||||||
state: Bob,
|
|
||||||
) -> Result<()> {
|
|
||||||
match state {
|
|
||||||
Bob::Handshaken(_) | Bob::SwapComplete => {
|
|
||||||
info!("Nothing to do");
|
|
||||||
}
|
|
||||||
Bob::BtcLocked(state) | Bob::XmrLocked(state) | Bob::BtcRefundable(state) => {
|
|
||||||
info!("Bitcoin may still be locked up, attempting to refund");
|
|
||||||
|
|
||||||
let tx_cancel = bitcoin::TxCancel::new(
|
|
||||||
&state.tx_lock,
|
|
||||||
state.refund_timelock,
|
|
||||||
state.A,
|
|
||||||
state.b.public(),
|
|
||||||
);
|
|
||||||
|
|
||||||
info!("Checking if the Bitcoin cancel transaction has been published");
|
|
||||||
if bitcoin_wallet
|
|
||||||
.inner
|
|
||||||
.get_raw_transaction(tx_cancel.txid())
|
|
||||||
.await
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
info!("Bitcoin cancel transaction not yet published");
|
|
||||||
|
|
||||||
let tx_lock_height = bitcoin_wallet
|
|
||||||
.transaction_block_height(state.tx_lock.txid())
|
|
||||||
.await;
|
|
||||||
poll_until_block_height_is_gte(
|
|
||||||
&bitcoin_wallet,
|
|
||||||
tx_lock_height + state.refund_timelock,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let sig_a = state.tx_cancel_sig_a.clone();
|
|
||||||
let sig_b = state.b.sign(tx_cancel.digest());
|
|
||||||
|
|
||||||
let tx_cancel = tx_cancel
|
|
||||||
.clone()
|
|
||||||
.add_signatures(&state.tx_lock, (state.A, sig_a), (state.b.public(), sig_b))
|
|
||||||
.expect("sig_{a,b} to be valid signatures for tx_cancel");
|
|
||||||
|
|
||||||
// TODO: We should not fail if the transaction is already on the blockchain
|
|
||||||
bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(tx_cancel)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Confirmed that Bitcoin cancel transaction is on the blockchain");
|
|
||||||
|
|
||||||
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &state.refund_address);
|
|
||||||
let signed_tx_refund = {
|
|
||||||
let adaptor = Adaptor::<Sha256, Deterministic<Sha256>>::default();
|
|
||||||
let sig_a = adaptor
|
|
||||||
.decrypt_signature(&state.s_b.into_secp256k1(), state.tx_refund_encsig.clone());
|
|
||||||
let sig_b = state.b.sign(tx_refund.digest());
|
|
||||||
|
|
||||||
tx_refund
|
|
||||||
.add_signatures(&tx_cancel, (state.A, sig_a), (state.b.public(), sig_b))
|
|
||||||
.expect("sig_{a,b} to be valid signatures for tx_refund")
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Check if Bitcoin has already been punished and provide a useful error
|
|
||||||
// message/log to the user if so
|
|
||||||
bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(signed_tx_refund)
|
|
||||||
.await?;
|
|
||||||
info!("Successfully refunded bitcoin");
|
|
||||||
}
|
|
||||||
Bob::BtcRedeemed(state) => {
|
|
||||||
info!("Bitcoin was redeemed, attempting to redeem monero");
|
|
||||||
|
|
||||||
let tx_redeem = bitcoin::TxRedeem::new(&state.tx_lock, &state.redeem_address);
|
|
||||||
let tx_redeem_published = bitcoin_wallet
|
|
||||||
.inner
|
|
||||||
.get_raw_transaction(tx_redeem.txid())
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let tx_redeem_encsig = state.b.encsign(state.S_a_bitcoin, tx_redeem.digest());
|
|
||||||
let tx_redeem_sig =
|
|
||||||
tx_redeem.extract_signature_by_key(tx_redeem_published, state.b.public())?;
|
|
||||||
|
|
||||||
let s_a = bitcoin::recover(state.S_a_bitcoin, tx_redeem_sig, tx_redeem_encsig)?;
|
|
||||||
let s_a = monero::PrivateKey::from_scalar(monero::Scalar::from_bytes_mod_order(
|
|
||||||
s_a.to_bytes(),
|
|
||||||
));
|
|
||||||
|
|
||||||
let s_b = monero::PrivateKey {
|
|
||||||
scalar: state.s_b.into_ed25519(),
|
|
||||||
};
|
|
||||||
|
|
||||||
monero_wallet
|
|
||||||
.create_and_load_wallet_for_output(s_a + s_b, state.v)
|
|
||||||
.await?;
|
|
||||||
info!("Successfully redeemed monero")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
47
swap/src/serde.rs
Normal file
47
swap/src/serde.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
pub mod peer_id {
|
||||||
|
use libp2p::PeerId;
|
||||||
|
use serde::{de::Error, Deserialize, Deserializer, Serializer};
|
||||||
|
|
||||||
|
pub fn serialize<S>(peer_id: &PeerId, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let string = peer_id.to_string();
|
||||||
|
serializer.serialize_str(&string)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<PeerId, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let string = String::deserialize(deserializer)?;
|
||||||
|
let peer_id = string.parse().map_err(D::Error::custom)?;
|
||||||
|
|
||||||
|
Ok(peer_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use serde::Serialize;
|
||||||
|
use spectral::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct SerializablePeerId(#[serde(with = "super")] PeerId);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn maker_id_serializes_as_expected() {
|
||||||
|
let peer_id = SerializablePeerId(
|
||||||
|
"QmfUfpC2frwFvcDzpspnfZitHt5wct6n4kpG5jzgRdsxkY"
|
||||||
|
.parse()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let got = serde_json::to_string(&peer_id).expect("failed to serialize peer id");
|
||||||
|
|
||||||
|
assert_that(&got)
|
||||||
|
.is_equal_to(r#""QmfUfpC2frwFvcDzpspnfZitHt5wct6n4kpG5jzgRdsxkY""#.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
|
use libp2p::PeerId;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use xmr_btc::{alice, bob, monero, serde::monero_private_key};
|
use xmr_btc::{alice, bitcoin::EncryptedSignature, bob, monero, serde::monero_private_key};
|
||||||
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
@ -15,10 +16,17 @@ pub enum Alice {
|
|||||||
Negotiated(alice::State3),
|
Negotiated(alice::State3),
|
||||||
BtcLocked(alice::State3),
|
BtcLocked(alice::State3),
|
||||||
XmrLocked(alice::State3),
|
XmrLocked(alice::State3),
|
||||||
|
// TODO(Franck): Delete this state as it is not used in alice::swap
|
||||||
BtcRedeemable {
|
BtcRedeemable {
|
||||||
state: alice::State3,
|
state: alice::State3,
|
||||||
redeem_tx: bitcoin::Transaction,
|
redeem_tx: bitcoin::Transaction,
|
||||||
},
|
},
|
||||||
|
EncSignLearned {
|
||||||
|
state: alice::State3,
|
||||||
|
encrypted_signature: EncryptedSignature,
|
||||||
|
},
|
||||||
|
T1Expired(alice::State3),
|
||||||
|
BtcCancelled(alice::State3),
|
||||||
BtcPunishable(alice::State3),
|
BtcPunishable(alice::State3),
|
||||||
BtcRefunded {
|
BtcRefunded {
|
||||||
state: alice::State3,
|
state: alice::State3,
|
||||||
@ -31,11 +39,28 @@ pub enum Alice {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
pub enum Bob {
|
pub enum Bob {
|
||||||
Handshaken(bob::State2),
|
Negotiated {
|
||||||
BtcLocked(bob::State2),
|
state2: bob::State2,
|
||||||
XmrLocked(bob::State2),
|
#[serde(with = "crate::serde::peer_id")]
|
||||||
BtcRedeemed(bob::State2),
|
peer_id: PeerId,
|
||||||
BtcRefundable(bob::State2),
|
},
|
||||||
|
BtcLocked {
|
||||||
|
state3: bob::State3,
|
||||||
|
#[serde(with = "crate::serde::peer_id")]
|
||||||
|
peer_id: PeerId,
|
||||||
|
},
|
||||||
|
XmrLocked {
|
||||||
|
state4: bob::State4,
|
||||||
|
#[serde(with = "crate::serde::peer_id")]
|
||||||
|
peer_id: PeerId,
|
||||||
|
},
|
||||||
|
EncSigSent {
|
||||||
|
state4: bob::State4,
|
||||||
|
#[serde(with = "crate::serde::peer_id")]
|
||||||
|
peer_id: PeerId,
|
||||||
|
},
|
||||||
|
BtcRedeemed(bob::State5),
|
||||||
|
BtcCancelled(bob::State4),
|
||||||
SwapComplete,
|
SwapComplete,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,9 +92,12 @@ impl Display for Alice {
|
|||||||
Alice::BtcLocked(_) => f.write_str("Bitcoin locked"),
|
Alice::BtcLocked(_) => f.write_str("Bitcoin locked"),
|
||||||
Alice::XmrLocked(_) => f.write_str("Monero locked"),
|
Alice::XmrLocked(_) => f.write_str("Monero locked"),
|
||||||
Alice::BtcRedeemable { .. } => f.write_str("Bitcoin redeemable"),
|
Alice::BtcRedeemable { .. } => f.write_str("Bitcoin redeemable"),
|
||||||
|
Alice::T1Expired(_) => f.write_str("Submitting TxCancel"),
|
||||||
|
Alice::BtcCancelled(_) => f.write_str("Bitcoin cancel transaction published"),
|
||||||
Alice::BtcPunishable(_) => f.write_str("Bitcoin punishable"),
|
Alice::BtcPunishable(_) => f.write_str("Bitcoin punishable"),
|
||||||
Alice::BtcRefunded { .. } => f.write_str("Monero refundable"),
|
Alice::BtcRefunded { .. } => f.write_str("Monero refundable"),
|
||||||
Alice::SwapComplete => f.write_str("Swap complete"),
|
Alice::SwapComplete => f.write_str("Swap complete"),
|
||||||
|
Alice::EncSignLearned { .. } => f.write_str("Encrypted signature learned"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,12 +105,13 @@ impl Display for Alice {
|
|||||||
impl Display for Bob {
|
impl Display for Bob {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Bob::Handshaken(_) => f.write_str("Handshake complete"),
|
Bob::Negotiated { .. } => f.write_str("Handshake complete"),
|
||||||
Bob::BtcLocked(_) | Bob::XmrLocked(_) | Bob::BtcRefundable(_) => {
|
Bob::BtcLocked { .. } | Bob::XmrLocked { .. } | Bob::BtcCancelled(_) => {
|
||||||
f.write_str("Bitcoin refundable")
|
f.write_str("Bitcoin refundable")
|
||||||
}
|
}
|
||||||
Bob::BtcRedeemed(_) => f.write_str("Monero redeemable"),
|
Bob::BtcRedeemed(_) => f.write_str("Monero redeemable"),
|
||||||
Bob::SwapComplete => f.write_str("Swap complete"),
|
Bob::SwapComplete => f.write_str("Swap complete"),
|
||||||
|
Bob::EncSigSent { .. } => f.write_str("Encrypted signature sent"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,574 +0,0 @@
|
|||||||
use bitcoin_harness::Bitcoind;
|
|
||||||
use futures::future::try_join;
|
|
||||||
use libp2p::{Multiaddr, PeerId};
|
|
||||||
use monero_harness::Monero;
|
|
||||||
use rand::rngs::OsRng;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use swap::{
|
|
||||||
alice, alice::swap::AliceState, bob, bob::swap::BobState, network::transport::build,
|
|
||||||
storage::Database, SwapAmounts,
|
|
||||||
};
|
|
||||||
use tempfile::tempdir;
|
|
||||||
use testcontainers::clients::Cli;
|
|
||||||
use tracing_core::dispatcher::DefaultGuard;
|
|
||||||
use tracing_log::LogTracer;
|
|
||||||
use uuid::Uuid;
|
|
||||||
use xmr_btc::{alice::State0, bitcoin, config::Config, cross_curve_dleq};
|
|
||||||
|
|
||||||
/// Run the following tests with RUST_MIN_STACK=10000000
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn happy_path() {
|
|
||||||
let _guard = init_tracing();
|
|
||||||
|
|
||||||
let cli = Cli::default();
|
|
||||||
let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap();
|
|
||||||
let _ = bitcoind.init(5).await;
|
|
||||||
let (monero, _container) =
|
|
||||||
Monero::new(&cli, None, vec!["alice".to_string(), "bob".to_string()])
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let btc_to_swap = bitcoin::Amount::from_sat(1_000_000);
|
|
||||||
let btc_alice = bitcoin::Amount::ZERO;
|
|
||||||
let btc_bob = btc_to_swap * 10;
|
|
||||||
|
|
||||||
// this xmr value matches the logic of alice::calculate_amounts i.e. btc *
|
|
||||||
// 10_000 * 100
|
|
||||||
let xmr_to_swap = xmr_btc::monero::Amount::from_piconero(1_000_000_000_000);
|
|
||||||
let xmr_alice = xmr_to_swap * 10;
|
|
||||||
let xmr_bob = xmr_btc::monero::Amount::from_piconero(0);
|
|
||||||
|
|
||||||
// todo: This should not be hardcoded
|
|
||||||
let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876"
|
|
||||||
.parse()
|
|
||||||
.expect("failed to parse Alice's address");
|
|
||||||
|
|
||||||
let config = Config::regtest();
|
|
||||||
|
|
||||||
let (
|
|
||||||
alice_state,
|
|
||||||
mut alice_swarm_driver,
|
|
||||||
alice_swarm_handle,
|
|
||||||
alice_btc_wallet,
|
|
||||||
alice_xmr_wallet,
|
|
||||||
alice_peer_id,
|
|
||||||
) = init_alice(
|
|
||||||
&bitcoind,
|
|
||||||
&monero,
|
|
||||||
btc_to_swap,
|
|
||||||
btc_alice,
|
|
||||||
xmr_to_swap,
|
|
||||||
xmr_alice,
|
|
||||||
alice_multiaddr.clone(),
|
|
||||||
config,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let (bob_state, bob_swarm_driver, bob_swarm_handle, bob_btc_wallet, bob_xmr_wallet, bob_db) =
|
|
||||||
init_bob(
|
|
||||||
alice_multiaddr,
|
|
||||||
alice_peer_id,
|
|
||||||
&bitcoind,
|
|
||||||
&monero,
|
|
||||||
btc_to_swap,
|
|
||||||
btc_bob,
|
|
||||||
xmr_to_swap,
|
|
||||||
xmr_bob,
|
|
||||||
config,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let alice_swap_fut = alice::swap::swap(
|
|
||||||
alice_state,
|
|
||||||
alice_swarm_handle,
|
|
||||||
alice_btc_wallet.clone(),
|
|
||||||
alice_xmr_wallet.clone(),
|
|
||||||
config,
|
|
||||||
);
|
|
||||||
|
|
||||||
let _alice_swarm_fut = tokio::spawn(async move { alice_swarm_driver.run().await });
|
|
||||||
|
|
||||||
let bob_swap_fut = bob::swap::swap(
|
|
||||||
bob_state,
|
|
||||||
bob_swarm_handle,
|
|
||||||
bob_db,
|
|
||||||
bob_btc_wallet.clone(),
|
|
||||||
bob_xmr_wallet.clone(),
|
|
||||||
OsRng,
|
|
||||||
Uuid::new_v4(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let _bob_swarm_fut = tokio::spawn(async move { bob_swarm_driver.run().await });
|
|
||||||
|
|
||||||
try_join(alice_swap_fut, bob_swap_fut).await.unwrap();
|
|
||||||
|
|
||||||
let btc_alice_final = alice_btc_wallet.as_ref().balance().await.unwrap();
|
|
||||||
let btc_bob_final = bob_btc_wallet.as_ref().balance().await.unwrap();
|
|
||||||
|
|
||||||
let xmr_alice_final = alice_xmr_wallet.as_ref().get_balance().await.unwrap();
|
|
||||||
|
|
||||||
bob_xmr_wallet.as_ref().0.refresh().await.unwrap();
|
|
||||||
let xmr_bob_final = bob_xmr_wallet.as_ref().get_balance().await.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
btc_alice_final,
|
|
||||||
btc_alice + btc_to_swap - bitcoin::Amount::from_sat(bitcoin::TX_FEE)
|
|
||||||
);
|
|
||||||
assert!(btc_bob_final <= btc_bob - btc_to_swap);
|
|
||||||
|
|
||||||
assert!(xmr_alice_final <= xmr_alice - xmr_to_swap);
|
|
||||||
assert_eq!(xmr_bob_final, xmr_bob + xmr_to_swap);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bob locks Btc and Alice locks Xmr. Bob does not act; he fails to send Alice
|
|
||||||
/// the encsig and fail to refund or redeem. Alice punishes.
|
|
||||||
#[tokio::test]
|
|
||||||
async fn alice_punishes_if_bob_never_acts_after_fund() {
|
|
||||||
let _guard = init_tracing();
|
|
||||||
|
|
||||||
let cli = Cli::default();
|
|
||||||
let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap();
|
|
||||||
let _ = bitcoind.init(5).await;
|
|
||||||
let (monero, _container) =
|
|
||||||
Monero::new(&cli, None, vec!["alice".to_string(), "bob".to_string()])
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let btc_to_swap = bitcoin::Amount::from_sat(1_000_000);
|
|
||||||
let xmr_to_swap = xmr_btc::monero::Amount::from_piconero(1_000_000_000_000);
|
|
||||||
|
|
||||||
let bob_btc_starting_balance = btc_to_swap * 10;
|
|
||||||
let bob_xmr_starting_balance = xmr_btc::monero::Amount::from_piconero(0);
|
|
||||||
|
|
||||||
let alice_btc_starting_balance = bitcoin::Amount::ZERO;
|
|
||||||
let alice_xmr_starting_balance = xmr_to_swap * 10;
|
|
||||||
|
|
||||||
// todo: This should not be hardcoded
|
|
||||||
let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9877"
|
|
||||||
.parse()
|
|
||||||
.expect("failed to parse Alice's address");
|
|
||||||
|
|
||||||
let config = Config::regtest();
|
|
||||||
|
|
||||||
let (
|
|
||||||
alice_state,
|
|
||||||
mut alice_swarm,
|
|
||||||
alice_swarm_handle,
|
|
||||||
alice_btc_wallet,
|
|
||||||
alice_xmr_wallet,
|
|
||||||
alice_peer_id,
|
|
||||||
) = init_alice(
|
|
||||||
&bitcoind,
|
|
||||||
&monero,
|
|
||||||
btc_to_swap,
|
|
||||||
alice_btc_starting_balance,
|
|
||||||
xmr_to_swap,
|
|
||||||
alice_xmr_starting_balance,
|
|
||||||
alice_multiaddr.clone(),
|
|
||||||
config,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let (bob_state, bob_swarm_driver, bob_swarm_handle, bob_btc_wallet, bob_xmr_wallet, bob_db) =
|
|
||||||
init_bob(
|
|
||||||
alice_multiaddr,
|
|
||||||
alice_peer_id,
|
|
||||||
&bitcoind,
|
|
||||||
&monero,
|
|
||||||
btc_to_swap,
|
|
||||||
bob_btc_starting_balance,
|
|
||||||
xmr_to_swap,
|
|
||||||
bob_xmr_starting_balance,
|
|
||||||
config,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let bob_btc_locked_fut = bob::swap::run_until(
|
|
||||||
bob_state,
|
|
||||||
bob::swap::is_btc_locked,
|
|
||||||
bob_swarm_handle,
|
|
||||||
bob_db,
|
|
||||||
bob_btc_wallet.clone(),
|
|
||||||
bob_xmr_wallet.clone(),
|
|
||||||
OsRng,
|
|
||||||
Uuid::new_v4(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let _bob_swarm_fut = tokio::spawn(async move { bob_swarm_driver.run().await });
|
|
||||||
|
|
||||||
let alice_fut = alice::swap::swap(
|
|
||||||
alice_state,
|
|
||||||
alice_swarm_handle,
|
|
||||||
alice_btc_wallet.clone(),
|
|
||||||
alice_xmr_wallet.clone(),
|
|
||||||
Config::regtest(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let _alice_swarm_fut = tokio::spawn(async move { alice_swarm.run().await });
|
|
||||||
|
|
||||||
// Wait until alice has locked xmr and bob has locked btc
|
|
||||||
let ((alice_state, _), bob_state) = try_join(alice_fut, bob_btc_locked_fut).await.unwrap();
|
|
||||||
|
|
||||||
assert!(matches!(alice_state, AliceState::Punished));
|
|
||||||
let bob_state3 = if let BobState::BtcLocked(state3, ..) = bob_state {
|
|
||||||
state3
|
|
||||||
} else {
|
|
||||||
panic!("Bob in unexpected state");
|
|
||||||
};
|
|
||||||
|
|
||||||
let btc_alice_final = alice_btc_wallet.as_ref().balance().await.unwrap();
|
|
||||||
let btc_bob_final = bob_btc_wallet.as_ref().balance().await.unwrap();
|
|
||||||
|
|
||||||
// lock_tx_bitcoin_fee is determined by the wallet, it is not necessarily equal
|
|
||||||
// to TX_FEE
|
|
||||||
let lock_tx_bitcoin_fee = bob_btc_wallet
|
|
||||||
.transaction_fee(bob_state3.tx_lock_id())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
btc_alice_final,
|
|
||||||
alice_btc_starting_balance + btc_to_swap - bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE)
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
btc_bob_final,
|
|
||||||
bob_btc_starting_balance - btc_to_swap - lock_tx_bitcoin_fee
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bob locks btc and Alice locks xmr. Alice fails to act so Bob refunds. Alice
|
|
||||||
// then also refunds.
|
|
||||||
#[tokio::test]
|
|
||||||
async fn both_refund() {
|
|
||||||
use tracing_subscriber::util::SubscriberInitExt as _;
|
|
||||||
let _guard = tracing_subscriber::fmt()
|
|
||||||
.with_env_filter("swap=info,xmr_btc=info")
|
|
||||||
.with_ansi(false)
|
|
||||||
.set_default();
|
|
||||||
|
|
||||||
let cli = Cli::default();
|
|
||||||
let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap();
|
|
||||||
let _ = bitcoind.init(5).await;
|
|
||||||
let (monero, _container) =
|
|
||||||
Monero::new(&cli, None, vec!["alice".to_string(), "bob".to_string()])
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let btc_to_swap = bitcoin::Amount::from_sat(1_000_000);
|
|
||||||
let xmr_to_swap = xmr_btc::monero::Amount::from_piconero(1_000_000_000_000);
|
|
||||||
|
|
||||||
let bob_btc_starting_balance = btc_to_swap * 10;
|
|
||||||
let bob_xmr_starting_balance = xmr_btc::monero::Amount::from_piconero(0);
|
|
||||||
|
|
||||||
let alice_btc_starting_balance = bitcoin::Amount::ZERO;
|
|
||||||
let alice_xmr_starting_balance = xmr_to_swap * 10;
|
|
||||||
|
|
||||||
// todo: This should not be hardcoded
|
|
||||||
let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9879"
|
|
||||||
.parse()
|
|
||||||
.expect("failed to parse Alice's address");
|
|
||||||
|
|
||||||
let (
|
|
||||||
alice_state,
|
|
||||||
mut alice_swarm_driver,
|
|
||||||
alice_swarm_handle,
|
|
||||||
alice_btc_wallet,
|
|
||||||
alice_xmr_wallet,
|
|
||||||
alice_peer_id,
|
|
||||||
) = init_alice(
|
|
||||||
&bitcoind,
|
|
||||||
&monero,
|
|
||||||
btc_to_swap,
|
|
||||||
alice_btc_starting_balance,
|
|
||||||
xmr_to_swap,
|
|
||||||
alice_xmr_starting_balance,
|
|
||||||
alice_multiaddr.clone(),
|
|
||||||
Config::regtest(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let (bob_state, bob_swarm_driver, bob_swarm_handle, bob_btc_wallet, bob_xmr_wallet, bob_db) =
|
|
||||||
init_bob(
|
|
||||||
alice_multiaddr,
|
|
||||||
alice_peer_id,
|
|
||||||
&bitcoind,
|
|
||||||
&monero,
|
|
||||||
btc_to_swap,
|
|
||||||
bob_btc_starting_balance,
|
|
||||||
xmr_to_swap,
|
|
||||||
bob_xmr_starting_balance,
|
|
||||||
Config::regtest(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let bob_fut = bob::swap::swap(
|
|
||||||
bob_state,
|
|
||||||
bob_swarm_handle,
|
|
||||||
bob_db,
|
|
||||||
bob_btc_wallet.clone(),
|
|
||||||
bob_xmr_wallet.clone(),
|
|
||||||
OsRng,
|
|
||||||
Uuid::new_v4(),
|
|
||||||
);
|
|
||||||
|
|
||||||
tokio::spawn(async move { bob_swarm_driver.run().await });
|
|
||||||
|
|
||||||
let alice_xmr_locked_fut = alice::swap::run_until(
|
|
||||||
alice_state,
|
|
||||||
alice::swap::is_xmr_locked,
|
|
||||||
alice_swarm_handle,
|
|
||||||
alice_btc_wallet.clone(),
|
|
||||||
alice_xmr_wallet.clone(),
|
|
||||||
Config::regtest(),
|
|
||||||
);
|
|
||||||
|
|
||||||
tokio::spawn(async move { alice_swarm_driver.run().await });
|
|
||||||
|
|
||||||
// Wait until alice has locked xmr and bob has locked btc
|
|
||||||
let (bob_state, (alice_state, alice_swarm_handle)) =
|
|
||||||
try_join(bob_fut, alice_xmr_locked_fut).await.unwrap();
|
|
||||||
|
|
||||||
let bob_state4 = if let BobState::BtcRefunded(state4) = bob_state {
|
|
||||||
state4
|
|
||||||
} else {
|
|
||||||
panic!("Bob in unexpected state");
|
|
||||||
};
|
|
||||||
|
|
||||||
let (alice_state, _) = alice::swap::swap(
|
|
||||||
alice_state,
|
|
||||||
alice_swarm_handle,
|
|
||||||
alice_btc_wallet.clone(),
|
|
||||||
alice_xmr_wallet.clone(),
|
|
||||||
Config::regtest(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(matches!(alice_state, AliceState::XmrRefunded));
|
|
||||||
|
|
||||||
let btc_alice_final = alice_btc_wallet.as_ref().balance().await.unwrap();
|
|
||||||
let btc_bob_final = bob_btc_wallet.as_ref().balance().await.unwrap();
|
|
||||||
|
|
||||||
// lock_tx_bitcoin_fee is determined by the wallet, it is not necessarily equal
|
|
||||||
// to TX_FEE
|
|
||||||
let lock_tx_bitcoin_fee = bob_btc_wallet
|
|
||||||
.transaction_fee(bob_state4.tx_lock_id())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(btc_alice_final, alice_btc_starting_balance);
|
|
||||||
|
|
||||||
// Alice or Bob could publish TxCancel. This means Bob could pay tx fees for
|
|
||||||
// TxCancel and TxRefund or only TxRefund
|
|
||||||
let btc_bob_final_alice_submitted_cancel = btc_bob_final
|
|
||||||
== bob_btc_starting_balance
|
|
||||||
- lock_tx_bitcoin_fee
|
|
||||||
- bitcoin::Amount::from_sat(bitcoin::TX_FEE);
|
|
||||||
|
|
||||||
let btc_bob_final_bob_submitted_cancel = btc_bob_final
|
|
||||||
== bob_btc_starting_balance
|
|
||||||
- lock_tx_bitcoin_fee
|
|
||||||
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE);
|
|
||||||
assert!(btc_bob_final_alice_submitted_cancel || btc_bob_final_bob_submitted_cancel);
|
|
||||||
|
|
||||||
alice_xmr_wallet.as_ref().0.refresh().await.unwrap();
|
|
||||||
let xmr_alice_final = alice_xmr_wallet.as_ref().get_balance().await.unwrap();
|
|
||||||
assert_eq!(xmr_alice_final, xmr_to_swap);
|
|
||||||
|
|
||||||
bob_xmr_wallet.as_ref().0.refresh().await.unwrap();
|
|
||||||
let xmr_bob_final = bob_xmr_wallet.as_ref().get_balance().await.unwrap();
|
|
||||||
assert_eq!(xmr_bob_final, bob_xmr_starting_balance);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
async fn init_alice(
|
|
||||||
bitcoind: &Bitcoind<'_>,
|
|
||||||
monero: &Monero,
|
|
||||||
btc_to_swap: bitcoin::Amount,
|
|
||||||
_btc_starting_balance: bitcoin::Amount,
|
|
||||||
xmr_to_swap: xmr_btc::monero::Amount,
|
|
||||||
xmr_starting_balance: xmr_btc::monero::Amount,
|
|
||||||
listen: Multiaddr,
|
|
||||||
config: Config,
|
|
||||||
) -> (
|
|
||||||
AliceState,
|
|
||||||
alice::event_loop::EventLoop,
|
|
||||||
alice::event_loop::EventLoopHandle,
|
|
||||||
Arc<swap::bitcoin::Wallet>,
|
|
||||||
Arc<swap::monero::Wallet>,
|
|
||||||
PeerId,
|
|
||||||
) {
|
|
||||||
monero
|
|
||||||
.init(vec![("alice", xmr_starting_balance.as_piconero())])
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let alice_xmr_wallet = Arc::new(swap::monero::Wallet(
|
|
||||||
monero.wallet("alice").unwrap().client(),
|
|
||||||
));
|
|
||||||
|
|
||||||
let alice_btc_wallet = Arc::new(
|
|
||||||
swap::bitcoin::Wallet::new("alice", bitcoind.node_url.clone(), config.bitcoin_network)
|
|
||||||
.await
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let amounts = SwapAmounts {
|
|
||||||
btc: btc_to_swap,
|
|
||||||
xmr: xmr_to_swap,
|
|
||||||
};
|
|
||||||
|
|
||||||
let (alice_state, alice_behaviour) = {
|
|
||||||
let rng = &mut OsRng;
|
|
||||||
let a = bitcoin::SecretKey::new_random(rng);
|
|
||||||
let s_a = cross_curve_dleq::Scalar::random(rng);
|
|
||||||
let v_a = xmr_btc::monero::PrivateViewKey::new_random(rng);
|
|
||||||
let redeem_address = alice_btc_wallet.as_ref().new_address().await.unwrap();
|
|
||||||
let punish_address = redeem_address.clone();
|
|
||||||
let state0 = State0::new(
|
|
||||||
a,
|
|
||||||
s_a,
|
|
||||||
v_a,
|
|
||||||
amounts.btc,
|
|
||||||
amounts.xmr,
|
|
||||||
config.bitcoin_refund_timelock,
|
|
||||||
config.bitcoin_punish_timelock,
|
|
||||||
redeem_address,
|
|
||||||
punish_address,
|
|
||||||
);
|
|
||||||
|
|
||||||
(
|
|
||||||
AliceState::Started {
|
|
||||||
amounts,
|
|
||||||
state0: state0.clone(),
|
|
||||||
},
|
|
||||||
alice::Behaviour::new(state0),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let alice_peer_id = alice_behaviour.peer_id();
|
|
||||||
let alice_transport = build(alice_behaviour.identity()).unwrap();
|
|
||||||
|
|
||||||
let (swarm_driver, handle) =
|
|
||||||
alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen).unwrap();
|
|
||||||
|
|
||||||
(
|
|
||||||
alice_state,
|
|
||||||
swarm_driver,
|
|
||||||
handle,
|
|
||||||
alice_btc_wallet,
|
|
||||||
alice_xmr_wallet,
|
|
||||||
alice_peer_id,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
async fn init_bob(
|
|
||||||
alice_multiaddr: Multiaddr,
|
|
||||||
alice_peer_id: PeerId,
|
|
||||||
bitcoind: &Bitcoind<'_>,
|
|
||||||
monero: &Monero,
|
|
||||||
btc_to_swap: bitcoin::Amount,
|
|
||||||
btc_starting_balance: bitcoin::Amount,
|
|
||||||
xmr_to_swap: xmr_btc::monero::Amount,
|
|
||||||
xmr_stating_balance: xmr_btc::monero::Amount,
|
|
||||||
config: Config,
|
|
||||||
) -> (
|
|
||||||
BobState,
|
|
||||||
bob::event_loop::EventLoop,
|
|
||||||
bob::event_loop::EventLoopHandle,
|
|
||||||
Arc<swap::bitcoin::Wallet>,
|
|
||||||
Arc<swap::monero::Wallet>,
|
|
||||||
Database,
|
|
||||||
) {
|
|
||||||
let bob_btc_wallet = Arc::new(
|
|
||||||
swap::bitcoin::Wallet::new("bob", bitcoind.node_url.clone(), config.bitcoin_network)
|
|
||||||
.await
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
bitcoind
|
|
||||||
.mint(
|
|
||||||
bob_btc_wallet.inner.new_address().await.unwrap(),
|
|
||||||
btc_starting_balance,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
monero
|
|
||||||
.init(vec![("bob", xmr_stating_balance.as_piconero())])
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.wallet("bob").unwrap().client()));
|
|
||||||
|
|
||||||
let amounts = SwapAmounts {
|
|
||||||
btc: btc_to_swap,
|
|
||||||
xmr: xmr_to_swap,
|
|
||||||
};
|
|
||||||
|
|
||||||
let bob_db_dir = tempdir().unwrap();
|
|
||||||
let bob_db = Database::open(bob_db_dir.path()).unwrap();
|
|
||||||
let bob_behaviour = bob::Behaviour::default();
|
|
||||||
let bob_transport = build(bob_behaviour.identity()).unwrap();
|
|
||||||
|
|
||||||
let refund_address = bob_btc_wallet.new_address().await.unwrap();
|
|
||||||
let state0 = xmr_btc::bob::State0::new(
|
|
||||||
&mut OsRng,
|
|
||||||
btc_to_swap,
|
|
||||||
xmr_to_swap,
|
|
||||||
config.bitcoin_refund_timelock,
|
|
||||||
config.bitcoin_punish_timelock,
|
|
||||||
refund_address,
|
|
||||||
);
|
|
||||||
let bob_state = BobState::Started {
|
|
||||||
state0,
|
|
||||||
amounts,
|
|
||||||
peer_id: alice_peer_id,
|
|
||||||
addr: alice_multiaddr,
|
|
||||||
};
|
|
||||||
|
|
||||||
let (swarm_driver, swarm_handle) =
|
|
||||||
bob::event_loop::EventLoop::new(bob_transport, bob_behaviour).unwrap();
|
|
||||||
|
|
||||||
(
|
|
||||||
bob_state,
|
|
||||||
swarm_driver,
|
|
||||||
swarm_handle,
|
|
||||||
bob_btc_wallet,
|
|
||||||
bob_xmr_wallet,
|
|
||||||
bob_db,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Utility function to initialize logging in the test environment.
|
|
||||||
/// Note that you have to keep the `_guard` in scope after calling in test:
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// let _guard = init_tracing();
|
|
||||||
/// ```
|
|
||||||
fn init_tracing() -> DefaultGuard {
|
|
||||||
// converts all log records into tracing events
|
|
||||||
// Note: Make sure to initialize without unwrapping, otherwise this causes
|
|
||||||
// trouble when running multiple tests.
|
|
||||||
let _ = LogTracer::init();
|
|
||||||
|
|
||||||
let global_filter = tracing::Level::WARN;
|
|
||||||
let swap_filter = tracing::Level::DEBUG;
|
|
||||||
let xmr_btc_filter = tracing::Level::DEBUG;
|
|
||||||
let monero_harness_filter = tracing::Level::INFO;
|
|
||||||
let bitcoin_harness_filter = tracing::Level::INFO;
|
|
||||||
|
|
||||||
use tracing_subscriber::util::SubscriberInitExt as _;
|
|
||||||
tracing_subscriber::fmt()
|
|
||||||
.with_env_filter(format!(
|
|
||||||
"{},swap={},xmr-btc={},monero_harness={},bitcoin_harness={}",
|
|
||||||
global_filter,
|
|
||||||
swap_filter,
|
|
||||||
xmr_btc_filter,
|
|
||||||
monero_harness_filter,
|
|
||||||
bitcoin_harness_filter,
|
|
||||||
))
|
|
||||||
.set_default()
|
|
||||||
}
|
|
117
swap/tests/happy_path.rs
Normal file
117
swap/tests/happy_path.rs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
use crate::testutils::{init_alice, init_bob};
|
||||||
|
use futures::future::try_join;
|
||||||
|
use libp2p::Multiaddr;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use swap::{alice, bob};
|
||||||
|
use testcontainers::clients::Cli;
|
||||||
|
use testutils::init_tracing;
|
||||||
|
use uuid::Uuid;
|
||||||
|
use xmr_btc::{bitcoin, config::Config};
|
||||||
|
|
||||||
|
pub mod testutils;
|
||||||
|
|
||||||
|
/// Run the following tests with RUST_MIN_STACK=10000000
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn happy_path() {
|
||||||
|
let _guard = init_tracing();
|
||||||
|
|
||||||
|
let cli = Cli::default();
|
||||||
|
let (
|
||||||
|
monero,
|
||||||
|
testutils::Containers {
|
||||||
|
bitcoind,
|
||||||
|
monerods: _monerods,
|
||||||
|
},
|
||||||
|
) = testutils::init_containers(&cli).await;
|
||||||
|
|
||||||
|
let btc_to_swap = bitcoin::Amount::from_sat(1_000_000);
|
||||||
|
let btc_alice = bitcoin::Amount::ZERO;
|
||||||
|
let btc_bob = btc_to_swap * 10;
|
||||||
|
|
||||||
|
// this xmr value matches the logic of alice::calculate_amounts i.e. btc *
|
||||||
|
// 10_000 * 100
|
||||||
|
let xmr_to_swap = xmr_btc::monero::Amount::from_piconero(1_000_000_000_000);
|
||||||
|
let xmr_alice = xmr_to_swap * 10;
|
||||||
|
let xmr_bob = xmr_btc::monero::Amount::ZERO;
|
||||||
|
|
||||||
|
// todo: This should not be hardcoded
|
||||||
|
let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876"
|
||||||
|
.parse()
|
||||||
|
.expect("failed to parse Alice's address");
|
||||||
|
|
||||||
|
let config = Config::regtest();
|
||||||
|
|
||||||
|
let (
|
||||||
|
alice_state,
|
||||||
|
mut alice_event_loop,
|
||||||
|
alice_event_loop_handle,
|
||||||
|
alice_btc_wallet,
|
||||||
|
alice_xmr_wallet,
|
||||||
|
alice_db,
|
||||||
|
) = init_alice(
|
||||||
|
&bitcoind,
|
||||||
|
&monero,
|
||||||
|
btc_to_swap,
|
||||||
|
xmr_to_swap,
|
||||||
|
xmr_alice,
|
||||||
|
alice_multiaddr.clone(),
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let (bob_state, bob_event_loop, bob_event_loop_handle, bob_btc_wallet, bob_xmr_wallet, bob_db) =
|
||||||
|
init_bob(
|
||||||
|
alice_multiaddr,
|
||||||
|
&bitcoind,
|
||||||
|
&monero,
|
||||||
|
btc_to_swap,
|
||||||
|
btc_bob,
|
||||||
|
xmr_to_swap,
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let alice_swap_fut = alice::swap::swap(
|
||||||
|
alice_state,
|
||||||
|
alice_event_loop_handle,
|
||||||
|
alice_btc_wallet.clone(),
|
||||||
|
alice_xmr_wallet.clone(),
|
||||||
|
config,
|
||||||
|
Uuid::new_v4(),
|
||||||
|
alice_db,
|
||||||
|
);
|
||||||
|
|
||||||
|
let _alice_swarm_fut = tokio::spawn(async move { alice_event_loop.run().await });
|
||||||
|
|
||||||
|
let bob_swap_fut = bob::swap::swap(
|
||||||
|
bob_state,
|
||||||
|
bob_event_loop_handle,
|
||||||
|
bob_db,
|
||||||
|
bob_btc_wallet.clone(),
|
||||||
|
bob_xmr_wallet.clone(),
|
||||||
|
OsRng,
|
||||||
|
Uuid::new_v4(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let _bob_swarm_fut = tokio::spawn(async move { bob_event_loop.run().await });
|
||||||
|
|
||||||
|
try_join(alice_swap_fut, bob_swap_fut).await.unwrap();
|
||||||
|
|
||||||
|
let btc_alice_final = alice_btc_wallet.as_ref().balance().await.unwrap();
|
||||||
|
let btc_bob_final = bob_btc_wallet.as_ref().balance().await.unwrap();
|
||||||
|
|
||||||
|
let xmr_alice_final = alice_xmr_wallet.as_ref().get_balance().await.unwrap();
|
||||||
|
|
||||||
|
bob_xmr_wallet.as_ref().0.refresh().await.unwrap();
|
||||||
|
let xmr_bob_final = bob_xmr_wallet.as_ref().get_balance().await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
btc_alice_final,
|
||||||
|
btc_alice + btc_to_swap - bitcoin::Amount::from_sat(bitcoin::TX_FEE)
|
||||||
|
);
|
||||||
|
assert!(btc_bob_final <= btc_bob - btc_to_swap);
|
||||||
|
|
||||||
|
assert!(xmr_alice_final <= xmr_alice - xmr_to_swap);
|
||||||
|
assert_eq!(xmr_bob_final, xmr_bob + xmr_to_swap);
|
||||||
|
}
|
150
swap/tests/happy_path_restart_alice.rs
Normal file
150
swap/tests/happy_path_restart_alice.rs
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
use libp2p::Multiaddr;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use swap::{alice, alice::swap::AliceState, bitcoin, bob, storage::Database};
|
||||||
|
use tempfile::tempdir;
|
||||||
|
use testcontainers::clients::Cli;
|
||||||
|
use uuid::Uuid;
|
||||||
|
use xmr_btc::config::Config;
|
||||||
|
|
||||||
|
pub mod testutils;
|
||||||
|
|
||||||
|
use crate::testutils::{init_alice, init_bob};
|
||||||
|
use testutils::init_tracing;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn given_alice_restarts_after_encsig_is_learned_resume_swap() {
|
||||||
|
let _guard = init_tracing();
|
||||||
|
|
||||||
|
let cli = Cli::default();
|
||||||
|
let (
|
||||||
|
monero,
|
||||||
|
testutils::Containers {
|
||||||
|
bitcoind,
|
||||||
|
monerods: _monerods,
|
||||||
|
},
|
||||||
|
) = testutils::init_containers(&cli).await;
|
||||||
|
|
||||||
|
let btc_to_swap = bitcoin::Amount::from_sat(1_000_000);
|
||||||
|
let xmr_to_swap = xmr_btc::monero::Amount::from_piconero(1_000_000_000_000);
|
||||||
|
|
||||||
|
let bob_btc_starting_balance = btc_to_swap * 10;
|
||||||
|
let alice_xmr_starting_balance = xmr_to_swap * 10;
|
||||||
|
|
||||||
|
let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9877"
|
||||||
|
.parse()
|
||||||
|
.expect("failed to parse Alice's address");
|
||||||
|
|
||||||
|
let config = Config::regtest();
|
||||||
|
|
||||||
|
let (
|
||||||
|
start_state,
|
||||||
|
mut alice_event_loop,
|
||||||
|
alice_event_loop_handle,
|
||||||
|
alice_btc_wallet,
|
||||||
|
alice_xmr_wallet,
|
||||||
|
_,
|
||||||
|
) = init_alice(
|
||||||
|
&bitcoind,
|
||||||
|
&monero,
|
||||||
|
btc_to_swap,
|
||||||
|
xmr_to_swap,
|
||||||
|
alice_xmr_starting_balance,
|
||||||
|
alice_multiaddr.clone(),
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let (bob_state, bob_event_loop, bob_event_loop_handle, bob_btc_wallet, bob_xmr_wallet, bob_db) =
|
||||||
|
init_bob(
|
||||||
|
alice_multiaddr.clone(),
|
||||||
|
&bitcoind,
|
||||||
|
&monero,
|
||||||
|
btc_to_swap,
|
||||||
|
bob_btc_starting_balance,
|
||||||
|
xmr_to_swap,
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// TODO: we are making a clone of Bob's wallets here to keep them in scope after
|
||||||
|
// Bob's wallets are moved into an async task.
|
||||||
|
let bob_btc_wallet_clone = bob_btc_wallet.clone();
|
||||||
|
let bob_xmr_wallet_clone = bob_xmr_wallet.clone();
|
||||||
|
|
||||||
|
let _ = tokio::spawn(async move {
|
||||||
|
bob::swap::swap(
|
||||||
|
bob_state,
|
||||||
|
bob_event_loop_handle,
|
||||||
|
bob_db,
|
||||||
|
bob_btc_wallet.clone(),
|
||||||
|
bob_xmr_wallet.clone(),
|
||||||
|
OsRng,
|
||||||
|
Uuid::new_v4(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
});
|
||||||
|
|
||||||
|
let _bob_swarm_fut = tokio::spawn(async move { bob_event_loop.run().await });
|
||||||
|
|
||||||
|
let alice_db_datadir = tempdir().unwrap();
|
||||||
|
let alice_db = Database::open(alice_db_datadir.path()).unwrap();
|
||||||
|
|
||||||
|
let _alice_swarm_fut = tokio::spawn(async move { alice_event_loop.run().await });
|
||||||
|
|
||||||
|
let alice_swap_id = Uuid::new_v4();
|
||||||
|
|
||||||
|
let alice_state = alice::swap::run_until(
|
||||||
|
start_state,
|
||||||
|
alice::swap::is_encsig_learned,
|
||||||
|
alice_event_loop_handle,
|
||||||
|
alice_btc_wallet.clone(),
|
||||||
|
alice_xmr_wallet.clone(),
|
||||||
|
config,
|
||||||
|
alice_swap_id,
|
||||||
|
alice_db,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(matches!(alice_state, AliceState::EncSignLearned {..}));
|
||||||
|
|
||||||
|
let alice_db = Database::open(alice_db_datadir.path()).unwrap();
|
||||||
|
let state_before_restart = alice_db.get_state(alice_swap_id).unwrap();
|
||||||
|
|
||||||
|
if let swap::state::Swap::Alice(state) = state_before_restart.clone() {
|
||||||
|
assert!(matches!(state, swap::state::Alice::EncSignLearned {..}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (mut event_loop_after_restart, event_loop_handle_after_restart) =
|
||||||
|
testutils::init_alice_event_loop(alice_multiaddr);
|
||||||
|
let _alice_swarm_fut = tokio::spawn(async move { event_loop_after_restart.run().await });
|
||||||
|
|
||||||
|
let alice_state = alice::swap::resume_from_database(
|
||||||
|
event_loop_handle_after_restart,
|
||||||
|
alice_btc_wallet.clone(),
|
||||||
|
alice_xmr_wallet.clone(),
|
||||||
|
config,
|
||||||
|
alice_swap_id,
|
||||||
|
alice_db,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(matches!(alice_state, AliceState::BtcRedeemed {..}));
|
||||||
|
|
||||||
|
let btc_alice_final = alice_btc_wallet.as_ref().balance().await.unwrap();
|
||||||
|
let btc_bob_final = bob_btc_wallet_clone.as_ref().balance().await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
btc_alice_final,
|
||||||
|
btc_to_swap - bitcoin::Amount::from_sat(bitcoin::TX_FEE)
|
||||||
|
);
|
||||||
|
assert!(btc_bob_final <= bob_btc_starting_balance - btc_to_swap);
|
||||||
|
|
||||||
|
let xmr_alice_final = alice_xmr_wallet.as_ref().get_balance().await.unwrap();
|
||||||
|
bob_xmr_wallet_clone.as_ref().0.refresh().await.unwrap();
|
||||||
|
let xmr_bob_final = bob_xmr_wallet_clone.as_ref().get_balance().await.unwrap();
|
||||||
|
|
||||||
|
assert!(xmr_alice_final <= alice_xmr_starting_balance - xmr_to_swap);
|
||||||
|
assert_eq!(xmr_bob_final, xmr_to_swap);
|
||||||
|
}
|
155
swap/tests/happy_path_restart_bob.rs
Normal file
155
swap/tests/happy_path_restart_bob.rs
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
use libp2p::Multiaddr;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use swap::{alice, bitcoin, bob, storage::Database};
|
||||||
|
use tempfile::tempdir;
|
||||||
|
use testcontainers::clients::Cli;
|
||||||
|
use uuid::Uuid;
|
||||||
|
use xmr_btc::config::Config;
|
||||||
|
|
||||||
|
pub mod testutils;
|
||||||
|
|
||||||
|
use crate::testutils::{init_alice, init_bob};
|
||||||
|
use swap::bob::swap::BobState;
|
||||||
|
use testutils::init_tracing;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn given_bob_restarts_after_encsig_is_sent_resume_swap() {
|
||||||
|
let _guard = init_tracing();
|
||||||
|
|
||||||
|
let cli = Cli::default();
|
||||||
|
let (
|
||||||
|
monero,
|
||||||
|
testutils::Containers {
|
||||||
|
bitcoind,
|
||||||
|
monerods: _monerods,
|
||||||
|
},
|
||||||
|
) = testutils::init_containers(&cli).await;
|
||||||
|
|
||||||
|
let btc_to_swap = bitcoin::Amount::from_sat(1_000_000);
|
||||||
|
let xmr_to_swap = xmr_btc::monero::Amount::from_piconero(1_000_000_000_000);
|
||||||
|
|
||||||
|
let bob_btc_starting_balance = btc_to_swap * 10;
|
||||||
|
let alice_xmr_starting_balance = xmr_to_swap * 10;
|
||||||
|
|
||||||
|
let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9877"
|
||||||
|
.parse()
|
||||||
|
.expect("failed to parse Alice's address");
|
||||||
|
|
||||||
|
let config = Config::regtest();
|
||||||
|
|
||||||
|
let (
|
||||||
|
alice_state,
|
||||||
|
mut alice_event_loop,
|
||||||
|
alice_event_loop_handle,
|
||||||
|
alice_btc_wallet,
|
||||||
|
alice_xmr_wallet,
|
||||||
|
alice_db,
|
||||||
|
) = init_alice(
|
||||||
|
&bitcoind,
|
||||||
|
&monero,
|
||||||
|
btc_to_swap,
|
||||||
|
xmr_to_swap,
|
||||||
|
alice_xmr_starting_balance,
|
||||||
|
alice_multiaddr.clone(),
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let (bob_state, bob_event_loop, bob_event_loop_handle, bob_btc_wallet, bob_xmr_wallet, _) =
|
||||||
|
init_bob(
|
||||||
|
alice_multiaddr.clone(),
|
||||||
|
&bitcoind,
|
||||||
|
&monero,
|
||||||
|
btc_to_swap,
|
||||||
|
bob_btc_starting_balance,
|
||||||
|
xmr_to_swap,
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// TODO: we are making a clone of Alices's wallets here to keep them in scope
|
||||||
|
// after Alices's wallets are moved into an async task.
|
||||||
|
let alice_btc_wallet_clone = alice_btc_wallet.clone();
|
||||||
|
let alice_xmr_wallet_clone = alice_xmr_wallet.clone();
|
||||||
|
|
||||||
|
// TODO: we are making a clone of Bob's wallets here to keep them in scope after
|
||||||
|
// Bob's wallets are moved into an async task.
|
||||||
|
let bob_btc_wallet_clone = bob_btc_wallet.clone();
|
||||||
|
let bob_xmr_wallet_clone = bob_xmr_wallet.clone();
|
||||||
|
|
||||||
|
let _ = tokio::spawn(async move {
|
||||||
|
alice::swap::swap(
|
||||||
|
alice_state,
|
||||||
|
alice_event_loop_handle,
|
||||||
|
alice_btc_wallet.clone(),
|
||||||
|
alice_xmr_wallet.clone(),
|
||||||
|
config,
|
||||||
|
Uuid::new_v4(),
|
||||||
|
alice_db,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
});
|
||||||
|
|
||||||
|
let _alice_swarm_fut = tokio::spawn(async move { alice_event_loop.run().await });
|
||||||
|
|
||||||
|
let _bob_swarm_fut = tokio::spawn(async move { bob_event_loop.run().await });
|
||||||
|
|
||||||
|
let bob_swap_id = Uuid::new_v4();
|
||||||
|
let bob_db_datadir = tempdir().unwrap();
|
||||||
|
let bob_db = Database::open(bob_db_datadir.path()).unwrap();
|
||||||
|
|
||||||
|
let bob_state = bob::swap::run_until(
|
||||||
|
bob_state,
|
||||||
|
bob::swap::is_encsig_sent,
|
||||||
|
bob_event_loop_handle,
|
||||||
|
bob_db,
|
||||||
|
bob_btc_wallet.clone(),
|
||||||
|
bob_xmr_wallet.clone(),
|
||||||
|
OsRng,
|
||||||
|
bob_swap_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(matches!(bob_state, BobState::EncSigSent {..}));
|
||||||
|
|
||||||
|
let bob_db = Database::open(bob_db_datadir.path()).unwrap();
|
||||||
|
let state_before_restart = bob_db.get_state(bob_swap_id).unwrap();
|
||||||
|
|
||||||
|
if let swap::state::Swap::Bob(state) = state_before_restart.clone() {
|
||||||
|
assert!(matches!(state, swap::state::Bob::EncSigSent {..}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (event_loop_after_restart, event_loop_handle_after_restart) =
|
||||||
|
testutils::init_bob_event_loop();
|
||||||
|
let _alice_swarm_fut = tokio::spawn(async move { event_loop_after_restart.run().await });
|
||||||
|
|
||||||
|
let alice_state = bob::swap::resume_from_database(
|
||||||
|
event_loop_handle_after_restart,
|
||||||
|
bob_db,
|
||||||
|
bob_btc_wallet,
|
||||||
|
bob_xmr_wallet,
|
||||||
|
OsRng,
|
||||||
|
bob_swap_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(matches!(alice_state, BobState::XmrRedeemed {..}));
|
||||||
|
|
||||||
|
let btc_alice_final = alice_btc_wallet_clone.as_ref().balance().await.unwrap();
|
||||||
|
let btc_bob_final = bob_btc_wallet_clone.as_ref().balance().await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
btc_alice_final,
|
||||||
|
btc_to_swap - bitcoin::Amount::from_sat(bitcoin::TX_FEE)
|
||||||
|
);
|
||||||
|
assert!(btc_bob_final <= bob_btc_starting_balance - btc_to_swap);
|
||||||
|
|
||||||
|
let xmr_alice_final = alice_xmr_wallet_clone.as_ref().get_balance().await.unwrap();
|
||||||
|
bob_xmr_wallet_clone.as_ref().0.refresh().await.unwrap();
|
||||||
|
let xmr_bob_final = bob_xmr_wallet_clone.as_ref().get_balance().await.unwrap();
|
||||||
|
|
||||||
|
assert!(xmr_alice_final <= alice_xmr_starting_balance - xmr_to_swap);
|
||||||
|
assert_eq!(xmr_bob_final, xmr_to_swap);
|
||||||
|
}
|
127
swap/tests/punish.rs
Normal file
127
swap/tests/punish.rs
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
use crate::testutils::{init_alice, init_bob};
|
||||||
|
use futures::future::try_join;
|
||||||
|
use libp2p::Multiaddr;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use swap::{alice, alice::swap::AliceState, bob, bob::swap::BobState};
|
||||||
|
use testcontainers::clients::Cli;
|
||||||
|
use testutils::init_tracing;
|
||||||
|
use uuid::Uuid;
|
||||||
|
use xmr_btc::{bitcoin, config::Config};
|
||||||
|
|
||||||
|
pub mod testutils;
|
||||||
|
|
||||||
|
/// Bob locks Btc and Alice locks Xmr. Bob does not act; he fails to send Alice
|
||||||
|
/// the encsig and fail to refund or redeem. Alice punishes.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn alice_punishes_if_bob_never_acts_after_fund() {
|
||||||
|
let _guard = init_tracing();
|
||||||
|
|
||||||
|
let cli = Cli::default();
|
||||||
|
let (
|
||||||
|
monero,
|
||||||
|
testutils::Containers {
|
||||||
|
bitcoind,
|
||||||
|
monerods: _monerods,
|
||||||
|
},
|
||||||
|
) = testutils::init_containers(&cli).await;
|
||||||
|
|
||||||
|
let btc_to_swap = bitcoin::Amount::from_sat(1_000_000);
|
||||||
|
let xmr_to_swap = xmr_btc::monero::Amount::from_piconero(1_000_000_000_000);
|
||||||
|
|
||||||
|
let bob_btc_starting_balance = btc_to_swap * 10;
|
||||||
|
|
||||||
|
let alice_btc_starting_balance = bitcoin::Amount::ZERO;
|
||||||
|
let alice_xmr_starting_balance = xmr_to_swap * 10;
|
||||||
|
|
||||||
|
// todo: This should not be hardcoded
|
||||||
|
let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9877"
|
||||||
|
.parse()
|
||||||
|
.expect("failed to parse Alice's address");
|
||||||
|
|
||||||
|
let config = Config::regtest();
|
||||||
|
|
||||||
|
let (
|
||||||
|
alice_state,
|
||||||
|
mut alice_event_loop,
|
||||||
|
alice_event_loop_handle,
|
||||||
|
alice_btc_wallet,
|
||||||
|
alice_xmr_wallet,
|
||||||
|
alice_db,
|
||||||
|
) = init_alice(
|
||||||
|
&bitcoind,
|
||||||
|
&monero,
|
||||||
|
btc_to_swap,
|
||||||
|
xmr_to_swap,
|
||||||
|
alice_xmr_starting_balance,
|
||||||
|
alice_multiaddr.clone(),
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let (bob_state, bob_event_loop, bob_event_loop_handle, bob_btc_wallet, bob_xmr_wallet, bob_db) =
|
||||||
|
init_bob(
|
||||||
|
alice_multiaddr,
|
||||||
|
&bitcoind,
|
||||||
|
&monero,
|
||||||
|
btc_to_swap,
|
||||||
|
bob_btc_starting_balance,
|
||||||
|
xmr_to_swap,
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let bob_btc_locked_fut = bob::swap::run_until(
|
||||||
|
bob_state,
|
||||||
|
bob::swap::is_btc_locked,
|
||||||
|
bob_event_loop_handle,
|
||||||
|
bob_db,
|
||||||
|
bob_btc_wallet.clone(),
|
||||||
|
bob_xmr_wallet.clone(),
|
||||||
|
OsRng,
|
||||||
|
Uuid::new_v4(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let _bob_swarm_fut = tokio::spawn(async move { bob_event_loop.run().await });
|
||||||
|
|
||||||
|
let alice_fut = alice::swap::swap(
|
||||||
|
alice_state,
|
||||||
|
alice_event_loop_handle,
|
||||||
|
alice_btc_wallet.clone(),
|
||||||
|
alice_xmr_wallet.clone(),
|
||||||
|
Config::regtest(),
|
||||||
|
Uuid::new_v4(),
|
||||||
|
alice_db,
|
||||||
|
);
|
||||||
|
|
||||||
|
let _alice_swarm_fut = tokio::spawn(async move { alice_event_loop.run().await });
|
||||||
|
|
||||||
|
// Wait until alice has locked xmr and bob has locked btc
|
||||||
|
let (alice_state, bob_state) = try_join(alice_fut, bob_btc_locked_fut).await.unwrap();
|
||||||
|
|
||||||
|
assert!(matches!(alice_state, AliceState::Punished));
|
||||||
|
let bob_state3 = if let BobState::BtcLocked(state3, ..) = bob_state {
|
||||||
|
state3
|
||||||
|
} else {
|
||||||
|
panic!("Bob in unexpected state");
|
||||||
|
};
|
||||||
|
|
||||||
|
let btc_alice_final = alice_btc_wallet.as_ref().balance().await.unwrap();
|
||||||
|
let btc_bob_final = bob_btc_wallet.as_ref().balance().await.unwrap();
|
||||||
|
|
||||||
|
// lock_tx_bitcoin_fee is determined by the wallet, it is not necessarily equal
|
||||||
|
// to TX_FEE
|
||||||
|
let lock_tx_bitcoin_fee = bob_btc_wallet
|
||||||
|
.transaction_fee(bob_state3.tx_lock_id())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
btc_alice_final,
|
||||||
|
alice_btc_starting_balance + btc_to_swap - bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
btc_bob_final,
|
||||||
|
bob_btc_starting_balance - btc_to_swap - lock_tx_bitcoin_fee
|
||||||
|
);
|
||||||
|
}
|
162
swap/tests/refund_restart.rs
Normal file
162
swap/tests/refund_restart.rs
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
use crate::testutils::{init_alice, init_bob};
|
||||||
|
use futures::future::try_join;
|
||||||
|
use libp2p::Multiaddr;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use swap::{alice, alice::swap::AliceState, bob, bob::swap::BobState, storage::Database};
|
||||||
|
use tempfile::tempdir;
|
||||||
|
use testcontainers::clients::Cli;
|
||||||
|
use testutils::init_tracing;
|
||||||
|
use uuid::Uuid;
|
||||||
|
use xmr_btc::{bitcoin, config::Config};
|
||||||
|
|
||||||
|
pub mod testutils;
|
||||||
|
|
||||||
|
// Bob locks btc and Alice locks xmr. Alice fails to act so Bob refunds. Alice
|
||||||
|
// then also refunds.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn both_refund() {
|
||||||
|
let _guard = init_tracing();
|
||||||
|
|
||||||
|
let cli = Cli::default();
|
||||||
|
let (
|
||||||
|
monero,
|
||||||
|
testutils::Containers {
|
||||||
|
bitcoind,
|
||||||
|
monerods: _monerods,
|
||||||
|
},
|
||||||
|
) = testutils::init_containers(&cli).await;
|
||||||
|
|
||||||
|
let btc_to_swap = bitcoin::Amount::from_sat(1_000_000);
|
||||||
|
let xmr_to_swap = xmr_btc::monero::Amount::from_piconero(1_000_000_000_000);
|
||||||
|
|
||||||
|
let bob_btc_starting_balance = btc_to_swap * 10;
|
||||||
|
let bob_xmr_starting_balance = xmr_btc::monero::Amount::from_piconero(0);
|
||||||
|
|
||||||
|
let alice_btc_starting_balance = bitcoin::Amount::ZERO;
|
||||||
|
let alice_xmr_starting_balance = xmr_to_swap * 10;
|
||||||
|
|
||||||
|
// todo: This should not be hardcoded
|
||||||
|
let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9879"
|
||||||
|
.parse()
|
||||||
|
.expect("failed to parse Alice's address");
|
||||||
|
|
||||||
|
let (
|
||||||
|
alice_state,
|
||||||
|
mut alice_event_loop,
|
||||||
|
alice_event_loop_handle,
|
||||||
|
alice_btc_wallet,
|
||||||
|
alice_xmr_wallet,
|
||||||
|
_,
|
||||||
|
) = init_alice(
|
||||||
|
&bitcoind,
|
||||||
|
&monero,
|
||||||
|
btc_to_swap,
|
||||||
|
xmr_to_swap,
|
||||||
|
alice_xmr_starting_balance,
|
||||||
|
alice_multiaddr.clone(),
|
||||||
|
Config::regtest(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let (bob_state, bob_event_loop, bob_event_loop_handle, bob_btc_wallet, bob_xmr_wallet, bob_db) =
|
||||||
|
init_bob(
|
||||||
|
alice_multiaddr.clone(),
|
||||||
|
&bitcoind,
|
||||||
|
&monero,
|
||||||
|
btc_to_swap,
|
||||||
|
bob_btc_starting_balance,
|
||||||
|
xmr_to_swap,
|
||||||
|
Config::regtest(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let bob_fut = bob::swap::swap(
|
||||||
|
bob_state,
|
||||||
|
bob_event_loop_handle,
|
||||||
|
bob_db,
|
||||||
|
bob_btc_wallet.clone(),
|
||||||
|
bob_xmr_wallet.clone(),
|
||||||
|
OsRng,
|
||||||
|
Uuid::new_v4(),
|
||||||
|
);
|
||||||
|
|
||||||
|
tokio::spawn(async move { bob_event_loop.run().await });
|
||||||
|
|
||||||
|
let alice_swap_id = Uuid::new_v4();
|
||||||
|
let alice_db_datadir = tempdir().unwrap();
|
||||||
|
let alice_db = Database::open(alice_db_datadir.path()).unwrap();
|
||||||
|
|
||||||
|
let alice_xmr_locked_fut = alice::swap::run_until(
|
||||||
|
alice_state,
|
||||||
|
alice::swap::is_xmr_locked,
|
||||||
|
alice_event_loop_handle,
|
||||||
|
alice_btc_wallet.clone(),
|
||||||
|
alice_xmr_wallet.clone(),
|
||||||
|
Config::regtest(),
|
||||||
|
alice_swap_id,
|
||||||
|
alice_db,
|
||||||
|
);
|
||||||
|
|
||||||
|
tokio::spawn(async move { alice_event_loop.run().await });
|
||||||
|
|
||||||
|
// Wait until alice has locked xmr and bob has locked btc
|
||||||
|
let (bob_state, alice_state) = try_join(bob_fut, alice_xmr_locked_fut).await.unwrap();
|
||||||
|
|
||||||
|
let bob_state4 = if let BobState::BtcRefunded(state4) = bob_state {
|
||||||
|
state4
|
||||||
|
} else {
|
||||||
|
panic!("Bob in unexpected state");
|
||||||
|
};
|
||||||
|
|
||||||
|
let alice_db = Database::open(alice_db_datadir.path()).unwrap();
|
||||||
|
let (mut alice_event_loop, alice_event_loop_handle) =
|
||||||
|
testutils::init_alice_event_loop(alice_multiaddr);
|
||||||
|
|
||||||
|
let alice_state = alice::swap::swap(
|
||||||
|
alice_state,
|
||||||
|
alice_event_loop_handle,
|
||||||
|
alice_btc_wallet.clone(),
|
||||||
|
alice_xmr_wallet.clone(),
|
||||||
|
Config::regtest(),
|
||||||
|
alice_swap_id,
|
||||||
|
alice_db,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
tokio::spawn(async move { alice_event_loop.run().await });
|
||||||
|
|
||||||
|
assert!(matches!(alice_state, AliceState::XmrRefunded));
|
||||||
|
|
||||||
|
let btc_alice_final = alice_btc_wallet.as_ref().balance().await.unwrap();
|
||||||
|
let btc_bob_final = bob_btc_wallet.as_ref().balance().await.unwrap();
|
||||||
|
|
||||||
|
// lock_tx_bitcoin_fee is determined by the wallet, it is not necessarily equal
|
||||||
|
// to TX_FEE
|
||||||
|
let lock_tx_bitcoin_fee = bob_btc_wallet
|
||||||
|
.transaction_fee(bob_state4.tx_lock_id())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(btc_alice_final, alice_btc_starting_balance);
|
||||||
|
|
||||||
|
// Alice or Bob could publish TxCancel. This means Bob could pay tx fees for
|
||||||
|
// TxCancel and TxRefund or only TxRefund
|
||||||
|
let btc_bob_final_alice_submitted_cancel = btc_bob_final
|
||||||
|
== bob_btc_starting_balance
|
||||||
|
- lock_tx_bitcoin_fee
|
||||||
|
- bitcoin::Amount::from_sat(bitcoin::TX_FEE);
|
||||||
|
|
||||||
|
let btc_bob_final_bob_submitted_cancel = btc_bob_final
|
||||||
|
== bob_btc_starting_balance
|
||||||
|
- lock_tx_bitcoin_fee
|
||||||
|
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE);
|
||||||
|
assert!(btc_bob_final_alice_submitted_cancel || btc_bob_final_bob_submitted_cancel);
|
||||||
|
|
||||||
|
alice_xmr_wallet.as_ref().0.refresh().await.unwrap();
|
||||||
|
let xmr_alice_final = alice_xmr_wallet.as_ref().get_balance().await.unwrap();
|
||||||
|
assert_eq!(xmr_alice_final, xmr_to_swap);
|
||||||
|
|
||||||
|
bob_xmr_wallet.as_ref().0.refresh().await.unwrap();
|
||||||
|
let xmr_bob_final = bob_xmr_wallet.as_ref().get_balance().await.unwrap();
|
||||||
|
assert_eq!(xmr_bob_final, bob_xmr_starting_balance);
|
||||||
|
}
|
279
swap/tests/testutils/mod.rs
Normal file
279
swap/tests/testutils/mod.rs
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
use bitcoin_harness::Bitcoind;
|
||||||
|
use libp2p::core::Multiaddr;
|
||||||
|
use monero_harness::{image, Monero};
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use swap::{
|
||||||
|
alice, alice::swap::AliceState, bitcoin, bob, bob::swap::BobState, monero,
|
||||||
|
network::transport::build, storage::Database, SwapAmounts,
|
||||||
|
};
|
||||||
|
use tempfile::tempdir;
|
||||||
|
use testcontainers::{clients::Cli, Container};
|
||||||
|
use tracing_core::dispatcher::DefaultGuard;
|
||||||
|
use tracing_log::LogTracer;
|
||||||
|
use xmr_btc::{alice::State0, config::Config, cross_curve_dleq};
|
||||||
|
|
||||||
|
pub async fn init_containers(cli: &Cli) -> (Monero, Containers<'_>) {
|
||||||
|
let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap();
|
||||||
|
let _ = bitcoind.init(5).await;
|
||||||
|
let (monero, monerods) = Monero::new(&cli, None, vec!["alice".to_string(), "bob".to_string()])
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
(monero, Containers { bitcoind, monerods })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn init_wallets(
|
||||||
|
name: &str,
|
||||||
|
bitcoind: &Bitcoind<'_>,
|
||||||
|
monero: &Monero,
|
||||||
|
btc_starting_balance: Option<xmr_btc::bitcoin::Amount>,
|
||||||
|
xmr_starting_balance: Option<xmr_btc::monero::Amount>,
|
||||||
|
config: Config,
|
||||||
|
) -> (Arc<bitcoin::Wallet>, Arc<monero::Wallet>) {
|
||||||
|
match xmr_starting_balance {
|
||||||
|
Some(amount) => {
|
||||||
|
monero
|
||||||
|
.init(vec![(name, amount.as_piconero())])
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
monero
|
||||||
|
.init(vec![(name, monero::Amount::ZERO.as_piconero())])
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let xmr_wallet = Arc::new(swap::monero::Wallet(monero.wallet(name).unwrap().client()));
|
||||||
|
|
||||||
|
let btc_wallet = Arc::new(
|
||||||
|
swap::bitcoin::Wallet::new(name, bitcoind.node_url.clone(), config.bitcoin_network)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(amount) = btc_starting_balance {
|
||||||
|
bitcoind
|
||||||
|
.mint(btc_wallet.inner.new_address().await.unwrap(), amount)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
(btc_wallet, xmr_wallet)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn init_alice_state(
|
||||||
|
btc_to_swap: bitcoin::Amount,
|
||||||
|
xmr_to_swap: monero::Amount,
|
||||||
|
alice_btc_wallet: Arc<bitcoin::Wallet>,
|
||||||
|
config: Config,
|
||||||
|
) -> AliceState {
|
||||||
|
let rng = &mut OsRng;
|
||||||
|
|
||||||
|
let amounts = SwapAmounts {
|
||||||
|
btc: btc_to_swap,
|
||||||
|
xmr: xmr_to_swap,
|
||||||
|
};
|
||||||
|
|
||||||
|
let a = crate::bitcoin::SecretKey::new_random(rng);
|
||||||
|
let s_a = cross_curve_dleq::Scalar::random(rng);
|
||||||
|
let v_a = xmr_btc::monero::PrivateViewKey::new_random(rng);
|
||||||
|
let redeem_address = alice_btc_wallet.as_ref().new_address().await.unwrap();
|
||||||
|
let punish_address = redeem_address.clone();
|
||||||
|
let state0 = State0::new(
|
||||||
|
a,
|
||||||
|
s_a,
|
||||||
|
v_a,
|
||||||
|
amounts.btc,
|
||||||
|
amounts.xmr,
|
||||||
|
config.bitcoin_refund_timelock,
|
||||||
|
config.bitcoin_punish_timelock,
|
||||||
|
redeem_address,
|
||||||
|
punish_address,
|
||||||
|
);
|
||||||
|
|
||||||
|
AliceState::Started { amounts, state0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_alice_event_loop(
|
||||||
|
listen: Multiaddr,
|
||||||
|
) -> (
|
||||||
|
alice::event_loop::EventLoop,
|
||||||
|
alice::event_loop::EventLoopHandle,
|
||||||
|
) {
|
||||||
|
let alice_behaviour = alice::Behaviour::default();
|
||||||
|
let alice_transport = build(alice_behaviour.identity()).unwrap();
|
||||||
|
|
||||||
|
alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub async fn init_alice(
|
||||||
|
bitcoind: &Bitcoind<'_>,
|
||||||
|
monero: &Monero,
|
||||||
|
btc_to_swap: bitcoin::Amount,
|
||||||
|
xmr_to_swap: monero::Amount,
|
||||||
|
xmr_starting_balance: xmr_btc::monero::Amount,
|
||||||
|
listen: Multiaddr,
|
||||||
|
config: Config,
|
||||||
|
) -> (
|
||||||
|
AliceState,
|
||||||
|
alice::event_loop::EventLoop,
|
||||||
|
alice::event_loop::EventLoopHandle,
|
||||||
|
Arc<swap::bitcoin::Wallet>,
|
||||||
|
Arc<swap::monero::Wallet>,
|
||||||
|
Database,
|
||||||
|
) {
|
||||||
|
let (alice_btc_wallet, alice_xmr_wallet) = init_wallets(
|
||||||
|
"alice",
|
||||||
|
bitcoind,
|
||||||
|
monero,
|
||||||
|
None,
|
||||||
|
Some(xmr_starting_balance),
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let alice_start_state =
|
||||||
|
init_alice_state(btc_to_swap, xmr_to_swap, alice_btc_wallet.clone(), config).await;
|
||||||
|
|
||||||
|
let (event_loop, event_loop_handle) = init_alice_event_loop(listen);
|
||||||
|
|
||||||
|
let alice_db_datadir = tempdir().unwrap();
|
||||||
|
let alice_db = Database::open(alice_db_datadir.path()).unwrap();
|
||||||
|
|
||||||
|
(
|
||||||
|
alice_start_state,
|
||||||
|
event_loop,
|
||||||
|
event_loop_handle,
|
||||||
|
alice_btc_wallet,
|
||||||
|
alice_xmr_wallet,
|
||||||
|
alice_db,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn init_bob_state(
|
||||||
|
alice_multiaddr: Multiaddr,
|
||||||
|
btc_to_swap: bitcoin::Amount,
|
||||||
|
xmr_to_swap: xmr_btc::monero::Amount,
|
||||||
|
bob_btc_wallet: Arc<bitcoin::Wallet>,
|
||||||
|
config: Config,
|
||||||
|
) -> BobState {
|
||||||
|
let amounts = SwapAmounts {
|
||||||
|
btc: btc_to_swap,
|
||||||
|
xmr: xmr_to_swap,
|
||||||
|
};
|
||||||
|
|
||||||
|
let refund_address = bob_btc_wallet.new_address().await.unwrap();
|
||||||
|
let state0 = xmr_btc::bob::State0::new(
|
||||||
|
&mut OsRng,
|
||||||
|
btc_to_swap,
|
||||||
|
xmr_to_swap,
|
||||||
|
config.bitcoin_refund_timelock,
|
||||||
|
config.bitcoin_punish_timelock,
|
||||||
|
refund_address,
|
||||||
|
);
|
||||||
|
|
||||||
|
BobState::Started {
|
||||||
|
state0,
|
||||||
|
amounts,
|
||||||
|
addr: alice_multiaddr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_bob_event_loop() -> (bob::event_loop::EventLoop, bob::event_loop::EventLoopHandle) {
|
||||||
|
let bob_behaviour = bob::Behaviour::default();
|
||||||
|
let bob_transport = build(bob_behaviour.identity()).unwrap();
|
||||||
|
bob::event_loop::EventLoop::new(bob_transport, bob_behaviour).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub async fn init_bob(
|
||||||
|
alice_multiaddr: Multiaddr,
|
||||||
|
bitcoind: &Bitcoind<'_>,
|
||||||
|
monero: &Monero,
|
||||||
|
btc_to_swap: bitcoin::Amount,
|
||||||
|
btc_starting_balance: bitcoin::Amount,
|
||||||
|
xmr_to_swap: xmr_btc::monero::Amount,
|
||||||
|
config: Config,
|
||||||
|
) -> (
|
||||||
|
BobState,
|
||||||
|
bob::event_loop::EventLoop,
|
||||||
|
bob::event_loop::EventLoopHandle,
|
||||||
|
Arc<swap::bitcoin::Wallet>,
|
||||||
|
Arc<swap::monero::Wallet>,
|
||||||
|
Database,
|
||||||
|
) {
|
||||||
|
let (bob_btc_wallet, bob_xmr_wallet) = init_wallets(
|
||||||
|
"bob",
|
||||||
|
bitcoind,
|
||||||
|
monero,
|
||||||
|
Some(btc_starting_balance),
|
||||||
|
None,
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let bob_state = init_bob_state(
|
||||||
|
alice_multiaddr,
|
||||||
|
btc_to_swap,
|
||||||
|
xmr_to_swap,
|
||||||
|
bob_btc_wallet.clone(),
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let (event_loop, event_loop_handle) = init_bob_event_loop();
|
||||||
|
|
||||||
|
let bob_db_dir = tempdir().unwrap();
|
||||||
|
let bob_db = Database::open(bob_db_dir.path()).unwrap();
|
||||||
|
|
||||||
|
(
|
||||||
|
bob_state,
|
||||||
|
event_loop,
|
||||||
|
event_loop_handle,
|
||||||
|
bob_btc_wallet,
|
||||||
|
bob_xmr_wallet,
|
||||||
|
bob_db,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is just to keep the containers alive
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct Containers<'a> {
|
||||||
|
pub bitcoind: Bitcoind<'a>,
|
||||||
|
pub monerods: Vec<Container<'a, Cli, image::Monero>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Utility function to initialize logging in the test environment.
|
||||||
|
/// Note that you have to keep the `_guard` in scope after calling in test:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// let _guard = init_tracing();
|
||||||
|
/// ```
|
||||||
|
pub fn init_tracing() -> DefaultGuard {
|
||||||
|
// converts all log records into tracing events
|
||||||
|
// Note: Make sure to initialize without unwrapping, otherwise this causes
|
||||||
|
// trouble when running multiple tests.
|
||||||
|
let _ = LogTracer::init();
|
||||||
|
|
||||||
|
let global_filter = tracing::Level::WARN;
|
||||||
|
let swap_filter = tracing::Level::DEBUG;
|
||||||
|
let xmr_btc_filter = tracing::Level::DEBUG;
|
||||||
|
let monero_harness_filter = tracing::Level::INFO;
|
||||||
|
let bitcoin_harness_filter = tracing::Level::INFO;
|
||||||
|
|
||||||
|
use tracing_subscriber::util::SubscriberInitExt as _;
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(format!(
|
||||||
|
"{},swap={},xmr-btc={},monero_harness={},bitcoin_harness={}",
|
||||||
|
global_filter,
|
||||||
|
swap_filter,
|
||||||
|
xmr_btc_filter,
|
||||||
|
monero_harness_filter,
|
||||||
|
bitcoin_harness_filter,
|
||||||
|
))
|
||||||
|
.set_default()
|
||||||
|
}
|
@ -435,16 +435,16 @@ impl State {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct State0 {
|
pub struct State0 {
|
||||||
a: bitcoin::SecretKey,
|
pub a: bitcoin::SecretKey,
|
||||||
s_a: cross_curve_dleq::Scalar,
|
pub s_a: cross_curve_dleq::Scalar,
|
||||||
v_a: monero::PrivateViewKey,
|
pub v_a: monero::PrivateViewKey,
|
||||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||||
btc: bitcoin::Amount,
|
pub btc: bitcoin::Amount,
|
||||||
xmr: monero::Amount,
|
pub xmr: monero::Amount,
|
||||||
refund_timelock: u32,
|
pub refund_timelock: u32,
|
||||||
punish_timelock: u32,
|
pub punish_timelock: u32,
|
||||||
redeem_address: bitcoin::Address,
|
pub redeem_address: bitcoin::Address,
|
||||||
punish_address: bitcoin::Address,
|
pub punish_address: bitcoin::Address,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State0 {
|
impl State0 {
|
||||||
|
@ -346,7 +346,7 @@ impl_from_child_enum!(State3, State);
|
|||||||
impl_from_child_enum!(State4, State);
|
impl_from_child_enum!(State4, State);
|
||||||
impl_from_child_enum!(State5, State);
|
impl_from_child_enum!(State5, State);
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
pub struct State0 {
|
pub struct State0 {
|
||||||
b: bitcoin::SecretKey,
|
b: bitcoin::SecretKey,
|
||||||
s_b: cross_curve_dleq::Scalar,
|
s_b: cross_curve_dleq::Scalar,
|
||||||
@ -560,25 +560,25 @@ impl State2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct State3 {
|
pub struct State3 {
|
||||||
A: bitcoin::PublicKey,
|
pub A: bitcoin::PublicKey,
|
||||||
b: bitcoin::SecretKey,
|
pub b: bitcoin::SecretKey,
|
||||||
s_b: cross_curve_dleq::Scalar,
|
pub s_b: cross_curve_dleq::Scalar,
|
||||||
S_a_monero: monero::PublicKey,
|
S_a_monero: monero::PublicKey,
|
||||||
S_a_bitcoin: bitcoin::PublicKey,
|
S_a_bitcoin: bitcoin::PublicKey,
|
||||||
v: monero::PrivateViewKey,
|
v: monero::PrivateViewKey,
|
||||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||||
btc: bitcoin::Amount,
|
btc: bitcoin::Amount,
|
||||||
xmr: monero::Amount,
|
xmr: monero::Amount,
|
||||||
refund_timelock: u32,
|
pub refund_timelock: u32,
|
||||||
punish_timelock: u32,
|
punish_timelock: u32,
|
||||||
refund_address: bitcoin::Address,
|
pub refund_address: bitcoin::Address,
|
||||||
redeem_address: bitcoin::Address,
|
redeem_address: bitcoin::Address,
|
||||||
punish_address: bitcoin::Address,
|
punish_address: bitcoin::Address,
|
||||||
tx_lock: bitcoin::TxLock,
|
pub tx_lock: bitcoin::TxLock,
|
||||||
tx_cancel_sig_a: Signature,
|
pub tx_cancel_sig_a: Signature,
|
||||||
tx_refund_encsig: EncryptedSignature,
|
pub tx_refund_encsig: EncryptedSignature,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State3 {
|
impl State3 {
|
||||||
@ -626,11 +626,11 @@ impl State3 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||||
pub struct State4 {
|
pub struct State4 {
|
||||||
pub A: bitcoin::PublicKey,
|
pub A: bitcoin::PublicKey,
|
||||||
pub b: bitcoin::SecretKey,
|
pub b: bitcoin::SecretKey,
|
||||||
s_b: cross_curve_dleq::Scalar,
|
pub s_b: cross_curve_dleq::Scalar,
|
||||||
S_a_monero: monero::PublicKey,
|
S_a_monero: monero::PublicKey,
|
||||||
pub S_a_bitcoin: bitcoin::PublicKey,
|
pub S_a_bitcoin: bitcoin::PublicKey,
|
||||||
v: monero::PrivateViewKey,
|
v: monero::PrivateViewKey,
|
||||||
@ -639,12 +639,12 @@ pub struct State4 {
|
|||||||
xmr: monero::Amount,
|
xmr: monero::Amount,
|
||||||
pub refund_timelock: u32,
|
pub refund_timelock: u32,
|
||||||
punish_timelock: u32,
|
punish_timelock: u32,
|
||||||
refund_address: bitcoin::Address,
|
pub refund_address: bitcoin::Address,
|
||||||
pub redeem_address: bitcoin::Address,
|
pub redeem_address: bitcoin::Address,
|
||||||
punish_address: bitcoin::Address,
|
punish_address: bitcoin::Address,
|
||||||
pub tx_lock: bitcoin::TxLock,
|
pub tx_lock: bitcoin::TxLock,
|
||||||
tx_cancel_sig_a: Signature,
|
pub tx_cancel_sig_a: Signature,
|
||||||
tx_refund_encsig: EncryptedSignature,
|
pub tx_refund_encsig: EncryptedSignature,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State4 {
|
impl State4 {
|
||||||
@ -823,25 +823,25 @@ impl State4 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||||
pub struct State5 {
|
pub struct State5 {
|
||||||
A: bitcoin::PublicKey,
|
A: bitcoin::PublicKey,
|
||||||
b: bitcoin::SecretKey,
|
pub b: bitcoin::SecretKey,
|
||||||
#[serde(with = "monero_private_key")]
|
#[serde(with = "monero_private_key")]
|
||||||
s_a: monero::PrivateKey,
|
s_a: monero::PrivateKey,
|
||||||
s_b: cross_curve_dleq::Scalar,
|
pub s_b: cross_curve_dleq::Scalar,
|
||||||
S_a_monero: monero::PublicKey,
|
S_a_monero: monero::PublicKey,
|
||||||
S_a_bitcoin: bitcoin::PublicKey,
|
pub S_a_bitcoin: bitcoin::PublicKey,
|
||||||
v: monero::PrivateViewKey,
|
pub v: monero::PrivateViewKey,
|
||||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||||
btc: bitcoin::Amount,
|
btc: bitcoin::Amount,
|
||||||
xmr: monero::Amount,
|
xmr: monero::Amount,
|
||||||
refund_timelock: u32,
|
refund_timelock: u32,
|
||||||
punish_timelock: u32,
|
punish_timelock: u32,
|
||||||
refund_address: bitcoin::Address,
|
refund_address: bitcoin::Address,
|
||||||
redeem_address: bitcoin::Address,
|
pub redeem_address: bitcoin::Address,
|
||||||
punish_address: bitcoin::Address,
|
punish_address: bitcoin::Address,
|
||||||
tx_lock: bitcoin::TxLock,
|
pub tx_lock: bitcoin::TxLock,
|
||||||
tx_refund_encsig: EncryptedSignature,
|
tx_refund_encsig: EncryptedSignature,
|
||||||
tx_cancel_sig: Signature,
|
tx_cancel_sig: Signature,
|
||||||
}
|
}
|
||||||
|
@ -76,6 +76,7 @@ pub struct PublicViewKey(PublicKey);
|
|||||||
pub struct Amount(u64);
|
pub struct Amount(u64);
|
||||||
|
|
||||||
impl Amount {
|
impl Amount {
|
||||||
|
pub const ZERO: Self = Self(0);
|
||||||
/// Create an [Amount] with piconero precision and the given number of
|
/// Create an [Amount] with piconero precision and the given number of
|
||||||
/// piconeros.
|
/// piconeros.
|
||||||
///
|
///
|
||||||
|
Loading…
Reference in New Issue
Block a user