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)
|
|
|
|
|
}
|
|
|
|
|
}
|