Skip to main content

tidecoin_consensus_core/
error.rs

1// SPDX-License-Identifier: CC0-1.0
2
3use core::fmt;
4
5use primitives::transaction::OutPoint;
6
7/// Script validation errors produced by the Tidecoin validation engine.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ScriptError {
10    /// Default value used before script execution sets a more specific error.
11    Ok,
12    /// Generic script failure when no more specific code is available.
13    Unknown,
14    /// Script evaluation left a false top stack item.
15    EvalFalse,
16    /// `OP_RETURN` aborted script execution.
17    OpReturn,
18    /// Script exceeded the maximum allowed byte size.
19    ScriptSize,
20    /// A pushed element exceeded the maximum allowed byte size.
21    PushSize,
22    /// Script exceeded the maximum allowed opcode count.
23    OpCount,
24    /// Script exceeded the maximum allowed stack size.
25    StackSize,
26    /// Multisig signature count was invalid.
27    SigCount,
28    /// Multisig public-key count was invalid.
29    PubkeyCount,
30    /// `OP_VERIFY` failed.
31    Verify,
32    /// `OP_EQUALVERIFY` failed.
33    EqualVerify,
34    /// `OP_CHECKSIGVERIFY` failed.
35    CheckSigVerify,
36    /// `OP_CHECKMULTISIGVERIFY` failed.
37    CheckMultiSigVerify,
38    /// `OP_NUMEQUALVERIFY` failed.
39    NumEqualVerify,
40    /// Script contained an invalid opcode.
41    BadOpcode,
42    /// `OP_CODESEPARATOR` was disallowed by the active flags.
43    OpCodeSeparator,
44    /// Script used a disabled opcode.
45    DisabledOpcode,
46    /// Script attempted an invalid main-stack operation.
47    InvalidStackOperation,
48    /// Script attempted an invalid alt-stack operation.
49    InvalidAltstackOperation,
50    /// Script conditionals were unbalanced.
51    UnbalancedConditional,
52    /// Locktime argument was negative.
53    NegativeLockTime,
54    /// Locktime requirement was not satisfied.
55    UnsatisfiedLockTime,
56    /// Signature hash type was invalid for the current validation path.
57    SigHashType,
58    /// Non-minimal pushdata encoding was used when forbidden.
59    MinimalData,
60    /// `scriptSig` was not push-only when required.
61    SigPushOnly,
62    /// Dummy stack argument for multisig was not null.
63    SigNullDummy,
64    /// Cleanstack rule was violated.
65    CleanStack,
66    /// `MINIMALIF` rule was violated.
67    MinimalIf,
68    /// `NULLFAIL` rule was violated.
69    NullFail,
70    /// Upgradable NOPs were discouraged and encountered.
71    DiscourageUpgradableNops,
72    /// Upgradable witness programs were discouraged and encountered.
73    DiscourageUpgradableWitnessProgram,
74    /// Witness program length did not match the expected form.
75    WitnessProgramWrongLength,
76    /// Witness program required witness items but found none.
77    WitnessProgramWitnessEmpty,
78    /// Witness program did not match the executed witness script.
79    WitnessProgramMismatch,
80    /// Native witness spend was malleated.
81    WitnessMalleated,
82    /// P2SH-wrapped witness spend was malleated.
83    WitnessMalleatedP2SH,
84    /// Unexpected witness data was present.
85    WitnessUnexpected,
86    /// Signature find-and-delete was disallowed by the active flags.
87    SigFindAndDelete,
88}
89
90impl fmt::Display for ScriptError {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        let s = match self {
93            Self::Ok => "OK",
94            Self::Unknown => "UNKNOWN",
95            Self::EvalFalse => "EVAL_FALSE",
96            Self::OpReturn => "OP_RETURN",
97            Self::ScriptSize => "SCRIPT_SIZE",
98            Self::PushSize => "PUSH_SIZE",
99            Self::OpCount => "OP_COUNT",
100            Self::StackSize => "STACK_SIZE",
101            Self::SigCount => "SIG_COUNT",
102            Self::PubkeyCount => "PUBKEY_COUNT",
103            Self::Verify => "VERIFY",
104            Self::EqualVerify => "EQUALVERIFY",
105            Self::CheckSigVerify => "CHECKSIGVERIFY",
106            Self::CheckMultiSigVerify => "CHECKMULTISIGVERIFY",
107            Self::NumEqualVerify => "NUMEQUALVERIFY",
108            Self::BadOpcode => "BAD_OPCODE",
109            Self::OpCodeSeparator => "OP_CODESEPARATOR",
110            Self::DisabledOpcode => "DISABLED_OPCODE",
111            Self::InvalidStackOperation => "INVALID_STACK_OPERATION",
112            Self::InvalidAltstackOperation => "INVALID_ALTSTACK_OPERATION",
113            Self::UnbalancedConditional => "UNBALANCED_CONDITIONAL",
114            Self::NegativeLockTime => "NEGATIVE_LOCKTIME",
115            Self::UnsatisfiedLockTime => "UNSATISFIED_LOCKTIME",
116            Self::SigHashType => "SIG_HASHTYPE",
117            Self::MinimalData => "MINIMALDATA",
118            Self::SigPushOnly => "SIG_PUSHONLY",
119            Self::SigNullDummy => "SIG_NULLDUMMY",
120            Self::CleanStack => "CLEANSTACK",
121            Self::MinimalIf => "MINIMALIF",
122            Self::NullFail => "NULLFAIL",
123            Self::DiscourageUpgradableNops => "DISCOURAGE_UPGRADABLE_NOPS",
124            Self::DiscourageUpgradableWitnessProgram => "DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM",
125            Self::WitnessProgramWrongLength => "WITNESS_PROGRAM_WRONG_LENGTH",
126            Self::WitnessProgramWitnessEmpty => "WITNESS_PROGRAM_WITNESS_EMPTY",
127            Self::WitnessProgramMismatch => "WITNESS_PROGRAM_MISMATCH",
128            Self::WitnessMalleated => "WITNESS_MALLEATED",
129            Self::WitnessMalleatedP2SH => "WITNESS_MALLEATED_P2SH",
130            Self::WitnessUnexpected => "WITNESS_UNEXPECTED",
131            Self::SigFindAndDelete => "SIG_FINDANDDELETE",
132        };
133        f.write_str(s)
134    }
135}
136
137/// High-level validation errors produced by the Tidecoin validation engine.
138#[derive(Debug, Clone, PartialEq, Eq)]
139pub enum TidecoinValidationError {
140    /// Script execution reached the consensus engine and failed with a specific
141    /// script error.
142    ///
143    /// Callers should treat this as a consensus-invalid spend for the selected
144    /// script, witness, amount, and flag combination rather than as malformed
145    /// caller input.
146    Script(ScriptError),
147    /// The requested input index does not exist in the spending transaction.
148    ///
149    /// This indicates a caller-side setup error. Retrying with the same input
150    /// index and transaction will fail again.
151    InvalidInputIndex {
152        /// The requested input index.
153        index: usize,
154        /// The total number of inputs in the spending transaction.
155        inputs: usize,
156    },
157    /// Script verification flags were invalid.
158    ///
159    /// This indicates the caller supplied unsupported or inconsistent
160    /// verification bits rather than a consensus-invalid script.
161    InvalidFlags,
162    /// Input amount is required for the selected witness verification path.
163    ///
164    /// This indicates the caller attempted a witness-aware verification path
165    /// without providing the spent output amount required by the engine.
166    AmountRequired,
167    /// A spent output needed for validation was missing.
168    ///
169    /// This indicates required prevout state was unavailable at the engine
170    /// boundary. It is a missing-context error, not a script failure.
171    MissingSpentOutput(OutPoint),
172}
173
174impl fmt::Display for TidecoinValidationError {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        match self {
177            Self::Script(err) => write!(f, "script validation failed: {}", err),
178            Self::InvalidInputIndex { index, inputs } => {
179                write!(
180                    f,
181                    "input index {} out of bounds for transaction with {} inputs",
182                    index, inputs
183                )
184            }
185            Self::InvalidFlags => f.write_str("script verification flags are invalid"),
186            Self::AmountRequired => {
187                f.write_str("input amount is required if witness verification is used")
188            }
189            Self::MissingSpentOutput(outpoint) => {
190                write!(f, "missing spent output {:?}", outpoint)
191            }
192        }
193    }
194}
195
196impl From<ScriptError> for TidecoinValidationError {
197    fn from(value: ScriptError) -> Self {
198        Self::Script(value)
199    }
200}
201
202#[cfg(feature = "std")]
203impl std::error::Error for TidecoinValidationError {}