Implemented Alice transition from BtcLocked to XmrLocked

This commit is contained in:
Franck Royer 2020-11-18 16:27:50 +11:00 committed by rishflab
parent 75e7fedfed
commit 8976a03b3d
3 changed files with 140 additions and 20 deletions

View File

@ -15,7 +15,7 @@ use crate::{
storage::Database, storage::Database,
SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK,
}; };
use anyhow::{bail, Context, Result}; use anyhow::{anyhow, bail, Context, Result};
use async_recursion::async_recursion; use async_recursion::async_recursion;
use async_trait::async_trait; use async_trait::async_trait;
use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _}; use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _};
@ -46,9 +46,21 @@ mod message3;
// 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.
pub enum AliceState { pub enum AliceState {
Started, Started {
Negotiated(State3), amounts: SwapAmounts,
BtcLocked, },
Negotiated {
swap_id: Uuid,
channel: ResponseChannel<AliceToBob>,
amounts: SwapAmounts,
state3: State3,
},
BtcLocked {
swap_id: Uuid,
channel: ResponseChannel<AliceToBob>,
amounts: SwapAmounts,
state3: State3,
},
XmrLocked, XmrLocked,
BtcRedeemed, BtcRedeemed,
XmrRefunded, XmrRefunded,
@ -61,16 +73,19 @@ pub enum AliceState {
#[async_recursion] #[async_recursion]
pub async fn simple_swap<R>( pub async fn simple_swap<R>(
state: AliceState, state: AliceState,
// TODO: Would it make it better if it's in the `Started` enum variant so we don't carry it
// along?
rng: &mut R, rng: &mut R,
mut swarm: Swarm, mut swarm: Swarm,
db: Database, db: Database,
bitcoin_wallet: Arc<crate::bitcoin::Wallet>, bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
monero_wallet: Arc<crate::monero::Wallet>,
) -> Result<AliceState> ) -> Result<AliceState>
where where
R: RngCore + CryptoRng + Send, R: RngCore + CryptoRng + Send,
{ {
match state { match state {
AliceState::Started => { AliceState::Started { amounts } => {
// Bob dials us // Bob dials us
let bob_peer_id = match swarm.next().await { let bob_peer_id = match swarm.next().await {
OutEvent::ConnectionEstablished(bob_peer_id) => bob_peer_id, OutEvent::ConnectionEstablished(bob_peer_id) => bob_peer_id,
@ -83,7 +98,13 @@ where
other => bail!("Unexpected event received: {:?}", other), other => bail!("Unexpected event received: {:?}", other),
}; };
let amounts = calculate_amounts(btc); if btc != amounts.btc {
bail!(
"Bob proposed a different amount; got {}, expected: {}",
btc,
amounts.btc
);
}
swarm.send_amounts(channel, amounts); swarm.send_amounts(channel, amounts);
let SwapAmounts { btc, xmr } = amounts; let SwapAmounts { btc, xmr } = amounts;
@ -141,30 +162,95 @@ where
); );
simple_swap( simple_swap(
AliceState::Negotiated(state3), AliceState::Negotiated {
swap_id,
state3,
channel,
amounts,
},
rng, rng,
swarm, swarm,
db, db,
bitcoin_wallet, bitcoin_wallet,
monero_wallet,
) )
.await .await
} }
AliceState::Negotiated(state3) => { AliceState::Negotiated {
swap_id,
state3,
channel,
amounts,
} => {
// TODO(1): Do a future select with watch bitcoin blockchain time // TODO(1): Do a future select with watch bitcoin blockchain time
// TODO(2): Implement a proper safe expiry module // TODO(2): Implement a proper safe expiry module
timeout( timeout(
Duration::from_secs(TX_LOCK_MINE_TIMEOUT), Duration::from_secs(TX_LOCK_MINE_TIMEOUT),
// TODO(Franck): Need to check amount?
bitcoin_wallet.watch_for_raw_transaction(state3.tx_lock.txid()), bitcoin_wallet.watch_for_raw_transaction(state3.tx_lock.txid()),
) )
.await .await
.context("Timed out, Bob did not lock Bitcoin in time")?; .context("Timed out, Bob did not lock Bitcoin in time")?;
simple_swap(AliceState::BtcLocked, rng, swarm, db, bitcoin_wallet).await db.insert_latest_state(swap_id, state::Alice::BtcLocked(state3.clone()).into())
.await?;
simple_swap(
AliceState::BtcLocked {
swap_id,
channel,
amounts,
state3,
},
rng,
swarm,
db,
bitcoin_wallet,
monero_wallet,
)
.await
} }
AliceState::BtcLocked => { AliceState::BtcLocked {
// Alice has seen that Bob has locked BTC swap_id,
// Todo: Alice locks XMR channel,
simple_swap(AliceState::XmrLocked, rng, swarm, db, bitcoin_wallet).await amounts,
state3,
} => {
let S_a = monero::PublicKey::from_private_key(&monero::PrivateKey {
scalar: state3.s_a.into_ed25519(),
});
let public_spend_key = S_a + state3.S_b_monero;
let public_view_key = state3.v.public();
// TODO(Franck): Probably need to wait at least 1 confirmation to be sure that
// we don't wrongfully think this is done.
let (transfer_proof, _) = monero_wallet
.transfer(public_spend_key, public_view_key, amounts.xmr)
.await?;
swarm.send_message2(channel, alice::Message2 {
tx_lock_proof: transfer_proof,
});
// TODO(Franck): we should merge state::Alice and AliceState.
// There should be only 2 states:
// 1. the cryptographic state (State0, etc) which only aware of the crypto
// primitive to execute the protocol 2. the more general/business
// state that contains the crypto + other business data such as network
// communication, amounts to verify, swap id, etc.
db.insert_latest_state(swap_id, state::Alice::XmrLocked(state3).into())
.await?;
simple_swap(
AliceState::XmrLocked,
rng,
swarm,
db,
bitcoin_wallet,
monero_wallet,
)
.await
} }
AliceState::XmrLocked => { AliceState::XmrLocked => {
// Alice has locked Xmr // Alice has locked Xmr
@ -172,10 +258,26 @@ where
// Todo: Poll the swarm here until msg from Bob arrives or t1 // Todo: Poll the swarm here until msg from Bob arrives or t1
if unimplemented!("key_received") { if unimplemented!("key_received") {
// Alice redeems BTC // Alice redeems BTC
simple_swap(AliceState::BtcRedeemed, rng, swarm, db, bitcoin_wallet).await simple_swap(
AliceState::BtcRedeemed,
rng,
swarm,
db,
bitcoin_wallet,
monero_wallet,
)
.await
} else { } else {
// submit TxCancel // submit TxCancel
simple_swap(AliceState::Cancelled, rng, swarm, db, bitcoin_wallet).await simple_swap(
AliceState::Cancelled,
rng,
swarm,
db,
bitcoin_wallet,
monero_wallet,
)
.await
} }
} }
AliceState::Cancelled => { AliceState::Cancelled => {
@ -183,9 +285,25 @@ where
// If Bob has refunded the Alice should extract Bob's monero secret key and move // If Bob has refunded the Alice should extract Bob's monero secret key and move
// the TxLockXmr output to her wallet. // the TxLockXmr output to her wallet.
if unimplemented!("refunded") { if unimplemented!("refunded") {
simple_swap(AliceState::XmrRefunded, rng, swarm, db, bitcoin_wallet).await simple_swap(
AliceState::XmrRefunded,
rng,
swarm,
db,
bitcoin_wallet,
monero_wallet,
)
.await
} else { } else {
simple_swap(AliceState::Punished, rng, swarm, db, bitcoin_wallet).await simple_swap(
AliceState::Punished,
rng,
swarm,
db,
bitcoin_wallet,
monero_wallet,
)
.await
} }
} }
AliceState::XmrRefunded => Ok(AliceState::XmrRefunded), AliceState::XmrRefunded => Ok(AliceState::XmrRefunded),
@ -199,15 +317,15 @@ where
#[async_recursion] #[async_recursion]
pub async fn abort(state: AliceState) -> Result<AliceState> { pub async fn abort(state: AliceState) -> Result<AliceState> {
match state { match state {
AliceState::Started => { AliceState::Started { .. } => {
// Nothing has been commited by either party, abort swap. // Nothing has been commited by either party, abort swap.
abort(AliceState::SafelyAborted).await abort(AliceState::SafelyAborted).await
} }
AliceState::Negotiated(_) => { AliceState::Negotiated { .. } => {
// Nothing has been commited by either party, abort swap. // Nothing has been commited by either party, abort swap.
abort(AliceState::SafelyAborted).await abort(AliceState::SafelyAborted).await
} }
AliceState::BtcLocked => { AliceState::BtcLocked { .. } => {
// Alice has seen that Bob has locked BTC // Alice has seen that Bob has locked BTC
// Alice does not need to do anything to recover // Alice does not need to do anything to recover
abort(AliceState::SafelyAborted).await abort(AliceState::SafelyAborted).await

View File

@ -34,6 +34,7 @@ pub enum Rsp {
/// XMR/BTC swap amounts. /// XMR/BTC swap amounts.
#[derive(Copy, Clone, Debug, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
// TODO(Franck): review necessity of this struct
pub struct SwapAmounts { pub struct SwapAmounts {
/// Amount of BTC to swap. /// Amount of BTC to swap.
#[serde(with = "::bitcoin::util::amount::serde::as_sat")] #[serde(with = "::bitcoin::util::amount::serde::as_sat")]

View File

@ -617,6 +617,7 @@ impl State2 {
S_b_monero: self.S_b_monero, S_b_monero: self.S_b_monero,
S_b_bitcoin: self.S_b_bitcoin, S_b_bitcoin: self.S_b_bitcoin,
v: self.v, v: self.v,
// TODO(Franck): Review if these amounts are actually needed
btc: self.btc, btc: self.btc,
xmr: self.xmr, xmr: self.xmr,
refund_timelock: self.refund_timelock, refund_timelock: self.refund_timelock,