extern crate alloc;
use core::ffi::c_uint;
use bitcoin::block::Header as BlockHeader;
use bitcoin::hashes::sha256;
use bitcoin::Block;
use bitcoin::CompactTarget;
use bitcoin::OutPoint;
use bitcoin::ScriptBuf;
use bitcoin::Target;
use bitcoin::Transaction;
use bitcoin::TxIn;
use bitcoin::Txid;
use floresta_common::prelude::*;
use rustreexo::accumulator::proof::Proof;
use rustreexo::accumulator::stump::Stump;
use super::chainparams::ChainParams;
use super::error::BlockValidationErrors;
use super::error::BlockchainError;
use super::udata;
use crate::pruned_utreexo::utxo_data::UtxoData;
use crate::TransactionError;
pub const COIN_VALUE: u64 = 100_000_000;
pub const UTREEXO_TAG_V1: [u8; 64] = [
0x5b, 0x83, 0x2d, 0xb8, 0xca, 0x26, 0xc2, 0x5b, 0xe1, 0xc5, 0x42, 0xd6, 0xcc, 0xed, 0xdd, 0xa8,
0xc1, 0x45, 0x61, 0x5c, 0xff, 0x5c, 0x35, 0x72, 0x7f, 0xb3, 0x46, 0x26, 0x10, 0x80, 0x7e, 0x20,
0xae, 0x53, 0x4d, 0xc3, 0xf6, 0x42, 0x99, 0x19, 0x99, 0x31, 0x77, 0x2e, 0x03, 0x78, 0x7d, 0x18,
0x15, 0x6e, 0xb3, 0x15, 0x1e, 0x0e, 0xd1, 0xb3, 0x09, 0x8b, 0xdc, 0x84, 0x45, 0x86, 0x18, 0x85,
];
pub const UNSPENDABLE_BIP30_UTXO_91722: [u8; 32] = [
0x84, 0xb3, 0xaf, 0x07, 0x83, 0xb4, 0x10, 0xb4, 0x56, 0x4c, 0x5d, 0x1f, 0x36, 0x18, 0x68, 0x55,
0x9f, 0x7c, 0xf7, 0x7c, 0xfc, 0x65, 0xce, 0x2b, 0xe9, 0x51, 0x21, 0x03, 0x57, 0x02, 0x2f, 0xe3,
];
pub const UNSPENDABLE_BIP30_UTXO_91812: [u8; 32] = [
0xbc, 0x6b, 0x4b, 0xf7, 0xce, 0xbb, 0xd3, 0x3a, 0x18, 0xd6, 0xb0, 0xfe, 0x1f, 0x8e, 0xcc, 0x7a,
0xa5, 0x40, 0x30, 0x83, 0xc3, 0x9e, 0xe3, 0x43, 0xb9, 0x85, 0xd5, 0x1f, 0xd0, 0x29, 0x5a, 0xd8,
];
#[derive(Debug, Clone)]
pub struct Consensus {
pub parameters: ChainParams,
}
impl Consensus {
pub fn get_subsidy(&self, height: u32) -> u64 {
let halvings = height / self.parameters.subsidy_halving_interval as u32;
if halvings >= 64 {
return 0;
}
let mut subsidy = 50 * COIN_VALUE;
subsidy >>= halvings;
subsidy
}
#[allow(unused)]
pub fn verify_block_transactions(
height: u32,
mut utxos: HashMap<OutPoint, UtxoData>,
transactions: &[Transaction],
subsidy: u64,
verify_script: bool,
flags: c_uint,
) -> Result<(), BlockchainError> {
if transactions.is_empty() {
return Err(BlockValidationErrors::EmptyBlock)?;
}
let mut fee = 0;
for (n, transaction) in transactions.iter().enumerate() {
if n == 0 {
if !transaction.is_coinbase() {
return Err(BlockValidationErrors::FirstTxIsNotCoinbase)?;
}
Self::verify_coinbase(transaction)?;
continue;
}
let (in_value, out_value) =
Self::verify_transaction(transaction, &mut utxos, height, verify_script, flags)?;
fee += in_value - out_value;
}
let allowed_reward = fee + subsidy;
let coinbase_total: u64 = transactions[0]
.output
.iter()
.map(|out| out.value.to_sat())
.sum();
if coinbase_total > allowed_reward {
return Err(BlockValidationErrors::BadCoinbaseOutValue)?;
}
Ok(())
}
pub fn verify_transaction(
transaction: &Transaction,
utxos: &mut HashMap<OutPoint, UtxoData>,
height: u32,
_verify_script: bool,
_flags: c_uint,
) -> Result<(u64, u64), BlockchainError> {
let txid = || transaction.compute_txid();
if transaction.input.is_empty() {
return Err(tx_err!(txid, EmptyInputs))?;
}
if transaction.output.is_empty() {
return Err(tx_err!(txid, EmptyOutputs))?;
}
let out_value: u64 = transaction
.output
.iter()
.map(|out| out.value.to_sat())
.sum();
let mut in_value = 0;
for input in transaction.input.iter() {
if input.previous_output.is_null() {
return Err(tx_err!(txid, NullPrevOut))?;
}
let utxo = Self::get_utxo(input, utxos, txid)?;
let txout = &utxo.txout;
if utxo.is_coinbase && (height < utxo.creation_height + 100) {
return Err(tx_err!(txid, CoinbaseNotMatured))?;
}
Self::validate_script_size(&txout.script_pubkey, txid)?;
Self::validate_script_size(&input.script_sig, txid)?;
in_value += txout.value.to_sat();
}
if out_value > in_value {
return Err(tx_err!(txid, NotEnoughMoney))?;
}
if out_value > 21_000_000 * COIN_VALUE {
return Err(BlockValidationErrors::TooManyCoins)?;
}
#[cfg(feature = "bitcoinconsensus")]
if _verify_script {
transaction
.verify_with_flags(
|outpoint| utxos.remove(outpoint).map(|utxo| utxo.txout),
_flags,
)
.map_err(|e| tx_err!(txid, ScriptValidationError, format!("{e:?}")))?;
};
Ok((in_value, out_value))
}
fn get_utxo<'a, F: Fn() -> Txid>(
input: &TxIn,
utxos: &'a HashMap<OutPoint, UtxoData>,
txid: F,
) -> Result<&'a UtxoData, TransactionError> {
match utxos.get(&input.previous_output) {
Some(utxo) => Ok(utxo),
None => Err(tx_err!(txid, UtxoNotFound, input.previous_output)),
}
}
#[allow(unused)]
fn validate_locktime(
input: &TxIn,
transaction: &Transaction,
height: u32,
) -> Result<(), BlockValidationErrors> {
unimplemented!("validate_locktime")
}
fn validate_script_size<F: Fn() -> Txid>(
script: &ScriptBuf,
txid: F,
) -> Result<(), TransactionError> {
if script.len() > 10_000 {
return Err(tx_err!(txid, ScriptError));
}
if script.count_sigops() > 80_000 {
return Err(tx_err!(txid, ScriptError));
}
Ok(())
}
pub fn verify_coinbase(tx: &Transaction) -> Result<(), TransactionError> {
let txid = || tx.compute_txid();
let input = match tx.input.as_slice() {
[i] => i,
_ => return Err(tx_err!(txid, InvalidCoinbase, "Coinbase must have 1 input")),
};
if !input.previous_output.is_null() {
return Err(tx_err!(txid, InvalidCoinbase, "Invalid Coinbase PrevOut"));
}
let size = input.script_sig.len();
if !(2..=100).contains(&size) {
return Err(tx_err!(txid, InvalidCoinbase, "Invalid ScriptSig size"));
}
Ok(())
}
pub fn check_bip94_time(
block: &BlockHeader,
prev_block: &BlockHeader,
) -> Result<(), BlockValidationErrors> {
if block.time < (prev_block.time - 600) {
return Err(BlockValidationErrors::BIP94TimeWarp);
}
Ok(())
}
pub fn calc_next_work_required(
last_block: &BlockHeader,
first_block: &BlockHeader,
params: ChainParams,
) -> Target {
let actual_timespan = last_block.time - first_block.time;
let bits = match params.enforce_bip94 {
true => first_block.bits,
false => last_block.bits,
};
CompactTarget::from_next_work_required(bits, actual_timespan as u64, params).into()
}
pub fn update_acc(
acc: &Stump,
block: &Block,
height: u32,
proof: Proof,
del_hashes: Vec<sha256::Hash>,
) -> Result<Stump, BlockchainError> {
let block_hash = block.block_hash();
if Self::contains_unspendable_utxo(&del_hashes) {
return Err(BlockValidationErrors::UnspendableUTXO)?;
}
let del_hashes: Vec<_> = del_hashes.into_iter().map(Into::into).collect();
let adds = udata::proof_util::get_block_adds(block, height, block_hash);
let acc = acc.modify(&adds, &del_hashes, &proof)?.0;
Ok(acc)
}
fn contains_unspendable_utxo(del_hashes: &[sha256::Hash]) -> bool {
del_hashes.iter().any(|hash| {
let bytes = hash.as_ref();
bytes == UNSPENDABLE_BIP30_UTXO_91722 || bytes == UNSPENDABLE_BIP30_UTXO_91812
})
}
}
#[cfg(test)]
mod tests {
use bitcoin::absolute::LockTime;
use bitcoin::consensus::encode::deserialize_hex;
use bitcoin::hashes::Hash;
use bitcoin::opcodes::all::OP_NOP;
use bitcoin::opcodes::OP_TRUE;
use bitcoin::transaction::Version;
use bitcoin::Amount;
use bitcoin::OutPoint;
use bitcoin::ScriptBuf;
use bitcoin::Sequence;
use bitcoin::Transaction;
use bitcoin::TxIn;
use bitcoin::TxOut;
use bitcoin::Txid;
use bitcoin::Witness;
use floresta_common::assert_err;
use floresta_common::assert_ok;
use super::*;
macro_rules! txout {
($sats:expr, $script:expr) => {
TxOut {
value: Amount::from_sat($sats),
script_pubkey: $script,
}
};
}
macro_rules! txin {
($outpoint:expr, $script:expr) => {
TxIn {
previous_output: $outpoint,
script_sig: $script,
sequence: Sequence::MAX,
witness: Witness::new(),
}
};
($outpoint:expr, $script:expr, $sequence:expr) => {
TxIn {
previous_output: $outpoint,
script_sig: $script,
sequence: $sequence,
witness: Witness::new(),
}
};
}
#[cfg(feature = "bitcoinconsensus")]
const TX_VALIDATION_CASES_LEGACY: &[&str] = &[
"0200000001fdaf053eeaeed2e96594b542792417c6c223fa8571e88d31e296b1a655c81eb500000000fd0306012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901294d000275757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575750000000001260200000000000017a9144e0c22952fa87a99064f93f77a74fb2e0184f04d8700000000:260200000000000017a9144e0c22952fa87a99064f93f77a74fb2e0184f04d87",
"02000000013b4568b6d740e1625710ec49e6ce994e79ad55d7d1eef03cd945d5667229c05200000000fdcb04012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901294cc97575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575750000000001260200000000000017a91436f39a404b67ec67516b353b30c3766b33609dec8700000000:260200000000000017a91436f39a404b67ec67516b353b30c3766b33609dec87",
"02000000010f5f070f769b7d6290882dfd8659150e781f4ea7f19c2f5d93a9917aebd7d54500000000fd970501290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901294d0004757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575750000000001260200000000000017a914dba622860b2edd8be5861f0cb204248f6a9f0b9d8700000000:260200000000000017a914dba622860b2edd8be5861f0cb204248f6a9f0b9d87",
"0200000001540b20c497074bcb8c83869601566749f210ce7965f4e55ec1be9a8f648d743800000000fd9a0801290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901294cc875757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575757575750000000001260200000000000017a9141ea5312cab0ad9a531c35b9051dc136bebc0669e8700000000:260200000000000017a9141ea5312cab0ad9a531c35b9051dc136bebc0669e87",
"02000000019ebe6327436c18f978611d3fae6c2f6834cd6fbc08471134bc4bee16f8b9062400000000fdec03012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901294cc86d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d0000000001260200000000000017a914a73ee4302bb9a6e5b7c42cae84b7c548638bc0148700000000:260200000000000017a914a73ee4302bb9a6e5b7c42cae84b7c548638bc01487",
"0200000001cbbe326a4360ed38487a6fd1a091da0c22fd5084127401c3fba1ad52b8a37f3300000000fdec03012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901290129012901294cc8acacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacacac0000000001260200000000000017a914436c244dc646042e3bafb50ff5729a0e80153e708700000000:260200000000000017a914436c244dc646042e3bafb50ff5729a0e80153e7087",
"020000000139cf57739cb5d08335b7ed529792de34987d763d4856957dcc4b258c9cb1d0d300000000d601ff4cd276767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767600000000012602000000000000160014ef814b32b68a6138974b1144603b5c10569eeef600000000:2602000000000000160014ef814b32b68a6138974b1144603b5c10569eeef6",
];
fn coinbase(is_valid: bool) -> Transaction {
let script_sig = if is_valid {
ScriptBuf::from_hex("03f0a2a4d9f0a2").unwrap()
} else {
ScriptBuf::from_hex(&format!("{:0>420}", "")).unwrap()
};
let input = txin!(OutPoint::null(), script_sig);
let output_script = ScriptBuf::from_hex("41047eda6bd04fb27cab6e7c28c99b94977f073e912f25d1ff7165d9c95cd9bbe6da7e7ad7f2acb09e0ced91705f7616af53bee51a238b7dc527f2be0aa60469d140ac").unwrap();
let output = txout!(5_000_350_000, output_script);
Transaction {
version: Version(1),
lock_time: LockTime::from_height(150_007).unwrap(),
input: vec![input],
output: vec![output],
}
}
#[test]
fn test_validate_script_size() {
use bitcoin::hashes::Hash;
let dummy_txid = Txid::all_zeros;
let large_script = ScriptBuf::from_hex(&format!("{:0>20002}", "")).unwrap();
assert_eq!(large_script.len(), 10_001);
let small_script =
ScriptBuf::from_hex("76a9149206a30c09cc853bb03bd917a4f9f29b089c1bc788ac").unwrap();
assert_ok!(Consensus::validate_script_size(&small_script, dummy_txid));
assert_err!(Consensus::validate_script_size(&large_script, dummy_txid));
}
#[test]
fn test_validate_coinbase() {
let valid_one = coinbase(true);
let invalid_one = coinbase(false);
assert_ok!(Consensus::verify_coinbase(&valid_one));
assert_eq!(
Consensus::verify_coinbase(&invalid_one)
.unwrap_err()
.error
.to_string(),
"Invalid coinbase: \"Invalid ScriptSig size\""
);
}
#[test]
fn test_coinbase_maturity() {
fn test_case(
coinbase_tx: &Transaction,
spending_tx: &Transaction,
expected_coinbase: &str,
expected_spending: &str,
heights: (u32, u32),
expected_ok: bool,
) {
let (creation_height, spending_height) = heights;
assert_eq!(coinbase_tx.compute_txid().to_string(), expected_coinbase);
assert_eq!(spending_tx.compute_txid().to_string(), expected_spending);
assert_ok!(Consensus::verify_coinbase(coinbase_tx));
let mut utxos = HashMap::new();
utxos.insert(
spending_tx.input[0].previous_output,
UtxoData {
txout: coinbase_tx.output[0].clone(),
is_coinbase: true,
creation_height,
creation_time: 0, },
);
let spend_result =
Consensus::verify_transaction(spending_tx, &mut utxos, spending_height, true, 0);
if expected_ok {
assert_ok!(spend_result);
} else {
match spend_result.unwrap_err() {
BlockchainError::TransactionError(inner) => {
let txid = || spending_tx.compute_txid();
assert_eq!(inner, tx_err!(txid, CoinbaseNotMatured));
}
e => panic!("Expected a TransactionError, but got: {e:?}"),
}
}
}
let coinbase_74_547 = deserialize_hex("01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff080418ba001c02c205ffffffff0100f2052a010000004341040e6d9c4cea77c12fab14f4ef5a7713ed50a5c847e4060ae513ea672427babf878ee2b88a0f515157b39b706b1c799cdc42edac2554b205a6e61bf4bcc8ca595aac00000000").unwrap();
let spending_tx = deserialize_hex("01000000011581359e5ef5c3d424dce0992bfd3506b787313e7bf1b75a571e34f90b7b4dbd0000000049483045022100a0bba3f5731b0d89af8a4fa9f20ccdd89a0758b9eee909db1f8a3eb55d8c907c02201e52ff6abc5c88c09f48dcd40a44beb25f6715aac01ab1e3b9b3e12b2509ab4c01ffffffff0100f2052a010000001976a914ee3a639d407116b0debb484f0335144d794f57e188ac00000000").unwrap();
test_case(
&coinbase_74_547,
&spending_tx,
"bd4d7b0bf9341e575ab7f17b3e3187b70635fd2b99e0dc24d4c3f55e9e358115",
"315b8651901195deed71f830cb37f33b23eab9c524bd2f2cca32d5b7273a3528",
(74_547, 74_648),
true,
);
let coinbase_232_709 = deserialize_hex("01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2703058d03062f503253482f04a9357651086007873a080000020d3430363638332f736c7573682f0000000001cbef8f96000000001976a914e285a29e0704004d4e95dbb7c57a98563d9fb2eb88ac00000000").unwrap();
let spending_tx = deserialize_hex("01000000014c901737cca754ad3d0c96fb3e8523d8b00380babf3c20d35779316392b40767000000006a473044022026f8b003ce423ba36fd95f48d3bf297dfb37cd5533a635b76a1f47c76c30e99402205113058dfc25f821905d4ba26a05e870722fb0877e1406605a857f1d71ea13f5012102c37e0f2966c6f154fd43dc45fc3b5fcfdd2d85ed3c95961d6d07a60409e6e4c1ffffffff02cbf68c01000000001976a9145c0727387071c75451b500aacd7077744d6dfbea88ac80626a94000000001976a914e1c9b052561cf0a1da9ee3175df7d5a2d7ff7dd488ac00000000").unwrap();
test_case(
&coinbase_232_709,
&spending_tx,
"6707b49263317957d3203cbfba8003b0d823853efb960c3dad54a7cc3717904c",
"0a5672eecf25f809d11053d7dc9098d8db3c98387d51052b499a4c79eef41b61",
(232_709, 232_809), true,
);
test_case(
&coinbase_232_709,
&spending_tx,
"6707b49263317957d3203cbfba8003b0d823853efb960c3dad54a7cc3717904c",
"0a5672eecf25f809d11053d7dc9098d8db3c98387d51052b499a4c79eef41b61",
(232_709, 232_808), false,
);
}
#[test]
#[cfg(feature = "bitcoinconsensus")]
fn test_consume_utxos() {
let mut utxos = HashMap::new();
let tx: Transaction = deserialize_hex("0100000001bd597773d03dcf6e22ba832f2387152c9ab69d250a8d86792bdfeb690764af5b010000006c493046022100841d4f503f44dd6cef8781270e7260db73d0e3c26c4f1eea61d008760000b01e022100bc2675b8598773984bcf0bb1a7cad054c649e8a34cb522a118b072a453de1bf6012102de023224486b81d3761edcd32cedda7cbb30a4263e666c87607883197c914022ffffffff021ee16700000000001976a9144883bb595608dcfe882aea5f7c579ef107a4fb5b88ac52a0aa00000000001976a914782231de72adb5c9df7367ab0c21c7b44bbd743188ac00000000").unwrap();
assert_eq!(tx.input.len(), 1, "We only spend one utxo in this tx");
let outpoint = tx.input[0].previous_output;
let output_script =
ScriptBuf::from_hex("76a9149206a30c09cc853bb03bd917a4f9f29b089c1bc788ac").unwrap();
utxos.insert(outpoint, txout!(18000000, output_script));
let flags = bitcoinconsensus::VERIFY_P2SH;
tx.verify_with_flags(|outpoint| utxos.remove(outpoint), flags)
.unwrap();
assert!(utxos.is_empty(), "Utxo should have been consumed");
assert_eq!(
tx.verify_with_flags(|outpoint| utxos.remove(outpoint), flags),
Err(bitcoin::transaction::TxVerifyError::UnknownSpentOutput(
outpoint
)),
);
}
#[cfg(feature = "bitcoinconsensus")]
fn create_case(case: &str) -> (Transaction, HashMap<OutPoint, UtxoData>) {
let Some((spending, prevout)) = case.split_once(':') else {
panic!("Invalid case: {case}");
};
let spending_tx: Transaction = deserialize_hex(spending).unwrap();
let txout: TxOut = deserialize_hex(prevout).unwrap();
let mut utxos = HashMap::new();
utxos.insert(
spending_tx.input[0].previous_output,
UtxoData {
txout,
is_coinbase: false,
creation_height: 0,
creation_time: 0,
},
);
(spending_tx, utxos)
}
#[cfg(feature = "bitcoinconsensus")]
#[test]
fn test_transaction_validation_legacy() {
let expected = [false, true, false, false, true, false, false];
let mut valid = expected.into_iter();
for case in TX_VALIDATION_CASES_LEGACY.iter() {
let (transaction, mut utxos) = create_case(case);
let dummy_height = 0;
let result = Consensus::verify_transaction(
&transaction,
&mut utxos,
dummy_height,
true,
bitcoinconsensus::VERIFY_ALL_PRE_TAPROOT,
);
let expected = valid.next().unwrap();
assert_eq!(result.is_ok(), expected, "{case} {result:?}");
}
}
pub fn true_script() -> ScriptBuf {
let mut script = ScriptBuf::default();
script.push_opcode(OP_TRUE);
script
}
pub fn oversized_script() -> ScriptBuf {
let mut script = ScriptBuf::default();
for _ in 0..10_000 {
script.push_opcode(OP_NOP);
}
script.push_opcode(OP_TRUE);
script
}
#[test]
fn test_spending_script_too_big() {
fn build_tx(input: TxIn, output: TxOut) -> Transaction {
Transaction {
version: Version(1),
lock_time: LockTime::from_height(0).unwrap(),
input: vec![input],
output: vec![output],
}
}
let dummy_outpoint = OutPoint {
txid: Txid::all_zeros(),
vout: 0,
};
let flags = 0;
let dummy_height = 0;
let mut utxos = HashMap::new();
utxos.insert(
dummy_outpoint,
UtxoData {
txout: txout!(0, true_script()),
is_coinbase: false,
creation_height: 0,
creation_time: 0,
},
);
let dummy_in = txin!(dummy_outpoint, ScriptBuf::new());
let oversized_out = txout!(0, oversized_script());
let tx_with_oversized = build_tx(dummy_in, oversized_out.clone());
Consensus::verify_transaction(&tx_with_oversized, &mut utxos, dummy_height, false, flags)
.unwrap();
let prevout = OutPoint::new(tx_with_oversized.compute_txid(), 0);
utxos.insert(
prevout,
UtxoData {
txout: oversized_out,
is_coinbase: false,
creation_height: 0,
creation_time: 0,
},
);
let spending_in = txin!(prevout, ScriptBuf::new());
let spending_tx = build_tx(spending_in, txout!(0, true_script()));
let err =
Consensus::verify_transaction(&spending_tx, &mut utxos, dummy_height, false, flags)
.unwrap_err();
match err {
BlockchainError::TransactionError(inner) => {
assert_eq!(inner, tx_err!(|| spending_tx.compute_txid(), ScriptError));
}
e => panic!("Expected a TransactionError, but got: {e:?}"),
}
}
}