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