95 lines
2.9 KiB
Rust
95 lines
2.9 KiB
Rust
|
|
use std::collections::HashMap;
|
||
|
|
|
||
|
|
use crate::{utils, AssetPair, OracleId, PriceAttestation};
|
||
|
|
|
||
|
|
#[derive(Clone)]
|
||
|
|
pub(crate) struct OracleNetworkCRDT {
|
||
|
|
pub(crate) attestations: HashMap<String, PriceAttestation>,
|
||
|
|
pub(crate) oracle_scores: HashMap<OracleId, f64>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl OracleNetworkCRDT {
|
||
|
|
pub(crate) fn new() -> Self {
|
||
|
|
Self {
|
||
|
|
attestations: HashMap::new(),
|
||
|
|
oracle_scores: HashMap::new(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
pub(crate) fn submit_attestation(&mut self, attestation: PriceAttestation) {
|
||
|
|
self.attestations
|
||
|
|
.insert(attestation.id.clone(), attestation.clone());
|
||
|
|
|
||
|
|
// Update oracle reputation
|
||
|
|
let score = self
|
||
|
|
.oracle_scores
|
||
|
|
.entry(attestation.oracle_id.clone())
|
||
|
|
.or_insert(0.5);
|
||
|
|
*score = (*score * 0.95) + 0.05;
|
||
|
|
}
|
||
|
|
|
||
|
|
pub(crate) fn merge(&mut self, other: &Self) {
|
||
|
|
for (id, attestation) in &other.attestations {
|
||
|
|
if !self.attestations.contains_key(id) {
|
||
|
|
self.attestations.insert(id.clone(), attestation.clone());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for (oracle_id, score) in &other.oracle_scores {
|
||
|
|
self.oracle_scores.insert(oracle_id.clone(), *score);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
pub(crate) fn get_aggregate_price(
|
||
|
|
&self,
|
||
|
|
asset_pair: &AssetPair,
|
||
|
|
max_age: u64,
|
||
|
|
) -> Option<(f64, u8, usize)> {
|
||
|
|
let now = utils::timestamp();
|
||
|
|
let min_time = now.saturating_sub(max_age);
|
||
|
|
|
||
|
|
let mut prices = Vec::new();
|
||
|
|
for attestation in self.attestations.values() {
|
||
|
|
if attestation.asset_pair == *asset_pair && attestation.timestamp >= min_time {
|
||
|
|
let weight = self
|
||
|
|
.oracle_scores
|
||
|
|
.get(&attestation.oracle_id)
|
||
|
|
.unwrap_or(&0.5);
|
||
|
|
prices.push((attestation.price, attestation.confidence, *weight));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if prices.is_empty() {
|
||
|
|
return None;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Remove outliers
|
||
|
|
prices.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
|
||
|
|
if prices.len() > 4 {
|
||
|
|
let q1 = prices[prices.len() / 4].0;
|
||
|
|
let q3 = prices[3 * prices.len() / 4].0;
|
||
|
|
let iqr = q3 - q1;
|
||
|
|
let lower = q1 - iqr * 1.5;
|
||
|
|
let upper = q3 + iqr * 1.5;
|
||
|
|
prices.retain(|(price, _, _)| *price >= lower && *price <= upper);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Calculate weighted average
|
||
|
|
let mut total_weight = 0.0;
|
||
|
|
let mut weighted_sum = 0.0;
|
||
|
|
let mut confidence_sum = 0.0;
|
||
|
|
|
||
|
|
for (price, confidence, weight) in &prices {
|
||
|
|
let w = (*confidence as f64 / 100.0) * weight;
|
||
|
|
weighted_sum += price * w;
|
||
|
|
confidence_sum += *confidence as f64 * w;
|
||
|
|
total_weight += w;
|
||
|
|
}
|
||
|
|
|
||
|
|
let avg_price = weighted_sum / total_weight;
|
||
|
|
let avg_confidence = (confidence_sum / total_weight) as u8;
|
||
|
|
|
||
|
|
Some((avg_price, avg_confidence, prices.len()))
|
||
|
|
}
|
||
|
|
}
|