use crate::common::skein::{skein_256_hash_data, skein_512_hash_data}; use crate::common::types::Transaction; use crate::records::block_height::get_block_height::get_height; use crate::records::memory::averages::asert_genesis_anchor; use crate::records::memory::chain_state::cached_chain_height; use crate::sled::Db; use crate::to_string; use crate::wallets::structures::Wallet; use crate::Cursor; use crate::Serialize; use crate::{decode, encode}; use crate::{AsyncReadExt, AsyncWriteExt}; const TARGET_BLOCK_SECONDS: i128 = 15; const ASERT_HALF_LIFE_SECONDS: i128 = 300; const ASERT_RADIX_BITS: i128 = 16; const ASERT_FIXED_ONE: i128 = 1 << ASERT_RADIX_BITS; pub const TIMESTAMP_OFFSET: usize = 0; pub const MINER_OFFSET: usize = TIMESTAMP_OFFSET + 4; pub const PREVIOUS_HASH_OFFSET: usize = MINER_OFFSET + Wallet::SHORT_ADDRESS_BYTES_LENGTH; pub const DIFFICULTY_OFFSET: usize = PREVIOUS_HASH_OFFSET + 32; pub const NONCE_OFFSET: usize = DIFFICULTY_OFFSET + 8; pub const VRF_OFFSET: usize = NONCE_OFFSET + 1; pub const PROOF_OFFSET: usize = VRF_OFFSET + 16; pub const UNMINED_BLOCK_BYTES: usize = 4 + Wallet::SHORT_ADDRESS_BYTES_LENGTH + 32 + 8 + 1; pub const VRF_BLOCK_BYTES: usize = UNMINED_BLOCK_BYTES + 16; pub const BLOCK_HEADER_BYTES: usize = VRF_BLOCK_BYTES + Wallet::SIGNATURE_LENGTH; // UnminedBlock is the deterministic header data used to derive the VRF. #[derive(Debug, Serialize, Clone)] // 67 bytes pub struct UnminedBlock { pub timestamp: u32, // 4 bytes block timestamp pub miner: String, // 22 bytes miner short address pub previous_hash: String, // 32 bytes parent block hash pub next_block_difficulty: u64, // 8 bytes difficulty for this block pub nonce: u8, // 1 byte nonce searched by mining workers } // VrfBlock adds the deterministic VRF number to the unsigned header. #[derive(Debug, Serialize, Clone)] // 83 bytes pub struct VrfBlock { pub unmined_block: UnminedBlock, // 67 bytes unsigned block header fields pub vrf: u128, // 16 bytes deterministic mining value } // Block stores the deterministic VRF header, miner proof, and transactions. #[derive(Debug, Serialize)] // header is 749 bytes plus transactions pub struct Block { pub vrf_block: VrfBlock, pub proof: String, // 666 bytes miner signature over the VRF header hash pub transactions: Vec, // Exact transaction slices are retained for downloaded/stored blocks so // mempool reuse can compare bytes without serializing or hashing again. #[serde(skip)] pub original_transactions: Vec>, } impl UnminedBlock { // Create the unmined block header fields. pub async fn new( timestamp: u32, miner: &str, previous_hash: &str, next_block_difficulty: u64, nonce: u8, ) -> Self { Self { timestamp, miner: miner.to_string(), previous_hash: previous_hash.to_string(), next_block_difficulty, nonce, } } pub async fn generate_random_number(input: &str) -> u128 { // Hash the supplied deterministic seed with Skein512, then fold the 64-byte result // into one u128 value by XORing four 16-byte chunks. let hash = skein_512_hash_data(input); let hash_bytes = decode(&hash).expect("Failed to decode hash"); if hash_bytes.len() != 64 { panic!("Hash must be exactly 64 bytes long."); } let a = u128::from_le_bytes( hash_bytes[0..16] .try_into() .expect("Chunk A must be 16 bytes"), ); let b = u128::from_le_bytes( hash_bytes[16..32] .try_into() .expect("Chunk B must be 16 bytes"), ); let c = u128::from_le_bytes( hash_bytes[32..48] .try_into() .expect("Chunk C must be 16 bytes"), ); let d = u128::from_le_bytes( hash_bytes[48..64] .try_into() .expect("Chunk D must be 16 bytes"), ); a ^ b ^ c ^ d } pub async fn vrf_generate(self) -> VrfBlock { // Derive the VRF from the unsigned header alone so every node // calculates one result for each timestamp/miner/parent/nonce tuple. let vrf = self.deterministic_vrf().await; VrfBlock { unmined_block: self, vrf, } } pub async fn deterministic_vrf(&self) -> u128 { let hash = self.hash().await; Self::generate_random_number(&hash).await } // Hash the serialized unsigned header to create the deterministic VRF seed. pub async fn hash(&self) -> String { let serialized = to_string(self).unwrap(); skein_512_hash_data(&serialized) } fn asert_target(anchor_target: u64, height_delta: u32, time_delta: i128) -> u64 { // Deterministic fixed-point ASERT calculation. The polynomial // approximates 2^x without platform-dependent floats. let expected_time = height_delta as i128 * TARGET_BLOCK_SECONDS; let time_error = time_delta - expected_time; let exponent = (time_error << ASERT_RADIX_BITS) / ASERT_HALF_LIFE_SECONDS; let shifts = exponent >> ASERT_RADIX_BITS; let frac = exponent - (shifts << ASERT_RADIX_BITS); let factor = ASERT_FIXED_ONE + ((195_766_423_245_049_i128 * frac + 971_821_376_i128 * frac * frac + 5_127_i128 * frac * frac * frac + (1_i128 << 47)) >> 48); let mut target = anchor_target as u128 * factor.max(1) as u128; if shifts >= 0 { if shifts >= 64 { return u64::MAX; } target = target.checked_shl(shifts as u32).unwrap_or(u128::MAX); } else { let right_shift = (-shifts) as u32; if right_shift >= 128 { return 1; } target >>= right_shift; } target >>= ASERT_RADIX_BITS as u32; target.clamp(1, u64::MAX as u128) as u64 } fn clamp_per_block(raw_target: u64, current_difficulty: u64) -> u64 { // ASERT provides the direction and scale, while this guard keeps any // single block from swinging the threshold too far. let lower_bound = current_difficulty .saturating_mul(85) .saturating_div(100) .max(1); let upper_bound = current_difficulty .saturating_mul(115) .saturating_div(100) .max(lower_bound); raw_target.clamp(lower_bound, upper_bound) } // Adjust difficulty based on ASERT drift from the genesis anchor. pub async fn adjust_difficulty( current_timestamp: u32, db: &Db, current_difficulty: u64, ) -> u64 { let block_number = cached_chain_height() .await .unwrap_or_else(|| get_height(db)); let candidate_height = block_number + 1; let Some((anchor_height, anchor_timestamp, anchor_difficulty)) = asert_genesis_anchor().await else { return current_difficulty; }; if anchor_height >= candidate_height { return current_difficulty; } let height_delta = candidate_height - anchor_height; let time_delta = current_timestamp as i128 - anchor_timestamp as i128; let raw_target = Self::asert_target(anchor_difficulty, height_delta, time_delta); Self::clamp_per_block(raw_target, current_difficulty) } } impl VrfBlock { pub async fn hash(&self) -> String { // Hash only deterministic header data for mining and chain identity. let serialized = to_string(self).unwrap(); skein_256_hash_data(&serialized) } pub async fn to_bytes(&self) -> tokio::io::Result> { // Serialize the fixed-width VRF header layout. let mut buffer = Vec::with_capacity(VRF_BLOCK_BYTES); let mut cursor = Cursor::new(&mut buffer); cursor .write_all(&self.unmined_block.timestamp.to_le_bytes()) .await?; let miner_bytes = Wallet::short_address_to_bytes(&self.unmined_block.miner) .ok_or_else(|| tokio::io::Error::other("Invalid short miner address"))?; cursor.write_all(&miner_bytes).await?; cursor .write_all(&decode(&self.unmined_block.previous_hash).unwrap()) .await?; cursor .write_all(&self.unmined_block.next_block_difficulty.to_le_bytes()) .await?; cursor .write_all(&self.unmined_block.nonce.to_le_bytes()) .await?; cursor.write_all(&self.vrf.to_le_bytes()).await?; Ok(buffer) } pub async fn from_bytes(bytes: &[u8]) -> tokio::io::Result { // A VRF header must be exactly the fixed header byte length. if bytes.len() != VRF_BLOCK_BYTES { return Err(tokio::io::Error::other("Invalid Byte Count for Block")); } // Read from the fixed-width VRF header bytes. let mut cursor = Cursor::new(bytes); // Decode timestamp and miner short address. let timestamp = cursor.read_u32_le().await?; let mut miner_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH]; cursor.read_exact(&mut miner_bytes).await?; let miner = Wallet::bytes_to_short_address(&miner_bytes) .ok_or_else(|| tokio::io::Error::other("Invalid short miner address"))?; // Decode parent hash, difficulty, nonce, and deterministic VRF number. let mut prev_hash_bytes = vec![0; 32]; cursor.read_exact(&mut prev_hash_bytes).await?; let previous_hash = encode(&prev_hash_bytes); let next_block_difficulty = cursor.read_u64_le().await?; let nonce = cursor.read_u8().await?; let mut vrf_bytes = [0u8; 16]; cursor.read_exact(&mut vrf_bytes).await?; let vrf = u128::from_le_bytes(vrf_bytes); let unmined_block = UnminedBlock { timestamp, miner, previous_hash, next_block_difficulty, nonce, }; Ok(VrfBlock { unmined_block, vrf }) } } impl Block { pub async fn to_bytes(&self) -> tokio::io::Result> { let mut buffer = Vec::new(); // Serialize the fixed-width VRF header before any transactions. let vrf_bytes = self.vrf_block.to_bytes().await?; buffer.extend_from_slice(&vrf_bytes); let proof_bytes = decode(&self.proof) .map_err(|_| tokio::io::Error::other("Invalid miner proof encoding"))?; if proof_bytes.len() != Wallet::SIGNATURE_LENGTH { return Err(tokio::io::Error::other("Invalid miner proof length")); } buffer.extend_from_slice(&proof_bytes); // Append each transaction in block order using its own fixed layout. for transaction in &self.transactions { match transaction { Transaction::Genesis(genesis_tx) => { let tx_bytes = genesis_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::Rewards(rewards_tx) => { let tx_bytes = rewards_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::Transfer(transfer_tx) => { let tx_bytes = transfer_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::Token(token_tx) => { let tx_bytes = token_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::IssueToken(issue_token_tx) => { let tx_bytes = issue_token_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::Burn(burn_tx) => { let tx_bytes = burn_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::Nft(nft_tx) => { let tx_bytes = nft_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::Marketing(marketing_tx) => { let tx_bytes = marketing_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::Swap(swap_tx) => { let tx_bytes = swap_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::Lender(lender_tx) => { let tx_bytes = lender_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::Borrower(borrower_tx) => { let tx_bytes = borrower_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::Collateral(collateral_tx) => { let tx_bytes = collateral_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::Vanity(vanity_tx) => { let tx_bytes = vanity_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::StorageKey(storage_key_tx) => { let tx_bytes = storage_key_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::ProposalKey(proposal_key_tx) => { let tx_bytes = proposal_key_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::ProposalVote(proposal_vote_tx) => { let tx_bytes = proposal_vote_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::ActivationVote(activation_vote_tx) => { let tx_bytes = activation_vote_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::StorageBool(storage_tx) => { let tx_bytes = storage_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::StorageU8(storage_tx) => { let tx_bytes = storage_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::StorageU16(storage_tx) => { let tx_bytes = storage_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::StorageU32(storage_tx) => { let tx_bytes = storage_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::StorageU64(storage_tx) => { let tx_bytes = storage_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::StorageU128(storage_tx) => { let tx_bytes = storage_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::StorageString(storage_tx) => { let tx_bytes = storage_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::StorageI8(storage_tx) => { let tx_bytes = storage_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::StorageI16(storage_tx) => { let tx_bytes = storage_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::StorageI32(storage_tx) => { let tx_bytes = storage_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::StorageI64(storage_tx) => { let tx_bytes = storage_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::StorageI128(storage_tx) => { let tx_bytes = storage_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } Transaction::DeleteKey(delete_key_tx) => { let tx_bytes = delete_key_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } } } Ok(buffer) } } #[cfg(test)] mod tests { use super::{Block, UnminedBlock, BLOCK_HEADER_BYTES, VRF_BLOCK_BYTES}; fn candidate(nonce: u8) -> UnminedBlock { UnminedBlock { timestamp: 1_800_000_000, miner: "1111111111111111111111111111111111111111.cltc".to_string(), previous_hash: "22".repeat(32), next_block_difficulty: 2_000_000_000_000_000, nonce, } } #[tokio::test] async fn identical_unsigned_headers_produce_identical_vrf_headers() { let first = candidate(7).vrf_generate().await; let second = candidate(7).vrf_generate().await; assert_eq!(first.vrf, second.vrf); assert_eq!(first.hash().await, second.hash().await); } #[tokio::test] async fn changing_nonce_changes_the_vrf_header() { let first = candidate(7).vrf_generate().await; let second = candidate(8).vrf_generate().await; assert_ne!(first.vrf, second.vrf); assert_ne!(first.hash().await, second.hash().await); } #[tokio::test] async fn proof_is_stored_after_the_deterministic_header() { let vrf_block = candidate(7).vrf_generate().await; let deterministic_bytes = vrf_block.to_bytes().await.unwrap(); let block = Block { vrf_block, proof: "33".repeat(crate::wallets::structures::Wallet::SIGNATURE_LENGTH), transactions: Vec::new(), original_transactions: Vec::new(), }; let block_bytes = block.to_bytes().await.unwrap(); assert_eq!(deterministic_bytes.len(), VRF_BLOCK_BYTES); assert_eq!(block_bytes.len(), BLOCK_HEADER_BYTES); assert_eq!(&block_bytes[..VRF_BLOCK_BYTES], deterministic_bytes); } }