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