1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
use kaspa_consensus_core::{
    errors::tx::TxRuleError,
    tx::{TransactionId, TransactionOutpoint},
};
use thiserror::Error;

#[derive(Error, Debug, Clone)]
pub enum RuleError {
    /// A consensus transaction rule error
    ///
    /// Note that following variants are converted:
    ///
    /// - TxRuleError::ImmatureCoinbaseSpend => RuleError::RejectImmatureSpend
    /// - TxRuleError::MissingTxOutpoints => RuleError::RejectMissingOutpoint
    #[error(transparent)]
    RejectTxRule(TxRuleError),

    #[error("at least one outpoint of transaction is lacking a matching UTXO entry")]
    RejectMissingOutpoint,

    #[error("transaction {0} was already accepted by the consensus")]
    RejectAlreadyAccepted(TransactionId),

    #[error("transaction {0} is already in the mempool")]
    RejectDuplicate(TransactionId),

    #[error("output {0} already spent by transaction {1} in the memory pool")]
    RejectDoubleSpendInMempool(TransactionOutpoint, TransactionId),

    /// New behavior: a transaction is rejected if the mempool is full
    #[error("number of high-priority transactions in mempool ({0}) has reached the maximum allowed ({1})")]
    RejectMempoolIsFull(usize, u64),

    /// An error emitted by mining\src\mempool\check_transaction_standard.rs
    #[error("transaction {0} is not standard: {1}")]
    RejectNonStandard(TransactionId, String),

    #[error("one of the transaction inputs spends an immature UTXO: {0}")]
    RejectImmatureSpend(TxRuleError),

    #[error("transaction {0} doesn't exist in transaction pool")]
    RejectMissingTransaction(TransactionId),

    #[error("orphan transaction size of {0} bytes is larger than max allowed size of {1} bytes")]
    RejectBadOrphanMass(u64, u64),

    #[error("orphan transaction {0} is already in the orphan pool")]
    RejectDuplicateOrphan(TransactionId),

    #[error("orphan transaction {0} is double spending an input from already existing orphan {1}")]
    RejectDoubleSpendOrphan(TransactionId, TransactionId),

    #[error("transaction {0} is an orphan where orphan is disallowed")]
    RejectDisallowedOrphan(TransactionId),

    #[error("input No. {0} of {1} ({2}) doesn't exist in orphan_ids_by_previous_outpoint")]
    RejectMissingOrphanOutpoint(usize, TransactionId, TransactionOutpoint),

    #[error("transaction {0} doesn't exist in orphan pool")]
    RejectMissingOrphanTransaction(TransactionId),

    /// New behavior: a transaction is rejected if the orphan pool is full
    #[error("number of high-priority transactions in orphan pool ({0}) has reached the maximum allowed ({1})")]
    RejectOrphanPoolIsFull(usize, u64),

    #[error("transactions in mempool form a cycle")]
    RejectCycleInMempoolTransactions,

    // TODO: This error is added for the tx_relay flow but is never constructed neither in the golang nor in this version. Discuss if it can be removed.
    #[error("transaction {0} is invalid")]
    RejectInvalid(TransactionId),

    #[error("Rejected spam tx {0} from mempool")]
    RejectSpamTransaction(TransactionId),

    #[error("Rejected tx {0} from mempool due to incomputable storage mass")]
    RejectStorageMassIncomputable(TransactionId),
}

impl From<NonStandardError> for RuleError {
    fn from(item: NonStandardError) -> Self {
        RuleError::RejectNonStandard(*item.transaction_id(), item.to_string())
    }
}

impl From<TxRuleError> for RuleError {
    fn from(item: TxRuleError) -> Self {
        match item {
            TxRuleError::ImmatureCoinbaseSpend(_, _, _, _, _) => RuleError::RejectImmatureSpend(item),
            TxRuleError::MissingTxOutpoints => RuleError::RejectMissingOutpoint,
            _ => RuleError::RejectTxRule(item),
        }
    }
}

pub type RuleResult<T> = std::result::Result<T, RuleError>;

#[derive(Error, Debug, Clone)]
pub enum NonStandardError {
    #[error("transaction version {1} is not in the valid range of {2}-{3}")]
    RejectVersion(TransactionId, u16, u16, u16),

    #[error("transaction mass of {1} is larger than max allowed size of {2}")]
    RejectMass(TransactionId, u64, u64),

    #[error("transaction mass in context (including storage mass) of {1} is larger than max allowed size of {2}")]
    RejectContextualMass(TransactionId, u64, u64),

    #[error("transaction input #{1}: signature script size of {2} bytes is larger than the maximum allowed size of {3} bytes")]
    RejectSignatureScriptSize(TransactionId, usize, u64, u64),

    #[error("transaction output #{1}: the version of the scriptPublicKey is higher than the known version")]
    RejectScriptPublicKeyVersion(TransactionId, usize),

    #[error("transaction output #{1}: non-standard script form")]
    RejectOutputScriptClass(TransactionId, usize),

    #[error("transaction output #{1}: payment of {2} is dust")]
    RejectDust(TransactionId, usize, u64),

    #[error("transaction input {1}: non-standard script form")]
    RejectInputScriptClass(TransactionId, usize),

    #[error("transaction has {1} fees which is under the required amount of {2}")]
    RejectInsufficientFee(TransactionId, u64, u64),

    #[error("transaction input #{1} has {2} signature operations which is more than the allowed max amount of {3}")]
    RejectSignatureCount(TransactionId, usize, u8, u8),
}

impl NonStandardError {
    pub fn transaction_id(&self) -> &TransactionId {
        match self {
            NonStandardError::RejectVersion(id, _, _, _) => id,
            NonStandardError::RejectMass(id, _, _) => id,
            NonStandardError::RejectContextualMass(id, _, _) => id,
            NonStandardError::RejectSignatureScriptSize(id, _, _, _) => id,
            NonStandardError::RejectScriptPublicKeyVersion(id, _) => id,
            NonStandardError::RejectOutputScriptClass(id, _) => id,
            NonStandardError::RejectDust(id, _, _) => id,
            NonStandardError::RejectInputScriptClass(id, _) => id,
            NonStandardError::RejectInsufficientFee(id, _, _) => id,
            NonStandardError::RejectSignatureCount(id, _, _, _) => id,
        }
    }
}

pub type NonStandardResult<T> = std::result::Result<T, NonStandardError>;