1mod apply;
9mod connect;
10mod header;
11mod script_cache;
12pub use apply::{apply_transaction, calculate_tx_id};
13pub use script_cache::{
14 calculate_base_script_flags_for_block_network, calculate_script_flags_for_block_network,
15 get_block_script_flags, get_block_script_verify_flags_core, script_flag_exceptions_lookup,
16 tx_has_nonempty_input_witness, tx_requires_witness_script_flags,
17};
18
19use crate::activation::{ForkActivationTable, IsForkActive};
20use crate::bip113::get_median_time_past;
21use crate::error::Result;
22use crate::segwit::Witness;
23use crate::types::*;
24use blvm_spec_lock::spec_locked;
25#[cfg(feature = "production")]
26use rustc_hash::{FxHashMap, FxHashSet};
27
28#[cfg(test)]
29use crate::opcodes::*;
30#[cfg(test)]
31use crate::transaction::is_coinbase;
32
33#[derive(Debug, Clone)]
41pub struct UtxoDeltaInner<M, S> {
42 pub additions: M,
43 pub deletions: S,
44}
45
46#[cfg(feature = "production")]
47pub type UtxoDelta = UtxoDeltaInner<
48 FxHashMap<OutPoint, std::sync::Arc<UTXO>>,
49 FxHashSet<crate::utxo_overlay::UtxoDeletionKey>,
50>;
51#[cfg(not(feature = "production"))]
52pub type UtxoDelta = UtxoDeltaInner<
53 std::collections::HashMap<OutPoint, std::sync::Arc<UTXO>>,
54 std::collections::HashSet<crate::utxo_overlay::UtxoDeletionKey>,
55>;
56
57#[cfg(feature = "production")]
86#[cfg(all(feature = "production", feature = "rayon"))]
87pub(crate) fn skip_script_exec_cache() -> bool {
88 use std::sync::OnceLock;
89 static CACHED: OnceLock<bool> = OnceLock::new();
90 *CACHED.get_or_init(|| {
91 std::env::var("BLVM_SKIP_SCRIPT_CACHE")
92 .map(|v| v == "1")
93 .unwrap_or(false)
94 })
95}
96
97pub fn get_assume_valid_height() -> u64 {
98 #[cfg(feature = "benchmarking")]
100 {
101 use std::sync::atomic::{AtomicU64, Ordering};
102 static OVERRIDE: AtomicU64 = AtomicU64::new(u64::MAX);
103 let override_val = OVERRIDE.load(Ordering::Relaxed);
104 if override_val != u64::MAX {
105 return override_val;
106 }
107 }
108
109 crate::config::get_assume_valid_height()
110}
111
112#[track_caller]
135#[spec_locked("5.3", "ConnectBlock")]
137pub fn connect_block(
138 block: &Block,
139 witnesses: &[Vec<Witness>],
140 utxo_set: UtxoSet,
141 height: Natural,
142 context: &BlockValidationContext,
143) -> Result<(
144 ValidationResult,
145 UtxoSet,
146 crate::reorganization::BlockUndoLog,
147)> {
148 #[cfg(all(feature = "production", feature = "rayon"))]
149 let block_arc = Some(std::sync::Arc::new(block.clone()));
150 #[cfg(not(all(feature = "production", feature = "rayon")))]
151 let block_arc = None;
152 let (result, new_utxo_set, _tx_ids, undo_log, _delta) = connect::connect_block_inner(
153 block, witnesses, utxo_set, None, height, context, None, None, block_arc, false, None,
154 )?;
155 Ok((result, new_utxo_set, undo_log))
156}
157
158#[cfg(feature = "benchmarking")]
167pub fn connect_block_with_chainwork(
168 block: &Block,
169 witnesses: &[Vec<Witness>],
170 utxo_set: UtxoSet,
171 height: Natural,
172 context: &BlockValidationContext,
173 best_header_chainwork: Option<u128>,
174) -> Result<(
175 ValidationResult,
176 UtxoSet,
177 crate::reorganization::BlockUndoLog,
178)> {
179 #[cfg(all(feature = "production", feature = "rayon"))]
180 let block_arc = Some(std::sync::Arc::new(block.clone()));
181 #[cfg(not(all(feature = "production", feature = "rayon")))]
182 let block_arc = None;
183 let (result, new_utxo_set, _tx_ids, undo_log, _delta) = connect::connect_block_inner(
184 block,
185 witnesses,
186 utxo_set,
187 None,
188 height,
189 context,
190 None,
191 None,
192 block_arc,
193 false,
194 best_header_chainwork,
195 )?;
196 Ok((result, new_utxo_set, undo_log))
197}
198
199#[spec_locked("5.3", "ConnectBlock")]
214pub fn connect_block_ibd<'a>(
215 block: &Block,
216 witnesses: &[Vec<Witness>],
217 utxo_set: UtxoSet,
218 height: Natural,
219 context: &BlockValidationContext,
220 bip30_index: Option<&mut crate::bip_validation::Bip30Index>,
221 precomputed_tx_ids: Option<&'a [Hash]>,
222 block_arc: Option<std::sync::Arc<Block>>,
223 witnesses_arc: Option<&std::sync::Arc<Vec<Vec<Witness>>>>,
224 best_header_chainwork: Option<u128>,
225) -> Result<(
226 ValidationResult,
227 UtxoSet,
228 std::borrow::Cow<'a, [Hash]>,
229 Option<UtxoDelta>,
230)> {
231 let (result, new_utxo_set, tx_ids, _undo_log, utxo_delta) = connect::connect_block_inner(
232 block,
233 witnesses,
234 utxo_set,
235 witnesses_arc,
236 height,
237 context,
238 bip30_index,
239 precomputed_tx_ids,
240 block_arc,
241 true,
242 best_header_chainwork,
243 )?;
244 Ok((result, new_utxo_set, tx_ids, utxo_delta))
245}
246
247fn build_time_context<H: AsRef<BlockHeader>>(
253 recent_headers: Option<&[H]>,
254 network_time: u64,
255) -> Option<crate::types::TimeContext> {
256 recent_headers.map(|headers| {
257 let median_time_past = get_median_time_past(headers);
258 crate::types::TimeContext {
259 network_time,
260 median_time_past,
261 }
262 })
263}
264
265#[derive(Clone)]
270pub struct BlockValidationContext {
271 pub time_context: Option<crate::types::TimeContext>,
273 pub network_time: u64,
275 pub network: crate::types::Network,
277 pub activation: ForkActivationTable,
279 pub bip54_boundary: Option<crate::types::Bip54BoundaryTimestamps>,
281}
282
283impl BlockValidationContext {
284 pub fn from_connect_block_ibd_args<H: AsRef<BlockHeader>>(
286 recent_headers: Option<&[H]>,
287 network_time: u64,
288 network: crate::types::Network,
289 bip54_activation_override: Option<u64>,
290 bip54_boundary: Option<crate::types::Bip54BoundaryTimestamps>,
291 ) -> Self {
292 let time_context = build_time_context(recent_headers, network_time);
293 let activation = ForkActivationTable::from_network_and_bip54_override(
294 network,
295 bip54_activation_override,
296 );
297 Self {
298 time_context,
299 network_time,
300 network,
301 activation,
302 bip54_boundary,
303 }
304 }
305
306 pub fn from_time_context_and_network(
308 time_context: Option<crate::types::TimeContext>,
309 network: crate::types::Network,
310 bip54_boundary: Option<crate::types::Bip54BoundaryTimestamps>,
311 ) -> Self {
312 let network_time = time_context.as_ref().map(|c| c.network_time).unwrap_or(0);
313 let activation = ForkActivationTable::from_network(network);
314 Self {
315 time_context,
316 network_time,
317 network,
318 activation,
319 bip54_boundary,
320 }
321 }
322
323 pub fn for_network(network: crate::types::Network) -> Self {
325 block_validation_context_for_connect_ibd(None::<&[BlockHeader]>, 0, network)
326 }
327}
328
329#[inline]
332pub fn block_validation_context_for_connect_ibd<H: AsRef<BlockHeader>>(
333 recent_headers: Option<&[H]>,
334 network_time: u64,
335 network: Network,
336) -> BlockValidationContext {
337 BlockValidationContext::from_connect_block_ibd_args(
338 recent_headers,
339 network_time,
340 network,
341 None,
342 None,
343 )
344}
345
346impl IsForkActive for BlockValidationContext {
347 #[inline]
348 fn is_fork_active(&self, fork: crate::types::ForkId, height: u64) -> bool {
349 self.activation.is_fork_active(fork, height)
350 }
351}
352
353#[cfg(feature = "production")]
354mod tx_id_pool {
355 use crate::types::{Hash, Transaction};
356 use std::cell::RefCell;
357
358 thread_local! {
359 static TX_BUF: RefCell<Vec<u8>> = RefCell::new(Vec::with_capacity(
360 crate::optimizations::proven_bounds::MAX_TX_SIZE_PROVEN
361 ));
362 }
363
364 pub fn compute_tx_id_with_pool(tx: &Transaction) -> Hash {
366 use crate::crypto::OptimizedSha256;
367 use crate::serialization::transaction::serialize_transaction_into;
368
369 TX_BUF.with(|cell| {
370 let mut buf = cell.borrow_mut();
371 serialize_transaction_into(&mut buf, tx);
372 OptimizedSha256::new().hash256(&buf)
373 })
374 }
375}
376
377#[spec_locked("8.4.1", "ComputeMerkleRoot")]
383pub fn compute_block_tx_ids_spec(block: &Block) -> Vec<Hash> {
384 block.transactions.iter().map(calculate_tx_id).collect()
385}
386
387pub fn compute_block_tx_ids_into(block: &Block, out: &mut Vec<Hash>) {
399 out.clear();
400 out.reserve(block.transactions.len());
401
402 #[cfg(feature = "production")]
403 {
404 out.extend(
405 block
406 .transactions
407 .iter()
408 .map(tx_id_pool::compute_tx_id_with_pool),
409 );
410 }
411
412 #[cfg(not(feature = "production"))]
413 {
414 out.extend(block.transactions.iter().map(calculate_tx_id));
415 }
416}
417
418pub fn compute_block_tx_ids(block: &Block) -> Vec<Hash> {
419 let mut v = Vec::new();
420 compute_block_tx_ids_into(block, &mut v);
421 v
422}
423
424#[test]
426fn compute_block_tx_ids_spec_matches_optimized_paths() {
427 let coinbase = Transaction {
428 version: 1,
429 inputs: vec![TransactionInput {
430 prevout: OutPoint {
431 hash: [0; 32],
432 index: 0xffffffff,
433 },
434 script_sig: vec![0x03, 0x01, 0x00, 0x00],
435 sequence: 0xffffffff,
436 }]
437 .into(),
438 outputs: vec![TransactionOutput {
439 value: 50_000_000_000,
440 script_pubkey: vec![0x51],
441 }]
442 .into(),
443 lock_time: 0,
444 };
445 let tx2 = Transaction {
446 version: 1,
447 inputs: vec![TransactionInput {
448 prevout: OutPoint {
449 hash: [1u8; 32],
450 index: 0,
451 },
452 script_sig: vec![0x51],
453 sequence: 0xffffffff,
454 }]
455 .into(),
456 outputs: vec![TransactionOutput {
457 value: 10_000,
458 script_pubkey: vec![0x51],
459 }]
460 .into(),
461 lock_time: 0,
462 };
463 let block = Block {
464 header: BlockHeader {
465 version: 1,
466 prev_block_hash: [0; 32],
467 merkle_root: [0; 32],
468 timestamp: 1,
469 bits: 0x207fffff,
470 nonce: 0,
471 },
472 transactions: vec![coinbase, tx2].into_boxed_slice(),
473 };
474 assert_eq!(
475 compute_block_tx_ids_spec(&block),
476 compute_block_tx_ids(&block),
477 "ยง8.4.1 spec reference must match compute_block_tx_ids / compute_block_tx_ids_into"
478 );
479}
480
481#[test]
482fn test_connect_block_invalid_header() {
483 let coinbase_tx = Transaction {
484 version: 1,
485 inputs: vec![TransactionInput {
486 prevout: OutPoint {
487 hash: [0; 32],
488 index: 0xffffffff,
489 },
490 script_sig: vec![],
491 sequence: 0xffffffff,
492 }]
493 .into(),
494 outputs: vec![TransactionOutput {
495 value: 5000000000,
496 script_pubkey: vec![],
497 }]
498 .into(),
499 lock_time: 0,
500 };
501
502 let block = Block {
503 header: BlockHeader {
504 version: 0, prev_block_hash: [0; 32],
506 merkle_root: [0; 32],
507 timestamp: 1231006505,
508 bits: 0x1d00ffff,
509 nonce: 0,
510 },
511 transactions: vec![coinbase_tx].into_boxed_slice(),
512 };
513
514 let utxo_set = UtxoSet::default();
515 let witnesses: Vec<Vec<Witness>> = block
516 .transactions
517 .iter()
518 .map(|tx| {
519 (0..tx.inputs.len())
520 .map(|_| Vec::with_capacity(2))
521 .collect()
522 })
523 .collect();
524 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
525 let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
526
527 assert!(matches!(result, ValidationResult::Invalid(_)));
528}
529
530#[test]
531fn test_connect_block_no_transactions() {
532 let block = Block {
533 header: BlockHeader {
534 version: 1,
535 prev_block_hash: [0; 32],
536 merkle_root: [0; 32],
537 timestamp: 1231006505,
538 bits: 0x1d00ffff,
539 nonce: 0,
540 },
541 transactions: vec![].into_boxed_slice(), };
543
544 let utxo_set = UtxoSet::default();
545 let witnesses: Vec<Vec<Witness>> = block
547 .transactions
548 .iter()
549 .map(|tx| {
550 (0..tx.inputs.len())
551 .map(|_| Vec::with_capacity(2))
552 .collect()
553 })
554 .collect();
555 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
556 let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
557
558 assert!(matches!(result, ValidationResult::Invalid(_)));
559}
560
561#[test]
562fn test_connect_block_first_tx_not_coinbase() {
563 let regular_tx = Transaction {
564 version: 1,
565 inputs: vec![TransactionInput {
566 prevout: OutPoint {
567 hash: [1; 32],
568 index: 0,
569 },
570 script_sig: vec![],
571 sequence: 0xffffffff,
572 }]
573 .into(),
574 outputs: vec![TransactionOutput {
575 value: 1000,
576 script_pubkey: vec![],
577 }]
578 .into(),
579 lock_time: 0,
580 };
581
582 let block = Block {
583 header: BlockHeader {
584 version: 1,
585 prev_block_hash: [0; 32],
586 merkle_root: [0; 32],
587 timestamp: 1231006505,
588 bits: 0x1d00ffff,
589 nonce: 0,
590 },
591 transactions: vec![regular_tx].into_boxed_slice(), };
593
594 let utxo_set = UtxoSet::default();
595 let witnesses: Vec<Vec<Witness>> = block
597 .transactions
598 .iter()
599 .map(|tx| {
600 (0..tx.inputs.len())
601 .map(|_| Vec::with_capacity(2))
602 .collect()
603 })
604 .collect();
605 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
606 let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
607
608 assert!(matches!(result, ValidationResult::Invalid(_)));
609}
610
611#[test]
612fn test_connect_block_coinbase_exceeds_subsidy() {
613 let coinbase_tx = Transaction {
614 version: 1,
615 inputs: vec![TransactionInput {
616 prevout: OutPoint {
617 hash: [0; 32],
618 index: 0xffffffff,
619 },
620 script_sig: vec![],
621 sequence: 0xffffffff,
622 }]
623 .into(),
624 outputs: vec![TransactionOutput {
625 value: 6000000000, script_pubkey: vec![],
627 }]
628 .into(),
629 lock_time: 0,
630 };
631
632 let block = Block {
633 header: BlockHeader {
634 version: 1,
635 prev_block_hash: [0; 32],
636 merkle_root: [0; 32],
637 timestamp: 1231006505,
638 bits: 0x1d00ffff,
639 nonce: 0,
640 },
641 transactions: vec![coinbase_tx].into_boxed_slice(),
642 };
643
644 let utxo_set = UtxoSet::default();
645 let witnesses: Vec<Vec<Witness>> = block
647 .transactions
648 .iter()
649 .map(|tx| {
650 (0..tx.inputs.len())
651 .map(|_| Vec::with_capacity(2))
652 .collect()
653 })
654 .collect();
655 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
656 let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
657
658 assert!(matches!(result, ValidationResult::Invalid(_)));
659}
660
661#[test]
662fn test_apply_transaction_regular() {
663 let mut utxo_set = UtxoSet::default();
664
665 let prev_outpoint = OutPoint {
667 hash: [1; 32],
668 index: 0,
669 };
670 let prev_utxo = UTXO {
671 value: 1000,
672 script_pubkey: vec![OP_1].into(), height: 0,
674 is_coinbase: false,
675 };
676 utxo_set.insert(prev_outpoint, std::sync::Arc::new(prev_utxo));
677
678 let regular_tx = Transaction {
679 version: 1,
680 inputs: vec![TransactionInput {
681 prevout: OutPoint {
682 hash: [1; 32],
683 index: 0,
684 },
685 script_sig: vec![OP_1], sequence: 0xffffffff,
687 }]
688 .into(),
689 outputs: vec![TransactionOutput {
690 value: 500,
691 script_pubkey: vec![OP_2], }]
693 .into(),
694 lock_time: 0,
695 };
696
697 let (new_utxo_set, _undo_entries) = apply_transaction(®ular_tx, utxo_set, 1).unwrap();
698
699 assert_eq!(new_utxo_set.len(), 1);
701}
702
703#[test]
704fn test_apply_transaction_multiple_outputs() {
705 let coinbase_tx = Transaction {
706 version: 1,
707 inputs: vec![TransactionInput {
708 prevout: OutPoint {
709 hash: [0; 32],
710 index: 0xffffffff,
711 },
712 script_sig: vec![],
713 sequence: 0xffffffff,
714 }]
715 .into(),
716 outputs: vec![
717 TransactionOutput {
718 value: 2500000000,
719 script_pubkey: vec![OP_1],
720 },
721 TransactionOutput {
722 value: 2500000000,
723 script_pubkey: vec![OP_2],
724 },
725 ]
726 .into(),
727 lock_time: 0,
728 };
729
730 let utxo_set = UtxoSet::default();
731 let (new_utxo_set, _undo_entries) = apply_transaction(&coinbase_tx, utxo_set, 0).unwrap();
732
733 assert_eq!(new_utxo_set.len(), 2);
734}
735
736#[test]
737fn test_validate_block_header_valid() {
738 use sha2::{Digest, Sha256};
739
740 let header = BlockHeader {
742 version: 1,
743 prev_block_hash: [0; 32],
744 merkle_root: Sha256::digest(b"test merkle root")[..].try_into().unwrap(),
745 timestamp: 1231006505,
746 bits: 0x1d00ffff,
747 nonce: 0,
748 };
749
750 let result = header::validate_block_header(&header, None).unwrap();
751 assert!(result);
752}
753
754#[test]
755fn test_validate_block_header_invalid_version() {
756 let header = BlockHeader {
757 version: 0, prev_block_hash: [0; 32],
759 merkle_root: [0; 32],
760 timestamp: 1231006505,
761 bits: 0x1d00ffff,
762 nonce: 0,
763 };
764
765 let result = header::validate_block_header(&header, None).unwrap();
766 assert!(!result);
767}
768
769#[test]
770fn test_validate_block_header_invalid_bits() {
771 let header = BlockHeader {
772 version: 1,
773 prev_block_hash: [0; 32],
774 merkle_root: [0; 32],
775 timestamp: 1231006505,
776 bits: 0, nonce: 0,
778 };
779
780 let result = header::validate_block_header(&header, None).unwrap();
781 assert!(!result);
782}
783
784#[test]
785fn test_is_coinbase_true() {
786 let coinbase_tx = Transaction {
787 version: 1,
788 inputs: vec![TransactionInput {
789 prevout: OutPoint {
790 hash: [0; 32],
791 index: 0xffffffff,
792 },
793 script_sig: vec![],
794 sequence: 0xffffffff,
795 }]
796 .into(),
797 outputs: vec![TransactionOutput {
798 value: 5000000000,
799 script_pubkey: vec![],
800 }]
801 .into(),
802 lock_time: 0,
803 };
804
805 assert!(is_coinbase(&coinbase_tx));
806}
807
808#[test]
809fn test_is_coinbase_false_wrong_hash() {
810 let regular_tx = Transaction {
811 version: 1,
812 inputs: vec![TransactionInput {
813 prevout: OutPoint {
814 hash: [1; 32],
815 index: 0xffffffff,
816 }, script_sig: vec![],
818 sequence: 0xffffffff,
819 }]
820 .into(),
821 outputs: vec![TransactionOutput {
822 value: 5000000000,
823 script_pubkey: vec![],
824 }]
825 .into(),
826 lock_time: 0,
827 };
828
829 assert!(!is_coinbase(®ular_tx));
830}
831
832#[test]
833fn test_is_coinbase_false_wrong_index() {
834 let regular_tx = Transaction {
835 version: 1,
836 inputs: vec![TransactionInput {
837 prevout: OutPoint {
838 hash: [0; 32],
839 index: 0,
840 }, script_sig: vec![],
842 sequence: 0xffffffff,
843 }]
844 .into(),
845 outputs: vec![TransactionOutput {
846 value: 5000000000,
847 script_pubkey: vec![],
848 }]
849 .into(),
850 lock_time: 0,
851 };
852
853 assert!(!is_coinbase(®ular_tx));
854}
855
856#[test]
857fn test_is_coinbase_false_multiple_inputs() {
858 let regular_tx = Transaction {
859 version: 1,
860 inputs: vec![
861 TransactionInput {
862 prevout: OutPoint {
863 hash: [0; 32],
864 index: 0xffffffff,
865 },
866 script_sig: vec![],
867 sequence: 0xffffffff,
868 },
869 TransactionInput {
870 prevout: OutPoint {
871 hash: [1; 32],
872 index: 0,
873 },
874 script_sig: vec![],
875 sequence: 0xffffffff,
876 },
877 ]
878 .into(),
879 outputs: vec![TransactionOutput {
880 value: 5000000000,
881 script_pubkey: vec![],
882 }]
883 .into(),
884 lock_time: 0,
885 };
886
887 assert!(!is_coinbase(®ular_tx));
888}
889
890#[test]
891fn test_calculate_tx_id() {
892 let tx = Transaction {
893 version: 1,
894 inputs: vec![TransactionInput {
895 prevout: OutPoint {
896 hash: [0; 32],
897 index: 0,
898 },
899 script_sig: vec![],
900 sequence: 0xffffffff,
901 }]
902 .into(),
903 outputs: vec![TransactionOutput {
904 value: 1000,
905 script_pubkey: vec![],
906 }]
907 .into(),
908 lock_time: 0,
909 };
910
911 let tx_id = calculate_tx_id(&tx);
912
913 assert_eq!(tx_id.len(), 32);
915
916 let tx_id2 = calculate_tx_id(&tx);
918 assert_eq!(tx_id, tx_id2);
919
920 let mut tx2 = tx.clone();
922 tx2.version = 2;
923 let tx_id3 = calculate_tx_id(&tx2);
924 assert_ne!(tx_id, tx_id3);
925}
926
927#[test]
928fn test_calculate_tx_id_different_versions() {
929 let tx1 = Transaction {
930 version: 2,
931 inputs: vec![].into(),
932 outputs: vec![].into(),
933 lock_time: 0,
934 };
935
936 let tx2 = Transaction {
937 version: 1,
938 inputs: vec![].into(),
939 outputs: vec![].into(),
940 lock_time: 0,
941 };
942
943 let id1 = calculate_tx_id(&tx1);
944 let id2 = calculate_tx_id(&tx2);
945
946 assert_ne!(id1, id2);
948}
949
950#[test]
951fn test_connect_block_empty_transactions() {
952 let block = Block {
956 header: BlockHeader {
957 version: 1,
958 prev_block_hash: [0; 32],
959 merkle_root: [0; 32], timestamp: 1231006505,
961 bits: 0x1d00ffff,
962 nonce: 0,
963 },
964 transactions: vec![].into_boxed_slice(), };
966
967 let utxo_set = UtxoSet::default();
968 let witnesses: Vec<Vec<Witness>> = block
970 .transactions
971 .iter()
972 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
973 .collect();
974 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
975 let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
976 assert!(result.is_ok());
978 let (validation_result, _, _undo_log) = result.unwrap();
979 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
980}
981
982#[test]
983fn test_connect_block_invalid_coinbase() {
984 let invalid_coinbase = Transaction {
985 version: 1,
986 inputs: vec![TransactionInput {
987 prevout: OutPoint {
988 hash: [1; 32],
989 index: 0,
990 }, script_sig: vec![],
992 sequence: 0xffffffff,
993 }]
994 .into(),
995 outputs: vec![TransactionOutput {
996 value: 5000000000,
997 script_pubkey: vec![],
998 }]
999 .into(),
1000 lock_time: 0,
1001 };
1002
1003 let block = Block {
1004 header: BlockHeader {
1005 version: 1,
1006 prev_block_hash: [0; 32],
1007 merkle_root: [0; 32],
1008 timestamp: 1231006505,
1009 bits: 0x1d00ffff,
1010 nonce: 0,
1011 },
1012 transactions: vec![invalid_coinbase].into_boxed_slice(),
1013 };
1014
1015 let utxo_set = UtxoSet::default();
1016 let witnesses: Vec<Vec<Witness>> = block
1018 .transactions
1019 .iter()
1020 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
1021 .collect();
1022 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
1023 let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
1024 assert!(result.is_ok());
1026 let (validation_result, _, _undo_log) = result.unwrap();
1027 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
1028}
1029
1030#[test]
1031fn test_apply_transaction_insufficient_funds() {
1032 let mut utxo_set = UtxoSet::default();
1033
1034 let prev_outpoint = OutPoint {
1036 hash: [1; 32],
1037 index: 0,
1038 };
1039 let prev_utxo = UTXO {
1040 value: 100, script_pubkey: vec![OP_1].into(),
1042 height: 0,
1043 is_coinbase: false,
1044 };
1045 utxo_set.insert(prev_outpoint, std::sync::Arc::new(prev_utxo));
1046
1047 let tx = Transaction {
1048 version: 1,
1049 inputs: vec![TransactionInput {
1050 prevout: OutPoint {
1051 hash: [1; 32],
1052 index: 0,
1053 },
1054 script_sig: vec![OP_1],
1055 sequence: 0xffffffff,
1056 }]
1057 .into(),
1058 outputs: vec![TransactionOutput {
1059 value: 200, script_pubkey: vec![OP_2],
1061 }]
1062 .into(),
1063 lock_time: 0,
1064 };
1065
1066 let result = apply_transaction(&tx, utxo_set, 1);
1068 assert!(result.is_ok());
1069}
1070
1071#[test]
1072fn test_apply_transaction_missing_utxo() {
1073 let utxo_set = UtxoSet::default(); let tx = Transaction {
1076 version: 1,
1077 inputs: vec![TransactionInput {
1078 prevout: OutPoint {
1079 hash: [1; 32],
1080 index: 0,
1081 },
1082 script_sig: vec![OP_1],
1083 sequence: 0xffffffff,
1084 }]
1085 .into(),
1086 outputs: vec![TransactionOutput {
1087 value: 100,
1088 script_pubkey: vec![OP_2],
1089 }]
1090 .into(),
1091 lock_time: 0,
1092 };
1093
1094 let result = apply_transaction(&tx, utxo_set, 1);
1096 assert!(result.is_ok());
1097}
1098
1099#[test]
1100fn test_validate_block_header_future_timestamp() {
1101 use sha2::{Digest, Sha256};
1102
1103 let header = BlockHeader {
1106 version: 1,
1107 prev_block_hash: [0; 32],
1108 merkle_root: Sha256::digest(b"test merkle root")[..].try_into().unwrap(),
1109 timestamp: 9999999999, bits: 0x1d00ffff,
1111 nonce: 0,
1112 };
1113
1114 let result = header::validate_block_header(&header, None).unwrap();
1116 assert!(result);
1117}
1118
1119#[test]
1120fn test_validate_block_header_zero_timestamp() {
1121 use sha2::{Digest, Sha256};
1122
1123 let header = BlockHeader {
1125 version: 1,
1126 prev_block_hash: [0; 32],
1127 merkle_root: Sha256::digest(b"test merkle root")[..].try_into().unwrap(),
1128 timestamp: 0, bits: 0x1d00ffff,
1130 nonce: 0,
1131 };
1132
1133 let result = header::validate_block_header(&header, None).unwrap();
1135 assert!(!result);
1136}
1137
1138#[test]
1139fn test_connect_block_coinbase_exceeds_subsidy_edge() {
1140 let coinbase_tx = Transaction {
1141 version: 1,
1142 inputs: vec![TransactionInput {
1143 prevout: OutPoint {
1144 hash: [0; 32],
1145 index: 0xffffffff,
1146 },
1147 script_sig: vec![],
1148 sequence: 0xffffffff,
1149 }]
1150 .into(),
1151 outputs: vec![TransactionOutput {
1152 value: 2100000000000000, script_pubkey: vec![],
1154 }]
1155 .into(),
1156 lock_time: 0,
1157 };
1158
1159 let block = Block {
1160 header: BlockHeader {
1161 version: 1,
1162 prev_block_hash: [0; 32],
1163 merkle_root: [0; 32],
1164 timestamp: 1231006505,
1165 bits: 0x1d00ffff,
1166 nonce: 0,
1167 },
1168 transactions: vec![coinbase_tx].into_boxed_slice(),
1169 };
1170
1171 let utxo_set = UtxoSet::default();
1172 let witnesses: Vec<Vec<Witness>> = block
1174 .transactions
1175 .iter()
1176 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
1177 .collect();
1178 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
1179 let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
1180 assert!(result.is_ok());
1182 let (validation_result, _, _undo_log) = result.unwrap();
1183 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
1184}
1185
1186#[test]
1187fn test_connect_block_first_tx_not_coinbase_edge() {
1188 let regular_tx = Transaction {
1189 version: 1,
1190 inputs: vec![TransactionInput {
1191 prevout: OutPoint {
1192 hash: [1; 32],
1193 index: 0,
1194 },
1195 script_sig: vec![OP_1],
1196 sequence: 0xffffffff,
1197 }]
1198 .into(),
1199 outputs: vec![TransactionOutput {
1200 value: 1000,
1201 script_pubkey: vec![OP_2],
1202 }]
1203 .into(),
1204 lock_time: 0,
1205 };
1206
1207 let block = Block {
1208 header: BlockHeader {
1209 version: 1,
1210 prev_block_hash: [0; 32],
1211 merkle_root: [0; 32],
1212 timestamp: 1231006505,
1213 bits: 0x1d00ffff,
1214 nonce: 0,
1215 },
1216 transactions: vec![regular_tx].into_boxed_slice(), };
1218
1219 let utxo_set = UtxoSet::default();
1220 let witnesses: Vec<Vec<Witness>> = block
1222 .transactions
1223 .iter()
1224 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
1225 .collect();
1226 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
1227 let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
1228 assert!(result.is_ok());
1230 let (validation_result, _, _undo_log) = result.unwrap();
1231 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
1232}
1233
1234#[test]
1235fn test_apply_transaction_multiple_inputs() {
1236 let mut utxo_set = UtxoSet::default();
1237
1238 let outpoint1 = OutPoint {
1240 hash: [1; 32],
1241 index: 0,
1242 };
1243 let utxo1 = UTXO {
1244 value: 500,
1245 script_pubkey: vec![OP_1].into(),
1246 height: 0,
1247 is_coinbase: false,
1248 };
1249 utxo_set.insert(outpoint1, std::sync::Arc::new(utxo1));
1250
1251 let outpoint2 = OutPoint {
1252 hash: [2; 32],
1253 index: 0,
1254 };
1255 let utxo2 = UTXO {
1256 value: 300,
1257 script_pubkey: vec![OP_2].into(),
1258 height: 0,
1259 is_coinbase: false,
1260 };
1261 utxo_set.insert(outpoint2, std::sync::Arc::new(utxo2));
1262
1263 let tx = Transaction {
1264 version: 1,
1265 inputs: vec![
1266 TransactionInput {
1267 prevout: OutPoint {
1268 hash: [1; 32],
1269 index: 0,
1270 },
1271 script_sig: vec![OP_1],
1272 sequence: 0xffffffff,
1273 },
1274 TransactionInput {
1275 prevout: OutPoint {
1276 hash: [2; 32],
1277 index: 0,
1278 },
1279 script_sig: vec![OP_2],
1280 sequence: 0xffffffff,
1281 },
1282 ]
1283 .into(),
1284 outputs: vec![TransactionOutput {
1285 value: 700, script_pubkey: vec![OP_3],
1287 }]
1288 .into(),
1289 lock_time: 0,
1290 };
1291
1292 let (new_utxo_set, _undo_entries) = apply_transaction(&tx, utxo_set, 1).unwrap();
1293 assert_eq!(new_utxo_set.len(), 1);
1294}
1295
1296#[test]
1297fn test_apply_transaction_no_outputs() {
1298 let mut utxo_set = UtxoSet::default();
1299
1300 let prev_outpoint = OutPoint {
1301 hash: [1; 32],
1302 index: 0,
1303 };
1304 let prev_utxo = UTXO {
1305 value: 1000,
1306 script_pubkey: vec![OP_1].into(),
1307 height: 0,
1308 is_coinbase: false,
1309 };
1310 utxo_set.insert(prev_outpoint, std::sync::Arc::new(prev_utxo));
1311
1312 let tx = Transaction {
1315 version: 1,
1316 inputs: vec![TransactionInput {
1317 prevout: OutPoint {
1318 hash: [1; 32],
1319 index: 0,
1320 },
1321 script_sig: vec![OP_1],
1322 sequence: 0xffffffff,
1323 }]
1324 .into(),
1325 outputs: vec![].into(), lock_time: 0,
1327 };
1328
1329 let validation_result = crate::transaction::check_transaction(&tx).unwrap();
1332 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
1333
1334 let valid_tx = Transaction {
1336 version: 1,
1337 inputs: vec![TransactionInput {
1338 prevout: OutPoint {
1339 hash: [1; 32],
1340 index: 0,
1341 },
1342 script_sig: vec![OP_1],
1343 sequence: 0xffffffff,
1344 }]
1345 .into(),
1346 outputs: vec![TransactionOutput {
1347 value: 500, script_pubkey: vec![OP_1],
1349 }]
1350 .into(),
1351 lock_time: 0,
1352 };
1353
1354 let (new_utxo_set, _undo_entries) = apply_transaction(&valid_tx, utxo_set, 1).unwrap();
1356 assert_eq!(new_utxo_set.len(), 1);
1358
1359 let output_outpoint = OutPoint {
1361 hash: calculate_tx_id(&valid_tx),
1362 index: 0,
1363 };
1364 assert!(new_utxo_set.contains_key(&output_outpoint));
1365}