Rust · 15915 bytes Raw Blame History
1 //! Contribution-Based Credit System
2 //!
3 //! Tracks storage and bandwidth contributions vs usage to allocate network access
4
5 use anyhow::Result;
6 use serde::{Deserialize, Serialize};
7 use std::collections::HashMap;
8 use chrono::{DateTime, Utc, Duration};
9
10 /// Contribution tracking for users in the network
11 #[derive(Debug, Clone, Serialize, Deserialize)]
12 pub struct ContributionTracker {
13 /// Per-user contribution records
14 pub user_contributions: HashMap<String, UserContribution>,
15 /// Network-wide statistics
16 pub network_stats: NetworkContributionStats,
17 /// Configuration for contribution requirements
18 pub config: ContributionConfig,
19 }
20
21 #[derive(Debug, Clone, Serialize, Deserialize)]
22 pub struct UserContribution {
23 pub user_id: String,
24 /// Storage currently offered to network (in GB)
25 pub storage_offered_gb: u64,
26 /// Storage currently being used by this user (in GB)
27 pub storage_used_gb: u64,
28 /// Bandwidth offered (average over last 30 days, in Mbps)
29 pub bandwidth_offered_mbps: f64,
30 /// Bandwidth used (average over last 30 days, in Mbps)
31 pub bandwidth_used_mbps: f64,
32 /// Reliability metrics
33 pub uptime_percentage: f64,
34 pub response_time_ms: u64,
35 pub successful_requests: u64,
36 pub failed_requests: u64,
37 /// Contribution score (calculated from ratios and reliability)
38 pub contribution_score: f64,
39 /// Priority level for resource allocation
40 pub priority_level: PriorityLevel,
41 /// Account status
42 pub account_status: AccountStatus,
43 /// Timestamps
44 pub joined_at: DateTime<Utc>,
45 pub last_active: DateTime<Utc>,
46 pub last_calculated: DateTime<Utc>,
47 }
48
49 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
50 pub enum PriorityLevel {
51 /// User taking more than giving - lowest priority
52 Deficit,
53 /// User giving slightly more than taking - normal priority
54 Balanced,
55 /// User giving significantly more than taking - high priority
56 Surplus,
57 /// User giving much more than taking - highest priority
58 Generous,
59 }
60
61 #[derive(Debug, Clone, Serialize, Deserialize)]
62 pub enum AccountStatus {
63 /// Account in good standing
64 Active,
65 /// Warning about low contribution ratio
66 Warning,
67 /// Limited access due to poor contribution ratio
68 Limited,
69 /// Access suspended due to not contributing
70 Suspended,
71 }
72
73 #[derive(Debug, Clone, Serialize, Deserialize)]
74 pub struct NetworkContributionStats {
75 pub total_storage_offered_gb: u64,
76 pub total_storage_used_gb: u64,
77 pub total_bandwidth_offered_mbps: f64,
78 pub total_bandwidth_used_mbps: f64,
79 pub active_contributors: u32,
80 pub network_utilization_percent: f64,
81 pub average_contribution_score: f64,
82 pub last_updated: DateTime<Utc>,
83 }
84
85 #[derive(Debug, Clone, Serialize, Deserialize)]
86 pub struct ContributionConfig {
87 /// Minimum contribution ratio to maintain good standing (1.0 = equal give/take)
88 pub min_contribution_ratio: f64,
89 /// Warning threshold ratio (below this triggers warning)
90 pub warning_ratio: f64,
91 /// Suspension threshold ratio (below this suspends access)
92 pub suspension_ratio: f64,
93 /// Minimum storage offering to participate (in GB)
94 pub min_storage_offering_gb: u64,
95 /// Grace period for new users (days)
96 pub new_user_grace_days: u32,
97 /// Weight of reliability in contribution score (0.0-1.0)
98 pub reliability_weight: f64,
99 /// Weight of storage ratio in contribution score (0.0-1.0)
100 pub storage_ratio_weight: f64,
101 /// Weight of bandwidth ratio in contribution score (0.0-1.0)
102 pub bandwidth_ratio_weight: f64,
103 }
104
105 impl ContributionTracker {
106 pub fn new() -> Self {
107 Self {
108 user_contributions: HashMap::new(),
109 network_stats: NetworkContributionStats {
110 total_storage_offered_gb: 0,
111 total_storage_used_gb: 0,
112 total_bandwidth_offered_mbps: 0.0,
113 total_bandwidth_used_mbps: 0.0,
114 active_contributors: 0,
115 network_utilization_percent: 0.0,
116 average_contribution_score: 0.0,
117 last_updated: Utc::now(),
118 },
119 config: ContributionConfig {
120 min_contribution_ratio: 1.0, // Must give at least as much as you take
121 warning_ratio: 0.8, // Warning at 80%
122 suspension_ratio: 0.5, // Suspend at 50%
123 min_storage_offering_gb: 10, // Minimum 10GB to participate
124 new_user_grace_days: 30, // 30 day grace period
125 reliability_weight: 0.3, // 30% from reliability
126 storage_ratio_weight: 0.4, // 40% from storage contribution
127 bandwidth_ratio_weight: 0.3, // 30% from bandwidth contribution
128 },
129 }
130 }
131
132 /// Register a new user in the contribution system
133 pub async fn register_user(&mut self, user_id: String, initial_storage_gb: u64) -> Result<()> {
134 if self.user_contributions.contains_key(&user_id) {
135 return Err(anyhow::anyhow!("User already registered"));
136 }
137
138 if initial_storage_gb < self.config.min_storage_offering_gb {
139 return Err(anyhow::anyhow!("Initial storage offering too low. Minimum: {} GB",
140 self.config.min_storage_offering_gb));
141 }
142
143 let user_contribution = UserContribution {
144 user_id: user_id.clone(),
145 storage_offered_gb: initial_storage_gb,
146 storage_used_gb: 0,
147 bandwidth_offered_mbps: 0.0,
148 bandwidth_used_mbps: 0.0,
149 uptime_percentage: 100.0,
150 response_time_ms: 50,
151 successful_requests: 0,
152 failed_requests: 0,
153 contribution_score: 1.0, // Start with neutral score
154 priority_level: PriorityLevel::Balanced,
155 account_status: AccountStatus::Active,
156 joined_at: Utc::now(),
157 last_active: Utc::now(),
158 last_calculated: Utc::now(),
159 };
160
161 self.user_contributions.insert(user_id, user_contribution);
162 self.update_network_stats().await?;
163
164 Ok(())
165 }
166
167 /// Update user's storage offering
168 pub async fn update_storage_offering(&mut self, user_id: &str, new_offering_gb: u64) -> Result<()> {
169 let contribution = self.user_contributions.get_mut(user_id)
170 .ok_or_else(|| anyhow::anyhow!("User not found"))?;
171
172 if new_offering_gb < self.config.min_storage_offering_gb {
173 return Err(anyhow::anyhow!("Storage offering too low. Minimum: {} GB",
174 self.config.min_storage_offering_gb));
175 }
176
177 contribution.storage_offered_gb = new_offering_gb;
178 contribution.last_active = Utc::now();
179
180 self.recalculate_contribution_score(user_id).await?;
181 self.update_network_stats().await?;
182
183 Ok(())
184 }
185
186 /// Update user's storage usage
187 pub async fn update_storage_usage(&mut self, user_id: &str, storage_used_gb: u64) -> Result<()> {
188 let contribution = self.user_contributions.get_mut(user_id)
189 .ok_or_else(|| anyhow::anyhow!("User not found"))?;
190
191 contribution.storage_used_gb = storage_used_gb;
192 contribution.last_active = Utc::now();
193
194 self.recalculate_contribution_score(user_id).await?;
195 self.update_network_stats().await?;
196
197 Ok(())
198 }
199
200 /// Update user's bandwidth metrics
201 pub async fn update_bandwidth_metrics(&mut self, user_id: &str, offered_mbps: f64, used_mbps: f64) -> Result<()> {
202 let contribution = self.user_contributions.get_mut(user_id)
203 .ok_or_else(|| anyhow::anyhow!("User not found"))?;
204
205 contribution.bandwidth_offered_mbps = offered_mbps;
206 contribution.bandwidth_used_mbps = used_mbps;
207 contribution.last_active = Utc::now();
208
209 self.recalculate_contribution_score(user_id).await?;
210
211 Ok(())
212 }
213
214 /// Update user's reliability metrics
215 pub async fn update_reliability_metrics(
216 &mut self,
217 user_id: &str,
218 uptime_percentage: f64,
219 response_time_ms: u64,
220 successful_requests: u64,
221 failed_requests: u64
222 ) -> Result<()> {
223 let contribution = self.user_contributions.get_mut(user_id)
224 .ok_or_else(|| anyhow::anyhow!("User not found"))?;
225
226 contribution.uptime_percentage = uptime_percentage;
227 contribution.response_time_ms = response_time_ms;
228 contribution.successful_requests = successful_requests;
229 contribution.failed_requests = failed_requests;
230 contribution.last_active = Utc::now();
231
232 self.recalculate_contribution_score(user_id).await?;
233
234 Ok(())
235 }
236
237 /// Recalculate contribution score for a user
238 pub async fn recalculate_contribution_score(&mut self, user_id: &str) -> Result<()> {
239 let contribution = self.user_contributions.get_mut(user_id)
240 .ok_or_else(|| anyhow::anyhow!("User not found"))?;
241
242 // Calculate storage ratio (offered / used, but handle zero usage)
243 let storage_ratio = if contribution.storage_used_gb == 0 {
244 2.0 // If not using storage, give good ratio
245 } else {
246 contribution.storage_offered_gb as f64 / contribution.storage_used_gb as f64
247 };
248
249 // Calculate bandwidth ratio
250 let bandwidth_ratio = if contribution.bandwidth_used_mbps == 0.0 {
251 2.0 // If not using bandwidth, give good ratio
252 } else {
253 contribution.bandwidth_offered_mbps / contribution.bandwidth_used_mbps
254 };
255
256 // Calculate reliability score (0.0-1.0)
257 let total_requests = contribution.successful_requests + contribution.failed_requests;
258 let success_rate = if total_requests == 0 {
259 1.0
260 } else {
261 contribution.successful_requests as f64 / total_requests as f64
262 };
263
264 let uptime_score = contribution.uptime_percentage / 100.0;
265 let response_score = (1000.0 - contribution.response_time_ms as f64).max(0.0) / 1000.0;
266 let reliability_score = (success_rate + uptime_score + response_score) / 3.0;
267
268 // Weighted contribution score
269 let score = (storage_ratio * self.config.storage_ratio_weight) +
270 (bandwidth_ratio * self.config.bandwidth_ratio_weight) +
271 (reliability_score * self.config.reliability_weight);
272
273 contribution.contribution_score = score;
274
275 // Update priority level based on score
276 contribution.priority_level = if score >= 2.0 {
277 PriorityLevel::Generous
278 } else if score >= 1.5 {
279 PriorityLevel::Surplus
280 } else if score >= self.config.min_contribution_ratio {
281 PriorityLevel::Balanced
282 } else {
283 PriorityLevel::Deficit
284 };
285
286 // Update account status
287 let is_new_user = (Utc::now() - contribution.joined_at).num_days() < self.config.new_user_grace_days as i64;
288
289 contribution.account_status = if is_new_user {
290 AccountStatus::Active // Grace period for new users
291 } else if score < self.config.suspension_ratio {
292 AccountStatus::Suspended
293 } else if score < self.config.warning_ratio {
294 AccountStatus::Limited
295 } else if score < self.config.min_contribution_ratio {
296 AccountStatus::Warning
297 } else {
298 AccountStatus::Active
299 };
300
301 contribution.last_calculated = Utc::now();
302
303 Ok(())
304 }
305
306 /// Update network-wide statistics
307 async fn update_network_stats(&mut self) -> Result<()> {
308 let mut total_storage_offered = 0u64;
309 let mut total_storage_used = 0u64;
310 let mut total_bandwidth_offered = 0.0f64;
311 let mut total_bandwidth_used = 0.0f64;
312 let mut total_score = 0.0f64;
313 let mut active_count = 0u32;
314
315 for contribution in self.user_contributions.values() {
316 if matches!(contribution.account_status, AccountStatus::Active | AccountStatus::Warning) {
317 total_storage_offered += contribution.storage_offered_gb;
318 total_storage_used += contribution.storage_used_gb;
319 total_bandwidth_offered += contribution.bandwidth_offered_mbps;
320 total_bandwidth_used += contribution.bandwidth_used_mbps;
321 total_score += contribution.contribution_score;
322 active_count += 1;
323 }
324 }
325
326 self.network_stats = NetworkContributionStats {
327 total_storage_offered_gb: total_storage_offered,
328 total_storage_used_gb: total_storage_used,
329 total_bandwidth_offered_mbps: total_bandwidth_offered,
330 total_bandwidth_used_mbps: total_bandwidth_used,
331 active_contributors: active_count,
332 network_utilization_percent: if total_storage_offered > 0 {
333 (total_storage_used as f64 / total_storage_offered as f64) * 100.0
334 } else {
335 0.0
336 },
337 average_contribution_score: if active_count > 0 {
338 total_score / active_count as f64
339 } else {
340 0.0
341 },
342 last_updated: Utc::now(),
343 };
344
345 Ok(())
346 }
347
348 /// Check if user can request storage allocation
349 pub fn can_request_storage(&self, user_id: &str, requested_gb: u64) -> Result<bool> {
350 let contribution = self.user_contributions.get(user_id)
351 .ok_or_else(|| anyhow::anyhow!("User not found"))?;
352
353 match contribution.account_status {
354 AccountStatus::Suspended => Ok(false),
355 AccountStatus::Limited => {
356 // Limited users can only request small amounts
357 Ok(requested_gb <= 1 && contribution.storage_used_gb + requested_gb <= contribution.storage_offered_gb / 2)
358 },
359 AccountStatus::Warning | AccountStatus::Active => {
360 // Check if request would violate their offering
361 let new_total = contribution.storage_used_gb + requested_gb;
362 Ok(new_total <= contribution.storage_offered_gb)
363 },
364 }
365 }
366
367 /// Get priority queue position for resource allocation
368 pub fn get_allocation_priority(&self, user_id: &str) -> Result<u32> {
369 let contribution = self.user_contributions.get(user_id)
370 .ok_or_else(|| anyhow::anyhow!("User not found"))?;
371
372 let priority_score = match contribution.priority_level {
373 PriorityLevel::Generous => 1000 + (contribution.contribution_score * 100.0) as u32,
374 PriorityLevel::Surplus => 800 + (contribution.contribution_score * 100.0) as u32,
375 PriorityLevel::Balanced => 500 + (contribution.contribution_score * 100.0) as u32,
376 PriorityLevel::Deficit => (contribution.contribution_score * 100.0) as u32,
377 };
378
379 Ok(priority_score)
380 }
381
382 /// Get user's current contribution status
383 pub fn get_user_status(&self, user_id: &str) -> Option<&UserContribution> {
384 self.user_contributions.get(user_id)
385 }
386
387 /// Get network statistics
388 pub fn get_network_stats(&self) -> &NetworkContributionStats {
389 &self.network_stats
390 }
391
392 /// Get users sorted by contribution score (highest first)
393 pub fn get_contribution_leaderboard(&self, limit: Option<usize>) -> Vec<&UserContribution> {
394 let mut users: Vec<_> = self.user_contributions.values()
395 .filter(|u| matches!(u.account_status, AccountStatus::Active | AccountStatus::Warning))
396 .collect();
397
398 users.sort_by(|a, b| b.contribution_score.partial_cmp(&a.contribution_score).unwrap());
399
400 if let Some(limit) = limit {
401 users.truncate(limit);
402 }
403
404 users
405 }
406 }
407
408 impl Default for ContributionTracker {
409 fn default() -> Self {
410 Self::new()
411 }
412 }