diff --git a/core/src/states/kickoff.rs b/core/src/states/kickoff.rs index c701f472..d13d1607 100644 --- a/core/src/states/kickoff.rs +++ b/core/src/states/kickoff.rs @@ -20,7 +20,7 @@ use super::{ Owner, }; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] pub enum KickoffEvent { Challenged, WatchtowerChallengeSent { @@ -72,12 +72,12 @@ impl BlockMatcher for KickoffStateMachine { self.matchers .iter() .filter_map(|(matcher, kickoff_event)| { - if matcher.matches(block) { - Some(kickoff_event.clone()) - } else { - None - } + matcher.matches(block).map(|ord| (ord, kickoff_event)) }) + .min() + .map(|(_, kickoff_event)| kickoff_event) + .into_iter() + .cloned() .collect() } } @@ -126,6 +126,12 @@ impl KickoffStateMachine { ) { tracing::debug!(?self.kickoff_id, "Dispatching event {:?}", evt); self.dirty = true; + + // Remove the matcher corresponding to the event. + if let Some((matcher, _)) = self.matchers.iter().find(|(_, ev)| ev == &evt) { + let matcher = matcher.clone(); + self.matchers.remove(&matcher); + } } async fn check_time_to_send_asserts(&mut self, context: &mut StateContext) { diff --git a/core/src/states/matcher.rs b/core/src/states/matcher.rs index 3b01e845..c6b684dd 100644 --- a/core/src/states/matcher.rs +++ b/core/src/states/matcher.rs @@ -1,14 +1,16 @@ +use std::cmp::Ordering; + use super::block_cache::BlockCache; use bitcoin::{OutPoint, Txid}; pub(crate) trait BlockMatcher { type StateEvent; - fn match_block(&self, block: &super::block_cache::BlockCache) -> Vec; + fn match_block(& self, block: &super::block_cache::BlockCache) -> Vec; } // Matcher for state machines to define what they're interested in -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] pub enum Matcher { SentTx(Txid), SpentUtxo(OutPoint), @@ -18,16 +20,50 @@ pub enum Matcher { BlockHeight(u32), } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum MatcherOrd { + TxIndex(usize), + BlockHeight, +} + +impl PartialOrd for MatcherOrd { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (MatcherOrd::TxIndex(a), MatcherOrd::TxIndex(b)) => a.partial_cmp(b), + (MatcherOrd::BlockHeight, MatcherOrd::BlockHeight) => Some(Ordering::Equal), + (MatcherOrd::BlockHeight, _) => Some(Ordering::Less), + (_, MatcherOrd::BlockHeight) => Some(Ordering::Greater), + } + } +} + +impl Ord for MatcherOrd { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other) + .expect("partial_cmp always returns Some") + } +} + impl Matcher { - // TODO: sort by matched tx index - pub fn matches(&self, block: &BlockCache) -> bool { + pub fn matches(&self, block: &BlockCache) -> Option { match self { - Matcher::SentTx(txid) => block.contains_txid(txid), - Matcher::SpentUtxo(outpoint) => block.is_utxo_spent(outpoint), - Matcher::BlockHeight(height) => *height <= block.block_height, - Matcher::SpentUtxoButNotTimeout(outpoint, txid) => { - block.is_utxo_spent(outpoint) && !block.contains_txid(txid) + Matcher::SentTx(txid) if block.contains_txid(txid) => Some(MatcherOrd::TxIndex( + block.txids.get(txid).expect("txid is in cache").clone(), + )), + Matcher::SpentUtxo(outpoint) if (block.is_utxo_spent(outpoint)) => Some( + MatcherOrd::TxIndex(*block.spent_utxos.get(outpoint).expect("utxo is in cache")), + ), + Matcher::BlockHeight(height) if *height <= block.block_height => { + Some(MatcherOrd::BlockHeight) + } + Matcher::SpentUtxoButNotTimeout(outpoint, txid) + if block.is_utxo_spent(outpoint) && !block.contains_txid(txid) => + { + Some(MatcherOrd::TxIndex( + *block.spent_utxos.get(outpoint).expect("utxo is in cache"), + )) } + _ => None, } } } diff --git a/core/src/states/round.rs b/core/src/states/round.rs index 405c8456..4dbb79c5 100644 --- a/core/src/states/round.rs +++ b/core/src/states/round.rs @@ -1,6 +1,9 @@ use eyre::Context; use statig::prelude::*; -use std::collections::{HashMap, HashSet}; +use std::{ + collections::{HashMap, HashSet}, + ops::DerefMut, +}; use crate::{ builder::{ @@ -17,7 +20,7 @@ use super::{ Owner, }; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum RoundEvent { KickoffUtxoUsed { kickoff_idx: usize }, ReadyToReimburseSent { round_idx: u32 }, @@ -40,12 +43,12 @@ impl BlockMatcher for RoundStateMachine { self.matchers .iter() .filter_map(|(matcher, round_event)| { - if matcher.matches(block) { - Some(round_event.clone()) - } else { - None - } + matcher.matches(block).map(|ord| (ord, round_event)) }) + .min() + .map(|(_, round_event)| round_event) + .into_iter() + .cloned() .collect() } } @@ -100,6 +103,12 @@ impl RoundStateMachine { ) { tracing::debug!(?self.operator_data, ?self.operator_idx, "Dispatching event {:?}", evt); self.dirty = true; + + // Remove the matcher corresponding to the event. + if let Some((matcher, _)) = self.matchers.iter().find(|(_, ev)| ev == &evt) { + let matcher = matcher.clone(); + self.matchers.remove(&matcher); + } } #[state(entry_action = "on_initial_collateral_entry")]