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 block_validation_context_for_connect_ibd(None::<&[BlockHeader]>, 0, network)
283 }
284}
285
286#[inline]
289pub fn block_validation_context_for_connect_ibd<H: AsRef<BlockHeader>>(
290 recent_headers: Option<&[H]>,
291 network_time: u64,
292 network: Network,
293) -> BlockValidationContext {
294 BlockValidationContext::from_connect_block_ibd_args(
295 recent_headers,
296 network_time,
297 network,
298 None,
299 None,
300 )
301}
302
303impl IsForkActive for BlockValidationContext {
304 #[inline]
305 fn is_fork_active(&self, fork: crate::types::ForkId, height: u64) -> bool {
306 self.activation.is_fork_active(fork, height)
307 }
308}
309
310#[cfg(feature = "production")]
311mod tx_id_pool {
312 use crate::types::{Hash, Transaction};
313 use std::cell::RefCell;
314
315 thread_local! {
316 static TX_BUF: RefCell<Vec<u8>> = RefCell::new(Vec::with_capacity(
317 crate::optimizations::proven_bounds::MAX_TX_SIZE_PROVEN
318 ));
319 }
320
321 pub fn compute_tx_id_with_pool(tx: &Transaction) -> Hash {
323 use crate::crypto::OptimizedSha256;
324 use crate::serialization::transaction::serialize_transaction_into;
325
326 TX_BUF.with(|cell| {
327 let mut buf = cell.borrow_mut();
328 serialize_transaction_into(&mut buf, tx);
329 OptimizedSha256::new().hash256(&buf)
330 })
331 }
332}
333
334#[spec_locked("8.4.1")]
340pub fn compute_block_tx_ids_spec(block: &Block) -> Vec<Hash> {
341 block.transactions.iter().map(calculate_tx_id).collect()
342}
343
344pub fn compute_block_tx_ids_into(block: &Block, out: &mut Vec<Hash>) {
348 out.clear();
349 out.reserve(block.transactions.len());
350 #[cfg(all(feature = "production", feature = "rayon"))]
351 {
352 use rayon::prelude::*;
353 assert!(
354 block.transactions.len() <= 25_000,
355 "Transaction count {} must be reasonable for batch processing",
356 block.transactions.len()
357 );
358 let chunk: Vec<Hash> = block
359 .transactions
360 .as_ref()
361 .par_iter()
362 .map(tx_id_pool::compute_tx_id_with_pool)
363 .collect();
364 out.extend(chunk);
365 }
366
367 #[cfg(all(feature = "production", not(feature = "rayon")))]
368 {
369 out.extend(
370 block
371 .transactions
372 .iter()
373 .map(tx_id_pool::compute_tx_id_with_pool),
374 );
375 }
376
377 #[cfg(not(feature = "production"))]
378 {
379 out.extend(block.transactions.iter().map(calculate_tx_id));
380 }
381}
382
383pub fn compute_block_tx_ids(block: &Block) -> Vec<Hash> {
384 let mut v = Vec::new();
385 compute_block_tx_ids_into(block, &mut v);
386 v
387}
388
389#[test]
391fn compute_block_tx_ids_spec_matches_optimized_paths() {
392 let coinbase = Transaction {
393 version: 1,
394 inputs: vec![TransactionInput {
395 prevout: OutPoint {
396 hash: [0; 32].into(),
397 index: 0xffffffff,
398 },
399 script_sig: vec![0x03, 0x01, 0x00, 0x00],
400 sequence: 0xffffffff,
401 }]
402 .into(),
403 outputs: vec![TransactionOutput {
404 value: 50_000_000_000,
405 script_pubkey: vec![0x51].into(),
406 }]
407 .into(),
408 lock_time: 0,
409 };
410 let tx2 = Transaction {
411 version: 1,
412 inputs: vec![TransactionInput {
413 prevout: OutPoint {
414 hash: [1u8; 32].into(),
415 index: 0,
416 },
417 script_sig: vec![0x51],
418 sequence: 0xffffffff,
419 }]
420 .into(),
421 outputs: vec![TransactionOutput {
422 value: 10_000,
423 script_pubkey: vec![0x51].into(),
424 }]
425 .into(),
426 lock_time: 0,
427 };
428 let block = Block {
429 header: BlockHeader {
430 version: 1,
431 prev_block_hash: [0; 32],
432 merkle_root: [0; 32],
433 timestamp: 1,
434 bits: 0x207fffff,
435 nonce: 0,
436 },
437 transactions: vec![coinbase, tx2].into_boxed_slice(),
438 };
439 assert_eq!(
440 compute_block_tx_ids_spec(&block),
441 compute_block_tx_ids(&block),
442 "ยง8.4.1 spec reference must match compute_block_tx_ids / compute_block_tx_ids_into"
443 );
444}
445
446#[test]
447fn test_connect_block_invalid_header() {
448 let coinbase_tx = Transaction {
449 version: 1,
450 inputs: vec![TransactionInput {
451 prevout: OutPoint {
452 hash: [0; 32].into(),
453 index: 0xffffffff,
454 },
455 script_sig: vec![],
456 sequence: 0xffffffff,
457 }]
458 .into(),
459 outputs: vec![TransactionOutput {
460 value: 5000000000,
461 script_pubkey: vec![].into(),
462 }]
463 .into(),
464 lock_time: 0,
465 };
466
467 let block = Block {
468 header: BlockHeader {
469 version: 0, prev_block_hash: [0; 32],
471 merkle_root: [0; 32],
472 timestamp: 1231006505,
473 bits: 0x1d00ffff,
474 nonce: 0,
475 },
476 transactions: vec![coinbase_tx].into_boxed_slice(),
477 };
478
479 let utxo_set = UtxoSet::default();
480 let witnesses: Vec<Vec<Witness>> = block
481 .transactions
482 .iter()
483 .map(|tx| {
484 (0..tx.inputs.len())
485 .map(|_| Vec::with_capacity(2))
486 .collect()
487 })
488 .collect();
489 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
490 let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
491
492 assert!(matches!(result, ValidationResult::Invalid(_)));
493}
494
495#[test]
496fn test_connect_block_no_transactions() {
497 let block = Block {
498 header: BlockHeader {
499 version: 1,
500 prev_block_hash: [0; 32],
501 merkle_root: [0; 32],
502 timestamp: 1231006505,
503 bits: 0x1d00ffff,
504 nonce: 0,
505 },
506 transactions: vec![].into_boxed_slice(), };
508
509 let utxo_set = UtxoSet::default();
510 let witnesses: Vec<Vec<Witness>> = block
512 .transactions
513 .iter()
514 .map(|tx| {
515 (0..tx.inputs.len())
516 .map(|_| Vec::with_capacity(2))
517 .collect()
518 })
519 .collect();
520 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
521 let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
522
523 assert!(matches!(result, ValidationResult::Invalid(_)));
524}
525
526#[test]
527fn test_connect_block_first_tx_not_coinbase() {
528 let regular_tx = Transaction {
529 version: 1,
530 inputs: vec![TransactionInput {
531 prevout: OutPoint {
532 hash: [1; 32].into(),
533 index: 0,
534 },
535 script_sig: vec![],
536 sequence: 0xffffffff,
537 }]
538 .into(),
539 outputs: vec![TransactionOutput {
540 value: 1000,
541 script_pubkey: vec![].into(),
542 }]
543 .into(),
544 lock_time: 0,
545 };
546
547 let block = Block {
548 header: BlockHeader {
549 version: 1,
550 prev_block_hash: [0; 32],
551 merkle_root: [0; 32],
552 timestamp: 1231006505,
553 bits: 0x1d00ffff,
554 nonce: 0,
555 },
556 transactions: vec![regular_tx].into_boxed_slice(), };
558
559 let utxo_set = UtxoSet::default();
560 let witnesses: Vec<Vec<Witness>> = block
562 .transactions
563 .iter()
564 .map(|tx| {
565 (0..tx.inputs.len())
566 .map(|_| Vec::with_capacity(2))
567 .collect()
568 })
569 .collect();
570 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
571 let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
572
573 assert!(matches!(result, ValidationResult::Invalid(_)));
574}
575
576#[test]
577fn test_connect_block_coinbase_exceeds_subsidy() {
578 let coinbase_tx = Transaction {
579 version: 1,
580 inputs: vec![TransactionInput {
581 prevout: OutPoint {
582 hash: [0; 32].into(),
583 index: 0xffffffff,
584 },
585 script_sig: vec![],
586 sequence: 0xffffffff,
587 }]
588 .into(),
589 outputs: vec![TransactionOutput {
590 value: 6000000000, script_pubkey: vec![].into(),
592 }]
593 .into(),
594 lock_time: 0,
595 };
596
597 let block = Block {
598 header: BlockHeader {
599 version: 1,
600 prev_block_hash: [0; 32],
601 merkle_root: [0; 32],
602 timestamp: 1231006505,
603 bits: 0x1d00ffff,
604 nonce: 0,
605 },
606 transactions: vec![coinbase_tx].into_boxed_slice(),
607 };
608
609 let utxo_set = UtxoSet::default();
610 let witnesses: Vec<Vec<Witness>> = block
612 .transactions
613 .iter()
614 .map(|tx| {
615 (0..tx.inputs.len())
616 .map(|_| Vec::with_capacity(2))
617 .collect()
618 })
619 .collect();
620 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
621 let (result, _, _undo_log) = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx).unwrap();
622
623 assert!(matches!(result, ValidationResult::Invalid(_)));
624}
625
626#[test]
627fn test_apply_transaction_regular() {
628 let mut utxo_set = UtxoSet::default();
629
630 let prev_outpoint = OutPoint {
632 hash: [1; 32],
633 index: 0,
634 };
635 let prev_utxo = UTXO {
636 value: 1000,
637 script_pubkey: vec![OP_1].into(), height: 0,
639 is_coinbase: false,
640 };
641 utxo_set.insert(prev_outpoint, std::sync::Arc::new(prev_utxo));
642
643 let regular_tx = Transaction {
644 version: 1,
645 inputs: vec![TransactionInput {
646 prevout: OutPoint {
647 hash: [1; 32].into(),
648 index: 0,
649 },
650 script_sig: vec![OP_1], sequence: 0xffffffff,
652 }]
653 .into(),
654 outputs: vec![TransactionOutput {
655 value: 500,
656 script_pubkey: vec![OP_2].into(), }]
658 .into(),
659 lock_time: 0,
660 };
661
662 let (new_utxo_set, _undo_entries) = apply_transaction(®ular_tx, utxo_set, 1).unwrap();
663
664 assert_eq!(new_utxo_set.len(), 1);
666}
667
668#[test]
669fn test_apply_transaction_multiple_outputs() {
670 let coinbase_tx = Transaction {
671 version: 1,
672 inputs: vec![TransactionInput {
673 prevout: OutPoint {
674 hash: [0; 32].into(),
675 index: 0xffffffff,
676 },
677 script_sig: vec![],
678 sequence: 0xffffffff,
679 }]
680 .into(),
681 outputs: vec![
682 TransactionOutput {
683 value: 2500000000,
684 script_pubkey: vec![OP_1].into(),
685 },
686 TransactionOutput {
687 value: 2500000000,
688 script_pubkey: vec![OP_2].into(),
689 },
690 ]
691 .into(),
692 lock_time: 0,
693 };
694
695 let utxo_set = UtxoSet::default();
696 let (new_utxo_set, _undo_entries) = apply_transaction(&coinbase_tx, utxo_set, 0).unwrap();
697
698 assert_eq!(new_utxo_set.len(), 2);
699}
700
701#[test]
702fn test_validate_block_header_valid() {
703 use sha2::{Digest, Sha256};
704
705 let header = BlockHeader {
707 version: 1,
708 prev_block_hash: [0; 32],
709 merkle_root: Sha256::digest(b"test merkle root")[..].try_into().unwrap(),
710 timestamp: 1231006505,
711 bits: 0x1d00ffff,
712 nonce: 0,
713 };
714
715 let result = header::validate_block_header(&header, None).unwrap();
716 assert!(result);
717}
718
719#[test]
720fn test_validate_block_header_invalid_version() {
721 let header = BlockHeader {
722 version: 0, prev_block_hash: [0; 32],
724 merkle_root: [0; 32],
725 timestamp: 1231006505,
726 bits: 0x1d00ffff,
727 nonce: 0,
728 };
729
730 let result = header::validate_block_header(&header, None).unwrap();
731 assert!(!result);
732}
733
734#[test]
735fn test_validate_block_header_invalid_bits() {
736 let header = BlockHeader {
737 version: 1,
738 prev_block_hash: [0; 32],
739 merkle_root: [0; 32],
740 timestamp: 1231006505,
741 bits: 0, nonce: 0,
743 };
744
745 let result = header::validate_block_header(&header, None).unwrap();
746 assert!(!result);
747}
748
749#[test]
750fn test_is_coinbase_true() {
751 let coinbase_tx = Transaction {
752 version: 1,
753 inputs: vec![TransactionInput {
754 prevout: OutPoint {
755 hash: [0; 32].into(),
756 index: 0xffffffff,
757 },
758 script_sig: vec![],
759 sequence: 0xffffffff,
760 }]
761 .into(),
762 outputs: vec![TransactionOutput {
763 value: 5000000000,
764 script_pubkey: vec![].into(),
765 }]
766 .into(),
767 lock_time: 0,
768 };
769
770 assert!(is_coinbase(&coinbase_tx));
771}
772
773#[test]
774fn test_is_coinbase_false_wrong_hash() {
775 let regular_tx = Transaction {
776 version: 1,
777 inputs: vec![TransactionInput {
778 prevout: OutPoint {
779 hash: [1; 32].into(),
780 index: 0xffffffff,
781 }, script_sig: vec![],
783 sequence: 0xffffffff,
784 }]
785 .into(),
786 outputs: vec![TransactionOutput {
787 value: 5000000000,
788 script_pubkey: vec![].into(),
789 }]
790 .into(),
791 lock_time: 0,
792 };
793
794 assert!(!is_coinbase(®ular_tx));
795}
796
797#[test]
798fn test_is_coinbase_false_wrong_index() {
799 let regular_tx = Transaction {
800 version: 1,
801 inputs: vec![TransactionInput {
802 prevout: OutPoint {
803 hash: [0; 32].into(),
804 index: 0,
805 }, script_sig: vec![],
807 sequence: 0xffffffff,
808 }]
809 .into(),
810 outputs: vec![TransactionOutput {
811 value: 5000000000,
812 script_pubkey: vec![].into(),
813 }]
814 .into(),
815 lock_time: 0,
816 };
817
818 assert!(!is_coinbase(®ular_tx));
819}
820
821#[test]
822fn test_is_coinbase_false_multiple_inputs() {
823 let regular_tx = Transaction {
824 version: 1,
825 inputs: vec![
826 TransactionInput {
827 prevout: OutPoint {
828 hash: [0; 32].into(),
829 index: 0xffffffff,
830 },
831 script_sig: vec![],
832 sequence: 0xffffffff,
833 },
834 TransactionInput {
835 prevout: OutPoint {
836 hash: [1; 32],
837 index: 0,
838 },
839 script_sig: vec![],
840 sequence: 0xffffffff,
841 },
842 ]
843 .into(),
844 outputs: vec![TransactionOutput {
845 value: 5000000000,
846 script_pubkey: vec![].into(),
847 }]
848 .into(),
849 lock_time: 0,
850 };
851
852 assert!(!is_coinbase(®ular_tx));
853}
854
855#[test]
856fn test_calculate_tx_id() {
857 let tx = Transaction {
858 version: 1,
859 inputs: vec![TransactionInput {
860 prevout: OutPoint {
861 hash: [0; 32].into(),
862 index: 0,
863 },
864 script_sig: vec![],
865 sequence: 0xffffffff,
866 }]
867 .into(),
868 outputs: vec![TransactionOutput {
869 value: 1000,
870 script_pubkey: vec![].into(),
871 }]
872 .into(),
873 lock_time: 0,
874 };
875
876 let tx_id = calculate_tx_id(&tx);
877
878 assert_eq!(tx_id.len(), 32);
880
881 let tx_id2 = calculate_tx_id(&tx);
883 assert_eq!(tx_id, tx_id2);
884
885 let mut tx2 = tx.clone();
887 tx2.version = 2;
888 let tx_id3 = calculate_tx_id(&tx2);
889 assert_ne!(tx_id, tx_id3);
890}
891
892#[test]
893fn test_calculate_tx_id_different_versions() {
894 let tx1 = Transaction {
895 version: 2,
896 inputs: vec![].into(),
897 outputs: vec![].into(),
898 lock_time: 0,
899 };
900
901 let tx2 = Transaction {
902 version: 1,
903 inputs: vec![].into(),
904 outputs: vec![].into(),
905 lock_time: 0,
906 };
907
908 let id1 = calculate_tx_id(&tx1);
909 let id2 = calculate_tx_id(&tx2);
910
911 assert_ne!(id1, id2);
913}
914
915#[test]
916fn test_connect_block_empty_transactions() {
917 let block = Block {
921 header: BlockHeader {
922 version: 1,
923 prev_block_hash: [0; 32],
924 merkle_root: [0; 32], timestamp: 1231006505,
926 bits: 0x1d00ffff,
927 nonce: 0,
928 },
929 transactions: vec![].into_boxed_slice(), };
931
932 let utxo_set = UtxoSet::default();
933 let witnesses: Vec<Vec<Witness>> = block
935 .transactions
936 .iter()
937 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
938 .collect();
939 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
940 let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
941 assert!(result.is_ok());
943 let (validation_result, _, _undo_log) = result.unwrap();
944 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
945}
946
947#[test]
948fn test_connect_block_invalid_coinbase() {
949 let invalid_coinbase = Transaction {
950 version: 1,
951 inputs: vec![TransactionInput {
952 prevout: OutPoint {
953 hash: [1; 32].into(),
954 index: 0,
955 }, script_sig: vec![],
957 sequence: 0xffffffff,
958 }]
959 .into(),
960 outputs: vec![TransactionOutput {
961 value: 5000000000,
962 script_pubkey: vec![].into(),
963 }]
964 .into(),
965 lock_time: 0,
966 };
967
968 let block = Block {
969 header: BlockHeader {
970 version: 1,
971 prev_block_hash: [0; 32],
972 merkle_root: [0; 32],
973 timestamp: 1231006505,
974 bits: 0x1d00ffff,
975 nonce: 0,
976 },
977 transactions: vec![invalid_coinbase].into_boxed_slice(),
978 };
979
980 let utxo_set = UtxoSet::default();
981 let witnesses: Vec<Vec<Witness>> = block
983 .transactions
984 .iter()
985 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
986 .collect();
987 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
988 let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
989 assert!(result.is_ok());
991 let (validation_result, _, _undo_log) = result.unwrap();
992 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
993}
994
995#[test]
996fn test_apply_transaction_insufficient_funds() {
997 let mut utxo_set = UtxoSet::default();
998
999 let prev_outpoint = OutPoint {
1001 hash: [1; 32],
1002 index: 0,
1003 };
1004 let prev_utxo = UTXO {
1005 value: 100, script_pubkey: vec![OP_1].into(),
1007 height: 0,
1008 is_coinbase: false,
1009 };
1010 utxo_set.insert(prev_outpoint, std::sync::Arc::new(prev_utxo));
1011
1012 let tx = Transaction {
1013 version: 1,
1014 inputs: vec![TransactionInput {
1015 prevout: OutPoint {
1016 hash: [1; 32].into(),
1017 index: 0,
1018 },
1019 script_sig: vec![OP_1],
1020 sequence: 0xffffffff,
1021 }]
1022 .into(),
1023 outputs: vec![TransactionOutput {
1024 value: 200, script_pubkey: vec![OP_2].into(),
1026 }]
1027 .into(),
1028 lock_time: 0,
1029 };
1030
1031 let result = apply_transaction(&tx, utxo_set, 1);
1033 assert!(result.is_ok());
1034}
1035
1036#[test]
1037fn test_apply_transaction_missing_utxo() {
1038 let utxo_set = UtxoSet::default(); let tx = Transaction {
1041 version: 1,
1042 inputs: vec![TransactionInput {
1043 prevout: OutPoint {
1044 hash: [1; 32].into(),
1045 index: 0,
1046 },
1047 script_sig: vec![OP_1],
1048 sequence: 0xffffffff,
1049 }]
1050 .into(),
1051 outputs: vec![TransactionOutput {
1052 value: 100,
1053 script_pubkey: vec![OP_2].into(),
1054 }]
1055 .into(),
1056 lock_time: 0,
1057 };
1058
1059 let result = apply_transaction(&tx, utxo_set, 1);
1061 assert!(result.is_ok());
1062}
1063
1064#[test]
1065fn test_validate_block_header_future_timestamp() {
1066 use sha2::{Digest, Sha256};
1067
1068 let header = BlockHeader {
1071 version: 1,
1072 prev_block_hash: [0; 32],
1073 merkle_root: Sha256::digest(b"test merkle root")[..].try_into().unwrap(),
1074 timestamp: 9999999999, bits: 0x1d00ffff,
1076 nonce: 0,
1077 };
1078
1079 let result = header::validate_block_header(&header, None).unwrap();
1081 assert!(result);
1082}
1083
1084#[test]
1085fn test_validate_block_header_zero_timestamp() {
1086 use sha2::{Digest, Sha256};
1087
1088 let header = BlockHeader {
1090 version: 1,
1091 prev_block_hash: [0; 32],
1092 merkle_root: Sha256::digest(b"test merkle root")[..].try_into().unwrap(),
1093 timestamp: 0, bits: 0x1d00ffff,
1095 nonce: 0,
1096 };
1097
1098 let result = header::validate_block_header(&header, None).unwrap();
1100 assert!(!result);
1101}
1102
1103#[test]
1104fn test_connect_block_coinbase_exceeds_subsidy_edge() {
1105 let coinbase_tx = Transaction {
1106 version: 1,
1107 inputs: vec![TransactionInput {
1108 prevout: OutPoint {
1109 hash: [0; 32].into(),
1110 index: 0xffffffff,
1111 },
1112 script_sig: vec![],
1113 sequence: 0xffffffff,
1114 }]
1115 .into(),
1116 outputs: vec![TransactionOutput {
1117 value: 2100000000000000, script_pubkey: vec![].into(),
1119 }]
1120 .into(),
1121 lock_time: 0,
1122 };
1123
1124 let block = Block {
1125 header: BlockHeader {
1126 version: 1,
1127 prev_block_hash: [0; 32],
1128 merkle_root: [0; 32],
1129 timestamp: 1231006505,
1130 bits: 0x1d00ffff,
1131 nonce: 0,
1132 },
1133 transactions: vec![coinbase_tx].into_boxed_slice(),
1134 };
1135
1136 let utxo_set = UtxoSet::default();
1137 let witnesses: Vec<Vec<Witness>> = block
1139 .transactions
1140 .iter()
1141 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
1142 .collect();
1143 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
1144 let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
1145 assert!(result.is_ok());
1147 let (validation_result, _, _undo_log) = result.unwrap();
1148 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
1149}
1150
1151#[test]
1152fn test_connect_block_first_tx_not_coinbase_edge() {
1153 let regular_tx = Transaction {
1154 version: 1,
1155 inputs: vec![TransactionInput {
1156 prevout: OutPoint {
1157 hash: [1; 32].into(),
1158 index: 0,
1159 },
1160 script_sig: vec![OP_1],
1161 sequence: 0xffffffff,
1162 }]
1163 .into(),
1164 outputs: vec![TransactionOutput {
1165 value: 1000,
1166 script_pubkey: vec![OP_2].into(),
1167 }]
1168 .into(),
1169 lock_time: 0,
1170 };
1171
1172 let block = Block {
1173 header: BlockHeader {
1174 version: 1,
1175 prev_block_hash: [0; 32],
1176 merkle_root: [0; 32],
1177 timestamp: 1231006505,
1178 bits: 0x1d00ffff,
1179 nonce: 0,
1180 },
1181 transactions: vec![regular_tx].into_boxed_slice(), };
1183
1184 let utxo_set = UtxoSet::default();
1185 let witnesses: Vec<Vec<Witness>> = block
1187 .transactions
1188 .iter()
1189 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
1190 .collect();
1191 let ctx = BlockValidationContext::for_network(crate::types::Network::Mainnet);
1192 let result = connect_block(&block, &witnesses[..], utxo_set, 0, &ctx);
1193 assert!(result.is_ok());
1195 let (validation_result, _, _undo_log) = result.unwrap();
1196 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
1197}
1198
1199#[test]
1200fn test_apply_transaction_multiple_inputs() {
1201 let mut utxo_set = UtxoSet::default();
1202
1203 let outpoint1 = OutPoint {
1205 hash: [1; 32],
1206 index: 0,
1207 };
1208 let utxo1 = UTXO {
1209 value: 500,
1210 script_pubkey: vec![OP_1].into(),
1211 height: 0,
1212 is_coinbase: false,
1213 };
1214 utxo_set.insert(outpoint1, std::sync::Arc::new(utxo1));
1215
1216 let outpoint2 = OutPoint {
1217 hash: [2; 32],
1218 index: 0,
1219 };
1220 let utxo2 = UTXO {
1221 value: 300,
1222 script_pubkey: vec![OP_2].into(),
1223 height: 0,
1224 is_coinbase: false,
1225 };
1226 utxo_set.insert(outpoint2, std::sync::Arc::new(utxo2));
1227
1228 let tx = Transaction {
1229 version: 1,
1230 inputs: vec![
1231 TransactionInput {
1232 prevout: OutPoint {
1233 hash: [1; 32].into(),
1234 index: 0,
1235 },
1236 script_sig: vec![OP_1],
1237 sequence: 0xffffffff,
1238 },
1239 TransactionInput {
1240 prevout: OutPoint {
1241 hash: [2; 32],
1242 index: 0,
1243 },
1244 script_sig: vec![OP_2],
1245 sequence: 0xffffffff,
1246 },
1247 ]
1248 .into(),
1249 outputs: vec![TransactionOutput {
1250 value: 700, script_pubkey: vec![OP_3].into(),
1252 }]
1253 .into(),
1254 lock_time: 0,
1255 };
1256
1257 let (new_utxo_set, _undo_entries) = apply_transaction(&tx, utxo_set, 1).unwrap();
1258 assert_eq!(new_utxo_set.len(), 1);
1259}
1260
1261#[test]
1262fn test_apply_transaction_no_outputs() {
1263 let mut utxo_set = UtxoSet::default();
1264
1265 let prev_outpoint = OutPoint {
1266 hash: [1; 32],
1267 index: 0,
1268 };
1269 let prev_utxo = UTXO {
1270 value: 1000,
1271 script_pubkey: vec![OP_1].into(),
1272 height: 0,
1273 is_coinbase: false,
1274 };
1275 utxo_set.insert(prev_outpoint, std::sync::Arc::new(prev_utxo));
1276
1277 let tx = Transaction {
1280 version: 1,
1281 inputs: vec![TransactionInput {
1282 prevout: OutPoint {
1283 hash: [1; 32].into(),
1284 index: 0,
1285 },
1286 script_sig: vec![OP_1],
1287 sequence: 0xffffffff,
1288 }]
1289 .into(),
1290 outputs: vec![].into(), lock_time: 0,
1292 };
1293
1294 let validation_result = crate::transaction::check_transaction(&tx).unwrap();
1297 assert!(matches!(validation_result, ValidationResult::Invalid(_)));
1298
1299 let valid_tx = Transaction {
1301 version: 1,
1302 inputs: vec![TransactionInput {
1303 prevout: OutPoint {
1304 hash: [1; 32].into(),
1305 index: 0,
1306 },
1307 script_sig: vec![OP_1],
1308 sequence: 0xffffffff,
1309 }]
1310 .into(),
1311 outputs: vec![TransactionOutput {
1312 value: 500, script_pubkey: vec![OP_1].into(),
1314 }]
1315 .into(),
1316 lock_time: 0,
1317 };
1318
1319 let (new_utxo_set, _undo_entries) = apply_transaction(&valid_tx, utxo_set, 1).unwrap();
1321 assert_eq!(new_utxo_set.len(), 1);
1323
1324 let output_outpoint = OutPoint {
1326 hash: calculate_tx_id(&valid_tx),
1327 index: 0,
1328 };
1329 assert!(new_utxo_set.contains_key(&output_outpoint));
1330}