1mod apply;
9mod connect;
10mod header;
11mod script_cache;
12pub use apply::{apply_transaction, calculate_tx_id};
13pub use header::{block_header_hash, validate_prev_block_hash};
14
15pub fn genesis_block(network: Network) -> Block {
17 let coinbase = header::genesis_coinbase(network);
18 Block {
19 header: header::header_for_genesis_coinbase(network, &coinbase),
20 transactions: vec![coinbase].into_boxed_slice(),
21 }
22}
23pub use script_cache::{
24 calculate_base_script_flags_for_block_network, calculate_script_flags_for_block_network,
25 get_block_script_flags, get_block_script_verify_flags_core, script_flag_exceptions_lookup,
26 tx_has_nonempty_input_witness, tx_requires_witness_script_flags,
27};
28
29use crate::activation::{ForkActivationTable, IsForkActive};
30use crate::bip113::get_median_time_past;
31use crate::error::Result;
32use crate::segwit::Witness;
33use crate::types::*;
34#[cfg(feature = "spec-lock")]
35use blvm_spec_lock::spec_locked;
36#[cfg(feature = "production")]
37use rustc_hash::{FxHashMap, FxHashSet};
38
39#[cfg(test)]
40use crate::opcodes::*;
41#[cfg(test)]
42use crate::transaction::is_coinbase;
43
44#[derive(Debug, Clone)]
52pub struct UtxoDeltaInner<M, S> {
53 pub additions: M,
54 pub deletions: S,
55}
56
57#[cfg(feature = "production")]
58pub type UtxoDelta = UtxoDeltaInner<
59 FxHashMap<OutPoint, std::sync::Arc<UTXO>>,
60 FxHashSet<crate::utxo_overlay::UtxoDeletionKey>,
61>;
62#[cfg(not(feature = "production"))]
63pub type UtxoDelta = UtxoDeltaInner<
64 std::collections::HashMap<OutPoint, std::sync::Arc<UTXO>>,
65 std::collections::HashSet<crate::utxo_overlay::UtxoDeletionKey>,
66>;
67
68#[cfg(feature = "production")]
97#[cfg(all(feature = "production", feature = "rayon"))]
98pub(crate) fn skip_script_exec_cache() -> bool {
99 use std::sync::OnceLock;
100 static CACHED: OnceLock<bool> = OnceLock::new();
101 *CACHED.get_or_init(|| {
102 std::env::var("BLVM_SKIP_SCRIPT_CACHE")
103 .map(|v| v == "1")
104 .unwrap_or(false)
105 })
106}
107
108pub fn get_assume_valid_height() -> u64 {
109 #[cfg(feature = "benchmarking")]
111 {
112 use std::sync::atomic::{AtomicU64, Ordering};
113 static OVERRIDE: AtomicU64 = AtomicU64::new(u64::MAX);
114 let override_val = OVERRIDE.load(Ordering::Relaxed);
115 if override_val != u64::MAX {
116 return override_val;
117 }
118 }
119
120 crate::config::get_assume_valid_height()
121}
122
123#[track_caller]
148#[cfg_attr(feature = "spec-lock", spec_locked("5.3", "ConnectBlock"))]
149pub fn connect_block(
150 block: &Block,
151 witnesses: &[Vec<Witness>],
152 utxo_set: UtxoSet,
153 height: Natural,
154 context: &BlockValidationContext,
155) -> Result<(
156 ValidationResult,
157 UtxoSet,
158 crate::reorganization::BlockUndoLog,
159)> {
160 #[cfg(all(feature = "production", feature = "rayon"))]
161 let block_arc = Some(std::sync::Arc::new(block.clone()));
162 #[cfg(not(all(feature = "production", feature = "rayon")))]
163 let block_arc = None;
164 let (result, new_utxo_set, _tx_ids, undo_log, _delta) = connect::connect_block_inner(
165 block, witnesses, utxo_set, None, height, context, None, None, block_arc, false, true,
166 None, None, false,
167 )?;
168 Ok((result, new_utxo_set, undo_log))
169}
170
171#[cfg(feature = "benchmarking")]
180pub fn connect_block_with_chainwork(
181 block: &Block,
182 witnesses: &[Vec<Witness>],
183 utxo_set: UtxoSet,
184 height: Natural,
185 context: &BlockValidationContext,
186 best_header_chainwork: Option<crate::pow::U256>,
187) -> Result<(
188 ValidationResult,
189 UtxoSet,
190 crate::reorganization::BlockUndoLog,
191)> {
192 #[cfg(all(feature = "production", feature = "rayon"))]
193 let block_arc = Some(std::sync::Arc::new(block.clone()));
194 #[cfg(not(all(feature = "production", feature = "rayon")))]
195 let block_arc = None;
196 let (result, new_utxo_set, _tx_ids, undo_log, _delta) = connect::connect_block_inner(
197 block,
198 witnesses,
199 utxo_set,
200 None,
201 height,
202 context,
203 None,
204 None,
205 block_arc,
206 false,
207 true,
208 best_header_chainwork,
209 None,
210 false,
211 )?;
212 Ok((result, new_utxo_set, undo_log))
213}
214
215#[cfg_attr(feature = "spec-lock", spec_locked("5.3", "ConnectBlock"))]
230pub fn connect_block_ibd<'a>(
231 block: &Block,
232 witnesses: &[Vec<Witness>],
233 utxo_set: UtxoSet,
234 height: Natural,
235 context: &BlockValidationContext,
236 bip30_index: Option<&mut crate::bip_validation::Bip30Index>,
237 precomputed_tx_ids: Option<&'a [Hash]>,
238 block_arc: Option<std::sync::Arc<Block>>,
239 witnesses_arc: Option<&std::sync::Arc<Vec<Vec<Witness>>>>,
240 best_header_chainwork: Option<crate::pow::U256>,
241) -> Result<(
242 ValidationResult,
243 UtxoSet,
244 std::borrow::Cow<'a, [Hash]>,
245 Option<UtxoDelta>,
246)> {
247 let (result, new_utxo_set, tx_ids, _undo_log, utxo_delta) = connect::connect_block_inner(
248 block,
249 witnesses,
250 utxo_set,
251 witnesses_arc,
252 height,
253 context,
254 bip30_index,
255 precomputed_tx_ids,
256 block_arc,
257 true,
258 false,
259 best_header_chainwork,
260 None,
261 false,
262 )?;
263 Ok((result, new_utxo_set, tx_ids, utxo_delta))
264}
265
266#[cfg_attr(feature = "spec-lock", spec_locked("5.3", "ConnectBlock"))]
268pub fn connect_block_ibd_with_undo<'a>(
269 block: &Block,
270 witnesses: &[Vec<Witness>],
271 utxo_set: UtxoSet,
272 height: Natural,
273 context: &BlockValidationContext,
274 bip30_index: Option<&mut crate::bip_validation::Bip30Index>,
275 precomputed_tx_ids: Option<&'a [Hash]>,
276 block_arc: Option<std::sync::Arc<Block>>,
277 witnesses_arc: Option<&std::sync::Arc<Vec<Vec<Witness>>>>,
278 best_header_chainwork: Option<crate::pow::U256>,
279 ibd_utxo_lookup: Option<&dyn crate::utxo_overlay::UtxoLookup>,
280 assume_valid_pin_connected: bool,
281) -> Result<(
282 ValidationResult,
283 UtxoSet,
284 std::borrow::Cow<'a, [Hash]>,
285 Option<UtxoDelta>,
286 crate::reorganization::BlockUndoLog,
287)> {
288 let (result, new_utxo_set, tx_ids, undo_log, utxo_delta) = connect::connect_block_inner(
289 block,
290 witnesses,
291 utxo_set,
292 witnesses_arc,
293 height,
294 context,
295 bip30_index,
296 precomputed_tx_ids,
297 block_arc,
298 true,
299 true,
300 best_header_chainwork,
301 ibd_utxo_lookup,
302 assume_valid_pin_connected,
303 )?;
304 Ok((result, new_utxo_set, tx_ids, utxo_delta, undo_log))
305}
306
307fn build_time_context<H: AsRef<BlockHeader>>(
313 recent_headers: Option<&[H]>,
314 network_time: u64,
315) -> Option<crate::types::TimeContext> {
316 recent_headers.map(|headers| {
317 let median_time_past = get_median_time_past(headers);
318 crate::types::TimeContext {
319 network_time,
320 median_time_past,
321 }
322 })
323}
324
325#[cfg(feature = "production")]
327pub type IbdBlockOutputCache = std::sync::Arc<
328 rustc_hash::FxHashMap<crate::types::OutPoint, std::sync::Arc<crate::types::UTXO>>,
329>;
330#[cfg(not(feature = "production"))]
331pub type IbdBlockOutputCache = std::sync::Arc<
332 std::collections::HashMap<crate::types::OutPoint, std::sync::Arc<crate::types::UTXO>>,
333>;
334
335#[derive(Clone)]
340pub struct BlockValidationContext {
341 pub time_context: Option<crate::types::TimeContext>,
343 pub network_time: u64,
345 pub network: crate::types::Network,
347 pub activation: ForkActivationTable,
349 pub bip54_boundary: Option<crate::types::Bip54BoundaryTimestamps>,
351 pub signet_challenge: Option<ByteString>,
353 pub ibd_block_outputs: Option<IbdBlockOutputCache>,
355 pub sequence_prev_mtp: Option<SequencePrevMtp>,
358 pub difficulty_ancestor: Option<DifficultyAncestor>,
360}
361
362pub type SequencePrevMtp = std::sync::Arc<dyn Fn(u64) -> Option<u64> + Send + Sync>;
364
365pub type DifficultyAncestor = std::sync::Arc<dyn Fn(u64) -> Option<(u64, u64)> + Send + Sync>;
370
371impl BlockValidationContext {
372 pub fn from_connect_block_ibd_args<H: AsRef<BlockHeader>>(
374 recent_headers: Option<&[H]>,
375 network_time: u64,
376 network: crate::types::Network,
377 bip54_activation_override: Option<u64>,
378 bip54_boundary: Option<crate::types::Bip54BoundaryTimestamps>,
379 ) -> Self {
380 let time_context = build_time_context(recent_headers, network_time);
381 let activation = ForkActivationTable::from_network_and_bip54_override(
382 network,
383 bip54_activation_override,
384 );
385 Self {
386 time_context,
387 network_time,
388 network,
389 activation,
390 bip54_boundary,
391 signet_challenge: None,
392 ibd_block_outputs: None,
393 sequence_prev_mtp: None,
394 difficulty_ancestor: None,
395 }
396 }
397
398 pub fn from_time_context_and_network(
400 time_context: Option<crate::types::TimeContext>,
401 network: crate::types::Network,
402 bip54_boundary: Option<crate::types::Bip54BoundaryTimestamps>,
403 ) -> Self {
404 let network_time = time_context.as_ref().map(|c| c.network_time).unwrap_or(0);
405 let activation = ForkActivationTable::from_network(network);
406 Self {
407 time_context,
408 network_time,
409 network,
410 activation,
411 bip54_boundary,
412 signet_challenge: None,
413 ibd_block_outputs: None,
414 sequence_prev_mtp: None,
415 difficulty_ancestor: None,
416 }
417 }
418
419 pub fn for_network(network: crate::types::Network) -> Self {
421 block_validation_context_for_connect_ibd(None::<&[BlockHeader]>, 0, network)
422 }
423}
424
425#[inline]
428pub fn block_validation_context_for_connect_ibd<H: AsRef<BlockHeader>>(
429 recent_headers: Option<&[H]>,
430 network_time: u64,
431 network: Network,
432) -> BlockValidationContext {
433 BlockValidationContext::from_connect_block_ibd_args(
434 recent_headers,
435 network_time,
436 network,
437 None,
438 None,
439 )
440}
441
442impl IsForkActive for BlockValidationContext {
443 #[inline]
444 fn is_fork_active(&self, fork: crate::types::ForkId, height: u64) -> bool {
445 self.activation.is_fork_active(fork, height)
446 }
447}
448
449#[cfg(feature = "production")]
450mod tx_id_pool {
451 use crate::types::{Hash, Transaction};
452 use std::cell::RefCell;
453
454 thread_local! {
455 static TX_BUF: RefCell<Vec<u8>> = RefCell::new(Vec::with_capacity(
456 crate::optimizations::proven_bounds::MAX_TX_SIZE_PROVEN
457 ));
458 }
459
460 pub fn compute_tx_id_with_pool(tx: &Transaction) -> Hash {
462 use crate::crypto::OptimizedSha256;
463 use crate::serialization::transaction::serialize_transaction_into;
464
465 TX_BUF.with(|cell| {
466 let mut buf = cell.borrow_mut();
467 serialize_transaction_into(&mut buf, tx);
468 OptimizedSha256::new().hash256(&buf)
469 })
470 }
471}
472
473#[cfg_attr(feature = "spec-lock", spec_locked("8.4.1", "ComputeMerkleRoot"))]
479pub fn compute_block_tx_ids_spec(block: &Block) -> Vec<Hash> {
480 block.transactions.iter().map(calculate_tx_id).collect()
481}
482
483pub fn compute_block_tx_ids_into(block: &Block, out: &mut Vec<Hash>) {
495 out.clear();
496 out.reserve(block.transactions.len());
497
498 #[cfg(feature = "production")]
499 {
500 out.extend(
501 block
502 .transactions
503 .iter()
504 .map(tx_id_pool::compute_tx_id_with_pool),
505 );
506 }
507
508 #[cfg(not(feature = "production"))]
509 {
510 out.extend(block.transactions.iter().map(calculate_tx_id));
511 }
512}
513
514pub fn compute_block_tx_ids(block: &Block) -> Vec<Hash> {
515 let mut v = Vec::new();
516 compute_block_tx_ids_into(block, &mut v);
517 v
518}
519
520#[test]
522fn compute_block_tx_ids_spec_matches_optimized_paths() {
523 let coinbase = Transaction {
524 version: 1,
525 inputs: vec![TransactionInput {
526 prevout: OutPoint {
527 hash: [0; 32],
528 index: 0xffffffff,
529 },
530 script_sig: vec![0x03, 0x01, 0x00, 0x00],
531 sequence: 0xffffffff,
532 }]
533 .into(),
534 outputs: vec![TransactionOutput {
535 value: 50_000_000_000,
536 script_pubkey: vec![0x51],
537 }]
538 .into(),
539 lock_time: 0,
540 };
541 let tx2 = Transaction {
542 version: 1,
543 inputs: vec![TransactionInput {
544 prevout: OutPoint {
545 hash: [1u8; 32],
546 index: 0,
547 },
548 script_sig: vec![0x51],
549 sequence: 0xffffffff,
550 }]
551 .into(),
552 outputs: vec![TransactionOutput {
553 value: 10_000,
554 script_pubkey: vec![0x51],
555 }]
556 .into(),
557 lock_time: 0,
558 };
559 let block = Block {
560 header: BlockHeader {
561 version: 1,
562 prev_block_hash: [0; 32],
563 merkle_root: [0; 32],
564 timestamp: 1,
565 bits: 0x207fffff,
566 nonce: 0,
567 },
568 transactions: vec![coinbase, tx2].into_boxed_slice(),
569 };
570 assert_eq!(
571 compute_block_tx_ids_spec(&block),
572 compute_block_tx_ids(&block),
573 "§8.4.1 spec reference must match compute_block_tx_ids / compute_block_tx_ids_into"
574 );
575}
576
577#[test]
578fn test_connect_block_invalid_header() {
579 let coinbase_tx = Transaction {
580 version: 1,
581 inputs: vec![TransactionInput {
582 prevout: OutPoint {
583 hash: [0; 32],
584 index: 0xffffffff,
585 },
586 script_sig: vec![],
587 sequence: 0xffffffff,
588 }]
589 .into(),
590 outputs: vec![TransactionOutput {
591 value: 5000000000,
592 script_pubkey: vec![],
593 }]
594 .into(),
595 lock_time: 0,
596 };
597
598 let block = Block {
599 header: BlockHeader {
600 version: 0, prev_block_hash: [0; 32],
602 merkle_root: [0; 32],
603 timestamp: 1231006505,
604 bits: 0x1d00ffff,
605 nonce: 0,
606 },
607 transactions: vec![coinbase_tx].into_boxed_slice(),
608 };
609
610 let utxo_set = UtxoSet::default();
611 let witnesses: Vec<Vec<Witness>> = block
612 .transactions
613 .iter()
614 .map(|tx| {
615 (0..tx.inputs.len())
616 .map(|_| Vec::with_capacity(2))
617 .collect()
618 })
619 .collect();
620 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
621 let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
622
623 assert!(matches!(result, ValidationResult::Invalid(_)));
624}
625
626#[test]
627fn test_connect_block_rejects_bits_that_are_not_the_parents() {
628 let coinbase_tx = Transaction {
629 version: 1,
630 inputs: vec![TransactionInput {
631 prevout: OutPoint {
632 hash: [0; 32],
633 index: 0xffffffff,
634 },
635 script_sig: vec![0x51, 0x51],
636 sequence: 0xffffffff,
637 }]
638 .into(),
639 outputs: vec![TransactionOutput {
640 value: 5000000000,
641 script_pubkey: vec![],
642 }]
643 .into(),
644 lock_time: 0,
645 };
646 let block = Block {
647 header: BlockHeader {
648 version: 1,
649 prev_block_hash: [0; 32],
650 merkle_root: [1; 32],
651 timestamp: 1_000,
652 bits: 0x1b00ffff,
653 nonce: 0,
654 },
655 transactions: vec![coinbase_tx].into_boxed_slice(),
656 };
657 let witnesses: Vec<Vec<Witness>> = vec![vec![Vec::new()]];
658 let mut ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
659 ctx.difficulty_ancestor = Some(std::sync::Arc::new(|height| {
660 if height == 0 {
661 Some((0x1d00ffff, 100))
662 } else {
663 None
664 }
665 }));
666 let (result, _, _) = connect_block(&block, &witnesses, UtxoSet::default(), 1, &ctx).unwrap();
667 match result {
668 ValidationResult::Invalid(reason) => {
669 assert!(
670 reason.contains("required work"),
671 "unexpected reject: {reason}"
672 );
673 }
674 ValidationResult::Valid => panic!("wrong bits were accepted"),
675 }
676}
677
678#[test]
679fn test_connect_block_no_transactions() {
680 let block = Block {
681 header: BlockHeader {
682 version: 1,
683 prev_block_hash: [0; 32],
684 merkle_root: [0; 32],
685 timestamp: 1231006505,
686 bits: 0x1d00ffff,
687 nonce: 0,
688 },
689 transactions: vec![].into_boxed_slice(), };
691
692 let utxo_set = UtxoSet::default();
693 let witnesses: Vec<Vec<Witness>> = block
695 .transactions
696 .iter()
697 .map(|tx| {
698 (0..tx.inputs.len())
699 .map(|_| Vec::with_capacity(2))
700 .collect()
701 })
702 .collect();
703 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
704 let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
705
706 assert!(matches!(result, ValidationResult::Invalid(_)));
707}
708
709#[test]
710fn test_connect_block_first_tx_not_coinbase() {
711 let regular_tx = Transaction {
712 version: 1,
713 inputs: vec![TransactionInput {
714 prevout: OutPoint {
715 hash: [1; 32],
716 index: 0,
717 },
718 script_sig: vec![],
719 sequence: 0xffffffff,
720 }]
721 .into(),
722 outputs: vec![TransactionOutput {
723 value: 1000,
724 script_pubkey: vec![],
725 }]
726 .into(),
727 lock_time: 0,
728 };
729
730 let block = Block {
731 header: BlockHeader {
732 version: 1,
733 prev_block_hash: [0; 32],
734 merkle_root: [0; 32],
735 timestamp: 1231006505,
736 bits: 0x1d00ffff,
737 nonce: 0,
738 },
739 transactions: vec![regular_tx].into_boxed_slice(), };
741
742 let utxo_set = UtxoSet::default();
743 let witnesses: Vec<Vec<Witness>> = block
745 .transactions
746 .iter()
747 .map(|tx| {
748 (0..tx.inputs.len())
749 .map(|_| Vec::with_capacity(2))
750 .collect()
751 })
752 .collect();
753 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
754 let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
755
756 assert!(matches!(result, ValidationResult::Invalid(_)));
757}
758
759#[test]
760fn test_connect_block_coinbase_exceeds_subsidy() {
761 let coinbase_tx = Transaction {
762 version: 1,
763 inputs: vec![TransactionInput {
764 prevout: OutPoint {
765 hash: [0; 32],
766 index: 0xffffffff,
767 },
768 script_sig: vec![],
769 sequence: 0xffffffff,
770 }]
771 .into(),
772 outputs: vec![TransactionOutput {
773 value: 6000000000, script_pubkey: vec![],
775 }]
776 .into(),
777 lock_time: 0,
778 };
779
780 let block = Block {
781 header: BlockHeader {
782 version: 1,
783 prev_block_hash: [0; 32],
784 merkle_root: [0; 32],
785 timestamp: 1231006505,
786 bits: 0x1d00ffff,
787 nonce: 0,
788 },
789 transactions: vec![coinbase_tx].into_boxed_slice(),
790 };
791
792 let utxo_set = UtxoSet::default();
793 let witnesses: Vec<Vec<Witness>> = block
795 .transactions
796 .iter()
797 .map(|tx| {
798 (0..tx.inputs.len())
799 .map(|_| Vec::with_capacity(2))
800 .collect()
801 })
802 .collect();
803 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
804 let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
805
806 assert!(matches!(result, ValidationResult::Invalid(_)));
807}
808
809#[test]
810fn test_apply_transaction_regular() {
811 let mut utxo_set = UtxoSet::default();
812
813 let prev_outpoint = OutPoint {
815 hash: [1; 32],
816 index: 0,
817 };
818 let prev_utxo = UTXO {
819 value: 1000,
820 script_pubkey: vec![OP_1].into(), height: 0,
822 is_coinbase: false,
823 };
824 utxo_set.insert(prev_outpoint, std::sync::Arc::new(prev_utxo));
825
826 let regular_tx = Transaction {
827 version: 1,
828 inputs: vec![TransactionInput {
829 prevout: OutPoint {
830 hash: [1; 32],
831 index: 0,
832 },
833 script_sig: vec![OP_1], sequence: 0xffffffff,
835 }]
836 .into(),
837 outputs: vec![TransactionOutput {
838 value: 500,
839 script_pubkey: vec![OP_2], }]
841 .into(),
842 lock_time: 0,
843 };
844
845 let (new_utxo_set, _undo_entries) = apply_transaction(®ular_tx, utxo_set, 1).unwrap();
846
847 assert_eq!(new_utxo_set.len(), 1);
849}
850
851#[test]
852fn test_apply_transaction_multiple_outputs() {
853 let coinbase_tx = Transaction {
854 version: 1,
855 inputs: vec![TransactionInput {
856 prevout: OutPoint {
857 hash: [0; 32],
858 index: 0xffffffff,
859 },
860 script_sig: vec![],
861 sequence: 0xffffffff,
862 }]
863 .into(),
864 outputs: vec![
865 TransactionOutput {
866 value: 2500000000,
867 script_pubkey: vec![OP_1],
868 },
869 TransactionOutput {
870 value: 2500000000,
871 script_pubkey: vec![OP_2],
872 },
873 ]
874 .into(),
875 lock_time: 0,
876 };
877
878 let utxo_set = UtxoSet::default();
879 let (new_utxo_set, _undo_entries) = apply_transaction(&coinbase_tx, utxo_set, 0).unwrap();
880
881 assert_eq!(new_utxo_set.len(), 2);
882}
883
884#[test]
885fn test_validate_block_header_valid() {
886 use sha2::{Digest, Sha256};
887
888 let header = BlockHeader {
890 version: 1,
891 prev_block_hash: [0; 32],
892 merkle_root: Sha256::digest(b"test merkle root")[..].try_into().unwrap(),
893 timestamp: 1231006505,
894 bits: 0x1d00ffff,
895 nonce: 0,
896 };
897
898 let result = header::validate_block_header(&header, None).unwrap();
899 assert!(result);
900}
901
902#[test]
903fn test_validate_block_header_accepts_version_zero() {
904 let header = BlockHeader {
905 version: 0,
906 prev_block_hash: [0; 32],
907 merkle_root: [1; 32],
908 timestamp: 1231006505,
909 bits: 0x1d00ffff,
910 nonce: 0,
911 };
912
913 let result = header::validate_block_header(&header, None).unwrap();
914 assert!(result);
915}
916
917#[test]
918fn test_validate_block_header_invalid_bits() {
919 let header = BlockHeader {
920 version: 1,
921 prev_block_hash: [0; 32],
922 merkle_root: [0; 32],
923 timestamp: 1231006505,
924 bits: 0, nonce: 0,
926 };
927
928 let result = header::validate_block_header(&header, None).unwrap();
929 assert!(!result);
930}
931
932#[test]
933fn test_is_coinbase_true() {
934 let coinbase_tx = Transaction {
935 version: 1,
936 inputs: vec![TransactionInput {
937 prevout: OutPoint {
938 hash: [0; 32],
939 index: 0xffffffff,
940 },
941 script_sig: vec![],
942 sequence: 0xffffffff,
943 }]
944 .into(),
945 outputs: vec![TransactionOutput {
946 value: 5000000000,
947 script_pubkey: vec![],
948 }]
949 .into(),
950 lock_time: 0,
951 };
952
953 assert!(is_coinbase(&coinbase_tx));
954}
955
956#[test]
957fn test_is_coinbase_false_wrong_hash() {
958 let regular_tx = Transaction {
959 version: 1,
960 inputs: vec![TransactionInput {
961 prevout: OutPoint {
962 hash: [1; 32],
963 index: 0xffffffff,
964 }, script_sig: vec![],
966 sequence: 0xffffffff,
967 }]
968 .into(),
969 outputs: vec![TransactionOutput {
970 value: 5000000000,
971 script_pubkey: vec![],
972 }]
973 .into(),
974 lock_time: 0,
975 };
976
977 assert!(!is_coinbase(®ular_tx));
978}
979
980#[test]
981fn test_is_coinbase_false_wrong_index() {
982 let regular_tx = Transaction {
983 version: 1,
984 inputs: vec![TransactionInput {
985 prevout: OutPoint {
986 hash: [0; 32],
987 index: 0,
988 }, script_sig: vec![],
990 sequence: 0xffffffff,
991 }]
992 .into(),
993 outputs: vec![TransactionOutput {
994 value: 5000000000,
995 script_pubkey: vec![],
996 }]
997 .into(),
998 lock_time: 0,
999 };
1000
1001 assert!(!is_coinbase(®ular_tx));
1002}
1003
1004#[test]
1005fn test_is_coinbase_false_multiple_inputs() {
1006 let regular_tx = Transaction {
1007 version: 1,
1008 inputs: vec![
1009 TransactionInput {
1010 prevout: OutPoint {
1011 hash: [0; 32],
1012 index: 0xffffffff,
1013 },
1014 script_sig: vec![],
1015 sequence: 0xffffffff,
1016 },
1017 TransactionInput {
1018 prevout: OutPoint {
1019 hash: [1; 32],
1020 index: 0,
1021 },
1022 script_sig: vec![],
1023 sequence: 0xffffffff,
1024 },
1025 ]
1026 .into(),
1027 outputs: vec![TransactionOutput {
1028 value: 5000000000,
1029 script_pubkey: vec![],
1030 }]
1031 .into(),
1032 lock_time: 0,
1033 };
1034
1035 assert!(!is_coinbase(®ular_tx));
1036}
1037
1038#[test]
1039fn test_calculate_tx_id() {
1040 let tx = Transaction {
1041 version: 1,
1042 inputs: vec![TransactionInput {
1043 prevout: OutPoint {
1044 hash: [0; 32],
1045 index: 0,
1046 },
1047 script_sig: vec![],
1048 sequence: 0xffffffff,
1049 }]
1050 .into(),
1051 outputs: vec![TransactionOutput {
1052 value: 1000,
1053 script_pubkey: vec![],
1054 }]
1055 .into(),
1056 lock_time: 0,
1057 };
1058
1059 let tx_id = calculate_tx_id(&tx);
1060
1061 assert_eq!(tx_id.len(), 32);
1063
1064 let tx_id2 = calculate_tx_id(&tx);
1066 assert_eq!(tx_id, tx_id2);
1067
1068 let mut tx2 = tx.clone();
1070 tx2.version = 2;
1071 let tx_id3 = calculate_tx_id(&tx2);
1072 assert_ne!(tx_id, tx_id3);
1073}
1074
1075#[test]
1076fn test_calculate_tx_id_different_versions() {
1077 let tx1 = Transaction {
1078 version: 2,
1079 inputs: vec![].into(),
1080 outputs: vec![].into(),
1081 lock_time: 0,
1082 };
1083
1084 let tx2 = Transaction {
1085 version: 1,
1086 inputs: vec![].into(),
1087 outputs: vec![].into(),
1088 lock_time: 0,
1089 };
1090
1091 let id1 = calculate_tx_id(&tx1);
1092 let id2 = calculate_tx_id(&tx2);
1093
1094 assert_ne!(id1, id2);
1096}
1097
1098#[test]
1099fn test_connect_block_empty_transactions() {
1100 let block = Block {
1104 header: BlockHeader {
1105 version: 1,
1106 prev_block_hash: [0; 32],
1107 merkle_root: [0; 32], timestamp: 1231006505,
1109 bits: 0x1d00ffff,
1110 nonce: 0,
1111 },
1112 transactions: vec![].into_boxed_slice(), };
1114
1115 let utxo_set = UtxoSet::default();
1116 let witnesses: Vec<Vec<Witness>> = block
1118 .transactions
1119 .iter()
1120 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
1121 .collect();
1122 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
1123 let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
1124 assert!(result.is_ok());
1126 let (validation_result, _, _undo_log) = result.unwrap();
1127 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
1128}
1129
1130#[test]
1131fn test_connect_block_invalid_coinbase() {
1132 let invalid_coinbase = Transaction {
1133 version: 1,
1134 inputs: vec![TransactionInput {
1135 prevout: OutPoint {
1136 hash: [1; 32],
1137 index: 0,
1138 }, script_sig: vec![],
1140 sequence: 0xffffffff,
1141 }]
1142 .into(),
1143 outputs: vec![TransactionOutput {
1144 value: 5000000000,
1145 script_pubkey: vec![],
1146 }]
1147 .into(),
1148 lock_time: 0,
1149 };
1150
1151 let block = Block {
1152 header: BlockHeader {
1153 version: 1,
1154 prev_block_hash: [0; 32],
1155 merkle_root: [0; 32],
1156 timestamp: 1231006505,
1157 bits: 0x1d00ffff,
1158 nonce: 0,
1159 },
1160 transactions: vec![invalid_coinbase].into_boxed_slice(),
1161 };
1162
1163 let utxo_set = UtxoSet::default();
1164 let witnesses: Vec<Vec<Witness>> = block
1166 .transactions
1167 .iter()
1168 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
1169 .collect();
1170 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
1171 let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
1172 assert!(result.is_ok());
1174 let (validation_result, _, _undo_log) = result.unwrap();
1175 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
1176}
1177
1178#[test]
1179fn test_apply_transaction_insufficient_funds() {
1180 let mut utxo_set = UtxoSet::default();
1181
1182 let prev_outpoint = OutPoint {
1184 hash: [1; 32],
1185 index: 0,
1186 };
1187 let prev_utxo = UTXO {
1188 value: 100, script_pubkey: vec![OP_1].into(),
1190 height: 0,
1191 is_coinbase: false,
1192 };
1193 utxo_set.insert(prev_outpoint, std::sync::Arc::new(prev_utxo));
1194
1195 let tx = Transaction {
1196 version: 1,
1197 inputs: vec![TransactionInput {
1198 prevout: OutPoint {
1199 hash: [1; 32],
1200 index: 0,
1201 },
1202 script_sig: vec![OP_1],
1203 sequence: 0xffffffff,
1204 }]
1205 .into(),
1206 outputs: vec![TransactionOutput {
1207 value: 200, script_pubkey: vec![OP_2],
1209 }]
1210 .into(),
1211 lock_time: 0,
1212 };
1213
1214 let result = apply_transaction(&tx, utxo_set, 1);
1217 assert!(result.is_ok());
1218}
1219
1220#[test]
1221fn test_apply_transaction_missing_utxo() {
1222 let utxo_set = UtxoSet::default(); let tx = Transaction {
1225 version: 1,
1226 inputs: vec![TransactionInput {
1227 prevout: OutPoint {
1228 hash: [1; 32],
1229 index: 0,
1230 },
1231 script_sig: vec![OP_1],
1232 sequence: 0xffffffff,
1233 }]
1234 .into(),
1235 outputs: vec![TransactionOutput {
1236 value: 100,
1237 script_pubkey: vec![OP_2],
1238 }]
1239 .into(),
1240 lock_time: 0,
1241 };
1242
1243 let result = apply_transaction(&tx, utxo_set, 1);
1246 assert!(result.is_ok());
1247}
1248
1249#[test]
1250fn test_validate_block_header_future_timestamp() {
1251 use sha2::{Digest, Sha256};
1252
1253 let header = BlockHeader {
1256 version: 1,
1257 prev_block_hash: [0; 32],
1258 merkle_root: Sha256::digest(b"test merkle root")[..].try_into().unwrap(),
1259 timestamp: 9999999999, bits: 0x1d00ffff,
1261 nonce: 0,
1262 };
1263
1264 let result = header::validate_block_header(&header, None).unwrap();
1266 assert!(result);
1267}
1268
1269#[test]
1270fn test_validate_block_header_zero_timestamp() {
1271 use sha2::{Digest, Sha256};
1272
1273 let header = BlockHeader {
1275 version: 1,
1276 prev_block_hash: [0; 32],
1277 merkle_root: Sha256::digest(b"test merkle root")[..].try_into().unwrap(),
1278 timestamp: 0, bits: 0x1d00ffff,
1280 nonce: 0,
1281 };
1282
1283 let result = header::validate_block_header(&header, None).unwrap();
1285 assert!(!result);
1286}
1287
1288#[test]
1289fn test_connect_block_coinbase_exceeds_subsidy_edge() {
1290 let coinbase_tx = Transaction {
1291 version: 1,
1292 inputs: vec![TransactionInput {
1293 prevout: OutPoint {
1294 hash: [0; 32],
1295 index: 0xffffffff,
1296 },
1297 script_sig: vec![],
1298 sequence: 0xffffffff,
1299 }]
1300 .into(),
1301 outputs: vec![TransactionOutput {
1302 value: 2100000000000000, script_pubkey: vec![],
1304 }]
1305 .into(),
1306 lock_time: 0,
1307 };
1308
1309 let block = Block {
1310 header: BlockHeader {
1311 version: 1,
1312 prev_block_hash: [0; 32],
1313 merkle_root: [0; 32],
1314 timestamp: 1231006505,
1315 bits: 0x1d00ffff,
1316 nonce: 0,
1317 },
1318 transactions: vec![coinbase_tx].into_boxed_slice(),
1319 };
1320
1321 let utxo_set = UtxoSet::default();
1322 let witnesses: Vec<Vec<Witness>> = block
1324 .transactions
1325 .iter()
1326 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
1327 .collect();
1328 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
1329 let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
1330 assert!(result.is_ok());
1332 let (validation_result, _, _undo_log) = result.unwrap();
1333 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
1334}
1335
1336#[test]
1337fn test_connect_block_first_tx_not_coinbase_edge() {
1338 let regular_tx = Transaction {
1339 version: 1,
1340 inputs: vec![TransactionInput {
1341 prevout: OutPoint {
1342 hash: [1; 32],
1343 index: 0,
1344 },
1345 script_sig: vec![OP_1],
1346 sequence: 0xffffffff,
1347 }]
1348 .into(),
1349 outputs: vec![TransactionOutput {
1350 value: 1000,
1351 script_pubkey: vec![OP_2],
1352 }]
1353 .into(),
1354 lock_time: 0,
1355 };
1356
1357 let block = Block {
1358 header: BlockHeader {
1359 version: 1,
1360 prev_block_hash: [0; 32],
1361 merkle_root: [0; 32],
1362 timestamp: 1231006505,
1363 bits: 0x1d00ffff,
1364 nonce: 0,
1365 },
1366 transactions: vec![regular_tx].into_boxed_slice(), };
1368
1369 let utxo_set = UtxoSet::default();
1370 let witnesses: Vec<Vec<Witness>> = block
1372 .transactions
1373 .iter()
1374 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
1375 .collect();
1376 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
1377 let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
1378 assert!(result.is_ok());
1380 let (validation_result, _, _undo_log) = result.unwrap();
1381 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
1382}
1383
1384#[test]
1385fn test_apply_transaction_multiple_inputs() {
1386 let mut utxo_set = UtxoSet::default();
1387
1388 let outpoint1 = OutPoint {
1390 hash: [1; 32],
1391 index: 0,
1392 };
1393 let utxo1 = UTXO {
1394 value: 500,
1395 script_pubkey: vec![OP_1].into(),
1396 height: 0,
1397 is_coinbase: false,
1398 };
1399 utxo_set.insert(outpoint1, std::sync::Arc::new(utxo1));
1400
1401 let outpoint2 = OutPoint {
1402 hash: [2; 32],
1403 index: 0,
1404 };
1405 let utxo2 = UTXO {
1406 value: 300,
1407 script_pubkey: vec![OP_2].into(),
1408 height: 0,
1409 is_coinbase: false,
1410 };
1411 utxo_set.insert(outpoint2, std::sync::Arc::new(utxo2));
1412
1413 let tx = Transaction {
1414 version: 1,
1415 inputs: vec![
1416 TransactionInput {
1417 prevout: OutPoint {
1418 hash: [1; 32],
1419 index: 0,
1420 },
1421 script_sig: vec![OP_1],
1422 sequence: 0xffffffff,
1423 },
1424 TransactionInput {
1425 prevout: OutPoint {
1426 hash: [2; 32],
1427 index: 0,
1428 },
1429 script_sig: vec![OP_2],
1430 sequence: 0xffffffff,
1431 },
1432 ]
1433 .into(),
1434 outputs: vec![TransactionOutput {
1435 value: 700, script_pubkey: vec![OP_3],
1437 }]
1438 .into(),
1439 lock_time: 0,
1440 };
1441
1442 let (new_utxo_set, _undo_entries) = apply_transaction(&tx, utxo_set, 1).unwrap();
1443 assert_eq!(new_utxo_set.len(), 1);
1444}
1445
1446#[test]
1447fn test_apply_transaction_no_outputs() {
1448 let mut utxo_set = UtxoSet::default();
1449
1450 let prev_outpoint = OutPoint {
1451 hash: [1; 32],
1452 index: 0,
1453 };
1454 let prev_utxo = UTXO {
1455 value: 1000,
1456 script_pubkey: vec![OP_1].into(),
1457 height: 0,
1458 is_coinbase: false,
1459 };
1460 utxo_set.insert(prev_outpoint, std::sync::Arc::new(prev_utxo));
1461
1462 let tx = Transaction {
1465 version: 1,
1466 inputs: vec![TransactionInput {
1467 prevout: OutPoint {
1468 hash: [1; 32],
1469 index: 0,
1470 },
1471 script_sig: vec![OP_1],
1472 sequence: 0xffffffff,
1473 }]
1474 .into(),
1475 outputs: vec![].into(), lock_time: 0,
1477 };
1478
1479 let validation_result = crate::transaction::check_transaction(&tx).unwrap();
1482 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
1483
1484 let valid_tx = Transaction {
1486 version: 1,
1487 inputs: vec![TransactionInput {
1488 prevout: OutPoint {
1489 hash: [1; 32],
1490 index: 0,
1491 },
1492 script_sig: vec![OP_1],
1493 sequence: 0xffffffff,
1494 }]
1495 .into(),
1496 outputs: vec![TransactionOutput {
1497 value: 500, script_pubkey: vec![OP_1],
1499 }]
1500 .into(),
1501 lock_time: 0,
1502 };
1503
1504 let (new_utxo_set, _undo_entries) = apply_transaction(&valid_tx, utxo_set, 1).unwrap();
1506 assert_eq!(new_utxo_set.len(), 1);
1508
1509 let output_outpoint = OutPoint {
1511 hash: calculate_tx_id(&valid_tx),
1512 index: 0,
1513 };
1514 assert!(new_utxo_set.contains_key(&output_outpoint));
1515}
1516
1517#[test]
1518fn genesis_coinbase_stays_out_of_the_utxo_set() {
1519 let block = genesis_block(Network::Mainnet);
1520 assert_eq!(
1521 block_header_hash(&block.header),
1522 header::genesis_header_hash(Network::Mainnet)
1523 );
1524 let witnesses: Vec<Vec<Witness>> = vec![vec![Vec::new()]];
1525 let ctx = BlockValidationContext::for_network(Network::Mainnet);
1526 let (result, utxo_set, undo_log) =
1527 connect_block(&block, &witnesses, UtxoSet::default(), 0, &ctx).unwrap();
1528 assert!(matches!(result, ValidationResult::Valid));
1529 assert!(utxo_set.is_empty());
1530 assert!(undo_log.is_empty());
1531}
1532
1533#[test]
1534fn every_network_genesis_leaves_the_utxo_set_and_delta_empty() {
1535 for network in [
1536 Network::Mainnet,
1537 Network::Testnet,
1538 Network::Regtest,
1539 Network::Signet,
1540 Network::Testnet4,
1541 ] {
1542 let block = genesis_block(network);
1543 let witnesses: Vec<Vec<Witness>> = vec![vec![Vec::new()]];
1544 let ctx = BlockValidationContext::for_network(network);
1545 let block_arc = std::sync::Arc::new(block.clone());
1546 let (result, utxo_set, tx_ids, delta, undo_log) = connect_block_ibd_with_undo(
1547 &block,
1548 &witnesses,
1549 UtxoSet::default(),
1550 0,
1551 &ctx,
1552 None,
1553 None,
1554 Some(block_arc),
1555 None,
1556 None,
1557 None,
1558 false,
1559 )
1560 .unwrap();
1561 assert!(utxo_set.is_empty(), "{network:?}");
1562 assert!(delta.is_none(), "{network:?}");
1563 assert!(undo_log.is_empty(), "{network:?}");
1564 match result {
1565 ValidationResult::Valid => assert_eq!(tx_ids.len(), 1, "{network:?}"),
1566 other => panic!("{network:?} {other:?}"),
1567 }
1568 }
1569}
1570
1571#[test]
1572fn genesis_coinbase_is_missing_when_spent_at_maturity() {
1573 use crate::mining::calculate_merkle_root;
1574 let genesis = genesis_block(Network::Mainnet);
1575 let witnesses: Vec<Vec<Witness>> = vec![vec![Vec::new()]];
1576 let ctx = BlockValidationContext::for_network(Network::Mainnet);
1577 let (result, utxo_set, _) =
1578 connect_block(&genesis, &witnesses, UtxoSet::default(), 0, &ctx).unwrap();
1579 assert!(matches!(result, ValidationResult::Valid));
1580 let genesis_txid = calculate_tx_id(&genesis.transactions[0]);
1581
1582 let coinbase = Transaction {
1583 version: 1,
1584 inputs: vec![TransactionInput {
1585 prevout: OutPoint {
1586 hash: [0; 32],
1587 index: 0xffffffff,
1588 },
1589 script_sig: vec![0x01, 0x64],
1590 sequence: 0xffffffff,
1591 }]
1592 .into(),
1593 outputs: vec![TransactionOutput {
1594 value: 5_000_000_000,
1595 script_pubkey: vec![0x51],
1596 }]
1597 .into(),
1598 lock_time: 0,
1599 };
1600 let spend = Transaction {
1601 version: 1,
1602 inputs: vec![TransactionInput {
1603 prevout: OutPoint {
1604 hash: genesis_txid,
1605 index: 0,
1606 },
1607 script_sig: vec![0x51],
1608 sequence: 0xffffffff,
1609 }]
1610 .into(),
1611 outputs: vec![TransactionOutput {
1612 value: 0,
1613 script_pubkey: vec![0x51],
1614 }]
1615 .into(),
1616 lock_time: 0,
1617 };
1618 let merkle_root = calculate_merkle_root(&[coinbase.clone(), spend.clone()]).unwrap();
1619 let mut child = Block {
1620 header: BlockHeader {
1621 version: 1,
1622 prev_block_hash: block_header_hash(&genesis.header),
1623 merkle_root,
1624 timestamp: 1_231_006_506,
1625 bits: 0x1d00ffff,
1626 nonce: 1,
1627 },
1628 transactions: vec![coinbase, spend].into_boxed_slice(),
1629 };
1630 while !crate::pow::check_proof_of_work(&child.header).unwrap_or(false) {
1631 child.header.bits = 0x207fffff;
1632 child.header.nonce = child.header.nonce.wrapping_add(1);
1633 }
1634 let child_witnesses: Vec<Vec<Witness>> = vec![vec![Vec::new()], vec![Vec::new()]];
1635 let (result, utxo_set, undo_log) =
1636 connect_block(&child, &child_witnesses, utxo_set, 100, &ctx).unwrap();
1637 match result {
1638 ValidationResult::Invalid(msg) => {
1639 assert!(
1640 msg.contains("UTXO not found"),
1641 "mature spend of the genesis coinbase must miss the input: {msg}"
1642 );
1643 }
1644 other => panic!("expected a missing genesis input, got {other:?}"),
1645 }
1646 assert!(utxo_set.is_empty());
1647 assert!(undo_log.is_empty());
1648}
1649
1650#[test]
1651fn a_non_genesis_block_at_height_zero_still_adds_its_coinbase() {
1652 use crate::mining::calculate_merkle_root;
1653 let coinbase = Transaction {
1654 version: 1,
1655 inputs: vec![TransactionInput {
1656 prevout: OutPoint {
1657 hash: [0; 32],
1658 index: 0xffffffff,
1659 },
1660 script_sig: vec![0x51, 0x51],
1661 sequence: 0xffffffff,
1662 }]
1663 .into(),
1664 outputs: vec![TransactionOutput {
1665 value: 5_000_000_000,
1666 script_pubkey: vec![0x51],
1667 }]
1668 .into(),
1669 lock_time: 0,
1670 };
1671 let merkle_root = calculate_merkle_root(std::slice::from_ref(&coinbase)).unwrap();
1672 let mut block = Block {
1673 header: BlockHeader {
1674 version: 1,
1675 prev_block_hash: [0; 32],
1676 merkle_root,
1677 timestamp: 1_231_006_505,
1678 bits: 0x207fffff,
1679 nonce: 0,
1680 },
1681 transactions: vec![coinbase].into_boxed_slice(),
1682 };
1683 while !crate::pow::check_proof_of_work(&block.header).unwrap_or(false) {
1684 block.header.nonce = block.header.nonce.wrapping_add(1);
1685 }
1686 let witnesses: Vec<Vec<Witness>> = vec![vec![Vec::new()]];
1687 let ctx = BlockValidationContext::for_network(Network::Mainnet);
1688 let (result, utxo_set, _) =
1689 connect_block(&block, &witnesses, UtxoSet::default(), 0, &ctx).unwrap();
1690 assert!(matches!(result, ValidationResult::Valid));
1691 assert_eq!(utxo_set.len(), 1);
1692}