Started to extract some steps

Tealised that the whole point is for them to be idempotent to be useful
This commit is contained in:
Franck Royer 2020-11-25 16:16:04 +11:00 committed by rishflab
parent 66866f8fbd
commit 437c1cbb80
4 changed files with 143 additions and 141 deletions

View File

@ -2,6 +2,7 @@
//! Alice holds XMR and wishes receive BTC. //! Alice holds XMR and wishes receive BTC.
use self::{amounts::*, message0::*, message1::*, message2::*, message3::*}; use self::{amounts::*, message0::*, message1::*, message2::*, message3::*};
use crate::{ use crate::{
alice::execution::{lock_xmr, negotiate},
bitcoin, bitcoin,
bitcoin::{EncryptedSignature, TX_LOCK_MINE_TIMEOUT}, bitcoin::{EncryptedSignature, TX_LOCK_MINE_TIMEOUT},
monero, monero,
@ -15,7 +16,7 @@ use crate::{
storage::Database, storage::Database,
SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK,
}; };
use anyhow::{anyhow, bail, Context, Result}; use anyhow::{anyhow, 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 _};
@ -40,14 +41,15 @@ use xmr_btc::{
alice::{self, action_generator, Action, ReceiveBitcoinRedeemEncsig, State0, State3}, alice::{self, action_generator, Action, ReceiveBitcoinRedeemEncsig, State0, State3},
bitcoin::{ bitcoin::{
poll_until_block_height_is_gte, BroadcastSignedTransaction, GetRawTransaction, poll_until_block_height_is_gte, BroadcastSignedTransaction, GetRawTransaction,
TransactionBlockHeight, TxCancel, TxRefund, WatchForRawTransaction, TransactionBlockHeight, TxCancel, TxRefund, WaitForTransactionFinality,
WatchForTransactionFinality, WatchForRawTransaction,
}, },
bob, cross_curve_dleq, bob, cross_curve_dleq,
monero::{CreateWalletForOutput, Transfer}, monero::{CreateWalletForOutput, Transfer},
}; };
mod amounts; mod amounts;
mod execution;
mod message0; mod message0;
mod message1; mod message1;
mod message2; mod message2;
@ -67,13 +69,11 @@ pub enum AliceState {
v_a: monero::PrivateViewKey, v_a: monero::PrivateViewKey,
}, },
Negotiated { Negotiated {
swap_id: Uuid,
channel: ResponseChannel<AliceToBob>, channel: ResponseChannel<AliceToBob>,
amounts: SwapAmounts, amounts: SwapAmounts,
state3: State3, state3: State3,
}, },
BtcLocked { BtcLocked {
swap_id: Uuid,
channel: ResponseChannel<AliceToBob>, channel: ResponseChannel<AliceToBob>,
amounts: SwapAmounts, amounts: SwapAmounts,
state3: State3, state3: State3,
@ -117,7 +117,6 @@ pub enum AliceState {
pub async fn simple_swap( pub async fn simple_swap(
state: AliceState, state: AliceState,
mut swarm: Swarm, mut swarm: Swarm,
db: Database,
bitcoin_wallet: Arc<crate::bitcoin::Wallet>, bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
monero_wallet: Arc<crate::monero::Wallet>, monero_wallet: Arc<crate::monero::Wallet>,
) -> Result<AliceState> { ) -> Result<AliceState> {
@ -128,166 +127,62 @@ pub async fn simple_swap(
s_a, s_a,
v_a, v_a,
} => { } => {
// Bob dials us let (channel, amounts, state3) =
let bob_peer_id = match swarm.next().await { negotiate(amounts, a, s_a, v_a, &mut swarm, bitcoin_wallet.clone()).await?;
OutEvent::ConnectionEstablished(bob_peer_id) => bob_peer_id,
other => bail!("Unexpected event received: {:?}", other),
};
// Bob sends us a request
let (btc, channel) = match swarm.next().await {
OutEvent::Request(amounts::OutEvent::Btc { btc, channel }) => (btc, channel),
other => bail!("Unexpected event received: {:?}", other),
};
if btc != amounts.btc {
bail!(
"Bob proposed a different amount; got {}, expected: {}",
btc,
amounts.btc
);
}
swarm.send_amounts(channel, amounts);
let SwapAmounts { btc, xmr } = amounts;
let redeem_address = bitcoin_wallet.as_ref().new_address().await?;
let punish_address = redeem_address.clone();
let state0 = State0::new(
a,
s_a,
v_a,
btc,
xmr,
REFUND_TIMELOCK,
PUNISH_TIMELOCK,
redeem_address,
punish_address,
);
// Bob sends us message0
let message0 = match swarm.next().await {
OutEvent::Message0(msg) => msg,
other => bail!("Unexpected event received: {:?}", other),
};
let state1 = state0.receive(message0)?;
// TODO(Franck) We should use the same channel everytime,
// Can we remove this response channel?
let (state2, channel) = match swarm.next().await {
OutEvent::Message1 { msg, channel } => {
let state2 = state1.receive(msg);
(state2, channel)
}
other => bail!("Unexpected event: {:?}", other),
};
let message1 = state2.next_message();
swarm.send_message1(channel, message1);
let (state3, channel) = match swarm.next().await {
OutEvent::Message2 { msg, channel } => {
let state3 = state2.receive(msg)?;
(state3, channel)
}
other => bail!("Unexpected event: {:?}", other),
};
let swap_id = Uuid::new_v4();
// TODO(Franck): Use the same terminology (negotiated) to describe this state.
db.insert_latest_state(swap_id, state::Alice::Handshaken(state3.clone()).into())
.await?;
info!(
"State transitioned from Started to Negotiated, Bob peer id is {}",
bob_peer_id
);
simple_swap( simple_swap(
AliceState::Negotiated { AliceState::Negotiated {
swap_id,
state3,
channel, channel,
amounts, amounts,
state3,
}, },
swarm, swarm,
db,
bitcoin_wallet, bitcoin_wallet,
monero_wallet, monero_wallet,
) )
.await .await
} }
AliceState::Negotiated { AliceState::Negotiated {
swap_id,
state3, state3,
channel, channel,
amounts, amounts,
} => { } => {
// TODO(1): Do a future select with watch bitcoin blockchain time
// 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.wait_for_transaction_finality(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")?;
db.insert_latest_state(swap_id, state::Alice::BtcLocked(state3.clone()).into())
.await?;
simple_swap( simple_swap(
AliceState::BtcLocked { AliceState::BtcLocked {
swap_id,
channel, channel,
amounts, amounts,
state3, state3,
}, },
swarm, swarm,
db,
bitcoin_wallet, bitcoin_wallet,
monero_wallet, monero_wallet,
) )
.await .await
} }
AliceState::BtcLocked { AliceState::BtcLocked {
swap_id,
channel, channel,
amounts, amounts,
state3, state3,
} => { } => {
let S_a = monero::PublicKey::from_private_key(&monero::PrivateKey { lock_xmr(
scalar: state3.s_a.into_ed25519(), channel,
}); amounts,
state3.clone(),
let public_spend_key = S_a + state3.S_b_monero; &mut swarm,
let public_view_key = state3.v.public(); monero_wallet.clone(),
)
// TODO(Franck): Probably need to wait at least 1 confirmation to be sure that .await?;
// 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.clone()).into())
.await?;
simple_swap( simple_swap(
AliceState::XmrLocked { state3 }, AliceState::XmrLocked { state3 },
swarm, swarm,
db,
bitcoin_wallet, bitcoin_wallet,
monero_wallet, monero_wallet,
) )
@ -295,8 +190,7 @@ pub async fn simple_swap(
} }
AliceState::XmrLocked { state3 } => { AliceState::XmrLocked { state3 } => {
let encsig = timeout( let encsig = timeout(
// TODO(Franck): This is now inefficient as time has been spent since btc was // Give a set arbitrary time to Bob to send us `tx_redeem_encsign`
// locked
Duration::from_secs(TX_LOCK_MINE_TIMEOUT), Duration::from_secs(TX_LOCK_MINE_TIMEOUT),
async { async {
match swarm.next().await { match swarm.next().await {
@ -318,7 +212,6 @@ pub async fn simple_swap(
simple_swap( simple_swap(
AliceState::WaitingToCancel { state3 }, AliceState::WaitingToCancel { state3 },
swarm, swarm,
db,
bitcoin_wallet, bitcoin_wallet,
monero_wallet, monero_wallet,
) )
@ -330,7 +223,6 @@ pub async fn simple_swap(
simple_swap( simple_swap(
AliceState::WaitingToCancel { state3 }, AliceState::WaitingToCancel { state3 },
swarm, swarm,
db,
bitcoin_wallet, bitcoin_wallet,
monero_wallet, monero_wallet,
) )
@ -345,7 +237,6 @@ pub async fn simple_swap(
encrypted_signature, encrypted_signature,
}, },
swarm, swarm,
db,
bitcoin_wallet, bitcoin_wallet,
monero_wallet, monero_wallet,
) )
@ -397,7 +288,6 @@ pub async fn simple_swap(
simple_swap( simple_swap(
AliceState::BtcRedeemed, AliceState::BtcRedeemed,
swarm, swarm,
db,
bitcoin_wallet, bitcoin_wallet,
monero_wallet, monero_wallet,
) )
@ -441,7 +331,6 @@ pub async fn simple_swap(
simple_swap( simple_swap(
AliceState::BtcCancelled { state3, tx_cancel }, AliceState::BtcCancelled { state3, tx_cancel },
swarm, swarm,
db,
bitcoin_wallet, bitcoin_wallet,
monero_wallet, monero_wallet,
) )
@ -468,7 +357,6 @@ pub async fn simple_swap(
simple_swap( simple_swap(
AliceState::BtcPunishable { tx_refund, state3 }, AliceState::BtcPunishable { tx_refund, state3 },
swarm, swarm,
db,
bitcoin_wallet.clone(), bitcoin_wallet.clone(),
monero_wallet, monero_wallet,
) )
@ -482,7 +370,6 @@ pub async fn simple_swap(
state3, state3,
}, },
swarm, swarm,
db,
bitcoin_wallet.clone(), bitcoin_wallet.clone(),
monero_wallet, monero_wallet,
) )
@ -552,7 +439,6 @@ pub async fn simple_swap(
state3, state3,
}, },
swarm, swarm,
db,
bitcoin_wallet.clone(), bitcoin_wallet.clone(),
monero_wallet, monero_wallet,
) )
@ -563,7 +449,7 @@ pub async fn simple_swap(
tx_refund, tx_refund,
state3, state3,
} => { } => {
let punish_tx_finalised = bitcoin_wallet.watch_for_transaction_finality(punished_tx_id); let punish_tx_finalised = bitcoin_wallet.wait_for_transaction_finality(punished_tx_id);
let refund_tx_seen = bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid()); let refund_tx_seen = bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid());
@ -575,7 +461,6 @@ pub async fn simple_swap(
simple_swap( simple_swap(
AliceState::Punished, AliceState::Punished,
swarm, swarm,
db,
bitcoin_wallet.clone(), bitcoin_wallet.clone(),
monero_wallet, monero_wallet,
) )
@ -589,7 +474,6 @@ pub async fn simple_swap(
state3, state3,
}, },
swarm, swarm,
db,
bitcoin_wallet.clone(), bitcoin_wallet.clone(),
monero_wallet, monero_wallet,
) )

119
swap/src/alice/execution.rs Normal file
View File

@ -0,0 +1,119 @@
use crate::{
alice::{amounts, OutEvent, Swarm},
network::request_response::AliceToBob,
SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK,
};
use anyhow::{bail, Result};
use libp2p::request_response::ResponseChannel;
use std::sync::Arc;
use xmr_btc::{
alice,
alice::{State0, State3},
cross_curve_dleq,
monero::Transfer,
};
// TODO(Franck): Make all methods here idempotent using db
pub async fn negotiate(
amounts: SwapAmounts,
a: crate::bitcoin::SecretKey,
s_a: cross_curve_dleq::Scalar,
v_a: crate::monero::PrivateViewKey,
swarm: &mut Swarm,
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
) -> Result<(ResponseChannel<AliceToBob>, SwapAmounts, State3)> {
// Bob dials us
match swarm.next().await {
OutEvent::ConnectionEstablished(_bob_peer_id) => {}
other => bail!("Unexpected event received: {:?}", other),
};
// Bob sends us a request
let (btc, channel) = match swarm.next().await {
OutEvent::Request(amounts::OutEvent::Btc { btc, channel }) => (btc, channel),
other => bail!("Unexpected event received: {:?}", other),
};
if btc != amounts.btc {
bail!(
"Bob proposed a different amount; got {}, expected: {}",
btc,
amounts.btc
);
}
swarm.send_amounts(channel, amounts);
let SwapAmounts { btc, xmr } = amounts;
let redeem_address = bitcoin_wallet.as_ref().new_address().await?;
let punish_address = redeem_address.clone();
let state0 = State0::new(
a,
s_a,
v_a,
btc,
xmr,
REFUND_TIMELOCK,
PUNISH_TIMELOCK,
redeem_address,
punish_address,
);
// Bob sends us message0
let message0 = match swarm.next().await {
OutEvent::Message0(msg) => msg,
other => bail!("Unexpected event received: {:?}", other),
};
let state1 = state0.receive(message0)?;
let (state2, channel) = match swarm.next().await {
OutEvent::Message1 { msg, channel } => {
let state2 = state1.receive(msg);
(state2, channel)
}
other => bail!("Unexpected event: {:?}", other),
};
let message1 = state2.next_message();
swarm.send_message1(channel, message1);
let (state3, channel) = match swarm.next().await {
OutEvent::Message2 { msg, channel } => {
let state3 = state2.receive(msg)?;
(state3, channel)
}
other => bail!("Unexpected event: {:?}", other),
};
Ok((channel, amounts, state3))
}
pub async fn lock_xmr(
channel: ResponseChannel<AliceToBob>,
amounts: SwapAmounts,
state3: State3,
swarm: &mut Swarm,
monero_wallet: Arc<crate::monero::Wallet>,
) -> Result<()> {
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();
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): Wait for Monero to be mined/finalised
Ok(())
}

View File

@ -162,8 +162,8 @@ impl GetRawTransaction for Wallet {
} }
#[async_trait] #[async_trait]
impl WatchForTransactionFinality for Wallet { impl WaitForTransactionFinality for Wallet {
async fn watch_for_transaction_finality(&self, _txid: Txid) { async fn wait_for_transaction_finality(&self, _txid: Txid) {
todo!() todo!()
} }
} }

View File

@ -186,9 +186,8 @@ pub trait WatchForRawTransaction {
async fn watch_for_raw_transaction(&self, txid: Txid) -> Transaction; async fn watch_for_raw_transaction(&self, txid: Txid) -> Transaction;
} }
#[async_trait] pub trait WaitForTransactionFinality {
pub trait GetRawTransaction { async fn wait_for_transaction_finality(&self, txid: Txid);
async fn get_raw_transaction(&self, txid: Txid) -> Result<Transaction>;
} }
#[async_trait] #[async_trait]
@ -208,7 +207,7 @@ pub trait WaitForBlockHeight {
#[async_trait] #[async_trait]
pub trait GetRawTransaction { pub trait GetRawTransaction {
async fn get_raw_transaction(&self, txid: Txid) -> Option<Transaction>; async fn get_raw_transaction(&self, txid: Txid) -> Result<Transaction>;
} }
pub fn recover(S: PublicKey, sig: Signature, encsig: EncryptedSignature) -> Result<SecretKey> { pub fn recover(S: PublicKey, sig: Signature, encsig: EncryptedSignature) -> Result<SecretKey> {