/ node / consensus / src / ratifications.rs
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  }