1use 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#[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; }
41 if activation.is_fork_active(ForkId::Bip66, height) {
45 flags |= 0x04; }
47 if activation.is_fork_active(ForkId::Bip65, height) {
48 flags |= 0x200; }
50 if activation.is_fork_active(ForkId::Bip112, height) {
52 flags |= 0x400; }
54 if activation.is_fork_active(ForkId::Bip147, height) {
56 flags |= 0x10; }
58 #[cfg(feature = "ctv")]
59 if activation.is_fork_active(ForkId::Ctv, height) {
60 flags |= 0x80000000; }
62
63 flags
64}
65
66#[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#[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
87pub fn tx_requires_witness_script_flags(tx: &Transaction, has_witness: bool) -> bool {
89 has_witness || is_segwit_transaction(tx)
90}
91
92#[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#[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#[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#[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
161fn 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
173static MAINNET_SCRIPT_FLAG_EXCEPTION_BIP16: LazyLock<Hash> = LazyLock::new(|| {
175 block_hash_from_rpc_hex("00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22")
176});
177static MAINNET_SCRIPT_FLAG_EXCEPTION_TAPROOT: LazyLock<Hash> = LazyLock::new(|| {
179 block_hash_from_rpc_hex("0000000000000000000f14c35b2d841e986ab5441de8c585d5ffe55ea1e395ad")
180});
181static TESTNET_SCRIPT_FLAG_EXCEPTION_BIP16: LazyLock<Hash> = LazyLock::new(|| {
183 block_hash_from_rpc_hex("00000000dd30457c001f4095d208cc1296b0eed002427aa599874af7a432b105")
184});
185
186#[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) } 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
210pub fn get_block_script_verify_flags_core(
219 block_hash: &Hash,
220 height: u64,
221 activation: &impl IsForkActive,
222 network: Network,
223) -> u32 {
224 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; }
236 if activation.is_fork_active(ForkId::Bip65, height) {
237 flags |= 0x200; }
239 if activation.is_fork_active(ForkId::Bip112, height) {
240 flags |= 0x400; }
242 if activation.is_fork_active(ForkId::Bip147, height) {
243 flags |= 0x10; }
245 #[cfg(feature = "ctv")]
246 if activation.is_fork_active(ForkId::Ctv, height) {
247 flags |= 0x80000000;
248 }
249 flags
250}
251
252#[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#[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#[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 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 } 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#[cfg(all(feature = "production", feature = "rayon"))]
384#[allow(dead_code)] pub(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 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}