ratifications.rs
1 // Copyright (c) 2025-2026 ACDC Network 2 // This file is part of the alphaos library. 3 // 4 // Alpha Chain | Delta Chain Protocol 5 // International Monetary Graphite. 6 // 7 // Derived from Aleo (https://aleo.org) and ProvableHQ (https://provable.com). 8 // They built world-class ZK infrastructure. We installed the EASY button. 9 // Their cryptography: elegant. Our modifications: bureaucracy-compatible. 10 // Original brilliance: theirs. Robert's Rules: ours. Bugs: definitely ours. 11 // 12 // Original Aleo/ProvableHQ code subject to Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0 13 // All modifications and new work: CC0 1.0 Universal Public Domain Dedication. 14 // No rights reserved. No permission required. No warranty. No refunds. 15 // 16 // https://creativecommons.org/publicdomain/zero/1.0/ 17 // SPDX-License-Identifier: CC0-1.0 18 19 // Network Upgrade Ratifications - Section 9 20 // Automatic upgrade activation based on on-chain governance votes 21 22 use alphavm::{ledger::store::ConsensusStorage, prelude::*}; 23 use anyhow::{bail, ensure, Result}; 24 use serde::{Deserialize, Serialize}; 25 use std::{ 26 collections::HashMap, 27 fs, 28 path::PathBuf, 29 time::{SystemTime, UNIX_EPOCH}, 30 }; 31 32 /// Vote choice for network upgrade proposals 33 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 34 pub enum VoteChoice { 35 No = 0, 36 Yes = 1, 37 Abstain = 2, 38 } 39 40 impl VoteChoice { 41 pub fn from_u8(value: u8) -> Result<Self> { 42 match value { 43 0 => Ok(VoteChoice::No), 44 1 => Ok(VoteChoice::Yes), 45 2 => Ok(VoteChoice::Abstain), 46 _ => bail!("Invalid vote choice: {}", value), 47 } 48 } 49 } 50 51 /// Network upgrade proposal (mirrors credits.alpha struct) 52 #[derive(Debug, Clone, Serialize, Deserialize)] 53 pub struct NetworkUpgradeProposal { 54 pub proposal_id: String, 55 pub source_network: u16, 56 pub target_network: u16, 57 pub activation_height: u32, 58 59 pub alpha_expected_hash: String, 60 pub alpha_params_hash: String, 61 62 pub delta_expected_hash: String, 63 pub delta_params_hash: String, 64 65 pub delta_min_alpha_blocks: u32, 66 67 pub min_governor_validators: u32, 68 pub min_public_validators: u32, 69 70 pub governor_yes_votes: u32, 71 pub governor_no_votes: u32, 72 pub public_yes_votes: u32, 73 pub public_no_votes: u32, 74 75 pub proposer: String, 76 pub created_at_height: u32, 77 pub voting_ends_height: u32, 78 } 79 80 /// Helper to get the proposals storage path 81 fn get_proposals_path() -> PathBuf { 82 dirs::home_dir().unwrap_or_else(|| PathBuf::from("/tmp")).join(".alphaos").join("proposals.json") 83 } 84 85 /// Helper to get the votes storage path 86 fn get_votes_path() -> PathBuf { 87 dirs::home_dir().unwrap_or_else(|| PathBuf::from("/tmp")).join(".alphaos").join("votes.json") 88 } 89 90 /// Load all proposals from storage 91 fn load_proposals() -> Result<HashMap<String, NetworkUpgradeProposal>> { 92 let path = get_proposals_path(); 93 if !path.exists() { 94 return Ok(HashMap::new()); 95 } 96 97 let content = fs::read_to_string(&path)?; 98 let proposals: HashMap<String, NetworkUpgradeProposal> = serde_json::from_str(&content)?; 99 Ok(proposals) 100 } 101 102 /// Load all votes from storage (key: "proposal_id:address" -> vote_choice) 103 fn load_votes() -> Result<HashMap<String, u8>> { 104 let path = get_votes_path(); 105 if !path.exists() { 106 return Ok(HashMap::new()); 107 } 108 109 let content = fs::read_to_string(&path)?; 110 let votes: HashMap<String, u8> = serde_json::from_str(&content)?; 111 Ok(votes) 112 } 113 114 /// Check if any pending network upgrade activates at the given block height. 115 /// 116 /// This function should be called during block production/validation to detect 117 /// when a governance-approved network upgrade should execute. 118 /// 119 /// **Security Note**: This function is disabled on mainnet builds via the 120 /// `#[cfg(not(feature = "mainnet"))]` attribute. Network upgrades can only 121 /// happen on testnets. 122 /// 123 /// # Arguments 124 /// * `ledger` - The ledger to query for upgrade proposals 125 /// * `height` - Current block height 126 /// 127 /// # Returns 128 /// * `Ok(Some(proposal))` if an upgrade activates at this height 129 /// * `Ok(None)` if no upgrade activates 130 /// * `Err` if query fails 131 #[cfg(not(feature = "mainnet"))] 132 pub fn check_upgrade_activation_sync<N: Network>( 133 _ledger: &impl std::any::Any, 134 height: u32, 135 ) -> Result<Option<NetworkUpgradeProposal>> { 136 // Log every 100 blocks to show governance checking is active 137 if height.is_multiple_of(100) { 138 info!("🔍 Governance check at height {} (proposals path: {})", height, get_proposals_path().display()); 139 } 140 141 // Load proposals from JSON storage 142 let proposals = match load_proposals() { 143 Ok(p) => { 144 if !p.is_empty() && height.is_multiple_of(100) { 145 info!("📋 Found {} active proposal(s) in governance storage", p.len()); 146 for (id, proposal) in p.iter() { 147 info!(" - Proposal '{}': activation_height={}", id, proposal.activation_height); 148 } 149 } 150 p 151 } 152 Err(e) => { 153 // Use INFO level instead of DEBUG so errors are visible 154 info!( 155 "⚠️ Could not load proposals at height {}: {} (path: {})", 156 height, 157 e, 158 get_proposals_path().display() 159 ); 160 return Ok(None); 161 } 162 }; 163 164 // Check if any proposal activates at this height 165 for (proposal_id, proposal) in proposals.iter() { 166 if proposal.activation_height == height { 167 info!("🎯 Network upgrade proposal '{}' activates at height {}", proposal_id, height); 168 return Ok(Some(proposal.clone())); 169 } 170 } 171 172 Ok(None) 173 } 174 175 /// Verify that a network upgrade proposal has sufficient approval. 176 /// 177 /// **Security Note**: This function is disabled on mainnet builds. 178 /// 179 /// # Requirements 180 /// * ≥67% of governors voted Yes 181 /// * ≥min_governor_validators governors committed 182 /// * ≥min_public_validators public validators committed 183 /// 184 /// # Arguments 185 /// * `proposal` - The proposal to verify 186 /// * `total_governors` - Total number of governors in the network 187 /// 188 /// # Returns 189 /// * `Ok(())` if approval requirements met 190 /// * `Err` with detailed reason if requirements not met 191 #[cfg(not(feature = "mainnet"))] 192 pub fn verify_upgrade_approval(proposal: &NetworkUpgradeProposal, total_governors: u32) -> Result<()> { 193 // Calculate approval rate 194 let approval_rate = proposal.governor_yes_votes as f64 / total_governors as f64; 195 196 // Require 67% governor approval 197 ensure!(approval_rate >= 0.67, "Upgrade failed: only {:.1}% governor approval (need 67%)", approval_rate * 100.0); 198 199 // Require minimum governor commitments 200 ensure!( 201 proposal.governor_yes_votes >= proposal.min_governor_validators, 202 "Insufficient governor commitments: {}/{} required", 203 proposal.governor_yes_votes, 204 proposal.min_governor_validators 205 ); 206 207 // Require minimum public validator commitments 208 ensure!( 209 proposal.public_yes_votes >= proposal.min_public_validators, 210 "Insufficient public validator commitments: {}/{}", 211 proposal.public_yes_votes, 212 proposal.min_public_validators 213 ); 214 215 info!("✅ Upgrade approval verified:"); 216 info!(" Governors: {}/{} ({:.1}% approval)", proposal.governor_yes_votes, total_governors, approval_rate * 100.0); 217 info!(" Public validators: {}/{} required", proposal.public_yes_votes, proposal.min_public_validators); 218 219 Ok(()) 220 } 221 222 /// Execute a network upgrade based on the validator's vote. 223 /// 224 /// **Security Note**: This function is disabled on mainnet builds. Network 225 /// upgrades can only happen on testnets where governance has explicitly 226 /// enabled the `network-upgrades` feature. 227 /// 228 /// This function implements the automatic upgrade logic: 229 /// - If validator voted Yes: Execute dual-chain upgrade (Alpha→Delta) 230 /// - If validator voted No/Abstain: Stay on current network 231 /// 232 /// # Arguments 233 /// * `proposal` - The approved upgrade proposal 234 /// * `my_vote` - This validator's vote choice 235 /// * `ledger` - The ledger (for generating new genesis) 236 /// 237 /// # Returns 238 /// * `Ok(())` if upgrade executed successfully or validator staying on current network 239 /// * `Err` if upgrade execution failed 240 #[cfg(not(feature = "mainnet"))] 241 pub fn execute_network_upgrade_sync<N: Network>( 242 proposal: &NetworkUpgradeProposal, 243 my_vote: VoteChoice, 244 _ledger: &impl std::any::Any, 245 ) -> Result<()> { 246 match my_vote { 247 VoteChoice::Yes => { 248 info!("🚀 AUTOMATIC NETWORK UPGRADE INITIATED"); 249 info!(" You voted YES - executing dual-chain upgrade..."); 250 info!(" Proposal: {}", proposal.proposal_id); 251 info!(" Target network: {}", proposal.target_network); 252 info!(" Activation height: {}", proposal.activation_height); 253 254 // Note: Full upgrade execution requires ledger access 255 // For Section 12b testing, we're demonstrating the governance flow 256 info!("✅ Upgrade execution logic triggered (full implementation requires genesis generation)"); 257 258 Ok(()) 259 } 260 VoteChoice::No => { 261 info!("⏩ CONTINUING ON CURRENT NETWORK"); 262 info!(" You voted NO - staying on network {}", proposal.source_network); 263 Ok(()) 264 } 265 VoteChoice::Abstain => { 266 info!("⏩ CONTINUING ON CURRENT NETWORK"); 267 info!(" You abstained - staying on network {}", proposal.source_network); 268 Ok(()) 269 } 270 } 271 } 272 273 /// Execute Alpha chain upgrade. 274 /// 275 /// **Security Note**: This function is disabled on mainnet builds. 276 /// 277 /// Steps: 278 /// 1. Generate new Alpha genesis from proposal parameters 279 /// 2. Verify genesis hash matches expected 280 /// 3. Clear existing Alpha ledger 281 /// 4. Insert new genesis block 282 /// 5. Restart consensus 283 #[cfg(not(feature = "mainnet"))] 284 #[allow(dead_code)] 285 async fn execute_alpha_upgrade<N: Network, C: ConsensusStorage<N>>( 286 proposal: &NetworkUpgradeProposal, 287 ledger: &Ledger<N, C>, 288 ) -> Result<()> { 289 info!("Step 1/3: Upgrading Alpha chain to network {}...", proposal.target_network); 290 291 // Call the genesis_generation module to handle the swap 292 // This loads parameters, generates genesis, and swaps the chain 293 crate::genesis_generation::execute_alpha_genesis_swap( 294 &proposal.alpha_params_hash, 295 &proposal.alpha_expected_hash, 296 ledger, 297 ) 298 .await?; 299 300 info!("✅ Alpha chain upgrade complete"); 301 Ok(()) 302 } 303 304 /// Execute Delta chain upgrade after Alpha stabilizes. 305 /// 306 /// **Security Note**: This function is disabled on mainnet builds. 307 /// 308 /// Steps: 309 /// 1. Wait for Alpha to produce N blocks 310 /// 2. Verify Alpha consensus healthy 311 /// 3. Generate Delta genesis (with Alpha cross-references) 312 /// 4. Verify Delta genesis hash matches expected 313 /// 5. Start Delta chain 314 #[cfg(not(feature = "mainnet"))] 315 #[allow(dead_code)] 316 async fn execute_delta_upgrade<N: Network, C: ConsensusStorage<N>>( 317 proposal: &NetworkUpgradeProposal, 318 ledger: &Ledger<N, C>, 319 ) -> Result<()> { 320 info!("Step 2/3: Waiting for Alpha stability ({} blocks)...", proposal.delta_min_alpha_blocks); 321 322 // Wait for Alpha to produce minimum required blocks 323 wait_for_alpha_stability(proposal, ledger).await?; 324 325 info!("✅ Alpha chain stable"); 326 327 info!("Step 3/3: Starting Delta chain on network {}...", proposal.target_network); 328 329 // Call the genesis_generation module to handle Delta genesis creation 330 // This loads parameters, embeds Alpha cross-references, and starts Delta 331 crate::genesis_generation::execute_delta_genesis_swap( 332 &proposal.delta_params_hash, 333 &proposal.delta_expected_hash, 334 ledger, 335 ) 336 .await?; 337 338 info!("✅ Delta chain started"); 339 Ok(()) 340 } 341 342 /// Wait for Alpha chain to reach stability before starting Delta. 343 /// 344 /// **Security Note**: This function is disabled on mainnet builds. 345 /// 346 /// Verifies: 347 /// * Alpha has produced `delta_min_alpha_blocks` since upgrade 348 /// * Alpha consensus is healthy (>67% participation) 349 /// * Block production is stable 350 #[cfg(not(feature = "mainnet"))] 351 #[allow(dead_code)] 352 async fn wait_for_alpha_stability<N: Network, C: ConsensusStorage<N>>( 353 proposal: &NetworkUpgradeProposal, 354 ledger: &Ledger<N, C>, 355 ) -> Result<()> { 356 let start_height = ledger.latest_height(); 357 let target_height = start_height + proposal.delta_min_alpha_blocks; 358 359 info!("Waiting for Alpha to reach height {} (current: {})", target_height, start_height); 360 361 loop { 362 let current_height = ledger.latest_height(); 363 364 if current_height < target_height { 365 info!("Alpha height: {}/{}", current_height, target_height); 366 tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; 367 continue; 368 } 369 370 // Verify consensus health 371 if !check_alpha_consensus_health(ledger).await? { 372 warn!("Alpha consensus not healthy, waiting..."); 373 tokio::time::sleep(tokio::time::Duration::from_secs(30)).await; 374 continue; 375 } 376 377 break; 378 } 379 380 Ok(()) 381 } 382 383 /// Check if Alpha consensus is healthy. 384 /// 385 /// **Security Note**: This function is disabled on mainnet builds. 386 /// 387 /// Verifies: 388 /// * Recent blocks being produced 389 /// * >67% validator participation 390 /// * No consensus faults 391 #[cfg(not(feature = "mainnet"))] 392 #[allow(dead_code)] 393 async fn check_alpha_consensus_health<N: Network, C: ConsensusStorage<N>>(ledger: &Ledger<N, C>) -> Result<bool> { 394 // Check recent block production 395 let latest_height = ledger.latest_height(); 396 let latest_block = ledger.get_block(latest_height)?; 397 398 // Check if block is recent (within last 30 seconds) 399 let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64; 400 let block_time = latest_block.timestamp(); 401 let age_seconds = now - block_time; 402 403 if age_seconds > 30 { 404 warn!("Latest block is {} seconds old", age_seconds); 405 return Ok(false); 406 } 407 408 // Check validator participation (would need committee data) 409 // In production: verify >67% of validators participated in recent blocks 410 411 // For now, just check that blocks are being produced 412 Ok(true) 413 } 414 415 /// Get this validator's vote on a proposal. 416 /// 417 /// **Security Note**: This function is disabled on mainnet builds. 418 /// 419 /// Queries the credits.alpha program to retrieve the vote record. 420 /// 421 /// # Arguments 422 /// * `ledger` - The ledger to query 423 /// * `proposal_id` - The proposal to check 424 /// * `my_address` - This validator's address 425 /// 426 /// # Returns 427 /// * `Ok(VoteChoice)` if vote found 428 /// * `Err` if vote not found or query failed 429 #[cfg(not(feature = "mainnet"))] 430 pub fn get_my_vote<N: Network>( 431 _ledger: &impl std::any::Any, 432 proposal_id: &str, 433 my_address: &Address<N>, 434 ) -> Result<VoteChoice> { 435 // Load votes from JSON storage 436 let votes = match load_votes() { 437 Ok(v) => v, 438 Err(e) => { 439 warn!("Could not load votes: {}, defaulting to No", e); 440 return Ok(VoteChoice::No); 441 } 442 }; 443 444 // Look up this validator's vote using "proposal_id:address" as key 445 let vote_key = format!("{}:{}", proposal_id, my_address); 446 447 match votes.get(&vote_key) { 448 Some(&vote_value) => { 449 let vote = VoteChoice::from_u8(vote_value)?; 450 info!("📊 My vote for proposal '{}': {:?}", proposal_id, vote); 451 Ok(vote) 452 } 453 None => { 454 info!("📊 No vote found for proposal '{}', defaulting to No", proposal_id); 455 Ok(VoteChoice::No) 456 } 457 } 458 } 459 460 #[cfg(test)] 461 mod tests { 462 use super::*; 463 464 #[test] 465 fn test_vote_choice_from_u8() { 466 assert_eq!(VoteChoice::from_u8(0).unwrap(), VoteChoice::No); 467 assert_eq!(VoteChoice::from_u8(1).unwrap(), VoteChoice::Yes); 468 assert_eq!(VoteChoice::from_u8(2).unwrap(), VoteChoice::Abstain); 469 assert!(VoteChoice::from_u8(3).is_err()); 470 } 471 472 #[test] 473 fn test_verify_upgrade_approval_success() { 474 let proposal = NetworkUpgradeProposal { 475 proposal_id: "test".to_string(), 476 source_network: 1, 477 target_network: 0, 478 activation_height: 1000, 479 alpha_expected_hash: "test".to_string(), 480 alpha_params_hash: "test".to_string(), 481 delta_expected_hash: "test".to_string(), 482 delta_params_hash: "test".to_string(), 483 delta_min_alpha_blocks: 100, 484 min_governor_validators: 20, 485 min_public_validators: 20, 486 governor_yes_votes: 27, // 67.5% of 40 487 governor_no_votes: 13, 488 public_yes_votes: 25, 489 public_no_votes: 5, 490 proposer: "test".to_string(), 491 created_at_height: 500, 492 voting_ends_height: 900, 493 }; 494 495 assert!(verify_upgrade_approval(&proposal, 40).is_ok()); 496 } 497 498 #[test] 499 fn test_verify_upgrade_approval_insufficient_percentage() { 500 let proposal = NetworkUpgradeProposal { 501 proposal_id: "test".to_string(), 502 source_network: 1, 503 target_network: 0, 504 activation_height: 1000, 505 alpha_expected_hash: "test".to_string(), 506 alpha_params_hash: "test".to_string(), 507 delta_expected_hash: "test".to_string(), 508 delta_params_hash: "test".to_string(), 509 delta_min_alpha_blocks: 100, 510 min_governor_validators: 20, 511 min_public_validators: 20, 512 governor_yes_votes: 26, // 65% of 40 (not enough) 513 governor_no_votes: 14, 514 public_yes_votes: 25, 515 public_no_votes: 5, 516 proposer: "test".to_string(), 517 created_at_height: 500, 518 voting_ends_height: 900, 519 }; 520 521 assert!(verify_upgrade_approval(&proposal, 40).is_err()); 522 } 523 524 #[test] 525 fn test_verify_upgrade_approval_insufficient_commitments() { 526 let proposal = NetworkUpgradeProposal { 527 proposal_id: "test".to_string(), 528 source_network: 1, 529 target_network: 0, 530 activation_height: 1000, 531 alpha_expected_hash: "test".to_string(), 532 alpha_params_hash: "test".to_string(), 533 delta_expected_hash: "test".to_string(), 534 delta_params_hash: "test".to_string(), 535 delta_min_alpha_blocks: 100, 536 min_governor_validators: 20, 537 min_public_validators: 20, 538 governor_yes_votes: 15, // Not enough commitments 539 governor_no_votes: 5, 540 public_yes_votes: 25, 541 public_no_votes: 5, 542 proposer: "test".to_string(), 543 created_at_height: 500, 544 voting_ends_height: 900, 545 }; 546 547 assert!(verify_upgrade_approval(&proposal, 20).is_err()); 548 } 549 }