summaryrefslogtreecommitdiffstats
path: root/consensus/src/relay_proof.rs
blob: 0d55af314c6f95c15e2bf6c1ecb19d082f1137a5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
use serde::{Deserialize, Serialize};
use blake3::Hasher;
use std::collections::VecDeque;

/// Proof that a validator relayed traffic through Tor for OnionCoin network
/// This is OnionCoin's UNIQUE feature: earn rewards by being a Tor relay!
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RelayProof {
    /// Validator's public key
    pub validator_pubkey: [u8; 32],

    /// Time period this proof covers (Unix timestamp range)
    pub period_start: i64,
    pub period_end: i64,

    /// Metrics for this period
    pub metrics: RelayMetrics,

    /// Cryptographic proof of relay work
    /// In production: ZK-SNARK proof of bandwidth relay
    /// For prototype: hash chain of relayed packet hashes
    pub proof_data: Vec<u8>,

    /// Signature from validator
    #[serde(with = "serde_bytes")]
    pub signature: [u8; 64],
}

impl RelayProof {
    /// Standard proof period (1 hour)
    pub const PROOF_PERIOD_SECONDS: i64 = 3600;

    /// Create a new relay proof
    pub fn new(
        validator_pubkey: [u8; 32],
        period_start: i64,
        period_end: i64,
        metrics: RelayMetrics,
    ) -> Self {
        // Generate proof data (simplified)
        let proof_data = Self::generate_proof_data(&metrics);

        Self {
            validator_pubkey,
            period_start,
            period_end,
            metrics,
            proof_data,
            signature: [0u8; 64], // Placeholder
        }
    }

    /// Verify relay proof
    pub fn verify(&self) -> bool {
        // Check time period is valid
        if self.period_end <= self.period_start {
            return false;
        }

        // Check period is not too long
        if self.period_end - self.period_start > Self::PROOF_PERIOD_SECONDS * 24 {
            return false;
        }

        // Verify metrics are reasonable
        if !self.metrics.is_valid() {
            return false;
        }

        // Verify proof data
        let expected_proof = Self::generate_proof_data(&self.metrics);
        if self.proof_data != expected_proof {
            return false;
        }

        true
    }

    /// Generate proof data from metrics
    fn generate_proof_data(metrics: &RelayMetrics) -> Vec<u8> {
        let mut hasher = Hasher::new();
        hasher.update(&metrics.bytes_relayed.to_le_bytes());
        hasher.update(&metrics.packets_relayed.to_le_bytes());
        hasher.update(&metrics.unique_circuits.to_le_bytes());
        hasher.finalize().as_bytes().to_vec()
    }

    /// Calculate reward for this relay proof
    pub fn calculate_reward(&self) -> u64 {
        // Base reward per GB relayed
        const REWARD_PER_GB: u64 = 1; // 1 ONC per GB

        let gb_relayed = self.metrics.bytes_relayed / (1024 * 1024 * 1024);

        // Bonus for high circuit diversity
        let circuit_bonus = if self.metrics.unique_circuits > 100 {
            1.2
        } else if self.metrics.unique_circuits > 50 {
            1.1
        } else {
            1.0
        };

        ((gb_relayed * REWARD_PER_GB) as f64 * circuit_bonus) as u64
    }
}

/// Metrics about relay activity
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RelayMetrics {
    /// Total bytes relayed in this period
    pub bytes_relayed: u64,

    /// Total packets relayed
    pub packets_relayed: u64,

    /// Number of unique circuits served
    pub unique_circuits: u32,

    /// Average latency (milliseconds)
    pub avg_latency_ms: u32,

    /// Number of failed relay attempts
    pub failed_relays: u32,

    /// Uptime during this period (seconds)
    pub uptime_seconds: u64,
}

impl RelayMetrics {
    pub fn new() -> Self {
        Self {
            bytes_relayed: 0,
            packets_relayed: 0,
            unique_circuits: 0,
            avg_latency_ms: 0,
            failed_relays: 0,
            uptime_seconds: 0,
        }
    }

    /// Check if metrics are valid (sanity checks)
    pub fn is_valid(&self) -> bool {
        // Reasonable bounds
        if self.bytes_relayed > 1_000_000_000_000 {
            // > 1 TB
            return false;
        }

        if self.packets_relayed > 1_000_000_000 {
            // > 1B packets
            return false;
        }

        if self.avg_latency_ms > 60_000 {
            // > 1 minute
            return false;
        }

        // Packets should roughly match bytes
        if self.packets_relayed > 0 {
            let avg_packet_size = self.bytes_relayed / self.packets_relayed;
            if avg_packet_size < 20 || avg_packet_size > 1_000_000 {
                // Unrealistic packet size
                return false;
            }
        }

        true
    }

    /// Calculate quality score (0.0 - 1.0)
    pub fn quality_score(&self) -> f64 {
        let mut score = 1.0;

        // Penalty for high latency
        if self.avg_latency_ms > 5000 {
            score *= 0.5;
        } else if self.avg_latency_ms > 2000 {
            score *= 0.8;
        }

        // Penalty for failed relays
        if self.packets_relayed > 0 {
            let failure_rate = self.failed_relays as f64 / self.packets_relayed as f64;
            score *= (1.0 - failure_rate).max(0.0);
        }

        score.clamp(0.0, 1.0)
    }
}

impl Default for RelayMetrics {
    fn default() -> Self {
        Self::new()
    }
}

/// Tracks relay activity for a validator over time
#[derive(Debug)]
pub struct RelayTracker {
    /// Validator public key
    validator_pubkey: [u8; 32],

    /// Current period metrics
    current_metrics: RelayMetrics,

    /// Period start time
    period_start: i64,

    /// Historical proofs (last 24 hours)
    history: VecDeque<RelayProof>,
}

impl RelayTracker {
    /// Maximum history to keep (24 hours = 24 proofs)
    const MAX_HISTORY: usize = 24;

    pub fn new(validator_pubkey: [u8; 32], current_time: i64) -> Self {
        Self {
            validator_pubkey,
            current_metrics: RelayMetrics::new(),
            period_start: current_time,
            history: VecDeque::new(),
        }
    }

    /// Record relayed bytes
    pub fn record_relay(&mut self, bytes: u64, latency_ms: u32, success: bool) {
        self.current_metrics.bytes_relayed += bytes;
        self.current_metrics.packets_relayed += 1;

        // Update average latency (simple moving average)
        let total = self.current_metrics.packets_relayed;
        self.current_metrics.avg_latency_ms =
            ((self.current_metrics.avg_latency_ms as u64 * (total - 1) + latency_ms as u64)
                / total) as u32;

        if !success {
            self.current_metrics.failed_relays += 1;
        }
    }

    /// Record a unique circuit
    pub fn record_circuit(&mut self) {
        self.current_metrics.unique_circuits += 1;
    }

    /// Record uptime
    pub fn record_uptime(&mut self, seconds: u64) {
        self.current_metrics.uptime_seconds += seconds;
    }

    /// Finalize current period and generate proof
    pub fn finalize_period(&mut self, current_time: i64) -> RelayProof {
        let proof = RelayProof::new(
            self.validator_pubkey,
            self.period_start,
            current_time,
            self.current_metrics.clone(),
        );

        // Add to history
        self.history.push_back(proof.clone());

        // Trim history
        if self.history.len() > Self::MAX_HISTORY {
            self.history.pop_front();
        }

        // Reset for next period
        self.current_metrics = RelayMetrics::new();
        self.period_start = current_time;

        proof
    }

    /// Get total bytes relayed in last 24 hours
    pub fn get_24h_bytes(&self) -> u64 {
        self.history
            .iter()
            .map(|p| p.metrics.bytes_relayed)
            .sum::<u64>()
            + self.current_metrics.bytes_relayed
    }

    /// Get average quality score over last 24 hours
    pub fn get_avg_quality(&self) -> f64 {
        if self.history.is_empty() {
            return self.current_metrics.quality_score();
        }

        let total: f64 = self
            .history
            .iter()
            .map(|p| p.metrics.quality_score())
            .sum();

        total / self.history.len() as f64
    }

    /// Get current metrics
    pub fn current_metrics(&self) -> &RelayMetrics {
        &self.current_metrics
    }

    /// Get proof history
    pub fn history(&self) -> &VecDeque<RelayProof> {
        &self.history
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_relay_metrics_validation() {
        let mut metrics = RelayMetrics::new();
        metrics.bytes_relayed = 1_000_000; // 1 MB
        metrics.packets_relayed = 1000;
        metrics.avg_latency_ms = 100;

        assert!(metrics.is_valid());

        // Invalid: too many bytes
        metrics.bytes_relayed = 2_000_000_000_000;
        assert!(!metrics.is_valid());
    }

    #[test]
    fn test_relay_proof_creation() {
        let pubkey = [1u8; 32];
        let mut metrics = RelayMetrics::new();
        metrics.bytes_relayed = 1_000_000_000; // 1 GB
        metrics.packets_relayed = 10_000;
        metrics.unique_circuits = 50;

        let proof = RelayProof::new(pubkey, 0, 3600, metrics);

        assert!(proof.verify());
        assert_eq!(proof.calculate_reward(), 1); // 1 ONC for 1 GB
    }

    #[test]
    fn test_relay_tracker() {
        let pubkey = [1u8; 32];
        let mut tracker = RelayTracker::new(pubkey, 0);

        // Record some relay activity
        tracker.record_relay(1000, 100, true);
        tracker.record_relay(2000, 150, true);
        tracker.record_circuit();

        assert_eq!(tracker.current_metrics().bytes_relayed, 3000);
        assert_eq!(tracker.current_metrics().packets_relayed, 2);
        assert_eq!(tracker.current_metrics().unique_circuits, 1);

        // Finalize period
        let proof = tracker.finalize_period(3600);
        assert!(proof.verify());
        assert_eq!(tracker.history().len(), 1);
    }

    #[test]
    fn test_quality_score() {
        let mut metrics = RelayMetrics::new();
        metrics.packets_relayed = 100;
        metrics.failed_relays = 10; // 10% failure rate
        metrics.avg_latency_ms = 1000;

        let score = metrics.quality_score();
        assert!(score < 1.0);
        assert!(score > 0.0);
    }

    #[test]
    fn test_reward_calculation() {
        let pubkey = [1u8; 32];

        // 5 GB relayed with good circuit diversity
        let mut metrics = RelayMetrics::new();
        metrics.bytes_relayed = 5 * 1024 * 1024 * 1024;
        metrics.unique_circuits = 150; // High diversity = bonus

        let proof = RelayProof::new(pubkey, 0, 3600, metrics);
        let reward = proof.calculate_reward();

        assert!(reward >= 5); // At least 5 ONC base
        assert!(reward > 5); // Should have bonus
    }
}