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 {}