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 crate::witness::is_witness_empty;
13use blvm_spec_lock::spec_locked;
14#[cfg(feature = "production")]
15use rustc_hash::{FxHashMap, FxHashSet};
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")]
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")]
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")]
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")]
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")]
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// Script-exec cache and overlay merge
142// ---------------------------------------------------------------------------
143
144/// Insert script exec cache keys for all txs in block (call when block validation passes).
145#[cfg(all(feature = "production", feature = "rayon"))]
146pub(super) fn insert_script_exec_cache_for_block(
147    block: &Block,
148    witnesses: &[Vec<Witness>],
149    height: u64,
150    context: &BlockValidationContext,
151) {
152    let base_script_flags = calculate_base_script_flags_for_block(height, context);
153    for (i, tx) in block.transactions.iter().enumerate() {
154        if is_coinbase(tx) {
155            continue;
156        }
157        let wits = witnesses.get(i).map(|w| w.as_slice()).unwrap_or(&[]);
158        let has_witness = wits.iter().any(|wit| !is_witness_empty(wit));
159        let flags = calculate_script_flags_for_block_with_base(
160            tx,
161            has_witness,
162            base_script_flags,
163            height,
164            context,
165        );
166        let witnesses_vec: Vec<_> = if wits.len() == tx.inputs.len() {
167            wits.to_vec()
168        } else {
169            (0..tx.inputs.len()).map(|_| Vec::new()).collect()
170        };
171        let key = crate::script_exec_cache::compute_key(tx, &witnesses_vec, flags);
172        crate::script_exec_cache::insert(&key);
173    }
174}
175
176/// Merge overlay changes into cache. Updates bip30_index and optionally builds undo log.
177/// When `undo_log` is None (IBD mode), skips undo entry construction entirely.
178#[cfg(feature = "production")]
179pub(super) fn merge_overlay_changes_to_cache(
180    additions: &FxHashMap<OutPoint, std::sync::Arc<UTXO>>,
181    deletions: &FxHashSet<crate::utxo_overlay::UtxoDeletionKey>,
182    utxo_set: &mut UtxoSet,
183    mut bip30_index: Option<&mut crate::bip_validation::Bip30Index>,
184    mut undo_log: Option<&mut crate::reorganization::BlockUndoLog>,
185) {
186    use crate::reorganization::UndoEntry;
187
188    for del_key in deletions {
189        let outpoint = crate::utxo_overlay::utxo_deletion_key_to_outpoint(del_key);
190        if let Some(arc) = utxo_set.remove(&outpoint) {
191            if let Some(idx) = bip30_index.as_deref_mut() {
192                if arc.is_coinbase {
193                    if let std::collections::hash_map::Entry::Occupied(mut o) =
194                        idx.entry(outpoint.hash)
195                    {
196                        *o.get_mut() = o.get().saturating_sub(1);
197                        if *o.get() == 0 {
198                            o.remove();
199                        }
200                    }
201                }
202            }
203            if let Some(ref mut log) = undo_log {
204                log.entries.push(UndoEntry {
205                    outpoint,
206                    previous_utxo: Some(arc),
207                    new_utxo: None,
208                });
209            }
210        }
211    }
212    for (outpoint, arc) in additions {
213        if let Some(ref mut log) = undo_log {
214            log.entries.push(UndoEntry {
215                outpoint: *outpoint,
216                previous_utxo: None,
217                new_utxo: Some(std::sync::Arc::clone(arc)),
218            });
219        }
220        utxo_set.insert(*outpoint, std::sync::Arc::clone(arc));
221    }
222}
223
224/// Compute BIP143/precomputed sighash for CCheckQueue path. Uses local refs and specs Vecs
225/// (dropped before return) so buf borrow ends.
226#[cfg(all(feature = "production", feature = "rayon"))]
227pub(super) fn compute_bip143_and_precomp(
228    tx: &Transaction,
229    prevout_values: &[i64],
230    script_pubkey_indices: &[(usize, usize)],
231    script_pubkey_buffer: &[u8],
232    has_witness: bool,
233) -> (
234    Option<crate::transaction_hash::Bip143PrecomputedHashes>,
235    Vec<Option<[u8; 32]>>,
236) {
237    let buf = script_pubkey_buffer;
238    let refs: Vec<&[u8]> = script_pubkey_indices
239        .iter()
240        .map(|&(s, l)| buf[s..s + l].as_ref())
241        .collect();
242    let refs: &[&[u8]] = &refs;
243    if has_witness {
244        let bip =
245            crate::transaction_hash::Bip143PrecomputedHashes::compute(tx, prevout_values, refs);
246        let mut precomp = vec![None; script_pubkey_indices.len()];
247        let mut specs: Vec<(usize, u8, &[u8])> = Vec::new();
248        for (j, &(s, l)) in script_pubkey_indices.iter().enumerate() {
249            let spk = &buf[s..s + l];
250            if spk.len() == 22 && spk[0] == OP_0 && spk[1] == PUSH_20_BYTES {
251                let mut script_code = [0u8; 25];
252                script_code[0] = OP_DUP;
253                script_code[1] = OP_HASH160;
254                script_code[2] = PUSH_20_BYTES;
255                script_code[3..23].copy_from_slice(&spk[2..22]);
256                script_code[23] = OP_EQUALVERIFY;
257                script_code[24] = OP_CHECKSIG;
258                let amount = prevout_values.get(j).copied().unwrap_or(0);
259                if let Ok(h) = crate::transaction_hash::calculate_bip143_sighash(
260                    tx,
261                    j,
262                    &script_code,
263                    amount,
264                    0x01,
265                    Some(&bip),
266                ) {
267                    precomp[j] = Some(h);
268                }
269            } else if spk.len() == 23
270                && spk[0] == OP_HASH160
271                && spk[1] == PUSH_20_BYTES
272                && spk[22] == OP_EQUAL
273            {
274                if let Some((sighash_byte, redeem)) =
275                    crate::script::parse_p2sh_p2pkh_for_precompute(&tx.inputs[j].script_sig)
276                {
277                    specs.push((j, sighash_byte, redeem));
278                }
279            }
280        }
281        if !specs.is_empty() {
282            if let Ok(hashes) = crate::transaction_hash::batch_compute_legacy_sighashes(
283                tx,
284                prevout_values,
285                refs,
286                &specs,
287            ) {
288                for (k, &(j, _, _)) in specs.iter().enumerate() {
289                    precomp[j] = Some(hashes[k]);
290                }
291            }
292        }
293        (Some(bip), precomp)
294    } else {
295        let mut precomp = vec![None; script_pubkey_indices.len()];
296        let mut specs: Vec<(usize, u8, &[u8])> = Vec::new();
297        for (j, &(s, l)) in script_pubkey_indices.iter().enumerate() {
298            let spk = &buf[s..s + l];
299            if spk.len() == 25
300                && spk[0] == OP_DUP
301                && spk[1] == OP_HASH160
302                && spk[2] == PUSH_20_BYTES
303                && spk[23] == OP_EQUALVERIFY
304                && spk[24] == OP_CHECKSIG
305            {
306                let script_sig = &tx.inputs[j].script_sig;
307                if let Some((sig, _pubkey)) = crate::script::parse_p2pkh_script_sig(script_sig) {
308                    if !sig.is_empty() {
309                        specs.push((j, sig[sig.len() - 1], spk));
310                    }
311                }
312            } else if spk.len() == 23
313                && spk[0] == OP_HASH160
314                && spk[1] == PUSH_20_BYTES
315                && spk[22] == OP_EQUAL
316            {
317                if let Some((sighash_byte, redeem)) =
318                    crate::script::parse_p2sh_p2pkh_for_precompute(&tx.inputs[j].script_sig)
319                {
320                    specs.push((j, sighash_byte, redeem));
321                }
322            }
323        }
324        if !specs.is_empty() {
325            if let Ok(hashes) = crate::transaction_hash::batch_compute_legacy_sighashes(
326                tx,
327                prevout_values,
328                refs,
329                &specs,
330            ) {
331                for (k, &(j, _, _)) in specs.iter().enumerate() {
332                    precomp[j] = Some(hashes[k]);
333                }
334            }
335        }
336        (None, precomp)
337    }
338}