1use crate::constants::*;
4use crate::economic::calculate_fee;
5use crate::error::{ConsensusError, Result};
6use crate::script::verify_script_with_context;
7use crate::segwit::Witness;
8use crate::transaction::{check_transaction, check_tx_inputs};
9use crate::types::*;
10use blvm_spec_lock::spec_locked;
11use std::collections::{HashMap, HashSet};
12
13#[spec_locked("9.1", "AcceptToMemoryPool")]
34pub fn accept_to_memory_pool(
35 tx: &Transaction,
36 witnesses: Option<&[Witness]>,
37 utxo_set: &UtxoSet,
38 mempool: &Mempool,
39 height: Natural,
40 time_context: Option<TimeContext>,
41 network: Network,
42) -> Result<MempoolResult> {
43 if tx.inputs.is_empty() && tx.outputs.is_empty() {
47 return Ok(MempoolResult::Rejected(
48 "Transaction must have at least one input or output".to_string(),
49 ));
50 }
51 if is_coinbase(tx) {
52 return Ok(MempoolResult::Rejected(
53 "Coinbase transactions cannot be added to mempool".to_string(),
54 ));
55 }
56 if height > i64::MAX as u64 {
57 return Ok(MempoolResult::Rejected(format!(
58 "Block height {height} exceeds i64::MAX"
59 )));
60 }
61 assert!(
62 utxo_set.len() <= u32::MAX as usize,
63 "UTXO set size {} exceeds maximum",
64 utxo_set.len()
65 );
66 if let Some(wits) = witnesses {
67 if wits.len() != tx.inputs.len() {
68 return Ok(MempoolResult::Rejected(format!(
69 "Witness count {} must match input count {}",
70 wits.len(),
71 tx.inputs.len()
72 )));
73 }
74 }
75
76 let tx_id = crate::block::calculate_tx_id(tx);
78 assert!(tx_id != [0u8; 32], "Transaction ID must be non-zero");
80 if mempool.contains(&tx_id) {
81 return Ok(MempoolResult::Rejected(
82 "Transaction already in mempool".to_string(),
83 ));
84 }
85
86 if !matches!(check_transaction(tx)?, ValidationResult::Valid) {
88 return Ok(MempoolResult::Rejected(
89 "Invalid transaction structure".to_string(),
90 ));
91 }
92
93 let block_time = time_context.map(|ctx| ctx.median_time_past).unwrap_or(0);
96 if !is_final_tx(tx, height, block_time) {
97 return Ok(MempoolResult::Rejected(
98 "Transaction not final (locktime not satisfied)".to_string(),
99 ));
100 }
101
102 let (input_valid, fee) = check_tx_inputs(tx, utxo_set, height)?;
104 assert!(fee >= 0, "Fee {fee} must be non-negative");
106 use crate::constants::MAX_MONEY;
107 assert!(fee <= MAX_MONEY, "Fee {fee} must not exceed MAX_MONEY");
108 if !matches!(input_valid, ValidationResult::Valid) {
109 return Ok(MempoolResult::Rejected(
110 "Invalid transaction inputs".to_string(),
111 ));
112 }
113
114 if !is_coinbase(tx) {
116 let flags = calculate_script_flags(tx, witnesses, network);
117 if let Some(msg) = verify_mempool_scripts(
118 tx,
119 witnesses,
120 utxo_set,
121 height,
122 time_context,
123 flags,
124 network,
125 )? {
126 return Ok(MempoolResult::Rejected(msg));
127 }
128 }
129
130 if !check_mempool_rules(tx, fee, mempool)? {
132 return Ok(MempoolResult::Rejected("Failed mempool rules".to_string()));
133 }
134
135 if has_conflicts(tx, mempool)? {
137 return Ok(MempoolResult::Rejected(
138 "Transaction conflicts with mempool".to_string(),
139 ));
140 }
141
142 Ok(MempoolResult::Accepted)
143}
144
145fn calculate_script_flags(
152 tx: &Transaction,
153 witnesses: Option<&[Witness]>,
154 network: Network,
155) -> u32 {
156 let has_witness = witnesses.map(|w| !w.is_empty()).unwrap_or(false);
167 const MEMPOOL_POLICY_HEIGHT: u64 = 1_000_000; crate::block::calculate_script_flags_for_block_network(
169 tx,
170 has_witness,
171 MEMPOOL_POLICY_HEIGHT,
172 network,
173 )
174}
175
176#[spec_locked("9.2", "IsStandardTxWithConfig")]
182pub fn is_standard_tx_with_config(
183 tx: &crate::types::Transaction,
184 config: Option<&blvm_primitives::config::MempoolConfig>,
185) -> Result<bool> {
186 let Some(cfg) = config else {
187 return is_standard_tx(tx);
188 };
189
190 let tx_size = crate::transaction::calculate_transaction_size(tx);
192 if tx_size > MAX_TX_SIZE {
193 return Ok(false);
194 }
195 for input in tx.inputs.iter() {
196 if input.script_sig.len() > MAX_SCRIPT_SIZE {
197 return Ok(false);
198 }
199 }
200
201 let max_script = cfg.max_standard_script_size as usize;
202 let max_op_return = cfg.max_op_return_size as usize;
203 let reject_envelope = cfg.reject_envelope_protocol;
204 let reject_multi_op_return = cfg.reject_multiple_op_return;
205
206 let mut op_return_count = 0usize;
207 for output in tx.outputs.iter() {
208 let s = &output.script_pubkey;
209
210 if s.len() > max_script {
212 return Ok(false);
213 }
214
215 if s.first() == Some(&0x6a) {
216 op_return_count += 1;
218 if s.len().saturating_sub(1) > max_op_return {
219 return Ok(false);
220 }
221 } else if s.len() >= 2 && s[0] == 0x00 && s[1] == 0x63 {
222 if reject_envelope {
224 return Ok(false);
225 }
226 }
227 }
228
229 if reject_multi_op_return && op_return_count > 1 {
231 return Ok(false);
232 }
233
234 Ok(true)
235}
236
237#[spec_locked("9.2", "IsStandardTx")]
245pub fn is_standard_tx(tx: &Transaction) -> Result<bool> {
246 let tx_size = calculate_transaction_size(tx);
248 if tx_size > MAX_TX_SIZE {
249 return Ok(false);
250 }
251
252 for (i, input) in tx.inputs.iter().enumerate() {
254 assert!(i < tx.inputs.len(), "Input index {i} out of bounds");
256 assert!(
258 input.script_sig.len() <= MAX_SCRIPT_SIZE * 2,
259 "Script size {} must be reasonable for input {}",
260 input.script_sig.len(),
261 i
262 );
263 if input.script_sig.len() > MAX_SCRIPT_SIZE {
264 return Ok(false);
265 }
266 }
267
268 for (i, output) in tx.outputs.iter().enumerate() {
269 assert!(i < tx.outputs.len(), "Output index {i} out of bounds");
271 assert!(
273 output.script_pubkey.len() <= MAX_SCRIPT_SIZE * 2,
274 "Script size {} must be reasonable for output {}",
275 output.script_pubkey.len(),
276 i
277 );
278 if output.script_pubkey.len() > MAX_SCRIPT_SIZE {
279 return Ok(false);
280 }
281 }
282
283 let mut op_return_count = 0usize;
285 for (i, output) in tx.outputs.iter().enumerate() {
286 assert!(
288 i < tx.outputs.len(),
289 "Output index {i} out of bounds in standard check"
290 );
291 if !is_standard_script(&output.script_pubkey)? {
292 return Ok(false);
293 }
294
295 if output.script_pubkey.first() == Some(&0x6a) {
297 op_return_count += 1;
298 }
299
300 let s = &output.script_pubkey;
302 if s.len() >= 2 && s[0] == 0x00 && s[1] == 0x63 {
303 return Ok(false);
304 }
305 }
306
307 if op_return_count > 1 {
309 return Ok(false);
310 }
311
312 Ok(true)
313}
314
315#[spec_locked("9.3", "ReplacementChecks")]
326pub fn replacement_checks(
327 new_tx: &Transaction,
328 existing_tx: &Transaction,
329 utxo_set: &UtxoSet,
330 mempool: &Mempool,
331) -> Result<bool> {
332 if new_tx.inputs.is_empty() && new_tx.outputs.is_empty() {
337 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
338 "New transaction must have at least one input or output"
339 .to_string()
340 .into(),
341 ));
342 }
343 if existing_tx.inputs.is_empty() && existing_tx.outputs.is_empty() {
344 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
345 "Existing transaction must have at least one input or output"
346 .to_string()
347 .into(),
348 ));
349 }
350 if is_coinbase(new_tx) {
351 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
352 "New transaction cannot be coinbase".to_string().into(),
353 ));
354 }
355 if is_coinbase(existing_tx) {
356 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
357 "Existing transaction cannot be coinbase".to_string().into(),
358 ));
359 }
360 assert!(
361 utxo_set.len() <= u32::MAX as usize,
362 "UTXO set size {} exceeds maximum",
363 utxo_set.len()
364 );
365
366 if !signals_rbf(existing_tx) {
369 return Ok(false);
370 }
371
372 let new_fee = calculate_fee(new_tx, utxo_set)?;
374 let existing_fee = calculate_fee(existing_tx, utxo_set)?;
375 assert!(new_fee >= 0, "New fee {new_fee} must be non-negative");
377 assert!(
378 existing_fee >= 0,
379 "Existing fee {existing_fee} must be non-negative"
380 );
381 use crate::constants::MAX_MONEY;
382 assert!(
383 new_fee <= MAX_MONEY,
384 "New fee {new_fee} must not exceed MAX_MONEY"
385 );
386 assert!(
387 existing_fee <= MAX_MONEY,
388 "Existing fee {existing_fee} must not exceed MAX_MONEY"
389 );
390
391 let new_tx_size = calculate_transaction_size_vbytes(new_tx);
392 let existing_tx_size = calculate_transaction_size_vbytes(existing_tx);
393 assert!(
395 new_tx_size > 0,
396 "New transaction size {new_tx_size} must be positive"
397 );
398 assert!(
399 existing_tx_size > 0,
400 "Existing transaction size {existing_tx_size} must be positive"
401 );
402 assert!(
403 new_tx_size <= MAX_TX_SIZE * 2,
404 "New transaction size {new_tx_size} must be reasonable"
405 );
406 assert!(
407 existing_tx_size <= MAX_TX_SIZE * 2,
408 "Existing transaction size {existing_tx_size} must be reasonable"
409 );
410
411 if new_tx_size == 0 || existing_tx_size == 0 {
412 return Ok(false);
413 }
414
415 debug_assert!(
422 new_tx_size > 0,
423 "New transaction size ({new_tx_size}) must be positive"
424 );
425 debug_assert!(
426 existing_tx_size > 0,
427 "Existing transaction size ({existing_tx_size}) must be positive"
428 );
429
430 let new_fee_scaled = (new_fee as u128)
433 .checked_mul(existing_tx_size as u128)
434 .ok_or_else(|| {
435 ConsensusError::TransactionValidation("Fee rate calculation overflow".into())
436 })?;
437 let existing_fee_scaled = (existing_fee as u128)
438 .checked_mul(new_tx_size as u128)
439 .ok_or_else(|| {
440 ConsensusError::TransactionValidation("Fee rate calculation overflow".into())
441 })?;
442
443 if new_fee_scaled <= existing_fee_scaled {
444 return Ok(false);
445 }
446
447 if new_fee <= existing_fee + MIN_RELAY_FEE {
449 return Ok(false);
450 }
451
452 if !has_conflict_with_tx(new_tx, existing_tx) {
454 return Ok(false);
455 }
456
457 if creates_new_dependencies(new_tx, existing_tx, utxo_set, mempool)? {
460 return Ok(false);
461 }
462
463 Ok(true)
464}
465
466#[derive(Clone, Debug, Default, PartialEq, Eq)]
472pub struct Mempool {
473 txids: HashSet<Hash>,
474 spent_outpoints: HashSet<OutPoint>,
475 tx_spent_outpoints: HashMap<Hash, Vec<OutPoint>>,
477 total_vbytes: usize,
479 tx_vsizes: HashMap<Hash, usize>,
481}
482
483impl Mempool {
484 pub fn new() -> Self {
485 Self::default()
486 }
487
488 pub fn insert(&mut self, txid: Hash) -> bool {
493 self.txids.insert(txid)
494 }
495
496 pub fn insert_transaction(&mut self, tx: &Transaction) -> bool {
498 let txid = crate::block::calculate_tx_id(tx);
499 if !self.txids.insert(txid) {
500 return false;
501 }
502 let vsize = calculate_transaction_size_vbytes(tx);
503 self.total_vbytes = self.total_vbytes.saturating_add(vsize);
504 self.tx_vsizes.insert(txid, vsize);
505 let mut outpoints = Vec::with_capacity(tx.inputs.len());
506 if !is_coinbase(tx) {
507 for input in &tx.inputs {
508 self.spent_outpoints.insert(input.prevout);
509 outpoints.push(input.prevout);
510 }
511 }
512 self.tx_spent_outpoints.insert(txid, outpoints);
513 true
514 }
515
516 pub fn contains(&self, txid: &Hash) -> bool {
517 self.txids.contains(txid)
518 }
519
520 pub fn spends_outpoint(&self, outpoint: &OutPoint) -> bool {
521 self.spent_outpoints.contains(outpoint)
522 }
523
524 pub fn remove(&mut self, txid: &Hash) -> bool {
525 if !self.txids.remove(txid) {
526 return false;
527 }
528 if let Some(vsize) = self.tx_vsizes.remove(txid) {
529 self.total_vbytes = self.total_vbytes.saturating_sub(vsize);
530 }
531 if let Some(outpoints) = self.tx_spent_outpoints.remove(txid) {
532 for op in outpoints {
533 self.spent_outpoints.remove(&op);
534 }
535 }
536 true
537 }
538
539 pub fn remove_spending_any(&mut self, outpoints: &HashSet<OutPoint>) -> Vec<Hash> {
541 if outpoints.is_empty() {
542 return Vec::new();
543 }
544 let to_remove: Vec<Hash> = self
545 .tx_spent_outpoints
546 .iter()
547 .filter(|(_, ops)| ops.iter().any(|op| outpoints.contains(op)))
548 .map(|(id, _)| *id)
549 .collect();
550 let mut removed = Vec::new();
551 for id in to_remove {
552 if self.remove(&id) {
553 removed.push(id);
554 }
555 }
556 removed
557 }
558
559 pub fn len(&self) -> usize {
560 self.txids.len()
561 }
562
563 pub fn is_empty(&self) -> bool {
564 self.txids.is_empty()
565 }
566
567 pub fn iter(&self) -> impl Iterator<Item = &Hash> {
568 self.txids.iter()
569 }
570
571 pub fn clear(&mut self) {
572 self.txids.clear();
573 self.spent_outpoints.clear();
574 self.tx_spent_outpoints.clear();
575 self.total_vbytes = 0;
576 self.tx_vsizes.clear();
577 }
578
579 pub fn total_vbytes(&self) -> usize {
581 self.total_vbytes
582 }
583}
584
585pub(crate) fn mempool_size_limits_exceeded(
587 mempool: &Mempool,
588 additional_vsize: usize,
589 max_txs: usize,
590 max_bytes: usize,
591) -> bool {
592 if mempool.len() >= max_txs {
593 return true;
594 }
595 max_bytes > 0 && mempool.total_vbytes().saturating_add(additional_vsize) > max_bytes
596}
597
598#[derive(Debug, Clone, PartialEq, Eq)]
600pub enum MempoolResult {
601 Accepted,
602 Rejected(String),
603}
604
605pub fn update_mempool_after_block(
666 mempool: &mut Mempool,
667 block: &crate::types::Block,
668 _utxo_set: &crate::types::UtxoSet,
669) -> Result<Vec<Hash>> {
670 let mut removed = Vec::new();
671
672 for tx in &block.transactions {
674 let tx_id = crate::block::calculate_tx_id(tx);
675 if mempool.remove(&tx_id) {
676 removed.push(tx_id);
677 }
678 }
679
680 let mut spent_by_block = HashSet::new();
682 for tx in &block.transactions {
683 if !is_coinbase(tx) {
684 for input in &tx.inputs {
685 spent_by_block.insert(input.prevout);
686 }
687 }
688 }
689 removed.extend(mempool.remove_spending_any(&spent_by_block));
690
691 Ok(removed)
692}
693
694pub fn update_mempool_after_block_with_lookup<F>(
709 mempool: &mut Mempool,
710 block: &crate::types::Block,
711 get_tx_by_id: F,
712) -> Result<Vec<Hash>>
713where
714 F: Fn(&Hash) -> Option<crate::types::Transaction>,
715{
716 let mut removed = Vec::new();
717
718 for tx in &block.transactions {
720 let tx_id = crate::block::calculate_tx_id(tx);
721 if mempool.remove(&tx_id) {
722 removed.push(tx_id);
723 }
724 }
725
726 let mut spent_outpoints = std::collections::HashSet::new();
729 for tx in &block.transactions {
730 if !crate::transaction::is_coinbase(tx) {
731 for input in &tx.inputs {
732 spent_outpoints.insert(input.prevout);
733 }
734 }
735 }
736
737 let mut invalid_tx_ids = Vec::new();
739 for &tx_id in mempool.iter() {
740 if let Some(tx) = get_tx_by_id(&tx_id) {
741 for input in &tx.inputs {
743 if spent_outpoints.contains(&input.prevout) {
744 invalid_tx_ids.push(tx_id);
745 break;
746 }
747 }
748 }
749 }
750
751 for tx_id in invalid_tx_ids {
753 if mempool.remove(&tx_id) {
754 removed.push(tx_id);
755 }
756 }
757
758 Ok(removed)
759}
760
761#[spec_locked("9.1", "CheckMempoolRules")]
763pub(crate) fn check_mempool_rules(
764 tx: &Transaction,
765 fee: Integer,
766 mempool: &Mempool,
767) -> Result<bool> {
768 let vsize = calculate_transaction_size_vbytes(tx);
770 if vsize == 0 {
771 return Ok(false);
772 }
773
774 let config = crate::config::get_consensus_config_ref();
775 let min_fee_rate = config.mempool.min_relay_fee_rate;
776 let min_tx_fee = config.mempool.min_tx_fee;
777
778 if fee < min_tx_fee {
779 return Ok(false);
780 }
781
782 let required_fee = (min_fee_rate as u128).saturating_mul(vsize as u128);
783 if (fee as u128) < required_fee {
784 return Ok(false);
785 }
786
787 let max_bytes = (config.mempool.max_mempool_mb as usize).saturating_mul(1_000_000);
788 if mempool_size_limits_exceeded(mempool, vsize, config.mempool.max_mempool_txs, max_bytes) {
789 return Ok(false);
790 }
791
792 Ok(true)
793}
794
795fn has_conflicts(tx: &Transaction, mempool: &Mempool) -> Result<bool> {
797 for input in &tx.inputs {
798 if mempool.spends_outpoint(&input.prevout) {
799 return Ok(true);
800 }
801 }
802
803 Ok(false)
804}
805
806fn verify_mempool_scripts(
808 tx: &Transaction,
809 witnesses: Option<&[Witness]>,
810 utxo_set: &UtxoSet,
811 height: Natural,
812 _time_context: Option<TimeContext>,
813 flags: u32,
814 network: Network,
815) -> Result<Option<String>> {
816 let mut prevouts = Vec::with_capacity(tx.inputs.len());
817 for input in &tx.inputs {
818 if let Some(utxo) = utxo_set.get(&input.prevout) {
819 prevouts.push(TransactionOutput {
820 value: utxo.value,
821 script_pubkey: utxo.script_pubkey.as_ref().to_vec(),
822 });
823 } else {
824 prevouts.push(TransactionOutput {
825 value: 0,
826 script_pubkey: ByteString::new(),
827 });
828 }
829 }
830
831 for (i, input) in tx.inputs.iter().enumerate() {
832 let Some(utxo) = utxo_set.get(&input.prevout) else {
833 continue;
834 };
835 let witness = witnesses.and_then(|wits| wits.get(i));
836 let is_valid = verify_script_with_context(
837 &input.script_sig,
838 utxo.script_pubkey.as_ref(),
839 witness,
840 flags,
841 tx,
842 i,
843 &prevouts,
844 Some(height),
845 network,
846 )?;
847 if !is_valid {
848 return Ok(Some(format!("Invalid script at input {i}")));
849 }
850 }
851
852 Ok(None)
853}
854
855fn input_allowed_for_replacement(
857 input: &TransactionInput,
858 existing_tx: &Transaction,
859 utxo_set: &UtxoSet,
860) -> bool {
861 if utxo_set.contains_key(&input.prevout) {
862 return true;
863 }
864 if existing_tx
865 .inputs
866 .iter()
867 .any(|existing_input| existing_input.prevout == input.prevout)
868 {
869 return true;
870 }
871 let existing_id = crate::block::calculate_tx_id(existing_tx);
872 (input.prevout.hash == existing_id)
873 && ((input.prevout.index as usize) < existing_tx.outputs.len())
874}
875
876#[spec_locked("9.1.1", "CheckFinalTxAtTip")]
917pub fn is_final_tx(tx: &Transaction, height: Natural, block_time: Natural) -> bool {
918 use crate::constants::SEQUENCE_FINAL;
919
920 if tx.lock_time == 0 {
922 return true;
923 }
924
925 let locktime_satisfied = if (tx.lock_time as u32) < LOCKTIME_THRESHOLD {
931 (tx.lock_time as Natural) < height
933 } else {
934 (tx.lock_time as Natural) < block_time
936 };
937
938 if locktime_satisfied {
939 return true;
940 }
941
942 for input in &tx.inputs {
946 if (input.sequence as u32) != SEQUENCE_FINAL {
947 return false;
948 }
949 }
950
951 true
953}
954
955#[spec_locked("9.3", "SignalsRBF")]
959pub fn signals_rbf(tx: &Transaction) -> bool {
960 for input in &tx.inputs {
961 if (input.sequence as u32) < SEQUENCE_FINAL {
962 return true;
963 }
964 }
965 false
966}
967
968fn calculate_transaction_size_vbytes(tx: &Transaction) -> usize {
972 use crate::segwit::calculate_transaction_weight;
973 use crate::witness::weight_to_vsize;
974 match calculate_transaction_weight(tx, None) {
975 Ok(weight) => weight_to_vsize(weight) as usize,
976 Err(_) => calculate_transaction_size(tx),
977 }
978}
979
980#[spec_locked("9.3", "HasConflictWithTx")]
985pub fn has_conflict_with_tx(new_tx: &Transaction, existing_tx: &Transaction) -> bool {
986 for new_input in &new_tx.inputs {
987 for existing_input in &existing_tx.inputs {
988 if new_input.prevout == existing_input.prevout {
989 return true;
990 }
991 }
992 }
993 false
994}
995
996#[spec_locked("9.3", "CreatesNewDependencies")]
1002pub(crate) fn creates_new_dependencies(
1003 new_tx: &Transaction,
1004 existing_tx: &Transaction,
1005 utxo_set: &UtxoSet,
1006 _mempool: &Mempool,
1007) -> Result<bool> {
1008 for input in &new_tx.inputs {
1009 if utxo_set.contains_key(&input.prevout) {
1011 continue;
1012 }
1013
1014 let mut found_in_existing = false;
1016 for existing_input in &existing_tx.inputs {
1017 if existing_input.prevout == input.prevout {
1018 found_in_existing = true;
1019 break;
1020 }
1021 }
1022
1023 if found_in_existing {
1024 continue;
1025 }
1026
1027 if input_allowed_for_replacement(input, existing_tx, utxo_set) {
1028 continue;
1029 }
1030
1031 return Ok(true);
1032 }
1033
1034 Ok(false)
1035}
1036
1037fn script_opcode_advance(script: &[u8], pc: usize) -> usize {
1039 let opcode = script[pc];
1040 match opcode {
1041 0x01..=0x4b => 1 + opcode as usize,
1042 0x4c => {
1043 if pc + 1 < script.len() {
1044 2 + script[pc + 1] as usize
1045 } else {
1046 1
1047 }
1048 }
1049 0x4d => {
1050 if pc + 2 < script.len() {
1051 3 + u16::from_le_bytes([script[pc + 1], script[pc + 2]]) as usize
1052 } else {
1053 1
1054 }
1055 }
1056 0x4e => {
1057 if pc + 4 < script.len() {
1058 5 + u32::from_le_bytes([
1059 script[pc + 1],
1060 script[pc + 2],
1061 script[pc + 3],
1062 script[pc + 4],
1063 ]) as usize
1064 } else {
1065 1
1066 }
1067 }
1068 _ => 1,
1069 }
1070}
1071
1072#[inline]
1074fn is_disabled_policy_opcode(opcode: u8) -> bool {
1075 use crate::opcodes::{
1076 OP_2DIV, OP_2MUL, OP_AND, OP_CAT, OP_DIV, OP_INVERT, OP_LEFT, OP_LSHIFT, OP_MOD, OP_MUL,
1077 OP_OR, OP_RIGHT, OP_RSHIFT, OP_SUBSTR, OP_VER, OP_VERIF, OP_VERNOTIF, OP_XOR,
1078 };
1079 matches!(
1080 opcode,
1081 OP_VER
1082 | OP_VERIF
1083 | OP_VERNOTIF
1084 | OP_CAT
1085 | OP_SUBSTR
1086 | OP_LEFT
1087 | OP_RIGHT
1088 | OP_INVERT
1089 | OP_AND
1090 | OP_OR
1091 | OP_XOR
1092 | OP_2MUL
1093 | OP_2DIV
1094 | OP_MUL
1095 | OP_DIV
1096 | OP_MOD
1097 | OP_LSHIFT
1098 | OP_RSHIFT
1099 )
1100}
1101
1102#[spec_locked("9.1", "IsStandardScript")]
1104pub(crate) fn is_standard_script(script: &ByteString) -> Result<bool> {
1105 if script.is_empty() {
1106 return Ok(false);
1107 }
1108
1109 if script.len() > MAX_SCRIPT_SIZE {
1110 return Ok(false);
1111 }
1112
1113 if script[0] == 0x6a {
1116 return Ok(script.len() <= 83);
1118 }
1119
1120 let mut pc = 0;
1122 while pc < script.len() {
1123 let opcode = script[pc];
1124 if is_disabled_policy_opcode(opcode) {
1125 return Ok(false);
1126 }
1127 let advance = script_opcode_advance(script, pc);
1128 if advance == 0 {
1129 break;
1130 }
1131 pc += advance;
1132 }
1133
1134 Ok(true)
1135}
1136
1137#[deprecated(note = "Use crate::block::calculate_tx_id instead")]
1142#[spec_locked("5.1", "CalculateTxId")]
1143pub fn calculate_tx_id(tx: &Transaction) -> Hash {
1144 crate::block::calculate_tx_id(tx)
1145}
1146
1147fn calculate_transaction_size(tx: &Transaction) -> usize {
1151 use crate::transaction::calculate_transaction_size as tx_size;
1152 tx_size(tx)
1153}
1154
1155fn is_coinbase(tx: &Transaction) -> bool {
1157 #[cfg(feature = "production")]
1159 {
1160 use crate::optimizations::constant_folding::is_zero_hash;
1161 tx.inputs.len() == 1
1162 && is_zero_hash(&tx.inputs[0].prevout.hash)
1163 && tx.inputs[0].prevout.index == 0xffffffff
1164 }
1165
1166 #[cfg(not(feature = "production"))]
1167 {
1168 tx.inputs.len() == 1
1169 && tx.inputs[0].prevout.hash == [0u8; 32]
1170 && tx.inputs[0].prevout.index == 0xffffffff
1171 }
1172}
1173
1174#[cfg(test)]
1194mod tests {
1195 use super::*;
1196 use crate::opcodes::*;
1197
1198 #[test]
1199 fn test_accept_to_memory_pool_valid() {
1200 let tx = create_valid_transaction();
1202 let utxo_set = create_test_utxo_set();
1203 let mempool = Mempool::new();
1204
1205 let time_context = Some(TimeContext {
1207 network_time: 1234567890,
1208 median_time_past: 1234567890,
1209 });
1210 let result = accept_to_memory_pool(
1211 &tx,
1212 None,
1213 &utxo_set,
1214 &mempool,
1215 100,
1216 time_context,
1217 Network::Mainnet,
1218 )
1219 .unwrap();
1220 assert!(matches!(result, MempoolResult::Accepted));
1221 }
1222
1223 #[test]
1224 fn test_accept_to_memory_pool_duplicate() {
1225 let tx = create_valid_transaction();
1226 let utxo_set = create_test_utxo_set();
1227 let mut mempool = Mempool::new();
1228 mempool.insert(crate::block::calculate_tx_id(&tx));
1229
1230 let time_context = Some(TimeContext {
1231 network_time: 1234567890,
1232 median_time_past: 1234567890,
1233 });
1234 let result = accept_to_memory_pool(
1235 &tx,
1236 None,
1237 &utxo_set,
1238 &mempool,
1239 100,
1240 time_context,
1241 Network::Mainnet,
1242 )
1243 .unwrap();
1244 assert!(matches!(result, MempoolResult::Rejected(_)));
1245 }
1246
1247 #[test]
1248 fn test_is_standard_tx_valid() {
1249 let tx = create_valid_transaction();
1250 assert!(is_standard_tx(&tx).unwrap());
1251 }
1252
1253 #[test]
1254 fn test_is_standard_tx_too_large() {
1255 let mut tx = create_valid_transaction();
1256 for _ in 0..MAX_INPUTS {
1259 tx.inputs.push(create_dummy_input());
1260 }
1261 assert!(!is_standard_tx(&tx).unwrap());
1263 }
1264
1265 #[test]
1266 fn test_replacement_checks_all_requirements() {
1267 let utxo_set = create_test_utxo_set();
1268 let mempool = Mempool::new();
1269
1270 let mut existing_tx = create_valid_transaction();
1272 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1273 existing_tx.outputs[0].value = 9000; let mut new_tx = existing_tx.clone();
1280 new_tx.outputs[0].value = 8000; new_tx.outputs[0].value = 7999; let result = replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
1286 assert!(result, "Valid RBF replacement should be accepted");
1287 }
1288
1289 #[test]
1290 fn test_replacement_checks_no_rbf_signal() {
1291 let utxo_set = create_test_utxo_set();
1292 let mempool = Mempool::new();
1293
1294 let new_tx = create_valid_transaction();
1295 let existing_tx = create_valid_transaction(); assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1299 }
1300
1301 #[test]
1302 fn test_replacement_checks_no_conflict() {
1303 let mut utxo_set = create_test_utxo_set();
1304 let new_outpoint = OutPoint {
1306 hash: [2; 32],
1307 index: 0,
1308 };
1309 let new_utxo = UTXO {
1310 value: 10000,
1311 script_pubkey: vec![OP_1].into(),
1312 height: 0,
1313 is_coinbase: false,
1314 };
1315 utxo_set.insert(new_outpoint, std::sync::Arc::new(new_utxo));
1316
1317 let mempool = Mempool::new();
1318
1319 let mut existing_tx = create_valid_transaction();
1320 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1321
1322 let mut new_tx = create_valid_transaction();
1324 new_tx.inputs[0].prevout.hash = [2; 32]; new_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1326 new_tx.outputs[0].value = 5000; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1331 }
1332
1333 #[test]
1334 fn test_replacement_checks_fee_rate_too_low() {
1335 let utxo_set = create_test_utxo_set();
1336 let mempool = Mempool::new();
1337
1338 let mut existing_tx = create_valid_transaction();
1340 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1341 existing_tx.outputs[0].value = 5000; let mut new_tx = existing_tx.clone();
1345 new_tx.outputs[0].value = 4999; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1349 }
1350
1351 #[test]
1352 fn test_replacement_checks_absolute_fee_insufficient() {
1353 let utxo_set = create_test_utxo_set();
1354 let mempool = Mempool::new();
1355
1356 let mut existing_tx = create_valid_transaction();
1358 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1359 existing_tx.outputs[0].value = 9000; let mut new_tx = existing_tx.clone();
1364 new_tx.outputs[0].value = 8001; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1368
1369 new_tx.outputs[0].value = 7999; }
1374
1375 #[test]
1380 fn test_accept_to_memory_pool_coinbase() {
1381 let coinbase_tx = create_coinbase_transaction();
1382 let utxo_set = UtxoSet::default();
1383 let mempool = Mempool::new();
1384 let time_context = Some(TimeContext {
1386 network_time: 0,
1387 median_time_past: 0,
1388 });
1389 let result = accept_to_memory_pool(
1390 &coinbase_tx,
1391 None,
1392 &utxo_set,
1393 &mempool,
1394 100,
1395 time_context,
1396 Network::Mainnet,
1397 )
1398 .unwrap();
1399 assert!(matches!(result, MempoolResult::Rejected(_)));
1400 }
1401
1402 #[test]
1403 fn test_is_standard_tx_large_script() {
1404 let mut tx = create_valid_transaction();
1405 tx.inputs[0].script_sig = vec![OP_1; MAX_SCRIPT_SIZE + 1];
1407
1408 let result = is_standard_tx(&tx).unwrap();
1409 assert!(!result);
1410 }
1411
1412 #[test]
1413 fn test_is_standard_tx_large_output_script() {
1414 let mut tx = create_valid_transaction();
1415 tx.outputs[0].script_pubkey = vec![OP_1; MAX_SCRIPT_SIZE + 1];
1417
1418 let result = is_standard_tx(&tx).unwrap();
1419 assert!(!result);
1420 }
1421
1422 #[test]
1423 fn test_replacement_checks_new_unconfirmed_dependency() {
1424 let utxo_set = create_test_utxo_set();
1425 let mempool = Mempool::new();
1426
1427 let mut existing_tx = create_valid_transaction();
1429 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1430
1431 let mut new_tx = existing_tx.clone();
1433 new_tx.inputs.push(TransactionInput {
1434 prevout: OutPoint {
1435 hash: [99; 32],
1436 index: 0,
1437 }, script_sig: vec![],
1439 sequence: SEQUENCE_RBF as u64,
1440 });
1441 new_tx.outputs[0].value = 7000; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1445 }
1446
1447 #[test]
1448 fn test_has_conflict_with_tx_true() {
1449 let tx1 = create_valid_transaction();
1450 let mut tx2 = create_valid_transaction();
1451 tx2.inputs[0].prevout = tx1.inputs[0].prevout; assert!(has_conflict_with_tx(&tx2, &tx1));
1454 }
1455
1456 #[test]
1457 fn test_has_conflict_with_tx_false() {
1458 let tx1 = create_valid_transaction();
1459 let mut tx2 = create_valid_transaction();
1460 tx2.inputs[0].prevout.hash = [2; 32]; assert!(!has_conflict_with_tx(&tx2, &tx1));
1463 }
1464
1465 #[test]
1466 fn test_replacement_checks_minimum_relay_fee() {
1467 let utxo_set = create_test_utxo_set();
1468 let mempool = Mempool::new();
1469
1470 let mut existing_tx = create_valid_transaction();
1472 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1473 existing_tx.outputs[0].value = 9500; let mut new_tx = existing_tx.clone();
1477 new_tx.outputs[0].value = 8500; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1479
1480 new_tx.outputs[0].value = 8499;
1484 }
1485
1486 #[test]
1487 fn test_check_mempool_rules_low_fee() {
1488 let tx = create_valid_transaction();
1489 let fee = 1; let mempool = Mempool::new();
1491
1492 let result = check_mempool_rules(&tx, fee, &mempool).unwrap();
1493 assert!(!result);
1494 }
1495
1496 #[test]
1497 fn test_check_mempool_rules_high_fee() {
1498 let tx = create_valid_transaction();
1499 let fee = 10000; let mempool = Mempool::new();
1501
1502 let result = check_mempool_rules(&tx, fee, &mempool).unwrap();
1503 assert!(result);
1504 }
1505
1506 #[test]
1507 fn test_mempool_total_vbytes_tracking() {
1508 let mut mempool = Mempool::new();
1509 let tx = create_valid_transaction();
1510 let vsize = calculate_transaction_size_vbytes(&tx);
1511 let txid = crate::block::calculate_tx_id(&tx);
1512
1513 mempool.insert_transaction(&tx);
1514 assert_eq!(mempool.total_vbytes(), vsize);
1515
1516 mempool.remove(&txid);
1517 assert_eq!(mempool.total_vbytes(), 0);
1518 }
1519
1520 #[test]
1521 fn test_mempool_byte_limit_exceeded() {
1522 let mut mempool = Mempool::new();
1523 let tx = create_valid_transaction();
1524 let vsize = calculate_transaction_size_vbytes(&tx);
1525 mempool.insert_transaction(&tx);
1526
1527 let at_limit = mempool.total_vbytes();
1528 assert!(!mempool_size_limits_exceeded(
1529 &mempool, 0, 1_000_000, at_limit
1530 ));
1531 assert!(mempool_size_limits_exceeded(
1532 &mempool, 1, 1_000_000, at_limit
1533 ));
1534 assert!(!mempool_size_limits_exceeded(
1535 &mempool,
1536 vsize,
1537 1_000_000,
1538 at_limit.saturating_add(vsize)
1539 ));
1540 }
1541
1542 #[test]
1543 fn test_check_mempool_rules_full_mempool() {
1544 let tx = create_valid_transaction();
1545 let fee = 10000;
1546 let mut mempool = Mempool::new();
1547
1548 for i in 0..100_000 {
1550 let mut hash = [0u8; 32];
1551 hash[0] = (i & 0xff) as u8;
1552 hash[1] = ((i >> 8) & 0xff) as u8;
1553 hash[2] = ((i >> 16) & 0xff) as u8;
1554 hash[3] = ((i >> 24) & 0xff) as u8;
1555 mempool.insert(hash);
1556 }
1557
1558 assert_eq!(mempool.len(), 100_000);
1559
1560 let result = check_mempool_rules(&tx, fee, &mempool).unwrap();
1561 assert!(!result);
1562 }
1563
1564 #[test]
1565 fn test_has_conflicts_no_conflicts() {
1566 let tx = create_valid_transaction();
1567 let mempool = Mempool::new();
1568
1569 let result = has_conflicts(&tx, &mempool).unwrap();
1570 assert!(!result);
1571 }
1572
1573 #[test]
1574 fn test_has_conflicts_with_conflicts() {
1575 let tx = create_valid_transaction();
1576 let mut mempool = Mempool::new();
1577
1578 let mut pool_tx = tx.clone();
1580 pool_tx.version = 2;
1581 mempool.insert_transaction(&pool_tx);
1582
1583 let result = has_conflicts(&tx, &mempool).unwrap();
1584 assert!(result);
1585 }
1586
1587 #[test]
1588 fn test_signals_rbf_true() {
1589 let mut tx = create_valid_transaction();
1590 tx.inputs[0].sequence = 0xfffffffe; assert!(signals_rbf(&tx));
1593 }
1594
1595 #[test]
1596 fn test_signals_rbf_false() {
1597 let tx = create_valid_transaction(); assert!(!signals_rbf(&tx));
1600 }
1601
1602 #[test]
1603 fn test_calculate_fee_rate() {
1604 let tx = create_valid_transaction();
1605 let utxo_set = create_test_utxo_set();
1606 let fee = calculate_fee(&tx, &utxo_set);
1607
1608 assert!(fee.is_ok());
1610 }
1611
1612 #[test]
1613 fn test_creates_new_dependencies_no_new() {
1614 let new_tx = create_valid_transaction();
1615 let existing_tx = create_valid_transaction();
1616 let mempool = Mempool::new();
1617
1618 let utxo_set = create_test_utxo_set();
1619 let result = creates_new_dependencies(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
1620 assert!(!result);
1621 }
1622
1623 #[test]
1624 fn test_creates_new_dependencies_with_new() {
1625 let mut new_tx = create_valid_transaction();
1626 let existing_tx = create_valid_transaction();
1627 let mempool = Mempool::new();
1628
1629 new_tx.inputs[0].prevout.hash = [2; 32];
1631
1632 let utxo_set = create_test_utxo_set();
1633 let result = creates_new_dependencies(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
1634 assert!(result);
1635 }
1636
1637 #[test]
1638 fn test_is_standard_script_empty() {
1639 let script = vec![];
1640 let result = is_standard_script(&script).unwrap();
1641 assert!(!result);
1642 }
1643
1644 #[test]
1645 fn test_is_standard_script_too_large() {
1646 let script = vec![OP_1; MAX_SCRIPT_SIZE + 1];
1647 let result = is_standard_script(&script).unwrap();
1648 assert!(!result);
1649 }
1650
1651 #[test]
1652 fn test_is_standard_script_non_standard_opcode() {
1653 let script = vec![OP_VERIF]; let result = is_standard_script(&script).unwrap();
1655 assert!(!result);
1656 }
1657
1658 #[test]
1659 fn test_is_standard_script_valid() {
1660 let script = vec![OP_1];
1661 let result = is_standard_script(&script).unwrap();
1662 assert!(result);
1663 }
1664
1665 #[test]
1666 fn test_calculate_tx_id() {
1667 let tx = create_valid_transaction();
1668 let tx_id = crate::block::calculate_tx_id(&tx);
1669
1670 assert_eq!(tx_id.len(), 32);
1672
1673 let tx_id2 = crate::block::calculate_tx_id(&tx);
1675 assert_eq!(tx_id, tx_id2);
1676 }
1677
1678 #[test]
1679 fn test_calculate_tx_id_different_txs() {
1680 let tx1 = create_valid_transaction();
1681 let mut tx2 = tx1.clone();
1682 tx2.version = 2; let id1 = crate::block::calculate_tx_id(&tx1);
1685 let id2 = crate::block::calculate_tx_id(&tx2);
1686
1687 assert_ne!(id1, id2);
1688 }
1689
1690 #[test]
1691 fn test_calculate_transaction_size() {
1692 let tx = create_valid_transaction();
1693 let size = calculate_transaction_size(&tx);
1694
1695 assert!(size > 0);
1696
1697 let size2 = calculate_transaction_size(&tx);
1699 assert_eq!(size, size2);
1700 }
1701
1702 #[test]
1703 fn test_calculate_transaction_size_multiple_inputs_outputs() {
1704 let mut tx = create_valid_transaction();
1705 tx.inputs.push(create_dummy_input());
1706 tx.outputs.push(create_dummy_output());
1707
1708 let size = calculate_transaction_size(&tx);
1709 assert!(size > 0);
1710 }
1711
1712 #[test]
1713 fn test_is_coinbase_true() {
1714 let coinbase_tx = create_coinbase_transaction();
1715 assert!(is_coinbase(&coinbase_tx));
1716 }
1717
1718 #[test]
1719 fn test_is_coinbase_false() {
1720 let regular_tx = create_valid_transaction();
1721 assert!(!is_coinbase(®ular_tx));
1722 }
1723
1724 fn create_valid_transaction() -> Transaction {
1726 Transaction {
1727 version: 1,
1728 inputs: vec![create_dummy_input()].into(),
1729 outputs: vec![create_dummy_output()].into(),
1730 lock_time: 0,
1731 }
1732 }
1733
1734 fn create_dummy_input() -> TransactionInput {
1735 TransactionInput {
1736 prevout: OutPoint {
1737 hash: [1; 32],
1738 index: 0,
1739 },
1740 script_sig: vec![OP_1],
1741 sequence: 0xffffffff,
1742 }
1743 }
1744
1745 fn create_dummy_output() -> TransactionOutput {
1746 TransactionOutput {
1747 value: 1000,
1748 script_pubkey: vec![OP_1], }
1750 }
1751
1752 fn create_test_utxo_set() -> UtxoSet {
1753 let mut utxo_set = UtxoSet::default();
1754 let outpoint = OutPoint {
1755 hash: [1; 32],
1756 index: 0,
1757 };
1758 let utxo = UTXO {
1759 value: 10000,
1760 script_pubkey: vec![OP_1].into(), height: 0,
1762 is_coinbase: false,
1763 };
1764 utxo_set.insert(outpoint, std::sync::Arc::new(utxo));
1765 utxo_set
1766 }
1767
1768 fn create_coinbase_transaction() -> Transaction {
1769 Transaction {
1770 version: 1,
1771 inputs: vec![TransactionInput {
1772 prevout: OutPoint {
1773 hash: [0; 32],
1774 index: 0xffffffff,
1775 },
1776 script_sig: vec![],
1777 sequence: 0xffffffff,
1778 }]
1779 .into(),
1780 outputs: vec![TransactionOutput {
1781 value: 5000000000,
1782 script_pubkey: vec![],
1783 }]
1784 .into(),
1785 lock_time: 0,
1786 }
1787 }
1788}