use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use crate::transaction::Transaction; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BlockHeader { pub index: u64, pub timestamp: DateTime, pub previous_hash: String, pub hash: String, pub nonce: u64, pub difficulty: u32, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Block { pub header: BlockHeader, pub transactions: Vec, } impl Block { /// Create a new block (hash is computed, nonce is 0 until mined). pub fn new(index: u64, previous_hash: String, transactions: Vec, difficulty: u32) -> Self { let mut block = Block { header: BlockHeader { index, timestamp: Utc::now(), previous_hash, hash: String::new(), nonce: 0, difficulty, }, transactions, }; block.header.hash = block.calculate_hash(); block } /// Create the genesis block. pub fn genesis() -> Self { Block::new(0, crate::config::GENESIS_PREV_HASH.to_string(), vec![], crate::config::INITIAL_DIFFICULTY) } /// Calculate SHA-256 hash of the block. pub fn calculate_hash(&self) -> String { let data = format!( "{}{}{}{}{}", self.header.index, self.header.timestamp.timestamp_millis(), self.header.previous_hash, self.header.nonce, serde_json::to_string(&self.transactions).unwrap_or_default(), ); let mut hasher = Sha256::new(); hasher.update(data.as_bytes()); hex::encode(hasher.finalize()) } /// Check if the block hash satisfies the difficulty target. pub fn hash_meets_difficulty(&self) -> bool { let prefix = "0".repeat(self.header.difficulty as usize); self.header.hash.starts_with(&prefix) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_genesis_block() { let genesis = Block::genesis(); assert_eq!(genesis.header.index, 0); assert!(genesis.transactions.is_empty()); assert!(!genesis.header.hash.is_empty()); } #[test] fn test_calculate_hash_deterministic() { let block = Block::genesis(); let hash1 = block.calculate_hash(); let hash2 = block.calculate_hash(); assert_eq!(hash1, hash2); } #[test] fn test_hash_is_hex_64_chars() { let block = Block::genesis(); assert_eq!(block.header.hash.len(), 64); assert!(block.header.hash.chars().all(|c| c.is_ascii_hexdigit())); } }