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