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 block_time = 1_700_000_000u64;
let result = create_new_block_with_time(
&utxo_set,
&mempool_txs,
height,
&prev_header,
&prev_headers,
&coinbase_script,
&coinbase_address,
block_time,
Network::Mainnet,
None,
);
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, block_time);
} 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 template = create_block_template(
&utxo_set,
&mempool_txs,
height,
&prev_header,
&prev_headers,
&coinbase_script,
&coinbase_address,
Network::Mainnet,
None,
)
.expect("off-boundary template");
assert_eq!(template.header.bits, prev_header.bits);
assert_eq!(template.height, height);
}
#[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,
Network::Mainnet,
None,
);
if let Ok(template) = result {
assert_eq!(template.height, height);
assert!(!template.target.is_zero());
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_create_coinbase_with_outputs_preserves_order() {
let script = vec![OP_1];
let a = vec![OP_1];
let b = vec![OP_2];
let tx =
create_coinbase_with_outputs(100, &script, &[(10, a.clone()), (20, b.clone())]).unwrap();
assert!(is_coinbase(&tx));
assert_eq!(tx.outputs.len(), 2);
assert_eq!(tx.outputs[0].value, 10);
assert_eq!(tx.outputs[0].script_pubkey, a);
assert_eq!(tx.outputs[1].value, 20);
assert_eq!(tx.outputs[1].script_pubkey, b);
}
#[test]
fn test_create_coinbase_with_outputs_empty_is_error() {
let script = vec![OP_1];
assert!(create_coinbase_with_outputs(1, &script, &[]).is_err());
}
#[test]
fn fit_payouts_tops_up_first_with_fees() {
let a = vec![OP_1];
let b = vec![OP_2];
let fitted = fit_payouts_to_reward(&[(10, a.clone()), (20, b.clone())], 100, 5).unwrap();
assert_eq!(fitted[0].0, 85);
assert_eq!(fitted[1].0, 20);
assert!(fit_payouts_to_reward(&[(90, a)], 50, 0).is_err());
}
#[test]
fn witness_commitment_script_roundtrips_extract() {
let root = [7u8; 32];
let nonce = [0u8; 32];
let script = crate::segwit::witness_commitment_script(&root, &nonce);
let got = crate::segwit::extract_witness_commitment(&script);
assert!(got.is_some());
assert_eq!(script[0], 0x6a);
assert_eq!(script[1], 0x24);
assert_eq!(&script[2..6], &[0xaa, 0x21, 0xa9, 0xed]);
}
#[test]
fn append_witness_commitment_adds_zero_value_op_return() {
let script = vec![OP_1];
let mut cb = create_coinbase_with_outputs(1, &script, &[(50, vec![OP_1])]).unwrap();
let mut txs = vec![cb.clone()];
append_witness_commitment(&mut cb, &mut txs).unwrap();
assert_eq!(cb.outputs.len(), 2);
assert_eq!(cb.outputs[1].value, 0);
assert_eq!(cb.outputs[1].script_pubkey[0], 0x6a);
assert!(crate::economic::check_coinbase_subsidy(&cb, 50, 0));
}
#[test]
fn append_from_nested_differs_from_empty_stacks() {
let script = vec![OP_1];
let cb = create_coinbase_with_outputs(1, &script, &[(50, vec![OP_1])]).unwrap();
let spend = Transaction {
version: 2,
inputs: crate::tx_inputs![TransactionInput {
prevout: OutPoint {
hash: [0x11u8; 32],
index: 0,
},
script_sig: vec![],
sequence: 0xfffffffe,
}],
outputs: crate::tx_outputs![TransactionOutput {
value: 90_000,
script_pubkey: vec![OP_1],
}],
lock_time: 0,
};
let mut empty_cb = cb.clone();
let mut empty_txs = vec![cb.clone(), spend.clone()];
append_witness_commitment(&mut empty_cb, &mut empty_txs).unwrap();
let nested = vec![vec![Vec::new()], vec![vec![vec![OP_1]]]];
let mut real_cb = cb.clone();
let mut real_txs = vec![cb, spend];
append_witness_commitment_from_nested(&mut real_cb, &mut real_txs, Some(&nested)).unwrap();
assert_ne!(
empty_cb.outputs.last().unwrap().script_pubkey,
real_cb.outputs.last().unwrap().script_pubkey,
"mempool witness must change the commitment"
);
let header = BlockHeader {
version: 1,
prev_block_hash: [0u8; 32],
merkle_root: [0u8; 32],
timestamp: 0,
bits: 0,
nonce: 0,
};
let block = Block {
header,
transactions: real_txs.clone().into_boxed_slice(),
};
let root =
crate::segwit::compute_witness_merkle_root_from_nested(&block, &nested, None).unwrap();
let expect = crate::segwit::witness_commitment_script(&root, &[0u8; 32]);
assert_eq!(real_cb.outputs.last().unwrap().script_pubkey, expect);
}
#[test]
fn missing_witness_does_not_commit_empty_stacks() {
let script = vec![OP_1];
let mut cb = create_coinbase_with_outputs(1, &script, &[(50, vec![OP_1])]).unwrap();
let spend = Transaction {
version: 2,
inputs: crate::tx_inputs![TransactionInput {
prevout: OutPoint {
hash: [0x11u8; 32],
index: 0,
},
script_sig: vec![],
sequence: 0xfffffffe,
}],
outputs: crate::tx_outputs![TransactionOutput {
value: 90_000,
script_pubkey: vec![OP_1],
}],
lock_time: 0,
};
let mut txs = vec![cb.clone(), spend.clone()];
let short = vec![vec![Vec::new()]];
assert!(append_witness_commitment_from_nested(&mut cb, &mut txs, Some(&short)).is_err());
assert_eq!(cb.outputs.len(), 1);
let txs = vec![cb, spend.clone()];
assert!(nested_witnesses_for_selected(&txs, &[spend], Some(&[None])).is_err());
}
#[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_pow_expand_target_regtest_minimum_difficulty() {
let target = crate::pow::expand_target(0x207fffff).expect("regtest nBits");
assert!(!target.is_zero());
assert_eq!(target.gbt_target_hex().len(), 64);
}
#[test]
fn test_create_block_template_regtest_nbits() {
let utxo_set = UtxoSet::default();
let prev_header = BlockHeader {
version: 4,
prev_block_hash: [0u8; 32],
merkle_root: [0u8; 32],
timestamp: 1_600_000_000,
bits: 0x207fffff,
nonce: 0,
};
let prev_headers = vec![prev_header.clone(), prev_header.clone()];
let template = create_block_template(
&utxo_set,
&[],
1,
&prev_header,
&prev_headers,
&vec![],
&vec![0x51],
Network::Regtest,
None,
)
.expect("create_block_template must not fail on regtest nBits");
assert_eq!(template.header.bits, 0x207fffff);
let expanded = crate::pow::expand_target(0x207fffff).unwrap();
assert_eq!(template.target, expanded);
assert_ne!(template.target, crate::pow::U256::one());
}
#[test]
fn test_get_current_timestamp() {
let timestamp = get_current_timestamp();
assert!(timestamp > 1_231_006_505);
assert!(timestamp < 4_000_000_000);
}
#[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,
Network::Mainnet,
None,
);
if let Ok(template) = result {
assert_eq!(template.height, height);
assert!(!template.target.is_zero());
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());
}
}
#[test]
fn a_child_of_a_selected_parent_is_in_the_template() {
use crate::block::calculate_tx_id;
let funding = OutPoint {
hash: [4; 32],
index: 0,
};
let mut utxo_set = UtxoSet::default();
utxo_set.insert(
funding,
std::sync::Arc::new(UTXO {
value: 50_000,
script_pubkey: vec![].into(),
height: 0,
is_coinbase: false,
}),
);
let parent = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: funding,
script_sig: vec![OP_1],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 40_000,
script_pubkey: vec![],
}]
.into(),
lock_time: 0,
};
let child = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: calculate_tx_id(&parent),
index: 0,
},
script_sig: vec![OP_1],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 25_000,
script_pubkey: vec![],
}]
.into(),
lock_time: 0,
};
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 tmpl = create_block_template_with_outputs(
&utxo_set,
&[parent, child.clone()],
height,
&prev_header,
&[prev_header.clone(), prev_header2],
&vec![OP_1],
&[(0, vec![OP_1])],
Network::Regtest,
None,
)
.unwrap();
assert!(
tmpl.transactions
.iter()
.any(|tx| calculate_tx_id(tx) == calculate_tx_id(&child)),
"child must be selected with its parent"
);
let subsidy = get_block_subsidy_for_network(height, Network::Regtest);
assert_eq!(tmpl.coinbase_tx.outputs[0].value, subsidy + 25_000);
}
fn create_valid_transaction() -> Transaction {
use std::cell::Cell;
thread_local! {
static COUNTER: Cell<u64> = const { 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], 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(),
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"
);
}
fn declared_spend(prev_hash: [u8; 32], extra: u8) -> Transaction {
Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: prev_hash,
index: 0,
},
script_sig: vec![OP_1, PUSH_1_BYTE, extra],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![],
}]
.into(),
lock_time: 0,
}
}
fn insert_legacy_utxo(utxo_set: &mut UtxoSet, hash: [u8; 32]) {
utxo_set.insert(
OutPoint { hash, index: 0 },
std::sync::Arc::new(UTXO {
value: 10_000,
script_pubkey: vec![].into(),
height: 0,
is_coinbase: false,
}),
);
}
fn declared_template_header() -> (BlockHeader, Vec<BlockHeader>) {
let prev = BlockHeader {
version: 4,
prev_block_hash: [0u8; 32],
merkle_root: [0u8; 32],
timestamp: 1_600_000_000,
bits: 0x207fffff,
nonce: 0,
};
let prev_headers = vec![prev.clone(), prev.clone()];
(prev, prev_headers)
}
#[test]
fn resolve_declared_txs_empty_ok() {
let tx = declared_spend([1u8; 32], 1);
assert!(resolve_declared_txs(&[tx], &[]).unwrap().is_empty());
}
#[test]
fn resolve_declared_txs_unknown_is_error() {
let tx = declared_spend([1u8; 32], 1);
let err = resolve_declared_txs(&[tx], &[[9u8; 32]]).unwrap_err();
assert!(err.to_string().contains("unknown declared txid"));
}
#[test]
fn resolve_declared_txs_duplicate_is_error() {
let tx = declared_spend([1u8; 32], 1);
let id = crate::block::calculate_tx_id(&tx);
let err = resolve_declared_txs(&[tx], &[id, id]).unwrap_err();
assert!(err.to_string().contains("duplicate declared txid"));
}
#[test]
fn declared_template_selects_named_subset_and_commons_bip141() {
let mut utxo_set = UtxoSet::default();
insert_legacy_utxo(&mut utxo_set, [1u8; 32]);
insert_legacy_utxo(&mut utxo_set, [2u8; 32]);
let a = declared_spend([1u8; 32], 1);
let b = declared_spend([2u8; 32], 2);
let id_b = crate::block::calculate_tx_id(&b);
let (prev, prev_headers) = declared_template_header();
let outputs = [(1, vec![OP_1]), (2, vec![OP_2])];
let tmpl = create_block_template_declared(
&utxo_set,
&[a, b.clone()],
&[id_b],
1,
&prev,
&prev_headers,
&vec![OP_1],
&outputs,
Network::Regtest,
None,
)
.expect("declared subset");
assert_eq!(tmpl.transactions.len(), 1);
assert_eq!(crate::block::calculate_tx_id(&tmpl.transactions[0]), id_b);
assert!(is_coinbase(&tmpl.coinbase_tx));
assert!(tmpl.coinbase_tx.outputs.len() >= 3);
let last = tmpl.coinbase_tx.outputs.last().unwrap();
assert_eq!(last.value, 0);
assert_eq!(
&last.script_pubkey[0..6],
&[0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed]
);
assert_eq!(tmpl.coinbase_tx.outputs[1].script_pubkey, vec![OP_2]);
}
#[test]
fn declared_template_empty_is_coinbase_only() {
let utxo_set = UtxoSet::default();
let extra = declared_spend([1u8; 32], 1);
let (prev, prev_headers) = declared_template_header();
let tmpl = create_block_template_declared(
&utxo_set,
&[extra],
&[],
1,
&prev,
&prev_headers,
&vec![OP_1],
&[(1, vec![OP_1])],
Network::Regtest,
None,
)
.expect("empty declaration");
assert!(tmpl.transactions.is_empty());
assert!(is_coinbase(&tmpl.coinbase_tx));
assert!(check_template_weight(&tmpl, crate::constants::MAX_BLOCK_WEIGHT as u64).is_ok());
let err = check_template_weight(&tmpl, 1).unwrap_err();
assert!(err.to_string().contains("exceeds max"));
}
#[test]
fn declared_template_unknown_txid_errors() {
let utxo_set = UtxoSet::default();
let tx = declared_spend([1u8; 32], 1);
let (prev, prev_headers) = declared_template_header();
let err = create_block_template_declared(
&utxo_set,
&[tx],
&[[9u8; 32]],
1,
&prev,
&prev_headers,
&vec![OP_1],
&[(1, vec![OP_1])],
Network::Regtest,
None,
)
.unwrap_err();
assert!(err.to_string().contains("unknown declared txid"));
}
#[test]
fn declared_template_duplicate_txid_errors() {
let mut utxo_set = UtxoSet::default();
insert_legacy_utxo(&mut utxo_set, [1u8; 32]);
let tx = declared_spend([1u8; 32], 1);
let id = crate::block::calculate_tx_id(&tx);
let (prev, prev_headers) = declared_template_header();
let err = create_block_template_declared(
&utxo_set,
&[tx],
&[id, id],
1,
&prev,
&prev_headers,
&vec![OP_1],
&[(1, vec![OP_1])],
Network::Regtest,
None,
)
.unwrap_err();
assert!(err.to_string().contains("duplicate declared txid"));
}
#[test]
fn declared_template_rejected_tx_errors() {
let utxo_set = UtxoSet::default();
let rejected = declared_spend([9u8; 32], 9);
let id = crate::block::calculate_tx_id(&rejected);
let (prev, prev_headers) = declared_template_header();
let err = create_block_template_declared(
&utxo_set,
&[rejected],
&[id],
1,
&prev,
&prev_headers,
&vec![OP_1],
&[(1, vec![OP_1])],
Network::Regtest,
None,
)
.unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("not selected") || msg.contains("rejected"),
"expected rejected-declared error, got {msg}"
);
}
#[test]
fn declared_template_missing_witness_stack_errors() {
let mut utxo_set = UtxoSet::default();
let mut p2wpkh = vec![OP_0, PUSH_20_BYTES];
p2wpkh.extend_from_slice(&[0u8; 20]);
utxo_set.insert(
OutPoint {
hash: [0xAAu8; 32],
index: 0,
},
std::sync::Arc::new(UTXO {
value: 10_000,
script_pubkey: p2wpkh.into(),
height: 0,
is_coinbase: false,
}),
);
let spend = Transaction {
version: 2,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [0xAAu8; 32],
index: 0,
},
script_sig: vec![],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![OP_1],
}]
.into(),
lock_time: 0,
};
let id = crate::block::calculate_tx_id(&spend);
let (prev, prev_headers) = declared_template_header();
let err = create_block_template_declared(
&utxo_set,
&[spend],
&[id],
1,
&prev,
&prev_headers,
&vec![OP_1],
&[(1, vec![OP_1])],
Network::Regtest,
None,
)
.unwrap_err();
assert!(
err.to_string().contains("missing mempool witnesses"),
"got {}",
err
);
}
#[test]
fn template_version_follows_bip90() {
assert_eq!(template_block_version(Network::Mainnet, 1), 1);
assert!(template_block_version(Network::Mainnet, 388_381) >= 4);
}
#[test]
fn template_time_is_at_least_the_median_plus_one() {
let prev = BlockHeader {
version: 1,
prev_block_hash: [0; 32],
merkle_root: [0; 32],
timestamp: 999,
bits: 0x1d00ffff,
nonce: 0,
};
let prev_headers = vec![prev.clone(); 11];
let utxo = UtxoSet::default();
let script = vec![OP_1];
let early = create_new_block_with_time(
&utxo,
&[],
1,
&prev,
&prev_headers,
&script,
&script,
900,
Network::Mainnet,
None,
)
.unwrap();
assert_eq!(early.header.timestamp, 1_000);
assert_eq!(early.header.version, 1);
let late = create_new_block_with_time(
&utxo,
&[],
1,
&prev,
&prev_headers,
&script,
&script,
1_100,
Network::Mainnet,
None,
)
.unwrap();
assert_eq!(late.header.timestamp, 1_100);
}