1use crate::activation::{ForkActivationTable, IsForkActive};
7use crate::opcodes::*;
8use crate::script::flags::{SCRIPT_VERIFY_P2SH, SCRIPT_VERIFY_TAPROOT, SCRIPT_VERIFY_WITNESS};
9use crate::segwit::{Witness, is_segwit_transaction};
10use crate::transaction::is_coinbase;
11use crate::types::*;
12use crate::witness::is_witness_empty;
13#[cfg(feature = "spec-lock")]
14use blvm_spec_lock::spec_locked;
15#[cfg(feature = "production")]
16use rustc_hash::{FxHashMap, FxHashSet};
17use std::sync::LazyLock;
18
19use super::BlockValidationContext;
20
21#[cfg_attr(feature = "spec-lock", spec_locked("5.2.5", "CalculateScriptFlags"))]
28#[inline]
29pub(crate) fn calculate_base_script_flags_for_block(
30 height: u64,
31 activation: &impl IsForkActive,
32) -> u32 {
33 let mut flags: u32 = 0;
34
35 if activation.is_fork_active(ForkId::Bip16, height) {
36 flags |= 0x01; }
38 if activation.is_fork_active(ForkId::Bip66, height) {
42 flags |= 0x04; }
44 if activation.is_fork_active(ForkId::Bip65, height) {
45 flags |= 0x200; }
47 if activation.is_fork_active(ForkId::Bip112, height) {
49 flags |= 0x400; }
51 if activation.is_fork_active(ForkId::Bip147, height) {
53 flags |= 0x10; }
55 #[cfg(feature = "ctv")]
56 if activation.is_fork_active(ForkId::Ctv, height) {
57 flags |= 0x80000000; }
59
60 flags
61}
62
63#[inline]
65pub fn calculate_base_script_flags_for_block_network(
66 height: u64,
67 network: crate::types::Network,
68) -> u32 {
69 let table = ForkActivationTable::from_network(network);
70 calculate_base_script_flags_for_block(height, &table)
71}
72
73#[cfg_attr(feature = "spec-lock", spec_locked("5.2.5", "HasNonEmptyInputWitness"))]
78pub fn tx_has_nonempty_input_witness(per_input_witnesses: Option<&[Witness]>) -> bool {
79 per_input_witnesses
80 .map(|ws| ws.iter().any(|w| !is_witness_empty(w)))
81 .unwrap_or(false)
82}
83
84pub fn tx_requires_witness_script_flags(tx: &Transaction, has_witness: bool) -> bool {
86 has_witness || is_segwit_transaction(tx)
87}
88
89#[cfg_attr(feature = "spec-lock", spec_locked("5.2.5", "CalculateScriptFlags"))]
91#[inline]
92fn add_per_tx_script_flags(
93 base_flags: u32,
94 tx: &Transaction,
95 has_witness: bool,
96 height: u64,
97 activation: &impl IsForkActive,
98) -> u32 {
99 let mut flags = base_flags;
100 if activation.is_fork_active(ForkId::SegWit, height)
101 && tx_requires_witness_script_flags(tx, has_witness)
102 {
103 flags |= 0x800;
104 }
105 flags
106}
107
108#[cfg_attr(feature = "spec-lock", spec_locked("5.2.5", "CalculateScriptFlags"))]
110pub(crate) fn calculate_script_flags_for_block(
111 tx: &Transaction,
112 has_witness: bool,
113 height: u64,
114 activation: &impl IsForkActive,
115) -> u32 {
116 let base = calculate_base_script_flags_for_block(height, activation);
117 add_per_tx_script_flags(base, tx, has_witness, height, activation)
118}
119
120#[cfg_attr(feature = "spec-lock", spec_locked("5.2.5", "CalculateScriptFlags"))]
122pub fn calculate_script_flags_for_block_network(
123 tx: &Transaction,
124 has_witness: bool,
125 height: u64,
126 network: crate::types::Network,
127) -> u32 {
128 let table = ForkActivationTable::from_network(network);
129 calculate_script_flags_for_block(tx, has_witness, height, &table)
130}
131
132#[cfg_attr(feature = "spec-lock", spec_locked("5.2.5", "CalculateScriptFlags"))]
134#[inline]
135#[allow(dead_code)]
136pub(crate) fn calculate_script_flags_for_block_with_base(
137 tx: &Transaction,
138 has_witness: bool,
139 base_flags: u32,
140 height: u64,
141 activation: &impl IsForkActive,
142) -> u32 {
143 add_per_tx_script_flags(base_flags, tx, has_witness, height, activation)
144}
145
146fn block_hash_from_rpc_hex(hex_str: &str) -> Hash {
152 let mut bytes = hex::decode(hex_str).expect("valid 64-char block hash hex");
153 assert_eq!(bytes.len(), 32);
154 bytes.reverse();
155 bytes.try_into().expect("length 32")
156}
157
158static MAINNET_SCRIPT_FLAG_EXCEPTION_BIP16: LazyLock<Hash> = LazyLock::new(|| {
160 block_hash_from_rpc_hex("00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22")
161});
162static MAINNET_SCRIPT_FLAG_EXCEPTION_TAPROOT: LazyLock<Hash> = LazyLock::new(|| {
164 block_hash_from_rpc_hex("0000000000000000000f14c35b2d841e986ab5441de8c585d5ffe55ea1e395ad")
165});
166static TESTNET_SCRIPT_FLAG_EXCEPTION_BIP16: LazyLock<Hash> = LazyLock::new(|| {
168 block_hash_from_rpc_hex("00000000dd30457c001f4095d208cc1296b0eed002427aa599874af7a432b105")
169});
170
171#[cfg_attr(feature = "spec-lock", spec_locked("5.2.6", "ScriptFlagExceptions"))]
173pub fn script_flag_exceptions_lookup(block_hash: &Hash, network: Network) -> Option<u32> {
174 match network {
175 Network::Mainnet => {
176 if block_hash == &*MAINNET_SCRIPT_FLAG_EXCEPTION_BIP16 {
177 Some(0)
178 } else if block_hash == &*MAINNET_SCRIPT_FLAG_EXCEPTION_TAPROOT {
179 Some(0x01 | 0x800) } else {
181 None
182 }
183 }
184 Network::Testnet => {
185 if block_hash == &*TESTNET_SCRIPT_FLAG_EXCEPTION_BIP16 {
186 Some(0)
187 } else {
188 None
189 }
190 }
191 Network::Regtest | Network::Signet | Network::Testnet4 => None,
192 }
193}
194
195pub fn get_block_script_verify_flags_core(
204 block_hash: &Hash,
205 height: u64,
206 activation: &impl IsForkActive,
207 network: Network,
208) -> u32 {
209 let mut flags = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_TAPROOT;
212 if let Some(v) = script_flag_exceptions_lookup(block_hash, network) {
213 flags = v;
214 }
215 if activation.is_fork_active(ForkId::Bip66, height) {
216 flags |= 0x04; }
218 if activation.is_fork_active(ForkId::Bip65, height) {
219 flags |= 0x200; }
221 if activation.is_fork_active(ForkId::Bip112, height) {
222 flags |= 0x400; }
224 if activation.is_fork_active(ForkId::Bip147, height) {
225 flags |= 0x10; }
227 #[cfg(feature = "ctv")]
228 if activation.is_fork_active(ForkId::Ctv, height) {
229 flags |= 0x80000000;
230 }
231 flags
232}
233
234#[cfg_attr(feature = "spec-lock", spec_locked("5.2.6", "GetBlockScriptFlags"))]
239pub fn get_block_script_flags(
240 block_hash: &Hash,
241 tx: &Transaction,
242 has_witness: bool,
243 height: u64,
244 network: Network,
245) -> u32 {
246 if let Some(flags) = script_flag_exceptions_lookup(block_hash, network) {
247 flags
248 } else {
249 calculate_script_flags_for_block_network(tx, has_witness, height, network)
250 }
251}
252
253#[cfg(all(feature = "production", feature = "rayon"))]
259pub(super) fn insert_script_exec_cache_for_block(
260 block: &Block,
261 witnesses: &[Vec<Witness>],
262 height: u64,
263 context: &BlockValidationContext,
264) {
265 if !crate::script_exec_cache::enabled() {
266 return;
267 }
268 let block_hash = crate::crypto::OptimizedSha256::new().hash256(
269 &crate::serialization::block::serialize_block_header(&block.header),
270 );
271 let block_script_verify_flags =
272 get_block_script_verify_flags_core(&block_hash, height, context, context.network);
273 for (i, tx) in block.transactions.iter().enumerate() {
274 if is_coinbase(tx) {
275 continue;
276 }
277 let wits = witnesses.get(i).map(|w| w.as_slice()).unwrap_or(&[]);
278 let witnesses_vec: Vec<_> = if wits.len() == tx.inputs.len() {
279 wits.to_vec()
280 } else {
281 (0..tx.inputs.len()).map(|_| Vec::new()).collect()
282 };
283 let key =
284 crate::script_exec_cache::compute_key(tx, &witnesses_vec, block_script_verify_flags);
285 crate::script_exec_cache::insert(&key);
286 }
287}
288
289#[cfg(feature = "production")]
297pub(super) fn merge_overlay_changes_to_cache(
298 additions: &FxHashMap<OutPoint, std::sync::Arc<UTXO>>,
299 deletions: &FxHashSet<crate::utxo_overlay::UtxoDeletionKey>,
300 utxo_set: &mut UtxoSet,
301 mut bip30_index: Option<&mut crate::bip_validation::Bip30Index>,
302 mut undo_log: Option<&mut crate::reorganization::BlockUndoLog>,
303 skip_utxo_set_mutation: bool,
304) {
305 use crate::reorganization::UndoEntry;
306
307 for del_key in deletions {
308 let outpoint = crate::utxo_overlay::utxo_deletion_key_to_outpoint(del_key);
309 if skip_utxo_set_mutation {
310 if let Some(idx) = bip30_index.as_deref_mut() {
313 if utxo_set
314 .get(&outpoint)
315 .map(|arc| arc.is_coinbase)
316 .unwrap_or(false)
317 {
318 if let std::collections::hash_map::Entry::Occupied(mut o) =
319 idx.entry(outpoint.hash)
320 {
321 *o.get_mut() = o.get().saturating_sub(1);
322 if *o.get() == 0 {
323 o.remove();
324 }
325 }
326 }
327 }
328 } else if let Some(arc) = utxo_set.remove(&outpoint) {
330 if let Some(idx) = bip30_index.as_deref_mut() {
331 if arc.is_coinbase {
332 if let std::collections::hash_map::Entry::Occupied(mut o) =
333 idx.entry(outpoint.hash)
334 {
335 *o.get_mut() = o.get().saturating_sub(1);
336 if *o.get() == 0 {
337 o.remove();
338 }
339 }
340 }
341 }
342 if let Some(ref mut log) = undo_log {
343 log.entries.push(UndoEntry {
344 outpoint,
345 previous_utxo: Some(arc),
346 new_utxo: None,
347 });
348 }
349 }
350 }
351
352 for (outpoint, arc) in additions {
353 if let Some(idx) = bip30_index.as_deref_mut() {
354 if arc.is_coinbase {
355 *idx.entry(outpoint.hash).or_insert(0) += 1;
356 }
357 }
358 if !skip_utxo_set_mutation {
359 if let Some(ref mut log) = undo_log {
360 log.entries.push(UndoEntry {
361 outpoint: *outpoint,
362 previous_utxo: None,
363 new_utxo: Some(std::sync::Arc::clone(arc)),
364 });
365 }
366 utxo_set.insert(*outpoint, std::sync::Arc::clone(arc));
367 }
368 }
369}
370
371#[cfg(feature = "production")]
377pub(super) fn undo_log_from_overlay(
378 additions: &FxHashMap<OutPoint, std::sync::Arc<UTXO>>,
379 deletions: &FxHashSet<crate::utxo_overlay::UtxoDeletionKey>,
380 base: &dyn crate::utxo_overlay::UtxoLookup,
381) -> crate::error::Result<crate::reorganization::BlockUndoLog> {
382 use crate::reorganization::{BlockUndoLog, UndoEntry};
383
384 let mut log = BlockUndoLog::new();
385 log.entries.reserve(deletions.len() + additions.len());
386 for del_key in deletions {
387 let outpoint = crate::utxo_overlay::utxo_deletion_key_to_outpoint(del_key);
388 let Some(prev) = base.get(&outpoint) else {
389 return Err(crate::error::ConsensusError::BlockValidation(
390 format!("undo log: spent coin {outpoint:?} is not in the base view").into(),
391 ));
392 };
393 log.entries.push(UndoEntry {
394 outpoint,
395 previous_utxo: Some(std::sync::Arc::new(prev.clone())),
396 new_utxo: None,
397 });
398 }
399 for (outpoint, arc) in additions {
400 let previous_utxo = base
401 .get(outpoint)
402 .map(|prev| std::sync::Arc::new(prev.clone()));
403 log.entries.push(UndoEntry {
404 outpoint: *outpoint,
405 previous_utxo,
406 new_utxo: Some(std::sync::Arc::clone(arc)),
407 });
408 }
409 Ok(log)
410}
411
412#[cfg(all(feature = "production", feature = "rayon"))]
415#[allow(dead_code)] pub(super) fn compute_bip143_and_precomp(
417 tx: &Transaction,
418 prevout_values: &[i64],
419 script_pubkey_indices: &[(usize, usize)],
420 script_pubkey_buffer: &[u8],
421 has_witness: bool,
422) -> (
423 Option<crate::transaction_hash::Bip143PrecomputedHashes>,
424 Vec<Option<[u8; 32]>>,
425) {
426 let buf = script_pubkey_buffer;
427 let refs: Vec<&[u8]> = script_pubkey_indices
428 .iter()
429 .map(|&(s, l)| buf[s..s + l].as_ref())
430 .collect();
431 let refs: &[&[u8]] = &refs;
432 if has_witness {
433 let bip =
434 crate::transaction_hash::Bip143PrecomputedHashes::compute(tx, prevout_values, refs);
435 let mut precomp = vec![None; script_pubkey_indices.len()];
436 let mut specs: Vec<(usize, u8, &[u8])> = Vec::new();
437 for (j, &(s, l)) in script_pubkey_indices.iter().enumerate() {
438 let spk = &buf[s..s + l];
439 if spk.len() == 22 && spk[0] == OP_0 && spk[1] == PUSH_20_BYTES {
440 let mut script_code = [0u8; 25];
441 script_code[0] = OP_DUP;
442 script_code[1] = OP_HASH160;
443 script_code[2] = PUSH_20_BYTES;
444 script_code[3..23].copy_from_slice(&spk[2..22]);
445 script_code[23] = OP_EQUALVERIFY;
446 script_code[24] = OP_CHECKSIG;
447 let amount = prevout_values.get(j).copied().unwrap_or(0);
448 if let Ok(h) = crate::transaction_hash::calculate_bip143_sighash(
449 tx,
450 j,
451 &script_code,
452 amount,
453 0x01,
454 Some(&bip),
455 ) {
456 precomp[j] = Some(h);
457 }
458 } else if spk.len() == 23
459 && spk[0] == OP_HASH160
460 && spk[1] == PUSH_20_BYTES
461 && spk[22] == OP_EQUAL
462 {
463 if let Some((sighash_byte, redeem)) =
464 crate::script::parse_p2sh_p2pkh_for_precompute(&tx.inputs[j].script_sig)
465 {
466 specs.push((j, sighash_byte, redeem));
467 }
468 }
469 }
470 if !specs.is_empty() {
471 if let Ok(hashes) = crate::transaction_hash::batch_compute_legacy_sighashes(
472 tx,
473 prevout_values,
474 refs,
475 &specs,
476 ) {
477 for (k, &(j, _, _)) in specs.iter().enumerate() {
478 precomp[j] = Some(hashes[k]);
479 }
480 }
481 }
482 (Some(bip), precomp)
483 } else {
484 let mut precomp = vec![None; script_pubkey_indices.len()];
485 let mut specs: Vec<(usize, u8, &[u8])> = Vec::new();
486 for (j, &(s, l)) in script_pubkey_indices.iter().enumerate() {
487 let spk = &buf[s..s + l];
488 if spk.len() == 25
489 && spk[0] == OP_DUP
490 && spk[1] == OP_HASH160
491 && spk[2] == PUSH_20_BYTES
492 && spk[23] == OP_EQUALVERIFY
493 && spk[24] == OP_CHECKSIG
494 {
495 let script_sig = &tx.inputs[j].script_sig;
496 if let Some((sig, _pubkey)) = crate::script::parse_p2pkh_script_sig(script_sig) {
497 if !sig.is_empty() {
498 specs.push((j, sig[sig.len() - 1], spk));
499 }
500 }
501 } else if spk.len() == 23
502 && spk[0] == OP_HASH160
503 && spk[1] == PUSH_20_BYTES
504 && spk[22] == OP_EQUAL
505 {
506 if let Some((sighash_byte, redeem)) =
507 crate::script::parse_p2sh_p2pkh_for_precompute(&tx.inputs[j].script_sig)
508 {
509 specs.push((j, sighash_byte, redeem));
510 }
511 }
512 }
513 if !specs.is_empty() {
514 if let Ok(hashes) = crate::transaction_hash::batch_compute_legacy_sighashes(
515 tx,
516 prevout_values,
517 refs,
518 &specs,
519 ) {
520 for (k, &(j, _, _)) in specs.iter().enumerate() {
521 precomp[j] = Some(hashes[k]);
522 }
523 }
524 }
525 (None, precomp)
526 }
527}
528
529#[cfg(test)]
530mod script_flag_exceptions_tests {
531 use super::{
532 calculate_script_flags_for_block_network, get_block_script_flags,
533 get_block_script_verify_flags_core, script_flag_exceptions_lookup,
534 };
535 use crate::activation::ForkActivationTable;
536 use crate::crypto::OptimizedSha256;
537 use crate::script::flags::{
538 SCRIPT_VERIFY_P2SH, SCRIPT_VERIFY_TAPROOT, SCRIPT_VERIFY_WITNESS,
539 SCRIPT_VERIFY_WITNESS_PUBKEYTYPE,
540 };
541 use crate::serialization::block::{deserialize_block_header, serialize_block_header};
542 use crate::types::{Network, Transaction};
543
544 fn hash_from_rpc_hex(hex_str: &str) -> [u8; 32] {
545 let mut bytes = hex::decode(hex_str).unwrap();
546 assert_eq!(bytes.len(), 32);
547 bytes.reverse();
548 bytes.try_into().unwrap()
549 }
550
551 #[test]
552 fn mainnet_bip16_exception_zero() {
553 let h =
554 hash_from_rpc_hex("00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22");
555 assert_eq!(script_flag_exceptions_lookup(&h, Network::Mainnet), Some(0));
556 }
557
558 #[test]
559 fn mainnet_taproot_exception_p2sh_witness() {
560 let h =
561 hash_from_rpc_hex("0000000000000000000f14c35b2d841e986ab5441de8c585d5ffe55ea1e395ad");
562 assert_eq!(
563 script_flag_exceptions_lookup(&h, Network::Mainnet),
564 Some(0x01 | 0x800)
565 );
566 }
567
568 #[test]
569 fn testnet_bip16_exception_zero() {
570 let h =
571 hash_from_rpc_hex("00000000dd30457c001f4095d208cc1296b0eed002427aa599874af7a432b105");
572 assert_eq!(script_flag_exceptions_lookup(&h, Network::Testnet), Some(0));
573 assert_eq!(script_flag_exceptions_lookup(&h, Network::Mainnet), None);
574 }
575
576 #[test]
577 fn regtest_has_no_exceptions() {
578 let h =
579 hash_from_rpc_hex("00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22");
580 assert_eq!(script_flag_exceptions_lookup(&h, Network::Regtest), None);
581 }
582
583 #[test]
584 fn get_block_script_flags_uses_exception() {
585 let h =
586 hash_from_rpc_hex("00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22");
587 let tx = Transaction {
588 version: 1,
589 inputs: Default::default(),
590 outputs: Default::default(),
591 lock_time: 0,
592 };
593 let flags = get_block_script_flags(&h, &tx, false, 1_000_000, Network::Mainnet);
594 assert_eq!(flags, 0);
595 }
596
597 #[test]
598 fn get_block_script_flags_falls_back_to_calculate() {
599 let h = [7u8; 32];
600 let tx = Transaction {
601 version: 1,
602 inputs: Default::default(),
603 outputs: Default::default(),
604 lock_time: 0,
605 };
606 assert_eq!(
607 get_block_script_flags(&h, &tx, false, 100, Network::Mainnet),
608 calculate_script_flags_for_block_network(&tx, false, 100, Network::Mainnet),
609 );
610 }
611
612 #[test]
613 fn genesis_rpc_hex_matches_hash256_of_header() {
614 let genesis_block_hex = "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c010100000001000000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000";
615 let bytes = hex::decode(genesis_block_hex).unwrap();
616 let header = deserialize_block_header(&bytes[..80]).unwrap();
617 let digest = OptimizedSha256::new().hash256(&serialize_block_header(&header));
618 let expected =
619 hash_from_rpc_hex("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f");
620 assert_eq!(digest, expected);
621 }
622
623 #[test]
624 fn core_block_flags_non_exception_includes_p2sh_witness_taproot_and_deployments() {
625 let table = ForkActivationTable::from_network(Network::Mainnet);
626 let h = [0xabu8; 32];
627 let flags = get_block_script_verify_flags_core(&h, 800_000, &table, Network::Mainnet);
628 assert_eq!(
630 flags
631 & (SCRIPT_VERIFY_P2SH
632 | SCRIPT_VERIFY_WITNESS
633 | SCRIPT_VERIFY_WITNESS_PUBKEYTYPE
634 | SCRIPT_VERIFY_TAPROOT),
635 SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_TAPROOT,
636 "P2SH | WITNESS | TAPROOT baseline"
637 );
638 assert_eq!(flags & SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, 0);
639 let pre_segwit = get_block_script_verify_flags_core(&h, 100, &table, Network::Mainnet);
640 assert_ne!(
641 pre_segwit & SCRIPT_VERIFY_WITNESS,
642 0,
643 "pre-segwit height still has WITNESS"
644 );
645 assert_ne!(flags & 0x04, 0, "DERSIG");
646 assert_ne!(flags & 0x200, 0, "CLTV");
647 assert_ne!(flags & 0x400, 0, "CSV");
648 assert_ne!(flags & 0x10, 0, "NULLDUMMY");
649 }
650
651 #[test]
652 fn core_block_flags_bip16_exception_orrs_buried_deployments() {
653 let table = ForkActivationTable::from_network(Network::Mainnet);
654 let h =
655 hash_from_rpc_hex("00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22");
656 assert_eq!(script_flag_exceptions_lookup(&h, Network::Mainnet), Some(0));
657 let flags = get_block_script_verify_flags_core(&h, 800_000, &table, Network::Mainnet);
658 let deployment_mask = 0x04 | 0x200 | 0x400 | 0x10;
659 assert_eq!(flags & 0x8801, 0);
660 assert_eq!(flags & deployment_mask, deployment_mask);
661 assert_eq!(flags & 0x800, 0);
662 assert_eq!(flags & 0x8000, 0);
663 assert_eq!(flags & 0x01, 0);
664 }
665
666 #[test]
667 fn core_block_flags_taproot_exception_orrs_deployments() {
668 let table = ForkActivationTable::from_network(Network::Mainnet);
669 let h =
670 hash_from_rpc_hex("0000000000000000000f14c35b2d841e986ab5441de8c585d5ffe55ea1e395ad");
671 let flags = get_block_script_verify_flags_core(&h, 800_000, &table, Network::Mainnet);
672 assert_eq!(flags & 0x8801, 0x801);
673 assert_eq!(flags & 0x8000, 0);
674 assert_ne!(flags & 0x800, 0);
675 assert_ne!(flags & 0x04, 0);
676 }
677}
678
679#[cfg(all(test, feature = "production"))]
680mod replaced_output_undo_tests {
681 use super::{merge_overlay_changes_to_cache, undo_log_from_overlay};
682 use crate::types::{OutPoint, UTXO, UtxoSet};
683 use rustc_hash::{FxHashMap, FxHashSet};
684 use std::sync::Arc;
685
686 #[test]
687 fn addition_records_the_coin_it_replaces() {
688 let outpoint = OutPoint {
689 hash: [9u8; 32],
690 index: 0,
691 };
692 let mut base = UtxoSet::default();
693 base.insert(
694 outpoint,
695 Arc::new(UTXO {
696 value: 50,
697 script_pubkey: vec![0x51].into(),
698 height: 1,
699 is_coinbase: true,
700 }),
701 );
702 let mut additions = FxHashMap::default();
703 additions.insert(
704 outpoint,
705 Arc::new(UTXO {
706 value: 50,
707 script_pubkey: vec![0x52].into(),
708 height: 91_842,
709 is_coinbase: true,
710 }),
711 );
712 let deletions = FxHashSet::default();
713 let log = undo_log_from_overlay(&additions, &deletions, &base).unwrap();
714 let previous = log.entries[0]
715 .previous_utxo
716 .as_ref()
717 .expect("replaced coin is kept");
718 assert_eq!(previous.height, 1);
719 assert!(previous.is_coinbase);
720 assert_eq!(log.entries[0].new_utxo.as_ref().unwrap().height, 91_842);
721
722 let mut after = UtxoSet::default();
723 after.insert(
724 outpoint,
725 Arc::clone(log.entries[0].new_utxo.as_ref().unwrap()),
726 );
727 after.remove(&outpoint);
728 after.insert(outpoint, Arc::clone(previous));
729 assert_eq!(after.get(&outpoint).unwrap().height, 1);
730 }
731
732 #[test]
733 fn ibd_merge_counts_a_new_coinbase_output() {
734 let txid = [0x11u8; 32];
735 let outpoint = OutPoint {
736 hash: txid,
737 index: 0,
738 };
739 let mut additions = FxHashMap::default();
740 additions.insert(
741 outpoint,
742 Arc::new(UTXO {
743 value: 50,
744 script_pubkey: vec![0x51].into(),
745 height: 1,
746 is_coinbase: true,
747 }),
748 );
749 let deletions = FxHashSet::default();
750 let mut index = crate::bip_validation::Bip30Index::default();
751 let mut utxo_set = UtxoSet::default();
752 merge_overlay_changes_to_cache(
753 &additions,
754 &deletions,
755 &mut utxo_set,
756 Some(&mut index),
757 None,
758 true,
759 );
760 assert!(utxo_set.is_empty(), "the IBD scratch set stays unchanged");
761 assert_eq!(index.get(&txid).copied(), Some(1));
762 }
763}