mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-09-17 19:34:48 -04:00
Add protocol state machine
This commit is contained in:
parent
ccb808bdf7
commit
acb243a0a2
8 changed files with 590 additions and 281 deletions
|
@ -21,6 +21,7 @@ pub fn init(level: LevelFilter) -> Result<()> {
|
|||
builder.init();
|
||||
}
|
||||
|
||||
tracing
|
||||
tracing::info!("Initialized tracing with level: {}", level);
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -27,5 +27,6 @@ pub mod monero;
|
|||
pub mod network;
|
||||
pub mod protocol;
|
||||
pub mod seed;
|
||||
mod xmr_first_protocol;
|
||||
|
||||
mod monero_ext;
|
||||
|
|
6
swap/src/xmr_first_protocol.rs
Normal file
6
swap/src/xmr_first_protocol.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
mod alice;
|
||||
mod bob;
|
||||
|
||||
pub trait Persist {
|
||||
fn persist(&self);
|
||||
}
|
133
swap/src/xmr_first_protocol/alice.rs
Normal file
133
swap/src/xmr_first_protocol/alice.rs
Normal file
|
@ -0,0 +1,133 @@
|
|||
use crate::xmr_first_protocol::Persist;
|
||||
use std::collections::VecDeque;
|
||||
use std::task::Poll;
|
||||
|
||||
pub struct StateMachine {
|
||||
state: State,
|
||||
actions: VecDeque<Action>,
|
||||
events: VecDeque<Event>,
|
||||
}
|
||||
|
||||
impl StateMachine {
|
||||
fn inject_event(&mut self, event: Event) {
|
||||
match self.state {
|
||||
State::WatchingForBtcLock => match event {
|
||||
Event::BtcLockSeenInMempool => {
|
||||
self.actions.push_back(Action::SignAndBroadcastBtcRedeem);
|
||||
self.actions.push_back(Action::WatchForXmrRedeem);
|
||||
self.state = State::WatchingForXmrRedeem;
|
||||
}
|
||||
Event::BtcLockTimeoutElapsed => {
|
||||
self.actions.push_back(Action::BroadcastXmrRefund);
|
||||
self.state = State::Aborted;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
State::WatchingForXmrRedeem => match event {
|
||||
Event::T2Elapsed => {
|
||||
self.actions.push_back(Action::BroadcastXmrRefund);
|
||||
self.actions.push_back(Action::SignAndBroadcastBtcPunish);
|
||||
self.state = State::Punished;
|
||||
}
|
||||
Event::XmrRedeemSeenInMempool => {
|
||||
self.actions.push_back(Action::SignAndBroadcastBtcPunish);
|
||||
self.state = State::Success;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
// State::WaitingForT2 => match event {
|
||||
// Event::T2Elapsed => {
|
||||
// self.actions.push_back(Action::SignAndBroadcastBtcPunish);
|
||||
// self.actions.push_back(Action::BroadcastXmrRefund);
|
||||
// self.state = State::Success;
|
||||
// }
|
||||
// _ => {}
|
||||
// },
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll(&mut self) -> Poll<Action> {
|
||||
if let Some(action) = self.actions.pop_front() {
|
||||
Poll::Ready(action)
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Persist for Event {
|
||||
fn persist(&self) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum State {
|
||||
WatchingForBtcLock,
|
||||
WatchingForXmrRedeem,
|
||||
// WaitingForT2,
|
||||
Punished,
|
||||
Success,
|
||||
Aborted,
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
BtcLockSeenInMempool,
|
||||
T2Elapsed,
|
||||
BtcLockTimeoutElapsed,
|
||||
XmrRedeemSeenInMempool,
|
||||
}
|
||||
|
||||
// These actions should not fail (are retried until successful) and should be
|
||||
// idempotent This allows us to greatly simplify the state machine
|
||||
pub enum Action {
|
||||
WatchForXmrRedeem,
|
||||
SignAndBroadcastBtcPunish,
|
||||
SignAndBroadcastBtcRedeem,
|
||||
BroadcastXmrRefund,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn happy_path() {
|
||||
let mut state_machine = StateMachine {
|
||||
state: State::WatchingForBtcLock,
|
||||
actions: Default::default(),
|
||||
events: Default::default(),
|
||||
};
|
||||
state_machine.inject_event(Event::BtcLockSeenInMempool);
|
||||
assert_eq!(state_machine.poll())
|
||||
state_machine.inject_event(Event::XmrRedeemSeenInMempool);
|
||||
assert_eq!(state_machine.state, State::Success);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn bob_fails_to_lock_btc() {
|
||||
let mut state_machine = StateMachine {
|
||||
state: State::WatchingForBtcLock,
|
||||
actions: Default::default(),
|
||||
events: Default::default(),
|
||||
};
|
||||
state_machine.events.push_back(Event::BtcLockTimeoutElapsed);
|
||||
state_machine.run();
|
||||
assert_eq!(state_machine.state, State::Aborted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bob_fails_to_redeem_xmr_before_t2() {
|
||||
let mut state_machine = StateMachine {
|
||||
state: State::WatchingForBtcLock,
|
||||
actions: Default::default(),
|
||||
events: Default::default(),
|
||||
};
|
||||
state_machine.events.push_back(Event::BtcLockSeenInMempool);
|
||||
state_machine.events.push_back(Event::T2Elapsed);
|
||||
state_machine.run();
|
||||
assert_eq!(state_machine.state, State::Punished);
|
||||
}
|
||||
}
|
127
swap/src/xmr_first_protocol/bob.rs
Normal file
127
swap/src/xmr_first_protocol/bob.rs
Normal file
|
@ -0,0 +1,127 @@
|
|||
use crate::monero::TransferProof;
|
||||
use crate::xmr_first_protocol::Persist;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
pub struct StateMachine {
|
||||
state: State,
|
||||
actions: VecDeque<Action>,
|
||||
events: VecDeque<Event>,
|
||||
}
|
||||
|
||||
impl StateMachine {
|
||||
fn next(&mut self, event: Event) {
|
||||
match self.state {
|
||||
State::WatchingForXmrLock => match event {
|
||||
Event::XmrConfirmed => {
|
||||
self.actions.push_back(Action::SignAndBroadcastBtcLock);
|
||||
self.state = State::WaitingForBtcRedeem;
|
||||
}
|
||||
Event::T1Elapsed => {
|
||||
self.state = State::Aborted;
|
||||
}
|
||||
Event::XmrRefundSeenInMempool => {
|
||||
self.state = State::Aborted;
|
||||
}
|
||||
_ => panic!("unhandled scenario"),
|
||||
},
|
||||
State::WaitingForBtcRedeem => match event {
|
||||
Event::BtcRedeemSeenInMempool => {
|
||||
self.actions.push_back(Action::BroadcastXmrRedeem);
|
||||
self.state = State::Success;
|
||||
}
|
||||
Event::T1Elapsed => {
|
||||
self.actions.push_back(Action::SignAndBroadcastBtcRefund);
|
||||
self.state = State::Refunded;
|
||||
}
|
||||
Event::XmrRefundSeenInMempool => {
|
||||
self.actions.push_back(Action::SignAndBroadcastBtcRefund);
|
||||
self.state = State::Refunded;
|
||||
}
|
||||
_ => panic!("unhandled scenario"),
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&mut self) {
|
||||
while let Some(event) = self.events.pop_front() {
|
||||
self.next(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Persist for Event {
|
||||
fn persist(&self) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum State {
|
||||
WatchingForXmrLock,
|
||||
WaitingForBtcRedeem,
|
||||
Success,
|
||||
Refunded,
|
||||
Aborted,
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
// todo: do we simply wait for confirmations are is mempool sufficient?
|
||||
XmrConfirmed,
|
||||
// This will contain the s_a allowing bob to build xmr_redeem
|
||||
BtcRedeemSeenInMempool,
|
||||
XmrRefundSeenInMempool,
|
||||
T1Elapsed,
|
||||
}
|
||||
|
||||
pub enum Action {
|
||||
SignAndBroadcastBtcLock,
|
||||
BroadcastXmrRedeem,
|
||||
SignAndBroadcastBtcRefund,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn happy_path() {
|
||||
let mut state_machine = StateMachine {
|
||||
state: State::WatchingForXmrLock,
|
||||
actions: Default::default(),
|
||||
events: Default::default(),
|
||||
};
|
||||
state_machine.events.push_back(Event::XmrConfirmed);
|
||||
state_machine
|
||||
.events
|
||||
.push_back(Event::BtcRedeemSeenInMempool);
|
||||
state_machine.run();
|
||||
assert_eq!(state_machine.state, State::Success);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alice_fails_to_redeem_btc_before_t1() {
|
||||
let mut state_machine = StateMachine {
|
||||
state: State::WatchingForXmrLock,
|
||||
actions: Default::default(),
|
||||
events: Default::default(),
|
||||
};
|
||||
state_machine.events.push_back(Event::XmrConfirmed);
|
||||
state_machine.events.push_back(Event::T1Elapsed);
|
||||
state_machine.run();
|
||||
assert_eq!(state_machine.state, State::Refunded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alice_tries_to_refund_xmr_after_redeeming_btc() {
|
||||
let mut state_machine = StateMachine {
|
||||
state: State::WatchingForXmrLock,
|
||||
actions: Default::default(),
|
||||
events: Default::default(),
|
||||
};
|
||||
state_machine.events.push_back(Event::XmrConfirmed);
|
||||
state_machine.events.push_back(Event::T1Elapsed);
|
||||
state_machine.run();
|
||||
assert_eq!(state_machine.state, State::Refunded);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue