use crate::economic::get_block_subsidy;
use crate::error::Result;
use crate::pow::{check_proof_of_work, get_next_work_required};
use crate::transaction::check_transaction;
use crate::types::*;
use blvm_spec_lock::spec_locked;
#[cfg(test)]
use crate::transaction::is_coinbase;
#[spec_locked("12.1")]
pub fn create_new_block(
utxo_set: &UtxoSet,
mempool_txs: &[Transaction],
height: Natural,
prev_header: &BlockHeader,
prev_headers: &[BlockHeader],
coinbase_script: &ByteString,
coinbase_address: &ByteString,
) -> Result<Block> {
let block_time = get_current_timestamp();
create_new_block_with_time(
utxo_set,
mempool_txs,
height,
prev_header,
prev_headers,
coinbase_script,
coinbase_address,
block_time,
)
}
#[allow(clippy::too_many_arguments)]
#[spec_locked("12.1")]
pub fn create_new_block_with_time(
utxo_set: &UtxoSet,
mempool_txs: &[Transaction],
height: Natural,
prev_header: &BlockHeader,
prev_headers: &[BlockHeader],
coinbase_script: &ByteString,
coinbase_address: &ByteString,
block_time: u64,
) -> Result<Block> {
use crate::mempool::{accept_to_memory_pool, Mempool, MempoolResult};
let coinbase_tx = create_coinbase_transaction(
height,
get_block_subsidy(height),
coinbase_script,
coinbase_address,
)?;
let mut selected_txs = Vec::new();
let temp_mempool: Mempool = std::collections::HashSet::new();
for tx in mempool_txs {
if check_transaction(tx)? != ValidationResult::Valid {
continue;
}
let time_context = Some(TimeContext {
network_time: block_time,
median_time_past: block_time, });
match accept_to_memory_pool(tx, None, utxo_set, &temp_mempool, height, time_context)? {
MempoolResult::Accepted => {
selected_txs.push(tx.clone());
}
MempoolResult::Rejected(_reason) => {
#[cfg(test)]
eprintln!("Transaction rejected: {_reason}");
continue;
}
}
}
let mut transactions = vec![coinbase_tx];
transactions.extend(selected_txs);
let merkle_root = calculate_merkle_root(&transactions)?;
let next_work = get_next_work_required(prev_header, prev_headers)?;
let header = BlockHeader {
version: 1,
prev_block_hash: calculate_block_hash(prev_header),
merkle_root,
timestamp: block_time,
bits: next_work,
nonce: 0, };
Ok(Block {
header,
transactions: transactions.into_boxed_slice(),
})
}
#[track_caller] #[spec_locked("12.3")]
pub fn mine_block(mut block: Block, max_attempts: Natural) -> Result<(Block, MiningResult)> {
for nonce in 0..max_attempts {
block.header.nonce = nonce;
if check_proof_of_work(&block.header).unwrap_or(false) {
return Ok((block, MiningResult::Success));
}
}
Ok((block, MiningResult::Failure))
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct BlockTemplate {
pub header: BlockHeader,
pub coinbase_tx: Transaction,
pub transactions: Vec<Transaction>,
pub target: u128,
pub height: Natural,
pub timestamp: Natural,
}
#[spec_locked("12.4")]
pub fn create_block_template(
utxo_set: &UtxoSet,
mempool_txs: &[Transaction],
height: Natural,
prev_header: &BlockHeader,
prev_headers: &[BlockHeader],
coinbase_script: &ByteString,
coinbase_address: &ByteString,
) -> Result<BlockTemplate> {
let block = create_new_block(
utxo_set,
mempool_txs,
height,
prev_header,
prev_headers,
coinbase_script,
coinbase_address,
)?;
let target = expand_target(block.header.bits)?;
let header = block.header.clone();
#[cfg(feature = "production")]
let coinbase_tx = {
use crate::optimizations::_optimized_access::get_proven_by_;
get_proven_by_(&block.transactions, 0)
.ok_or_else(|| {
crate::error::ConsensusError::BlockValidation("Block has no transactions".into())
})?
.clone()
};
#[cfg(not(feature = "production"))]
let coinbase_tx = block.transactions[0].clone();
Ok(BlockTemplate {
header: block.header,
coinbase_tx,
transactions: block.transactions[1..].to_vec(),
target,
height,
timestamp: header.timestamp,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MiningResult {
Success,
Failure,
}
#[spec_locked("12.2")]
fn create_coinbase_transaction(
height: Natural,
subsidy: Integer,
script: &ByteString,
address: &ByteString,
) -> Result<Transaction> {
let lock_time = height.saturating_sub(13);
let coinbase_input = TransactionInput {
prevout: OutPoint {
hash: [0u8; 32],
index: 0xffffffff,
},
script_sig: script.clone(),
sequence: 0xfffffffe, };
let coinbase_output = TransactionOutput {
value: subsidy,
script_pubkey: address.clone(),
};
Ok(Transaction {
version: 1,
inputs: crate::tx_inputs![coinbase_input],
outputs: crate::tx_outputs![coinbase_output],
lock_time,
})
}
#[track_caller] #[cfg_attr(feature = "production", inline(always))]
#[cfg_attr(not(feature = "production"), inline)]
#[spec_locked("8.4.1")]
pub fn calculate_merkle_root(transactions: &[Transaction]) -> Result<Hash> {
if transactions.is_empty() {
return Err(crate::error::ConsensusError::InvalidProofOfWork(
"Cannot calculate merkle root for empty transaction list".into(),
));
}
#[cfg(feature = "production")]
let mut hashes: Vec<crate::optimizations::CacheAlignedHash> = {
use crate::optimizations::simd_vectorization;
let serialized_txs: Vec<Vec<u8>> = {
#[cfg(feature = "rayon")]
{
use rayon::prelude::*;
transactions
.par_iter()
.map(serialize_tx_for_hash) .collect()
}
#[cfg(not(feature = "rayon"))]
{
transactions
.iter()
.map(serialize_tx_for_hash) .collect()
}
};
let tx_data_refs: Vec<&[u8]> = serialized_txs.iter().map(|v| v.as_slice()).collect();
simd_vectorization::batch_double_sha256_aligned(&tx_data_refs)
};
#[cfg(not(feature = "production"))]
let mut hashes: Vec<Hash> = {
let mut hashes = Vec::with_capacity(transactions.len());
for tx in transactions {
hashes.push(calculate_tx_hash(tx));
}
hashes
};
#[cfg(feature = "production")]
{
use crate::optimizations::CacheAlignedHash;
let mut mutated = false;
while hashes.len() > 1 {
let mut level_mutated = false;
let mut pos = 0;
while pos + 1 < hashes.len() {
if hashes[pos].as_bytes() == hashes[pos + 1].as_bytes() {
level_mutated = true;
}
pos += 2;
}
if level_mutated {
mutated = true;
}
if hashes.len() & 1 != 0 {
let last = hashes[hashes.len() - 1].clone();
hashes.push(last);
}
let next_level: Vec<CacheAlignedHash> = hashes
.chunks(2)
.map(|chunk| {
let mut combined = [0u8; 64];
combined[..32].copy_from_slice(chunk[0].as_bytes());
combined[32..].copy_from_slice(if chunk.len() == 2 {
chunk[1].as_bytes()
} else {
chunk[0].as_bytes()
});
CacheAlignedHash::new(double_sha256_hash(&combined))
})
.collect();
hashes = next_level;
}
if mutated {
return Err(crate::error::ConsensusError::InvalidProofOfWork(
"Merkle root mutation detected (CVE-2012-2459)".into(),
));
}
Ok(*hashes[0].as_bytes())
}
#[cfg(not(feature = "production"))]
{
let (root, mutated) = merkle_tree_from_hashes(&mut hashes)?;
if mutated {
return Err(crate::error::ConsensusError::InvalidProofOfWork(
"Merkle root mutation detected (CVE-2012-2459)".into(),
));
}
Ok(root)
}
}
#[spec_locked("8.4.1")]
pub fn calculate_merkle_root_from_tx_ids(tx_ids: &[Hash]) -> Result<Hash> {
let (root, mutated) = compute_merkle_root_and_mutated(tx_ids)?;
if mutated {
return Err(crate::error::ConsensusError::InvalidProofOfWork(
"Merkle root mutation detected (CVE-2012-2459)".into(),
));
}
Ok(root)
}
#[spec_locked("8.4.1")]
pub fn compute_merkle_root_and_mutated(tx_ids: &[Hash]) -> Result<(Hash, bool)> {
if tx_ids.is_empty() {
return Err(crate::error::ConsensusError::InvalidProofOfWork(
"Cannot calculate merkle root for empty transaction list".into(),
));
}
let mut hashes = tx_ids.to_vec();
merkle_tree_from_hashes(&mut hashes)
}
#[spec_locked("8.4.1")]
fn merkle_tree_from_hashes(hashes: &mut Vec<Hash>) -> Result<(Hash, bool)> {
let mut mutated = false;
while hashes.len() > 1 {
for pos in (0..hashes.len().saturating_sub(1)).step_by(2) {
if hashes[pos] == hashes[pos + 1] {
mutated = true;
}
}
if hashes.len() & 1 != 0 {
hashes.push(hashes[hashes.len() - 1]);
}
let mut next_level = Vec::with_capacity(hashes.len() / 2);
for chunk in hashes.chunks(2) {
let mut combined = [0u8; 64];
combined[..32].copy_from_slice(&chunk[0]);
combined[32..].copy_from_slice(if chunk.len() == 2 {
&chunk[1]
} else {
&chunk[0]
});
next_level.push(double_sha256_hash(&combined));
}
*hashes = next_level;
}
Ok((hashes[0], mutated))
}
fn serialize_tx_for_hash(tx: &Transaction) -> Vec<u8> {
#[cfg(feature = "production")]
let mut data = {
use crate::optimizations::prealloc_tx_buffer;
prealloc_tx_buffer()
};
#[cfg(not(feature = "production"))]
let mut data = Vec::new();
data.extend_from_slice(&(tx.version as u32).to_le_bytes());
data.extend_from_slice(&encode_varint(tx.inputs.len() as u64));
for input in &tx.inputs {
data.extend_from_slice(&input.prevout.hash);
data.extend_from_slice(&input.prevout.index.to_le_bytes());
data.extend_from_slice(&encode_varint(input.script_sig.len() as u64));
data.extend_from_slice(&input.script_sig);
data.extend_from_slice(&(input.sequence as u32).to_le_bytes());
}
data.extend_from_slice(&encode_varint(tx.outputs.len() as u64));
for output in &tx.outputs {
data.extend_from_slice(&(output.value as u64).to_le_bytes());
data.extend_from_slice(&encode_varint(output.script_pubkey.len() as u64));
data.extend_from_slice(&output.script_pubkey);
}
data.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
data
}
#[allow(dead_code)] fn calculate_tx_hash(tx: &Transaction) -> Hash {
let data = serialize_tx_for_hash(tx);
let hash1 = sha256_hash(&data);
sha256_hash(&hash1)
}
fn encode_varint(value: u64) -> Vec<u8> {
if value < 0xfd {
vec![value as u8]
} else if value <= 0xffff {
let mut result = vec![0xfd];
result.extend_from_slice(&(value as u16).to_le_bytes());
result
} else if value <= 0xffffffff {
let mut result = vec![0xfe];
result.extend_from_slice(&(value as u32).to_le_bytes());
result
} else {
let mut result = vec![0xff];
result.extend_from_slice(&value.to_le_bytes());
result
}
}
#[spec_locked("7.2")]
fn calculate_block_hash(header: &BlockHeader) -> Hash {
let mut data = Vec::new();
data.extend_from_slice(&(header.version as u32).to_le_bytes());
data.extend_from_slice(&header.prev_block_hash);
data.extend_from_slice(&header.merkle_root);
data.extend_from_slice(&(header.timestamp as u32).to_le_bytes());
data.extend_from_slice(&(header.bits as u32).to_le_bytes());
data.extend_from_slice(&(header.nonce as u32).to_le_bytes());
sha256_hash(&data)
}
#[inline(always)]
fn sha256_hash(data: &[u8]) -> Hash {
use crate::crypto::OptimizedSha256;
OptimizedSha256::new().hash(data)
}
#[inline(always)]
fn double_sha256_hash(data: &[u8]) -> Hash {
use crate::crypto::OptimizedSha256;
OptimizedSha256::new().hash256(data)
}
#[spec_locked("7.1")]
fn expand_target(bits: Natural) -> Result<u128> {
let exponent = (bits >> 24) as u8;
let mantissa = bits & 0x00ffffff;
if exponent <= 3 {
let shift = 8 * (3 - exponent);
Ok((mantissa >> shift) as u128)
} else {
let shift = 8 * (exponent - 3);
if shift >= 104 {
return Err(crate::error::ConsensusError::InvalidProofOfWork(
"Target too large".into(),
));
}
Ok((mantissa as u128) << shift)
}
}
fn get_current_timestamp() -> Natural {
1231006505
}
#[cfg(test)]
mod tests {
use super::*;
use crate::opcodes::*;
#[test]
fn test_create_new_block() {
let mut utxo_set = UtxoSet::default();
let outpoint = OutPoint {
hash: [1; 32],
index: 0,
};
let utxo = UTXO {
value: 10000,
script_pubkey: vec![].into(),
height: 0,
is_coinbase: false,
};
utxo_set.insert(outpoint, std::sync::Arc::new(utxo));
let mempool_txs = vec![create_valid_transaction()];
let height = 100;
let prev_header = create_valid_block_header();
let mut prev_header2 = prev_header.clone();
prev_header2.timestamp = prev_header.timestamp + 600; let prev_headers = vec![prev_header.clone(), prev_header2];
let coinbase_script = vec![OP_1];
let coinbase_address = vec![OP_1];
let result = create_new_block(
&utxo_set,
&mempool_txs,
height,
&prev_header,
&prev_headers,
&coinbase_script,
&coinbase_address,
);
if let Ok(block) = result {
assert_eq!(block.transactions.len(), 2); assert!(is_coinbase(&block.transactions[0]));
assert_eq!(block.header.version, 1);
assert_eq!(block.header.timestamp, 1231006505);
} else {
assert!(result.is_err());
}
}
#[test]
fn test_mine_block_success() {
let block = create_test_block();
let result = mine_block(block, 1000);
assert!(result.is_ok());
let (mined_block, mining_result) = result.unwrap();
assert!(matches!(
mining_result,
MiningResult::Success | MiningResult::Failure
));
assert_eq!(mined_block.header.version, 1);
}
#[test]
fn test_create_block_template() {
let utxo_set = UtxoSet::default();
let mempool_txs = vec![create_valid_transaction()];
let height = 100;
let prev_header = create_valid_block_header();
let prev_headers = vec![prev_header.clone()];
let coinbase_script = vec![OP_1];
let coinbase_address = vec![OP_1];
let result = create_block_template(
&utxo_set,
&mempool_txs,
height,
&prev_header,
&prev_headers,
&coinbase_script,
&coinbase_address,
);
assert!(result.is_err());
}
#[test]
fn test_coinbase_transaction() {
let height = 100;
let subsidy = get_block_subsidy(height);
let script = vec![OP_1];
let address = vec![OP_1];
let coinbase_tx = create_coinbase_transaction(height, subsidy, &script, &address).unwrap();
assert!(is_coinbase(&coinbase_tx));
assert_eq!(coinbase_tx.outputs[0].value, subsidy);
assert_eq!(coinbase_tx.inputs[0].prevout.hash, [0u8; 32]);
assert_eq!(coinbase_tx.inputs[0].prevout.index, 0xffffffff);
}
#[test]
fn test_merkle_root_calculation() {
let txs = vec![create_valid_transaction(), create_valid_transaction()];
let merkle_root = calculate_merkle_root(&txs).unwrap();
assert_ne!(merkle_root, [0u8; 32]);
}
#[test]
fn test_merkle_root_empty() {
let txs = vec![];
let result = calculate_merkle_root(&txs);
assert!(result.is_err());
}
#[test]
fn test_create_block_template_comprehensive() {
let mut utxo_set = UtxoSet::default();
let outpoint = OutPoint {
hash: [1; 32],
index: 0,
};
let utxo = UTXO {
value: 10000,
script_pubkey: vec![].into(),
height: 0,
is_coinbase: false,
};
utxo_set.insert(outpoint, std::sync::Arc::new(utxo));
let mempool_txs = vec![create_valid_transaction()];
let height = 100;
let prev_header = create_valid_block_header();
let mut prev_header2 = prev_header.clone();
prev_header2.timestamp = prev_header.timestamp + 600; let prev_headers = vec![prev_header.clone(), prev_header2];
let coinbase_script = vec![OP_1];
let coinbase_address = vec![OP_2];
let result = create_block_template(
&utxo_set,
&mempool_txs,
height,
&prev_header,
&prev_headers,
&coinbase_script,
&coinbase_address,
);
if let Ok(template) = result {
assert_eq!(template.height, height);
assert!(template.target > 0);
assert!(is_coinbase(&template.coinbase_tx));
assert_eq!(template.transactions.len(), 1);
} else {
assert!(result.is_err());
}
}
#[test]
fn test_mine_block_attempts() {
let block = create_test_block();
let (mined_block, result) = mine_block(block, 1000).unwrap();
assert!(matches!(
result,
MiningResult::Success | MiningResult::Failure
));
assert_eq!(mined_block.header.version, 1);
}
#[test]
fn test_mine_block_failure() {
let block = create_test_block();
let (mined_block, result) = mine_block(block, 0).unwrap();
assert_eq!(result, MiningResult::Failure);
assert_eq!(mined_block.header.nonce, 0);
}
#[test]
fn test_create_coinbase_transaction() {
let height = 100;
let subsidy = 5000000000;
let script = vec![0x51, 0x52];
let address = vec![0x53, 0x54];
let coinbase_tx = create_coinbase_transaction(height, subsidy, &script, &address).unwrap();
assert!(is_coinbase(&coinbase_tx));
assert_eq!(coinbase_tx.outputs.len(), 1);
assert_eq!(coinbase_tx.outputs[0].value, subsidy);
assert_eq!(coinbase_tx.outputs[0].script_pubkey, address);
assert_eq!(coinbase_tx.inputs[0].script_sig, script);
assert_eq!(coinbase_tx.inputs[0].prevout.hash, [0u8; 32]);
assert_eq!(coinbase_tx.inputs[0].prevout.index, 0xffffffff);
}
#[test]
fn test_calculate_tx_hash() {
let tx = create_valid_transaction();
let hash = calculate_tx_hash(&tx);
assert_eq!(hash.len(), 32);
let hash2 = calculate_tx_hash(&tx);
assert_eq!(hash, hash2);
}
#[test]
fn test_calculate_tx_hash_different_txs() {
let tx1 = create_valid_transaction();
let mut tx2 = tx1.clone();
tx2.version = 2;
let hash1 = calculate_tx_hash(&tx1);
let hash2 = calculate_tx_hash(&tx2);
assert_ne!(hash1, hash2);
}
#[test]
fn test_encode_varint_small() {
let encoded = encode_varint(0x42);
assert_eq!(encoded, vec![0x42]);
}
#[test]
fn test_encode_varint_medium() {
let encoded = encode_varint(0x1234);
assert_eq!(encoded.len(), 3);
assert_eq!(encoded[0], 0xfd);
}
#[test]
fn test_encode_varint_large() {
let encoded = encode_varint(0x12345678);
assert_eq!(encoded.len(), 5);
assert_eq!(encoded[0], 0xfe);
}
#[test]
fn test_encode_varint_huge() {
let encoded = encode_varint(0x123456789abcdef0);
assert_eq!(encoded.len(), 9);
assert_eq!(encoded[0], 0xff);
}
#[test]
fn test_calculate_block_hash() {
let header = create_valid_block_header();
let hash = calculate_block_hash(&header);
assert_eq!(hash.len(), 32);
let hash2 = calculate_block_hash(&header);
assert_eq!(hash, hash2);
}
#[test]
fn test_calculate_block_hash_different_headers() {
let header1 = create_valid_block_header();
let mut header2 = header1.clone();
header2.version = 2;
let hash1 = calculate_block_hash(&header1);
let hash2 = calculate_block_hash(&header2);
assert_ne!(hash1, hash2);
}
#[test]
fn test_sha256_hash() {
let data = b"hello world";
let hash = sha256_hash(data);
assert_eq!(hash.len(), 32);
let hash2 = sha256_hash(data);
assert_eq!(hash, hash2);
}
#[test]
fn test_sha256_hash_different_data() {
let data1 = b"hello";
let data2 = b"world";
let hash1 = sha256_hash(data1);
let hash2 = sha256_hash(data2);
assert_ne!(hash1, hash2);
}
#[test]
fn test_expand_target_small() {
let bits = 0x0300ffff; let target = expand_target(bits).unwrap();
assert!(target > 0);
}
#[test]
fn test_expand_target_medium() {
let bits = 0x0600ffff; let target = expand_target(bits).unwrap();
assert!(target > 0);
}
#[test]
fn test_expand_target_too_large() {
let bits = 0x2000ffff; let result = expand_target(bits);
assert!(result.is_err());
}
#[test]
fn test_get_current_timestamp() {
let timestamp = get_current_timestamp();
assert_eq!(timestamp, 1231006505);
}
#[test]
fn test_merkle_root_single_transaction() {
let txs = vec![create_valid_transaction()];
let merkle_root = calculate_merkle_root(&txs).unwrap();
assert_eq!(merkle_root.len(), 32);
assert_ne!(merkle_root, [0u8; 32]);
}
#[test]
fn test_merkle_root_three_transactions() {
let txs = vec![
create_valid_transaction(),
create_valid_transaction(),
create_valid_transaction(),
];
let merkle_root = calculate_merkle_root(&txs).unwrap();
assert_eq!(merkle_root.len(), 32);
assert_ne!(merkle_root, [0u8; 32]);
}
#[test]
fn test_merkle_root_five_transactions() {
let txs = vec![
create_valid_transaction(),
create_valid_transaction(),
create_valid_transaction(),
create_valid_transaction(),
create_valid_transaction(),
];
let merkle_root = calculate_merkle_root(&txs).unwrap();
assert_eq!(merkle_root.len(), 32);
assert_ne!(merkle_root, [0u8; 32]);
}
#[test]
fn test_block_template_fields() {
let mut utxo_set = UtxoSet::default();
let outpoint = OutPoint {
hash: [1; 32],
index: 0,
};
let utxo = UTXO {
value: 10000,
script_pubkey: vec![].into(),
height: 0,
is_coinbase: false,
};
utxo_set.insert(outpoint, std::sync::Arc::new(utxo));
let mempool_txs = vec![create_valid_transaction()];
let height = 100;
let prev_header = create_valid_block_header();
let prev_headers = vec![prev_header.clone(), prev_header.clone()];
let coinbase_script = vec![OP_1];
let coinbase_address = vec![OP_2];
let result = create_block_template(
&utxo_set,
&mempool_txs,
height,
&prev_header,
&prev_headers,
&coinbase_script,
&coinbase_address,
);
if let Ok(template) = result {
assert_eq!(template.height, height);
assert!(template.target > 0);
assert!(template.timestamp > 0);
assert!(is_coinbase(&template.coinbase_tx));
assert_eq!(template.transactions.len(), 1);
assert_eq!(template.header.version, 1);
} else {
assert!(result.is_err());
}
}
fn create_valid_transaction() -> Transaction {
use std::cell::Cell;
thread_local! {
static COUNTER: Cell<u64> = Cell::new(0);
}
let counter = COUNTER.with(|c| {
let val = c.get();
c.set(val + 1);
val
});
Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [1; 32].into(), index: 0,
},
script_sig: {
let mut sig = vec![OP_1]; if counter > 0 {
sig.push(PUSH_1_BYTE); sig.push((counter & 0xff) as u8); }
sig
},
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 1000 + counter as i64, script_pubkey: vec![].into(),
}]
.into(),
lock_time: 0,
}
}
fn create_valid_block_header() -> BlockHeader {
BlockHeader {
version: 1,
prev_block_hash: [0; 32],
merkle_root: [0; 32],
timestamp: 1231006505,
bits: 0x0600ffff, nonce: 0,
}
}
fn create_test_block() -> Block {
Block {
header: create_valid_block_header(),
transactions: vec![create_valid_transaction()].into_boxed_slice(),
}
}
#[test]
fn test_create_coinbase_transaction_zero_subsidy() {
let height = 100;
let subsidy = 0; let script = vec![OP_1];
let address = vec![OP_1];
let coinbase_tx = create_coinbase_transaction(height, subsidy, &script, &address).unwrap();
assert!(is_coinbase(&coinbase_tx));
assert_eq!(coinbase_tx.outputs[0].value, 0);
}
#[test]
fn test_create_coinbase_transaction_large_subsidy() {
let height = 100;
let subsidy = 2100000000000000; let script = vec![OP_1];
let address = vec![OP_1];
let coinbase_tx = create_coinbase_transaction(height, subsidy, &script, &address).unwrap();
assert!(is_coinbase(&coinbase_tx));
assert_eq!(coinbase_tx.outputs[0].value, subsidy);
}
#[test]
fn test_create_coinbase_transaction_empty_script() {
let height = 100;
let subsidy = 5000000000;
let script = vec![]; let address = vec![OP_1];
let coinbase_tx = create_coinbase_transaction(height, subsidy, &script, &address).unwrap();
assert!(is_coinbase(&coinbase_tx));
assert_eq!(coinbase_tx.outputs[0].value, subsidy);
}
#[test]
fn test_create_coinbase_transaction_empty_address() {
let height = 100;
let subsidy = 5000000000;
let script = vec![OP_1];
let address = vec![];
let coinbase_tx = create_coinbase_transaction(height, subsidy, &script, &address).unwrap();
assert!(is_coinbase(&coinbase_tx));
assert_eq!(coinbase_tx.outputs[0].value, subsidy);
}
#[test]
fn test_calculate_merkle_root_single_transaction() {
let txs = vec![create_valid_transaction()];
let merkle_root = calculate_merkle_root(&txs).unwrap();
assert_eq!(merkle_root.len(), 32);
assert_ne!(merkle_root, [0u8; 32]);
}
#[test]
fn test_calculate_merkle_root_three_transactions() {
let txs = vec![
create_valid_transaction(),
create_valid_transaction(),
create_valid_transaction(),
];
let merkle_root = calculate_merkle_root(&txs).unwrap();
assert_eq!(merkle_root.len(), 32);
assert_ne!(merkle_root, [0u8; 32]);
}
#[test]
fn test_calculate_merkle_root_five_transactions() {
let txs = vec![
create_valid_transaction(),
create_valid_transaction(),
create_valid_transaction(),
create_valid_transaction(),
create_valid_transaction(),
];
let merkle_root = calculate_merkle_root(&txs).unwrap();
assert_eq!(merkle_root.len(), 32);
assert_ne!(merkle_root, [0u8; 32]);
}
#[test]
fn test_calculate_tx_hash_different_transactions() {
let tx1 = create_valid_transaction();
let mut tx2 = create_valid_transaction();
tx2.version = 2;
let hash1 = calculate_tx_hash(&tx1);
let hash2 = calculate_tx_hash(&tx2);
assert_ne!(hash1, hash2);
}
#[test]
fn test_sha256_hash_empty_data() {
let data = vec![];
let hash = sha256_hash(&data);
assert_eq!(hash.len(), 32);
}
#[test]
fn test_merkle_tree_uses_double_sha256_not_single() {
let hash_a = [0x01u8; 32];
let hash_b = [0x02u8; 32];
let mut combined = [0u8; 64];
combined[..32].copy_from_slice(&hash_a);
combined[32..].copy_from_slice(&hash_b);
let expected_double = double_sha256_hash(&combined);
let wrong_single = sha256_hash(&combined);
assert_ne!(
expected_double, wrong_single,
"Double SHA256 and single SHA256 must produce different results"
);
let root = calculate_merkle_root_from_tx_ids(&[hash_a, hash_b]).unwrap();
assert_eq!(
root, expected_double,
"Merkle root must use double SHA256, not single SHA256"
);
assert_ne!(
root, wrong_single,
"Merkle root must NOT match single SHA256 result"
);
}
#[test]
fn test_merkle_root_single_tx_equals_txid() {
let tx = create_valid_transaction();
let txid = calculate_tx_hash(&tx);
let root_from_txs = calculate_merkle_root(&[tx]).unwrap();
let root_from_ids = calculate_merkle_root_from_tx_ids(&[txid]).unwrap();
assert_eq!(root_from_txs, txid, "Single tx merkle root must equal txid");
assert_eq!(
root_from_ids, txid,
"Single txid merkle root must equal txid"
);
}
#[test]
fn test_calculate_merkle_root_from_tx_ids_matches_calculate_merkle_root() {
let tx1 = create_valid_transaction();
let tx2 = create_valid_transaction();
let tx3 = create_valid_transaction();
let txid1 = calculate_tx_hash(&tx1);
let txid2 = calculate_tx_hash(&tx2);
let txid3 = calculate_tx_hash(&tx3);
let root_from_txs = calculate_merkle_root(&[tx1, tx2, tx3]).unwrap();
let root_from_ids = calculate_merkle_root_from_tx_ids(&[txid1, txid2, txid3]).unwrap();
assert_eq!(
root_from_txs, root_from_ids,
"calculate_merkle_root and calculate_merkle_root_from_tx_ids must produce identical results"
);
}
#[test]
fn test_calculate_merkle_root_from_tx_ids_two_txs() {
let tx1 = create_valid_transaction();
let tx2 = create_valid_transaction();
let txid1 = calculate_tx_hash(&tx1);
let txid2 = calculate_tx_hash(&tx2);
let root_from_txs = calculate_merkle_root(&[tx1, tx2]).unwrap();
let root_from_ids = calculate_merkle_root_from_tx_ids(&[txid1, txid2]).unwrap();
assert_eq!(root_from_txs, root_from_ids);
}
#[test]
fn test_calculate_merkle_root_from_tx_ids_four_txs() {
let tx1 = create_valid_transaction();
let tx2 = create_valid_transaction();
let tx3 = create_valid_transaction();
let tx4 = create_valid_transaction();
let txid1 = calculate_tx_hash(&tx1);
let txid2 = calculate_tx_hash(&tx2);
let txid3 = calculate_tx_hash(&tx3);
let txid4 = calculate_tx_hash(&tx4);
let root_from_txs = calculate_merkle_root(&[tx1, tx2, tx3, tx4]).unwrap();
let root_from_ids =
calculate_merkle_root_from_tx_ids(&[txid1, txid2, txid3, txid4]).unwrap();
assert_eq!(root_from_txs, root_from_ids);
}
#[test]
fn test_calculate_merkle_root_from_tx_ids_empty() {
let result = calculate_merkle_root_from_tx_ids(&[]);
assert!(result.is_err(), "Empty tx_ids list should fail");
}
#[test]
fn test_merkle_root_deterministic() {
let tx1 = create_valid_transaction();
let tx2 = create_valid_transaction();
let txid1 = calculate_tx_hash(&tx1);
let txid2 = calculate_tx_hash(&tx2);
let root1 = calculate_merkle_root_from_tx_ids(&[txid1, txid2]).unwrap();
let root2 = calculate_merkle_root_from_tx_ids(&[txid1, txid2]).unwrap();
assert_eq!(root1, root2, "Merkle root must be deterministic");
}
#[test]
fn test_merkle_root_order_matters() {
let txid1 = [0x01u8; 32];
let txid2 = [0x02u8; 32];
let root_ab = calculate_merkle_root_from_tx_ids(&[txid1, txid2]).unwrap();
let root_ba = calculate_merkle_root_from_tx_ids(&[txid2, txid1]).unwrap();
assert_ne!(root_ab, root_ba, "Tx order must affect merkle root");
}
#[test]
fn test_double_sha256_hash_known_value() {
let result = double_sha256_hash(&[]);
assert_eq!(result.len(), 32);
let result2 = double_sha256_hash(&[]);
assert_eq!(result, result2);
let single = sha256_hash(&[]);
assert_ne!(
result, single,
"double SHA256 must differ from single SHA256"
);
}
}