Implemented Alice transition from Started to Negotiated

This commit is contained in:
Franck Royer 2020-11-17 10:24:59 +11:00 committed by rishflab
parent 0fe5131a8a
commit c4cd64d134
4 changed files with 109 additions and 33 deletions

View File

@ -4,7 +4,6 @@ use self::{amounts::*, message0::*, message1::*, message2::*, message3::*};
use crate::{ use crate::{
bitcoin, bitcoin,
bitcoin::TX_LOCK_MINE_TIMEOUT, bitcoin::TX_LOCK_MINE_TIMEOUT,
io::Io,
monero, monero,
network::{ network::{
peer_tracker::{self, PeerTracker}, peer_tracker::{self, PeerTracker},
@ -16,7 +15,7 @@ use crate::{
storage::Database, storage::Database,
SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK,
}; };
use anyhow::Result; use anyhow::{bail, 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 _};
@ -60,23 +59,99 @@ pub enum AliceState {
// State machine driver for swap execution // State machine driver for swap execution
#[async_recursion] #[async_recursion]
pub async fn simple_swap(state: AliceState, io: Io) -> Result<AliceState> { pub async fn simple_swap<R: RngCore + CryptoRng>(
state: AliceState,
&mut rng: R,
mut swarm: Swarm,
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
) -> Result<AliceState> {
match state { match state {
AliceState::Started => { AliceState::Started => {
// Alice and Bob exchange swap info // Bob dials us
// Todo: Poll the swarm here until Alice and Bob have exchanged info let bob_peer_id = match swarm.next().await {
simple_swap(AliceState::Negotiated, io).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),
};
let amounts = calculate_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(
rng,
btc,
xmr,
REFUND_TIMELOCK,
PUNISH_TIMELOCK,
redeem_address,
punish_address,
);
// TODO(Franck) This is not needed
// Review if the swarm really needs to store states
swarm.set_state0(state0.clone());
// 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(AliceState::Negotiated, swarm, rng, bitcoin_wallet).await
} }
AliceState::Negotiated => { AliceState::Negotiated => {
// Alice and Bob have exchanged info // Alice and Bob have exchanged info
// Todo: Alice watches for BTC to be locked on chain // Todo: Alice watches for BTC to be locked on chain
// Todo: Timeout at t1? // Todo: Timeout at t1?
simple_swap(AliceState::BtcLocked, io).await simple_swap(AliceState::BtcLocked, swarm, rng, bitcoin_wallet).await
} }
AliceState::BtcLocked => { AliceState::BtcLocked => {
// Alice has seen that Bob has locked BTC // Alice has seen that Bob has locked BTC
// Todo: Alice locks XMR // Todo: Alice locks XMR
simple_swap(AliceState::XmrLocked, io).await simple_swap(AliceState::XmrLocked, swarm, bitcoin_wallet).await
} }
AliceState::XmrLocked => { AliceState::XmrLocked => {
// Alice has locked Xmr // Alice has locked Xmr
@ -84,10 +159,10 @@ pub async fn simple_swap(state: AliceState, io: Io) -> Result<AliceState> {
// 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, io).await simple_swap(AliceState::BtcRedeemed, swarm, bitcoin_wallet).await
} else { } else {
// submit TxCancel // submit TxCancel
simple_swap(AliceState::Cancelled, io).await simple_swap(AliceState::Cancelled, swarm, bitcoin_wallet).await
} }
} }
AliceState::Cancelled => { AliceState::Cancelled => {
@ -95,9 +170,9 @@ pub async fn simple_swap(state: AliceState, io: Io) -> Result<AliceState> {
// 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, io).await simple_swap(AliceState::XmrRefunded, swarm, bitcoin_wallet).await
} else { } else {
simple_swap(AliceState::Punished, io).await simple_swap(AliceState::Punished, swarm, bitcoin_wallet).await
} }
} }
AliceState::XmrRefunded => Ok(AliceState::XmrRefunded), AliceState::XmrRefunded => Ok(AliceState::XmrRefunded),
@ -109,30 +184,30 @@ pub async fn simple_swap(state: AliceState, io: Io) -> Result<AliceState> {
// State machine driver for recovery execution // State machine driver for recovery execution
#[async_recursion] #[async_recursion]
pub async fn abort(state: AliceState, io: Io) -> 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, io).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, io).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, io).await abort(AliceState::SafelyAborted).await
} }
AliceState::XmrLocked => { AliceState::XmrLocked => {
// Alice has locked XMR // Alice has locked XMR
// Alice watches for TxRedeem until t1 // Alice watches for TxRedeem until t1
if unimplemented!("TxRedeemSeen") { if unimplemented!("TxRedeemSeen") {
// Alice has successfully redeemed, protocol was a success // Alice has successfully redeemed, protocol was a success
abort(AliceState::BtcRedeemed, io).await abort(AliceState::BtcRedeemed).await
} else if unimplemented!("T1Elapsed") { } else if unimplemented!("T1Elapsed") {
// publish TxCancel or see if it has been published // publish TxCancel or see if it has been published
abort(AliceState::Cancelled, io).await abort(AliceState::Cancelled).await
} else { } else {
Err(unimplemented!()) Err(unimplemented!())
} }
@ -142,11 +217,11 @@ pub async fn abort(state: AliceState, io: Io) -> Result<AliceState> {
// Alice waits watches for t2 or TxRefund // Alice waits watches for t2 or TxRefund
if unimplemented!("TxRefundSeen") { if unimplemented!("TxRefundSeen") {
// Bob has refunded and leaked s_b // Bob has refunded and leaked s_b
abort(AliceState::XmrRefunded, io).await abort(AliceState::XmrRefunded).await
} else if unimplemented!("T1Elapsed") { } else if unimplemented!("T1Elapsed") {
// publish TxCancel or see if it has been published // publish TxCancel or see if it has been published
// Wait until t2 and publish TxPunish // Wait until t2 and publish TxPunish
abort(AliceState::Punished, io).await abort(AliceState::Punished).await
} else { } else {
Err(unimplemented!()) Err(unimplemented!())
} }
@ -164,7 +239,7 @@ pub async fn swap(
db: Database, db: Database,
listen: Multiaddr, listen: Multiaddr,
transport: SwapTransport, transport: SwapTransport,
behaviour: Alice, behaviour: Behaviour,
) -> Result<()> { ) -> Result<()> {
struct Network(Arc<Mutex<Swarm>>); struct Network(Arc<Mutex<Swarm>>);
@ -359,9 +434,9 @@ pub async fn swap(
} }
} }
pub type Swarm = libp2p::Swarm<Alice>; pub type Swarm = libp2p::Swarm<Behaviour>;
fn new_swarm(listen: Multiaddr, transport: SwapTransport, behaviour: Alice) -> Result<Swarm> { fn new_swarm(listen: Multiaddr, transport: SwapTransport, behaviour: Behaviour) -> Result<Swarm> {
use anyhow::Context as _; use anyhow::Context as _;
let local_peer_id = behaviour.peer_id(); let local_peer_id = behaviour.peer_id();
@ -384,6 +459,8 @@ fn new_swarm(listen: Multiaddr, transport: SwapTransport, behaviour: Alice) -> R
#[derive(Debug)] #[derive(Debug)]
pub enum OutEvent { pub enum OutEvent {
ConnectionEstablished(PeerId), ConnectionEstablished(PeerId),
// TODO (Franck): Change this to get both amounts so parties can verify the amounts are
// 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(bob::Message0),
Message1 { Message1 {
@ -446,7 +523,7 @@ impl From<message3::OutEvent> for OutEvent {
#[derive(NetworkBehaviour)] #[derive(NetworkBehaviour)]
#[behaviour(out_event = "OutEvent", event_process = false)] #[behaviour(out_event = "OutEvent", event_process = false)]
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Alice { pub struct Behaviour {
pt: PeerTracker, pt: PeerTracker,
amounts: Amounts, amounts: Amounts,
message0: Message0, message0: Message0,
@ -457,7 +534,7 @@ pub struct Alice {
identity: Keypair, identity: Keypair,
} }
impl Alice { impl Behaviour {
pub fn identity(&self) -> Keypair { pub fn identity(&self) -> Keypair {
self.identity.clone() self.identity.clone()
} }
@ -490,7 +567,7 @@ impl Alice {
} }
} }
impl Default for Alice { impl Default for Behaviour {
fn default() -> Self { fn default() -> Self {
let identity = Keypair::generate_ed25519(); let identity = Keypair::generate_ed25519();
@ -507,8 +584,8 @@ impl Default for Alice {
} }
fn calculate_amounts(btc: ::bitcoin::Amount) -> SwapAmounts { fn calculate_amounts(btc: ::bitcoin::Amount) -> SwapAmounts {
// TODO: Get this from an exchange. // TODO (Franck): This should instead verify that the received amounts matches
// This value corresponds to 100 XMR per BTC // the command line arguments This value corresponds to 100 XMR per BTC
const PICONERO_PER_SAT: u64 = 1_000_000; const PICONERO_PER_SAT: u64 = 1_000_000;
let picos = btc.as_sat() * PICONERO_PER_SAT; let picos = btc.as_sat() * PICONERO_PER_SAT;

View File

@ -19,7 +19,7 @@ use prettytable::{row, Table};
use std::{io, io::Write, process, sync::Arc}; use std::{io, io::Write, process, sync::Arc};
use structopt::StructOpt; use structopt::StructOpt;
use swap::{ use swap::{
alice::{self, Alice}, alice::{self, Behaviour},
bitcoin, bitcoin,
bob::{self, Bob}, bob::{self, Bob},
cli::Options, cli::Options,
@ -52,7 +52,7 @@ async fn main() -> Result<()> {
} => { } => {
info!("running swap node as Alice ..."); info!("running swap node as Alice ...");
let behaviour = Alice::default(); let behaviour = Behaviour::default();
let local_key_pair = behaviour.identity(); let local_key_pair = behaviour.identity();
let (listen_addr, _ac, transport) = match tor_port { let (listen_addr, _ac, transport) = match tor_port {
@ -180,7 +180,7 @@ async fn swap_as_alice(
db: Database, db: Database,
addr: Multiaddr, addr: Multiaddr,
transport: SwapTransport, transport: SwapTransport,
behaviour: Alice, behaviour: Behaviour,
) -> Result<()> { ) -> Result<()> {
alice::swap( alice::swap(
bitcoin_wallet, bitcoin_wallet,

View File

@ -36,7 +36,6 @@ use crate::{
storage::Database, storage::Database,
Cmd, Rsp, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, Cmd, Rsp, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK,
}; };
use xmr_btc::{ use xmr_btc::{
alice, alice,
bitcoin::{BroadcastSignedTransaction, EncryptedSignature, SignTxLock}, bitcoin::{BroadcastSignedTransaction, EncryptedSignature, SignTxLock},

View File

@ -74,7 +74,7 @@ async fn swap() {
)); ));
let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.wallet("bob").unwrap().client())); let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.wallet("bob").unwrap().client()));
let alice_behaviour = alice::Alice::default(); let alice_behaviour = alice::Behaviour::default();
let alice_transport = build(alice_behaviour.identity()).unwrap(); let alice_transport = build(alice_behaviour.identity()).unwrap();
let db = Database::open(std::path::Path::new("../.swap-db/")).unwrap(); let db = Database::open(std::path::Path::new("../.swap-db/")).unwrap();