profile.rs (6350B)
1 use serde::{Deserialize, Serialize}; 2 3 use super::{ 4 BURN_LINEAGE_MATURITY_HEIGHTS, DEFAULT_TICKET_EXPIRY_WINDOW, DEFAULT_TICKET_MATURITY_DELAY, 5 MAX_BLOCK_BYTES, MAX_BLOCK_TRANSACTIONS, MAX_PENDING_TRANSACTIONS, MINE_DIFFICULTY_BITS, 6 hex_hash, 7 }; 8 9 #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] 10 pub struct LaunchProfile { 11 pub profile_id: String, 12 pub ticket_maturity_delay_heights: u64, 13 #[serde(default = "default_ticket_expiry_window_heights")] 14 pub ticket_expiry_window_heights: u64, 15 #[serde(default = "default_mine_difficulty_bits")] 16 pub mine_difficulty_bits: u32, 17 #[serde(default = "default_burn_lineage_maturity_heights")] 18 pub burn_lineage_maturity_heights: u64, 19 pub max_pending_transactions: usize, 20 pub max_block_transactions: usize, 21 #[serde(default = "default_max_block_bytes")] 22 pub max_block_bytes: usize, 23 } 24 25 impl Default for LaunchProfile { 26 fn default() -> Self { 27 Self { 28 profile_id: "iuna-mainnet-candidate".to_string(), 29 ticket_maturity_delay_heights: DEFAULT_TICKET_MATURITY_DELAY, 30 ticket_expiry_window_heights: DEFAULT_TICKET_EXPIRY_WINDOW, 31 mine_difficulty_bits: MINE_DIFFICULTY_BITS, 32 burn_lineage_maturity_heights: BURN_LINEAGE_MATURITY_HEIGHTS, 33 max_pending_transactions: MAX_PENDING_TRANSACTIONS, 34 max_block_transactions: MAX_BLOCK_TRANSACTIONS, 35 max_block_bytes: MAX_BLOCK_BYTES, 36 } 37 } 38 } 39 40 fn default_max_block_bytes() -> usize { 41 MAX_BLOCK_BYTES 42 } 43 44 fn default_ticket_expiry_window_heights() -> u64 { 45 DEFAULT_TICKET_EXPIRY_WINDOW 46 } 47 48 fn default_mine_difficulty_bits() -> u32 { 49 MINE_DIFFICULTY_BITS 50 } 51 52 fn default_burn_lineage_maturity_heights() -> u64 { 53 BURN_LINEAGE_MATURITY_HEIGHTS 54 } 55 56 impl LaunchProfile { 57 pub fn local_testnet() -> Self { 58 Self { 59 profile_id: local_testnet_profile_id().to_string(), 60 burn_lineage_maturity_heights: 0, 61 ..Self::default() 62 } 63 } 64 65 pub fn hash(&self) -> String { 66 let canonical = if self.burn_lineage_maturity_heights == BURN_LINEAGE_MATURITY_HEIGHTS { 67 // Preserve the legacy canonical format when maturity remains at its 68 // protocol default. The field used to be a frozen constant. 69 format!( 70 "iuna-launch-profile:{}:{}:{}:{}:{}:{}:{}", 71 self.profile_id, 72 self.ticket_maturity_delay_heights, 73 self.ticket_expiry_window_heights, 74 self.mine_difficulty_bits, 75 self.max_pending_transactions, 76 self.max_block_transactions, 77 self.max_block_bytes 78 ) 79 } else { 80 format!( 81 "iuna-launch-profile-v2:{}:{}:{}:{}:{}:{}:{}:{}", 82 self.profile_id, 83 self.ticket_maturity_delay_heights, 84 self.ticket_expiry_window_heights, 85 self.mine_difficulty_bits, 86 self.burn_lineage_maturity_heights, 87 self.max_pending_transactions, 88 self.max_block_transactions, 89 self.max_block_bytes 90 ) 91 }; 92 hex_hash(canonical) 93 } 94 } 95 96 #[cfg(not(feature = "e2e"))] 97 fn local_testnet_profile_id() -> &'static str { 98 "iuna-local-testnet-v1" 99 } 100 101 #[cfg(feature = "e2e")] 102 fn local_testnet_profile_id() -> &'static str { 103 "iuna-local-e2e-5s-v1" 104 } 105 106 #[derive(Clone, Debug, Eq, PartialEq)] 107 pub struct GenesisBurn { 108 pub from: String, 109 pub amount: u64, 110 } 111 112 impl GenesisBurn { 113 pub fn new(from: impl Into<String>, amount: u64) -> Self { 114 Self { 115 from: from.into(), 116 amount, 117 } 118 } 119 } 120 121 #[cfg(test)] 122 mod tests { 123 use super::{GenesisBurn, LaunchProfile}; 124 use crate::domain::{ 125 DEFAULT_TICKET_EXPIRY_WINDOW, DEFAULT_TICKET_MATURITY_DELAY, MAX_BLOCK_BYTES, 126 MAX_BLOCK_TRANSACTIONS, MAX_PENDING_TRANSACTIONS, MINE_DIFFICULTY_BITS, 127 }; 128 129 #[test] 130 fn default_launch_profile_matches_protocol_defaults() { 131 let profile = LaunchProfile::default(); 132 133 assert_eq!(profile.profile_id, "iuna-mainnet-candidate"); 134 assert_eq!( 135 profile.ticket_maturity_delay_heights, 136 DEFAULT_TICKET_MATURITY_DELAY 137 ); 138 assert_eq!( 139 profile.ticket_expiry_window_heights, 140 DEFAULT_TICKET_EXPIRY_WINDOW 141 ); 142 assert_eq!(profile.mine_difficulty_bits, MINE_DIFFICULTY_BITS); 143 assert_eq!( 144 profile.burn_lineage_maturity_heights, 145 crate::domain::BURN_LINEAGE_MATURITY_HEIGHTS 146 ); 147 assert_eq!(profile.max_pending_transactions, MAX_PENDING_TRANSACTIONS); 148 assert_eq!(profile.max_block_transactions, MAX_BLOCK_TRANSACTIONS); 149 assert_eq!(profile.max_block_bytes, MAX_BLOCK_BYTES); 150 assert_eq!( 151 profile.hash(), 152 "eb2f67e9d735474859ceb1fe124fe270977214f6e2f4cd855a4d8c3b5ecac558" 153 ); 154 } 155 156 #[test] 157 fn local_testnet_profile_has_immediate_burn_lineage_eligibility() { 158 let profile = LaunchProfile::local_testnet(); 159 160 #[cfg(not(feature = "e2e"))] 161 assert_eq!(profile.profile_id, "iuna-local-testnet-v1"); 162 #[cfg(feature = "e2e")] 163 assert_eq!(profile.profile_id, "iuna-local-e2e-5s-v1"); 164 assert_eq!(profile.burn_lineage_maturity_heights, 0); 165 assert_ne!(profile.hash(), LaunchProfile::default().hash()); 166 } 167 168 #[test] 169 fn legacy_json_profile_defaults_burn_lineage_maturity() { 170 let profile: LaunchProfile = serde_json::from_str( 171 r#"{ 172 "profile_id":"legacy", 173 "ticket_maturity_delay_heights":3, 174 "ticket_expiry_window_heights":3, 175 "mine_difficulty_bits":12, 176 "max_pending_transactions":10000, 177 "max_block_transactions":1000, 178 "max_block_bytes":1000000 179 }"#, 180 ) 181 .unwrap(); 182 183 assert_eq!( 184 profile.burn_lineage_maturity_heights, 185 crate::domain::BURN_LINEAGE_MATURITY_HEIGHTS 186 ); 187 } 188 189 #[test] 190 fn genesis_burn_constructor_preserves_fields() { 191 let burn = GenesisBurn::new("alice", 42); 192 193 assert_eq!(burn.from, "alice"); 194 assert_eq!(burn.amount, 42); 195 } 196 }