use dashmap::DashMap;
use libp2p::PeerId;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tenzro_types::tee::TeeVendor;
pub const DEFAULT_FRESHNESS: Duration = Duration::from_secs(60);
pub const MAX_TIP_LEAD: u64 = 1024;
#[derive(Debug, Clone, Copy)]
pub struct PeerStatus {
pub height: u64,
pub chain_id: u64,
pub last_seen: Instant,
pub tee_capable: bool,
pub tee_vendor: Option<TeeVendor>,
}
pub struct PeerStatusTracker {
statuses: DashMap<PeerId, PeerStatus>,
freshness: Duration,
chain_id: u64,
}
impl PeerStatusTracker {
pub fn new(chain_id: u64) -> Arc<Self> {
Arc::new(Self {
statuses: DashMap::new(),
freshness: DEFAULT_FRESHNESS,
chain_id,
})
}
pub fn with_freshness(chain_id: u64, freshness: Duration) -> Arc<Self> {
Arc::new(Self {
statuses: DashMap::new(),
freshness,
chain_id,
})
}
pub fn record(
&self,
peer_id: PeerId,
height: u64,
chain_id: u64,
tee_capable: bool,
tee_vendor: Option<TeeVendor>,
) {
if chain_id != self.chain_id {
tracing::debug!(
peer = %peer_id,
got = chain_id,
expected = self.chain_id,
"Dropping StatusMessage from peer on different chain"
);
return;
}
self.statuses.insert(
peer_id,
PeerStatus {
height,
chain_id,
last_seen: Instant::now(),
tee_capable,
tee_vendor,
},
);
}
pub fn find_tee_peers(&self, vendor: Option<TeeVendor>) -> Vec<(PeerId, PeerStatus)> {
let now = Instant::now();
self.statuses
.iter()
.filter(|entry| {
let s = entry.value();
s.tee_capable
&& vendor.map_or(true, |v| s.tee_vendor == Some(v))
&& now
.checked_duration_since(s.last_seen)
.map(|d| d <= self.freshness)
.unwrap_or(true)
})
.map(|entry| (*entry.key(), *entry.value()))
.collect()
}
pub fn network_tip(&self) -> Option<u64> {
let now = Instant::now();
self.statuses
.iter()
.filter(|entry| {
now.checked_duration_since(entry.last_seen)
.map(|d| d <= self.freshness)
.unwrap_or(true)
})
.map(|entry| entry.height)
.max()
}
pub fn network_tip_capped(&self, local_tip: u64) -> Option<u64> {
let now = Instant::now();
let mut heights: Vec<u64> = self
.statuses
.iter()
.filter(|entry| {
now.checked_duration_since(entry.last_seen)
.map(|d| d <= self.freshness)
.unwrap_or(true)
})
.map(|entry| entry.height)
.collect();
if heights.is_empty() {
return None;
}
heights.sort_unstable();
let median = heights[heights.len() / 2];
let cap = local_tip.saturating_add(MAX_TIP_LEAD);
Some(median.min(cap))
}
pub fn fresh_peer_count(&self) -> usize {
let now = Instant::now();
self.statuses
.iter()
.filter(|entry| {
now.checked_duration_since(entry.last_seen)
.map(|d| d <= self.freshness)
.unwrap_or(true)
})
.count()
}
pub fn prune_stale(&self) {
let now = Instant::now();
let stale: Vec<PeerId> = self
.statuses
.iter()
.filter(|entry| {
now.checked_duration_since(entry.last_seen)
.map(|d| d > self.freshness)
.unwrap_or(false)
})
.map(|entry| *entry.key())
.collect();
for peer_id in stale {
self.statuses.remove(&peer_id);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_tracker_has_no_tip() {
let tracker = PeerStatusTracker::new(1337);
assert_eq!(tracker.network_tip(), None);
assert_eq!(tracker.fresh_peer_count(), 0);
}
#[test]
fn records_and_returns_max_height() {
let tracker = PeerStatusTracker::new(1337);
let p1 = PeerId::random();
let p2 = PeerId::random();
let p3 = PeerId::random();
tracker.record(p1, 100, 1337, false, None);
tracker.record(p2, 250, 1337, false, None);
tracker.record(p3, 175, 1337, false, None);
assert_eq!(tracker.network_tip(), Some(250));
assert_eq!(tracker.fresh_peer_count(), 3);
}
#[test]
fn drops_status_from_other_chain() {
let tracker = PeerStatusTracker::new(1337);
let p1 = PeerId::random();
tracker.record(p1, 9999, 9999, false, None);
assert_eq!(tracker.network_tip(), None);
}
#[test]
fn updates_overwrite_per_peer() {
let tracker = PeerStatusTracker::new(1337);
let p1 = PeerId::random();
tracker.record(p1, 100, 1337, false, None);
tracker.record(p1, 200, 1337, false, None);
tracker.record(p1, 150, 1337, false, None);
assert_eq!(tracker.network_tip(), Some(150));
assert_eq!(tracker.fresh_peer_count(), 1);
}
#[test]
fn stale_entries_are_ignored_and_pruned() {
let tracker = PeerStatusTracker::with_freshness(1337, Duration::from_millis(50));
let p1 = PeerId::random();
let p2 = PeerId::random();
tracker.record(p1, 100, 1337, false, None);
std::thread::sleep(Duration::from_millis(80));
tracker.record(p2, 200, 1337, false, None);
assert_eq!(tracker.network_tip(), Some(200));
assert_eq!(tracker.fresh_peer_count(), 1);
tracker.prune_stale();
assert_eq!(tracker.network_tip(), Some(200));
}
#[test]
fn finds_tee_capable_peers() {
let tracker = PeerStatusTracker::new(1337);
let p_sev = PeerId::random();
let p_tdx = PeerId::random();
let p_none = PeerId::random();
tracker.record(p_sev, 100, 1337, true, Some(TeeVendor::AmdSevSnp));
tracker.record(p_tdx, 100, 1337, true, Some(TeeVendor::IntelTdx));
tracker.record(p_none, 100, 1337, false, None);
let any = tracker.find_tee_peers(None);
assert_eq!(any.len(), 2);
assert!(any.iter().all(|(_, s)| s.tee_capable));
let sev = tracker.find_tee_peers(Some(TeeVendor::AmdSevSnp));
assert_eq!(sev.len(), 1);
assert_eq!(sev[0].0, p_sev);
let nitro = tracker.find_tee_peers(Some(TeeVendor::AwsNitro));
assert_eq!(nitro.len(), 0);
}
#[test]
fn stale_tee_peers_excluded_from_find() {
let tracker = PeerStatusTracker::with_freshness(1337, Duration::from_millis(50));
let p_sev = PeerId::random();
tracker.record(p_sev, 100, 1337, true, Some(TeeVendor::AmdSevSnp));
std::thread::sleep(Duration::from_millis(80));
assert_eq!(tracker.find_tee_peers(None).len(), 0);
}
#[test]
fn network_tip_capped_returns_median_of_fresh_peers() {
let tracker = PeerStatusTracker::new(1337);
for (i, h) in [100u64, 101, 102, 103, 104].iter().enumerate() {
tracker.record(
PeerId::random(),
*h,
1337,
false,
None,
);
let _ = i;
}
assert_eq!(tracker.network_tip_capped(50), Some(102));
}
#[test]
fn network_tip_capped_tolerates_one_malicious_high_height() {
let tracker = PeerStatusTracker::new(1337);
for h in [100u64, 101, 102] {
tracker.record(PeerId::random(), h, 1337, false, None);
}
tracker.record(PeerId::random(), u64::MAX, 1337, false, None);
assert_eq!(tracker.network_tip_capped(50), Some(102));
assert_eq!(tracker.network_tip(), Some(u64::MAX));
}
#[test]
fn network_tip_capped_caps_at_local_tip_plus_lead() {
let tracker = PeerStatusTracker::new(1337);
for _ in 0..5 {
tracker.record(PeerId::random(), u64::MAX, 1337, false, None);
}
let local_tip = 1000u64;
assert_eq!(
tracker.network_tip_capped(local_tip),
Some(local_tip + MAX_TIP_LEAD)
);
}
#[test]
fn network_tip_capped_returns_none_when_no_fresh_peers() {
let tracker = PeerStatusTracker::with_freshness(1337, Duration::from_millis(50));
tracker.record(PeerId::random(), 1000, 1337, false, None);
std::thread::sleep(Duration::from_millis(80));
assert_eq!(tracker.network_tip_capped(0), None);
}
}