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};
|
|
|
|
|
|
2026-06-01 14:29:11 +00:00
|
|
|
const TARGET_BLOCK_SECONDS: i128 = 15;
|
2026-06-01 19:51:23 +00:00
|
|
|
const ASERT_HALF_LIFE_SECONDS: i128 = 300;
|
2026-06-01 14:29:11 +00:00
|
|
|
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;
|
2026-08-01 20:36:10 +00:00
|
|
|
pub const VRF_BLOCK_BYTES: usize = UNMINED_BLOCK_BYTES + 16;
|
|
|
|
|
pub const BLOCK_HEADER_BYTES: usize = VRF_BLOCK_BYTES + Wallet::SIGNATURE_LENGTH;
|
2026-05-24 17:56:57 +00:00
|
|
|
|
2026-08-01 20:36:10 +00:00
|
|
|
// UnminedBlock is the deterministic header data used to derive the VRF.
|
2026-05-24 17:56:57 +00:00
|
|
|
#[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
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-01 20:36:10 +00:00
|
|
|
// VrfBlock adds the deterministic VRF number to the unsigned header.
|
|
|
|
|
#[derive(Debug, Serialize, Clone)] // 83 bytes
|
2026-05-24 17:56:57 +00:00
|
|
|
pub struct VrfBlock {
|
|
|
|
|
pub unmined_block: UnminedBlock, // 67 bytes unsigned block header fields
|
2026-08-01 20:36:10 +00:00
|
|
|
pub vrf: u128, // 16 bytes deterministic mining value
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
|
|
|
|
|
2026-08-01 20:36:10 +00:00
|
|
|
// Block stores the deterministic VRF header, miner proof, and transactions.
|
2026-05-24 17:56:57 +00:00
|
|
|
#[derive(Debug, Serialize)] // header is 749 bytes plus transactions
|
|
|
|
|
pub struct Block {
|
|
|
|
|
pub vrf_block: VrfBlock,
|
2026-08-01 20:36:10 +00:00
|
|
|
pub proof: String, // 666 bytes miner signature over the VRF header hash
|
2026-05-24 17:56:57 +00:00
|
|
|
pub transactions: Vec<Transaction>,
|
2026-07-21 16:47:28 +00:00
|
|
|
// 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<Vec<u8>>,
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
2026-08-01 20:36:10 +00:00
|
|
|
// Hash the supplied deterministic seed with Skein512, then fold the 64-byte result
|
2026-05-24 17:56:57 +00:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-01 20:36:10 +00:00
|
|
|
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;
|
2026-05-24 17:56:57 +00:00
|
|
|
VrfBlock {
|
|
|
|
|
unmined_block: self,
|
|
|
|
|
vrf,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-01 20:36:10 +00:00
|
|
|
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.
|
2026-05-24 17:56:57 +00:00
|
|
|
pub async fn hash(&self) -> String {
|
|
|
|
|
let serialized = to_string(self).unwrap();
|
2026-08-01 20:36:10 +00:00
|
|
|
skein_512_hash_data(&serialized)
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-01 14:29:11 +00:00
|
|
|
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.
|
2026-06-01 14:29:11 +00:00
|
|
|
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 {
|
2026-06-01 14:29:11 +00:00
|
|
|
let right_shift = (-shifts) as u32;
|
|
|
|
|
if right_shift >= 128 {
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
target >>= right_shift;
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
2026-06-01 14:29:11 +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
|
|
|
}
|
|
|
|
|
|
2026-06-01 14:29:11 +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));
|
2026-06-01 14:29:11 +00:00
|
|
|
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
|
2026-06-01 14:29:11 +00:00
|
|
|
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
|
|
|
|
2026-06-01 14:29:11 +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 {
|
2026-08-01 20:36:10 +00:00
|
|
|
// Hash only deterministic header data for mining and chain identity.
|
2026-05-24 17:56:57 +00:00
|
|
|
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?;
|
|
|
|
|
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
|
|
|
|
2026-08-01 20:36:10 +00:00
|
|
|
// Decode parent hash, difficulty, nonce, and deterministic VRF number.
|
2026-05-24 17:56:57 +00:00
|
|
|
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,
|
|
|
|
|
};
|
2026-08-01 20:36:10 +00:00
|
|
|
Ok(VrfBlock { unmined_block, vrf })
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
2026-08-01 20:36:10 +00:00
|
|
|
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);
|
2026-05-24 17:56:57 +00:00
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
}
|
2026-07-24 22:12:25 +00:00
|
|
|
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);
|
|
|
|
|
}
|
2026-07-11 16:28:11 +00:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-01 20:36:10 +00:00
|
|
|
|
|
|
|
|
#[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);
|
|
|
|
|
}
|
|
|
|
|
}
|