use serde::{Deserialize, Serialize}; use crate::transaction::Transaction; use blake3::Hasher; /// Block hash pub type BlockHash = [u8; 32]; /// A block in the OnionCoin blockchain #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Block { /// Block header pub header: BlockHeader, /// Transactions in this block pub transactions: Vec, /// Staking actions (stake/unstake) pub stake_actions: Vec, } impl Block { /// Maximum block size (2 MB for Tor bandwidth constraints) pub const MAX_SIZE: usize = 2 * 1024 * 1024; /// Target block time (10 minutes) pub const TARGET_BLOCK_TIME_SECS: u64 = 10 * 60; /// Calculate block hash pub fn hash(&self) -> BlockHash { self.header.hash() } /// Validate block structure pub fn validate(&self) -> Result<(), BlockError> { // Check size let size = self.size(); if size > Self::MAX_SIZE { return Err(BlockError::TooLarge(size)); } // Validate all transactions for tx in &self.transactions { tx.validate().map_err(|_| BlockError::InvalidTransaction)?; } // Verify merkle root let calculated_merkle = Self::calculate_merkle_root(&self.transactions); if calculated_merkle != self.header.merkle_root { return Err(BlockError::InvalidMerkleRoot); } Ok(()) } /// Get block size in bytes pub fn size(&self) -> usize { bincode::serialize(self) .map(|s| s.len()) .unwrap_or(0) } /// Calculate merkle root of transactions fn calculate_merkle_root(transactions: &[Transaction]) -> [u8; 32] { if transactions.is_empty() { return [0u8; 32]; } let mut hashes: Vec<[u8; 32]> = transactions .iter() .map(|tx| tx.id()) .collect(); // Build merkle tree bottom-up while hashes.len() > 1 { let mut next_level = Vec::new(); for chunk in hashes.chunks(2) { let mut hasher = Hasher::new(); hasher.update(&chunk[0]); if chunk.len() > 1 { hasher.update(&chunk[1]); } else { hasher.update(&chunk[0]); // Duplicate if odd } next_level.push(*hasher.finalize().as_bytes()); } hashes = next_level; } hashes[0] } } /// Block header #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BlockHeader { /// Protocol version pub version: u8, /// Previous block hash pub previous_hash: BlockHash, /// Merkle root of transactions pub merkle_root: [u8; 32], /// Block timestamp (Unix seconds) pub timestamp: i64, /// Block height pub height: u64, /// Validator's public key (PoS) pub validator_pubkey: [u8; 32], /// Validator's signature #[serde(with = "serde_bytes")] pub validator_signature: [u8; 64], } impl BlockHeader { /// Calculate header hash pub fn hash(&self) -> BlockHash { let mut hasher = Hasher::new(); hasher.update(&[self.version]); hasher.update(&self.previous_hash); hasher.update(&self.merkle_root); hasher.update(&self.timestamp.to_le_bytes()); hasher.update(&self.height.to_le_bytes()); hasher.update(&self.validator_pubkey); // Note: signature not included in hash *hasher.finalize().as_bytes() } } /// Staking action (stake or unstake coins) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct StakeAction { /// Action type pub action_type: StakeActionType, /// Validator public key pub validator_pubkey: [u8; 32], /// Amount to stake/unstake pub amount: u64, /// Signature #[serde(with = "serde_bytes")] pub signature: [u8; 64], } #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] pub enum StakeActionType { Stake, Unstake, } #[derive(Debug, thiserror::Error)] pub enum BlockError { #[error("Block too large: {0} bytes (max: {})", Block::MAX_SIZE)] TooLarge(usize), #[error("Invalid transaction in block")] InvalidTransaction, #[error("Invalid merkle root")] InvalidMerkleRoot, #[error("Invalid validator signature")] InvalidValidatorSignature, #[error("Invalid previous hash")] InvalidPreviousHash, #[error("Block timestamp too far in future")] TimestampTooFarFuture, } #[cfg(test)] mod tests { use super::*; use crate::transaction::*; use chrono::Utc; use onioncoin_timing::TimingMetadata; fn create_test_block() -> Block { let seed = [42u8; 32]; let timing = TimingMetadata::new(Utc::now(), &seed).unwrap(); let tx = Transaction { version: 1, inputs: vec![TransactionInput { previous_output: OutPoint { txid: [1u8; 32], vout: 0, }, key_image: [2u8; 32], }], outputs: vec![TransactionOutput { encrypted_amount: vec![0u8; 32], stealth_address: [3u8; 32], commitment: [4u8; 32], }], ring_signatures: vec![RingSignature::new(11)], range_proof: vec![0u8; 64], encrypted_fee: 1000, timing, }; let merkle_root = Block::calculate_merkle_root(&[tx.clone()]); Block { header: BlockHeader { version: 1, previous_hash: [0u8; 32], merkle_root, timestamp: Utc::now().timestamp(), height: 1, validator_pubkey: [5u8; 32], validator_signature: [6u8; 64], }, transactions: vec![tx], stake_actions: vec![], } } #[test] fn test_block_hash() { let block = create_test_block(); let hash = block.hash(); assert_ne!(hash, [0u8; 32]); } #[test] fn test_block_validation() { let block = create_test_block(); assert!(block.validate().is_ok()); } #[test] fn test_merkle_root() { let block = create_test_block(); let calculated = Block::calculate_merkle_root(&block.transactions); assert_eq!(calculated, block.header.merkle_root); } #[test] fn test_empty_merkle_root() { let root = Block::calculate_merkle_root(&[]); assert_eq!(root, [0u8; 32]); } }