PR: #35 (feat/10-chainlink-pricecache)
Commit: 5c36873
File: crates/charon-scanner/src/oracle.rs, unix_now() and is_fresh()
Problem:
fn unix_now() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
}
If SystemTime::now() predates UNIX_EPOCH (clock rollback, container clock skew), duration_since() returns Err and function returns 0.
is_fresh() evaluates cached.updated_at + max_age.as_secs() >= now → some_recent_ts + 600 >= 0 → always true.
Every cached price — expired, stale, uninitialized — reported as fresh. Staleness gate completely bypassed with no log or error.
Same 0-return affects refresh(): staleness bail! becomes updated_at + max_age < 0 (impossible for u64, always false) → every feed including stale ones inserted and served.
Separately, updatedAt = 0 from uninitialized aggregator not explicitly detected. Error message says "stale" (age = now - 0) rather than "uninitialized feed", harder to debug.
Risk: Containerized environments (Hetzner CX22 Docker, PR #55) experience clock drift or resets during upgrades. Any such event silently serves stale prices to liquidation path.
Fix:
fn unix_now() -> Result<u64> {
Ok(SystemTime::now()
.duration_since(UNIX_EPOCH)
.context("system clock is before UNIX_EPOCH")?
.as_secs())
}
is_fresh(): return false on clock Err. refresh(): bail! on clock Err. Separate guard:
if updated_at == 0 {
anyhow::bail!("feed '{symbol}': updatedAt=0, feed is uninitialized");
}
Keeps clock-failure and uninitialized-feed failure modes distinct in logs.
PR: #35 (feat/10-chainlink-pricecache)
Commit: 5c36873
File: crates/charon-scanner/src/oracle.rs,
unix_now()andis_fresh()Problem:
If
SystemTime::now()predatesUNIX_EPOCH(clock rollback, container clock skew),duration_since()returns Err and function returns 0.is_fresh()evaluatescached.updated_at + max_age.as_secs() >= now→some_recent_ts + 600 >= 0→ always true.Every cached price — expired, stale, uninitialized — reported as fresh. Staleness gate completely bypassed with no log or error.
Same 0-return affects
refresh(): stalenessbail!becomesupdated_at + max_age < 0(impossible for u64, always false) → every feed including stale ones inserted and served.Separately,
updatedAt = 0from uninitialized aggregator not explicitly detected. Error message says "stale" (age = now - 0) rather than "uninitialized feed", harder to debug.Risk: Containerized environments (Hetzner CX22 Docker, PR #55) experience clock drift or resets during upgrades. Any such event silently serves stale prices to liquidation path.
Fix:
is_fresh(): return false on clock Err.refresh(): bail! on clock Err. Separate guard:Keeps clock-failure and uninitialized-feed failure modes distinct in logs.