use crate::constants::*;
use crate::economic::calculate_fee;
use crate::error::{ConsensusError, Result};
use crate::script::verify_script;
use crate::segwit::Witness;
use crate::transaction::{check_transaction, check_tx_inputs};
use crate::types::*;
use blvm_spec_lock::spec_locked;
use std::collections::HashSet;
#[spec_locked("9.1")]
pub fn accept_to_memory_pool(
tx: &Transaction,
witnesses: Option<&[Witness]>,
utxo_set: &UtxoSet,
mempool: &Mempool,
height: Natural,
time_context: Option<TimeContext>,
) -> Result<MempoolResult> {
if tx.inputs.is_empty() && tx.outputs.is_empty() {
return Ok(MempoolResult::Rejected(
"Transaction must have at least one input or output".to_string(),
));
}
if is_coinbase(tx) {
return Ok(MempoolResult::Rejected(
"Coinbase transactions cannot be added to mempool".to_string(),
));
}
assert!(
height <= i64::MAX as u64,
"Block height {height} must fit in i64"
);
assert!(
utxo_set.len() <= u32::MAX as usize,
"UTXO set size {} exceeds maximum",
utxo_set.len()
);
if let Some(wits) = witnesses {
assert!(
wits.len() == tx.inputs.len(),
"Witness count {} must match input count {}",
wits.len(),
tx.inputs.len()
);
}
let tx_id = crate::block::calculate_tx_id(tx);
assert!(tx_id != [0u8; 32], "Transaction ID must be non-zero");
if mempool.contains(&tx_id) {
return Ok(MempoolResult::Rejected(
"Transaction already in mempool".to_string(),
));
}
if !matches!(check_transaction(tx)?, ValidationResult::Valid) {
return Ok(MempoolResult::Rejected(
"Invalid transaction structure".to_string(),
));
}
let block_time = time_context.map(|ctx| ctx.median_time_past).unwrap_or(0);
if !is_final_tx(tx, height, block_time) {
return Ok(MempoolResult::Rejected(
"Transaction not final (locktime not satisfied)".to_string(),
));
}
let (input_valid, fee) = check_tx_inputs(tx, utxo_set, height)?;
assert!(fee >= 0, "Fee {fee} must be non-negative");
use crate::constants::MAX_MONEY;
assert!(fee <= MAX_MONEY, "Fee {fee} must not exceed MAX_MONEY");
if !matches!(input_valid, ValidationResult::Valid) {
return Ok(MempoolResult::Rejected(
"Invalid transaction inputs".to_string(),
));
}
if !is_coinbase(tx) {
let flags = calculate_script_flags(tx, witnesses);
#[cfg(all(feature = "production", feature = "rayon"))]
{
use rayon::prelude::*;
let input_utxos: Vec<(usize, Option<&UTXO>)> = {
let mut result = Vec::with_capacity(tx.inputs.len());
for (i, input) in tx.inputs.iter().enumerate() {
result.push((i, utxo_set.get(&input.prevout).map(|a| a.as_ref())));
}
result
};
let script_results: Result<Vec<bool>> = input_utxos
.par_iter()
.map(|(i, opt_utxo)| {
if let Some(utxo) = opt_utxo {
let input = &tx.inputs[*i];
let witness: Option<&ByteString> = witnesses
.and_then(|wits| wits.get(*i))
.and_then(|wit| wit.first());
verify_script(&input.script_sig, &utxo.script_pubkey, witness, flags)
} else {
Ok(false)
}
})
.collect();
let script_results = script_results?;
assert!(
script_results.len() == tx.inputs.len(),
"Script results count {} must match input count {}",
script_results.len(),
tx.inputs.len()
);
for (i, &is_valid) in script_results.iter().enumerate() {
assert!(
i < tx.inputs.len(),
"Input index {i} out of bounds in script validation loop",
);
if !is_valid {
return Ok(MempoolResult::Rejected(format!(
"Invalid script at input {i}"
)));
}
}
}
#[cfg(not(all(feature = "production", feature = "rayon")))]
{
for (i, input) in tx.inputs.iter().enumerate() {
if let Some(utxo) = utxo_set.get(&input.prevout) {
let witness: Option<&ByteString> =
witnesses.and_then(|wits| wits.get(i)).and_then(|wit| {
wit.first()
});
if !verify_script(&input.script_sig, &utxo.script_pubkey, witness, flags)? {
return Ok(MempoolResult::Rejected(format!(
"Invalid script at input {i}"
)));
}
}
}
}
}
if !check_mempool_rules(tx, fee, mempool)? {
return Ok(MempoolResult::Rejected("Failed mempool rules".to_string()));
}
if has_conflicts(tx, mempool)? {
return Ok(MempoolResult::Rejected(
"Transaction conflicts with mempool".to_string(),
));
}
Ok(MempoolResult::Accepted)
}
fn calculate_script_flags(tx: &Transaction, witnesses: Option<&[Witness]>) -> u32 {
let has_witness = witnesses.map(|w| !w.is_empty()).unwrap_or(false);
const MEMPOOL_POLICY_HEIGHT: u64 = 1_000_000; crate::block::calculate_script_flags_for_block_network(
tx,
has_witness,
MEMPOOL_POLICY_HEIGHT,
crate::types::Network::Mainnet,
)
}
#[spec_locked("9.2")]
pub fn is_standard_tx(tx: &Transaction) -> Result<bool> {
let tx_size = calculate_transaction_size(tx);
if tx_size > MAX_TX_SIZE {
return Ok(false);
}
for (i, input) in tx.inputs.iter().enumerate() {
assert!(i < tx.inputs.len(), "Input index {i} out of bounds");
assert!(
input.script_sig.len() <= MAX_SCRIPT_SIZE * 2,
"Script size {} must be reasonable for input {}",
input.script_sig.len(),
i
);
if input.script_sig.len() > MAX_SCRIPT_SIZE {
return Ok(false);
}
}
for (i, output) in tx.outputs.iter().enumerate() {
assert!(i < tx.outputs.len(), "Output index {i} out of bounds");
assert!(
output.script_pubkey.len() <= MAX_SCRIPT_SIZE * 2,
"Script size {} must be reasonable for output {}",
output.script_pubkey.len(),
i
);
if output.script_pubkey.len() > MAX_SCRIPT_SIZE {
return Ok(false);
}
}
for (i, output) in tx.outputs.iter().enumerate() {
assert!(
i < tx.outputs.len(),
"Output index {i} out of bounds in standard check"
);
if !is_standard_script(&output.script_pubkey)? {
return Ok(false);
}
}
let result = true;
Ok(result)
}
#[spec_locked("9.3")]
pub fn replacement_checks(
new_tx: &Transaction,
existing_tx: &Transaction,
utxo_set: &UtxoSet,
mempool: &Mempool,
) -> Result<bool> {
if new_tx.inputs.is_empty() && new_tx.outputs.is_empty() {
return Err(crate::error::ConsensusError::ConsensusRuleViolation(
"New transaction must have at least one input or output"
.to_string()
.into(),
));
}
if existing_tx.inputs.is_empty() && existing_tx.outputs.is_empty() {
return Err(crate::error::ConsensusError::ConsensusRuleViolation(
"Existing transaction must have at least one input or output"
.to_string()
.into(),
));
}
if is_coinbase(new_tx) {
return Err(crate::error::ConsensusError::ConsensusRuleViolation(
"New transaction cannot be coinbase".to_string().into(),
));
}
if is_coinbase(existing_tx) {
return Err(crate::error::ConsensusError::ConsensusRuleViolation(
"Existing transaction cannot be coinbase".to_string().into(),
));
}
assert!(
utxo_set.len() <= u32::MAX as usize,
"UTXO set size {} exceeds maximum",
utxo_set.len()
);
if !signals_rbf(existing_tx) {
return Ok(false);
}
let new_fee = calculate_fee(new_tx, utxo_set)?;
let existing_fee = calculate_fee(existing_tx, utxo_set)?;
assert!(new_fee >= 0, "New fee {new_fee} must be non-negative");
assert!(
existing_fee >= 0,
"Existing fee {existing_fee} must be non-negative"
);
use crate::constants::MAX_MONEY;
assert!(
new_fee <= MAX_MONEY,
"New fee {new_fee} must not exceed MAX_MONEY"
);
assert!(
existing_fee <= MAX_MONEY,
"Existing fee {existing_fee} must not exceed MAX_MONEY"
);
let new_tx_size = calculate_transaction_size_vbytes(new_tx);
let existing_tx_size = calculate_transaction_size_vbytes(existing_tx);
assert!(
new_tx_size > 0,
"New transaction size {new_tx_size} must be positive"
);
assert!(
existing_tx_size > 0,
"Existing transaction size {existing_tx_size} must be positive"
);
assert!(
new_tx_size <= MAX_TX_SIZE * 2,
"New transaction size {new_tx_size} must be reasonable"
);
assert!(
existing_tx_size <= MAX_TX_SIZE * 2,
"Existing transaction size {existing_tx_size} must be reasonable"
);
if new_tx_size == 0 || existing_tx_size == 0 {
return Ok(false);
}
debug_assert!(
new_tx_size > 0,
"New transaction size ({new_tx_size}) must be positive"
);
debug_assert!(
existing_tx_size > 0,
"Existing transaction size ({existing_tx_size}) must be positive"
);
let new_fee_scaled = (new_fee as u128)
.checked_mul(existing_tx_size as u128)
.ok_or_else(|| {
ConsensusError::TransactionValidation("Fee rate calculation overflow".into())
})?;
let existing_fee_scaled = (existing_fee as u128)
.checked_mul(new_tx_size as u128)
.ok_or_else(|| {
ConsensusError::TransactionValidation("Fee rate calculation overflow".into())
})?;
if new_fee_scaled <= existing_fee_scaled {
return Ok(false);
}
if new_fee <= existing_fee + MIN_RELAY_FEE {
return Ok(false);
}
if !has_conflict_with_tx(new_tx, existing_tx) {
return Ok(false);
}
if creates_new_dependencies(new_tx, existing_tx, utxo_set, mempool)? {
return Ok(false);
}
Ok(true)
}
pub type Mempool = HashSet<Hash>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MempoolResult {
Accepted,
Rejected(String),
}
#[spec_locked("9.1")]
pub fn update_mempool_after_block(
mempool: &mut Mempool,
block: &crate::types::Block,
_utxo_set: &crate::types::UtxoSet,
) -> Result<Vec<Hash>> {
let mut removed = Vec::new();
for tx in &block.transactions {
let tx_id = crate::block::calculate_tx_id(tx);
if mempool.remove(&tx_id) {
removed.push(tx_id);
}
}
Ok(removed)
}
#[spec_locked("9.1")]
pub fn update_mempool_after_block_with_lookup<F>(
mempool: &mut Mempool,
block: &crate::types::Block,
get_tx_by_id: F,
) -> Result<Vec<Hash>>
where
F: Fn(&Hash) -> Option<crate::types::Transaction>,
{
let mut removed = Vec::new();
for tx in &block.transactions {
let tx_id = crate::block::calculate_tx_id(tx);
if mempool.remove(&tx_id) {
removed.push(tx_id);
}
}
let mut spent_outpoints = std::collections::HashSet::new();
for tx in &block.transactions {
if !crate::transaction::is_coinbase(tx) {
for input in &tx.inputs {
spent_outpoints.insert(input.prevout);
}
}
}
let mut invalid_tx_ids = Vec::new();
for &tx_id in mempool.iter() {
if let Some(tx) = get_tx_by_id(&tx_id) {
for input in &tx.inputs {
if spent_outpoints.contains(&input.prevout) {
invalid_tx_ids.push(tx_id);
break;
}
}
}
}
for tx_id in invalid_tx_ids {
if mempool.remove(&tx_id) {
removed.push(tx_id);
}
}
Ok(removed)
}
fn check_mempool_rules(tx: &Transaction, fee: Integer, mempool: &Mempool) -> Result<bool> {
let tx_size = calculate_transaction_size(tx);
debug_assert!(
tx_size > 0,
"Transaction size ({tx_size}) must be positive for fee rate calculation"
);
let fee_rate = (fee as f64) / (tx_size as f64);
debug_assert!(
fee_rate >= 0.0,
"Fee rate ({fee_rate:.6}) must be non-negative (fee: {fee}, size: {tx_size})"
);
let config = crate::config::get_consensus_config_ref();
let min_fee_rate = config.mempool.min_relay_fee_rate as f64; let min_tx_fee = config.mempool.min_tx_fee;
if fee < min_tx_fee {
return Ok(false);
}
if fee_rate < min_fee_rate {
return Ok(false);
}
if mempool.len() > config.mempool.max_mempool_txs {
return Ok(false);
}
Ok(true)
}
fn has_conflicts(tx: &Transaction, mempool: &Mempool) -> Result<bool> {
for input in &tx.inputs {
if mempool.contains(&input.prevout.hash) {
return Ok(true);
}
}
Ok(false)
}
#[spec_locked("9.1")]
pub fn is_final_tx(tx: &Transaction, height: Natural, block_time: Natural) -> bool {
use crate::constants::SEQUENCE_FINAL;
if tx.lock_time == 0 {
return true;
}
let locktime_satisfied = if (tx.lock_time as u32) < LOCKTIME_THRESHOLD {
(tx.lock_time as Natural) < height
} else {
(tx.lock_time as Natural) < block_time
};
if locktime_satisfied {
return true;
}
for input in &tx.inputs {
if (input.sequence as u32) != SEQUENCE_FINAL {
return false;
}
}
true
}
#[spec_locked("9.3")]
pub fn signals_rbf(tx: &Transaction) -> bool {
for input in &tx.inputs {
if (input.sequence as u32) < SEQUENCE_FINAL {
return true;
}
}
false
}
fn calculate_transaction_size_vbytes(tx: &Transaction) -> usize {
calculate_transaction_size(tx)
}
#[spec_locked("9.3")]
pub fn has_conflict_with_tx(new_tx: &Transaction, existing_tx: &Transaction) -> bool {
for new_input in &new_tx.inputs {
for existing_input in &existing_tx.inputs {
if new_input.prevout == existing_input.prevout {
return true;
}
}
}
false
}
fn creates_new_dependencies(
new_tx: &Transaction,
existing_tx: &Transaction,
utxo_set: &UtxoSet,
mempool: &Mempool,
) -> Result<bool> {
for input in &new_tx.inputs {
if utxo_set.contains_key(&input.prevout) {
continue;
}
let mut found_in_existing = false;
for existing_input in &existing_tx.inputs {
if existing_input.prevout == input.prevout {
found_in_existing = true;
break;
}
}
if found_in_existing {
continue;
}
if !mempool.contains(&input.prevout.hash) {
return Ok(true); }
}
Ok(false)
}
fn is_standard_script(script: &ByteString) -> Result<bool> {
if script.is_empty() {
return Ok(false);
}
if script.len() > MAX_SCRIPT_SIZE {
return Ok(false);
}
for &byte in script {
if byte > 0x60 && byte < 0x7f {
return Ok(false);
}
}
Ok(true)
}
#[deprecated(note = "Use crate::block::calculate_tx_id instead")]
#[spec_locked("5.1")]
pub fn calculate_tx_id(tx: &Transaction) -> Hash {
crate::block::calculate_tx_id(tx)
}
fn calculate_transaction_size(tx: &Transaction) -> usize {
use crate::transaction::calculate_transaction_size as tx_size;
tx_size(tx)
}
fn is_coinbase(tx: &Transaction) -> bool {
#[cfg(feature = "production")]
{
use crate::optimizations::constant_folding::is_zero_hash;
tx.inputs.len() == 1
&& is_zero_hash(&tx.inputs[0].prevout.hash)
&& tx.inputs[0].prevout.index == 0xffffffff
}
#[cfg(not(feature = "production"))]
{
tx.inputs.len() == 1
&& tx.inputs[0].prevout.hash == [0u8; 32]
&& tx.inputs[0].prevout.index == 0xffffffff
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::opcodes::*;
#[test]
fn test_accept_to_memory_pool_valid() {
let tx = create_valid_transaction();
let utxo_set = create_test_utxo_set();
let mempool = Mempool::new();
let time_context = Some(TimeContext {
network_time: 1234567890,
median_time_past: 1234567890,
});
let result =
accept_to_memory_pool(&tx, None, &utxo_set, &mempool, 100, time_context).unwrap();
assert!(matches!(result, MempoolResult::Rejected(_)));
}
#[test]
fn test_accept_to_memory_pool_duplicate() {
let tx = create_valid_transaction();
let utxo_set = create_test_utxo_set();
let mut mempool = Mempool::new();
mempool.insert(crate::block::calculate_tx_id(&tx));
let time_context = Some(TimeContext {
network_time: 1234567890,
median_time_past: 1234567890,
});
let result =
accept_to_memory_pool(&tx, None, &utxo_set, &mempool, 100, time_context).unwrap();
assert!(matches!(result, MempoolResult::Rejected(_)));
}
#[test]
fn test_is_standard_tx_valid() {
let tx = create_valid_transaction();
assert!(is_standard_tx(&tx).unwrap());
}
#[test]
fn test_is_standard_tx_too_large() {
let mut tx = create_valid_transaction();
for _ in 0..MAX_INPUTS {
tx.inputs.push(create_dummy_input());
}
assert!(!is_standard_tx(&tx).unwrap());
}
#[test]
fn test_replacement_checks_all_requirements() {
let utxo_set = create_test_utxo_set();
let mempool = Mempool::new();
let mut existing_tx = create_valid_transaction();
existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
existing_tx.outputs[0].value = 9000;
let mut new_tx = existing_tx.clone();
new_tx.outputs[0].value = 8000; new_tx.outputs[0].value = 7999;
let result = replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
assert!(result, "Valid RBF replacement should be accepted");
}
#[test]
fn test_replacement_checks_no_rbf_signal() {
let utxo_set = create_test_utxo_set();
let mempool = Mempool::new();
let new_tx = create_valid_transaction();
let existing_tx = create_valid_transaction();
assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
}
#[test]
fn test_replacement_checks_no_conflict() {
let mut utxo_set = create_test_utxo_set();
let new_outpoint = OutPoint {
hash: [2; 32],
index: 0,
};
let new_utxo = UTXO {
value: 10000,
script_pubkey: vec![OP_1].into(),
height: 0,
is_coinbase: false,
};
utxo_set.insert(new_outpoint, std::sync::Arc::new(new_utxo));
let mempool = Mempool::new();
let mut existing_tx = create_valid_transaction();
existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
let mut new_tx = create_valid_transaction();
new_tx.inputs[0].prevout.hash = [2; 32]; new_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
new_tx.outputs[0].value = 5000;
assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
}
#[test]
fn test_replacement_checks_fee_rate_too_low() {
let utxo_set = create_test_utxo_set();
let mempool = Mempool::new();
let mut existing_tx = create_valid_transaction();
existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
existing_tx.outputs[0].value = 5000;
let mut new_tx = existing_tx.clone();
new_tx.outputs[0].value = 4999;
assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
}
#[test]
fn test_replacement_checks_absolute_fee_insufficient() {
let utxo_set = create_test_utxo_set();
let mempool = Mempool::new();
let mut existing_tx = create_valid_transaction();
existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
existing_tx.outputs[0].value = 9000;
let mut new_tx = existing_tx.clone();
new_tx.outputs[0].value = 8001;
assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
new_tx.outputs[0].value = 7999; }
#[test]
fn test_accept_to_memory_pool_coinbase() {
let coinbase_tx = create_coinbase_transaction();
let utxo_set = UtxoSet::default();
let mempool = Mempool::new();
let time_context = Some(TimeContext {
network_time: 0,
median_time_past: 0,
});
let result =
accept_to_memory_pool(&coinbase_tx, None, &utxo_set, &mempool, 100, time_context)
.unwrap();
assert!(matches!(result, MempoolResult::Rejected(_)));
}
#[test]
fn test_is_standard_tx_large_script() {
let mut tx = create_valid_transaction();
tx.inputs[0].script_sig = vec![OP_1; MAX_SCRIPT_SIZE + 1];
let result = is_standard_tx(&tx).unwrap();
assert!(!result);
}
#[test]
fn test_is_standard_tx_large_output_script() {
let mut tx = create_valid_transaction();
tx.outputs[0].script_pubkey = vec![OP_1; MAX_SCRIPT_SIZE + 1];
let result = is_standard_tx(&tx).unwrap();
assert!(!result);
}
#[test]
fn test_replacement_checks_new_unconfirmed_dependency() {
let utxo_set = create_test_utxo_set();
let mempool = Mempool::new();
let mut existing_tx = create_valid_transaction();
existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
let mut new_tx = existing_tx.clone();
new_tx.inputs.push(TransactionInput {
prevout: OutPoint {
hash: [99; 32],
index: 0,
}, script_sig: vec![],
sequence: SEQUENCE_RBF as u64,
});
new_tx.outputs[0].value = 7000;
assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
}
#[test]
fn test_has_conflict_with_tx_true() {
let tx1 = create_valid_transaction();
let mut tx2 = create_valid_transaction();
tx2.inputs[0].prevout = tx1.inputs[0].prevout.clone();
assert!(has_conflict_with_tx(&tx2, &tx1));
}
#[test]
fn test_has_conflict_with_tx_false() {
let tx1 = create_valid_transaction();
let mut tx2 = create_valid_transaction();
tx2.inputs[0].prevout.hash = [2; 32];
assert!(!has_conflict_with_tx(&tx2, &tx1));
}
#[test]
fn test_replacement_checks_minimum_relay_fee() {
let utxo_set = create_test_utxo_set();
let mempool = Mempool::new();
let mut existing_tx = create_valid_transaction();
existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
existing_tx.outputs[0].value = 9500;
let mut new_tx = existing_tx.clone();
new_tx.outputs[0].value = 8500; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
new_tx.outputs[0].value = 8499;
}
#[test]
fn test_check_mempool_rules_low_fee() {
let tx = create_valid_transaction();
let fee = 1; let mempool = Mempool::new();
let result = check_mempool_rules(&tx, fee, &mempool).unwrap();
assert!(!result);
}
#[test]
fn test_check_mempool_rules_high_fee() {
let tx = create_valid_transaction();
let fee = 10000; let mempool = Mempool::new();
let result = check_mempool_rules(&tx, fee, &mempool).unwrap();
assert!(result);
}
#[test]
fn test_check_mempool_rules_full_mempool() {
let tx = create_valid_transaction();
let fee = 10000;
let mut mempool = Mempool::new();
for i in 0..100_001 {
let mut hash = [0u8; 32];
hash[0] = (i & 0xff) as u8;
hash[1] = ((i >> 8) & 0xff) as u8;
hash[2] = ((i >> 16) & 0xff) as u8;
hash[3] = ((i >> 24) & 0xff) as u8;
mempool.insert(hash);
}
assert!(mempool.len() > 100_000);
let result = check_mempool_rules(&tx, fee, &mempool).unwrap();
assert!(!result);
}
#[test]
fn test_has_conflicts_no_conflicts() {
let tx = create_valid_transaction();
let mempool = Mempool::new();
let result = has_conflicts(&tx, &mempool).unwrap();
assert!(!result);
}
#[test]
fn test_has_conflicts_with_conflicts() {
let tx = create_valid_transaction();
let mut mempool = Mempool::new();
mempool.insert(tx.inputs[0].prevout.hash);
let result = has_conflicts(&tx, &mempool).unwrap();
assert!(result);
}
#[test]
fn test_signals_rbf_true() {
let mut tx = create_valid_transaction();
tx.inputs[0].sequence = 0xfffffffe;
assert!(signals_rbf(&tx));
}
#[test]
fn test_signals_rbf_false() {
let tx = create_valid_transaction();
assert!(!signals_rbf(&tx));
}
#[test]
fn test_calculate_fee_rate() {
let tx = create_valid_transaction();
let utxo_set = create_test_utxo_set();
let fee = calculate_fee(&tx, &utxo_set);
assert!(fee.is_ok());
}
#[test]
fn test_creates_new_dependencies_no_new() {
let new_tx = create_valid_transaction();
let existing_tx = create_valid_transaction();
let mempool = Mempool::new();
let utxo_set = create_test_utxo_set();
let result = creates_new_dependencies(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
assert!(!result);
}
#[test]
fn test_creates_new_dependencies_with_new() {
let mut new_tx = create_valid_transaction();
let existing_tx = create_valid_transaction();
let mempool = Mempool::new();
new_tx.inputs[0].prevout.hash = [2; 32];
let utxo_set = create_test_utxo_set();
let result = creates_new_dependencies(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
assert!(result);
}
#[test]
fn test_is_standard_script_empty() {
let script = vec![];
let result = is_standard_script(&script).unwrap();
assert!(!result);
}
#[test]
fn test_is_standard_script_too_large() {
let script = vec![OP_1; MAX_SCRIPT_SIZE + 1];
let result = is_standard_script(&script).unwrap();
assert!(!result);
}
#[test]
fn test_is_standard_script_non_standard_opcode() {
let script = vec![OP_VERIF]; let result = is_standard_script(&script).unwrap();
assert!(!result);
}
#[test]
fn test_is_standard_script_valid() {
let script = vec![OP_1];
let result = is_standard_script(&script).unwrap();
assert!(result);
}
#[test]
fn test_calculate_tx_id() {
let tx = create_valid_transaction();
let tx_id = crate::block::calculate_tx_id(&tx);
assert_eq!(tx_id.len(), 32);
let tx_id2 = crate::block::calculate_tx_id(&tx);
assert_eq!(tx_id, tx_id2);
}
#[test]
fn test_calculate_tx_id_different_txs() {
let tx1 = create_valid_transaction();
let mut tx2 = tx1.clone();
tx2.version = 2;
let id1 = crate::block::calculate_tx_id(&tx1);
let id2 = crate::block::calculate_tx_id(&tx2);
assert_ne!(id1, id2);
}
#[test]
fn test_calculate_transaction_size() {
let tx = create_valid_transaction();
let size = calculate_transaction_size(&tx);
assert!(size > 0);
let size2 = calculate_transaction_size(&tx);
assert_eq!(size, size2);
}
#[test]
fn test_calculate_transaction_size_multiple_inputs_outputs() {
let mut tx = create_valid_transaction();
tx.inputs.push(create_dummy_input());
tx.outputs.push(create_dummy_output());
let size = calculate_transaction_size(&tx);
assert!(size > 0);
}
#[test]
fn test_is_coinbase_true() {
let coinbase_tx = create_coinbase_transaction();
assert!(is_coinbase(&coinbase_tx));
}
#[test]
fn test_is_coinbase_false() {
let regular_tx = create_valid_transaction();
assert!(!is_coinbase(®ular_tx));
}
fn create_valid_transaction() -> Transaction {
Transaction {
version: 1,
inputs: vec![create_dummy_input()].into(),
outputs: vec![create_dummy_output()].into(),
lock_time: 0,
}
}
fn create_dummy_input() -> TransactionInput {
TransactionInput {
prevout: OutPoint {
hash: [1; 32],
index: 0,
},
script_sig: vec![OP_1],
sequence: 0xffffffff,
}
}
fn create_dummy_output() -> TransactionOutput {
TransactionOutput {
value: 1000,
script_pubkey: vec![OP_1].into(), }
}
fn create_test_utxo_set() -> UtxoSet {
let mut utxo_set = UtxoSet::default();
let outpoint = OutPoint {
hash: [1; 32],
index: 0,
};
let utxo = UTXO {
value: 10000,
script_pubkey: vec![OP_1].into(), height: 0,
is_coinbase: false,
};
utxo_set.insert(outpoint, std::sync::Arc::new(utxo));
utxo_set
}
fn create_coinbase_transaction() -> Transaction {
Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [0; 32].into(),
index: 0xffffffff,
},
script_sig: vec![],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 5000000000,
script_pubkey: vec![].into(),
}]
.into(),
lock_time: 0,
}
}
}