Skip to main content

blvm_consensus/block/
script_cache.rs

1//! Script verification flags and script-exec cache helpers for block validation.
2//!
3//! Groups base/per-tx script flags, cache insertion/merge, and BIP143 precompute
4//! so block connect logic stays in the parent module.
5
6use crate::activation::{ForkActivationTable, IsForkActive};
7use crate::opcodes::*;
8use crate::script::flags::{SCRIPT_VERIFY_P2SH, SCRIPT_VERIFY_TAPROOT, SCRIPT_VERIFY_WITNESS};
9use crate::segwit::{Witness, is_segwit_transaction};
10use crate::transaction::is_coinbase;
11use crate::types::*;
12use crate::witness::is_witness_empty;
13#[cfg(feature = "spec-lock")]
14use blvm_spec_lock::spec_locked;
15#[cfg(feature = "production")]
16use rustc_hash::{FxHashMap, FxHashSet};
17use std::sync::LazyLock;
18
19use super::BlockValidationContext;
20
21// ---------------------------------------------------------------------------
22// Script flags (base, per-tx, and combined)
23// ---------------------------------------------------------------------------
24
25/// Base script flags for a block from activation context.
26/// Call once per block, then use `calculate_script_flags_for_block` or `add_per_tx_script_flags`.
27#[cfg_attr(feature = "spec-lock", spec_locked("5.2.5", "CalculateScriptFlags"))]
28#[inline]
29pub(crate) fn calculate_base_script_flags_for_block(
30    height: u64,
31    activation: &impl IsForkActive,
32) -> u32 {
33    let mut flags: u32 = 0;
34
35    if activation.is_fork_active(ForkId::Bip16, height) {
36        flags |= 0x01; // SCRIPT_VERIFY_P2SH
37    }
38    // BIP66: strict DER for ECDSA signatures only. Adds SCRIPT_VERIFY_DERSIG here —
39    // not SCRIPT_VERIFY_STRICTENC or SCRIPT_VERIFY_LOW_S
40    // (those are standardness / mempool policy; legacy blocks may contain high-S sigs).
41    if activation.is_fork_active(ForkId::Bip66, height) {
42        flags |= 0x04; // SCRIPT_VERIFY_DERSIG
43    }
44    if activation.is_fork_active(ForkId::Bip65, height) {
45        flags |= 0x200; // CHECKLOCKTIMEVERIFY
46    }
47    // BIP112 (CSV): CHECKSEQUENCEVERIFY at CSV deployment height (mainnet 419328).
48    if activation.is_fork_active(ForkId::Bip112, height) {
49        flags |= 0x400; // SCRIPT_VERIFY_CHECKSEQUENCEVERIFY
50    }
51    // BIP147 NULLDUMMY: enabled at SegWit deployment height (same as BIP147 on mainnet).
52    if activation.is_fork_active(ForkId::Bip147, height) {
53        flags |= 0x10; // SCRIPT_VERIFY_NULLDUMMY
54    }
55    #[cfg(feature = "ctv")]
56    if activation.is_fork_active(ForkId::Ctv, height) {
57        flags |= 0x80000000; // CHECK_TEMPLATE_VERIFY_HASH
58    }
59
60    flags
61}
62
63/// Convenience: base script flags from (height, network) when no context is available (e.g. mempool).
64#[inline]
65pub fn calculate_base_script_flags_for_block_network(
66    height: u64,
67    network: crate::types::Network,
68) -> u32 {
69    let table = ForkActivationTable::from_network(network);
70    calculate_base_script_flags_for_block(height, &table)
71}
72
73/// True when any input witness stack in a block has a non-empty element.
74///
75/// Witness-serialized blocks attach a witness vector per input even for legacy spends;
76/// those empty stacks must not alone trigger witness script rules (sort-merge step6 regression).
77#[cfg_attr(feature = "spec-lock", spec_locked("5.2.5", "HasNonEmptyInputWitness"))]
78pub fn tx_has_nonempty_input_witness(per_input_witnesses: Option<&[Witness]>) -> bool {
79    per_input_witnesses
80        .map(|ws| ws.iter().any(|w| !is_witness_empty(w)))
81        .unwrap_or(false)
82}
83
84/// Input to per-tx `SCRIPT_VERIFY_WITNESS` gating (Orange Paper §5.2.5 FlagCondition).
85pub fn tx_requires_witness_script_flags(tx: &Transaction, has_witness: bool) -> bool {
86    has_witness || is_segwit_transaction(tx)
87}
88
89/// Per-tx script flags (SegWit + Taproot). Add to base flags from `calculate_base_script_flags_for_block`.
90#[cfg_attr(feature = "spec-lock", spec_locked("5.2.5", "CalculateScriptFlags"))]
91#[inline]
92fn add_per_tx_script_flags(
93    base_flags: u32,
94    tx: &Transaction,
95    has_witness: bool,
96    height: u64,
97    activation: &impl IsForkActive,
98) -> u32 {
99    let mut flags = base_flags;
100    if activation.is_fork_active(ForkId::SegWit, height)
101        && tx_requires_witness_script_flags(tx, has_witness)
102    {
103        flags |= 0x800;
104    }
105    flags
106}
107
108/// Calculate script verification flags for a transaction in a block (with activation context).
109#[cfg_attr(feature = "spec-lock", spec_locked("5.2.5", "CalculateScriptFlags"))]
110pub(crate) fn calculate_script_flags_for_block(
111    tx: &Transaction,
112    has_witness: bool,
113    height: u64,
114    activation: &impl IsForkActive,
115) -> u32 {
116    let base = calculate_base_script_flags_for_block(height, activation);
117    add_per_tx_script_flags(base, tx, has_witness, height, activation)
118}
119
120/// Convenience: script flags from (height, network) when no context is available (e.g. mempool, bench tools).
121#[cfg_attr(feature = "spec-lock", spec_locked("5.2.5", "CalculateScriptFlags"))]
122pub fn calculate_script_flags_for_block_network(
123    tx: &Transaction,
124    has_witness: bool,
125    height: u64,
126    network: crate::types::Network,
127) -> u32 {
128    let table = ForkActivationTable::from_network(network);
129    calculate_script_flags_for_block(tx, has_witness, height, &table)
130}
131
132/// Calculate script verification flags for a transaction in a block (with precomputed base flags).
133#[cfg_attr(feature = "spec-lock", spec_locked("5.2.5", "CalculateScriptFlags"))]
134#[inline]
135#[allow(dead_code)]
136pub(crate) fn calculate_script_flags_for_block_with_base(
137    tx: &Transaction,
138    has_witness: bool,
139    base_flags: u32,
140    height: u64,
141    activation: &impl IsForkActive,
142) -> u32 {
143    add_per_tx_script_flags(base_flags, tx, has_witness, height, activation)
144}
145
146// ---------------------------------------------------------------------------
147// §5.2.6 Script flag exceptions (Orange Paper §5.2.6)
148// ---------------------------------------------------------------------------
149
150/// RPC / explorer 64-hex block id → canonical digest order used by `hash256(serialize(header))` here.
151fn block_hash_from_rpc_hex(hex_str: &str) -> Hash {
152    let mut bytes = hex::decode(hex_str).expect("valid 64-char block hash hex");
153    assert_eq!(bytes.len(), 32);
154    bytes.reverse();
155    bytes.try_into().expect("length 32")
156}
157
158/// BIP16 exception block (mainnet). Uses `SCRIPT_VERIFY_NONE`.
159static MAINNET_SCRIPT_FLAG_EXCEPTION_BIP16: LazyLock<Hash> = LazyLock::new(|| {
160    block_hash_from_rpc_hex("00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22")
161});
162/// Taproot exception block (mainnet). Uses `SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS`.
163static MAINNET_SCRIPT_FLAG_EXCEPTION_TAPROOT: LazyLock<Hash> = LazyLock::new(|| {
164    block_hash_from_rpc_hex("0000000000000000000f14c35b2d841e986ab5441de8c585d5ffe55ea1e395ad")
165});
166/// BIP16 exception block (testnet3). Uses `SCRIPT_VERIFY_NONE`.
167static TESTNET_SCRIPT_FLAG_EXCEPTION_BIP16: LazyLock<Hash> = LazyLock::new(|| {
168    block_hash_from_rpc_hex("00000000dd30457c001f4095d208cc1296b0eed002427aa599874af7a432b105")
169});
170
171/// Consensus script-flag override for `block_hash` on `network`, if any (Orange Paper §5.2.6 table).
172#[cfg_attr(feature = "spec-lock", spec_locked("5.2.6", "ScriptFlagExceptions"))]
173pub fn script_flag_exceptions_lookup(block_hash: &Hash, network: Network) -> Option<u32> {
174    match network {
175        Network::Mainnet => {
176            if block_hash == &*MAINNET_SCRIPT_FLAG_EXCEPTION_BIP16 {
177                Some(0)
178            } else if block_hash == &*MAINNET_SCRIPT_FLAG_EXCEPTION_TAPROOT {
179                Some(0x01 | 0x800) // SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS
180            } else {
181                None
182            }
183        }
184        Network::Testnet => {
185            if block_hash == &*TESTNET_SCRIPT_FLAG_EXCEPTION_BIP16 {
186                Some(0)
187            } else {
188                None
189            }
190        }
191        Network::Regtest | Network::Signet | Network::Testnet4 => None,
192    }
193}
194
195/// Block-level script verify flags: start from `SCRIPT_VERIFY_P2SH |
196/// SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_TAPROOT`, replace with the script-flag exception entry
197/// when present, then OR buried deployments (BIP66, BIP65, CSV, BIP147-at-SegWit). Same **block-level**
198/// bitmask is passed to script checks for every non-coinbase transaction.
199///
200/// This is **not** the Orange Paper §5.2.6 piecewise `GetBlockScriptFlags` (exception vs per-tx
201/// `CalculateScriptFlags`); use [`get_block_script_flags`] for that formulation. `connect_block`
202/// uses this function for production mainnet block validation.
203pub fn get_block_script_verify_flags_core(
204    block_hash: &Hash,
205    height: u64,
206    activation: &impl IsForkActive,
207    network: Network,
208) -> u32 {
209    // Baseline matches Bitcoin Core GetBlockScriptFlags: P2SH | WITNESS | TAPROOT.
210    // WITNESS_PUBKEYTYPE (0x8000) is standardness and is not set here.
211    let mut flags = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_TAPROOT;
212    if let Some(v) = script_flag_exceptions_lookup(block_hash, network) {
213        flags = v;
214    }
215    if activation.is_fork_active(ForkId::Bip66, height) {
216        flags |= 0x04; // SCRIPT_VERIFY_DERSIG
217    }
218    if activation.is_fork_active(ForkId::Bip65, height) {
219        flags |= 0x200; // CHECKLOCKTIMEVERIFY
220    }
221    if activation.is_fork_active(ForkId::Bip112, height) {
222        flags |= 0x400; // CHECKSEQUENCEVERIFY
223    }
224    if activation.is_fork_active(ForkId::Bip147, height) {
225        flags |= 0x10; // NULLDUMMY (SegWit deployment)
226    }
227    #[cfg(feature = "ctv")]
228    if activation.is_fork_active(ForkId::Ctv, height) {
229        flags |= 0x80000000;
230    }
231    flags
232}
233
234/// Orange Paper §5.2.6: exception table wins when defined; otherwise per-tx `CalculateScriptFlags`.
235///
236/// **`connect_block`** uses [`get_block_script_verify_flags_core`] for the block-level mask;
237/// this function is the Orange Paper §5.2.6 piecewise spec.
238#[cfg_attr(feature = "spec-lock", spec_locked("5.2.6", "GetBlockScriptFlags"))]
239pub fn get_block_script_flags(
240    block_hash: &Hash,
241    tx: &Transaction,
242    has_witness: bool,
243    height: u64,
244    network: Network,
245) -> u32 {
246    if let Some(flags) = script_flag_exceptions_lookup(block_hash, network) {
247        flags
248    } else {
249        calculate_script_flags_for_block_network(tx, has_witness, height, network)
250    }
251}
252
253// ---------------------------------------------------------------------------
254// Script-exec cache and overlay merge
255// ---------------------------------------------------------------------------
256
257/// Insert script exec cache keys for all txs in block (call when block validation passes).
258#[cfg(all(feature = "production", feature = "rayon"))]
259pub(super) fn insert_script_exec_cache_for_block(
260    block: &Block,
261    witnesses: &[Vec<Witness>],
262    height: u64,
263    context: &BlockValidationContext,
264) {
265    if !crate::script_exec_cache::enabled() {
266        return;
267    }
268    let block_hash = crate::crypto::OptimizedSha256::new().hash256(
269        &crate::serialization::block::serialize_block_header(&block.header),
270    );
271    let block_script_verify_flags =
272        get_block_script_verify_flags_core(&block_hash, height, context, context.network);
273    for (i, tx) in block.transactions.iter().enumerate() {
274        if is_coinbase(tx) {
275            continue;
276        }
277        let wits = witnesses.get(i).map(|w| w.as_slice()).unwrap_or(&[]);
278        let witnesses_vec: Vec<_> = if wits.len() == tx.inputs.len() {
279            wits.to_vec()
280        } else {
281            (0..tx.inputs.len()).map(|_| Vec::new()).collect()
282        };
283        let key =
284            crate::script_exec_cache::compute_key(tx, &witnesses_vec, block_script_verify_flags);
285        crate::script_exec_cache::insert(&key);
286    }
287}
288
289/// Merge overlay changes into the UTXO set and update BIP30 index / undo log.
290///
291/// `skip_utxo_set_mutation`: when `true` (IBD path), the `utxo_set` is a per-block
292/// scratch view that is discarded after this call. Skip all HashMap inserts/removes.
293/// BIP30 still counts new coinbase outputs and decrements spent ones.
294/// The non-IBD path (`false`) performs the full remove/insert cycle and builds the
295/// undo log entry. `undo_log` must be `None` when `skip_utxo_set_mutation` is `true`.
296#[cfg(feature = "production")]
297pub(super) fn merge_overlay_changes_to_cache(
298    additions: &FxHashMap<OutPoint, std::sync::Arc<UTXO>>,
299    deletions: &FxHashSet<crate::utxo_overlay::UtxoDeletionKey>,
300    utxo_set: &mut UtxoSet,
301    mut bip30_index: Option<&mut crate::bip_validation::Bip30Index>,
302    mut undo_log: Option<&mut crate::reorganization::BlockUndoLog>,
303    skip_utxo_set_mutation: bool,
304) {
305    use crate::reorganization::UndoEntry;
306
307    for del_key in deletions {
308        let outpoint = crate::utxo_overlay::utxo_deletion_key_to_outpoint(del_key);
309        if skip_utxo_set_mutation {
310            // IBD fast path: utxo_set is discarded after this call.
311            // Use get (no remove) solely for the BIP30 coinbase check.
312            if let Some(idx) = bip30_index.as_deref_mut() {
313                if utxo_set
314                    .get(&outpoint)
315                    .map(|arc| arc.is_coinbase)
316                    .unwrap_or(false)
317                {
318                    if let std::collections::hash_map::Entry::Occupied(mut o) =
319                        idx.entry(outpoint.hash)
320                    {
321                        *o.get_mut() = o.get().saturating_sub(1);
322                        if *o.get() == 0 {
323                            o.remove();
324                        }
325                    }
326                }
327            }
328            // The IBD undo log is built by `undo_log_from_overlay`, not here.
329        } else if let Some(arc) = utxo_set.remove(&outpoint) {
330            if let Some(idx) = bip30_index.as_deref_mut() {
331                if arc.is_coinbase {
332                    if let std::collections::hash_map::Entry::Occupied(mut o) =
333                        idx.entry(outpoint.hash)
334                    {
335                        *o.get_mut() = o.get().saturating_sub(1);
336                        if *o.get() == 0 {
337                            o.remove();
338                        }
339                    }
340                }
341            }
342            if let Some(ref mut log) = undo_log {
343                log.entries.push(UndoEntry {
344                    outpoint,
345                    previous_utxo: Some(arc),
346                    new_utxo: None,
347                });
348            }
349        }
350    }
351
352    for (outpoint, arc) in additions {
353        if let Some(idx) = bip30_index.as_deref_mut() {
354            if arc.is_coinbase {
355                *idx.entry(outpoint.hash).or_insert(0) += 1;
356            }
357        }
358        if !skip_utxo_set_mutation {
359            if let Some(ref mut log) = undo_log {
360                log.entries.push(UndoEntry {
361                    outpoint: *outpoint,
362                    previous_utxo: None,
363                    new_utxo: Some(std::sync::Arc::clone(arc)),
364                });
365            }
366            utxo_set.insert(*outpoint, std::sync::Arc::clone(arc));
367        }
368    }
369}
370
371/// Undo log for an overlay that was not merged into the scratch UTXO set.
372///
373/// Spent coins are read from `base`, the view the block was validated against.
374/// A spent coin that `base` cannot return is an error. An empty log would let a
375/// later disconnect succeed and restore nothing.
376#[cfg(feature = "production")]
377pub(super) fn undo_log_from_overlay(
378    additions: &FxHashMap<OutPoint, std::sync::Arc<UTXO>>,
379    deletions: &FxHashSet<crate::utxo_overlay::UtxoDeletionKey>,
380    base: &dyn crate::utxo_overlay::UtxoLookup,
381) -> crate::error::Result<crate::reorganization::BlockUndoLog> {
382    use crate::reorganization::{BlockUndoLog, UndoEntry};
383
384    let mut log = BlockUndoLog::new();
385    log.entries.reserve(deletions.len() + additions.len());
386    for del_key in deletions {
387        let outpoint = crate::utxo_overlay::utxo_deletion_key_to_outpoint(del_key);
388        let Some(prev) = base.get(&outpoint) else {
389            return Err(crate::error::ConsensusError::BlockValidation(
390                format!("undo log: spent coin {outpoint:?} is not in the base view").into(),
391            ));
392        };
393        log.entries.push(UndoEntry {
394            outpoint,
395            previous_utxo: Some(std::sync::Arc::new(prev.clone())),
396            new_utxo: None,
397        });
398    }
399    for (outpoint, arc) in additions {
400        let previous_utxo = base
401            .get(outpoint)
402            .map(|prev| std::sync::Arc::new(prev.clone()));
403        log.entries.push(UndoEntry {
404            outpoint: *outpoint,
405            previous_utxo,
406            new_utxo: Some(std::sync::Arc::clone(arc)),
407        });
408    }
409    Ok(log)
410}
411
412/// Compute BIP143/precomputed sighash for the parallel script-check path. Uses local refs and specs Vecs
413/// (dropped before return) so buf borrow ends.
414#[cfg(all(feature = "production", feature = "rayon"))]
415#[allow(dead_code)] // parallel script-check precompute hook (not yet wired in hot path)
416pub(super) fn compute_bip143_and_precomp(
417    tx: &Transaction,
418    prevout_values: &[i64],
419    script_pubkey_indices: &[(usize, usize)],
420    script_pubkey_buffer: &[u8],
421    has_witness: bool,
422) -> (
423    Option<crate::transaction_hash::Bip143PrecomputedHashes>,
424    Vec<Option<[u8; 32]>>,
425) {
426    let buf = script_pubkey_buffer;
427    let refs: Vec<&[u8]> = script_pubkey_indices
428        .iter()
429        .map(|&(s, l)| buf[s..s + l].as_ref())
430        .collect();
431    let refs: &[&[u8]] = &refs;
432    if has_witness {
433        let bip =
434            crate::transaction_hash::Bip143PrecomputedHashes::compute(tx, prevout_values, refs);
435        let mut precomp = vec![None; script_pubkey_indices.len()];
436        let mut specs: Vec<(usize, u8, &[u8])> = Vec::new();
437        for (j, &(s, l)) in script_pubkey_indices.iter().enumerate() {
438            let spk = &buf[s..s + l];
439            if spk.len() == 22 && spk[0] == OP_0 && spk[1] == PUSH_20_BYTES {
440                let mut script_code = [0u8; 25];
441                script_code[0] = OP_DUP;
442                script_code[1] = OP_HASH160;
443                script_code[2] = PUSH_20_BYTES;
444                script_code[3..23].copy_from_slice(&spk[2..22]);
445                script_code[23] = OP_EQUALVERIFY;
446                script_code[24] = OP_CHECKSIG;
447                let amount = prevout_values.get(j).copied().unwrap_or(0);
448                if let Ok(h) = crate::transaction_hash::calculate_bip143_sighash(
449                    tx,
450                    j,
451                    &script_code,
452                    amount,
453                    0x01,
454                    Some(&bip),
455                ) {
456                    precomp[j] = Some(h);
457                }
458            } else if spk.len() == 23
459                && spk[0] == OP_HASH160
460                && spk[1] == PUSH_20_BYTES
461                && spk[22] == OP_EQUAL
462            {
463                if let Some((sighash_byte, redeem)) =
464                    crate::script::parse_p2sh_p2pkh_for_precompute(&tx.inputs[j].script_sig)
465                {
466                    specs.push((j, sighash_byte, redeem));
467                }
468            }
469        }
470        if !specs.is_empty() {
471            if let Ok(hashes) = crate::transaction_hash::batch_compute_legacy_sighashes(
472                tx,
473                prevout_values,
474                refs,
475                &specs,
476            ) {
477                for (k, &(j, _, _)) in specs.iter().enumerate() {
478                    precomp[j] = Some(hashes[k]);
479                }
480            }
481        }
482        (Some(bip), precomp)
483    } else {
484        let mut precomp = vec![None; script_pubkey_indices.len()];
485        let mut specs: Vec<(usize, u8, &[u8])> = Vec::new();
486        for (j, &(s, l)) in script_pubkey_indices.iter().enumerate() {
487            let spk = &buf[s..s + l];
488            if spk.len() == 25
489                && spk[0] == OP_DUP
490                && spk[1] == OP_HASH160
491                && spk[2] == PUSH_20_BYTES
492                && spk[23] == OP_EQUALVERIFY
493                && spk[24] == OP_CHECKSIG
494            {
495                let script_sig = &tx.inputs[j].script_sig;
496                if let Some((sig, _pubkey)) = crate::script::parse_p2pkh_script_sig(script_sig) {
497                    if !sig.is_empty() {
498                        specs.push((j, sig[sig.len() - 1], spk));
499                    }
500                }
501            } else if spk.len() == 23
502                && spk[0] == OP_HASH160
503                && spk[1] == PUSH_20_BYTES
504                && spk[22] == OP_EQUAL
505            {
506                if let Some((sighash_byte, redeem)) =
507                    crate::script::parse_p2sh_p2pkh_for_precompute(&tx.inputs[j].script_sig)
508                {
509                    specs.push((j, sighash_byte, redeem));
510                }
511            }
512        }
513        if !specs.is_empty() {
514            if let Ok(hashes) = crate::transaction_hash::batch_compute_legacy_sighashes(
515                tx,
516                prevout_values,
517                refs,
518                &specs,
519            ) {
520                for (k, &(j, _, _)) in specs.iter().enumerate() {
521                    precomp[j] = Some(hashes[k]);
522                }
523            }
524        }
525        (None, precomp)
526    }
527}
528
529#[cfg(test)]
530mod script_flag_exceptions_tests {
531    use super::{
532        calculate_script_flags_for_block_network, get_block_script_flags,
533        get_block_script_verify_flags_core, script_flag_exceptions_lookup,
534    };
535    use crate::activation::ForkActivationTable;
536    use crate::crypto::OptimizedSha256;
537    use crate::script::flags::{
538        SCRIPT_VERIFY_P2SH, SCRIPT_VERIFY_TAPROOT, SCRIPT_VERIFY_WITNESS,
539        SCRIPT_VERIFY_WITNESS_PUBKEYTYPE,
540    };
541    use crate::serialization::block::{deserialize_block_header, serialize_block_header};
542    use crate::types::{Network, Transaction};
543
544    fn hash_from_rpc_hex(hex_str: &str) -> [u8; 32] {
545        let mut bytes = hex::decode(hex_str).unwrap();
546        assert_eq!(bytes.len(), 32);
547        bytes.reverse();
548        bytes.try_into().unwrap()
549    }
550
551    #[test]
552    fn mainnet_bip16_exception_zero() {
553        let h =
554            hash_from_rpc_hex("00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22");
555        assert_eq!(script_flag_exceptions_lookup(&h, Network::Mainnet), Some(0));
556    }
557
558    #[test]
559    fn mainnet_taproot_exception_p2sh_witness() {
560        let h =
561            hash_from_rpc_hex("0000000000000000000f14c35b2d841e986ab5441de8c585d5ffe55ea1e395ad");
562        assert_eq!(
563            script_flag_exceptions_lookup(&h, Network::Mainnet),
564            Some(0x01 | 0x800)
565        );
566    }
567
568    #[test]
569    fn testnet_bip16_exception_zero() {
570        let h =
571            hash_from_rpc_hex("00000000dd30457c001f4095d208cc1296b0eed002427aa599874af7a432b105");
572        assert_eq!(script_flag_exceptions_lookup(&h, Network::Testnet), Some(0));
573        assert_eq!(script_flag_exceptions_lookup(&h, Network::Mainnet), None);
574    }
575
576    #[test]
577    fn regtest_has_no_exceptions() {
578        let h =
579            hash_from_rpc_hex("00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22");
580        assert_eq!(script_flag_exceptions_lookup(&h, Network::Regtest), None);
581    }
582
583    #[test]
584    fn get_block_script_flags_uses_exception() {
585        let h =
586            hash_from_rpc_hex("00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22");
587        let tx = Transaction {
588            version: 1,
589            inputs: Default::default(),
590            outputs: Default::default(),
591            lock_time: 0,
592        };
593        let flags = get_block_script_flags(&h, &tx, false, 1_000_000, Network::Mainnet);
594        assert_eq!(flags, 0);
595    }
596
597    #[test]
598    fn get_block_script_flags_falls_back_to_calculate() {
599        let h = [7u8; 32];
600        let tx = Transaction {
601            version: 1,
602            inputs: Default::default(),
603            outputs: Default::default(),
604            lock_time: 0,
605        };
606        assert_eq!(
607            get_block_script_flags(&h, &tx, false, 100, Network::Mainnet),
608            calculate_script_flags_for_block_network(&tx, false, 100, Network::Mainnet),
609        );
610    }
611
612    #[test]
613    fn genesis_rpc_hex_matches_hash256_of_header() {
614        let genesis_block_hex = "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c010100000001000000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000";
615        let bytes = hex::decode(genesis_block_hex).unwrap();
616        let header = deserialize_block_header(&bytes[..80]).unwrap();
617        let digest = OptimizedSha256::new().hash256(&serialize_block_header(&header));
618        let expected =
619            hash_from_rpc_hex("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f");
620        assert_eq!(digest, expected);
621    }
622
623    #[test]
624    fn core_block_flags_non_exception_includes_p2sh_witness_taproot_and_deployments() {
625        let table = ForkActivationTable::from_network(Network::Mainnet);
626        let h = [0xabu8; 32];
627        let flags = get_block_script_verify_flags_core(&h, 800_000, &table, Network::Mainnet);
628        // P2SH (0x1) | WITNESS (0x800) | TAPROOT (0x20000). WITNESS_PUBKEYTYPE is not a block flag.
629        assert_eq!(
630            flags
631                & (SCRIPT_VERIFY_P2SH
632                    | SCRIPT_VERIFY_WITNESS
633                    | SCRIPT_VERIFY_WITNESS_PUBKEYTYPE
634                    | SCRIPT_VERIFY_TAPROOT),
635            SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_TAPROOT,
636            "P2SH | WITNESS | TAPROOT baseline"
637        );
638        assert_eq!(flags & SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, 0);
639        let pre_segwit = get_block_script_verify_flags_core(&h, 100, &table, Network::Mainnet);
640        assert_ne!(
641            pre_segwit & SCRIPT_VERIFY_WITNESS,
642            0,
643            "pre-segwit height still has WITNESS"
644        );
645        assert_ne!(flags & 0x04, 0, "DERSIG");
646        assert_ne!(flags & 0x200, 0, "CLTV");
647        assert_ne!(flags & 0x400, 0, "CSV");
648        assert_ne!(flags & 0x10, 0, "NULLDUMMY");
649    }
650
651    #[test]
652    fn core_block_flags_bip16_exception_orrs_buried_deployments() {
653        let table = ForkActivationTable::from_network(Network::Mainnet);
654        let h =
655            hash_from_rpc_hex("00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22");
656        assert_eq!(script_flag_exceptions_lookup(&h, Network::Mainnet), Some(0));
657        let flags = get_block_script_verify_flags_core(&h, 800_000, &table, Network::Mainnet);
658        let deployment_mask = 0x04 | 0x200 | 0x400 | 0x10;
659        assert_eq!(flags & 0x8801, 0);
660        assert_eq!(flags & deployment_mask, deployment_mask);
661        assert_eq!(flags & 0x800, 0);
662        assert_eq!(flags & 0x8000, 0);
663        assert_eq!(flags & 0x01, 0);
664    }
665
666    #[test]
667    fn core_block_flags_taproot_exception_orrs_deployments() {
668        let table = ForkActivationTable::from_network(Network::Mainnet);
669        let h =
670            hash_from_rpc_hex("0000000000000000000f14c35b2d841e986ab5441de8c585d5ffe55ea1e395ad");
671        let flags = get_block_script_verify_flags_core(&h, 800_000, &table, Network::Mainnet);
672        assert_eq!(flags & 0x8801, 0x801);
673        assert_eq!(flags & 0x8000, 0);
674        assert_ne!(flags & 0x800, 0);
675        assert_ne!(flags & 0x04, 0);
676    }
677}
678
679#[cfg(all(test, feature = "production"))]
680mod replaced_output_undo_tests {
681    use super::{merge_overlay_changes_to_cache, undo_log_from_overlay};
682    use crate::types::{OutPoint, UTXO, UtxoSet};
683    use rustc_hash::{FxHashMap, FxHashSet};
684    use std::sync::Arc;
685
686    #[test]
687    fn addition_records_the_coin_it_replaces() {
688        let outpoint = OutPoint {
689            hash: [9u8; 32],
690            index: 0,
691        };
692        let mut base = UtxoSet::default();
693        base.insert(
694            outpoint,
695            Arc::new(UTXO {
696                value: 50,
697                script_pubkey: vec![0x51].into(),
698                height: 1,
699                is_coinbase: true,
700            }),
701        );
702        let mut additions = FxHashMap::default();
703        additions.insert(
704            outpoint,
705            Arc::new(UTXO {
706                value: 50,
707                script_pubkey: vec![0x52].into(),
708                height: 91_842,
709                is_coinbase: true,
710            }),
711        );
712        let deletions = FxHashSet::default();
713        let log = undo_log_from_overlay(&additions, &deletions, &base).unwrap();
714        let previous = log.entries[0]
715            .previous_utxo
716            .as_ref()
717            .expect("replaced coin is kept");
718        assert_eq!(previous.height, 1);
719        assert!(previous.is_coinbase);
720        assert_eq!(log.entries[0].new_utxo.as_ref().unwrap().height, 91_842);
721
722        let mut after = UtxoSet::default();
723        after.insert(
724            outpoint,
725            Arc::clone(log.entries[0].new_utxo.as_ref().unwrap()),
726        );
727        after.remove(&outpoint);
728        after.insert(outpoint, Arc::clone(previous));
729        assert_eq!(after.get(&outpoint).unwrap().height, 1);
730    }
731
732    #[test]
733    fn ibd_merge_counts_a_new_coinbase_output() {
734        let txid = [0x11u8; 32];
735        let outpoint = OutPoint {
736            hash: txid,
737            index: 0,
738        };
739        let mut additions = FxHashMap::default();
740        additions.insert(
741            outpoint,
742            Arc::new(UTXO {
743                value: 50,
744                script_pubkey: vec![0x51].into(),
745                height: 1,
746                is_coinbase: true,
747            }),
748        );
749        let deletions = FxHashSet::default();
750        let mut index = crate::bip_validation::Bip30Index::default();
751        let mut utxo_set = UtxoSet::default();
752        merge_overlay_changes_to_cache(
753            &additions,
754            &deletions,
755            &mut utxo_set,
756            Some(&mut index),
757            None,
758            true,
759        );
760        assert!(utxo_set.is_empty(), "the IBD scratch set stays unchanged");
761        assert_eq!(index.get(&txid).copied(), Some(1));
762    }
763}