subcoin_primitives/
consensus.rs1use crate::MAX_BLOCK_WEIGHT;
2use bitcoin::blockdata::weight::WITNESS_SCALE_FACTOR;
3use bitcoin::{Amount, Transaction, Weight};
4use std::collections::HashSet;
5
6const MIN_COINBASE_SCRIPT_LEN: usize = 2;
8
9const MAX_COINBASE_SCRIPT_LEN: usize = 100;
11
12#[derive(Debug, thiserror::Error)]
14pub enum TxError {
15 #[error("Transaction has no inputs")]
16 EmptyInput,
17 #[error("Transaction has no outputs")]
18 EmptyOutput,
19 #[error("Transaction is too large")]
20 TransactionOversize,
21 #[error("Transaction contains duplicate inputs at index {0}")]
22 DuplicateTxInput(usize),
23 #[error("Output value (0) is too large")]
24 OutputValueTooLarge(Amount),
25 #[error("Total output value (0) is too large")]
26 TotalOutputValueTooLarge(Amount),
27 #[error(
28 "Coinbase transaction script length of {0} is out of range \
29 (min: {MIN_COINBASE_SCRIPT_LEN}, max: {MAX_COINBASE_SCRIPT_LEN})"
30 )]
31 BadCoinbaseLength(usize),
32 #[error("Transaction input refers to a previous output that is null")]
33 PreviousOutputNull,
34}
35
36pub fn check_transaction_sanity(tx: &Transaction) -> Result<(), TxError> {
39 if tx.input.is_empty() {
40 return Err(TxError::EmptyInput);
41 }
42
43 if tx.output.is_empty() {
44 return Err(TxError::EmptyOutput);
45 }
46
47 if Weight::from_wu((tx.base_size() * WITNESS_SCALE_FACTOR) as u64) > MAX_BLOCK_WEIGHT {
48 return Err(TxError::TransactionOversize);
49 }
50
51 let mut value_out = Amount::ZERO;
52 tx.output.iter().try_for_each(|txout| {
53 if txout.value > Amount::MAX_MONEY {
54 return Err(TxError::OutputValueTooLarge(txout.value));
55 }
56
57 value_out += txout.value;
58
59 if value_out > Amount::MAX_MONEY {
60 return Err(TxError::TotalOutputValueTooLarge(value_out));
61 }
62
63 Ok(())
64 })?;
65
66 let mut seen_inputs = HashSet::with_capacity(tx.input.len());
68 for (index, txin) in tx.input.iter().enumerate() {
69 if !seen_inputs.insert(txin.previous_output) {
70 return Err(TxError::DuplicateTxInput(index));
71 }
72 }
73
74 if tx.is_coinbase() {
76 let script_sig_len = tx.input[0].script_sig.len();
77
78 if !(MIN_COINBASE_SCRIPT_LEN..=MAX_COINBASE_SCRIPT_LEN).contains(&script_sig_len) {
79 return Err(TxError::BadCoinbaseLength(script_sig_len));
80 }
81 } else {
82 if tx.input.iter().any(|txin| txin.previous_output.is_null()) {
85 return Err(TxError::PreviousOutputNull);
86 }
87 }
88
89 Ok(())
90}