-
-
Save RandyMcMillan/44bb08fb38c07edecc95f0c263c8ae86 to your computer and use it in GitHub Desktop.
uasf_simulator.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use std::time::{SystemTime, UNIX_EPOCH}; | |
| /// Simple Xorshift PRNG to maintain a 0-dependency standard library build. | |
| struct Prng { | |
| state: u32, | |
| } | |
| impl Prng { | |
| fn new() -> Self { | |
| let nanos = SystemTime::now() | |
| .duration_since(UNIX_EPOCH) | |
| .unwrap() | |
| .subsec_nanos(); | |
| Self { | |
| state: if nanos == 0 { 1 } else { nanos }, // state must not be 0 | |
| } | |
| } | |
| /// Generates a random integer between min and max (inclusive). | |
| fn gen_range(&mut self, min: u32, max: u32) -> u32 { | |
| self.state ^= self.state << 13; | |
| self.state ^= self.state >> 17; | |
| self.state ^= self.state << 5; | |
| min + (self.state % (max - min + 1)) | |
| } | |
| } | |
| /// Simulates the block header structure, focusing on the version bits used for signaling. | |
| #[derive(Debug, Clone)] | |
| pub struct BlockHeader { | |
| pub height: u32, | |
| pub version: i32, | |
| pub previous_block_hash: String, | |
| pub timestamp: u64, | |
| } | |
| impl BlockHeader { | |
| /// Creates a new mock block header. | |
| pub fn new(height: u32, version: i32, prev_hash: &str) -> Self { | |
| BlockHeader { | |
| height, | |
| version, | |
| previous_block_hash: prev_hash.to_string(), | |
| timestamp: SystemTime::now() | |
| .duration_since(UNIX_EPOCH) | |
| .unwrap() | |
| .as_secs(), | |
| } | |
| } | |
| /// Checks if the block is signaling for the specific upgrade. | |
| pub fn signals_upgrade(&self, bit: u8) -> bool { | |
| let mask = 1 << bit; | |
| (self.version & mask) != 0 | |
| } | |
| } | |
| /// Defines the consensus ruleset the node is running. | |
| #[derive(Debug, Clone, PartialEq)] | |
| pub enum NodeType { | |
| /// Enforces the flag day (rejects non-signaling blocks). | |
| UasfEnforcing, | |
| /// Traditional node (accepts blocks regardless of signaling). | |
| Legacy, | |
| } | |
| /// Defines the miner's political stance on the upgrade. | |
| #[derive(Debug, Clone, PartialEq)] | |
| pub enum MinerType { | |
| /// Signals the required bit to activate the soft fork. | |
| Compliant, | |
| /// Refuses to signal the required bit, attempting to veto. | |
| Defiant, | |
| } | |
| /// Represents a single fully validating node on the network. | |
| pub struct Node { | |
| pub id: String, | |
| pub node_type: NodeType, | |
| pub activation_height: u32, | |
| pub required_signal_bit: u8, | |
| pub local_chain: Vec<BlockHeader>, | |
| } | |
| impl Node { | |
| pub fn new( | |
| id: &str, | |
| node_type: NodeType, | |
| activation_height: u32, | |
| required_signal_bit: u8, | |
| genesis: BlockHeader, | |
| ) -> Self { | |
| Node { | |
| id: id.to_string(), | |
| node_type, | |
| activation_height, | |
| required_signal_bit, | |
| local_chain: vec![genesis], | |
| } | |
| } | |
| /// Processes an incoming block based on local consensus rules. | |
| pub fn process_block(&mut self, block: &BlockHeader) -> Result<(), String> { | |
| let current_tip = self.local_chain.last().unwrap(); | |
| // 1. Basic validation (simulated height check to ensure it builds on our tip) | |
| if block.height != current_tip.height + 1 { | |
| return Err(format!( | |
| "Invalid height. Expected {}, got {}", | |
| current_tip.height + 1, | |
| block.height | |
| )); | |
| } | |
| // 2. UASF Enforcement Logic (Only applies to UasfEnforcing nodes) | |
| if self.node_type == NodeType::UasfEnforcing && block.height >= self.activation_height { | |
| if !block.signals_upgrade(self.required_signal_bit) { | |
| return Err(format!( | |
| "UASF REJECTION: Failed to signal required bit {}.", | |
| self.required_signal_bit | |
| )); | |
| } | |
| } | |
| // Block is valid according to this node's ruleset. | |
| self.local_chain.push(block.clone()); | |
| Ok(()) | |
| } | |
| pub fn tip_hash(&self) -> &str { | |
| &self.local_chain.last().unwrap().previous_block_hash | |
| } | |
| } | |
| /// Represents the P2P network broadcasting blocks to all connected nodes. | |
| pub struct Network { | |
| pub nodes: Vec<Node>, | |
| } | |
| impl Network { | |
| pub fn new() -> Self { | |
| Network { nodes: Vec::new() } | |
| } | |
| pub fn add_node(&mut self, node: Node) { | |
| self.nodes.push(node); | |
| } | |
| /// Broadcasts a mined block to all nodes and prints their individual consensus decisions. | |
| pub fn broadcast_block(&mut self, block: &BlockHeader) { | |
| println!( | |
| "\n[NETWORK] Broadcasting Block {} (Version: {:#010X})", | |
| block.height, block.version | |
| ); | |
| let mut uasf_accepted = 0; | |
| let mut legacy_accepted = 0; | |
| for node in self.nodes.iter_mut() { | |
| match node.process_block(block) { | |
| Ok(_) => { | |
| println!(" [✓] Node {} ({:?}) accepted.", node.id, node.node_type); | |
| if node.node_type == NodeType::UasfEnforcing { | |
| uasf_accepted += 1; | |
| } else { | |
| legacy_accepted += 1; | |
| } | |
| } | |
| Err(e) => { | |
| println!( | |
| " [✗] Node {} ({:?}) rejected: {}", | |
| node.id, node.node_type, e | |
| ); | |
| } | |
| } | |
| } | |
| self.print_network_state(uasf_accepted, legacy_accepted); | |
| } | |
| /// Analyzes if the network has split into multiple distinct chains. | |
| fn print_network_state(&self, uasf_accepted: usize, legacy_accepted: usize) { | |
| let total_uasf = self | |
| .nodes | |
| .iter() | |
| .filter(|n| n.node_type == NodeType::UasfEnforcing) | |
| .count(); | |
| let total_legacy = self | |
| .nodes | |
| .iter() | |
| .filter(|n| n.node_type == NodeType::Legacy) | |
| .count(); | |
| println!( | |
| " => Status: UASF Nodes ({}/{}), Legacy Nodes ({}/{})", | |
| uasf_accepted, total_uasf, legacy_accepted, total_legacy | |
| ); | |
| if uasf_accepted == 0 && legacy_accepted > 0 && total_uasf > 0 { | |
| println!(" [!] WARNING: CHAIN SPLIT DETECTED. Network consensus has fractured."); | |
| } else if uasf_accepted > 0 && legacy_accepted > 0 { | |
| println!(" [✓] CONSENSUS MAINTAINED. All nodes are on the same chain."); | |
| } | |
| } | |
| } | |
| fn main() { | |
| let mut prng = Prng::new(); | |
| let activation_height = 100; | |
| let required_bit = 1; | |
| let genesis = BlockHeader::new(98, 0x20000000, "00000000000000000000000000000000"); | |
| for iteration in 1..=100 { | |
| println!("\n======================================================="); | |
| println!("--- SIMULATION ITERATION {} ---", iteration); | |
| println!("======================================================="); | |
| let mut network = Network::new(); | |
| // 1. Randomize Node Distribution | |
| let num_uasf = prng.gen_range(1, 10); | |
| let num_legacy = prng.gen_range(1, 10); | |
| println!( | |
| "Initializing network with {} UASF nodes and {} Legacy nodes.", | |
| num_uasf, num_legacy | |
| ); | |
| println!("UASF Flag Day configured for Block Height {}.", activation_height); | |
| for i in 0..num_uasf { | |
| network.add_node(Node::new( | |
| &format!("UASF_Node_{}", i), | |
| NodeType::UasfEnforcing, | |
| activation_height, | |
| required_bit, | |
| genesis.clone(), | |
| )); | |
| } | |
| for i in 0..num_legacy { | |
| network.add_node(Node::new( | |
| &format!("Legacy_Node_{}", i), | |
| NodeType::Legacy, | |
| activation_height, | |
| required_bit, | |
| genesis.clone(), | |
| )); | |
| } | |
| // 2. Randomize Miner Hash Power Distribution | |
| let num_compliant_miners = prng.gen_range(1, 10); | |
| let num_defiant_miners = prng.gen_range(1, 10); | |
| let mut miners = Vec::new(); | |
| for _ in 0..num_compliant_miners { miners.push(MinerType::Compliant); } | |
| for _ in 0..num_defiant_miners { miners.push(MinerType::Defiant); } | |
| println!( | |
| "Hash power distribution: {} Compliant Miners, {} Defiant Miners.", | |
| num_compliant_miners, num_defiant_miners | |
| ); | |
| // Block 99: Pre-activation. No signal required. | |
| println!("\n--- Mining Block 99 (Pre-Activation) ---"); | |
| let block_99 = BlockHeader::new(99, 0x20000000, "hash98"); | |
| network.broadcast_block(&block_99); | |
| // Block 100: The Flag Day. A random miner wins the block. | |
| let winning_miner_idx = prng.gen_range(0, miners.len() as u32 - 1) as usize; | |
| let winning_miner = &miners[winning_miner_idx]; | |
| println!("\n--- Mining Block 100 (Activation Boundary) ---"); | |
| println!("A {:?} miner won the proof-of-work race for Block 100.", winning_miner); | |
| let mut block_100_version = 0x20000000; | |
| if *winning_miner == MinerType::Compliant { | |
| // Compliant miner flips the required bit | |
| block_100_version |= 1 << required_bit; | |
| } // Defiant miner leaves it unset | |
| let block_100 = BlockHeader::new(100, block_100_version, "hash99"); | |
| network.broadcast_block(&block_100); | |
| // Iteration concludes, showcasing the immediate split or success based on who mined the block. | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=1619e9f171715c5de980b936d2792cdc