1mod apply;
9mod connect;
10mod header;
11mod script_cache;
12pub use apply::{apply_transaction, calculate_tx_id};
13pub use header::{block_header_hash, validate_prev_block_hash};
14pub use script_cache::{
15 calculate_base_script_flags_for_block_network, calculate_script_flags_for_block_network,
16 get_block_script_flags, get_block_script_verify_flags_core, script_flag_exceptions_lookup,
17 tx_has_nonempty_input_witness, tx_requires_witness_script_flags,
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", "ConnectBlock")]
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#[cfg(feature = "benchmarking")]
168pub fn connect_block_with_chainwork(
169 block: &Block,
170 witnesses: &[Vec<Witness>],
171 utxo_set: UtxoSet,
172 height: Natural,
173 context: &BlockValidationContext,
174 best_header_chainwork: Option<u128>,
175) -> Result<(
176 ValidationResult,
177 UtxoSet,
178 crate::reorganization::BlockUndoLog,
179)> {
180 #[cfg(all(feature = "production", feature = "rayon"))]
181 let block_arc = Some(std::sync::Arc::new(block.clone()));
182 #[cfg(not(all(feature = "production", feature = "rayon")))]
183 let block_arc = None;
184 let (result, new_utxo_set, _tx_ids, undo_log, _delta) = connect::connect_block_inner(
185 block,
186 witnesses,
187 utxo_set,
188 None,
189 height,
190 context,
191 None,
192 None,
193 block_arc,
194 false,
195 best_header_chainwork,
196 )?;
197 Ok((result, new_utxo_set, undo_log))
198}
199
200#[spec_locked("5.3", "ConnectBlock")]
215pub fn connect_block_ibd<'a>(
216 block: &Block,
217 witnesses: &[Vec<Witness>],
218 utxo_set: UtxoSet,
219 height: Natural,
220 context: &BlockValidationContext,
221 bip30_index: Option<&mut crate::bip_validation::Bip30Index>,
222 precomputed_tx_ids: Option<&'a [Hash]>,
223 block_arc: Option<std::sync::Arc<Block>>,
224 witnesses_arc: Option<&std::sync::Arc<Vec<Vec<Witness>>>>,
225 best_header_chainwork: Option<u128>,
226) -> Result<(
227 ValidationResult,
228 UtxoSet,
229 std::borrow::Cow<'a, [Hash]>,
230 Option<UtxoDelta>,
231)> {
232 let (result, new_utxo_set, tx_ids, _undo_log, utxo_delta) = connect::connect_block_inner(
233 block,
234 witnesses,
235 utxo_set,
236 witnesses_arc,
237 height,
238 context,
239 bip30_index,
240 precomputed_tx_ids,
241 block_arc,
242 true,
243 best_header_chainwork,
244 )?;
245 Ok((result, new_utxo_set, tx_ids, utxo_delta))
246}
247
248#[spec_locked("5.3", "ConnectBlock")]
250pub fn connect_block_ibd_with_undo<'a>(
251 block: &Block,
252 witnesses: &[Vec<Witness>],
253 utxo_set: UtxoSet,
254 height: Natural,
255 context: &BlockValidationContext,
256 bip30_index: Option<&mut crate::bip_validation::Bip30Index>,
257 precomputed_tx_ids: Option<&'a [Hash]>,
258 block_arc: Option<std::sync::Arc<Block>>,
259 witnesses_arc: Option<&std::sync::Arc<Vec<Vec<Witness>>>>,
260 best_header_chainwork: Option<u128>,
261) -> Result<(
262 ValidationResult,
263 UtxoSet,
264 std::borrow::Cow<'a, [Hash]>,
265 Option<UtxoDelta>,
266 crate::reorganization::BlockUndoLog,
267)> {
268 let (result, new_utxo_set, tx_ids, undo_log, utxo_delta) = connect::connect_block_inner(
269 block,
270 witnesses,
271 utxo_set,
272 witnesses_arc,
273 height,
274 context,
275 bip30_index,
276 precomputed_tx_ids,
277 block_arc,
278 true,
279 best_header_chainwork,
280 )?;
281 Ok((result, new_utxo_set, tx_ids, utxo_delta, undo_log))
282}
283
284fn build_time_context<H: AsRef<BlockHeader>>(
290 recent_headers: Option<&[H]>,
291 network_time: u64,
292) -> Option<crate::types::TimeContext> {
293 recent_headers.map(|headers| {
294 let median_time_past = get_median_time_past(headers);
295 crate::types::TimeContext {
296 network_time,
297 median_time_past,
298 }
299 })
300}
301
302#[derive(Clone)]
307pub struct BlockValidationContext {
308 pub time_context: Option<crate::types::TimeContext>,
310 pub network_time: u64,
312 pub network: crate::types::Network,
314 pub activation: ForkActivationTable,
316 pub bip54_boundary: Option<crate::types::Bip54BoundaryTimestamps>,
318 pub signet_challenge: Option<ByteString>,
320}
321
322impl BlockValidationContext {
323 pub fn from_connect_block_ibd_args<H: AsRef<BlockHeader>>(
325 recent_headers: Option<&[H]>,
326 network_time: u64,
327 network: crate::types::Network,
328 bip54_activation_override: Option<u64>,
329 bip54_boundary: Option<crate::types::Bip54BoundaryTimestamps>,
330 ) -> Self {
331 let time_context = build_time_context(recent_headers, network_time);
332 let activation = ForkActivationTable::from_network_and_bip54_override(
333 network,
334 bip54_activation_override,
335 );
336 Self {
337 time_context,
338 network_time,
339 network,
340 activation,
341 bip54_boundary,
342 signet_challenge: None,
343 }
344 }
345
346 pub fn from_time_context_and_network(
348 time_context: Option<crate::types::TimeContext>,
349 network: crate::types::Network,
350 bip54_boundary: Option<crate::types::Bip54BoundaryTimestamps>,
351 ) -> Self {
352 let network_time = time_context.as_ref().map(|c| c.network_time).unwrap_or(0);
353 let activation = ForkActivationTable::from_network(network);
354 Self {
355 time_context,
356 network_time,
357 network,
358 activation,
359 bip54_boundary,
360 signet_challenge: None,
361 }
362 }
363
364 pub fn for_network(network: crate::types::Network) -> Self {
366 block_validation_context_for_connect_ibd(None::<&[BlockHeader]>, 0, network)
367 }
368}
369
370#[inline]
373pub fn block_validation_context_for_connect_ibd<H: AsRef<BlockHeader>>(
374 recent_headers: Option<&[H]>,
375 network_time: u64,
376 network: Network,
377) -> BlockValidationContext {
378 BlockValidationContext::from_connect_block_ibd_args(
379 recent_headers,
380 network_time,
381 network,
382 None,
383 None,
384 )
385}
386
387impl IsForkActive for BlockValidationContext {
388 #[inline]
389 fn is_fork_active(&self, fork: crate::types::ForkId, height: u64) -> bool {
390 self.activation.is_fork_active(fork, height)
391 }
392}
393
394#[cfg(feature = "production")]
395mod tx_id_pool {
396 use crate::types::{Hash, Transaction};
397 use std::cell::RefCell;
398
399 thread_local! {
400 static TX_BUF: RefCell<Vec<u8>> = RefCell::new(Vec::with_capacity(
401 crate::optimizations::proven_bounds::MAX_TX_SIZE_PROVEN
402 ));
403 }
404
405 pub fn compute_tx_id_with_pool(tx: &Transaction) -> Hash {
407 use crate::crypto::OptimizedSha256;
408 use crate::serialization::transaction::serialize_transaction_into;
409
410 TX_BUF.with(|cell| {
411 let mut buf = cell.borrow_mut();
412 serialize_transaction_into(&mut buf, tx);
413 OptimizedSha256::new().hash256(&buf)
414 })
415 }
416}
417
418#[spec_locked("8.4.1", "ComputeMerkleRoot")]
424pub fn compute_block_tx_ids_spec(block: &Block) -> Vec<Hash> {
425 block.transactions.iter().map(calculate_tx_id).collect()
426}
427
428pub fn compute_block_tx_ids_into(block: &Block, out: &mut Vec<Hash>) {
440 out.clear();
441 out.reserve(block.transactions.len());
442
443 #[cfg(feature = "production")]
444 {
445 out.extend(
446 block
447 .transactions
448 .iter()
449 .map(tx_id_pool::compute_tx_id_with_pool),
450 );
451 }
452
453 #[cfg(not(feature = "production"))]
454 {
455 out.extend(block.transactions.iter().map(calculate_tx_id));
456 }
457}
458
459pub fn compute_block_tx_ids(block: &Block) -> Vec<Hash> {
460 let mut v = Vec::new();
461 compute_block_tx_ids_into(block, &mut v);
462 v
463}
464
465#[test]
467fn compute_block_tx_ids_spec_matches_optimized_paths() {
468 let coinbase = Transaction {
469 version: 1,
470 inputs: vec![TransactionInput {
471 prevout: OutPoint {
472 hash: [0; 32],
473 index: 0xffffffff,
474 },
475 script_sig: vec![0x03, 0x01, 0x00, 0x00],
476 sequence: 0xffffffff,
477 }]
478 .into(),
479 outputs: vec![TransactionOutput {
480 value: 50_000_000_000,
481 script_pubkey: vec![0x51],
482 }]
483 .into(),
484 lock_time: 0,
485 };
486 let tx2 = Transaction {
487 version: 1,
488 inputs: vec![TransactionInput {
489 prevout: OutPoint {
490 hash: [1u8; 32],
491 index: 0,
492 },
493 script_sig: vec![0x51],
494 sequence: 0xffffffff,
495 }]
496 .into(),
497 outputs: vec![TransactionOutput {
498 value: 10_000,
499 script_pubkey: vec![0x51],
500 }]
501 .into(),
502 lock_time: 0,
503 };
504 let block = Block {
505 header: BlockHeader {
506 version: 1,
507 prev_block_hash: [0; 32],
508 merkle_root: [0; 32],
509 timestamp: 1,
510 bits: 0x207fffff,
511 nonce: 0,
512 },
513 transactions: vec![coinbase, tx2].into_boxed_slice(),
514 };
515 assert_eq!(
516 compute_block_tx_ids_spec(&block),
517 compute_block_tx_ids(&block),
518 "ยง8.4.1 spec reference must match compute_block_tx_ids / compute_block_tx_ids_into"
519 );
520}
521
522#[test]
523fn test_connect_block_invalid_header() {
524 let coinbase_tx = Transaction {
525 version: 1,
526 inputs: vec![TransactionInput {
527 prevout: OutPoint {
528 hash: [0; 32],
529 index: 0xffffffff,
530 },
531 script_sig: vec![],
532 sequence: 0xffffffff,
533 }]
534 .into(),
535 outputs: vec![TransactionOutput {
536 value: 5000000000,
537 script_pubkey: vec![],
538 }]
539 .into(),
540 lock_time: 0,
541 };
542
543 let block = Block {
544 header: BlockHeader {
545 version: 0, prev_block_hash: [0; 32],
547 merkle_root: [0; 32],
548 timestamp: 1231006505,
549 bits: 0x1d00ffff,
550 nonce: 0,
551 },
552 transactions: vec![coinbase_tx].into_boxed_slice(),
553 };
554
555 let utxo_set = UtxoSet::default();
556 let witnesses: Vec<Vec<Witness>> = block
557 .transactions
558 .iter()
559 .map(|tx| {
560 (0..tx.inputs.len())
561 .map(|_| Vec::with_capacity(2))
562 .collect()
563 })
564 .collect();
565 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
566 let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
567
568 assert!(matches!(result, ValidationResult::Invalid(_)));
569}
570
571#[test]
572fn test_connect_block_no_transactions() {
573 let block = Block {
574 header: BlockHeader {
575 version: 1,
576 prev_block_hash: [0; 32],
577 merkle_root: [0; 32],
578 timestamp: 1231006505,
579 bits: 0x1d00ffff,
580 nonce: 0,
581 },
582 transactions: vec![].into_boxed_slice(), };
584
585 let utxo_set = UtxoSet::default();
586 let witnesses: Vec<Vec<Witness>> = block
588 .transactions
589 .iter()
590 .map(|tx| {
591 (0..tx.inputs.len())
592 .map(|_| Vec::with_capacity(2))
593 .collect()
594 })
595 .collect();
596 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
597 let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
598
599 assert!(matches!(result, ValidationResult::Invalid(_)));
600}
601
602#[test]
603fn test_connect_block_first_tx_not_coinbase() {
604 let regular_tx = Transaction {
605 version: 1,
606 inputs: vec![TransactionInput {
607 prevout: OutPoint {
608 hash: [1; 32],
609 index: 0,
610 },
611 script_sig: vec![],
612 sequence: 0xffffffff,
613 }]
614 .into(),
615 outputs: vec![TransactionOutput {
616 value: 1000,
617 script_pubkey: vec![],
618 }]
619 .into(),
620 lock_time: 0,
621 };
622
623 let block = Block {
624 header: BlockHeader {
625 version: 1,
626 prev_block_hash: [0; 32],
627 merkle_root: [0; 32],
628 timestamp: 1231006505,
629 bits: 0x1d00ffff,
630 nonce: 0,
631 },
632 transactions: vec![regular_tx].into_boxed_slice(), };
634
635 let utxo_set = UtxoSet::default();
636 let witnesses: Vec<Vec<Witness>> = block
638 .transactions
639 .iter()
640 .map(|tx| {
641 (0..tx.inputs.len())
642 .map(|_| Vec::with_capacity(2))
643 .collect()
644 })
645 .collect();
646 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
647 let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
648
649 assert!(matches!(result, ValidationResult::Invalid(_)));
650}
651
652#[test]
653fn test_connect_block_coinbase_exceeds_subsidy() {
654 let coinbase_tx = Transaction {
655 version: 1,
656 inputs: vec![TransactionInput {
657 prevout: OutPoint {
658 hash: [0; 32],
659 index: 0xffffffff,
660 },
661 script_sig: vec![],
662 sequence: 0xffffffff,
663 }]
664 .into(),
665 outputs: vec![TransactionOutput {
666 value: 6000000000, script_pubkey: vec![],
668 }]
669 .into(),
670 lock_time: 0,
671 };
672
673 let block = Block {
674 header: BlockHeader {
675 version: 1,
676 prev_block_hash: [0; 32],
677 merkle_root: [0; 32],
678 timestamp: 1231006505,
679 bits: 0x1d00ffff,
680 nonce: 0,
681 },
682 transactions: vec![coinbase_tx].into_boxed_slice(),
683 };
684
685 let utxo_set = UtxoSet::default();
686 let witnesses: Vec<Vec<Witness>> = block
688 .transactions
689 .iter()
690 .map(|tx| {
691 (0..tx.inputs.len())
692 .map(|_| Vec::with_capacity(2))
693 .collect()
694 })
695 .collect();
696 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
697 let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
698
699 assert!(matches!(result, ValidationResult::Invalid(_)));
700}
701
702#[test]
703fn test_apply_transaction_regular() {
704 let mut utxo_set = UtxoSet::default();
705
706 let prev_outpoint = OutPoint {
708 hash: [1; 32],
709 index: 0,
710 };
711 let prev_utxo = UTXO {
712 value: 1000,
713 script_pubkey: vec![OP_1].into(), height: 0,
715 is_coinbase: false,
716 };
717 utxo_set.insert(prev_outpoint, std::sync::Arc::new(prev_utxo));
718
719 let regular_tx = Transaction {
720 version: 1,
721 inputs: vec![TransactionInput {
722 prevout: OutPoint {
723 hash: [1; 32],
724 index: 0,
725 },
726 script_sig: vec![OP_1], sequence: 0xffffffff,
728 }]
729 .into(),
730 outputs: vec![TransactionOutput {
731 value: 500,
732 script_pubkey: vec![OP_2], }]
734 .into(),
735 lock_time: 0,
736 };
737
738 let (new_utxo_set, _undo_entries) = apply_transaction(®ular_tx, utxo_set, 1).unwrap();
739
740 assert_eq!(new_utxo_set.len(), 1);
742}
743
744#[test]
745fn test_apply_transaction_multiple_outputs() {
746 let coinbase_tx = Transaction {
747 version: 1,
748 inputs: vec![TransactionInput {
749 prevout: OutPoint {
750 hash: [0; 32],
751 index: 0xffffffff,
752 },
753 script_sig: vec![],
754 sequence: 0xffffffff,
755 }]
756 .into(),
757 outputs: vec![
758 TransactionOutput {
759 value: 2500000000,
760 script_pubkey: vec![OP_1],
761 },
762 TransactionOutput {
763 value: 2500000000,
764 script_pubkey: vec![OP_2],
765 },
766 ]
767 .into(),
768 lock_time: 0,
769 };
770
771 let utxo_set = UtxoSet::default();
772 let (new_utxo_set, _undo_entries) = apply_transaction(&coinbase_tx, utxo_set, 0).unwrap();
773
774 assert_eq!(new_utxo_set.len(), 2);
775}
776
777#[test]
778fn test_validate_block_header_valid() {
779 use sha2::{Digest, Sha256};
780
781 let header = BlockHeader {
783 version: 1,
784 prev_block_hash: [0; 32],
785 merkle_root: Sha256::digest(b"test merkle root")[..].try_into().unwrap(),
786 timestamp: 1231006505,
787 bits: 0x1d00ffff,
788 nonce: 0,
789 };
790
791 let result = header::validate_block_header(&header, None).unwrap();
792 assert!(result);
793}
794
795#[test]
796fn test_validate_block_header_invalid_version() {
797 let header = BlockHeader {
798 version: 0, prev_block_hash: [0; 32],
800 merkle_root: [0; 32],
801 timestamp: 1231006505,
802 bits: 0x1d00ffff,
803 nonce: 0,
804 };
805
806 let result = header::validate_block_header(&header, None).unwrap();
807 assert!(!result);
808}
809
810#[test]
811fn test_validate_block_header_invalid_bits() {
812 let header = BlockHeader {
813 version: 1,
814 prev_block_hash: [0; 32],
815 merkle_root: [0; 32],
816 timestamp: 1231006505,
817 bits: 0, nonce: 0,
819 };
820
821 let result = header::validate_block_header(&header, None).unwrap();
822 assert!(!result);
823}
824
825#[test]
826fn test_is_coinbase_true() {
827 let coinbase_tx = Transaction {
828 version: 1,
829 inputs: vec![TransactionInput {
830 prevout: OutPoint {
831 hash: [0; 32],
832 index: 0xffffffff,
833 },
834 script_sig: vec![],
835 sequence: 0xffffffff,
836 }]
837 .into(),
838 outputs: vec![TransactionOutput {
839 value: 5000000000,
840 script_pubkey: vec![],
841 }]
842 .into(),
843 lock_time: 0,
844 };
845
846 assert!(is_coinbase(&coinbase_tx));
847}
848
849#[test]
850fn test_is_coinbase_false_wrong_hash() {
851 let regular_tx = Transaction {
852 version: 1,
853 inputs: vec![TransactionInput {
854 prevout: OutPoint {
855 hash: [1; 32],
856 index: 0xffffffff,
857 }, script_sig: vec![],
859 sequence: 0xffffffff,
860 }]
861 .into(),
862 outputs: vec![TransactionOutput {
863 value: 5000000000,
864 script_pubkey: vec![],
865 }]
866 .into(),
867 lock_time: 0,
868 };
869
870 assert!(!is_coinbase(®ular_tx));
871}
872
873#[test]
874fn test_is_coinbase_false_wrong_index() {
875 let regular_tx = Transaction {
876 version: 1,
877 inputs: vec![TransactionInput {
878 prevout: OutPoint {
879 hash: [0; 32],
880 index: 0,
881 }, script_sig: vec![],
883 sequence: 0xffffffff,
884 }]
885 .into(),
886 outputs: vec![TransactionOutput {
887 value: 5000000000,
888 script_pubkey: vec![],
889 }]
890 .into(),
891 lock_time: 0,
892 };
893
894 assert!(!is_coinbase(®ular_tx));
895}
896
897#[test]
898fn test_is_coinbase_false_multiple_inputs() {
899 let regular_tx = Transaction {
900 version: 1,
901 inputs: vec![
902 TransactionInput {
903 prevout: OutPoint {
904 hash: [0; 32],
905 index: 0xffffffff,
906 },
907 script_sig: vec![],
908 sequence: 0xffffffff,
909 },
910 TransactionInput {
911 prevout: OutPoint {
912 hash: [1; 32],
913 index: 0,
914 },
915 script_sig: vec![],
916 sequence: 0xffffffff,
917 },
918 ]
919 .into(),
920 outputs: vec![TransactionOutput {
921 value: 5000000000,
922 script_pubkey: vec![],
923 }]
924 .into(),
925 lock_time: 0,
926 };
927
928 assert!(!is_coinbase(®ular_tx));
929}
930
931#[test]
932fn test_calculate_tx_id() {
933 let tx = Transaction {
934 version: 1,
935 inputs: vec![TransactionInput {
936 prevout: OutPoint {
937 hash: [0; 32],
938 index: 0,
939 },
940 script_sig: vec![],
941 sequence: 0xffffffff,
942 }]
943 .into(),
944 outputs: vec![TransactionOutput {
945 value: 1000,
946 script_pubkey: vec![],
947 }]
948 .into(),
949 lock_time: 0,
950 };
951
952 let tx_id = calculate_tx_id(&tx);
953
954 assert_eq!(tx_id.len(), 32);
956
957 let tx_id2 = calculate_tx_id(&tx);
959 assert_eq!(tx_id, tx_id2);
960
961 let mut tx2 = tx.clone();
963 tx2.version = 2;
964 let tx_id3 = calculate_tx_id(&tx2);
965 assert_ne!(tx_id, tx_id3);
966}
967
968#[test]
969fn test_calculate_tx_id_different_versions() {
970 let tx1 = Transaction {
971 version: 2,
972 inputs: vec![].into(),
973 outputs: vec![].into(),
974 lock_time: 0,
975 };
976
977 let tx2 = Transaction {
978 version: 1,
979 inputs: vec![].into(),
980 outputs: vec![].into(),
981 lock_time: 0,
982 };
983
984 let id1 = calculate_tx_id(&tx1);
985 let id2 = calculate_tx_id(&tx2);
986
987 assert_ne!(id1, id2);
989}
990
991#[test]
992fn test_connect_block_empty_transactions() {
993 let block = Block {
997 header: BlockHeader {
998 version: 1,
999 prev_block_hash: [0; 32],
1000 merkle_root: [0; 32], timestamp: 1231006505,
1002 bits: 0x1d00ffff,
1003 nonce: 0,
1004 },
1005 transactions: vec![].into_boxed_slice(), };
1007
1008 let utxo_set = UtxoSet::default();
1009 let witnesses: Vec<Vec<Witness>> = block
1011 .transactions
1012 .iter()
1013 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
1014 .collect();
1015 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
1016 let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
1017 assert!(result.is_ok());
1019 let (validation_result, _, _undo_log) = result.unwrap();
1020 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
1021}
1022
1023#[test]
1024fn test_connect_block_invalid_coinbase() {
1025 let invalid_coinbase = Transaction {
1026 version: 1,
1027 inputs: vec![TransactionInput {
1028 prevout: OutPoint {
1029 hash: [1; 32],
1030 index: 0,
1031 }, script_sig: vec![],
1033 sequence: 0xffffffff,
1034 }]
1035 .into(),
1036 outputs: vec![TransactionOutput {
1037 value: 5000000000,
1038 script_pubkey: vec![],
1039 }]
1040 .into(),
1041 lock_time: 0,
1042 };
1043
1044 let block = Block {
1045 header: BlockHeader {
1046 version: 1,
1047 prev_block_hash: [0; 32],
1048 merkle_root: [0; 32],
1049 timestamp: 1231006505,
1050 bits: 0x1d00ffff,
1051 nonce: 0,
1052 },
1053 transactions: vec![invalid_coinbase].into_boxed_slice(),
1054 };
1055
1056 let utxo_set = UtxoSet::default();
1057 let witnesses: Vec<Vec<Witness>> = block
1059 .transactions
1060 .iter()
1061 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
1062 .collect();
1063 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
1064 let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
1065 assert!(result.is_ok());
1067 let (validation_result, _, _undo_log) = result.unwrap();
1068 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
1069}
1070
1071#[test]
1072fn test_apply_transaction_insufficient_funds() {
1073 let mut utxo_set = UtxoSet::default();
1074
1075 let prev_outpoint = OutPoint {
1077 hash: [1; 32],
1078 index: 0,
1079 };
1080 let prev_utxo = UTXO {
1081 value: 100, script_pubkey: vec![OP_1].into(),
1083 height: 0,
1084 is_coinbase: false,
1085 };
1086 utxo_set.insert(prev_outpoint, std::sync::Arc::new(prev_utxo));
1087
1088 let tx = Transaction {
1089 version: 1,
1090 inputs: vec![TransactionInput {
1091 prevout: OutPoint {
1092 hash: [1; 32],
1093 index: 0,
1094 },
1095 script_sig: vec![OP_1],
1096 sequence: 0xffffffff,
1097 }]
1098 .into(),
1099 outputs: vec![TransactionOutput {
1100 value: 200, script_pubkey: vec![OP_2],
1102 }]
1103 .into(),
1104 lock_time: 0,
1105 };
1106
1107 let result = apply_transaction(&tx, utxo_set, 1);
1109 assert!(result.is_ok());
1110}
1111
1112#[test]
1113fn test_apply_transaction_missing_utxo() {
1114 let utxo_set = UtxoSet::default(); let tx = Transaction {
1117 version: 1,
1118 inputs: vec![TransactionInput {
1119 prevout: OutPoint {
1120 hash: [1; 32],
1121 index: 0,
1122 },
1123 script_sig: vec![OP_1],
1124 sequence: 0xffffffff,
1125 }]
1126 .into(),
1127 outputs: vec![TransactionOutput {
1128 value: 100,
1129 script_pubkey: vec![OP_2],
1130 }]
1131 .into(),
1132 lock_time: 0,
1133 };
1134
1135 let result = apply_transaction(&tx, utxo_set, 1);
1137 assert!(result.is_ok());
1138}
1139
1140#[test]
1141fn test_validate_block_header_future_timestamp() {
1142 use sha2::{Digest, Sha256};
1143
1144 let header = BlockHeader {
1147 version: 1,
1148 prev_block_hash: [0; 32],
1149 merkle_root: Sha256::digest(b"test merkle root")[..].try_into().unwrap(),
1150 timestamp: 9999999999, bits: 0x1d00ffff,
1152 nonce: 0,
1153 };
1154
1155 let result = header::validate_block_header(&header, None).unwrap();
1157 assert!(result);
1158}
1159
1160#[test]
1161fn test_validate_block_header_zero_timestamp() {
1162 use sha2::{Digest, Sha256};
1163
1164 let header = BlockHeader {
1166 version: 1,
1167 prev_block_hash: [0; 32],
1168 merkle_root: Sha256::digest(b"test merkle root")[..].try_into().unwrap(),
1169 timestamp: 0, bits: 0x1d00ffff,
1171 nonce: 0,
1172 };
1173
1174 let result = header::validate_block_header(&header, None).unwrap();
1176 assert!(!result);
1177}
1178
1179#[test]
1180fn test_connect_block_coinbase_exceeds_subsidy_edge() {
1181 let coinbase_tx = Transaction {
1182 version: 1,
1183 inputs: vec![TransactionInput {
1184 prevout: OutPoint {
1185 hash: [0; 32],
1186 index: 0xffffffff,
1187 },
1188 script_sig: vec![],
1189 sequence: 0xffffffff,
1190 }]
1191 .into(),
1192 outputs: vec![TransactionOutput {
1193 value: 2100000000000000, script_pubkey: vec![],
1195 }]
1196 .into(),
1197 lock_time: 0,
1198 };
1199
1200 let block = Block {
1201 header: BlockHeader {
1202 version: 1,
1203 prev_block_hash: [0; 32],
1204 merkle_root: [0; 32],
1205 timestamp: 1231006505,
1206 bits: 0x1d00ffff,
1207 nonce: 0,
1208 },
1209 transactions: vec![coinbase_tx].into_boxed_slice(),
1210 };
1211
1212 let utxo_set = UtxoSet::default();
1213 let witnesses: Vec<Vec<Witness>> = block
1215 .transactions
1216 .iter()
1217 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
1218 .collect();
1219 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
1220 let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
1221 assert!(result.is_ok());
1223 let (validation_result, _, _undo_log) = result.unwrap();
1224 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
1225}
1226
1227#[test]
1228fn test_connect_block_first_tx_not_coinbase_edge() {
1229 let regular_tx = Transaction {
1230 version: 1,
1231 inputs: vec![TransactionInput {
1232 prevout: OutPoint {
1233 hash: [1; 32],
1234 index: 0,
1235 },
1236 script_sig: vec![OP_1],
1237 sequence: 0xffffffff,
1238 }]
1239 .into(),
1240 outputs: vec![TransactionOutput {
1241 value: 1000,
1242 script_pubkey: vec![OP_2],
1243 }]
1244 .into(),
1245 lock_time: 0,
1246 };
1247
1248 let block = Block {
1249 header: BlockHeader {
1250 version: 1,
1251 prev_block_hash: [0; 32],
1252 merkle_root: [0; 32],
1253 timestamp: 1231006505,
1254 bits: 0x1d00ffff,
1255 nonce: 0,
1256 },
1257 transactions: vec![regular_tx].into_boxed_slice(), };
1259
1260 let utxo_set = UtxoSet::default();
1261 let witnesses: Vec<Vec<Witness>> = block
1263 .transactions
1264 .iter()
1265 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
1266 .collect();
1267 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
1268 let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
1269 assert!(result.is_ok());
1271 let (validation_result, _, _undo_log) = result.unwrap();
1272 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
1273}
1274
1275#[test]
1276fn test_apply_transaction_multiple_inputs() {
1277 let mut utxo_set = UtxoSet::default();
1278
1279 let outpoint1 = OutPoint {
1281 hash: [1; 32],
1282 index: 0,
1283 };
1284 let utxo1 = UTXO {
1285 value: 500,
1286 script_pubkey: vec![OP_1].into(),
1287 height: 0,
1288 is_coinbase: false,
1289 };
1290 utxo_set.insert(outpoint1, std::sync::Arc::new(utxo1));
1291
1292 let outpoint2 = OutPoint {
1293 hash: [2; 32],
1294 index: 0,
1295 };
1296 let utxo2 = UTXO {
1297 value: 300,
1298 script_pubkey: vec![OP_2].into(),
1299 height: 0,
1300 is_coinbase: false,
1301 };
1302 utxo_set.insert(outpoint2, std::sync::Arc::new(utxo2));
1303
1304 let tx = Transaction {
1305 version: 1,
1306 inputs: vec![
1307 TransactionInput {
1308 prevout: OutPoint {
1309 hash: [1; 32],
1310 index: 0,
1311 },
1312 script_sig: vec![OP_1],
1313 sequence: 0xffffffff,
1314 },
1315 TransactionInput {
1316 prevout: OutPoint {
1317 hash: [2; 32],
1318 index: 0,
1319 },
1320 script_sig: vec![OP_2],
1321 sequence: 0xffffffff,
1322 },
1323 ]
1324 .into(),
1325 outputs: vec![TransactionOutput {
1326 value: 700, script_pubkey: vec![OP_3],
1328 }]
1329 .into(),
1330 lock_time: 0,
1331 };
1332
1333 let (new_utxo_set, _undo_entries) = apply_transaction(&tx, utxo_set, 1).unwrap();
1334 assert_eq!(new_utxo_set.len(), 1);
1335}
1336
1337#[test]
1338fn test_apply_transaction_no_outputs() {
1339 let mut utxo_set = UtxoSet::default();
1340
1341 let prev_outpoint = OutPoint {
1342 hash: [1; 32],
1343 index: 0,
1344 };
1345 let prev_utxo = UTXO {
1346 value: 1000,
1347 script_pubkey: vec![OP_1].into(),
1348 height: 0,
1349 is_coinbase: false,
1350 };
1351 utxo_set.insert(prev_outpoint, std::sync::Arc::new(prev_utxo));
1352
1353 let tx = Transaction {
1356 version: 1,
1357 inputs: vec![TransactionInput {
1358 prevout: OutPoint {
1359 hash: [1; 32],
1360 index: 0,
1361 },
1362 script_sig: vec![OP_1],
1363 sequence: 0xffffffff,
1364 }]
1365 .into(),
1366 outputs: vec![].into(), lock_time: 0,
1368 };
1369
1370 let validation_result = crate::transaction::check_transaction(&tx).unwrap();
1373 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
1374
1375 let valid_tx = Transaction {
1377 version: 1,
1378 inputs: vec![TransactionInput {
1379 prevout: OutPoint {
1380 hash: [1; 32],
1381 index: 0,
1382 },
1383 script_sig: vec![OP_1],
1384 sequence: 0xffffffff,
1385 }]
1386 .into(),
1387 outputs: vec![TransactionOutput {
1388 value: 500, script_pubkey: vec![OP_1],
1390 }]
1391 .into(),
1392 lock_time: 0,
1393 };
1394
1395 let (new_utxo_set, _undo_entries) = apply_transaction(&valid_tx, utxo_set, 1).unwrap();
1397 assert_eq!(new_utxo_set.len(), 1);
1399
1400 let output_outpoint = OutPoint {
1402 hash: calculate_tx_id(&valid_tx),
1403 index: 0,
1404 };
1405 assert!(new_utxo_set.contains_key(&output_outpoint));
1406}