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