summaryrefslogtreecommitdiffstats
path: root/core/src/block.rs
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/block.rs')
-rw-r--r--core/src/block.rs253
1 files changed, 253 insertions, 0 deletions
diff --git a/core/src/block.rs b/core/src/block.rs
new file mode 100644
index 0000000..d72f962
--- /dev/null
+++ b/core/src/block.rs
@@ -0,0 +1,253 @@
+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<Transaction>,
+
+ /// Staking actions (stake/unstake)
+ pub stake_actions: Vec<StakeAction>,
+}
+
+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]);
+ }
+}