Save Bob state during swap

This commit is contained in:
Franck Royer 2020-12-08 14:54:08 +11:00
parent 7e0a1ffe84
commit 905fc6cf35
No known key found for this signature in database
GPG Key ID: A82ED75A8DFC50A4
6 changed files with 255 additions and 92 deletions

View File

@ -1,5 +1,7 @@
use crate::{ use crate::{
bob::{event_loop::EventLoopHandle, execution::negotiate}, bob::{event_loop::EventLoopHandle, execution::negotiate},
state,
state::Bob,
storage::Database, storage::Database,
SwapAmounts, SwapAmounts,
}; };
@ -17,6 +19,7 @@ use xmr_btc::{
// The same data structure is used for swap execution and recovery. // The same data structure is used for swap execution and recovery.
// This allows for a seamless transition from a failed swap to recovery. // This allows for a seamless transition from a failed swap to recovery.
#[derive(Debug, Clone)]
pub enum BobState { pub enum BobState {
Started { Started {
state0: bob::State0, state0: bob::State0,
@ -53,6 +56,32 @@ impl fmt::Display for BobState {
} }
} }
impl From<BobState> for state::Bob {
fn from(bob_state: BobState) -> Self {
match bob_state {
BobState::Started {
state0,
amounts,
addr,
} => Bob::Started {
state0,
amounts,
addr,
},
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,
}
}
}
pub async fn swap<R>( pub async fn swap<R>(
state: BobState, state: BobState,
event_loop_handle: EventLoopHandle, event_loop_handle: EventLoopHandle,
@ -131,8 +160,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, alice_peer_id), state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
db, db,
@ -146,9 +180,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,
@ -168,8 +206,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,
@ -192,8 +234,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,
@ -209,10 +255,14 @@ where
let redeem_watcher = state.watch_for_redeem_btc(bitcoin_wallet.as_ref()); let redeem_watcher = state.watch_for_redeem_btc(bitcoin_wallet.as_ref());
let t1_timeout = state.wait_for_t1(bitcoin_wallet.as_ref()); let t1_timeout = state.wait_for_t1(bitcoin_wallet.as_ref());
// TODO(Franck): Check if db save and run_until can be factorized
tokio::select! { tokio::select! {
val = redeem_watcher => { val = redeem_watcher => {
let state = BobState::BtcRedeemed(val?);
let db_state = state.clone().into();
db.insert_latest_state(swap_id, state::Swap::Bob(db_state)).await?;
run_until( run_until(
BobState::BtcRedeemed(val?), state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
db, db,
@ -230,15 +280,18 @@ where
state.submit_tx_cancel(bitcoin_wallet.as_ref()).await?; state.submit_tx_cancel(bitcoin_wallet.as_ref()).await?;
} }
let state = BobState::Cancelled(state);
let db_state = state.clone().into();
db.insert_latest_state(swap_id, state::Swap::Bob(db_state)).await?;
run_until( run_until(
BobState::Cancelled(state), state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
db, db,
bitcoin_wallet, bitcoin_wallet,
monero_wallet, monero_wallet,
rng, rng,
swap_id swap_id
) )
.await .await
@ -248,8 +301,13 @@ where
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,
@ -261,37 +319,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),

View File

@ -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;
@ -10,6 +10,7 @@ pub mod cli;
pub mod monero; pub mod monero;
pub mod network; pub mod network;
pub mod recover; 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 +32,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.

View File

@ -24,9 +24,12 @@ use futures::{
}; };
use sha2::Sha256; use sha2::Sha256;
use tracing::info; use tracing::info;
use xmr_btc::bitcoin::{ use xmr_btc::{
poll_until_block_height_is_gte, BroadcastSignedTransaction, TransactionBlockHeight, bitcoin::{
WatchForRawTransaction, poll_until_block_height_is_gte, BroadcastSignedTransaction, TransactionBlockHeight,
WatchForRawTransaction,
},
bob::{State3, State4},
}; };
pub async fn recover( pub async fn recover(
@ -366,18 +369,53 @@ pub async fn bob_recover(
state: Bob, state: Bob,
) -> Result<()> { ) -> Result<()> {
match state { match state {
Bob::Handshaken(_) | Bob::SwapComplete => { Bob::Negotiated { .. } | Bob::SwapComplete => {
info!("Nothing to do"); info!("Nothing to do");
} }
Bob::BtcLocked(state) | Bob::XmrLocked(state) | Bob::BtcRefundable(state) => { Bob::BtcLocked {
state3:
State3 {
A,
b,
s_b,
refund_timelock,
refund_address,
tx_lock,
tx_cancel_sig_a,
tx_refund_encsig,
..
},
..
}
| Bob::XmrLocked {
state4:
State4 {
A,
b,
s_b,
refund_timelock,
refund_address,
tx_lock,
tx_cancel_sig_a,
tx_refund_encsig,
..
},
..
}
| Bob::BtcCancelled(State4 {
A,
b,
s_b,
refund_timelock,
refund_address,
tx_lock,
tx_cancel_sig_a,
tx_refund_encsig,
..
}) => {
info!("Bitcoin may still be locked up, attempting to refund"); info!("Bitcoin may still be locked up, attempting to refund");
let tx_cancel = bitcoin::TxCancel::new( let tx_cancel = bitcoin::TxCancel::new(&tx_lock, refund_timelock, A, b.public());
&state.tx_lock,
state.refund_timelock,
state.A,
state.b.public(),
);
info!("Checking if the Bitcoin cancel transaction has been published"); info!("Checking if the Bitcoin cancel transaction has been published");
if bitcoin_wallet if bitcoin_wallet
@ -389,20 +427,17 @@ pub async fn bob_recover(
info!("Bitcoin cancel transaction not yet published"); info!("Bitcoin cancel transaction not yet published");
let tx_lock_height = bitcoin_wallet let tx_lock_height = bitcoin_wallet
.transaction_block_height(state.tx_lock.txid()) .transaction_block_height(tx_lock.txid())
.await;
poll_until_block_height_is_gte(&bitcoin_wallet, tx_lock_height + refund_timelock)
.await; .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_a = tx_cancel_sig_a.clone();
let sig_b = state.b.sign(tx_cancel.digest()); let sig_b = b.sign(tx_cancel.digest());
let tx_cancel = tx_cancel let tx_cancel = tx_cancel
.clone() .clone()
.add_signatures(&state.tx_lock, (state.A, sig_a), (state.b.public(), sig_b)) .add_signatures(&tx_lock, (A, sig_a), (b.public(), sig_b))
.expect("sig_{a,b} to be valid signatures for tx_cancel"); .expect("sig_{a,b} to be valid signatures for tx_cancel");
// TODO: We should not fail if the transaction is already on the blockchain // TODO: We should not fail if the transaction is already on the blockchain
@ -413,15 +448,15 @@ pub async fn bob_recover(
info!("Confirmed that Bitcoin cancel transaction is on the blockchain"); info!("Confirmed that Bitcoin cancel transaction is on the blockchain");
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &state.refund_address); let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &refund_address);
let signed_tx_refund = { let signed_tx_refund = {
let adaptor = Adaptor::<Sha256, Deterministic<Sha256>>::default(); let adaptor = Adaptor::<Sha256, Deterministic<Sha256>>::default();
let sig_a = adaptor let sig_a =
.decrypt_signature(&state.s_b.into_secp256k1(), state.tx_refund_encsig.clone()); adaptor.decrypt_signature(&s_b.into_secp256k1(), tx_refund_encsig.clone());
let sig_b = state.b.sign(tx_refund.digest()); let sig_b = b.sign(tx_refund.digest());
tx_refund tx_refund
.add_signatures(&tx_cancel, (state.A, sig_a), (state.b.public(), sig_b)) .add_signatures(&tx_cancel, (A, sig_a), (b.public(), sig_b))
.expect("sig_{a,b} to be valid signatures for tx_refund") .expect("sig_{a,b} to be valid signatures for tx_refund")
}; };
@ -459,6 +494,8 @@ pub async fn bob_recover(
.await?; .await?;
info!("Successfully redeemed monero") info!("Successfully redeemed monero")
} }
Bob::Started { .. } => todo!(),
Bob::EncSigSent { .. } => todo!(),
}; };
Ok(()) Ok(())

47
swap/src/serde.rs Normal file
View 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());
}
}
}

View File

@ -1,3 +1,5 @@
use crate::SwapAmounts;
use libp2p::{core::Multiaddr, PeerId};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Display; use std::fmt::Display;
use xmr_btc::{alice, bitcoin::EncryptedSignature, bob, monero, serde::monero_private_key}; use xmr_btc::{alice, bitcoin::EncryptedSignature, bob, monero, serde::monero_private_key};
@ -37,11 +39,33 @@ pub enum Alice {
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum Bob { pub enum Bob {
Handshaken(bob::State2), Started {
BtcLocked(bob::State2), state0: bob::State0,
XmrLocked(bob::State2), amounts: SwapAmounts,
BtcRedeemed(bob::State2), addr: Multiaddr,
BtcRefundable(bob::State2), },
Negotiated {
state2: bob::State2,
#[serde(with = "crate::serde::peer_id")]
peer_id: PeerId,
},
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,
} }
@ -85,12 +109,14 @@ 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::Started { .. } => f.write_str("Swap started"),
Bob::EncSigSent { .. } => f.write_str("Encrypted signature sent"),
} }
} }
} }

View File

@ -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,
} }