|
| 1 | +use std::collections::{BTreeMap, BTreeSet, VecDeque}; |
| 2 | + |
| 3 | +use sim_core::{ |
| 4 | + clock::Timestamp, |
| 5 | + config::SimConfiguration, |
| 6 | + events::{BlockRef, Event}, |
| 7 | + model::{TransactionId, TransactionLostReason}, |
| 8 | +}; |
| 9 | +use tokio::sync::mpsc; |
| 10 | + |
| 11 | +use super::{EndorserBlockId, InputBlockId}; |
| 12 | + |
| 13 | +/// Emits additional events when it is no longer possible for a transaction to reach the chain. |
| 14 | +pub struct LivenessMonitor { |
| 15 | + events_source: mpsc::UnboundedReceiver<(Event, Timestamp)>, |
| 16 | + queue: VecDeque<(Event, Timestamp)>, |
| 17 | + txs: BTreeMap<TransactionId, MonitoredTX>, |
| 18 | + ibs: BTreeMap<InputBlockId, MonitoredIB>, |
| 19 | + ebs: BTreeMap<EndorserBlockId, MonitoredEB>, |
| 20 | + stage_length: u64, |
| 21 | + vote_threshold: u64, |
| 22 | +} |
| 23 | + |
| 24 | +impl LivenessMonitor { |
| 25 | + pub fn new( |
| 26 | + config: &SimConfiguration, |
| 27 | + events_source: mpsc::UnboundedReceiver<(Event, Timestamp)>, |
| 28 | + ) -> Self { |
| 29 | + Self { |
| 30 | + events_source, |
| 31 | + queue: VecDeque::new(), |
| 32 | + txs: BTreeMap::new(), |
| 33 | + ibs: BTreeMap::new(), |
| 34 | + ebs: BTreeMap::new(), |
| 35 | + stage_length: config.stage_length, |
| 36 | + vote_threshold: config.vote_threshold, |
| 37 | + } |
| 38 | + } |
| 39 | + |
| 40 | + pub async fn recv(&mut self) -> Option<(Event, Timestamp)> { |
| 41 | + if let Some(next) = self.queue.pop_front() { |
| 42 | + return Some(next); |
| 43 | + } |
| 44 | + let (event, time) = self.events_source.recv().await?; |
| 45 | + match &event { |
| 46 | + Event::TXGenerated { id, .. } => { |
| 47 | + self.txs.insert(*id, MonitoredTX::new()); |
| 48 | + } |
| 49 | + Event::IBGenerated { |
| 50 | + id, transactions, .. |
| 51 | + } => { |
| 52 | + for tx_id in transactions { |
| 53 | + if let Some(tx) = self.txs.get_mut(tx_id) { |
| 54 | + tx.ibs.insert(id.clone()); |
| 55 | + tx.last_ib_pipeline = std::cmp::max(tx.last_ib_pipeline, Some(id.pipeline)); |
| 56 | + } |
| 57 | + } |
| 58 | + self.ibs |
| 59 | + .insert(id.clone(), MonitoredIB::new(transactions.clone())); |
| 60 | + } |
| 61 | + Event::EBGenerated { |
| 62 | + id, |
| 63 | + transactions, |
| 64 | + input_blocks, |
| 65 | + endorser_blocks, |
| 66 | + .. |
| 67 | + } => { |
| 68 | + for BlockRef { id: tx_id } in transactions { |
| 69 | + if let Some(tx) = self.txs.get_mut(tx_id) { |
| 70 | + tx.ebs.insert(id.clone()); |
| 71 | + tx.last_eb_pipeline = std::cmp::max(tx.last_eb_pipeline, Some(id.pipeline)); |
| 72 | + } |
| 73 | + } |
| 74 | + for BlockRef { id: ib_id } in input_blocks { |
| 75 | + if let Some(ib) = self.ibs.get(ib_id) { |
| 76 | + for tx_id in &ib.txs { |
| 77 | + if let Some(tx) = self.txs.get_mut(tx_id) { |
| 78 | + tx.ebs.insert(id.clone()); |
| 79 | + tx.last_eb_pipeline = |
| 80 | + std::cmp::max(tx.last_eb_pipeline, Some(id.pipeline)); |
| 81 | + } |
| 82 | + } |
| 83 | + } |
| 84 | + } |
| 85 | + self.ebs.insert( |
| 86 | + id.clone(), |
| 87 | + MonitoredEB::new( |
| 88 | + transactions.iter().map(|tx| tx.id).collect(), |
| 89 | + input_blocks.iter().map(|ib| ib.id.clone()).collect(), |
| 90 | + endorser_blocks.iter().map(|eb| eb.id.clone()).collect(), |
| 91 | + ), |
| 92 | + ); |
| 93 | + } |
| 94 | + Event::VTBundleGenerated { votes, .. } => { |
| 95 | + for (eb_id, count) in &votes.0 { |
| 96 | + let eb = self.ebs.get_mut(eb_id).unwrap(); |
| 97 | + eb.votes += *count as u64; |
| 98 | + if eb.votes >= self.vote_threshold { |
| 99 | + for ib_id in &eb.ibs { |
| 100 | + if let Some(ib) = self.ibs.get_mut(ib_id) { |
| 101 | + for tx_id in &ib.txs { |
| 102 | + if let Some(tx) = self.txs.get_mut(tx_id) { |
| 103 | + tx.certified_ebs.insert(eb_id.clone()); |
| 104 | + } |
| 105 | + } |
| 106 | + } |
| 107 | + } |
| 108 | + for tx_id in &eb.txs { |
| 109 | + if let Some(tx) = self.txs.get_mut(tx_id) { |
| 110 | + tx.certified_ebs.insert(eb_id.clone()); |
| 111 | + } |
| 112 | + } |
| 113 | + } |
| 114 | + } |
| 115 | + } |
| 116 | + Event::RBGenerated { |
| 117 | + endorsement, |
| 118 | + transactions, |
| 119 | + .. |
| 120 | + } => { |
| 121 | + for tx in transactions { |
| 122 | + self.txs.remove(tx); |
| 123 | + } |
| 124 | + if let Some(endorsement) = endorsement { |
| 125 | + let mut eb_queue = vec![endorsement.eb.id.clone()]; |
| 126 | + while let Some(eb_id) = eb_queue.pop() { |
| 127 | + let Some(eb) = self.ebs.remove(&eb_id) else { |
| 128 | + continue; |
| 129 | + }; |
| 130 | + self.txs.retain(|_, tx| !tx.ebs.contains(&eb_id)); |
| 131 | + for ib_id in &eb.ibs { |
| 132 | + self.ibs.remove(ib_id); |
| 133 | + } |
| 134 | + eb_queue.extend(eb.ebs); |
| 135 | + } |
| 136 | + } |
| 137 | + } |
| 138 | + Event::GlobalSlot { slot } => { |
| 139 | + if slot % self.stage_length == 0 { |
| 140 | + let pipeline = slot / self.stage_length; |
| 141 | + self.handle_new_pipeline(pipeline, time); |
| 142 | + } |
| 143 | + } |
| 144 | + _ => {} |
| 145 | + } |
| 146 | + Some((event, time)) |
| 147 | + } |
| 148 | + |
| 149 | + fn handle_new_pipeline(&mut self, pipeline: u64, time: Timestamp) { |
| 150 | + self.txs.retain(|id, tx| { |
| 151 | + if tx.ebs.is_empty() |
| 152 | + && tx |
| 153 | + .last_ib_pipeline |
| 154 | + .is_some_and(|ib_pipeline| ib_pipeline + 4 < pipeline) |
| 155 | + { |
| 156 | + // this transaction was only in IBs which never reached any EBs. |
| 157 | + self.queue.push_back(( |
| 158 | + Event::TXLost { |
| 159 | + id: *id, |
| 160 | + reason: TransactionLostReason::IBExpired, |
| 161 | + }, |
| 162 | + time, |
| 163 | + )); |
| 164 | + return false; |
| 165 | + } |
| 166 | + |
| 167 | + if tx.certified_ebs.is_empty() |
| 168 | + && tx |
| 169 | + .last_eb_pipeline |
| 170 | + .is_some_and(|eb_pipeline| eb_pipeline + 1 < pipeline) |
| 171 | + { |
| 172 | + // this transaction was only in EBs which were never certified |
| 173 | + self.queue.push_back(( |
| 174 | + Event::TXLost { |
| 175 | + id: *id, |
| 176 | + reason: TransactionLostReason::EBExpired, |
| 177 | + }, |
| 178 | + time, |
| 179 | + )); |
| 180 | + return false; |
| 181 | + } |
| 182 | + |
| 183 | + true |
| 184 | + }); |
| 185 | + } |
| 186 | +} |
| 187 | + |
| 188 | +struct MonitoredTX { |
| 189 | + last_ib_pipeline: Option<u64>, |
| 190 | + ibs: BTreeSet<InputBlockId>, |
| 191 | + last_eb_pipeline: Option<u64>, |
| 192 | + ebs: BTreeSet<EndorserBlockId>, |
| 193 | + certified_ebs: BTreeSet<EndorserBlockId>, |
| 194 | +} |
| 195 | +impl MonitoredTX { |
| 196 | + fn new() -> Self { |
| 197 | + Self { |
| 198 | + last_ib_pipeline: None, |
| 199 | + ibs: BTreeSet::new(), |
| 200 | + last_eb_pipeline: None, |
| 201 | + ebs: BTreeSet::new(), |
| 202 | + certified_ebs: BTreeSet::new(), |
| 203 | + } |
| 204 | + } |
| 205 | +} |
| 206 | + |
| 207 | +struct MonitoredIB { |
| 208 | + txs: Vec<TransactionId>, |
| 209 | +} |
| 210 | +impl MonitoredIB { |
| 211 | + fn new(txs: Vec<TransactionId>) -> Self { |
| 212 | + Self { txs } |
| 213 | + } |
| 214 | +} |
| 215 | + |
| 216 | +struct MonitoredEB { |
| 217 | + txs: Vec<TransactionId>, |
| 218 | + ibs: Vec<InputBlockId>, |
| 219 | + ebs: Vec<EndorserBlockId>, |
| 220 | + votes: u64, |
| 221 | +} |
| 222 | +impl MonitoredEB { |
| 223 | + fn new(txs: Vec<TransactionId>, ibs: Vec<InputBlockId>, ebs: Vec<EndorserBlockId>) -> Self { |
| 224 | + Self { |
| 225 | + txs, |
| 226 | + ibs, |
| 227 | + ebs, |
| 228 | + votes: 0, |
| 229 | + } |
| 230 | + } |
| 231 | +} |
0 commit comments