Add protocol state machine

This commit is contained in:
rishflab 2021-05-05 13:31:07 +10:00
parent ccb808bdf7
commit acb243a0a2
8 changed files with 590 additions and 281 deletions

View file

@ -21,6 +21,7 @@ pub fn init(level: LevelFilter) -> Result<()> {
builder.init();
}
tracing
tracing::info!("Initialized tracing with level: {}", level);
Ok(())

View file

@ -27,5 +27,6 @@ pub mod monero;
pub mod network;
pub mod protocol;
pub mod seed;
mod xmr_first_protocol;
mod monero_ext;

View file

@ -0,0 +1,6 @@
mod alice;
mod bob;
pub trait Persist {
fn persist(&self);
}

View 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);
}
}

View 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);
}
}