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")]
167pub fn connect_block_ibd<'a>(
168 block: &Block,
169 witnesses: &[Vec<Witness>],
170 utxo_set: UtxoSet,
171 height: Natural,
172 context: &BlockValidationContext,
173 bip30_index: Option<&mut crate::bip_validation::Bip30Index>,
174 precomputed_tx_ids: Option<&'a [Hash]>,
175 block_arc: Option<std::sync::Arc<Block>>,
176 witnesses_arc: Option<&std::sync::Arc<Vec<Vec<Witness>>>>,
177) -> Result<(
178 ValidationResult,
179 UtxoSet,
180 std::borrow::Cow<'a, [Hash]>,
181 Option<UtxoDelta>,
182)> {
183 let (result, new_utxo_set, tx_ids, _undo_log, utxo_delta) = connect::connect_block_inner(
184 block,
185 witnesses,
186 utxo_set,
187 witnesses_arc,
188 height,
189 context,
190 bip30_index,
191 precomputed_tx_ids,
192 block_arc,
193 true,
194 None,
195 )?;
196 Ok((result, new_utxo_set, tx_ids, utxo_delta))
197}
198
199fn build_time_context<H: AsRef<BlockHeader>>(
205 recent_headers: Option<&[H]>,
206 network_time: u64,
207) -> Option<crate::types::TimeContext> {
208 recent_headers.map(|headers| {
209 let median_time_past = get_median_time_past(headers);
210 crate::types::TimeContext {
211 network_time,
212 median_time_past,
213 }
214 })
215}
216
217#[derive(Clone)]
222pub struct BlockValidationContext {
223 pub time_context: Option<crate::types::TimeContext>,
225 pub network_time: u64,
227 pub network: crate::types::Network,
229 pub activation: ForkActivationTable,
231 pub bip54_boundary: Option<crate::types::Bip54BoundaryTimestamps>,
233}
234
235impl BlockValidationContext {
236 pub fn from_connect_block_ibd_args<H: AsRef<BlockHeader>>(
238 recent_headers: Option<&[H]>,
239 network_time: u64,
240 network: crate::types::Network,
241 bip54_activation_override: Option<u64>,
242 bip54_boundary: Option<crate::types::Bip54BoundaryTimestamps>,
243 ) -> Self {
244 let time_context = build_time_context(recent_headers, network_time);
245 let activation = ForkActivationTable::from_network_and_bip54_override(
246 network,
247 bip54_activation_override,
248 );
249 Self {
250 time_context,
251 network_time,
252 network,
253 activation,
254 bip54_boundary,
255 }
256 }
257
258 pub fn from_time_context_and_network(
260 time_context: Option<crate::types::TimeContext>,
261 network: crate::types::Network,
262 bip54_boundary: Option<crate::types::Bip54BoundaryTimestamps>,
263 ) -> Self {
264 let network_time = time_context.as_ref().map(|c| c.network_time).unwrap_or(0);
265 let activation = ForkActivationTable::from_network(network);
266 Self {
267 time_context,
268 network_time,
269 network,
270 activation,
271 bip54_boundary,
272 }
273 }
274
275 pub fn for_network(network: crate::types::Network) -> Self {
277 block_validation_context_for_connect_ibd(None::<&[BlockHeader]>, 0, network)
278 }
279}
280
281#[inline]
284pub fn block_validation_context_for_connect_ibd<H: AsRef<BlockHeader>>(
285 recent_headers: Option<&[H]>,
286 network_time: u64,
287 network: Network,
288) -> BlockValidationContext {
289 BlockValidationContext::from_connect_block_ibd_args(
290 recent_headers,
291 network_time,
292 network,
293 None,
294 None,
295 )
296}
297
298impl IsForkActive for BlockValidationContext {
299 #[inline]
300 fn is_fork_active(&self, fork: crate::types::ForkId, height: u64) -> bool {
301 self.activation.is_fork_active(fork, height)
302 }
303}
304
305#[cfg(feature = "production")]
306mod tx_id_pool {
307 use crate::types::{Hash, Transaction};
308 use std::cell::RefCell;
309
310 thread_local! {
311 static TX_BUF: RefCell<Vec<u8>> = RefCell::new(Vec::with_capacity(
312 crate::optimizations::proven_bounds::MAX_TX_SIZE_PROVEN
313 ));
314 }
315
316 pub fn compute_tx_id_with_pool(tx: &Transaction) -> Hash {
318 use crate::crypto::OptimizedSha256;
319 use crate::serialization::transaction::serialize_transaction_into;
320
321 TX_BUF.with(|cell| {
322 let mut buf = cell.borrow_mut();
323 serialize_transaction_into(&mut buf, tx);
324 OptimizedSha256::new().hash256(&buf)
325 })
326 }
327}
328
329#[spec_locked("8.4.1", "ComputeMerkleRoot")]
335pub fn compute_block_tx_ids_spec(block: &Block) -> Vec<Hash> {
336 block.transactions.iter().map(calculate_tx_id).collect()
337}
338
339pub fn compute_block_tx_ids_into(block: &Block, out: &mut Vec<Hash>) {
351 out.clear();
352 out.reserve(block.transactions.len());
353
354 #[cfg(feature = "production")]
355 {
356 out.extend(
357 block
358 .transactions
359 .iter()
360 .map(tx_id_pool::compute_tx_id_with_pool),
361 );
362 }
363
364 #[cfg(not(feature = "production"))]
365 {
366 out.extend(block.transactions.iter().map(calculate_tx_id));
367 }
368}
369
370pub fn compute_block_tx_ids(block: &Block) -> Vec<Hash> {
371 let mut v = Vec::new();
372 compute_block_tx_ids_into(block, &mut v);
373 v
374}
375
376#[test]
378fn compute_block_tx_ids_spec_matches_optimized_paths() {
379 let coinbase = Transaction {
380 version: 1,
381 inputs: vec![TransactionInput {
382 prevout: OutPoint {
383 hash: [0; 32].into(),
384 index: 0xffffffff,
385 },
386 script_sig: vec![0x03, 0x01, 0x00, 0x00],
387 sequence: 0xffffffff,
388 }]
389 .into(),
390 outputs: vec![TransactionOutput {
391 value: 50_000_000_000,
392 script_pubkey: vec![0x51].into(),
393 }]
394 .into(),
395 lock_time: 0,
396 };
397 let tx2 = Transaction {
398 version: 1,
399 inputs: vec![TransactionInput {
400 prevout: OutPoint {
401 hash: [1u8; 32].into(),
402 index: 0,
403 },
404 script_sig: vec![0x51],
405 sequence: 0xffffffff,
406 }]
407 .into(),
408 outputs: vec![TransactionOutput {
409 value: 10_000,
410 script_pubkey: vec![0x51].into(),
411 }]
412 .into(),
413 lock_time: 0,
414 };
415 let block = Block {
416 header: BlockHeader {
417 version: 1,
418 prev_block_hash: [0; 32],
419 merkle_root: [0; 32],
420 timestamp: 1,
421 bits: 0x207fffff,
422 nonce: 0,
423 },
424 transactions: vec![coinbase, tx2].into_boxed_slice(),
425 };
426 assert_eq!(
427 compute_block_tx_ids_spec(&block),
428 compute_block_tx_ids(&block),
429 "ยง8.4.1 spec reference must match compute_block_tx_ids / compute_block_tx_ids_into"
430 );
431}
432
433#[test]
434fn test_connect_block_invalid_header() {
435 let coinbase_tx = Transaction {
436 version: 1,
437 inputs: vec![TransactionInput {
438 prevout: OutPoint {
439 hash: [0; 32].into(),
440 index: 0xffffffff,
441 },
442 script_sig: vec![],
443 sequence: 0xffffffff,
444 }]
445 .into(),
446 outputs: vec![TransactionOutput {
447 value: 5000000000,
448 script_pubkey: vec![].into(),
449 }]
450 .into(),
451 lock_time: 0,
452 };
453
454 let block = Block {
455 header: BlockHeader {
456 version: 0, prev_block_hash: [0; 32],
458 merkle_root: [0; 32],
459 timestamp: 1231006505,
460 bits: 0x1d00ffff,
461 nonce: 0,
462 },
463 transactions: vec![coinbase_tx].into_boxed_slice(),
464 };
465
466 let utxo_set = UtxoSet::default();
467 let witnesses: Vec<Vec<Witness>> = block
468 .transactions
469 .iter()
470 .map(|tx| {
471 (0..tx.inputs.len())
472 .map(|_| Vec::with_capacity(2))
473 .collect()
474 })
475 .collect();
476 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
477 let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
478
479 assert!(matches!(result, ValidationResult::Invalid(_)));
480}
481
482#[test]
483fn test_connect_block_no_transactions() {
484 let block = Block {
485 header: BlockHeader {
486 version: 1,
487 prev_block_hash: [0; 32],
488 merkle_root: [0; 32],
489 timestamp: 1231006505,
490 bits: 0x1d00ffff,
491 nonce: 0,
492 },
493 transactions: vec![].into_boxed_slice(), };
495
496 let utxo_set = UtxoSet::default();
497 let witnesses: Vec<Vec<Witness>> = block
499 .transactions
500 .iter()
501 .map(|tx| {
502 (0..tx.inputs.len())
503 .map(|_| Vec::with_capacity(2))
504 .collect()
505 })
506 .collect();
507 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
508 let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
509
510 assert!(matches!(result, ValidationResult::Invalid(_)));
511}
512
513#[test]
514fn test_connect_block_first_tx_not_coinbase() {
515 let regular_tx = Transaction {
516 version: 1,
517 inputs: vec![TransactionInput {
518 prevout: OutPoint {
519 hash: [1; 32].into(),
520 index: 0,
521 },
522 script_sig: vec![],
523 sequence: 0xffffffff,
524 }]
525 .into(),
526 outputs: vec![TransactionOutput {
527 value: 1000,
528 script_pubkey: vec![].into(),
529 }]
530 .into(),
531 lock_time: 0,
532 };
533
534 let block = Block {
535 header: BlockHeader {
536 version: 1,
537 prev_block_hash: [0; 32],
538 merkle_root: [0; 32],
539 timestamp: 1231006505,
540 bits: 0x1d00ffff,
541 nonce: 0,
542 },
543 transactions: vec![regular_tx].into_boxed_slice(), };
545
546 let utxo_set = UtxoSet::default();
547 let witnesses: Vec<Vec<Witness>> = block
549 .transactions
550 .iter()
551 .map(|tx| {
552 (0..tx.inputs.len())
553 .map(|_| Vec::with_capacity(2))
554 .collect()
555 })
556 .collect();
557 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
558 let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
559
560 assert!(matches!(result, ValidationResult::Invalid(_)));
561}
562
563#[test]
564fn test_connect_block_coinbase_exceeds_subsidy() {
565 let coinbase_tx = Transaction {
566 version: 1,
567 inputs: vec![TransactionInput {
568 prevout: OutPoint {
569 hash: [0; 32].into(),
570 index: 0xffffffff,
571 },
572 script_sig: vec![],
573 sequence: 0xffffffff,
574 }]
575 .into(),
576 outputs: vec![TransactionOutput {
577 value: 6000000000, script_pubkey: vec![].into(),
579 }]
580 .into(),
581 lock_time: 0,
582 };
583
584 let block = Block {
585 header: BlockHeader {
586 version: 1,
587 prev_block_hash: [0; 32],
588 merkle_root: [0; 32],
589 timestamp: 1231006505,
590 bits: 0x1d00ffff,
591 nonce: 0,
592 },
593 transactions: vec![coinbase_tx].into_boxed_slice(),
594 };
595
596 let utxo_set = UtxoSet::default();
597 let witnesses: Vec<Vec<Witness>> = block
599 .transactions
600 .iter()
601 .map(|tx| {
602 (0..tx.inputs.len())
603 .map(|_| Vec::with_capacity(2))
604 .collect()
605 })
606 .collect();
607 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
608 let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
609
610 assert!(matches!(result, ValidationResult::Invalid(_)));
611}
612
613#[test]
614fn test_apply_transaction_regular() {
615 let mut utxo_set = UtxoSet::default();
616
617 let prev_outpoint = OutPoint {
619 hash: [1; 32],
620 index: 0,
621 };
622 let prev_utxo = UTXO {
623 value: 1000,
624 script_pubkey: vec![OP_1].into(), height: 0,
626 is_coinbase: false,
627 };
628 utxo_set.insert(prev_outpoint, std::sync::Arc::new(prev_utxo));
629
630 let regular_tx = Transaction {
631 version: 1,
632 inputs: vec![TransactionInput {
633 prevout: OutPoint {
634 hash: [1; 32].into(),
635 index: 0,
636 },
637 script_sig: vec![OP_1], sequence: 0xffffffff,
639 }]
640 .into(),
641 outputs: vec![TransactionOutput {
642 value: 500,
643 script_pubkey: vec![OP_2].into(), }]
645 .into(),
646 lock_time: 0,
647 };
648
649 let (new_utxo_set, _undo_entries) = apply_transaction(®ular_tx, utxo_set, 1).unwrap();
650
651 assert_eq!(new_utxo_set.len(), 1);
653}
654
655#[test]
656fn test_apply_transaction_multiple_outputs() {
657 let coinbase_tx = Transaction {
658 version: 1,
659 inputs: vec![TransactionInput {
660 prevout: OutPoint {
661 hash: [0; 32].into(),
662 index: 0xffffffff,
663 },
664 script_sig: vec![],
665 sequence: 0xffffffff,
666 }]
667 .into(),
668 outputs: vec![
669 TransactionOutput {
670 value: 2500000000,
671 script_pubkey: vec![OP_1].into(),
672 },
673 TransactionOutput {
674 value: 2500000000,
675 script_pubkey: vec![OP_2].into(),
676 },
677 ]
678 .into(),
679 lock_time: 0,
680 };
681
682 let utxo_set = UtxoSet::default();
683 let (new_utxo_set, _undo_entries) = apply_transaction(&coinbase_tx, utxo_set, 0).unwrap();
684
685 assert_eq!(new_utxo_set.len(), 2);
686}
687
688#[test]
689fn test_validate_block_header_valid() {
690 use sha2::{Digest, Sha256};
691
692 let header = BlockHeader {
694 version: 1,
695 prev_block_hash: [0; 32],
696 merkle_root: Sha256::digest(b"test merkle root")[..].try_into().unwrap(),
697 timestamp: 1231006505,
698 bits: 0x1d00ffff,
699 nonce: 0,
700 };
701
702 let result = header::validate_block_header(&header, None).unwrap();
703 assert!(result);
704}
705
706#[test]
707fn test_validate_block_header_invalid_version() {
708 let header = BlockHeader {
709 version: 0, prev_block_hash: [0; 32],
711 merkle_root: [0; 32],
712 timestamp: 1231006505,
713 bits: 0x1d00ffff,
714 nonce: 0,
715 };
716
717 let result = header::validate_block_header(&header, None).unwrap();
718 assert!(!result);
719}
720
721#[test]
722fn test_validate_block_header_invalid_bits() {
723 let header = BlockHeader {
724 version: 1,
725 prev_block_hash: [0; 32],
726 merkle_root: [0; 32],
727 timestamp: 1231006505,
728 bits: 0, nonce: 0,
730 };
731
732 let result = header::validate_block_header(&header, None).unwrap();
733 assert!(!result);
734}
735
736#[test]
737fn test_is_coinbase_true() {
738 let coinbase_tx = Transaction {
739 version: 1,
740 inputs: vec![TransactionInput {
741 prevout: OutPoint {
742 hash: [0; 32].into(),
743 index: 0xffffffff,
744 },
745 script_sig: vec![],
746 sequence: 0xffffffff,
747 }]
748 .into(),
749 outputs: vec![TransactionOutput {
750 value: 5000000000,
751 script_pubkey: vec![].into(),
752 }]
753 .into(),
754 lock_time: 0,
755 };
756
757 assert!(is_coinbase(&coinbase_tx));
758}
759
760#[test]
761fn test_is_coinbase_false_wrong_hash() {
762 let regular_tx = Transaction {
763 version: 1,
764 inputs: vec![TransactionInput {
765 prevout: OutPoint {
766 hash: [1; 32].into(),
767 index: 0xffffffff,
768 }, script_sig: vec![],
770 sequence: 0xffffffff,
771 }]
772 .into(),
773 outputs: vec![TransactionOutput {
774 value: 5000000000,
775 script_pubkey: vec![].into(),
776 }]
777 .into(),
778 lock_time: 0,
779 };
780
781 assert!(!is_coinbase(®ular_tx));
782}
783
784#[test]
785fn test_is_coinbase_false_wrong_index() {
786 let regular_tx = Transaction {
787 version: 1,
788 inputs: vec![TransactionInput {
789 prevout: OutPoint {
790 hash: [0; 32].into(),
791 index: 0,
792 }, script_sig: vec![],
794 sequence: 0xffffffff,
795 }]
796 .into(),
797 outputs: vec![TransactionOutput {
798 value: 5000000000,
799 script_pubkey: vec![].into(),
800 }]
801 .into(),
802 lock_time: 0,
803 };
804
805 assert!(!is_coinbase(®ular_tx));
806}
807
808#[test]
809fn test_is_coinbase_false_multiple_inputs() {
810 let regular_tx = Transaction {
811 version: 1,
812 inputs: vec![
813 TransactionInput {
814 prevout: OutPoint {
815 hash: [0; 32].into(),
816 index: 0xffffffff,
817 },
818 script_sig: vec![],
819 sequence: 0xffffffff,
820 },
821 TransactionInput {
822 prevout: OutPoint {
823 hash: [1; 32],
824 index: 0,
825 },
826 script_sig: vec![],
827 sequence: 0xffffffff,
828 },
829 ]
830 .into(),
831 outputs: vec![TransactionOutput {
832 value: 5000000000,
833 script_pubkey: vec![].into(),
834 }]
835 .into(),
836 lock_time: 0,
837 };
838
839 assert!(!is_coinbase(®ular_tx));
840}
841
842#[test]
843fn test_calculate_tx_id() {
844 let tx = Transaction {
845 version: 1,
846 inputs: vec![TransactionInput {
847 prevout: OutPoint {
848 hash: [0; 32].into(),
849 index: 0,
850 },
851 script_sig: vec![],
852 sequence: 0xffffffff,
853 }]
854 .into(),
855 outputs: vec![TransactionOutput {
856 value: 1000,
857 script_pubkey: vec![].into(),
858 }]
859 .into(),
860 lock_time: 0,
861 };
862
863 let tx_id = calculate_tx_id(&tx);
864
865 assert_eq!(tx_id.len(), 32);
867
868 let tx_id2 = calculate_tx_id(&tx);
870 assert_eq!(tx_id, tx_id2);
871
872 let mut tx2 = tx.clone();
874 tx2.version = 2;
875 let tx_id3 = calculate_tx_id(&tx2);
876 assert_ne!(tx_id, tx_id3);
877}
878
879#[test]
880fn test_calculate_tx_id_different_versions() {
881 let tx1 = Transaction {
882 version: 2,
883 inputs: vec![].into(),
884 outputs: vec![].into(),
885 lock_time: 0,
886 };
887
888 let tx2 = Transaction {
889 version: 1,
890 inputs: vec![].into(),
891 outputs: vec![].into(),
892 lock_time: 0,
893 };
894
895 let id1 = calculate_tx_id(&tx1);
896 let id2 = calculate_tx_id(&tx2);
897
898 assert_ne!(id1, id2);
900}
901
902#[test]
903fn test_connect_block_empty_transactions() {
904 let block = Block {
908 header: BlockHeader {
909 version: 1,
910 prev_block_hash: [0; 32],
911 merkle_root: [0; 32], timestamp: 1231006505,
913 bits: 0x1d00ffff,
914 nonce: 0,
915 },
916 transactions: vec![].into_boxed_slice(), };
918
919 let utxo_set = UtxoSet::default();
920 let witnesses: Vec<Vec<Witness>> = block
922 .transactions
923 .iter()
924 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
925 .collect();
926 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
927 let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
928 assert!(result.is_ok());
930 let (validation_result, _, _undo_log) = result.unwrap();
931 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
932}
933
934#[test]
935fn test_connect_block_invalid_coinbase() {
936 let invalid_coinbase = Transaction {
937 version: 1,
938 inputs: vec![TransactionInput {
939 prevout: OutPoint {
940 hash: [1; 32].into(),
941 index: 0,
942 }, script_sig: vec![],
944 sequence: 0xffffffff,
945 }]
946 .into(),
947 outputs: vec![TransactionOutput {
948 value: 5000000000,
949 script_pubkey: vec![].into(),
950 }]
951 .into(),
952 lock_time: 0,
953 };
954
955 let block = Block {
956 header: BlockHeader {
957 version: 1,
958 prev_block_hash: [0; 32],
959 merkle_root: [0; 32],
960 timestamp: 1231006505,
961 bits: 0x1d00ffff,
962 nonce: 0,
963 },
964 transactions: vec![invalid_coinbase].into_boxed_slice(),
965 };
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_apply_transaction_insufficient_funds() {
984 let mut utxo_set = UtxoSet::default();
985
986 let prev_outpoint = OutPoint {
988 hash: [1; 32],
989 index: 0,
990 };
991 let prev_utxo = UTXO {
992 value: 100, script_pubkey: vec![OP_1].into(),
994 height: 0,
995 is_coinbase: false,
996 };
997 utxo_set.insert(prev_outpoint, std::sync::Arc::new(prev_utxo));
998
999 let tx = Transaction {
1000 version: 1,
1001 inputs: vec![TransactionInput {
1002 prevout: OutPoint {
1003 hash: [1; 32].into(),
1004 index: 0,
1005 },
1006 script_sig: vec![OP_1],
1007 sequence: 0xffffffff,
1008 }]
1009 .into(),
1010 outputs: vec![TransactionOutput {
1011 value: 200, script_pubkey: vec![OP_2].into(),
1013 }]
1014 .into(),
1015 lock_time: 0,
1016 };
1017
1018 let result = apply_transaction(&tx, utxo_set, 1);
1020 assert!(result.is_ok());
1021}
1022
1023#[test]
1024fn test_apply_transaction_missing_utxo() {
1025 let utxo_set = UtxoSet::default(); let tx = Transaction {
1028 version: 1,
1029 inputs: vec![TransactionInput {
1030 prevout: OutPoint {
1031 hash: [1; 32].into(),
1032 index: 0,
1033 },
1034 script_sig: vec![OP_1],
1035 sequence: 0xffffffff,
1036 }]
1037 .into(),
1038 outputs: vec![TransactionOutput {
1039 value: 100,
1040 script_pubkey: vec![OP_2].into(),
1041 }]
1042 .into(),
1043 lock_time: 0,
1044 };
1045
1046 let result = apply_transaction(&tx, utxo_set, 1);
1048 assert!(result.is_ok());
1049}
1050
1051#[test]
1052fn test_validate_block_header_future_timestamp() {
1053 use sha2::{Digest, Sha256};
1054
1055 let header = BlockHeader {
1058 version: 1,
1059 prev_block_hash: [0; 32],
1060 merkle_root: Sha256::digest(b"test merkle root")[..].try_into().unwrap(),
1061 timestamp: 9999999999, bits: 0x1d00ffff,
1063 nonce: 0,
1064 };
1065
1066 let result = header::validate_block_header(&header, None).unwrap();
1068 assert!(result);
1069}
1070
1071#[test]
1072fn test_validate_block_header_zero_timestamp() {
1073 use sha2::{Digest, Sha256};
1074
1075 let header = BlockHeader {
1077 version: 1,
1078 prev_block_hash: [0; 32],
1079 merkle_root: Sha256::digest(b"test merkle root")[..].try_into().unwrap(),
1080 timestamp: 0, bits: 0x1d00ffff,
1082 nonce: 0,
1083 };
1084
1085 let result = header::validate_block_header(&header, None).unwrap();
1087 assert!(!result);
1088}
1089
1090#[test]
1091fn test_connect_block_coinbase_exceeds_subsidy_edge() {
1092 let coinbase_tx = Transaction {
1093 version: 1,
1094 inputs: vec![TransactionInput {
1095 prevout: OutPoint {
1096 hash: [0; 32].into(),
1097 index: 0xffffffff,
1098 },
1099 script_sig: vec![],
1100 sequence: 0xffffffff,
1101 }]
1102 .into(),
1103 outputs: vec![TransactionOutput {
1104 value: 2100000000000000, script_pubkey: vec![].into(),
1106 }]
1107 .into(),
1108 lock_time: 0,
1109 };
1110
1111 let block = Block {
1112 header: BlockHeader {
1113 version: 1,
1114 prev_block_hash: [0; 32],
1115 merkle_root: [0; 32],
1116 timestamp: 1231006505,
1117 bits: 0x1d00ffff,
1118 nonce: 0,
1119 },
1120 transactions: vec![coinbase_tx].into_boxed_slice(),
1121 };
1122
1123 let utxo_set = UtxoSet::default();
1124 let witnesses: Vec<Vec<Witness>> = block
1126 .transactions
1127 .iter()
1128 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
1129 .collect();
1130 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
1131 let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
1132 assert!(result.is_ok());
1134 let (validation_result, _, _undo_log) = result.unwrap();
1135 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
1136}
1137
1138#[test]
1139fn test_connect_block_first_tx_not_coinbase_edge() {
1140 let regular_tx = Transaction {
1141 version: 1,
1142 inputs: vec![TransactionInput {
1143 prevout: OutPoint {
1144 hash: [1; 32].into(),
1145 index: 0,
1146 },
1147 script_sig: vec![OP_1],
1148 sequence: 0xffffffff,
1149 }]
1150 .into(),
1151 outputs: vec![TransactionOutput {
1152 value: 1000,
1153 script_pubkey: vec![OP_2].into(),
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![regular_tx].into_boxed_slice(), };
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_apply_transaction_multiple_inputs() {
1188 let mut utxo_set = UtxoSet::default();
1189
1190 let outpoint1 = OutPoint {
1192 hash: [1; 32],
1193 index: 0,
1194 };
1195 let utxo1 = UTXO {
1196 value: 500,
1197 script_pubkey: vec![OP_1].into(),
1198 height: 0,
1199 is_coinbase: false,
1200 };
1201 utxo_set.insert(outpoint1, std::sync::Arc::new(utxo1));
1202
1203 let outpoint2 = OutPoint {
1204 hash: [2; 32],
1205 index: 0,
1206 };
1207 let utxo2 = UTXO {
1208 value: 300,
1209 script_pubkey: vec![OP_2].into(),
1210 height: 0,
1211 is_coinbase: false,
1212 };
1213 utxo_set.insert(outpoint2, std::sync::Arc::new(utxo2));
1214
1215 let tx = Transaction {
1216 version: 1,
1217 inputs: vec![
1218 TransactionInput {
1219 prevout: OutPoint {
1220 hash: [1; 32].into(),
1221 index: 0,
1222 },
1223 script_sig: vec![OP_1],
1224 sequence: 0xffffffff,
1225 },
1226 TransactionInput {
1227 prevout: OutPoint {
1228 hash: [2; 32],
1229 index: 0,
1230 },
1231 script_sig: vec![OP_2],
1232 sequence: 0xffffffff,
1233 },
1234 ]
1235 .into(),
1236 outputs: vec![TransactionOutput {
1237 value: 700, script_pubkey: vec![OP_3].into(),
1239 }]
1240 .into(),
1241 lock_time: 0,
1242 };
1243
1244 let (new_utxo_set, _undo_entries) = apply_transaction(&tx, utxo_set, 1).unwrap();
1245 assert_eq!(new_utxo_set.len(), 1);
1246}
1247
1248#[test]
1249fn test_apply_transaction_no_outputs() {
1250 let mut utxo_set = UtxoSet::default();
1251
1252 let prev_outpoint = OutPoint {
1253 hash: [1; 32],
1254 index: 0,
1255 };
1256 let prev_utxo = UTXO {
1257 value: 1000,
1258 script_pubkey: vec![OP_1].into(),
1259 height: 0,
1260 is_coinbase: false,
1261 };
1262 utxo_set.insert(prev_outpoint, std::sync::Arc::new(prev_utxo));
1263
1264 let tx = Transaction {
1267 version: 1,
1268 inputs: vec![TransactionInput {
1269 prevout: OutPoint {
1270 hash: [1; 32].into(),
1271 index: 0,
1272 },
1273 script_sig: vec![OP_1],
1274 sequence: 0xffffffff,
1275 }]
1276 .into(),
1277 outputs: vec![].into(), lock_time: 0,
1279 };
1280
1281 let validation_result = crate::transaction::check_transaction(&tx).unwrap();
1284 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
1285
1286 let valid_tx = Transaction {
1288 version: 1,
1289 inputs: vec![TransactionInput {
1290 prevout: OutPoint {
1291 hash: [1; 32].into(),
1292 index: 0,
1293 },
1294 script_sig: vec![OP_1],
1295 sequence: 0xffffffff,
1296 }]
1297 .into(),
1298 outputs: vec![TransactionOutput {
1299 value: 500, script_pubkey: vec![OP_1].into(),
1301 }]
1302 .into(),
1303 lock_time: 0,
1304 };
1305
1306 let (new_utxo_set, _undo_entries) = apply_transaction(&valid_tx, utxo_set, 1).unwrap();
1308 assert_eq!(new_utxo_set.len(), 1);
1310
1311 let output_outpoint = OutPoint {
1313 hash: calculate_tx_id(&valid_tx),
1314 index: 0,
1315 };
1316 assert!(new_utxo_set.contains_key(&output_outpoint));
1317}