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