1use chrono::{DateTime, Utc};
9use thiserror::Error;
10
11use zebra_chain::{
12 amount, block, orchard,
13 parameters::subsidy::SubsidyError,
14 sapling, sprout,
15 transparent::{self, MIN_TRANSPARENT_COINBASE_MATURITY},
16};
17use zebra_state::ValidateContextError;
18
19use crate::{block::MAX_BLOCK_SIGOPS, BoxError};
20
21#[cfg(any(test, feature = "proptest-impl"))]
22use proptest_derive::Arbitrary;
23
24const MAX_EXPIRY_HEIGHT: block::Height = block::Height::MAX_EXPIRY_HEIGHT;
26
27#[derive(Error, Clone, Debug, PartialEq, Eq)]
29#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
30#[allow(missing_docs)]
31pub enum TransactionError {
32 #[error("first transaction must be coinbase")]
33 CoinbasePosition,
34
35 #[error("coinbase input found in non-coinbase transaction")]
36 CoinbaseAfterFirst,
37
38 #[error("coinbase transaction MUST NOT have any JoinSplit descriptions")]
39 CoinbaseHasJoinSplit,
40
41 #[error("coinbase transaction MUST NOT have any Spend descriptions")]
42 CoinbaseHasSpend,
43
44 #[error("coinbase transaction MUST NOT have any Output descriptions pre-Heartwood")]
45 CoinbaseHasOutputPreHeartwood,
46
47 #[error("coinbase transaction MUST NOT have the EnableSpendsOrchard flag set")]
48 CoinbaseHasEnableSpendsOrchard,
49
50 #[error("coinbase transaction Sapling or Orchard outputs MUST be decryptable with an all-zero outgoing viewing key")]
51 CoinbaseOutputsNotDecryptable,
52
53 #[error("coinbase inputs MUST NOT exist in mempool")]
54 CoinbaseInMempool,
55
56 #[error("non-coinbase transactions MUST NOT have coinbase inputs")]
57 NonCoinbaseHasCoinbaseInput,
58
59 #[error("the tx is not coinbase, but it should be")]
60 NotCoinbase,
61
62 #[error("transaction is locked until after block height {}", _0.0)]
63 LockedUntilAfterBlockHeight(block::Height),
64
65 #[error("transaction is locked until after block time {0}")]
66 #[cfg_attr(any(test, feature = "proptest-impl"), proptest(skip))]
67 LockedUntilAfterBlockTime(DateTime<Utc>),
68
69 #[error(
70 "coinbase expiry {expiry_height:?} must be the same as the block {block_height:?} \
71 after NU5 activation, failing transaction: {transaction_hash:?}"
72 )]
73 CoinbaseExpiryBlockHeight {
74 expiry_height: Option<zebra_chain::block::Height>,
75 block_height: zebra_chain::block::Height,
76 transaction_hash: zebra_chain::transaction::Hash,
77 },
78
79 #[error(
80 "expiry {expiry_height:?} must be less than the maximum {MAX_EXPIRY_HEIGHT:?} \
81 coinbase: {is_coinbase}, block: {block_height:?}, failing transaction: {transaction_hash:?}"
82 )]
83 MaximumExpiryHeight {
84 expiry_height: zebra_chain::block::Height,
85 is_coinbase: bool,
86 block_height: zebra_chain::block::Height,
87 transaction_hash: zebra_chain::transaction::Hash,
88 },
89
90 #[error(
91 "transaction must not be mined at a block {block_height:?} \
92 greater than its expiry {expiry_height:?}, failing transaction {transaction_hash:?}"
93 )]
94 ExpiredTransaction {
95 expiry_height: zebra_chain::block::Height,
96 block_height: zebra_chain::block::Height,
97 transaction_hash: zebra_chain::transaction::Hash,
98 },
99
100 #[error("coinbase transaction failed subsidy validation: {0}")]
101 #[cfg_attr(any(test, feature = "proptest-impl"), proptest(skip))]
102 Subsidy(#[from] SubsidyError),
103
104 #[error("transaction version number MUST be >= 4")]
105 WrongVersion,
106
107 #[error("transaction version {0} not supported by the network upgrade {1:?}")]
108 UnsupportedByNetworkUpgrade(u32, zebra_chain::parameters::NetworkUpgrade),
109
110 #[error("must have at least one input: transparent, shielded spend, or joinsplit")]
111 NoInputs,
112
113 #[error("must have at least one output: transparent, shielded output, or joinsplit")]
114 NoOutputs,
115
116 #[error("if there are no Spends or Outputs, the value balance MUST be 0.")]
117 BadBalance,
118
119 #[error("could not verify a transparent script: {0}")]
120 #[cfg_attr(any(test, feature = "proptest-impl"), proptest(skip))]
121 Script(#[from] zebra_script::Error),
122
123 #[error("spend description cv and rk MUST NOT be of small order")]
124 SmallOrder,
125
126 #[error("spend proof MUST be valid given a primary input formed from the other fields except spendAuthSig: {0}")]
129 Groth16(String),
130
131 #[error("Groth16 proof is malformed: {0}")]
133 MalformedGroth16(String),
134
135 #[error(
136 "Sprout joinSplitSig MUST represent a valid signature under joinSplitPubKey of dataToBeSigned: {0}"
137 )]
138 #[cfg_attr(any(test, feature = "proptest-impl"), proptest(skip))]
139 Ed25519(#[from] zebra_chain::primitives::ed25519::Error),
140
141 #[error("Sapling bindingSig MUST represent a valid signature under the transaction binding validating key bvk of SigHash: {0}")]
142 #[cfg_attr(any(test, feature = "proptest-impl"), proptest(skip))]
143 RedJubjub(zebra_chain::primitives::redjubjub::Error),
144
145 #[error("Orchard bindingSig MUST represent a valid signature under the transaction binding validating key bvk of SigHash: {0}")]
146 #[cfg_attr(any(test, feature = "proptest-impl"), proptest(skip))]
147 RedPallas(zebra_chain::primitives::reddsa::Error),
148
149 #[error("Downcast from BoxError to redjubjub::Error failed: {0}")]
151 InternalDowncastError(String),
152
153 #[error("either vpub_old or vpub_new must be zero")]
154 BothVPubsNonZero,
155
156 #[error("adding to the sprout pool is disabled after Canopy")]
157 DisabledAddToSproutPool,
158
159 #[error("could not calculate the transaction fee")]
160 IncorrectFee,
161
162 #[error("transparent double-spend: {_0:?} is spent twice")]
163 DuplicateTransparentSpend(transparent::OutPoint),
164
165 #[error("sprout double-spend: duplicate nullifier: {_0:?}")]
166 DuplicateSproutNullifier(sprout::Nullifier),
167
168 #[error("sapling double-spend: duplicate nullifier: {_0:?}")]
169 DuplicateSaplingNullifier(sapling::Nullifier),
170
171 #[error("orchard double-spend: duplicate nullifier: {_0:?}")]
172 DuplicateOrchardNullifier(orchard::Nullifier),
173
174 #[error("must have at least one active orchard flag")]
175 NotEnoughFlags,
176
177 #[error("could not find a mempool transaction input UTXO in the best chain")]
178 TransparentInputNotFound,
179
180 #[error("could not contextually validate transaction on best chain: {0}")]
181 #[cfg_attr(any(test, feature = "proptest-impl"), proptest(skip))]
182 ValidateContextError(Box<ValidateContextError>),
184
185 #[error("could not validate mempool transaction lock time on best chain: {0}")]
186 #[cfg_attr(any(test, feature = "proptest-impl"), proptest(skip))]
187 ValidateMempoolLockTimeError(String),
189
190 #[error(
191 "immature transparent coinbase spend: \
192 attempt to spend {outpoint:?} at {spend_height:?}, \
193 but spends are invalid before {min_spend_height:?}, \
194 which is {MIN_TRANSPARENT_COINBASE_MATURITY:?} blocks \
195 after it was created at {created_height:?}"
196 )]
197 #[non_exhaustive]
198 ImmatureTransparentCoinbaseSpend {
199 outpoint: transparent::OutPoint,
200 spend_height: block::Height,
201 min_spend_height: block::Height,
202 created_height: block::Height,
203 },
204
205 #[error(
206 "unshielded transparent coinbase spend: {outpoint:?} \
207 must be spent in a transaction which only has shielded outputs"
208 )]
209 #[non_exhaustive]
210 UnshieldedTransparentCoinbaseSpend {
211 outpoint: transparent::OutPoint,
212 min_spend_height: block::Height,
213 },
214
215 #[error(
216 "failed to verify ZIP-317 transaction rules, transaction was not inserted to mempool: {0}"
217 )]
218 #[cfg_attr(any(test, feature = "proptest-impl"), proptest(skip))]
219 Zip317(#[from] zebra_chain::transaction::zip317::Error),
220
221 #[error("transaction uses an incorrect consensus branch id")]
222 WrongConsensusBranchId,
223
224 #[error("wrong tx format: tx version is ≥ 5, but `nConsensusBranchId` is missing")]
225 MissingConsensusBranchId,
226}
227
228impl From<ValidateContextError> for TransactionError {
229 fn from(err: ValidateContextError) -> Self {
230 TransactionError::ValidateContextError(Box::new(err))
231 }
232}
233
234impl From<BoxError> for TransactionError {
236 fn from(mut err: BoxError) -> Self {
237 match err.downcast::<zebra_chain::primitives::redjubjub::Error>() {
239 Ok(e) => return TransactionError::RedJubjub(*e),
240 Err(e) => err = e,
241 }
242
243 match err.downcast::<ValidateContextError>() {
244 Ok(e) => return (*e).into(),
245 Err(e) => err = e,
246 }
247
248 match err.downcast::<TransactionError>() {
250 Ok(e) => return *e,
251 Err(e) => err = e,
252 }
253
254 TransactionError::InternalDowncastError(format!(
255 "downcast to known transaction error type failed, original error: {err:?}",
256 ))
257 }
258}
259
260impl TransactionError {
261 pub fn mempool_misbehavior_score(&self) -> u32 {
264 use TransactionError::*;
265
266 match self {
268 ImmatureTransparentCoinbaseSpend { .. }
269 | UnshieldedTransparentCoinbaseSpend { .. }
270 | CoinbasePosition
271 | CoinbaseAfterFirst
272 | CoinbaseHasJoinSplit
273 | CoinbaseHasSpend
274 | CoinbaseHasOutputPreHeartwood
275 | CoinbaseHasEnableSpendsOrchard
276 | CoinbaseOutputsNotDecryptable
277 | CoinbaseInMempool
278 | NonCoinbaseHasCoinbaseInput
279 | CoinbaseExpiryBlockHeight { .. }
280 | IncorrectFee
281 | Subsidy(_)
282 | WrongVersion
283 | NoInputs
284 | NoOutputs
285 | BadBalance
286 | Script(_)
287 | SmallOrder
288 | Groth16(_)
289 | MalformedGroth16(_)
290 | Ed25519(_)
291 | RedJubjub(_)
292 | RedPallas(_)
293 | BothVPubsNonZero
294 | DisabledAddToSproutPool
295 | NotEnoughFlags
296 | WrongConsensusBranchId
297 | MissingConsensusBranchId => 100,
298
299 _other => 0,
300 }
301 }
302}
303
304#[derive(Error, Clone, Debug, PartialEq, Eq)]
305#[allow(missing_docs)]
306pub enum BlockError {
307 #[error("block contains invalid transactions")]
308 Transaction(#[from] TransactionError),
309
310 #[error("block has no transactions")]
311 NoTransactions,
312
313 #[error("block has mismatched merkle root")]
314 BadMerkleRoot {
315 actual: zebra_chain::block::merkle::Root,
316 expected: zebra_chain::block::merkle::Root,
317 },
318
319 #[error("block contains duplicate transactions")]
320 DuplicateTransaction,
321
322 #[error("block {0:?} is already in present in the state {1:?}")]
323 AlreadyInChain(zebra_chain::block::Hash, zebra_state::KnownBlock),
324
325 #[error("invalid block {0:?}: missing block height")]
326 MissingHeight(zebra_chain::block::Hash),
327
328 #[error("invalid block height {0:?} in {1:?}: greater than the maximum height {2:?}")]
329 MaxHeight(
330 zebra_chain::block::Height,
331 zebra_chain::block::Hash,
332 zebra_chain::block::Height,
333 ),
334
335 #[error("invalid difficulty threshold in block header {0:?} {1:?}")]
336 InvalidDifficulty(zebra_chain::block::Height, zebra_chain::block::Hash),
337
338 #[error("block {0:?} has a difficulty threshold {2:?} that is easier than the {3:?} difficulty limit {4:?}, hash: {1:?}")]
339 TargetDifficultyLimit(
340 zebra_chain::block::Height,
341 zebra_chain::block::Hash,
342 zebra_chain::work::difficulty::ExpandedDifficulty,
343 zebra_chain::parameters::Network,
344 zebra_chain::work::difficulty::ExpandedDifficulty,
345 ),
346
347 #[error(
348 "block {0:?} on {3:?} has a hash {1:?} that is easier than its difficulty threshold {2:?}"
349 )]
350 DifficultyFilter(
351 zebra_chain::block::Height,
352 zebra_chain::block::Hash,
353 zebra_chain::work::difficulty::ExpandedDifficulty,
354 zebra_chain::parameters::Network,
355 ),
356
357 #[error("transaction has wrong consensus branch id for block network upgrade")]
358 WrongTransactionConsensusBranchId,
359
360 #[error(
361 "block {height:?} {hash:?} has {legacy_sigop_count} legacy transparent signature operations, \
362 but the limit is {MAX_BLOCK_SIGOPS}"
363 )]
364 TooManyTransparentSignatureOperations {
365 height: zebra_chain::block::Height,
366 hash: zebra_chain::block::Hash,
367 legacy_sigop_count: u64,
368 },
369
370 #[error("summing miner fees for block {height:?} {hash:?} failed: {source:?}")]
371 SummingMinerFees {
372 height: zebra_chain::block::Height,
373 hash: zebra_chain::block::Hash,
374 source: amount::Error,
375 },
376
377 #[error("unexpected error occurred: {0}")]
378 Other(String),
379}
380
381impl From<SubsidyError> for BlockError {
382 fn from(err: SubsidyError) -> BlockError {
383 BlockError::Transaction(TransactionError::Subsidy(err))
384 }
385}
386
387impl BlockError {
388 pub fn is_duplicate_request(&self) -> bool {
391 matches!(self, BlockError::AlreadyInChain(..))
392 }
393
394 pub(crate) fn misbehavior_score(&self) -> u32 {
396 use BlockError::*;
397
398 match self {
399 MissingHeight(_)
400 | MaxHeight(_, _, _)
401 | InvalidDifficulty(_, _)
402 | TargetDifficultyLimit(_, _, _, _, _)
403 | DifficultyFilter(_, _, _, _) => 100,
404 _other => 0,
405 }
406 }
407}