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