Skip to main content

zakura_chain/serialization/
error.rs

1//! Errors for Zcash consensus-critical serialization.
2
3use std::{array::TryFromSliceError, io, num::TryFromIntError, str::Utf8Error, sync::Arc};
4
5use bounded_vec::BoundedVecOutOfBounds;
6use hex::FromHexError;
7use thiserror::Error;
8
9/// A serialization error.
10// TODO: refine error types -- better to use boxed errors?
11#[derive(Clone, Error, Debug)]
12pub enum SerializationError {
13    /// An io error that prevented deserialization
14    #[error("io error: {0}")]
15    Io(#[from] Arc<io::Error>),
16
17    /// The data to be deserialized was malformed.
18    // TODO: refine errors
19    #[error("parse error: {0}")]
20    Parse(&'static str),
21
22    /// A shielded protocol proof did not have the canonical size for its
23    /// action count.
24    #[error("non-canonical shielded protocol proof size")]
25    NonCanonicalShieldedProofSize,
26
27    /// A string was not UTF-8.
28    ///
29    /// Note: Rust `String` and `str` are always UTF-8.
30    #[error("string was not UTF-8: {0}")]
31    Utf8Error(#[from] Utf8Error),
32
33    /// A slice was an unexpected length during deserialization.
34    #[error("slice was the wrong length: {0}")]
35    TryFromSliceError(#[from] TryFromSliceError),
36
37    /// The length of a vec is too large to convert to a usize (and thus, too large to allocate on this platform)
38    #[error("CompactSize too large: {0}")]
39    TryFromIntError(#[from] TryFromIntError),
40
41    /// A string was not valid hexadecimal.
42    #[error("string was not hex: {0}")]
43    FromHexError(#[from] FromHexError),
44
45    /// An error caused when validating a zatoshi `Amount`
46    #[error("input couldn't be parsed as a zatoshi `Amount`: {source}")]
47    Amount {
48        /// The source error indicating how the num failed to validate
49        #[from]
50        source: crate::amount::Error,
51    },
52
53    /// Invalid transaction with a non-zero balance and no Sapling shielded spends or outputs.
54    ///
55    /// Transaction does not conform to the Sapling [consensus
56    /// rule](https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus).
57    #[error("transaction balance is non-zero but doesn't have Sapling shielded spends or outputs")]
58    BadTransactionBalance,
59
60    /// Could not de/serialize a transparent script.
61    #[error("script error: {0}")]
62    Script(#[from] zcash_script::script::Error),
63
64    /// Errors that occur when parsing opcodes in transparent scripts.
65    #[error("script opcode error: {0}")]
66    Opcode(#[from] zcash_script::opcode::Error),
67
68    /// Errors that occur when parsing integers in transparent scripts.
69    #[error("script number error: {0}")]
70    Num(#[from] zcash_script::num::Error),
71}
72
73impl From<SerializationError> for io::Error {
74    fn from(e: SerializationError) -> Self {
75        match e {
76            SerializationError::Io(e) => {
77                Arc::try_unwrap(e).unwrap_or_else(|e| io::Error::new(e.kind(), e.to_string()))
78            }
79            SerializationError::Parse(msg) => io::Error::new(io::ErrorKind::InvalidData, msg),
80            error @ SerializationError::NonCanonicalShieldedProofSize => {
81                io::Error::new(io::ErrorKind::InvalidData, error)
82            }
83            SerializationError::Utf8Error(e) => io::Error::new(io::ErrorKind::InvalidData, e),
84            SerializationError::TryFromSliceError(e) => {
85                io::Error::new(io::ErrorKind::InvalidData, e)
86            }
87            SerializationError::TryFromIntError(e) => io::Error::new(io::ErrorKind::InvalidData, e),
88            SerializationError::FromHexError(e) => io::Error::new(io::ErrorKind::InvalidData, e),
89            SerializationError::Amount { source } => {
90                io::Error::new(io::ErrorKind::InvalidData, source)
91            }
92            SerializationError::BadTransactionBalance => io::Error::new(
93                io::ErrorKind::InvalidData,
94                "bad transaction balance: non-zero with no Sapling shielded spends or outputs",
95            ),
96            SerializationError::Script(e) => io::Error::new(io::ErrorKind::InvalidData, e),
97            SerializationError::Opcode(e) => io::Error::new(io::ErrorKind::InvalidData, e),
98            SerializationError::Num(e) => io::Error::new(io::ErrorKind::InvalidData, e),
99        }
100    }
101}
102
103impl From<crate::Error> for SerializationError {
104    fn from(e: crate::Error) -> Self {
105        match e {
106            crate::Error::InvalidConsensusBranchId => Self::Parse("invalid consensus branch id"),
107            crate::Error::Io(e) => Self::Io(e),
108            crate::Error::MissingNetworkUpgrade => Self::Parse("missing network upgrade"),
109            crate::Error::Amount(_) => Self::BadTransactionBalance,
110            crate::Error::Conversion(_) => {
111                Self::Parse("Zakura's type could not be converted to its librustzcash equivalent")
112            }
113        }
114    }
115}
116
117/// Allow converting `io::Error` to `SerializationError`; we need this since we
118/// use `Arc<io::Error>` in `SerializationError::Io`.
119impl From<io::Error> for SerializationError {
120    fn from(value: io::Error) -> Self {
121        Arc::new(value).into()
122    }
123}
124
125impl From<BoundedVecOutOfBounds> for SerializationError {
126    fn from(_: BoundedVecOutOfBounds) -> Self {
127        SerializationError::Parse("vector length out of bounds")
128    }
129}