Contractless/src/blocks/block.rs

320 lines
12 KiB
Rust
Raw Normal View History

2026-05-24 17:56:57 +00:00
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::{calculate_averages, update_block_data};
use crate::sled::Db;
use crate::to_string;
use crate::wallets::structures::Wallet;
use crate::Cursor;
use crate::Duration;
use crate::Serialize;
use crate::{decode, encode};
use crate::{AsyncReadExt, AsyncWriteExt};
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 + Wallet::SIGNATURE_LENGTH;
// UnminedBlock is the deterministic header data that exists before
// the miner wallet adds the VRF proof.
#[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 miner's signed proof and derived VRF number to the header.
#[derive(Debug, Serialize, Clone)] // 749 bytes
pub struct VrfBlock {
pub unmined_block: UnminedBlock, // 67 bytes unsigned block header fields
pub vrf: u128, // 16 bytes random number derived from proof
pub proof: String, // 666 bytes miner signature proof
}
// Block stores the VRF header plus the ordered transaction list.
#[derive(Debug, Serialize)] // header is 749 bytes plus transactions
pub struct Block {
pub vrf_block: VrfBlock,
pub transactions: Vec<Transaction>,
}
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 proof 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, wallet_key: String) -> VrfBlock {
// Sign the unmined header hash with the miner wallet and derive
// the VRF number from that signature.
let hash = self.hash().await;
let wallet = Wallet::try_obtain_wallet(wallet_key, None)
.await
.unwrap_or_else(|err| panic!("Wallet decryption failed: {err}"));
let privkey = &wallet.saved.private_key;
let proof = Wallet::sign_transaction(&hash, privkey).await;
let vrf = Self::generate_random_number(&proof).await;
VrfBlock {
unmined_block: self,
vrf,
proof,
}
}
// Hash the unmined block header for VRF signing.
pub async fn hash(&self) -> String {
let serialized = to_string(self).unwrap();
skein_256_hash_data(&serialized)
}
// Calculate the next difficulty using the rolling average and target block time.
fn calculate_new_difficulty(
current_difficulty: u64,
difficulty_average: u64,
average_duration: Duration,
) -> u64 {
let lower_bound = Duration::from_secs(14);
let upper_bound = Duration::from_secs(16);
// When the rolling average is already within the target window,
// use the cached mean difficulty exactly.
if difficulty_average > 0
&& average_duration >= lower_bound
&& average_duration <= upper_bound
{
return difficulty_average;
}
// Outside the target window, apply the capped 30% adjustment
// with integer math to keep the result stable.
let adjustment = current_difficulty.saturating_mul(30).saturating_div(100);
if average_duration > upper_bound {
current_difficulty.saturating_add(adjustment)
} else if average_duration < lower_bound {
current_difficulty.saturating_sub(adjustment)
} else {
current_difficulty
}
}
// Adjust difficulty based on the latest saved block averages.
pub async fn adjust_difficulty(
current_timestamp: u32,
db: &Db,
current_difficulty: u64,
) -> u64 {
let block_number = get_height(db);
// Refresh rolling block data before reading averages.
update_block_data(block_number).await;
// Get the current rolling difficulty and duration averages.
let (difficulty_average, average_duration) = calculate_averages(current_timestamp).await;
// Apply the bounded difficulty adjustment.
Self::calculate_new_difficulty(current_difficulty, difficulty_average, average_duration)
}
}
impl VrfBlock {
pub async fn hash(&self) -> String {
// Hash the full VRF header for indexing and validation.
let serialized = to_string(self).unwrap();
skein_256_hash_data(&serialized)
}
pub async fn to_bytes(&self) -> tokio::io::Result<Vec<u8>> {
// 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?;
2026-05-26 06:24:57 +00:00
let miner_bytes = Wallet::short_address_to_bytes(&self.unmined_block.miner)
.ok_or_else(|| tokio::io::Error::other("Invalid short miner address"))?;
2026-05-24 17:56:57 +00:00
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?;
cursor.write_all(&decode(&self.proof).unwrap()).await?;
Ok(buffer)
}
pub async fn from_bytes(bytes: &[u8]) -> tokio::io::Result<Self> {
// A VRF header must be exactly the fixed header byte length.
if bytes.len() != VRF_BLOCK_BYTES {
2026-05-26 06:24:57 +00:00
return Err(tokio::io::Error::other("Invalid Byte Count for Block"));
2026-05-24 17:56:57 +00:00
}
// 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?;
2026-05-26 06:24:57 +00:00
let miner = Wallet::bytes_to_short_address(&miner_bytes)
.ok_or_else(|| tokio::io::Error::other("Invalid short miner address"))?;
2026-05-24 17:56:57 +00:00
// Decode parent hash, difficulty, nonce, VRF number, and proof.
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 mut proof_bytes = vec![0; Wallet::SIGNATURE_LENGTH];
cursor.read_exact(&mut proof_bytes).await?;
let proof = encode(&proof_bytes);
let unmined_block = UnminedBlock {
timestamp,
miner,
previous_hash,
next_block_difficulty,
nonce,
};
Ok(VrfBlock {
unmined_block,
vrf,
proof,
})
}
}
impl Block {
pub async fn to_bytes(&self) -> tokio::io::Result<Vec<u8>> {
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);
// 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);
}
}
}
Ok(buffer)
}
}