use crate::types::{EventEnvelope, EventFilter, SubscriptionId, TenzroEvent, VmType};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use tokio::sync::broadcast;
use tracing::{debug, warn};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EventBusError {
Closed,
Lagged(u64),
}
impl std::fmt::Display for EventBusError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EventBusError::Closed => write!(f, "EventBus closed"),
EventBusError::Lagged(n) => write!(f, "EventBus consumer lagged by {} events", n),
}
}
}
impl std::error::Error for EventBusError {}
#[derive(Debug, Clone)]
pub struct EventBusConfig {
pub capacity: usize,
pub enable_persistence: bool,
}
impl Default for EventBusConfig {
fn default() -> Self {
Self {
capacity: 65_536,
enable_persistence: false,
}
}
}
impl EventBusConfig {
pub fn with_capacity(mut self, capacity: usize) -> Self {
self.capacity = capacity;
self
}
pub fn with_persistence(mut self, enable: bool) -> Self {
self.enable_persistence = enable;
self
}
}
#[derive(Debug)]
pub struct EventBusStats {
pub total_events_published: AtomicU64,
pub total_events_dropped: AtomicU64,
pub active_subscribers: AtomicU64,
pub current_sequence: AtomicU64,
}
impl EventBusStats {
fn new() -> Self {
Self {
total_events_published: AtomicU64::new(0),
total_events_dropped: AtomicU64::new(0),
active_subscribers: AtomicU64::new(0),
current_sequence: AtomicU64::new(0),
}
}
pub fn snapshot(&self) -> StatsSnapshot {
StatsSnapshot {
total_events_published: self.total_events_published.load(Ordering::Relaxed),
total_events_dropped: self.total_events_dropped.load(Ordering::Relaxed),
active_subscribers: self.active_subscribers.load(Ordering::Relaxed),
current_sequence: self.current_sequence.load(Ordering::Relaxed),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StatsSnapshot {
pub total_events_published: u64,
pub total_events_dropped: u64,
pub active_subscribers: u64,
pub current_sequence: u64,
}
pub struct EventBus {
config: EventBusConfig,
sender: broadcast::Sender<Arc<EventEnvelope>>,
sequence_counter: AtomicU64,
stats: Arc<EventBusStats>,
next_subscription_id: AtomicU64,
}
impl EventBus {
pub fn new(config: EventBusConfig) -> Self {
let (sender, _) = broadcast::channel(config.capacity);
Self {
config,
sender,
sequence_counter: AtomicU64::new(0),
stats: Arc::new(EventBusStats::new()),
next_subscription_id: AtomicU64::new(1),
}
}
pub fn config(&self) -> &EventBusConfig {
&self.config
}
pub fn capacity(&self) -> usize {
self.config.capacity
}
pub fn persistence_enabled(&self) -> bool {
self.config.enable_persistence
}
pub fn default_bus() -> Self {
Self::new(EventBusConfig::default())
}
pub fn publish(
&self,
event: TenzroEvent,
block_height: Option<u64>,
vm_type: Option<VmType>,
) -> u64 {
let seq = self.sequence_counter.fetch_add(1, Ordering::SeqCst);
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as i64;
let envelope = Arc::new(EventEnvelope {
sequence: seq,
timestamp,
block_height,
vm_type,
event,
});
let _ = self.sender.send(envelope);
self.stats
.total_events_published
.fetch_add(1, Ordering::Relaxed);
self.stats.current_sequence.store(seq, Ordering::Relaxed);
debug!(sequence = seq, "event published");
seq
}
pub fn subscribe(&self) -> EventSubscriber {
let receiver = self.sender.subscribe();
self.stats
.active_subscribers
.fetch_add(1, Ordering::Relaxed);
let id = SubscriptionId(
self.next_subscription_id.fetch_add(1, Ordering::Relaxed),
);
EventSubscriber {
id,
receiver,
stats: Arc::clone(&self.stats),
}
}
pub fn subscribe_filtered(&self, filter: EventFilter) -> FilteredEventSubscriber {
let inner = self.subscribe();
FilteredEventSubscriber { inner, filter }
}
pub fn stats(&self) -> &Arc<EventBusStats> {
&self.stats
}
pub fn current_sequence(&self) -> u64 {
self.sequence_counter.load(Ordering::SeqCst)
}
pub fn subscriber_count(&self) -> u64 {
self.stats.active_subscribers.load(Ordering::Relaxed)
}
}
pub struct EventSubscriber {
id: SubscriptionId,
receiver: broadcast::Receiver<Arc<EventEnvelope>>,
stats: Arc<EventBusStats>,
}
impl EventSubscriber {
pub fn id(&self) -> SubscriptionId {
self.id
}
pub async fn recv(&mut self) -> Result<Arc<EventEnvelope>, EventBusError> {
match self.receiver.recv().await {
Ok(envelope) => Ok(envelope),
Err(broadcast::error::RecvError::Lagged(n)) => {
warn!(
subscription = %self.id,
missed = n,
"subscriber lagged behind"
);
self.stats
.total_events_dropped
.fetch_add(n, Ordering::Relaxed);
Err(EventBusError::Lagged(n))
}
Err(broadcast::error::RecvError::Closed) => Err(EventBusError::Closed),
}
}
}
impl Drop for EventSubscriber {
fn drop(&mut self) {
self.stats
.active_subscribers
.fetch_sub(1, Ordering::Relaxed);
}
}
pub struct FilteredEventSubscriber {
inner: EventSubscriber,
filter: EventFilter,
}
impl FilteredEventSubscriber {
pub fn id(&self) -> SubscriptionId {
self.inner.id()
}
pub fn filter(&self) -> &EventFilter {
&self.filter
}
pub async fn recv(&mut self) -> Result<Arc<EventEnvelope>, EventBusError> {
loop {
match self.inner.recv().await {
Ok(envelope) => {
if self.filter.matches(&envelope) {
return Ok(envelope);
}
}
Err(e) => return Err(e),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{EventType, TenzroEvent, VmType};
fn make_block_event(height: u64) -> TenzroEvent {
TenzroEvent::NewBlock {
block_hash: [0xAA; 32],
parent_hash: [0xBB; 32],
height,
tx_count: 5,
proposer: [0x01; 20],
}
}
fn make_transfer_event(amount: u128) -> TenzroEvent {
TenzroEvent::Transfer {
from: [0x01; 20],
to: [0x02; 20],
amount,
token_id: "TNZO".into(),
tx_hash: [0xCC; 32],
}
}
#[tokio::test]
async fn test_publish_and_receive() {
let bus = EventBus::default_bus();
let mut sub = bus.subscribe();
let seq = bus.publish(make_block_event(1), Some(1), Some(VmType::Native));
assert_eq!(seq, 0);
let env = sub.recv().await.unwrap();
assert_eq!(env.sequence, 0);
assert_eq!(env.block_height, Some(1));
assert_eq!(env.event_type(), EventType::NewBlock);
}
#[tokio::test]
async fn test_multiple_subscribers() {
let bus = EventBus::default_bus();
let mut sub1 = bus.subscribe();
let mut sub2 = bus.subscribe();
assert_eq!(bus.subscriber_count(), 2);
bus.publish(make_transfer_event(100), Some(5), None);
let e1 = sub1.recv().await.unwrap();
let e2 = sub2.recv().await.unwrap();
assert_eq!(e1.sequence, e2.sequence);
assert_eq!(e1.sequence, 0);
}
#[tokio::test]
async fn test_filtered_subscriber() {
let bus = EventBus::default_bus();
let filter = EventFilter {
event_types: vec![EventType::Transfer],
..Default::default()
};
let mut filtered = bus.subscribe_filtered(filter);
bus.publish(make_block_event(1), Some(1), Some(VmType::Native));
bus.publish(make_transfer_event(999), Some(2), Some(VmType::Native));
let env = filtered.recv().await.unwrap();
assert_eq!(env.event_type(), EventType::Transfer);
assert_eq!(env.sequence, 1); }
#[tokio::test]
async fn test_lagged_consumer() {
let config = EventBusConfig::default().with_capacity(4);
let bus = EventBus::new(config);
let mut sub = bus.subscribe();
for i in 0..10 {
bus.publish(make_block_event(i), Some(i), None);
}
let result = sub.recv().await;
match result {
Err(EventBusError::Lagged(n)) => {
assert!(n > 0, "should have lagged by at least 1 event");
}
Ok(env) => {
assert!(env.sequence < 10);
}
Err(EventBusError::Closed) => panic!("unexpected close"),
}
let snap = bus.stats().snapshot();
assert!(snap.total_events_published == 10);
}
#[tokio::test]
async fn test_sequence_monotonicity() {
let bus = EventBus::default_bus();
let mut sub = bus.subscribe();
for i in 0u64..20 {
let seq = bus.publish(make_block_event(i), Some(i), None);
assert_eq!(seq, i);
}
let mut prev_seq = None;
for _ in 0..20 {
let env = sub.recv().await.unwrap();
if let Some(prev) = prev_seq {
assert_eq!(env.sequence, prev + 1, "sequence must be gap-free");
}
prev_seq = Some(env.sequence);
}
}
#[tokio::test]
async fn test_stats_tracking() {
let bus = EventBus::default_bus();
let _sub = bus.subscribe();
assert_eq!(bus.stats().snapshot().active_subscribers, 1);
assert_eq!(bus.stats().snapshot().total_events_published, 0);
bus.publish(make_block_event(1), Some(1), None);
bus.publish(make_transfer_event(50), None, None);
let snap = bus.stats().snapshot();
assert_eq!(snap.total_events_published, 2);
assert_eq!(snap.current_sequence, 1); }
#[tokio::test]
async fn test_subscriber_count_on_drop() {
let bus = EventBus::default_bus();
assert_eq!(bus.subscriber_count(), 0);
let sub1 = bus.subscribe();
assert_eq!(bus.subscriber_count(), 1);
{
let _sub2 = bus.subscribe();
assert_eq!(bus.subscriber_count(), 2);
}
assert_eq!(bus.subscriber_count(), 1);
drop(sub1);
assert_eq!(bus.subscriber_count(), 0);
}
#[tokio::test]
async fn test_subscribe_after_publish_gets_future_only() {
let bus = EventBus::default_bus();
bus.publish(make_block_event(1), Some(1), None);
bus.publish(make_block_event(2), Some(2), None);
let mut sub = bus.subscribe();
bus.publish(make_block_event(3), Some(3), None);
let env = sub.recv().await.unwrap();
assert_eq!(env.sequence, 2); if let TenzroEvent::NewBlock { height, .. } = &env.event {
assert_eq!(*height, 3);
} else {
panic!("expected NewBlock");
}
}
#[tokio::test]
async fn test_filtered_subscriber_with_vm_type() {
let bus = EventBus::default_bus();
let filter = EventFilter {
vm_types: vec![VmType::Evm],
..Default::default()
};
let mut filtered = bus.subscribe_filtered(filter);
bus.publish(make_block_event(1), Some(1), Some(VmType::Native));
bus.publish(make_transfer_event(100), Some(2), Some(VmType::Evm));
let env = filtered.recv().await.unwrap();
assert_eq!(env.vm_type, Some(VmType::Evm));
assert_eq!(env.event_type(), EventType::Transfer);
}
#[tokio::test]
async fn test_current_sequence() {
let bus = EventBus::default_bus();
assert_eq!(bus.current_sequence(), 0);
bus.publish(make_block_event(1), None, None);
assert_eq!(bus.current_sequence(), 1);
bus.publish(make_block_event(2), None, None);
assert_eq!(bus.current_sequence(), 2);
}
#[tokio::test]
async fn test_closed_error_when_bus_dropped() {
let bus = EventBus::default_bus();
let mut sub = bus.subscribe();
bus.publish(make_block_event(1), None, None);
drop(bus);
let _ok = sub.recv().await.unwrap();
let result = sub.recv().await;
assert_eq!(result, Err(EventBusError::Closed));
}
}