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;
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,
Network::Mainnet,
None,
)
}
#[allow(clippy::too_many_arguments)]
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,
network: Network,
mempool_witnesses: Option<&[Option<Vec<crate::segwit::Witness>>]>,
) -> Result<Block> {
use crate::bip113::get_median_time_past;
use crate::mempool::{Mempool, MempoolResult, accept_to_memory_pool};
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::new();
for (idx, tx) in mempool_txs.iter().enumerate() {
if check_transaction(tx)? != ValidationResult::Valid {
continue;
}
let median_time_past = get_median_time_past(prev_headers);
let time_context = Some(TimeContext {
network_time: block_time,
median_time_past,
});
let tx_witnesses = mempool_witnesses
.and_then(|all| all.get(idx))
.and_then(|w| w.as_deref());
match accept_to_memory_pool(
tx,
tx_witnesses,
utxo_set,
&temp_mempool,
height,
time_context,
network,
)? {
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] 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,
}
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,
network: Network,
mempool_witnesses: Option<&[Option<Vec<crate::segwit::Witness>>]>,
) -> Result<BlockTemplate> {
let block_time = get_current_timestamp();
let block = create_new_block_with_time(
utxo_set,
mempool_txs,
height,
prev_header,
prev_headers,
coinbase_script,
coinbase_address,
block_time,
network,
mempool_witnesses,
)?;
let target_u256 = crate::pow::expand_target(block.header.bits)?;
let target = if target_u256.is_zero() {
0
} else if target_u256.low_u128() > 0 {
target_u256.low_u128()
} else {
1
};
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.clone(),
coinbase_tx,
transactions: block.transactions[1..].to_vec(),
target,
height,
timestamp: header.timestamp,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MiningResult {
Success,
Failure,
}
fn create_coinbase_transaction(
height: Natural,
subsidy: Integer,
script: &ByteString,
address: &ByteString,
) -> Result<Transaction> {
create_coinbase_with_outputs(height, script, &[(subsidy, address.clone())])
}
pub fn create_coinbase_with_outputs(
height: Natural,
script: &ByteString,
outputs: &[(Integer, ByteString)],
) -> Result<Transaction> {
if outputs.is_empty() {
return Err(crate::error::ConsensusError::InvalidProofOfWork(
"coinbase requires at least one output".into(),
));
}
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 outs: Vec<TransactionOutput> = outputs
.iter()
.map(|(value, script_pubkey)| TransactionOutput {
value: *value,
script_pubkey: script_pubkey.clone(),
})
.collect();
Ok(Transaction {
version: 1,
inputs: crate::tx_inputs![coinbase_input],
outputs: outs.into(),
lock_time,
})
}
#[allow(clippy::too_many_arguments)]
pub fn create_block_template_with_outputs(
utxo_set: &UtxoSet,
mempool_txs: &[Transaction],
height: Natural,
prev_header: &BlockHeader,
prev_headers: &[BlockHeader],
coinbase_script: &ByteString,
coinbase_outputs: &[(Integer, ByteString)],
network: Network,
mempool_witnesses: Option<&[Option<Vec<crate::segwit::Witness>>]>,
) -> Result<BlockTemplate> {
let address = coinbase_outputs
.first()
.map(|(_, s)| s.clone())
.unwrap_or_default();
let mut tmpl = create_block_template(
utxo_set,
mempool_txs,
height,
prev_header,
prev_headers,
coinbase_script,
&address,
network,
mempool_witnesses,
)?;
let subsidy = get_block_subsidy(height);
let fees = sum_selected_fees(utxo_set, &tmpl.transactions);
let fitted = fit_payouts_to_reward(coinbase_outputs, subsidy, fees)?;
tmpl.coinbase_tx = create_coinbase_with_outputs(height, coinbase_script, &fitted)?;
let mut txs = Vec::with_capacity(1 + tmpl.transactions.len());
txs.push(tmpl.coinbase_tx.clone());
txs.extend(tmpl.transactions.iter().cloned());
let nested = nested_witnesses_for_selected(&txs, mempool_txs, mempool_witnesses);
append_witness_commitment_from_nested(&mut tmpl.coinbase_tx, &mut txs, Some(&nested))?;
tmpl.header.merkle_root = calculate_merkle_root(&txs)?;
Ok(tmpl)
}
pub fn resolve_declared_txs(
mempool_txs: &[Transaction],
txids: &[Hash],
) -> Result<Vec<Transaction>> {
use crate::block::calculate_tx_id;
use std::collections::{HashMap, HashSet};
let mut by_id: HashMap<Hash, &Transaction> = HashMap::new();
for tx in mempool_txs {
by_id.entry(calculate_tx_id(tx)).or_insert(tx);
}
let mut seen = HashSet::new();
let mut out = Vec::with_capacity(txids.len());
for id in txids {
if !seen.insert(*id) {
return Err(crate::error::ConsensusError::BlockValidation(
format!("duplicate declared txid {}", hex::encode(id)).into(),
));
}
let tx = by_id.get(id).ok_or_else(|| {
crate::error::ConsensusError::BlockValidation(
format!("unknown declared txid {}", hex::encode(id)).into(),
)
})?;
out.push((*tx).clone());
}
Ok(out)
}
fn declared_tx_spends_witness_utxo(tx: &Transaction, utxo_set: &UtxoSet) -> bool {
use crate::witness::{
extract_witness_program, extract_witness_version, validate_witness_program_length,
};
tx.inputs.iter().any(|input| {
utxo_set.get(&input.prevout).is_some_and(|utxo| {
let script = utxo.script_pubkey.as_ref().to_vec();
extract_witness_version(&script)
.and_then(|version| {
extract_witness_program(&script, version).map(|program| (version, program))
})
.is_some_and(|(version, program)| {
validate_witness_program_length(&program, version)
})
})
})
}
fn align_declared_witnesses(
declared: &[Transaction],
mempool_txs: &[Transaction],
mempool_witnesses: Option<&[Option<Vec<crate::segwit::Witness>>]>,
utxo_set: &UtxoSet,
) -> Result<Vec<Option<Vec<crate::segwit::Witness>>>> {
use crate::block::calculate_tx_id;
use std::collections::HashMap;
let mut by_txid = HashMap::new();
if let Some(wits) = mempool_witnesses {
for (tx, w) in mempool_txs.iter().zip(wits.iter()) {
by_txid.insert(calculate_tx_id(tx), w.clone());
}
}
let mut out = Vec::with_capacity(declared.len());
for tx in declared {
let id = calculate_tx_id(tx);
let slot = by_txid.get(&id).cloned().flatten();
if let Some(ref stacks) = slot {
if stacks.len() != tx.inputs.len() {
return Err(crate::error::ConsensusError::BlockValidation(
format!(
"witness count {} != input count {} for declared tx {}",
stacks.len(),
tx.inputs.len(),
hex::encode(id)
)
.into(),
));
}
} else if declared_tx_spends_witness_utxo(tx, utxo_set) {
return Err(crate::error::ConsensusError::BlockValidation(
format!(
"missing mempool witnesses for declared witness spend {}",
hex::encode(id)
)
.into(),
));
}
out.push(slot);
}
Ok(out)
}
#[allow(clippy::too_many_arguments)]
pub fn create_block_template_declared(
utxo_set: &UtxoSet,
mempool_txs: &[Transaction],
declared_txids: &[Hash],
height: Natural,
prev_header: &BlockHeader,
prev_headers: &[BlockHeader],
coinbase_script: &ByteString,
coinbase_outputs: &[(Integer, ByteString)],
network: Network,
mempool_witnesses: Option<&[Option<Vec<crate::segwit::Witness>>]>,
) -> Result<BlockTemplate> {
use crate::block::calculate_tx_id;
let declared = resolve_declared_txs(mempool_txs, declared_txids)?;
let aligned = align_declared_witnesses(&declared, mempool_txs, mempool_witnesses, utxo_set)?;
let tmpl = create_block_template_with_outputs(
utxo_set,
&declared,
height,
prev_header,
prev_headers,
coinbase_script,
coinbase_outputs,
network,
Some(aligned.as_slice()),
)?;
let selected: Vec<Hash> = tmpl.transactions.iter().map(calculate_tx_id).collect();
if selected.as_slice() != declared_txids {
return Err(crate::error::ConsensusError::BlockValidation(
"declared transaction was not selected (rejected or skipped)".into(),
));
}
check_template_weight(&tmpl, crate::constants::MAX_BLOCK_WEIGHT as u64)?;
Ok(tmpl)
}
pub fn check_template_weight(tmpl: &BlockTemplate, max_weight: u64) -> Result<()> {
let mut txs = Vec::with_capacity(1 + tmpl.transactions.len());
txs.push(tmpl.coinbase_tx.clone());
txs.extend(tmpl.transactions.iter().cloned());
let block = Block {
header: tmpl.header.clone(),
transactions: txs.clone().into_boxed_slice(),
};
let nested: Vec<Vec<crate::segwit::Witness>> = txs
.iter()
.map(|tx| vec![Vec::new(); tx.inputs.len()])
.collect();
let weight = crate::segwit::calculate_block_weight_from_nested(&block, &nested)?;
if weight > max_weight {
return Err(crate::error::ConsensusError::BlockValidation(
format!("declared template weight {weight} exceeds max {max_weight}").into(),
));
}
Ok(())
}
fn nested_witnesses_for_selected(
txs: &[Transaction],
mempool_txs: &[Transaction],
mempool_witnesses: Option<&[Option<Vec<crate::segwit::Witness>>]>,
) -> Vec<Vec<crate::segwit::Witness>> {
use crate::block::calculate_tx_id;
use std::collections::HashMap;
let mut by_txid = HashMap::new();
if let Some(wits) = mempool_witnesses {
for (tx, w) in mempool_txs.iter().zip(wits.iter()) {
if let Some(stacks) = w {
by_txid.insert(calculate_tx_id(tx), stacks.clone());
}
}
}
txs.iter()
.enumerate()
.map(|(i, tx)| {
if i == 0 {
vec![Vec::new(); tx.inputs.len()]
} else {
by_txid
.get(&calculate_tx_id(tx))
.cloned()
.unwrap_or_else(|| vec![Vec::new(); tx.inputs.len()])
}
})
.collect()
}
pub fn sum_selected_fees(utxo_set: &UtxoSet, txs: &[Transaction]) -> Integer {
txs.iter()
.map(|tx| crate::economic::calculate_fee(tx, utxo_set).unwrap_or(0))
.fold(0, |a, b| a.saturating_add(b))
}
pub fn fit_payouts_to_reward(
payouts: &[(Integer, ByteString)],
subsidy: Integer,
fees: Integer,
) -> Result<Vec<(Integer, ByteString)>> {
if payouts.is_empty() {
return Err(crate::error::ConsensusError::InvalidProofOfWork(
"coinbase requires at least one output".into(),
));
}
let max = subsidy.saturating_add(fees);
let sum: Integer = payouts
.iter()
.map(|(v, _)| *v)
.fold(0, |a, b| a.saturating_add(b));
if sum > max {
return Err(crate::error::ConsensusError::EconomicValidation(
format!("payouts {sum} exceed subsidy+fees {max}").into(),
));
}
let mut out = payouts.to_vec();
if sum < max {
out[0].0 = out[0].0.saturating_add(max - sum);
}
Ok(out)
}
pub fn append_witness_commitment(
coinbase: &mut Transaction,
txs: &mut [Transaction],
) -> Result<()> {
append_witness_commitment_from_nested(coinbase, txs, None)
}
pub fn append_witness_commitment_from_nested(
coinbase: &mut Transaction,
txs: &mut [Transaction],
nested: Option<&[Vec<crate::segwit::Witness>]>,
) -> Result<()> {
if txs.is_empty() {
return Ok(());
}
let owned: Vec<Vec<crate::segwit::Witness>>;
let nested = if let Some(n) = nested {
if n.len() >= txs.len() {
n
} else {
owned = (0..txs.len())
.map(|i| {
n.get(i)
.cloned()
.unwrap_or_else(|| vec![Vec::new(); txs[i].inputs.len()])
})
.collect();
&owned
}
} else {
owned = txs
.iter()
.map(|tx| vec![Vec::new(); tx.inputs.len()])
.collect();
&owned
};
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: txs.to_vec().into_boxed_slice(),
};
let root = crate::segwit::compute_witness_merkle_root_from_nested(&block, nested, None)?;
let script = crate::segwit::witness_commitment_script(&root, &[0u8; 32]);
let mut outs = coinbase.outputs.to_vec();
outs.push(TransactionOutput {
value: 0,
script_pubkey: script,
});
coinbase.outputs = outs.into();
txs[0] = coinbase.clone();
Ok(())
}
#[track_caller] #[cfg_attr(feature = "production", inline(always))]
#[cfg_attr(not(feature = "production"), inline)]
#[spec_locked("8.4.1", "ComputeMerkleRoot")]
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", "ComputeMerkleRoot")]
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", "ComputeMerkleRoot")]
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", "ComputeMerkleRoot")]
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
}
}
fn calculate_block_hash(header: &BlockHeader) -> Hash {
let wire = crate::serialization::block::serialize_block_header(header);
double_sha256_hash(&wire)
}
#[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)
}
fn get_current_timestamp() -> Natural {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or(std::time::Duration::ZERO)
.as_secs()
}
#[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 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 result = create_block_template(
&utxo_set,
&mempool_txs,
height,
&prev_header,
&prev_headers,
&coinbase_script,
&coinbase_address,
Network::Mainnet,
None,
);
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,
Network::Mainnet,
None,
);
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_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 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);
assert!(template.target > 0);
}
#[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 > 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> = 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
);
}
}