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::{
bob::{event_loop::EventLoopHandle, execution::negotiate},
state,
state::Bob,
storage::Database,
SwapAmounts,
};
@ -17,6 +19,7 @@ use xmr_btc::{
// The same data structure is used for swap execution and recovery.
// This allows for a seamless transition from a failed swap to recovery.
#[derive(Debug, Clone)]
pub enum BobState {
Started {
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>(
state: BobState,
event_loop_handle: EventLoopHandle,
@ -131,8 +160,13 @@ where
bitcoin_wallet.clone(),
)
.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(
BobState::Negotiated(state2, alice_peer_id),
state,
is_target_state,
event_loop_handle,
db,
@ -146,9 +180,13 @@ where
BobState::Negotiated(state2, alice_peer_id) => {
// Alice and Bob have exchanged info
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(
BobState::BtcLocked(state3, alice_peer_id),
state,
is_target_state,
event_loop_handle,
db,
@ -168,8 +206,12 @@ where
.watch_for_lock_xmr(monero_wallet.as_ref(), msg2)
.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(
BobState::XmrLocked(state4, alice_peer_id),
state,
is_target_state,
event_loop_handle,
db,
@ -192,8 +234,12 @@ where
.send_message3(alice_peer_id.clone(), tx_redeem_encsig)
.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(
BobState::EncSigSent(state, alice_peer_id),
state,
is_target_state,
event_loop_handle,
db,
@ -209,10 +255,14 @@ where
let redeem_watcher = state.watch_for_redeem_btc(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! {
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(
BobState::BtcRedeemed(val?),
state,
is_target_state,
event_loop_handle,
db,
@ -230,15 +280,18 @@ where
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(
BobState::Cancelled(state),
is_target_state,
state,
is_target_state,
event_loop_handle,
db,
bitcoin_wallet,
monero_wallet,
rng,
swap_id
swap_id
)
.await
@ -248,8 +301,13 @@ where
BobState::BtcRedeemed(state) => {
// Bob redeems XMR using revealed s_a
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(
BobState::XmrRedeemed,
state,
is_target_state,
event_loop_handle,
db,
@ -261,37 +319,31 @@ where
.await
}
BobState::Cancelled(state) => {
// TODO
// 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::T1 => {
state.refund_btc(bitcoin_wallet.as_ref()).await?;
run_until(
BobState::BtcRefunded(state),
is_target_state,
event_loop_handle,
db,
bitcoin_wallet,
monero_wallet,
rng,
swap_id,
)
.await
BobState::BtcRefunded(state)
}
Epoch::T2 => {
run_until(
BobState::Punished,
is_target_state,
event_loop_handle,
db,
bitcoin_wallet,
monero_wallet,
rng,
swap_id,
)
.await
}
}
Epoch::T2 => BobState::Punished,
};
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,
monero_wallet,
rng,
swap_id,
)
.await
}
BobState::BtcRefunded(state4) => Ok(BobState::BtcRefunded(state4)),
BobState::Punished => Ok(BobState::Punished),

View File

@ -1,6 +1,6 @@
#![allow(non_snake_case)]
use serde::{Deserialize, Serialize};
use ::serde::{Deserialize, Serialize};
use std::fmt::{self, Display};
pub mod alice;
@ -10,6 +10,7 @@ pub mod cli;
pub mod monero;
pub mod network;
pub mod recover;
pub mod serde;
pub mod state;
pub mod storage;
pub mod tor;
@ -31,7 +32,7 @@ pub enum Rsp {
}
/// XMR/BTC swap amounts.
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
// TODO(Franck): review necessity of this struct
pub struct SwapAmounts {
/// Amount of BTC to swap.

View File

@ -24,9 +24,12 @@ use futures::{
};
use sha2::Sha256;
use tracing::info;
use xmr_btc::bitcoin::{
poll_until_block_height_is_gte, BroadcastSignedTransaction, TransactionBlockHeight,
WatchForRawTransaction,
use xmr_btc::{
bitcoin::{
poll_until_block_height_is_gte, BroadcastSignedTransaction, TransactionBlockHeight,
WatchForRawTransaction,
},
bob::{State3, State4},
};
pub async fn recover(
@ -366,18 +369,53 @@ pub async fn bob_recover(
state: Bob,
) -> Result<()> {
match state {
Bob::Handshaken(_) | Bob::SwapComplete => {
Bob::Negotiated { .. } | Bob::SwapComplete => {
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");
let tx_cancel = bitcoin::TxCancel::new(
&state.tx_lock,
state.refund_timelock,
state.A,
state.b.public(),
);
let tx_cancel = bitcoin::TxCancel::new(&tx_lock, refund_timelock, A, b.public());
info!("Checking if the Bitcoin cancel transaction has been published");
if bitcoin_wallet
@ -389,20 +427,17 @@ pub async fn bob_recover(
info!("Bitcoin cancel transaction not yet published");
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;
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 sig_a = tx_cancel_sig_a.clone();
let sig_b = 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))
.add_signatures(&tx_lock, (A, sig_a), (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
@ -413,15 +448,15 @@ pub async fn bob_recover(
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 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());
let sig_a =
adaptor.decrypt_signature(&s_b.into_secp256k1(), tx_refund_encsig.clone());
let sig_b = b.sign(tx_refund.digest());
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")
};
@ -459,6 +494,8 @@ pub async fn bob_recover(
.await?;
info!("Successfully redeemed monero")
}
Bob::Started { .. } => todo!(),
Bob::EncSigSent { .. } => todo!(),
};
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 std::fmt::Display;
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)]
pub enum Bob {
Handshaken(bob::State2),
BtcLocked(bob::State2),
XmrLocked(bob::State2),
BtcRedeemed(bob::State2),
BtcRefundable(bob::State2),
Started {
state0: bob::State0,
amounts: SwapAmounts,
addr: Multiaddr,
},
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,
}
@ -85,12 +109,14 @@ impl Display for Alice {
impl Display for Bob {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Bob::Handshaken(_) => f.write_str("Handshake complete"),
Bob::BtcLocked(_) | Bob::XmrLocked(_) | Bob::BtcRefundable(_) => {
Bob::Negotiated { .. } => f.write_str("Handshake complete"),
Bob::BtcLocked { .. } | Bob::XmrLocked { .. } | Bob::BtcCancelled(_) => {
f.write_str("Bitcoin refundable")
}
Bob::BtcRedeemed(_) => f.write_str("Monero redeemable"),
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!(State5, State);
#[derive(Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct State0 {
b: bitcoin::SecretKey,
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 {
A: bitcoin::PublicKey,
b: bitcoin::SecretKey,
s_b: cross_curve_dleq::Scalar,
pub A: bitcoin::PublicKey,
pub b: bitcoin::SecretKey,
pub s_b: cross_curve_dleq::Scalar,
S_a_monero: monero::PublicKey,
S_a_bitcoin: bitcoin::PublicKey,
v: monero::PrivateViewKey,
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
btc: bitcoin::Amount,
xmr: monero::Amount,
refund_timelock: u32,
pub refund_timelock: u32,
punish_timelock: u32,
refund_address: bitcoin::Address,
pub refund_address: bitcoin::Address,
redeem_address: bitcoin::Address,
punish_address: bitcoin::Address,
tx_lock: bitcoin::TxLock,
tx_cancel_sig_a: Signature,
tx_refund_encsig: EncryptedSignature,
pub tx_lock: bitcoin::TxLock,
pub tx_cancel_sig_a: Signature,
pub tx_refund_encsig: EncryptedSignature,
}
impl State3 {
@ -626,11 +626,11 @@ impl State3 {
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct State4 {
pub A: bitcoin::PublicKey,
pub b: bitcoin::SecretKey,
s_b: cross_curve_dleq::Scalar,
pub s_b: cross_curve_dleq::Scalar,
S_a_monero: monero::PublicKey,
pub S_a_bitcoin: bitcoin::PublicKey,
v: monero::PrivateViewKey,
@ -639,12 +639,12 @@ pub struct State4 {
xmr: monero::Amount,
pub refund_timelock: u32,
punish_timelock: u32,
refund_address: bitcoin::Address,
pub refund_address: bitcoin::Address,
pub redeem_address: bitcoin::Address,
punish_address: bitcoin::Address,
pub tx_lock: bitcoin::TxLock,
tx_cancel_sig_a: Signature,
tx_refund_encsig: EncryptedSignature,
pub tx_cancel_sig_a: Signature,
pub tx_refund_encsig: EncryptedSignature,
}
impl State4 {
@ -823,25 +823,25 @@ impl State4 {
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct State5 {
A: bitcoin::PublicKey,
b: bitcoin::SecretKey,
pub b: bitcoin::SecretKey,
#[serde(with = "monero_private_key")]
s_a: monero::PrivateKey,
s_b: cross_curve_dleq::Scalar,
pub s_b: cross_curve_dleq::Scalar,
S_a_monero: monero::PublicKey,
S_a_bitcoin: bitcoin::PublicKey,
v: monero::PrivateViewKey,
pub S_a_bitcoin: bitcoin::PublicKey,
pub v: monero::PrivateViewKey,
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
btc: bitcoin::Amount,
xmr: monero::Amount,
refund_timelock: u32,
punish_timelock: u32,
refund_address: bitcoin::Address,
redeem_address: bitcoin::Address,
pub redeem_address: bitcoin::Address,
punish_address: bitcoin::Address,
tx_lock: bitcoin::TxLock,
pub tx_lock: bitcoin::TxLock,
tx_refund_encsig: EncryptedSignature,
tx_cancel_sig: Signature,
}