Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ tracing-subscriber = { version = "0.3.22", features = ["json"] }
url = "2.5.0"
vergen = "8"
winner-selection = { path = "crates/winner-selection" }
rand_chacha = { version = "0.9.0" }

[workspace.lints]
clippy.cast_possible_wrap = "deny"
1 change: 1 addition & 0 deletions crates/price-estimation/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ mockall = { workspace = true }
testlib = { workspace = true }
token-info = { workspace = true, features = ["test-util"] }
toml = { workspace = true }
rand_chacha = { workspace = true }

[features]
test-util = ["dep:mockall"]
Expand Down
72 changes: 66 additions & 6 deletions crates/price-estimation/src/native_price_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,13 +193,21 @@ impl Cache {
self.0.max_age
}

/// Returns a randomized `updated_at` timestamp that is 50-90% of max_age
/// in the past, to avoid spikes of expired prices all being fetched at
/// once.
/// Returns a timestamp that is a `percentage` of `max_age`
/// in the past. Panics if `percentage` > 100. Clamps to `now` if an
/// underflow occurs.
fn updated_at_percentage(max_age: Duration, now: Instant, percentage: u32) -> Instant {
assert!(percentage <= 100, "percentage > 100");
let age = max_age.saturating_mul(percentage) / 100;
Comment thread
metalurgical marked this conversation as resolved.
now.checked_sub(age).unwrap_or(now)
}

/// Returns a randomized `updated_at_percentage` timestamp that is 50–90% of
/// `max_age` in the past, to avoid spikes of expired prices all being
/// fetched at once.
fn random_updated_at(max_age: Duration, now: Instant, rng: &mut impl Rng) -> Instant {
let percent_expired = rng.random_range(50..=90);
let age = max_age.as_secs() * percent_expired / 100;
now - Duration::from_secs(age)
let percent_expired: u32 = rng.random_range(50..=90);
Self::updated_at_percentage(max_age, now, percent_expired)
Comment thread
jmg-duarte marked this conversation as resolved.
}

fn len(&self) -> usize {
Expand Down Expand Up @@ -544,6 +552,7 @@ mod tests {
anyhow::anyhow,
futures::FutureExt,
num::ToPrimitive,
rand_chacha::{ChaCha20Rng, rand_core::SeedableRng},
};

fn token(u: u64) -> Address {
Expand Down Expand Up @@ -1143,4 +1152,55 @@ mod tests {
anyhow!("protocol")
))));
}

#[test]
fn random_updated_at_underflow_check() {
let now = Instant::now();
let max_age = Duration::MAX;
let mut rng = ChaCha20Rng::from_seed(Default::default());
Comment thread
jmg-duarte marked this conversation as resolved.
Outdated

let updated_at = Cache::random_updated_at(max_age, now, &mut rng);
assert_eq!(updated_at, now);
}

#[test]
#[should_panic(expected = "percentage > 100")]
fn updated_at_percent_panic() {
let now = Instant::now();
let max_age = Duration::MAX;
Cache::updated_at_percentage(max_age, now, 101);
}

#[test]
fn updated_at_percent_edges() {
let now = Instant::now();
let max_age = Duration::from_secs(600);
let cases = [
(0, now), // 0%
(50, now - Duration::from_secs(300)), // 50%
(90, now - Duration::from_secs(540)), // 90%
(100, now - Duration::from_secs(600)), // 100%
];

for (percentage, expected) in cases {
assert_eq!(
Cache::updated_at_percentage(max_age, now, percentage),
expected,
);
}
}

#[test]
fn random_updated_at_range() {
let now = Instant::now();
let max_age = Duration::from_secs(600);
let mut rng = ChaCha20Rng::from_seed(Default::default());
let min = now - (max_age * 90 / 100);
let max = now - (max_age * 50 / 100);

for _ in 0..100 {
let updated_at = Cache::random_updated_at(max_age, now, &mut rng);
assert!(updated_at >= min && updated_at <= max);
}
}
}
Loading