mod apply;
mod connect;
mod header;
mod script_cache;
pub use apply::{apply_transaction, calculate_tx_id};
pub(crate) use script_cache::calculate_base_script_flags_for_block;
#[cfg(not(feature = "production"))]
pub(crate) use script_cache::calculate_script_flags_for_block_with_base;
pub use script_cache::{
calculate_base_script_flags_for_block_network, calculate_script_flags_for_block_network,
};
use crate::activation::{ForkActivationTable, IsForkActive};
use crate::bip113::get_median_time_past;
use crate::error::Result;
use crate::segwit::Witness;
use crate::types::*;
use blvm_spec_lock::spec_locked;
#[cfg(feature = "production")]
use rustc_hash::{FxHashMap, FxHashSet};
#[cfg(test)]
use crate::constants::*;
#[cfg(test)]
use crate::opcodes::*;
#[cfg(test)]
use crate::transaction::{check_transaction, is_coinbase};
#[derive(Debug, Clone)]
pub struct UtxoDeltaInner<M, S> {
pub additions: M,
pub deletions: S,
}
#[cfg(feature = "production")]
pub type UtxoDelta = UtxoDeltaInner<
FxHashMap<OutPoint, std::sync::Arc<UTXO>>,
FxHashSet<crate::utxo_overlay::UtxoDeletionKey>,
>;
#[cfg(not(feature = "production"))]
pub type UtxoDelta = UtxoDeltaInner<
std::collections::HashMap<OutPoint, std::sync::Arc<UTXO>>,
std::collections::HashSet<crate::utxo_overlay::UtxoDeletionKey>,
>;
#[cfg(feature = "production")]
#[cfg(all(feature = "production", feature = "rayon"))]
pub(crate) fn skip_script_exec_cache() -> bool {
use std::sync::OnceLock;
static CACHED: OnceLock<bool> = OnceLock::new();
*CACHED.get_or_init(|| {
std::env::var("BLVM_SKIP_SCRIPT_CACHE")
.map(|v| v == "1")
.unwrap_or(false)
})
}
pub fn get_assume_valid_height() -> u64 {
#[cfg(feature = "benchmarking")]
{
use std::sync::atomic::{AtomicU64, Ordering};
static OVERRIDE: AtomicU64 = AtomicU64::new(u64::MAX);
let override_val = OVERRIDE.load(Ordering::Relaxed);
if override_val != u64::MAX {
return override_val;
}
}
crate::config::get_assume_valid_height()
}
#[track_caller]
#[spec_locked("5.3")]
pub fn connect_block(
block: &Block,
witnesses: &[Vec<Witness>],
utxo_set: UtxoSet,
height: Natural,
context: &BlockValidationContext,
) -> Result<(
ValidationResult,
UtxoSet,
crate::reorganization::BlockUndoLog,
)> {
#[cfg(all(feature = "production", feature = "rayon"))]
let block_arc = Some(std::sync::Arc::new(block.clone()));
#[cfg(not(all(feature = "production", feature = "rayon")))]
let block_arc = None;
let (result, new_utxo_set, _tx_ids, undo_log, _delta) = connect::connect_block_inner(
block, witnesses, utxo_set, None, height, context, None, None, block_arc, false, None,
)?;
Ok((result, new_utxo_set, undo_log))
}
#[spec_locked("5.3")]
pub fn connect_block_ibd<'a>(
block: &Block,
witnesses: &[Vec<Witness>],
utxo_set: UtxoSet,
height: Natural,
context: &BlockValidationContext,
bip30_index: Option<&mut crate::bip_validation::Bip30Index>,
precomputed_tx_ids: Option<&'a [Hash]>,
block_arc: Option<std::sync::Arc<Block>>,
witnesses_arc: Option<&std::sync::Arc<Vec<Vec<Witness>>>>,
) -> Result<(
ValidationResult,
UtxoSet,
std::borrow::Cow<'a, [Hash]>,
Option<UtxoDelta>,
)> {
let (result, new_utxo_set, tx_ids, _undo_log, utxo_delta) = connect::connect_block_inner(
block,
witnesses,
utxo_set,
witnesses_arc,
height,
context,
bip30_index,
precomputed_tx_ids,
block_arc,
true,
None,
)?;
Ok((result, new_utxo_set, tx_ids, utxo_delta))
}
#[spec_locked("5.5")]
fn build_time_context<H: AsRef<BlockHeader>>(
recent_headers: Option<&[H]>,
network_time: u64,
) -> Option<crate::types::TimeContext> {
recent_headers.map(|headers| {
let median_time_past = get_median_time_past(headers);
crate::types::TimeContext {
network_time,
median_time_past,
}
})
}
#[derive(Clone)]
pub struct BlockValidationContext {
pub time_context: Option<crate::types::TimeContext>,
pub network_time: u64,
pub network: crate::types::Network,
pub activation: ForkActivationTable,
pub bip54_boundary: Option<crate::types::Bip54BoundaryTimestamps>,
}
impl BlockValidationContext {
pub fn from_connect_block_ibd_args<H: AsRef<BlockHeader>>(
recent_headers: Option<&[H]>,
network_time: u64,
network: crate::types::Network,
bip54_activation_override: Option<u64>,
bip54_boundary: Option<crate::types::Bip54BoundaryTimestamps>,
) -> Self {
let time_context = build_time_context(recent_headers, network_time);
let activation = ForkActivationTable::from_network_and_bip54_override(
network,
bip54_activation_override,
);
Self {
time_context,
network_time,
network,
activation,
bip54_boundary,
}
}
pub fn from_time_context_and_network(
time_context: Option<crate::types::TimeContext>,
network: crate::types::Network,
bip54_boundary: Option<crate::types::Bip54BoundaryTimestamps>,
) -> Self {
let network_time = time_context.as_ref().map(|c| c.network_time).unwrap_or(0);
let activation = ForkActivationTable::from_network(network);
Self {
time_context,
network_time,
network,
activation,
bip54_boundary,
}
}
pub fn for_network(network: crate::types::Network) -> Self {
block_validation_context_for_connect_ibd(None::<&[BlockHeader]>, 0, network)
}
}
#[inline]
pub fn block_validation_context_for_connect_ibd<H: AsRef<BlockHeader>>(
recent_headers: Option<&[H]>,
network_time: u64,
network: Network,
) -> BlockValidationContext {
BlockValidationContext::from_connect_block_ibd_args(
recent_headers,
network_time,
network,
None,
None,
)
}
impl IsForkActive for BlockValidationContext {
#[inline]
fn is_fork_active(&self, fork: crate::types::ForkId, height: u64) -> bool {
self.activation.is_fork_active(fork, height)
}
}
#[cfg(feature = "production")]
mod tx_id_pool {
use crate::types::{Hash, Transaction};
use std::cell::RefCell;
thread_local! {
static TX_BUF: RefCell<Vec<u8>> = RefCell::new(Vec::with_capacity(
crate::optimizations::proven_bounds::MAX_TX_SIZE_PROVEN
));
}
pub fn compute_tx_id_with_pool(tx: &Transaction) -> Hash {
use crate::crypto::OptimizedSha256;
use crate::serialization::transaction::serialize_transaction_into;
TX_BUF.with(|cell| {
let mut buf = cell.borrow_mut();
serialize_transaction_into(&mut buf, tx);
OptimizedSha256::new().hash256(&buf)
})
}
}
#[spec_locked("8.4.1")]
pub fn compute_block_tx_ids_spec(block: &Block) -> Vec<Hash> {
block.transactions.iter().map(calculate_tx_id).collect()
}
pub fn compute_block_tx_ids_into(block: &Block, out: &mut Vec<Hash>) {
out.clear();
out.reserve(block.transactions.len());
#[cfg(all(feature = "production", feature = "rayon"))]
{
use rayon::prelude::*;
assert!(
block.transactions.len() <= 25_000,
"Transaction count {} must be reasonable for batch processing",
block.transactions.len()
);
let chunk: Vec<Hash> = block
.transactions
.as_ref()
.par_iter()
.map(tx_id_pool::compute_tx_id_with_pool)
.collect();
out.extend(chunk);
}
#[cfg(all(feature = "production", not(feature = "rayon")))]
{
out.extend(
block
.transactions
.iter()
.map(tx_id_pool::compute_tx_id_with_pool),
);
}
#[cfg(not(feature = "production"))]
{
out.extend(block.transactions.iter().map(calculate_tx_id));
}
}
pub fn compute_block_tx_ids(block: &Block) -> Vec<Hash> {
let mut v = Vec::new();
compute_block_tx_ids_into(block, &mut v);
v
}
#[test]
fn compute_block_tx_ids_spec_matches_optimized_paths() {
let coinbase = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [0; 32].into(),
index: 0xffffffff,
},
script_sig: vec![0x03, 0x01, 0x00, 0x00],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 50_000_000_000,
script_pubkey: vec![0x51].into(),
}]
.into(),
lock_time: 0,
};
let tx2 = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [1u8; 32].into(),
index: 0,
},
script_sig: vec![0x51],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 10_000,
script_pubkey: vec![0x51].into(),
}]
.into(),
lock_time: 0,
};
let block = Block {
header: BlockHeader {
version: 1,
prev_block_hash: [0; 32],
merkle_root: [0; 32],
timestamp: 1,
bits: 0x207fffff,
nonce: 0,
},
transactions: vec![coinbase, tx2].into_boxed_slice(),
};
assert_eq!(
compute_block_tx_ids_spec(&block),
compute_block_tx_ids(&block),
"§8.4.1 spec reference must match compute_block_tx_ids / compute_block_tx_ids_into"
);
}
#[test]
fn test_connect_block_invalid_header() {
let coinbase_tx = 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,
};
let block = Block {
header: BlockHeader {
version: 0, prev_block_hash: [0; 32],
merkle_root: [0; 32],
timestamp: 1231006505,
bits: 0x1d00ffff,
nonce: 0,
},
transactions: vec![coinbase_tx].into_boxed_slice(),
};
let utxo_set = UtxoSet::default();
let witnesses: Vec<Vec<Witness>> = block
.transactions
.iter()
.map(|tx| {
(0..tx.inputs.len())
.map(|_| Vec::with_capacity(2))
.collect()
})
.collect();
let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
assert!(matches!(result, ValidationResult::Invalid(_)));
}
#[test]
fn test_connect_block_no_transactions() {
let block = Block {
header: BlockHeader {
version: 1,
prev_block_hash: [0; 32],
merkle_root: [0; 32],
timestamp: 1231006505,
bits: 0x1d00ffff,
nonce: 0,
},
transactions: vec![].into_boxed_slice(), };
let utxo_set = UtxoSet::default();
let witnesses: Vec<Vec<Witness>> = block
.transactions
.iter()
.map(|tx| {
(0..tx.inputs.len())
.map(|_| Vec::with_capacity(2))
.collect()
})
.collect();
let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
assert!(matches!(result, ValidationResult::Invalid(_)));
}
#[test]
fn test_connect_block_first_tx_not_coinbase() {
let regular_tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [1; 32].into(),
index: 0,
},
script_sig: vec![],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![].into(),
}]
.into(),
lock_time: 0,
};
let block = Block {
header: BlockHeader {
version: 1,
prev_block_hash: [0; 32],
merkle_root: [0; 32],
timestamp: 1231006505,
bits: 0x1d00ffff,
nonce: 0,
},
transactions: vec![regular_tx].into_boxed_slice(), };
let utxo_set = UtxoSet::default();
let witnesses: Vec<Vec<Witness>> = block
.transactions
.iter()
.map(|tx| {
(0..tx.inputs.len())
.map(|_| Vec::with_capacity(2))
.collect()
})
.collect();
let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
assert!(matches!(result, ValidationResult::Invalid(_)));
}
#[test]
fn test_connect_block_coinbase_exceeds_subsidy() {
let coinbase_tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [0; 32].into(),
index: 0xffffffff,
},
script_sig: vec![],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 6000000000, script_pubkey: vec![].into(),
}]
.into(),
lock_time: 0,
};
let block = Block {
header: BlockHeader {
version: 1,
prev_block_hash: [0; 32],
merkle_root: [0; 32],
timestamp: 1231006505,
bits: 0x1d00ffff,
nonce: 0,
},
transactions: vec![coinbase_tx].into_boxed_slice(),
};
let utxo_set = UtxoSet::default();
let witnesses: Vec<Vec<Witness>> = block
.transactions
.iter()
.map(|tx| {
(0..tx.inputs.len())
.map(|_| Vec::with_capacity(2))
.collect()
})
.collect();
let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
assert!(matches!(result, ValidationResult::Invalid(_)));
}
#[test]
fn test_apply_transaction_regular() {
let mut utxo_set = UtxoSet::default();
let prev_outpoint = OutPoint {
hash: [1; 32],
index: 0,
};
let prev_utxo = UTXO {
value: 1000,
script_pubkey: vec![OP_1].into(), height: 0,
is_coinbase: false,
};
utxo_set.insert(prev_outpoint, std::sync::Arc::new(prev_utxo));
let regular_tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [1; 32].into(),
index: 0,
},
script_sig: vec![OP_1], sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 500,
script_pubkey: vec![OP_2].into(), }]
.into(),
lock_time: 0,
};
let (new_utxo_set, _undo_entries) = apply_transaction(®ular_tx, utxo_set, 1).unwrap();
assert_eq!(new_utxo_set.len(), 1);
}
#[test]
fn test_apply_transaction_multiple_outputs() {
let coinbase_tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [0; 32].into(),
index: 0xffffffff,
},
script_sig: vec![],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![
TransactionOutput {
value: 2500000000,
script_pubkey: vec![OP_1].into(),
},
TransactionOutput {
value: 2500000000,
script_pubkey: vec![OP_2].into(),
},
]
.into(),
lock_time: 0,
};
let utxo_set = UtxoSet::default();
let (new_utxo_set, _undo_entries) = apply_transaction(&coinbase_tx, utxo_set, 0).unwrap();
assert_eq!(new_utxo_set.len(), 2);
}
#[test]
fn test_validate_block_header_valid() {
use sha2::{Digest, Sha256};
let header = BlockHeader {
version: 1,
prev_block_hash: [0; 32],
merkle_root: Sha256::digest(b"test merkle root")[..].try_into().unwrap(),
timestamp: 1231006505,
bits: 0x1d00ffff,
nonce: 0,
};
let result = header::validate_block_header(&header, None).unwrap();
assert!(result);
}
#[test]
fn test_validate_block_header_invalid_version() {
let header = BlockHeader {
version: 0, prev_block_hash: [0; 32],
merkle_root: [0; 32],
timestamp: 1231006505,
bits: 0x1d00ffff,
nonce: 0,
};
let result = header::validate_block_header(&header, None).unwrap();
assert!(!result);
}
#[test]
fn test_validate_block_header_invalid_bits() {
let header = BlockHeader {
version: 1,
prev_block_hash: [0; 32],
merkle_root: [0; 32],
timestamp: 1231006505,
bits: 0, nonce: 0,
};
let result = header::validate_block_header(&header, None).unwrap();
assert!(!result);
}
#[test]
fn test_is_coinbase_true() {
let coinbase_tx = 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,
};
assert!(is_coinbase(&coinbase_tx));
}
#[test]
fn test_is_coinbase_false_wrong_hash() {
let regular_tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [1; 32].into(),
index: 0xffffffff,
}, script_sig: vec![],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 5000000000,
script_pubkey: vec![].into(),
}]
.into(),
lock_time: 0,
};
assert!(!is_coinbase(®ular_tx));
}
#[test]
fn test_is_coinbase_false_wrong_index() {
let regular_tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [0; 32].into(),
index: 0,
}, script_sig: vec![],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 5000000000,
script_pubkey: vec![].into(),
}]
.into(),
lock_time: 0,
};
assert!(!is_coinbase(®ular_tx));
}
#[test]
fn test_is_coinbase_false_multiple_inputs() {
let regular_tx = Transaction {
version: 1,
inputs: vec![
TransactionInput {
prevout: OutPoint {
hash: [0; 32].into(),
index: 0xffffffff,
},
script_sig: vec![],
sequence: 0xffffffff,
},
TransactionInput {
prevout: OutPoint {
hash: [1; 32],
index: 0,
},
script_sig: vec![],
sequence: 0xffffffff,
},
]
.into(),
outputs: vec![TransactionOutput {
value: 5000000000,
script_pubkey: vec![].into(),
}]
.into(),
lock_time: 0,
};
assert!(!is_coinbase(®ular_tx));
}
#[test]
fn test_calculate_tx_id() {
let tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [0; 32].into(),
index: 0,
},
script_sig: vec![],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![].into(),
}]
.into(),
lock_time: 0,
};
let tx_id = calculate_tx_id(&tx);
assert_eq!(tx_id.len(), 32);
let tx_id2 = calculate_tx_id(&tx);
assert_eq!(tx_id, tx_id2);
let mut tx2 = tx.clone();
tx2.version = 2;
let tx_id3 = calculate_tx_id(&tx2);
assert_ne!(tx_id, tx_id3);
}
#[test]
fn test_calculate_tx_id_different_versions() {
let tx1 = Transaction {
version: 2,
inputs: vec![].into(),
outputs: vec![].into(),
lock_time: 0,
};
let tx2 = Transaction {
version: 1,
inputs: vec![].into(),
outputs: vec![].into(),
lock_time: 0,
};
let id1 = calculate_tx_id(&tx1);
let id2 = calculate_tx_id(&tx2);
assert_ne!(id1, id2);
}
#[test]
fn test_connect_block_empty_transactions() {
let block = Block {
header: BlockHeader {
version: 1,
prev_block_hash: [0; 32],
merkle_root: [0; 32], timestamp: 1231006505,
bits: 0x1d00ffff,
nonce: 0,
},
transactions: vec![].into_boxed_slice(), };
let utxo_set = UtxoSet::default();
let witnesses: Vec<Vec<Witness>> = block
.transactions
.iter()
.map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
.collect();
let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
assert!(result.is_ok());
let (validation_result, _, _undo_log) = result.unwrap();
assert!(matches!(validation_result, ValidationResult::Invalid(_)));
}
#[test]
fn test_connect_block_invalid_coinbase() {
let invalid_coinbase = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [1; 32].into(),
index: 0,
}, script_sig: vec![],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 5000000000,
script_pubkey: vec![].into(),
}]
.into(),
lock_time: 0,
};
let block = Block {
header: BlockHeader {
version: 1,
prev_block_hash: [0; 32],
merkle_root: [0; 32],
timestamp: 1231006505,
bits: 0x1d00ffff,
nonce: 0,
},
transactions: vec![invalid_coinbase].into_boxed_slice(),
};
let utxo_set = UtxoSet::default();
let witnesses: Vec<Vec<Witness>> = block
.transactions
.iter()
.map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
.collect();
let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
assert!(result.is_ok());
let (validation_result, _, _undo_log) = result.unwrap();
assert!(matches!(validation_result, ValidationResult::Invalid(_)));
}
#[test]
fn test_apply_transaction_insufficient_funds() {
let mut utxo_set = UtxoSet::default();
let prev_outpoint = OutPoint {
hash: [1; 32],
index: 0,
};
let prev_utxo = UTXO {
value: 100, script_pubkey: vec![OP_1].into(),
height: 0,
is_coinbase: false,
};
utxo_set.insert(prev_outpoint, std::sync::Arc::new(prev_utxo));
let tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [1; 32].into(),
index: 0,
},
script_sig: vec![OP_1],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 200, script_pubkey: vec![OP_2].into(),
}]
.into(),
lock_time: 0,
};
let result = apply_transaction(&tx, utxo_set, 1);
assert!(result.is_ok());
}
#[test]
fn test_apply_transaction_missing_utxo() {
let utxo_set = UtxoSet::default();
let tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [1; 32].into(),
index: 0,
},
script_sig: vec![OP_1],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 100,
script_pubkey: vec![OP_2].into(),
}]
.into(),
lock_time: 0,
};
let result = apply_transaction(&tx, utxo_set, 1);
assert!(result.is_ok());
}
#[test]
fn test_validate_block_header_future_timestamp() {
use sha2::{Digest, Sha256};
let header = BlockHeader {
version: 1,
prev_block_hash: [0; 32],
merkle_root: Sha256::digest(b"test merkle root")[..].try_into().unwrap(),
timestamp: 9999999999, bits: 0x1d00ffff,
nonce: 0,
};
let result = header::validate_block_header(&header, None).unwrap();
assert!(result);
}
#[test]
fn test_validate_block_header_zero_timestamp() {
use sha2::{Digest, Sha256};
let header = BlockHeader {
version: 1,
prev_block_hash: [0; 32],
merkle_root: Sha256::digest(b"test merkle root")[..].try_into().unwrap(),
timestamp: 0, bits: 0x1d00ffff,
nonce: 0,
};
let result = header::validate_block_header(&header, None).unwrap();
assert!(!result);
}
#[test]
fn test_connect_block_coinbase_exceeds_subsidy_edge() {
let coinbase_tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [0; 32].into(),
index: 0xffffffff,
},
script_sig: vec![],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 2100000000000000, script_pubkey: vec![].into(),
}]
.into(),
lock_time: 0,
};
let block = Block {
header: BlockHeader {
version: 1,
prev_block_hash: [0; 32],
merkle_root: [0; 32],
timestamp: 1231006505,
bits: 0x1d00ffff,
nonce: 0,
},
transactions: vec![coinbase_tx].into_boxed_slice(),
};
let utxo_set = UtxoSet::default();
let witnesses: Vec<Vec<Witness>> = block
.transactions
.iter()
.map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
.collect();
let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
assert!(result.is_ok());
let (validation_result, _, _undo_log) = result.unwrap();
assert!(matches!(validation_result, ValidationResult::Invalid(_)));
}
#[test]
fn test_connect_block_first_tx_not_coinbase_edge() {
let regular_tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [1; 32].into(),
index: 0,
},
script_sig: vec![OP_1],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 1000,
script_pubkey: vec![OP_2].into(),
}]
.into(),
lock_time: 0,
};
let block = Block {
header: BlockHeader {
version: 1,
prev_block_hash: [0; 32],
merkle_root: [0; 32],
timestamp: 1231006505,
bits: 0x1d00ffff,
nonce: 0,
},
transactions: vec![regular_tx].into_boxed_slice(), };
let utxo_set = UtxoSet::default();
let witnesses: Vec<Vec<Witness>> = block
.transactions
.iter()
.map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
.collect();
let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
assert!(result.is_ok());
let (validation_result, _, _undo_log) = result.unwrap();
assert!(matches!(validation_result, ValidationResult::Invalid(_)));
}
#[test]
fn test_apply_transaction_multiple_inputs() {
let mut utxo_set = UtxoSet::default();
let outpoint1 = OutPoint {
hash: [1; 32],
index: 0,
};
let utxo1 = UTXO {
value: 500,
script_pubkey: vec![OP_1].into(),
height: 0,
is_coinbase: false,
};
utxo_set.insert(outpoint1, std::sync::Arc::new(utxo1));
let outpoint2 = OutPoint {
hash: [2; 32],
index: 0,
};
let utxo2 = UTXO {
value: 300,
script_pubkey: vec![OP_2].into(),
height: 0,
is_coinbase: false,
};
utxo_set.insert(outpoint2, std::sync::Arc::new(utxo2));
let tx = Transaction {
version: 1,
inputs: vec![
TransactionInput {
prevout: OutPoint {
hash: [1; 32].into(),
index: 0,
},
script_sig: vec![OP_1],
sequence: 0xffffffff,
},
TransactionInput {
prevout: OutPoint {
hash: [2; 32],
index: 0,
},
script_sig: vec![OP_2],
sequence: 0xffffffff,
},
]
.into(),
outputs: vec![TransactionOutput {
value: 700, script_pubkey: vec![OP_3].into(),
}]
.into(),
lock_time: 0,
};
let (new_utxo_set, _undo_entries) = apply_transaction(&tx, utxo_set, 1).unwrap();
assert_eq!(new_utxo_set.len(), 1);
}
#[test]
fn test_apply_transaction_no_outputs() {
let mut utxo_set = UtxoSet::default();
let prev_outpoint = OutPoint {
hash: [1; 32],
index: 0,
};
let prev_utxo = UTXO {
value: 1000,
script_pubkey: vec![OP_1].into(),
height: 0,
is_coinbase: false,
};
utxo_set.insert(prev_outpoint, std::sync::Arc::new(prev_utxo));
let tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [1; 32].into(),
index: 0,
},
script_sig: vec![OP_1],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![].into(), lock_time: 0,
};
let validation_result = crate::transaction::check_transaction(&tx).unwrap();
assert!(matches!(validation_result, ValidationResult::Invalid(_)));
let valid_tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [1; 32].into(),
index: 0,
},
script_sig: vec![OP_1],
sequence: 0xffffffff,
}]
.into(),
outputs: vec![TransactionOutput {
value: 500, script_pubkey: vec![OP_1].into(),
}]
.into(),
lock_time: 0,
};
let (new_utxo_set, _undo_entries) = apply_transaction(&valid_tx, utxo_set, 1).unwrap();
assert_eq!(new_utxo_set.len(), 1);
let output_outpoint = OutPoint {
hash: calculate_tx_id(&valid_tx),
index: 0,
};
assert!(new_utxo_set.contains_key(&output_outpoint));
}