Contractless/src/blocks/block.rs

407 lines
16 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;
2026-06-01 19:51:23 +00:00
use crate::records::memory::averages::asert_genesis_anchor;
2026-06-13 19:51:54 +00:00
use crate::records::memory::chain_state::cached_chain_height;
2026-05-24 17:56:57 +00:00
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;
2026-06-01 19:51:23 +00:00
const ASERT_HALF_LIFE_SECONDS: i128 = 300;
const ASERT_RADIX_BITS: i128 = 16;
const ASERT_FIXED_ONE: i128 = 1 << ASERT_RADIX_BITS;
2026-05-24 17:56:57 +00:00
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, private_key: &str) -> VrfBlock {
2026-05-24 17:56:57 +00:00
// Sign the unmined header hash with the miner wallet and derive
// the VRF number from that signature.
let hash = self.hash().await;
let proof = Wallet::sign_transaction(&hash, private_key).await;
2026-05-24 17:56:57 +00:00
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)
}
fn asert_target(anchor_target: u64, height_delta: u32, time_delta: i128) -> u64 {
2026-06-07 15:23:52 +00:00
// 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);
2026-05-24 17:56:57 +00:00
} else {
let right_shift = (-shifts) as u32;
if right_shift >= 128 {
return 1;
}
target >>= right_shift;
2026-05-24 17:56:57 +00:00
}
target >>= ASERT_RADIX_BITS as u32;
target.clamp(1, u64::MAX as u128) as u64
2026-05-24 17:56:57 +00:00
}
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)
}
2026-06-01 19:51:23 +00:00
// Adjust difficulty based on ASERT drift from the genesis anchor.
2026-05-24 17:56:57 +00:00
pub async fn adjust_difficulty(
current_timestamp: u32,
db: &Db,
current_difficulty: u64,
) -> u64 {
2026-06-13 19:51:54 +00:00
let block_number = cached_chain_height()
.await
.unwrap_or_else(|| get_height(db));
let candidate_height = block_number + 1;
2026-05-24 17:56:57 +00:00
2026-06-01 19:51:23 +00:00
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);
2026-05-24 17:56:57 +00:00
Self::clamp_per_block(raw_target, current_difficulty)
2026-05-24 17:56:57 +00:00
}
}
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);
}
2026-07-11 16:28:11 +00:00
Transaction::StorageKey(storage_key_tx) => {
let tx_bytes = storage_key_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);
}
2026-07-16 00:18:13 +00:00
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);
}
2026-05-24 17:56:57 +00:00
}
}
Ok(buffer)
}
}