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