zebra_consensus/
error.rs

1//! Errors that can occur when checking consensus rules.
2//!
3//! Each error variant corresponds to a consensus rule, so enumerating
4//! all possible verification failures enumerates the consensus rules we
5//! implement, and ensures that we don't reject blocks or transactions
6//! for a non-enumerated reason.
7
8use 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
24/// Workaround for format string identifier rules.
25const MAX_EXPIRY_HEIGHT: block::Height = block::Height::MAX_EXPIRY_HEIGHT;
26
27/// Errors for semantic transaction validation.
28#[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    // TODO: the underlying error is bellman::VerificationError, but it does not implement
127    // Arbitrary as required here.
128    #[error("spend proof MUST be valid given a primary input formed from the other fields except spendAuthSig: {0}")]
129    Groth16(String),
130
131    // TODO: the underlying error is io::Error, but it does not implement Clone as required here.
132    #[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    // temporary error type until #1186 is fixed
150    #[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    // This error variant is at least 128 bytes
183    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    // TODO: turn this into a typed error
188    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
234// TODO: use a dedicated variant and From impl for each concrete type, and update callers (#5732)
235impl From<BoxError> for TransactionError {
236    fn from(mut err: BoxError) -> Self {
237        // TODO: handle redpallas::Error, ScriptInvalid, InvalidSignature
238        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        // buffered transaction verifier service error
249        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    /// Returns a suggested misbehaviour score increment for a certain error when
262    /// verifying a mempool transaction.
263    pub fn mempool_misbehavior_score(&self) -> u32 {
264        use TransactionError::*;
265
266        // TODO: Adjust these values based on zcashd (#9258).
267        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    /// Returns `true` if this is definitely a duplicate request.
389    /// Some duplicate requests might not be detected, and therefore return `false`.
390    pub fn is_duplicate_request(&self) -> bool {
391        matches!(self, BlockError::AlreadyInChain(..))
392    }
393
394    /// Returns a suggested misbehaviour score increment for a certain error.
395    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}