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