Added some crazy AI generated ideas, as a brainstorming exercise.

This commit is contained in:
Dave
2025-06-12 15:06:34 -04:00
parent 073ce25306
commit 693ce3fafe
20 changed files with 7229 additions and 3 deletions

View File

@@ -0,0 +1,310 @@
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::time::{SystemTime, UNIX_EPOCH};
/// Represents a blockchain identifier
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct ChainId(pub String);
/// A message being relayed between chains
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrossChainMessage {
/// Unique identifier for this message
pub id: String,
/// Source blockchain
pub source_chain: ChainId,
/// Destination blockchain
pub dest_chain: ChainId,
/// Block height on source chain when message was created
pub source_block: u64,
/// Nonce for ordering messages from same block
pub nonce: u64,
/// The actual message payload
pub payload: Vec<u8>,
/// Timestamp when message was created
pub timestamp: u64,
/// Signatures from source chain validators
pub validator_signatures: Vec<ValidatorSignature>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidatorSignature {
pub validator_id: String,
pub signature: Vec<u8>,
}
/// CRDT structure for accumulating cross-chain messages
#[derive(Debug, Clone)]
pub struct CrossChainRelayCRDT {
/// All messages seen by this node
messages: HashMap<String, CrossChainMessage>,
/// Track which messages have been delivered to which chains
deliveries: HashMap<ChainId, HashSet<String>>,
/// Byzantine fault detection - track conflicting messages
conflicts: HashMap<String, Vec<CrossChainMessage>>,
}
impl CrossChainRelayCRDT {
pub fn new() -> Self {
Self {
messages: HashMap::new(),
deliveries: HashMap::new(),
conflicts: HashMap::new(),
}
}
/// Add a new message to the CRDT
pub fn add_message(&mut self, message: CrossChainMessage) -> Result<(), String> {
// Verify the message has enough validator signatures
if !self.verify_signatures(&message) {
return Err("Insufficient valid signatures".to_string());
}
// Check for Byzantine behavior - same ID but different content
if let Some(existing) = self.messages.get(&message.id) {
if !self.messages_equal(existing, &message) {
self.conflicts
.entry(message.id.clone())
.or_insert_with(Vec::new)
.push(message.clone());
return Err("Conflicting message detected".to_string());
}
}
// Add the message
self.messages.insert(message.id.clone(), message);
Ok(())
}
/// Merge another CRDT instance into this one
pub fn merge(&mut self, other: &CrossChainRelayCRDT) {
// Merge messages
for (id, message) in &other.messages {
if !self.messages.contains_key(id) {
let _ = self.add_message(message.clone());
}
}
// Merge delivery tracking
for (chain, delivered) in &other.deliveries {
self.deliveries
.entry(chain.clone())
.or_insert_with(HashSet::new)
.extend(delivered.clone());
}
// Merge conflict tracking
for (id, conflicts) in &other.conflicts {
self.conflicts
.entry(id.clone())
.or_insert_with(Vec::new)
.extend(conflicts.clone());
}
}
/// Get all messages destined for a specific chain that haven't been delivered yet
pub fn get_pending_messages(&self, dest_chain: &ChainId) -> Vec<&CrossChainMessage> {
let delivered = self.deliveries.get(dest_chain).cloned().unwrap_or_default();
let mut pending: Vec<&CrossChainMessage> = self
.messages
.values()
.filter(|msg| msg.dest_chain == *dest_chain && !delivered.contains(&msg.id))
.collect();
// Sort by source block height, then nonce for deterministic ordering
pending.sort_by(|a, b| {
a.source_block
.cmp(&b.source_block)
.then(a.nonce.cmp(&b.nonce))
});
pending
}
/// Mark messages as delivered to a chain
pub fn mark_delivered(&mut self, chain: &ChainId, message_ids: Vec<String>) {
let delivered = self
.deliveries
.entry(chain.clone())
.or_insert_with(HashSet::new);
delivered.extend(message_ids);
}
/// Get messages within a time window (for oracle-like use cases)
pub fn get_messages_in_window(
&self,
chain: &ChainId,
start_time: u64,
end_time: u64,
) -> Vec<&CrossChainMessage> {
self.messages
.values()
.filter(|msg| {
msg.dest_chain == *chain && msg.timestamp >= start_time && msg.timestamp <= end_time
})
.collect()
}
/// Verify validator signatures (simplified - real implementation would check against validator set)
fn verify_signatures(&self, message: &CrossChainMessage) -> bool {
// In real implementation:
// 1. Get validator set for source chain at source block height
// 2. Verify each signature
// 3. Check if we have enough stake represented
// For now, just check we have at least 2 signatures
message.validator_signatures.len() >= 2
}
fn messages_equal(&self, a: &CrossChainMessage, b: &CrossChainMessage) -> bool {
a.source_chain == b.source_chain
&& a.dest_chain == b.dest_chain
&& a.source_block == b.source_block
&& a.nonce == b.nonce
&& a.payload == b.payload
}
}
/// Example: Oracle price aggregation use case
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriceOracleMessage {
pub oracle_id: String,
pub asset_pair: String,
pub price: u128,
pub confidence: u8,
}
impl CrossChainRelayCRDT {
/// Aggregate oracle prices from messages in a time window
pub fn aggregate_oracle_prices(
&self,
dest_chain: &ChainId,
asset_pair: &str,
time_window: u64,
) -> Option<u128> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let messages = self.get_messages_in_window(dest_chain, now - time_window, now);
let mut prices = Vec::new();
for msg in messages {
if let Ok(oracle_msg) = serde_json::from_slice::<PriceOracleMessage>(&msg.payload) {
if oracle_msg.asset_pair == asset_pair {
prices.push(oracle_msg.price);
}
}
}
if prices.is_empty() {
return None;
}
// Calculate median price
prices.sort();
Some(prices[prices.len() / 2])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cross_chain_relay() {
let mut relay1 = CrossChainRelayCRDT::new();
let mut relay2 = CrossChainRelayCRDT::new();
let ethereum = ChainId("ethereum".to_string());
let polygon = ChainId("polygon".to_string());
// Create a message from Ethereum to Polygon
let message = CrossChainMessage {
id: "msg1".to_string(),
source_chain: ethereum.clone(),
dest_chain: polygon.clone(),
source_block: 1000,
nonce: 1,
payload: b"transfer:100:USDC:0x123...".to_vec(),
timestamp: 1234567890,
validator_signatures: vec![
ValidatorSignature {
validator_id: "val1".to_string(),
signature: vec![1, 2, 3],
},
ValidatorSignature {
validator_id: "val2".to_string(),
signature: vec![4, 5, 6],
},
],
};
// Add to first relay
relay1.add_message(message.clone()).unwrap();
// Get pending messages for Polygon
let pending = relay1.get_pending_messages(&polygon);
assert_eq!(pending.len(), 1);
// Merge relay1 into relay2
relay2.merge(&relay1);
// Both should now have the same message
assert_eq!(relay2.get_pending_messages(&polygon).len(), 1);
// Mark as delivered
relay2.mark_delivered(&polygon, vec!["msg1".to_string()]);
assert_eq!(relay2.get_pending_messages(&polygon).len(), 0);
}
#[test]
fn test_oracle_aggregation() {
let mut relay = CrossChainRelayCRDT::new();
let ethereum = ChainId("ethereum".to_string());
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
// Add price submissions from multiple oracles
for i in 0..5 {
let price_msg = PriceOracleMessage {
oracle_id: format!("oracle{}", i),
asset_pair: "ETH/USD".to_string(),
price: 2000 + (i as u128 * 10), // Prices: 2000, 2010, 2020, 2030, 2040
confidence: 95,
};
let message = CrossChainMessage {
id: format!("price{}", i),
source_chain: ChainId(format!("oracle{}", i)),
dest_chain: ethereum.clone(),
source_block: 1000,
nonce: i as u64,
payload: serde_json::to_vec(&price_msg).unwrap(),
timestamp: now - 30, // 30 seconds ago
validator_signatures: vec![
ValidatorSignature {
validator_id: "val1".to_string(),
signature: vec![1, 2, 3],
},
ValidatorSignature {
validator_id: "val2".to_string(),
signature: vec![4, 5, 6],
},
],
};
relay.add_message(message).unwrap();
}
// Aggregate prices from last minute
let median_price = relay
.aggregate_oracle_prices(&ethereum, "ETH/USD", 60)
.unwrap();
assert_eq!(median_price, 2020); // Median of 2000, 2010, 2020, 2030, 2040
}
}

View File

@@ -0,0 +1,720 @@
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
/// This example demonstrates how multiple BFT-CRDT use cases can be combined
/// into a comprehensive DeFi platform that operates without global consensus.
// ==== Identity Layer ====
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct IdentityId(pub String);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Identity {
pub id: IdentityId,
pub public_key: Vec<u8>,
pub attestations_received: HashSet<AttestationId>,
pub attestations_given: HashSet<AttestationId>,
pub reputation_score: f64,
pub created_at: u64,
}
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct AttestationId(pub String);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Attestation {
pub id: AttestationId,
pub issuer: IdentityId,
pub subject: IdentityId,
pub claim: String,
pub confidence: u8,
pub timestamp: u64,
pub expiry: Option<u64>,
pub signature: Vec<u8>,
}
// ==== Multi-Party State Channel ====
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct ChannelId(pub String);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StateChannel {
pub id: ChannelId,
pub participants: Vec<IdentityId>,
pub balances: HashMap<(IdentityId, String), u128>, // (user, token) -> balance
pub orders: OrderBookCRDT,
pub positions: HashMap<IdentityId, Vec<Position>>,
pub nonce: u64,
pub last_update: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Position {
pub id: String,
pub owner: IdentityId,
pub market: String,
pub size: i128,
pub entry_price: u128,
pub leverage: u8,
pub margin: u128,
pub unrealized_pnl: i128,
}
// ==== Order Book CRDT ====
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderBookCRDT {
pub orders: HashMap<String, Order>,
pub executions: HashMap<String, Execution>,
pub cancellations: HashSet<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Order {
pub id: String,
pub trader: IdentityId,
pub side: OrderSide,
pub price: u128,
pub amount: u128,
pub remaining: u128,
pub timestamp: u64,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum OrderSide {
Buy,
Sell,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Execution {
pub id: String,
pub buy_order: String,
pub sell_order: String,
pub price: u128,
pub amount: u128,
pub timestamp: u64,
}
// ==== Oracle Price Data ====
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriceSubmission {
pub oracle: IdentityId,
pub asset: String,
pub price: u128,
pub confidence: u8,
pub timestamp: u64,
pub sources: Vec<String>,
pub signature: Vec<u8>,
}
// ==== Cross-Chain Messages ====
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct ChainId(pub String);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrossChainMessage {
pub id: String,
pub source_chain: ChainId,
pub dest_chain: ChainId,
pub sender: IdentityId,
pub action: CrossChainAction,
pub timestamp: u64,
pub signatures: Vec<Vec<u8>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CrossChainAction {
Deposit { token: String, amount: u128 },
Withdraw { token: String, amount: u128 },
SyncPosition { position: Position },
LiquidationAlert { position_id: String },
}
// ==== Integrated DeFi Platform ====
pub struct IntegratedDeFiPlatform {
// Identity layer
pub identities: HashMap<IdentityId, Identity>,
pub attestations: HashMap<AttestationId, Attestation>,
// State channels
pub channels: HashMap<ChannelId, StateChannel>,
// Oracle data
pub price_submissions: BTreeMap<(String, u64), Vec<PriceSubmission>>,
// Cross-chain messages
pub cross_chain_messages: HashMap<String, CrossChainMessage>,
// Platform parameters
pub min_reputation_score: f64,
pub liquidation_threshold: f64,
}
impl IntegratedDeFiPlatform {
pub fn new() -> Self {
Self {
identities: HashMap::new(),
attestations: HashMap::new(),
channels: HashMap::new(),
price_submissions: BTreeMap::new(),
cross_chain_messages: HashMap::new(),
min_reputation_score: 0.5,
liquidation_threshold: 0.8,
}
}
// ==== Identity Functions ====
pub fn create_identity(&mut self, id: IdentityId, public_key: Vec<u8>) -> Result<(), String> {
if self.identities.contains_key(&id) {
return Err("Identity already exists".to_string());
}
let identity = Identity {
id: id.clone(),
public_key,
attestations_received: HashSet::new(),
attestations_given: HashSet::new(),
reputation_score: 0.0,
created_at: Self::timestamp(),
};
self.identities.insert(id, identity);
Ok(())
}
pub fn create_attestation(
&mut self,
issuer: IdentityId,
subject: IdentityId,
claim: String,
confidence: u8,
) -> Result<AttestationId, String> {
// Check issuer exists and has sufficient reputation
let issuer_identity = self.identities.get(&issuer).ok_or("Issuer not found")?;
if issuer_identity.reputation_score < self.min_reputation_score {
return Err("Insufficient reputation to issue attestations".to_string());
}
let attestation_id = AttestationId(format!("att_{}", Self::timestamp()));
let attestation = Attestation {
id: attestation_id.clone(),
issuer: issuer.clone(),
subject: subject.clone(),
claim,
confidence,
timestamp: Self::timestamp(),
expiry: None,
signature: vec![1, 2, 3], // Placeholder signature
};
// Update both identities
self.attestations
.insert(attestation_id.clone(), attestation);
if let Some(issuer_identity) = self.identities.get_mut(&issuer) {
issuer_identity
.attestations_given
.insert(attestation_id.clone());
}
if let Some(subject_identity) = self.identities.get_mut(&subject) {
subject_identity
.attestations_received
.insert(attestation_id.clone());
// Simple reputation update
subject_identity.reputation_score += (confidence as f64 / 100.0) * 0.1;
}
Ok(attestation_id)
}
// ==== State Channel Functions ====
pub fn create_channel(&mut self, participants: Vec<IdentityId>) -> Result<ChannelId, String> {
// Verify all participants exist and have sufficient reputation
for participant in &participants {
let identity = self
.identities
.get(participant)
.ok_or("Participant not found")?;
if identity.reputation_score < self.min_reputation_score {
return Err(format!(
"Participant {} has insufficient reputation",
participant.0
));
}
}
let channel_id = ChannelId(format!("channel_{}", Self::timestamp()));
let channel = StateChannel {
id: channel_id.clone(),
participants,
balances: HashMap::new(),
orders: OrderBookCRDT {
orders: HashMap::new(),
executions: HashMap::new(),
cancellations: HashSet::new(),
},
positions: HashMap::new(),
nonce: 0,
last_update: Self::timestamp(),
};
self.channels.insert(channel_id.clone(), channel);
Ok(channel_id)
}
pub fn place_order(
&mut self,
channel_id: &ChannelId,
trader: &IdentityId,
side: OrderSide,
price: u128,
amount: u128,
) -> Result<String, String> {
let channel = self
.channels
.get_mut(channel_id)
.ok_or("Channel not found")?;
// Verify trader is participant
if !channel.participants.contains(trader) {
return Err("Trader not in channel".to_string());
}
// Check balance for sells or margin for buys
match side {
OrderSide::Sell => {
let balance = channel
.balances
.get(&(trader.clone(), "ETH".to_string()))
.unwrap_or(&0);
if *balance < amount {
return Err("Insufficient balance".to_string());
}
}
OrderSide::Buy => {
let usdc_balance = channel
.balances
.get(&(trader.clone(), "USDC".to_string()))
.unwrap_or(&0);
let required = price * amount / 1_000_000; // Assuming 6 decimals
if *usdc_balance < required {
return Err("Insufficient USDC balance".to_string());
}
}
}
let order_id = format!("order_{}_{}", trader.0, Self::timestamp());
let order = Order {
id: order_id.clone(),
trader: trader.clone(),
side,
price,
amount,
remaining: amount,
timestamp: Self::timestamp(),
};
channel.orders.orders.insert(order_id.clone(), order);
channel.last_update = Self::timestamp();
channel.nonce += 1;
// Try to match orders
self.match_orders(channel_id)?;
Ok(order_id)
}
fn match_orders(&mut self, channel_id: &ChannelId) -> Result<(), String> {
let channel = self
.channels
.get_mut(channel_id)
.ok_or("Channel not found")?;
let mut executions = Vec::new();
// Simple matching logic
let mut buy_orders: Vec<_> = channel
.orders
.orders
.values()
.filter(|o| matches!(o.side, OrderSide::Buy) && o.remaining > 0)
.collect();
buy_orders.sort_by_key(|o| std::cmp::Reverse(o.price));
let mut sell_orders: Vec<_> = channel
.orders
.orders
.values()
.filter(|o| matches!(o.side, OrderSide::Sell) && o.remaining > 0)
.collect();
sell_orders.sort_by_key(|o| o.price);
for buy_order in buy_orders {
for sell_order in &mut sell_orders {
if buy_order.price >= sell_order.price
&& buy_order.remaining > 0
&& sell_order.remaining > 0
{
let amount = buy_order.remaining.min(sell_order.remaining);
let execution = Execution {
id: format!("exec_{}", Self::timestamp()),
buy_order: buy_order.id.clone(),
sell_order: sell_order.id.clone(),
price: sell_order.price,
amount,
timestamp: Self::timestamp(),
};
executions.push(execution);
}
}
}
// Apply executions
for execution in executions {
channel
.orders
.executions
.insert(execution.id.clone(), execution.clone());
// Update order remaining amounts
if let Some(buy_order) = channel.orders.orders.get_mut(&execution.buy_order) {
buy_order.remaining -= execution.amount;
}
if let Some(sell_order) = channel.orders.orders.get_mut(&execution.sell_order) {
sell_order.remaining -= execution.amount;
}
// Update balances
// This is simplified - real implementation would handle decimals properly
let buyer = channel
.orders
.orders
.get(&execution.buy_order)
.unwrap()
.trader
.clone();
let seller = channel
.orders
.orders
.get(&execution.sell_order)
.unwrap()
.trader
.clone();
*channel
.balances
.entry((buyer.clone(), "ETH".to_string()))
.or_insert(0) += execution.amount;
*channel
.balances
.entry((seller.clone(), "ETH".to_string()))
.or_insert(0) -= execution.amount;
let usdc_amount = execution.price * execution.amount / 1_000_000;
*channel
.balances
.entry((buyer, "USDC".to_string()))
.or_insert(0) -= usdc_amount;
*channel
.balances
.entry((seller, "USDC".to_string()))
.or_insert(0) += usdc_amount;
}
Ok(())
}
// ==== Oracle Functions ====
pub fn submit_price(
&mut self,
oracle: IdentityId,
asset: String,
price: u128,
confidence: u8,
) -> Result<(), String> {
// Verify oracle has sufficient reputation
let oracle_identity = self.identities.get(&oracle).ok_or("Oracle not found")?;
if oracle_identity.reputation_score < self.min_reputation_score * 2.0 {
return Err("Insufficient reputation to submit prices".to_string());
}
let submission = PriceSubmission {
oracle,
asset: asset.clone(),
price,
confidence,
timestamp: Self::timestamp(),
sources: vec!["binance".to_string(), "coinbase".to_string()],
signature: vec![1, 2, 3],
};
let key = (asset, submission.timestamp);
self.price_submissions
.entry(key)
.or_insert_with(Vec::new)
.push(submission);
Ok(())
}
pub fn get_aggregate_price(&self, asset: &str, time_window: Duration) -> Option<u128> {
let now = Self::timestamp();
let start_time = now - time_window.as_secs();
let mut prices = Vec::new();
for ((price_asset, timestamp), submissions) in &self.price_submissions {
if price_asset == asset && *timestamp >= start_time && *timestamp <= now {
for submission in submissions {
// Weight by confidence
for _ in 0..submission.confidence {
prices.push(submission.price);
}
}
}
}
if prices.is_empty() {
return None;
}
// Calculate weighted median
prices.sort();
Some(prices[prices.len() / 2])
}
// ==== Cross-Chain Functions ====
pub fn send_cross_chain_message(
&mut self,
source_chain: ChainId,
dest_chain: ChainId,
sender: IdentityId,
action: CrossChainAction,
) -> Result<String, String> {
let message_id = format!("msg_{}", Self::timestamp());
let message = CrossChainMessage {
id: message_id.clone(),
source_chain,
dest_chain,
sender,
action,
timestamp: Self::timestamp(),
signatures: vec![vec![1, 2, 3]], // Placeholder
};
self.cross_chain_messages
.insert(message_id.clone(), message);
Ok(message_id)
}
// ==== Liquidation Monitor ====
pub fn check_liquidations(&mut self, channel_id: &ChannelId) -> Result<Vec<String>, String> {
let channel = self.channels.get(channel_id).ok_or("Channel not found")?;
let mut liquidations = Vec::new();
for (identity, positions) in &channel.positions {
for position in positions {
// Get current price
let price = self
.get_aggregate_price(&position.market, Duration::from_secs(300))
.unwrap_or(position.entry_price);
// Calculate health
let value = (position.size.abs() as u128) * price / 1_000_000;
let health = position.margin as f64 / value as f64;
if health < self.liquidation_threshold {
liquidations.push(position.id.clone());
// Send cross-chain alert
self.send_cross_chain_message(
ChainId("ethereum".to_string()),
ChainId("arbitrum".to_string()),
identity.clone(),
CrossChainAction::LiquidationAlert {
position_id: position.id.clone(),
},
)?;
}
}
}
Ok(liquidations)
}
// ==== CRDT Merge Function ====
pub fn merge(&mut self, other: &Self) {
// Merge identities
for (id, identity) in &other.identities {
self.identities
.entry(id.clone())
.or_insert_with(|| identity.clone());
}
// Merge attestations
for (id, attestation) in &other.attestations {
self.attestations
.entry(id.clone())
.or_insert_with(|| attestation.clone());
}
// Merge channels (simplified - real implementation would merge internal state)
for (id, channel) in &other.channels {
if let Some(our_channel) = self.channels.get_mut(id) {
// Merge orders
for (order_id, order) in &channel.orders.orders {
our_channel
.orders
.orders
.entry(order_id.clone())
.or_insert_with(|| order.clone());
}
// Merge executions
for (exec_id, execution) in &channel.orders.executions {
our_channel
.orders
.executions
.entry(exec_id.clone())
.or_insert_with(|| execution.clone());
}
// Update nonce to max
our_channel.nonce = our_channel.nonce.max(channel.nonce);
} else {
self.channels.insert(id.clone(), channel.clone());
}
}
// Merge price submissions
for (key, submissions) in &other.price_submissions {
self.price_submissions
.entry(key.clone())
.or_insert_with(Vec::new)
.extend(submissions.clone());
}
// Merge cross-chain messages
for (id, message) in &other.cross_chain_messages {
self.cross_chain_messages
.entry(id.clone())
.or_insert_with(|| message.clone());
}
}
fn timestamp() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_integrated_platform() {
let mut platform = IntegratedDeFiPlatform::new();
// Create identities
let alice = IdentityId("alice".to_string());
let bob = IdentityId("bob".to_string());
let oracle = IdentityId("oracle1".to_string());
platform
.create_identity(alice.clone(), vec![1, 2, 3])
.unwrap();
platform
.create_identity(bob.clone(), vec![4, 5, 6])
.unwrap();
platform
.create_identity(oracle.clone(), vec![7, 8, 9])
.unwrap();
// Build reputation through attestations
platform
.identities
.get_mut(&alice)
.unwrap()
.reputation_score = 1.0;
platform
.create_attestation(alice.clone(), bob.clone(), "TrustedTrader".to_string(), 90)
.unwrap();
platform
.identities
.get_mut(&oracle)
.unwrap()
.reputation_score = 2.0;
// Create trading channel
let channel_id = platform
.create_channel(vec![alice.clone(), bob.clone()])
.unwrap();
// Add some balances
let channel = platform.channels.get_mut(&channel_id).unwrap();
channel
.balances
.insert((alice.clone(), "ETH".to_string()), 10_000_000);
channel
.balances
.insert((bob.clone(), "USDC".to_string()), 25_000_000_000);
// Submit oracle prices
platform
.submit_price(oracle.clone(), "ETH".to_string(), 2500_000_000, 95)
.unwrap();
// Place orders
platform
.place_order(
&channel_id,
&alice,
OrderSide::Sell,
2505_000_000,
5_000_000,
)
.unwrap();
platform
.place_order(&channel_id, &bob, OrderSide::Buy, 2510_000_000, 3_000_000)
.unwrap();
// Check that orders matched
let channel = platform.channels.get(&channel_id).unwrap();
assert!(!channel.orders.executions.is_empty());
// Check cross-chain functionality
platform
.send_cross_chain_message(
ChainId("ethereum".to_string()),
ChainId("polygon".to_string()),
alice.clone(),
CrossChainAction::Deposit {
token: "USDC".to_string(),
amount: 1000_000_000,
},
)
.unwrap();
assert!(!platform.cross_chain_messages.is_empty());
}
}

View File

@@ -0,0 +1,439 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
/**
* @title OracleConsumer
* @notice Example smart contracts showing how to consume data from the BFT-CRDT oracle network
* @dev This demonstrates various aggregation strategies and use cases
*/
interface IOracleNetwork {
struct PriceData {
uint128 price;
uint8 confidence;
address oracle;
uint256 timestamp;
bytes32 sourceHash;
}
function getPrices(
string calldata assetPair,
uint256 startTime,
uint256 endTime
) external view returns (PriceData[] memory);
function getOracleReputation(address oracle) external view returns (
uint256 qualityScore,
uint256 totalAttestations,
uint256 anomalyReports
);
}
/**
* @title PriceAggregator
* @notice Advanced price aggregation with multiple strategies
*/
contract PriceAggregator {
IOracleNetwork public immutable oracleNetwork;
uint256 public constant MIN_SOURCES = 3;
uint256 public constant OUTLIER_THRESHOLD = 500; // 5%
uint256 public constant CONFIDENCE_THRESHOLD = 80;
struct AggregatedPrice {
uint128 price;
uint8 confidence;
uint256 numSources;
uint256 timestamp;
}
mapping(string => AggregatedPrice) public latestPrices;
event PriceUpdated(
string indexed assetPair,
uint128 price,
uint8 confidence,
uint256 sources
);
constructor(address _oracleNetwork) {
oracleNetwork = IOracleNetwork(_oracleNetwork);
}
/**
* @notice Get aggregated price using weighted median
* @param assetPair The asset pair (e.g., "ETH/USD")
* @param maxAge Maximum age of price data in seconds
*/
function getPrice(
string calldata assetPair,
uint256 maxAge
) external view returns (uint128 price, uint8 confidence) {
require(maxAge > 0, "Invalid max age");
// Get all prices from oracle network
IOracleNetwork.PriceData[] memory prices = oracleNetwork.getPrices(
assetPair,
block.timestamp - maxAge,
block.timestamp
);
require(prices.length >= MIN_SOURCES, "Insufficient price sources");
// Calculate weighted median
AggregatedPrice memory aggregated = _calculateWeightedMedian(prices);
return (aggregated.price, aggregated.confidence);
}
/**
* @notice Calculate Time-Weighted Average Price (TWAP)
* @param assetPair The asset pair
* @param duration Time window in seconds
*/
function getTWAP(
string calldata assetPair,
uint256 duration
) external view returns (uint128) {
IOracleNetwork.PriceData[] memory prices = oracleNetwork.getPrices(
assetPair,
block.timestamp - duration,
block.timestamp
);
require(prices.length > 0, "No price data available");
uint256 weightedSum = 0;
uint256 totalWeight = 0;
for (uint i = 0; i < prices.length; i++) {
// Weight by time and confidence
uint256 timeWeight = duration - (block.timestamp - prices[i].timestamp);
uint256 confWeight = prices[i].confidence;
uint256 weight = timeWeight * confWeight / 100;
weightedSum += prices[i].price * weight;
totalWeight += weight;
}
return uint128(weightedSum / totalWeight);
}
/**
* @notice Get volatility-adjusted price
* @dev Uses standard deviation to adjust confidence
*/
function getVolatilityAdjustedPrice(
string calldata assetPair,
uint256 maxAge
) external view returns (
uint128 price,
uint8 confidence,
uint128 standardDeviation
) {
IOracleNetwork.PriceData[] memory prices = oracleNetwork.getPrices(
assetPair,
block.timestamp - maxAge,
block.timestamp
);
require(prices.length >= MIN_SOURCES, "Insufficient sources");
// Remove outliers first
uint128[] memory filteredPrices = _removeOutliers(prices);
// Calculate mean
uint256 sum = 0;
for (uint i = 0; i < filteredPrices.length; i++) {
sum += filteredPrices[i];
}
uint128 mean = uint128(sum / filteredPrices.length);
// Calculate standard deviation
uint256 variance = 0;
for (uint i = 0; i < filteredPrices.length; i++) {
int256 diff = int256(uint256(filteredPrices[i])) - int256(uint256(mean));
variance += uint256(diff * diff);
}
variance = variance / filteredPrices.length;
standardDeviation = uint128(_sqrt(variance));
// Adjust confidence based on volatility
uint256 volatilityRatio = (standardDeviation * 10000) / mean;
if (volatilityRatio < 100) { // < 1%
confidence = 99;
} else if (volatilityRatio < 500) { // < 5%
confidence = 90;
} else if (volatilityRatio < 1000) { // < 10%
confidence = 70;
} else {
confidence = 50;
}
return (mean, confidence, standardDeviation);
}
/**
* @notice Update stored price if newer data is available
*/
function updatePrice(string calldata assetPair) external {
AggregatedPrice memory current = latestPrices[assetPair];
IOracleNetwork.PriceData[] memory prices = oracleNetwork.getPrices(
assetPair,
current.timestamp,
block.timestamp
);
if (prices.length >= MIN_SOURCES) {
AggregatedPrice memory newPrice = _calculateWeightedMedian(prices);
latestPrices[assetPair] = newPrice;
emit PriceUpdated(
assetPair,
newPrice.price,
newPrice.confidence,
newPrice.numSources
);
}
}
function _calculateWeightedMedian(
IOracleNetwork.PriceData[] memory prices
) private view returns (AggregatedPrice memory) {
// Sort prices and calculate weights
uint256 length = prices.length;
uint128[] memory sortedPrices = new uint128[](length);
uint256[] memory weights = new uint256[](length);
for (uint i = 0; i < length; i++) {
sortedPrices[i] = prices[i].price;
// Calculate weight based on oracle reputation and confidence
(uint256 qualityScore,,) = oracleNetwork.getOracleReputation(prices[i].oracle);
weights[i] = prices[i].confidence * qualityScore / 100;
}
// Bubble sort (gas inefficient but simple for example)
for (uint i = 0; i < length - 1; i++) {
for (uint j = 0; j < length - i - 1; j++) {
if (sortedPrices[j] > sortedPrices[j + 1]) {
// Swap prices
uint128 tempPrice = sortedPrices[j];
sortedPrices[j] = sortedPrices[j + 1];
sortedPrices[j + 1] = tempPrice;
// Swap weights
uint256 tempWeight = weights[j];
weights[j] = weights[j + 1];
weights[j + 1] = tempWeight;
}
}
}
// Find weighted median
uint256 totalWeight = 0;
for (uint i = 0; i < length; i++) {
totalWeight += weights[i];
}
uint256 targetWeight = totalWeight / 2;
uint256 cumulativeWeight = 0;
uint128 medianPrice = sortedPrices[length / 2]; // fallback
for (uint i = 0; i < length; i++) {
cumulativeWeight += weights[i];
if (cumulativeWeight >= targetWeight) {
medianPrice = sortedPrices[i];
break;
}
}
// Calculate confidence
uint8 avgConfidence = 0;
for (uint i = 0; i < length; i++) {
avgConfidence += prices[i].confidence;
}
avgConfidence = avgConfidence / uint8(length);
return AggregatedPrice({
price: medianPrice,
confidence: avgConfidence,
numSources: length,
timestamp: block.timestamp
});
}
function _removeOutliers(
IOracleNetwork.PriceData[] memory prices
) private pure returns (uint128[] memory) {
if (prices.length < 4) {
// Not enough data for outlier detection
uint128[] memory result = new uint128[](prices.length);
for (uint i = 0; i < prices.length; i++) {
result[i] = prices[i].price;
}
return result;
}
// Calculate mean
uint256 sum = 0;
for (uint i = 0; i < prices.length; i++) {
sum += prices[i].price;
}
uint256 mean = sum / prices.length;
// Count non-outliers
uint256 validCount = 0;
for (uint i = 0; i < prices.length; i++) {
uint256 deviation = prices[i].price > mean
? prices[i].price - mean
: mean - prices[i].price;
uint256 percentDeviation = (deviation * 10000) / mean;
if (percentDeviation <= OUTLIER_THRESHOLD) {
validCount++;
}
}
// Create filtered array
uint128[] memory filtered = new uint128[](validCount);
uint256 index = 0;
for (uint i = 0; i < prices.length; i++) {
uint256 deviation = prices[i].price > mean
? prices[i].price - mean
: mean - prices[i].price;
uint256 percentDeviation = (deviation * 10000) / mean;
if (percentDeviation <= OUTLIER_THRESHOLD) {
filtered[index++] = prices[i].price;
}
}
return filtered;
}
function _sqrt(uint256 x) private pure returns (uint256) {
if (x == 0) return 0;
uint256 z = (x + 1) / 2;
uint256 y = x;
while (z < y) {
y = z;
z = (x / z + z) / 2;
}
return y;
}
}
/**
* @title DeFiLendingProtocol
* @notice Example lending protocol using BFT-CRDT oracle
*/
contract DeFiLendingProtocol {
PriceAggregator public immutable priceAggregator;
uint256 public constant COLLATERAL_FACTOR = 8000; // 80%
uint256 public constant LIQUIDATION_THRESHOLD = 8500; // 85%
uint256 public constant PRICE_STALENESS = 300; // 5 minutes
mapping(address => mapping(string => uint256)) public deposits;
mapping(address => mapping(string => uint256)) public borrows;
constructor(address _priceAggregator) {
priceAggregator = PriceAggregator(_priceAggregator);
}
/**
* @notice Calculate USD value using oracle prices
*/
function getCollateralValueUSD(
address user,
string calldata asset
) public view returns (uint256) {
uint256 amount = deposits[user][asset];
if (amount == 0) return 0;
(uint128 price, uint8 confidence) = priceAggregator.getPrice(
string(abi.encodePacked(asset, "/USD")),
PRICE_STALENESS
);
require(confidence >= 80, "Price confidence too low");
// Apply conservative estimate for collateral
return (amount * price * COLLATERAL_FACTOR) / 10000 / 1e6;
}
/**
* @notice Check if a position is healthy
*/
function isPositionHealthy(address user) public view returns (bool) {
uint256 totalCollateralUSD = 0;
uint256 totalBorrowUSD = 0;
// In real implementation, would iterate through all assets
totalCollateralUSD += getCollateralValueUSD(user, "ETH");
totalCollateralUSD += getCollateralValueUSD(user, "BTC");
// Calculate total borrows in USD
uint256 usdcBorrow = borrows[user]["USDC"];
totalBorrowUSD += usdcBorrow; // USDC is 1:1 with USD
if (totalBorrowUSD == 0) return true;
uint256 healthFactor = (totalCollateralUSD * 10000) / totalBorrowUSD;
return healthFactor >= LIQUIDATION_THRESHOLD;
}
}
/**
* @title OptionsProtocol
* @notice Example options protocol using oracle for settlement
*/
contract OptionsProtocol {
PriceAggregator public immutable priceAggregator;
struct Option {
string assetPair;
uint128 strikePrice;
uint256 expiry;
bool isCall;
bool isSettled;
uint128 settlementPrice;
}
mapping(uint256 => Option) public options;
uint256 public nextOptionId;
constructor(address _priceAggregator) {
priceAggregator = PriceAggregator(_priceAggregator);
}
/**
* @notice Settle an option at expiry using TWAP
*/
function settleOption(uint256 optionId) external {
Option storage option = options[optionId];
require(!option.isSettled, "Already settled");
require(block.timestamp >= option.expiry, "Not expired");
// Use 1-hour TWAP around expiry for fair settlement
uint128 settlementPrice = priceAggregator.getTWAP(
option.assetPair,
3600 // 1 hour
);
option.settlementPrice = settlementPrice;
option.isSettled = true;
// Calculate payoff
uint256 payoff = 0;
if (option.isCall && settlementPrice > option.strikePrice) {
payoff = settlementPrice - option.strikePrice;
} else if (!option.isCall && settlementPrice < option.strikePrice) {
payoff = option.strikePrice - settlementPrice;
}
// Process payoff...
}
}

802
examples/oracle_network.rs Normal file
View File

@@ -0,0 +1,802 @@
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
/// A comprehensive example of a decentralized oracle network using BFT-CRDTs
/// that eliminates the need for consensus on price updates.
// ==== Core Types ====
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct OracleId(pub String);
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct AssetPair(pub String);
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct AttestationId(pub String);
/// A price attestation from an oracle
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriceAttestation {
pub id: AttestationId,
pub oracle_id: OracleId,
pub asset_pair: AssetPair,
pub price: u128,
pub confidence: u8, // 0-100
pub timestamp: u64,
pub sources: Vec<DataSource>,
pub proof: AttestationProof,
pub signature: Vec<u8>,
}
/// Data source information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DataSource {
pub name: String,
pub price: u128,
pub volume: u128,
pub timestamp: u64,
}
/// Proof that the price data is authentic
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AttestationProof {
/// TLS proof from HTTPS API
TlsProof {
server_cert: Vec<u8>,
response_hash: Vec<u8>,
timestamp: u64,
},
/// Signed data from WebSocket feed
SignedFeed {
exchange_signature: Vec<u8>,
sequence_number: u64,
},
/// On-chain proof (e.g., from DEX)
OnChainProof {
chain_id: String,
block_number: u64,
transaction_hash: Vec<u8>,
},
}
/// Oracle reputation and performance metrics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OracleMetrics {
pub oracle_id: OracleId,
pub total_attestations: u64,
pub average_deviation: f64,
pub uptime_percentage: f64,
pub last_submission: u64,
pub quality_score: f64,
}
/// The main CRDT structure for the oracle network
#[derive(Debug, Clone)]
pub struct OracleNetworkCRDT {
/// All price attestations
attestations: HashMap<AttestationId, PriceAttestation>,
/// Index by asset pair and time for efficient queries
price_index: BTreeMap<(AssetPair, u64), Vec<AttestationId>>,
/// Oracle performance metrics
oracle_metrics: HashMap<OracleId, OracleMetrics>,
/// Detected anomalies and disputes
anomalies: HashMap<AttestationId, AnomalyReport>,
/// Network parameters
params: NetworkParams,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkParams {
pub min_oracle_stake: u128,
pub max_price_age: Duration,
pub outlier_threshold: f64,
pub min_sources: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnomalyReport {
pub attestation_id: AttestationId,
pub report_type: AnomalyType,
pub reporter: OracleId,
pub evidence: String,
pub timestamp: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AnomalyType {
OutlierPrice { deviation_percentage: f64 },
InvalidProof,
StaleData { age_seconds: u64 },
SuspiciousPattern,
}
impl OracleNetworkCRDT {
pub fn new(params: NetworkParams) -> Self {
Self {
attestations: HashMap::new(),
price_index: BTreeMap::new(),
oracle_metrics: HashMap::new(),
anomalies: HashMap::new(),
params,
}
}
/// Submit a new price attestation to the network
pub fn submit_attestation(
&mut self,
attestation: PriceAttestation,
) -> Result<(), String> {
// Validate attestation
self.validate_attestation(&attestation)?;
// Check for duplicate
if self.attestations.contains_key(&attestation.id) {
return Ok(()); // Idempotent
}
// Add to main storage
let id = attestation.id.clone();
let asset_pair = attestation.asset_pair.clone();
let timestamp = attestation.timestamp;
let oracle_id = attestation.oracle_id.clone();
self.attestations.insert(id.clone(), attestation);
// Update index
self.price_index
.entry((asset_pair, timestamp))
.or_insert_with(Vec::new)
.push(id);
// Update oracle metrics
self.update_oracle_metrics(&oracle_id);
Ok(())
}
/// Validate an attestation before accepting it
fn validate_attestation(&self, attestation: &PriceAttestation) -> Result<(), String> {
// Check timestamp is reasonable
let now = Self::timestamp();
if attestation.timestamp > now + 60 {
return Err("Attestation timestamp is in the future".to_string());
}
if attestation.timestamp < now - self.params.max_price_age.as_secs() {
return Err("Attestation is too old".to_string());
}
// Verify minimum sources
if attestation.sources.len() < self.params.min_sources {
return Err("Insufficient data sources".to_string());
}
// Verify signature (placeholder - real implementation would verify cryptographically)
if attestation.signature.is_empty() {
return Err("Missing signature".to_string());
}
// Validate proof
match &attestation.proof {
AttestationProof::TlsProof { timestamp, .. } => {
if *timestamp < attestation.timestamp - 300 {
return Err("TLS proof too old".to_string());
}
}
AttestationProof::SignedFeed { .. } => {
// Verify exchange signature in real implementation
}
AttestationProof::OnChainProof { .. } => {
// Verify on-chain data in real implementation
}
}
Ok(())
}
/// Get aggregated price for an asset pair within a time window
pub fn get_aggregate_price(
&self,
asset_pair: &AssetPair,
time_window: Duration,
) -> Option<AggregatedPrice> {
let now = Self::timestamp();
let start_time = now.saturating_sub(time_window.as_secs());
// Collect all attestations in time window
let mut attestations = Vec::new();
for ((pair, timestamp), attestation_ids) in self.price_index.range(
(asset_pair.clone(), start_time)..=(asset_pair.clone(), now)
) {
if pair == asset_pair {
for id in attestation_ids {
if let Some(attestation) = self.attestations.get(id) {
attestations.push(attestation);
}
}
}
}
if attestations.is_empty() {
return None;
}
// Calculate aggregated price
self.aggregate_prices(attestations, now)
}
/// Aggregate multiple price attestations into a single price
fn aggregate_prices(
&self,
attestations: Vec<&PriceAttestation>,
current_time: u64,
) -> Option<AggregatedPrice> {
let mut weighted_prices = Vec::new();
for attestation in &attestations {
// Calculate weight based on:
// 1. Oracle quality score
// 2. Attestation confidence
// 3. Recency
let oracle_score = self.oracle_metrics
.get(&attestation.oracle_id)
.map(|m| m.quality_score)
.unwrap_or(0.5);
let confidence_weight = attestation.confidence as f64 / 100.0;
let age = current_time.saturating_sub(attestation.timestamp);
let recency_weight = 1.0 / (1.0 + (age as f64 / 300.0)); // 5-minute half-life
let total_weight = oracle_score * confidence_weight * recency_weight;
weighted_prices.push((attestation.price, total_weight));
}
// Remove outliers
let filtered_prices = self.remove_outliers(weighted_prices);
if filtered_prices.is_empty() {
return None;
}
// Calculate weighted average
let total_weight: f64 = filtered_prices.iter().map(|(_, w)| w).sum();
let weighted_sum: f64 = filtered_prices
.iter()
.map(|(price, weight)| *price as f64 * weight)
.sum();
let average_price = (weighted_sum / total_weight) as u128;
// Calculate confidence metrics
let prices: Vec<u128> = filtered_prices.iter().map(|(p, _)| *p).collect();
let std_dev = self.calculate_std_dev(&prices, average_price);
let confidence = self.calculate_confidence(&prices, std_dev, average_price);
Some(AggregatedPrice {
price: average_price,
confidence,
num_sources: filtered_prices.len(),
std_deviation: std_dev,
timestamp: current_time,
})
}
/// Remove statistical outliers from price data
fn remove_outliers(&self, mut prices: Vec<(u128, f64)>) -> Vec<(u128, f64)> {
if prices.len() < 3 {
return prices; // Not enough data to detect outliers
}
// Sort by price
prices.sort_by_key(|(price, _)| *price);
// Calculate IQR (Interquartile Range)
let q1_idx = prices.len() / 4;
let q3_idx = 3 * prices.len() / 4;
let q1 = prices[q1_idx].0;
let q3 = prices[q3_idx].0;
let iqr = q3.saturating_sub(q1);
// Filter out outliers (prices outside 1.5 * IQR)
let lower_bound = q1.saturating_sub(iqr * 3 / 2);
let upper_bound = q3.saturating_add(iqr * 3 / 2);
prices
.into_iter()
.filter(|(price, _)| *price >= lower_bound && *price <= upper_bound)
.collect()
}
/// Calculate standard deviation
fn calculate_std_dev(&self, prices: &[u128], mean: u128) -> u128 {
if prices.is_empty() {
return 0;
}
let variance: f64 = prices
.iter()
.map(|price| {
let diff = if *price > mean {
(*price - mean) as f64
} else {
(mean - *price) as f64
};
diff * diff
})
.sum::<f64>() / prices.len() as f64;
variance.sqrt() as u128
}
/// Calculate confidence score based on data quality
fn calculate_confidence(&self, prices: &[u128], std_dev: u128, mean: u128) -> u8 {
// Base confidence on:
// 1. Number of sources
// 2. Standard deviation relative to mean
// 3. Agreement between sources
let num_sources_score = (prices.len().min(10) * 10) as u8;
let deviation_ratio = if mean > 0 {
(std_dev as f64) / (mean as f64)
} else {
1.0
};
let deviation_score = if deviation_ratio < 0.01 {
100
} else if deviation_ratio < 0.05 {
80
} else if deviation_ratio < 0.10 {
60
} else {
40
};
(num_sources_score.min(100) + deviation_score) / 2
}
/// Update oracle performance metrics
fn update_oracle_metrics(&mut self, oracle_id: &OracleId) {
let attestations: Vec<_> = self.attestations
.values()
.filter(|a| &a.oracle_id == oracle_id)
.collect();
if attestations.is_empty() {
return;
}
let metrics = self.oracle_metrics
.entry(oracle_id.clone())
.or_insert(OracleMetrics {
oracle_id: oracle_id.clone(),
total_attestations: 0,
average_deviation: 0.0,
uptime_percentage: 100.0,
last_submission: 0,
quality_score: 0.5,
});
metrics.total_attestations = attestations.len() as u64;
metrics.last_submission = attestations
.iter()
.map(|a| a.timestamp)
.max()
.unwrap_or(0);
// Calculate quality score based on historical performance
// (simplified - real implementation would track accuracy over time)
metrics.quality_score = 0.5 + (metrics.total_attestations.min(100) as f64 / 200.0);
}
/// Report an anomaly in an attestation
pub fn report_anomaly(
&mut self,
attestation_id: AttestationId,
reporter: OracleId,
anomaly_type: AnomalyType,
evidence: String,
) -> Result<(), String> {
if !self.attestations.contains_key(&attestation_id) {
return Err("Attestation not found".to_string());
}
let report = AnomalyReport {
attestation_id: attestation_id.clone(),
report_type: anomaly_type,
reporter,
evidence,
timestamp: Self::timestamp(),
};
self.anomalies.insert(attestation_id, report);
Ok(())
}
/// Get oracle reputation score
pub fn get_oracle_reputation(&self, oracle_id: &OracleId) -> Option<OracleReputation> {
let metrics = self.oracle_metrics.get(oracle_id)?;
// Count anomalies reported against this oracle
let anomaly_count = self.anomalies
.values()
.filter(|report| {
self.attestations
.get(&report.attestation_id)
.map(|a| &a.oracle_id == oracle_id)
.unwrap_or(false)
})
.count();
Some(OracleReputation {
oracle_id: oracle_id.clone(),
quality_score: metrics.quality_score,
total_attestations: metrics.total_attestations,
anomaly_reports: anomaly_count as u64,
last_active: metrics.last_submission,
})
}
/// Merge another CRDT instance
pub fn merge(&mut self, other: &Self) {
// Merge attestations
for (id, attestation) in &other.attestations {
if !self.attestations.contains_key(id) {
let _ = self.submit_attestation(attestation.clone());
}
}
// Merge anomaly reports
for (id, report) in &other.anomalies {
self.anomalies.entry(id.clone()).or_insert(report.clone());
}
// Recalculate metrics after merge
for oracle_id in other.oracle_metrics.keys() {
self.update_oracle_metrics(oracle_id);
}
}
fn timestamp() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
}
}
/// Aggregated price result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AggregatedPrice {
pub price: u128,
pub confidence: u8,
pub num_sources: usize,
pub std_deviation: u128,
pub timestamp: u64,
}
/// Oracle reputation information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OracleReputation {
pub oracle_id: OracleId,
pub quality_score: f64,
pub total_attestations: u64,
pub anomaly_reports: u64,
pub last_active: u64,
}
/// Example oracle client implementation
pub struct OracleClient {
oracle_id: OracleId,
network: OracleNetworkCRDT,
data_sources: Vec<Box<dyn DataSourceClient>>,
}
trait DataSourceClient {
fn fetch_price(&self, asset_pair: &AssetPair) -> Result<DataSource, String>;
}
impl OracleClient {
pub async fn submit_price(&mut self, asset_pair: AssetPair) -> Result<(), String> {
// Fetch prices from multiple sources
let mut sources = Vec::new();
for client in &self.data_sources {
match client.fetch_price(&asset_pair) {
Ok(source) => sources.push(source),
Err(_) => continue, // Skip failed sources
}
}
if sources.len() < self.network.params.min_sources {
return Err("Insufficient data sources available".to_string());
}
// Calculate aggregate price from sources
let total_volume: u128 = sources.iter().map(|s| s.volume).sum();
let weighted_sum: u128 = sources
.iter()
.map(|s| s.price * s.volume)
.sum();
let price = weighted_sum / total_volume;
// Calculate confidence based on source agreement
let prices: Vec<u128> = sources.iter().map(|s| s.price).collect();
let std_dev = self.network.calculate_std_dev(&prices, price);
let confidence = if std_dev < price / 100 { 95 } else { 80 };
// Create attestation
let attestation = PriceAttestation {
id: AttestationId(format!("{}_{}", self.oracle_id.0, Self::timestamp())),
oracle_id: self.oracle_id.clone(),
asset_pair,
price,
confidence,
timestamp: Self::timestamp(),
sources,
proof: AttestationProof::SignedFeed {
exchange_signature: vec![1, 2, 3], // Placeholder
sequence_number: Self::timestamp(),
},
signature: vec![4, 5, 6], // Placeholder
};
// Submit to network
self.network.submit_attestation(attestation)
}
fn timestamp() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_oracle_network() {
let params = NetworkParams {
min_oracle_stake: 1000,
max_price_age: Duration::from_secs(3600),
outlier_threshold: 0.1,
min_sources: 2,
};
let mut network = OracleNetworkCRDT::new(params);
// Create test oracles
let oracle1 = OracleId("oracle1".to_string());
let oracle2 = OracleId("oracle2".to_string());
let oracle3 = OracleId("oracle3".to_string());
let eth_usd = AssetPair("ETH/USD".to_string());
// Submit prices from multiple oracles
let attestation1 = PriceAttestation {
id: AttestationId("att1".to_string()),
oracle_id: oracle1.clone(),
asset_pair: eth_usd.clone(),
price: 2500_000_000, // $2500 with 6 decimals
confidence: 95,
timestamp: OracleNetworkCRDT::timestamp(),
sources: vec![
DataSource {
name: "Binance".to_string(),
price: 2501_000_000,
volume: 1000_000_000,
timestamp: OracleNetworkCRDT::timestamp(),
},
DataSource {
name: "Coinbase".to_string(),
price: 2499_000_000,
volume: 800_000_000,
timestamp: OracleNetworkCRDT::timestamp(),
},
],
proof: AttestationProof::SignedFeed {
exchange_signature: vec![1, 2, 3],
sequence_number: 1,
},
signature: vec![4, 5, 6],
};
network.submit_attestation(attestation1).unwrap();
// Submit from oracle 2
let attestation2 = PriceAttestation {
id: AttestationId("att2".to_string()),
oracle_id: oracle2.clone(),
asset_pair: eth_usd.clone(),
price: 2502_000_000,
confidence: 90,
timestamp: OracleNetworkCRDT::timestamp(),
sources: vec![
DataSource {
name: "Kraken".to_string(),
price: 2502_000_000,
volume: 500_000_000,
timestamp: OracleNetworkCRDT::timestamp(),
},
DataSource {
name: "Gemini".to_string(),
price: 2502_000_000,
volume: 300_000_000,
timestamp: OracleNetworkCRDT::timestamp(),
},
],
proof: AttestationProof::SignedFeed {
exchange_signature: vec![7, 8, 9],
sequence_number: 2,
},
signature: vec![10, 11, 12],
};
network.submit_attestation(attestation2).unwrap();
// Submit outlier price from oracle 3
let attestation3 = PriceAttestation {
id: AttestationId("att3".to_string()),
oracle_id: oracle3.clone(),
asset_pair: eth_usd.clone(),
price: 3000_000_000, // Outlier price
confidence: 50,
timestamp: OracleNetworkCRDT::timestamp(),
sources: vec![
DataSource {
name: "Unknown".to_string(),
price: 3000_000_000,
volume: 100_000_000,
timestamp: OracleNetworkCRDT::timestamp(),
},
DataSource {
name: "Sketchy".to_string(),
price: 3000_000_000,
volume: 50_000_000,
timestamp: OracleNetworkCRDT::timestamp(),
},
],
proof: AttestationProof::SignedFeed {
exchange_signature: vec![13, 14, 15],
sequence_number: 3,
},
signature: vec![16, 17, 18],
};
network.submit_attestation(attestation3).unwrap();
// Get aggregated price
let aggregated = network
.get_aggregate_price(&eth_usd, Duration::from_secs(300))
.unwrap();
// Should filter out the outlier
assert!(aggregated.price > 2490_000_000 && aggregated.price < 2510_000_000);
assert!(aggregated.confidence > 80);
assert_eq!(aggregated.num_sources, 2); // Outlier filtered out
// Report anomaly
network
.report_anomaly(
AttestationId("att3".to_string()),
oracle1.clone(),
AnomalyType::OutlierPrice {
deviation_percentage: 20.0,
},
"Price deviates significantly from market".to_string(),
)
.unwrap();
// Check oracle reputation
let reputation = network.get_oracle_reputation(&oracle3).unwrap();
assert_eq!(reputation.anomaly_reports, 1);
}
#[test]
fn test_crdt_merge() {
let params = NetworkParams {
min_oracle_stake: 1000,
max_price_age: Duration::from_secs(3600),
outlier_threshold: 0.1,
min_sources: 2,
};
let mut network1 = OracleNetworkCRDT::new(params.clone());
let mut network2 = OracleNetworkCRDT::new(params);
let oracle1 = OracleId("oracle1".to_string());
let btc_usd = AssetPair("BTC/USD".to_string());
// Submit to network1
let attestation1 = PriceAttestation {
id: AttestationId("att1".to_string()),
oracle_id: oracle1.clone(),
asset_pair: btc_usd.clone(),
price: 45000_000_000,
confidence: 95,
timestamp: OracleNetworkCRDT::timestamp(),
sources: vec![
DataSource {
name: "Binance".to_string(),
price: 45000_000_000,
volume: 2000_000_000,
timestamp: OracleNetworkCRDT::timestamp(),
},
DataSource {
name: "Coinbase".to_string(),
price: 45000_000_000,
volume: 1500_000_000,
timestamp: OracleNetworkCRDT::timestamp(),
},
],
proof: AttestationProof::OnChainProof {
chain_id: "ethereum".to_string(),
block_number: 18000000,
transaction_hash: vec![1, 2, 3],
},
signature: vec![4, 5, 6],
};
network1.submit_attestation(attestation1).unwrap();
// Submit different attestation to network2
let attestation2 = PriceAttestation {
id: AttestationId("att2".to_string()),
oracle_id: oracle1.clone(),
asset_pair: btc_usd.clone(),
price: 45100_000_000,
confidence: 90,
timestamp: OracleNetworkCRDT::timestamp() + 1,
sources: vec![
DataSource {
name: "Kraken".to_string(),
price: 45100_000_000,
volume: 1000_000_000,
timestamp: OracleNetworkCRDT::timestamp(),
},
DataSource {
name: "Gemini".to_string(),
price: 45100_000_000,
volume: 800_000_000,
timestamp: OracleNetworkCRDT::timestamp(),
},
],
proof: AttestationProof::SignedFeed {
exchange_signature: vec![7, 8, 9],
sequence_number: 100,
},
signature: vec![10, 11, 12],
};
network2.submit_attestation(attestation2).unwrap();
// Merge networks
network1.merge(&network2);
network2.merge(&network1);
// Both should have same data
assert_eq!(network1.attestations.len(), 2);
assert_eq!(network2.attestations.len(), 2);
// Both should calculate same aggregate price
let price1 = network1
.get_aggregate_price(&btc_usd, Duration::from_secs(300))
.unwrap();
let price2 = network2
.get_aggregate_price(&btc_usd, Duration::from_secs(300))
.unwrap();
assert_eq!(price1.price, price2.price);
}
}

View File

@@ -0,0 +1,559 @@
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
// Import our oracle network types
use crate::oracle_network::*;
/// A practical simulation showing how the BFT-CRDT oracle network operates
/// This demonstrates:
/// 1. Multiple oracles submitting prices independently
/// 2. Network partitions and reunification
/// 3. Byzantine oracle behavior
/// 4. Real-time price aggregation
/// 5. Performance under various conditions
pub struct OracleSimulation {
/// Multiple oracle nodes in the network
oracle_nodes: HashMap<String, Arc<Mutex<OracleNode>>>,
/// Network conditions for simulation
network_conditions: NetworkConditions,
/// Statistics collector
stats: SimulationStats,
}
pub struct OracleNode {
pub id: OracleId,
pub crdt: OracleNetworkCRDT,
pub data_sources: Vec<MockDataSource>,
pub is_byzantine: bool,
pub partition_group: Option<u8>,
}
pub struct NetworkConditions {
pub latency_ms: u64,
pub packet_loss_rate: f64,
pub partition_active: bool,
pub partition_groups: Vec<Vec<String>>,
}
pub struct SimulationStats {
pub total_attestations: u64,
pub successful_aggregations: u64,
pub byzantine_attempts: u64,
pub network_merges: u64,
pub average_price_deviation: f64,
}
pub struct MockDataSource {
pub name: String,
pub base_price: u128,
pub volatility: f64,
pub reliability: f64,
}
impl OracleSimulation {
pub fn new() -> Self {
let params = NetworkParams {
min_oracle_stake: 1000,
max_price_age: Duration::from_secs(300),
outlier_threshold: 0.15,
min_sources: 2,
};
let mut oracle_nodes = HashMap::new();
// Create honest oracles
for i in 1..=5 {
let oracle_id = OracleId(format!("oracle_{}", i));
let node = OracleNode {
id: oracle_id.clone(),
crdt: OracleNetworkCRDT::new(params.clone()),
data_sources: Self::create_data_sources(),
is_byzantine: false,
partition_group: None,
};
oracle_nodes.insert(format!("oracle_{}", i), Arc::new(Mutex::new(node)));
}
// Create Byzantine oracles
for i in 6..=7 {
let oracle_id = OracleId(format!("byzantine_{}", i));
let node = OracleNode {
id: oracle_id.clone(),
crdt: OracleNetworkCRDT::new(params.clone()),
data_sources: Self::create_data_sources(),
is_byzantine: true,
partition_group: None,
};
oracle_nodes.insert(format!("byzantine_{}", i), Arc::new(Mutex::new(node)));
}
Self {
oracle_nodes,
network_conditions: NetworkConditions {
latency_ms: 50,
packet_loss_rate: 0.01,
partition_active: false,
partition_groups: vec![],
},
stats: SimulationStats {
total_attestations: 0,
successful_aggregations: 0,
byzantine_attempts: 0,
network_merges: 0,
average_price_deviation: 0.0,
},
}
}
fn create_data_sources() -> Vec<MockDataSource> {
vec![
MockDataSource {
name: "Binance".to_string(),
base_price: 2500_000_000,
volatility: 0.02,
reliability: 0.99,
},
MockDataSource {
name: "Coinbase".to_string(),
base_price: 2501_000_000,
volatility: 0.02,
reliability: 0.98,
},
MockDataSource {
name: "Kraken".to_string(),
base_price: 2499_000_000,
volatility: 0.025,
reliability: 0.97,
},
]
}
/// Run the simulation for a specified duration
pub fn run(&mut self, duration: Duration) {
println!("Starting Oracle Network Simulation");
println!("==================================");
println!("Nodes: {} ({} Byzantine)", self.oracle_nodes.len(), 2);
println!("Duration: {:?}", duration);
println!();
let start_time = Instant::now();
let mut last_stats_print = Instant::now();
// Spawn oracle threads
let handles: Vec<_> = self
.oracle_nodes
.iter()
.map(|(name, node)| {
let node_clone = Arc::clone(node);
let name_clone = name.clone();
let duration_clone = duration;
thread::spawn(move || {
Self::oracle_thread(name_clone, node_clone, duration_clone);
})
})
.collect();
// Main simulation loop
while start_time.elapsed() < duration {
thread::sleep(Duration::from_millis(100));
// Simulate network propagation
self.propagate_attestations();
// Simulate network partition if active
if self.network_conditions.partition_active {
self.simulate_partition();
}
// Print statistics every 5 seconds
if last_stats_print.elapsed() > Duration::from_secs(5) {
self.print_current_state();
last_stats_print = Instant::now();
}
// Randomly introduce network events
if rand::random::<f64>() < 0.1 {
self.introduce_network_event();
}
}
// Wait for oracle threads to complete
for handle in handles {
handle.join().unwrap();
}
self.print_final_statistics();
}
/// Oracle thread that submits prices periodically
fn oracle_thread(name: String, node: Arc<Mutex<OracleNode>>, duration: Duration) {
let start = Instant::now();
let mut last_submission = Instant::now();
while start.elapsed() < duration {
if last_submission.elapsed() > Duration::from_secs(1) {
let mut node_guard = node.lock().unwrap();
// Fetch prices from data sources
let mut sources = Vec::new();
for data_source in &node_guard.data_sources {
if rand::random::<f64>() < data_source.reliability {
let price = if node_guard.is_byzantine {
// Byzantine oracles submit manipulated prices
Self::generate_byzantine_price(data_source.base_price)
} else {
Self::generate_realistic_price(
data_source.base_price,
data_source.volatility,
)
};
sources.push(DataSource {
name: data_source.name.clone(),
price,
volume: (rand::random::<f64>() * 1000_000_000.0) as u128,
timestamp: Self::timestamp(),
});
}
}
if sources.len() >= 2 {
// Create attestation
let attestation = PriceAttestation {
id: AttestationId(format!("{}_{}", name, Self::timestamp())),
oracle_id: node_guard.id.clone(),
asset_pair: AssetPair("ETH/USD".to_string()),
price: Self::calculate_weighted_price(&sources),
confidence: if node_guard.is_byzantine {
50
} else {
90 + (rand::random::<f64>() * 10.0) as u8
},
timestamp: Self::timestamp(),
sources,
proof: AttestationProof::SignedFeed {
exchange_signature: vec![1, 2, 3],
sequence_number: Self::timestamp(),
},
signature: vec![4, 5, 6],
};
if let Err(e) = node_guard.crdt.submit_attestation(attestation) {
eprintln!("Failed to submit attestation from {}: {}", name, e);
}
}
last_submission = Instant::now();
}
thread::sleep(Duration::from_millis(100));
}
}
/// Propagate attestations between nodes based on network conditions
fn propagate_attestations(&mut self) {
let nodes_snapshot: Vec<_> = self.oracle_nodes.keys().cloned().collect();
for i in 0..nodes_snapshot.len() {
for j in (i + 1)..nodes_snapshot.len() {
let node1_name = &nodes_snapshot[i];
let node2_name = &nodes_snapshot[j];
// Skip if nodes are in different partition groups
if self.network_conditions.partition_active {
if !self.can_communicate(node1_name, node2_name) {
continue;
}
}
// Simulate packet loss
if rand::random::<f64>() < self.network_conditions.packet_loss_rate {
continue;
}
// Get nodes
let node1 = Arc::clone(&self.oracle_nodes[node1_name]);
let node2 = Arc::clone(&self.oracle_nodes[node2_name]);
// Merge CRDTs with simulated latency
thread::spawn(move || {
thread::sleep(Duration::from_millis(50)); // Simulated network latency
let mut node1_guard = node1.lock().unwrap();
let mut node2_guard = node2.lock().unwrap();
// Bidirectional merge
node1_guard.crdt.merge(&node2_guard.crdt);
node2_guard.crdt.merge(&node1_guard.crdt);
});
self.stats.network_merges += 1;
}
}
}
/// Check if two nodes can communicate (for partition simulation)
fn can_communicate(&self, node1: &str, node2: &str) -> bool {
if !self.network_conditions.partition_active {
return true;
}
for group in &self.network_conditions.partition_groups {
let node1_in_group = group.contains(&node1.to_string());
let node2_in_group = group.contains(&node2.to_string());
if node1_in_group && node2_in_group {
return true;
}
}
false
}
/// Introduce random network events
fn introduce_network_event(&mut self) {
let event = rand::random::<f64>();
if event < 0.3 {
// Increase latency
self.network_conditions.latency_ms = 200;
println!("Network event: High latency (200ms)");
} else if event < 0.5 {
// Network partition
self.network_conditions.partition_active = true;
self.network_conditions.partition_groups = vec![
vec![
"oracle_1".to_string(),
"oracle_2".to_string(),
"oracle_3".to_string(),
],
vec![
"oracle_4".to_string(),
"oracle_5".to_string(),
"byzantine_6".to_string(),
"byzantine_7".to_string(),
],
];
println!("Network event: Partition active");
} else if event < 0.7 {
// Heal partition
if self.network_conditions.partition_active {
self.network_conditions.partition_active = false;
println!("Network event: Partition healed");
}
} else {
// Restore normal conditions
self.network_conditions.latency_ms = 50;
self.network_conditions.packet_loss_rate = 0.01;
println!("Network event: Normal conditions restored");
}
}
/// Simulate network partition effects
fn simulate_partition(&mut self) {
// Partitions are handled in propagate_attestations
// This method could add additional partition-specific logic
}
/// Print current state of the network
fn print_current_state(&self) {
println!("\n--- Current Network State ---");
// Get aggregate price from first available node
let mut aggregate_price = None;
for (name, node) in &self.oracle_nodes {
let node_guard = node.lock().unwrap();
if let Some(price) = node_guard
.crdt
.get_aggregate_price(&AssetPair("ETH/USD".to_string()), Duration::from_secs(60))
{
aggregate_price = Some(price);
break;
}
}
if let Some(price) = aggregate_price {
println!(
"Aggregate ETH/USD Price: ${:.2} (confidence: {}%)",
price.price as f64 / 1_000_000.0,
price.confidence
);
println!(
"Sources: {}, Std Dev: ${:.2}",
price.num_sources,
price.std_deviation as f64 / 1_000_000.0
);
}
// Show individual node states
println!("\nNode Attestation Counts:");
for (name, node) in &self.oracle_nodes {
let node_guard = node.lock().unwrap();
let count = node_guard.crdt.attestations.len();
println!(" {}: {} attestations", name, count);
}
// Network conditions
println!("\nNetwork Conditions:");
println!(" Latency: {}ms", self.network_conditions.latency_ms);
println!(
" Packet Loss: {:.1}%",
self.network_conditions.packet_loss_rate * 100.0
);
println!(
" Partition Active: {}",
self.network_conditions.partition_active
);
}
/// Print final simulation statistics
fn print_final_statistics(&self) {
println!("\n\n=== Final Simulation Statistics ===");
// Count total attestations across all nodes
let mut total_attestations = 0;
let mut price_samples = Vec::new();
for (_, node) in &self.oracle_nodes {
let node_guard = node.lock().unwrap();
total_attestations += node_guard.crdt.attestations.len();
// Collect price samples
if let Some(price) = node_guard
.crdt
.get_aggregate_price(&AssetPair("ETH/USD".to_string()), Duration::from_secs(300))
{
price_samples.push(price.price);
}
}
println!("Total Attestations: {}", total_attestations);
println!("Network Merges: {}", self.stats.network_merges);
// Calculate price consistency
if !price_samples.is_empty() {
let avg_price: u128 = price_samples.iter().sum::<u128>() / price_samples.len() as u128;
let max_deviation = price_samples
.iter()
.map(|p| {
if *p > avg_price {
((*p - avg_price) as f64 / avg_price as f64) * 100.0
} else {
((avg_price - *p) as f64 / avg_price as f64) * 100.0
}
})
.fold(0.0, f64::max);
println!("\nPrice Consistency:");
println!(" Average Price: ${:.2}", avg_price as f64 / 1_000_000.0);
println!(" Max Deviation: {:.2}%", max_deviation);
println!(
" Nodes in Agreement: {}/{}",
price_samples.len(),
self.oracle_nodes.len()
);
}
// Performance metrics
let attestation_rate = total_attestations as f64 / 300.0; // Assuming 5 minute simulation
println!("\nPerformance:");
println!(" Attestation Rate: {:.1} per second", attestation_rate);
println!(
" Merge Rate: {:.1} per second",
self.stats.network_merges as f64 / 300.0
);
}
/// Generate realistic price with volatility
fn generate_realistic_price(base_price: u128, volatility: f64) -> u128 {
let change = (rand::random::<f64>() - 0.5) * 2.0 * volatility;
let multiplier = 1.0 + change;
(base_price as f64 * multiplier) as u128
}
/// Generate manipulated price for Byzantine oracles
fn generate_byzantine_price(base_price: u128) -> u128 {
let manipulation = rand::random::<f64>();
if manipulation < 0.3 {
// Try subtle manipulation
(base_price as f64 * 1.05) as u128
} else if manipulation < 0.6 {
// Try larger manipulation
(base_price as f64 * 1.20) as u128
} else {
// Sometimes submit normal price to avoid detection
base_price
}
}
/// Calculate weighted average price from sources
fn calculate_weighted_price(sources: &[DataSource]) -> u128 {
let total_volume: u128 = sources.iter().map(|s| s.volume).sum();
let weighted_sum: u128 = sources.iter().map(|s| s.price * s.volume).sum();
weighted_sum / total_volume
}
fn timestamp() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
}
}
/// Example usage
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_oracle_simulation() {
let mut simulation = OracleSimulation::new();
// Run for 30 seconds
simulation.run(Duration::from_secs(30));
// Verify network reached consistency
assert!(simulation.stats.network_merges > 0);
}
#[test]
fn test_byzantine_resistance() {
let mut simulation = OracleSimulation::new();
// Run simulation
simulation.run(Duration::from_secs(10));
// Check that Byzantine oracles didn't corrupt the network
// The aggregate price should still be reasonable despite Byzantine attempts
for (_, node) in &simulation.oracle_nodes {
let node_guard = node.lock().unwrap();
if let Some(price) = node_guard
.crdt
.get_aggregate_price(&AssetPair("ETH/USD".to_string()), Duration::from_secs(60))
{
// Price should be within reasonable bounds despite Byzantine oracles
assert!(price.price > 2000_000_000 && price.price < 3000_000_000);
}
}
}
}
// Mock rand module for simulation
mod rand {
pub fn random<T>() -> T
where
T: From<f64>,
{
// Simple pseudo-random for simulation
let time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
T::from((time % 1000) as f64 / 1000.0)
}
}

537
examples/orderbook_crdt.rs Normal file
View File

@@ -0,0 +1,537 @@
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::collections::{BTreeMap, HashMap, HashSet};
/// Unique identifier for an order
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct OrderId(pub String);
/// Public key representing a trader
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct TraderId(pub String);
/// Side of the order (buy or sell)
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub enum Side {
Buy,
Sell,
}
/// Status of an order
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub enum OrderStatus {
Open,
PartiallyFilled,
Filled,
Cancelled,
}
/// An order in the order book
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Order {
pub id: OrderId,
pub trader: TraderId,
pub side: Side,
pub price: u128,
pub original_amount: u128,
pub remaining_amount: u128,
pub timestamp: u64,
pub status: OrderStatus,
pub signature: Vec<u8>, // Signature from the trader
}
/// A trade execution
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Execution {
pub id: String,
pub buy_order_id: OrderId,
pub sell_order_id: OrderId,
pub price: u128,
pub amount: u128,
pub timestamp: u64,
pub executor: TraderId, // Who executed the trade (could be either party)
}
/// A cancellation request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Cancellation {
pub order_id: OrderId,
pub timestamp: u64,
pub signature: Vec<u8>, // Must be signed by original trader
}
/// CRDT-based decentralized order book
#[derive(Debug, Clone)]
pub struct OrderBookCRDT {
/// All orders ever placed
orders: HashMap<OrderId, Order>,
/// All executions
executions: HashMap<String, Execution>,
/// Cancellation tombstones
cancellations: HashMap<OrderId, Cancellation>,
/// Index for fast price-level lookups
buy_levels: BTreeMap<u128, HashSet<OrderId>>,
sell_levels: BTreeMap<u128, HashSet<OrderId>>,
}
impl OrderBookCRDT {
pub fn new() -> Self {
Self {
orders: HashMap::new(),
executions: HashMap::new(),
cancellations: HashMap::new(),
buy_levels: BTreeMap::new(),
sell_levels: BTreeMap::new(),
}
}
/// Add a new order to the book
pub fn add_order(&mut self, order: Order) -> Result<(), String> {
// Verify signature (simplified - real implementation would verify cryptographically)
if order.signature.is_empty() {
return Err("Order must be signed".to_string());
}
// Check if order was previously cancelled
if self.cancellations.contains_key(&order.id) {
return Err("Order was previously cancelled".to_string());
}
// Add to orders map
let order_id = order.id.clone();
let price = order.price;
let side = order.side;
self.orders.insert(order_id.clone(), order);
// Update price level index
match side {
Side::Buy => {
self.buy_levels
.entry(price)
.or_insert_with(HashSet::new)
.insert(order_id);
}
Side::Sell => {
self.sell_levels
.entry(price)
.or_insert_with(HashSet::new)
.insert(order_id);
}
}
Ok(())
}
/// Execute a trade between two orders
pub fn execute_trade(&mut self, execution: Execution) -> Result<(), String> {
// Verify both orders exist and are not cancelled
let buy_order = self
.orders
.get(&execution.buy_order_id)
.ok_or("Buy order not found")?;
let sell_order = self
.orders
.get(&execution.sell_order_id)
.ok_or("Sell order not found")?;
if self.cancellations.contains_key(&execution.buy_order_id) {
return Err("Buy order is cancelled".to_string());
}
if self.cancellations.contains_key(&execution.sell_order_id) {
return Err("Sell order is cancelled".to_string());
}
// Verify price matching
if buy_order.price < sell_order.price {
return Err("Price mismatch - buy price less than sell price".to_string());
}
// Check for duplicate execution
if self.executions.contains_key(&execution.id) {
return Ok(()); // Idempotent - already processed
}
// Add execution
self.executions
.insert(execution.id.clone(), execution.clone());
// Update order states
self.update_order_after_execution(&execution.buy_order_id, execution.amount);
self.update_order_after_execution(&execution.sell_order_id, execution.amount);
Ok(())
}
/// Cancel an order
pub fn cancel_order(&mut self, cancellation: Cancellation) -> Result<(), String> {
// Verify the order exists
let order = self
.orders
.get(&cancellation.order_id)
.ok_or("Order not found")?;
// Verify signature matches order owner (simplified)
if cancellation.signature.is_empty() {
return Err("Cancellation must be signed".to_string());
}
// Add cancellation tombstone
self.cancellations
.insert(cancellation.order_id.clone(), cancellation);
// Remove from price level indices
if let Some(order) = self.orders.get_mut(&cancellation.order_id) {
order.status = OrderStatus::Cancelled;
match order.side {
Side::Buy => {
if let Some(level) = self.buy_levels.get_mut(&order.price) {
level.remove(&order.id);
}
}
Side::Sell => {
if let Some(level) = self.sell_levels.get_mut(&order.price) {
level.remove(&order.id);
}
}
}
}
Ok(())
}
/// Merge another OrderBookCRDT into this one
pub fn merge(&mut self, other: &OrderBookCRDT) {
// Merge orders
for (id, order) in &other.orders {
if !self.orders.contains_key(id) {
let _ = self.add_order(order.clone());
}
}
// Merge executions
for (id, execution) in &other.executions {
if !self.executions.contains_key(id) {
let _ = self.execute_trade(execution.clone());
}
}
// Merge cancellations (cancellations always win)
for (id, cancellation) in &other.cancellations {
if !self.cancellations.contains_key(id) {
let _ = self.cancel_order(cancellation.clone());
}
}
}
/// Get the current order book view
pub fn get_book_view(&self) -> OrderBookView {
let mut bids = Vec::new();
let mut asks = Vec::new();
// Collect buy orders (descending price)
for (price, order_ids) in self.buy_levels.iter().rev() {
let mut level_amount = 0u128;
for order_id in order_ids {
if let Some(order) = self.orders.get(order_id) {
if order.status == OrderStatus::Open
|| order.status == OrderStatus::PartiallyFilled
{
level_amount += self.get_remaining_amount(order_id);
}
}
}
if level_amount > 0 {
bids.push(PriceLevel {
price: *price,
amount: level_amount,
});
}
}
// Collect sell orders (ascending price)
for (price, order_ids) in &self.sell_levels {
let mut level_amount = 0u128;
for order_id in order_ids {
if let Some(order) = self.orders.get(order_id) {
if order.status == OrderStatus::Open
|| order.status == OrderStatus::PartiallyFilled
{
level_amount += self.get_remaining_amount(order_id);
}
}
}
if level_amount > 0 {
asks.push(PriceLevel {
price: *price,
amount: level_amount,
});
}
}
OrderBookView { bids, asks }
}
/// Get remaining amount for an order after all executions
fn get_remaining_amount(&self, order_id: &OrderId) -> u128 {
let order = match self.orders.get(order_id) {
Some(o) => o,
None => return 0,
};
let mut filled_amount = 0u128;
for execution in self.executions.values() {
if &execution.buy_order_id == order_id || &execution.sell_order_id == order_id {
filled_amount += execution.amount;
}
}
order.original_amount.saturating_sub(filled_amount)
}
/// Update order status after execution
fn update_order_after_execution(&mut self, order_id: &OrderId, amount: u128) {
if let Some(order) = self.orders.get_mut(order_id) {
let remaining = self.get_remaining_amount(order_id);
order.remaining_amount = remaining;
if remaining == 0 {
order.status = OrderStatus::Filled;
// Remove from price level index
match order.side {
Side::Buy => {
if let Some(level) = self.buy_levels.get_mut(&order.price) {
level.remove(order_id);
}
}
Side::Sell => {
if let Some(level) = self.sell_levels.get_mut(&order.price) {
level.remove(order_id);
}
}
}
} else if remaining < order.original_amount {
order.status = OrderStatus::PartiallyFilled;
}
}
}
/// Find matching orders for a new order (for market making)
pub fn find_matches(&self, order: &Order) -> Vec<&Order> {
let mut matches = Vec::new();
match order.side {
Side::Buy => {
// Look for sell orders at or below buy price
for (price, order_ids) in &self.sell_levels {
if *price > order.price {
break; // Prices too high
}
for order_id in order_ids {
if let Some(sell_order) = self.orders.get(order_id) {
if !self.cancellations.contains_key(order_id)
&& self.get_remaining_amount(order_id) > 0
{
matches.push(sell_order);
}
}
}
}
}
Side::Sell => {
// Look for buy orders at or above sell price
for (price, order_ids) in self.buy_levels.iter().rev() {
if *price < order.price {
break; // Prices too low
}
for order_id in order_ids {
if let Some(buy_order) = self.orders.get(order_id) {
if !self.cancellations.contains_key(order_id)
&& self.get_remaining_amount(order_id) > 0
{
matches.push(buy_order);
}
}
}
}
}
}
matches
}
}
/// A view of the order book at a point in time
#[derive(Debug, Clone)]
pub struct OrderBookView {
pub bids: Vec<PriceLevel>,
pub asks: Vec<PriceLevel>,
}
#[derive(Debug, Clone)]
pub struct PriceLevel {
pub price: u128,
pub amount: u128,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_order_book_operations() {
let mut book1 = OrderBookCRDT::new();
let mut book2 = OrderBookCRDT::new();
// Add buy order to book1
let buy_order = Order {
id: OrderId("buy1".to_string()),
trader: TraderId("alice".to_string()),
side: Side::Buy,
price: 100,
original_amount: 50,
remaining_amount: 50,
timestamp: 1000,
status: OrderStatus::Open,
signature: vec![1, 2, 3],
};
book1.add_order(buy_order).unwrap();
// Add sell order to book2
let sell_order = Order {
id: OrderId("sell1".to_string()),
trader: TraderId("bob".to_string()),
side: Side::Sell,
price: 95,
original_amount: 30,
remaining_amount: 30,
timestamp: 1001,
status: OrderStatus::Open,
signature: vec![4, 5, 6],
};
book2.add_order(sell_order).unwrap();
// Merge books
book1.merge(&book2);
book2.merge(&book1);
// Both books should have both orders
assert_eq!(book1.orders.len(), 2);
assert_eq!(book2.orders.len(), 2);
// Execute trade on book1
let execution = Execution {
id: "exec1".to_string(),
buy_order_id: OrderId("buy1".to_string()),
sell_order_id: OrderId("sell1".to_string()),
price: 95,
amount: 30,
timestamp: 1002,
executor: TraderId("alice".to_string()),
};
book1.execute_trade(execution).unwrap();
// Merge again
book2.merge(&book1);
// Both should have the execution
assert_eq!(book1.executions.len(), 1);
assert_eq!(book2.executions.len(), 1);
// Check remaining amounts
assert_eq!(book1.get_remaining_amount(&OrderId("buy1".to_string())), 20);
assert_eq!(book1.get_remaining_amount(&OrderId("sell1".to_string())), 0);
}
#[test]
fn test_order_cancellation() {
let mut book = OrderBookCRDT::new();
// Add order
let order = Order {
id: OrderId("order1".to_string()),
trader: TraderId("alice".to_string()),
side: Side::Buy,
price: 100,
original_amount: 50,
remaining_amount: 50,
timestamp: 1000,
status: OrderStatus::Open,
signature: vec![1, 2, 3],
};
book.add_order(order).unwrap();
// Cancel order
let cancellation = Cancellation {
order_id: OrderId("order1".to_string()),
timestamp: 1001,
signature: vec![1, 2, 3],
};
book.cancel_order(cancellation).unwrap();
// Verify order is cancelled
let order = book.orders.get(&OrderId("order1".to_string())).unwrap();
assert_eq!(order.status, OrderStatus::Cancelled);
// Try to execute against cancelled order
let execution = Execution {
id: "exec1".to_string(),
buy_order_id: OrderId("order1".to_string()),
sell_order_id: OrderId("sell1".to_string()),
price: 100,
amount: 10,
timestamp: 1002,
executor: TraderId("bob".to_string()),
};
assert!(book.execute_trade(execution).is_err());
}
#[test]
fn test_order_book_view() {
let mut book = OrderBookCRDT::new();
// Add multiple orders at different price levels
for i in 0..3 {
let buy_order = Order {
id: OrderId(format!("buy{}", i)),
trader: TraderId("alice".to_string()),
side: Side::Buy,
price: 100 - i as u128,
original_amount: 10 * (i + 1) as u128,
remaining_amount: 10 * (i + 1) as u128,
timestamp: 1000 + i,
status: OrderStatus::Open,
signature: vec![1, 2, 3],
};
book.add_order(buy_order).unwrap();
let sell_order = Order {
id: OrderId(format!("sell{}", i)),
trader: TraderId("bob".to_string()),
side: Side::Sell,
price: 105 + i as u128,
original_amount: 10 * (i + 1) as u128,
remaining_amount: 10 * (i + 1) as u128,
timestamp: 2000 + i,
status: OrderStatus::Open,
signature: vec![4, 5, 6],
};
book.add_order(sell_order).unwrap();
}
let view = book.get_book_view();
// Check bid side (should be sorted descending)
assert_eq!(view.bids.len(), 3);
assert_eq!(view.bids[0].price, 100);
assert_eq!(view.bids[1].price, 99);
assert_eq!(view.bids[2].price, 98);
// Check ask side (should be sorted ascending)
assert_eq!(view.asks.len(), 3);
assert_eq!(view.asks[0].price, 105);
assert_eq!(view.asks[1].price, 106);
assert_eq!(view.asks[2].price, 107);
}
}

429
examples/run_oracle_demo.rs Normal file
View File

@@ -0,0 +1,429 @@
use std::collections::{BTreeMap, HashMap, HashSet};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
/// A runnable demonstration of the BFT-CRDT Oracle Network
///
/// This example shows:
/// 1. Multiple oracle nodes submitting prices independently
/// 2. Byzantine oracles trying to manipulate prices
/// 3. Network partitions and healing
/// 4. Real-time price aggregation without consensus
///
/// Run with: cargo run --example run_oracle_demo
// ============ Core Types ============
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
struct OracleId(String);
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
struct AssetPair(String);
#[derive(Debug, Clone)]
struct PriceAttestation {
id: String,
oracle_id: OracleId,
asset_pair: AssetPair,
price: u128,
confidence: u8,
timestamp: u64,
sources: Vec<DataSource>,
}
#[derive(Debug, Clone)]
struct DataSource {
name: String,
price: u128,
volume: u128,
}
// ============ Simple CRDT Implementation ============
#[derive(Clone)]
struct OracleNetworkCRDT {
attestations: HashMap<String, PriceAttestation>,
oracle_scores: HashMap<OracleId, f64>,
}
impl OracleNetworkCRDT {
fn new() -> Self {
Self {
attestations: HashMap::new(),
oracle_scores: HashMap::new(),
}
}
fn submit_attestation(&mut self, attestation: PriceAttestation) {
self.attestations
.insert(attestation.id.clone(), attestation.clone());
// Update oracle score
let score = self
.oracle_scores
.entry(attestation.oracle_id.clone())
.or_insert(0.5);
*score = (*score * 0.95) + 0.05; // Simple reputation update
}
fn merge(&mut self, other: &Self) {
// Merge attestations
for (id, attestation) in &other.attestations {
if !self.attestations.contains_key(id) {
self.attestations.insert(id.clone(), attestation.clone());
}
}
// Merge oracle scores
for (oracle_id, score) in &other.oracle_scores {
self.oracle_scores.insert(oracle_id.clone(), *score);
}
}
fn get_aggregate_price(
&self,
asset_pair: &AssetPair,
max_age: u64,
) -> Option<(u128, u8, usize)> {
let now = 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 using simple IQR method
prices.sort_by_key(|(price, _, _)| *price);
let q1_idx = prices.len() / 4;
let q3_idx = 3 * prices.len() / 4;
if prices.len() > 4 {
let q1 = prices[q1_idx].0;
let q3 = prices[q3_idx].0;
let iqr = q3.saturating_sub(q1);
let lower = q1.saturating_sub(iqr * 3 / 2);
let upper = q3.saturating_add(iqr * 3 / 2);
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 as f64 * w;
confidence_sum += *confidence as f64 * w;
total_weight += w;
}
let avg_price = (weighted_sum / total_weight) as u128;
let avg_confidence = (confidence_sum / total_weight) as u8;
Some((avg_price, avg_confidence, prices.len()))
}
}
// ============ Oracle Node ============
struct OracleNode {
id: OracleId,
crdt: Arc<Mutex<OracleNetworkCRDT>>,
is_byzantine: bool,
base_price: u128,
}
impl OracleNode {
fn new(id: String, is_byzantine: bool) -> Self {
Self {
id: OracleId(id),
crdt: Arc::new(Mutex::new(OracleNetworkCRDT::new())),
is_byzantine,
base_price: 2500_000_000, // $2500 with 6 decimals
}
}
fn submit_price(&self) {
let price = if self.is_byzantine {
// Byzantine nodes try to manipulate
self.base_price * 120 / 100 // 20% higher
} else {
// Honest nodes add realistic variance
let variance = (rand() * 0.02 - 0.01) as f64;
((self.base_price as f64) * (1.0 + variance)) as u128
};
let attestation = PriceAttestation {
id: format!("{}_{}", self.id.0, timestamp()),
oracle_id: self.id.clone(),
asset_pair: AssetPair("ETH/USD".to_string()),
price,
confidence: if self.is_byzantine { 50 } else { 95 },
timestamp: timestamp(),
sources: vec![
DataSource {
name: "Binance".to_string(),
price,
volume: 1000_000_000,
},
DataSource {
name: "Coinbase".to_string(),
price: price + 1_000_000, // Slight difference
volume: 800_000_000,
},
],
};
let mut crdt = self.crdt.lock().unwrap();
crdt.submit_attestation(attestation);
}
}
// ============ Network Simulation ============
struct NetworkSimulator {
nodes: Vec<Arc<OracleNode>>,
partitioned: Arc<Mutex<bool>>,
}
impl NetworkSimulator {
fn new() -> Self {
let mut nodes = Vec::new();
// Create 5 honest nodes
for i in 1..=5 {
nodes.push(Arc::new(OracleNode::new(format!("honest_{}", i), false)));
}
// Create 2 Byzantine nodes
for i in 1..=2 {
nodes.push(Arc::new(OracleNode::new(format!("byzantine_{}", i), true)));
}
Self {
nodes,
partitioned: Arc::new(Mutex::new(false)),
}
}
fn run(&self, duration: Duration) {
println!("🚀 Starting BFT-CRDT Oracle Network Demo");
println!("=========================================");
println!("📊 Network: {} nodes ({} Byzantine)", self.nodes.len(), 2);
println!("⏱️ Duration: {:?}\n", duration);
let start = Instant::now();
// Spawn oracle threads
let handles: Vec<_> = self
.nodes
.iter()
.map(|node| {
let node_clone = Arc::clone(node);
thread::spawn(move || {
while start.elapsed() < duration {
node_clone.submit_price();
thread::sleep(Duration::from_millis(1000));
}
})
})
.collect();
// Spawn network propagation thread
let nodes_clone = self.nodes.clone();
let partitioned_clone = Arc::clone(&self.partitioned);
let propagation_handle = thread::spawn(move || {
while start.elapsed() < duration {
let is_partitioned = *partitioned_clone.lock().unwrap();
// Propagate between nodes
for i in 0..nodes_clone.len() {
for j in 0..nodes_clone.len() {
if i != j {
// Skip if partitioned
if is_partitioned && ((i < 3 && j >= 3) || (i >= 3 && j < 3)) {
continue;
}
let crdt1 = nodes_clone[i].crdt.lock().unwrap();
let mut crdt2 = nodes_clone[j].crdt.lock().unwrap();
crdt2.merge(&*crdt1);
}
}
}
thread::sleep(Duration::from_millis(100));
}
});
// Main monitoring loop
let mut last_partition = Instant::now();
while start.elapsed() < duration {
thread::sleep(Duration::from_secs(2));
// Print current state
self.print_network_state();
// Simulate network partition every 10 seconds
if last_partition.elapsed() > Duration::from_secs(10) {
let mut partitioned = self.partitioned.lock().unwrap();
*partitioned = !*partitioned;
if *partitioned {
println!("\n⚠️ NETWORK PARTITION ACTIVE - Nodes split into two groups");
} else {
println!("\n✅ NETWORK PARTITION HEALED - All nodes can communicate");
}
last_partition = Instant::now();
}
}
// Wait for threads
for handle in handles {
handle.join().unwrap();
}
propagation_handle.join().unwrap();
// Print final statistics
self.print_final_stats();
}
fn print_network_state(&self) {
println!("\n📈 Current Network State:");
println!("------------------------");
// Get price from each node's perspective
let mut prices = Vec::new();
for node in &self.nodes {
let crdt = node.crdt.lock().unwrap();
if let Some((price, confidence, sources)) =
crdt.get_aggregate_price(&AssetPair("ETH/USD".to_string()), 60)
{
prices.push((node.id.0.clone(), price, confidence, sources));
println!(
" {} sees: ${:.2} (confidence: {}%, sources: {})",
node.id.0,
price as f64 / 1_000_000.0,
confidence,
sources
);
}
}
// Calculate network consensus
if !prices.is_empty() {
let avg_price: u128 =
prices.iter().map(|(_, p, _, _)| *p).sum::<u128>() / prices.len() as u128;
let min_price = prices.iter().map(|(_, p, _, _)| *p).min().unwrap();
let max_price = prices.iter().map(|(_, p, _, _)| *p).max().unwrap();
let deviation = ((max_price - min_price) as f64 / avg_price as f64) * 100.0;
println!("\n📊 Network Consensus:");
println!(" Average: ${:.2}", avg_price as f64 / 1_000_000.0);
println!(
" Range: ${:.2} - ${:.2}",
min_price as f64 / 1_000_000.0,
max_price as f64 / 1_000_000.0
);
println!(" Max Deviation: {:.2}%", deviation);
}
}
fn print_final_stats(&self) {
println!("\n\n🏁 Final Statistics");
println!("===================");
let mut total_attestations = 0;
let mut oracle_stats = Vec::new();
for node in &self.nodes {
let crdt = node.crdt.lock().unwrap();
let node_attestations = crdt.attestations.len();
total_attestations += node_attestations;
let score = crdt.oracle_scores.get(&node.id).unwrap_or(&0.5);
oracle_stats.push((node.id.0.clone(), node_attestations, *score));
}
println!("\n📈 Oracle Performance:");
for (id, attestations, score) in oracle_stats {
let node_type = if id.starts_with("byzantine") {
"🔴"
} else {
"🟢"
};
println!(
" {} {} - Attestations: {}, Reputation: {:.2}",
node_type, id, attestations, score
);
}
println!("\n📊 Network Totals:");
println!(" Total Attestations: {}", total_attestations);
println!(
" Attestations/second: {:.2}",
total_attestations as f64 / 30.0
);
// Show that Byzantine nodes were filtered out
if let Some(node) = self.nodes.first() {
let crdt = node.crdt.lock().unwrap();
if let Some((price, confidence, sources)) =
crdt.get_aggregate_price(&AssetPair("ETH/USD".to_string()), 300)
{
println!(
"\n✅ Final Aggregated Price: ${:.2} (confidence: {}%)",
price as f64 / 1_000_000.0,
confidence
);
println!(" Despite Byzantine manipulation attempts!");
}
}
}
}
// ============ Helper Functions ============
fn timestamp() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
}
fn rand() -> f64 {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
((nanos % 1000) as f64) / 1000.0
}
// ============ Main Function ============
fn main() {
println!("BFT-CRDT Oracle Network Demo");
println!("============================\n");
let simulator = NetworkSimulator::new();
simulator.run(Duration::from_secs(30));
println!("\n✅ Demo completed!");
println!("\n💡 Key Takeaways:");
println!(" • Oracles submitted prices without coordination");
println!(" • Byzantine nodes couldn't corrupt the aggregate price");
println!(" • Network partitions were handled gracefully");
println!(" • No consensus protocol was needed!");
}