zcash_client_sqlite/
error.rs

1//! Error types for problems that may arise when reading or storing wallet data to SQLite.
2
3use std::error;
4use std::fmt;
5
6use nonempty::NonEmpty;
7use shardtree::error::ShardTreeError;
8
9use zcash_address::ParseError;
10use zcash_client_backend::data_api::NoteFilter;
11use zcash_keys::address::UnifiedAddress;
12use zcash_keys::keys::AddressGenerationError;
13use zcash_protocol::{consensus::BlockHeight, value::BalanceError, PoolType, TxId};
14use zip32::DiversifierIndex;
15
16use crate::{wallet::commitment_tree, AccountUuid};
17
18#[cfg(feature = "transparent-inputs")]
19use {
20    crate::wallet::transparent::SchedulingError,
21    ::transparent::{address::TransparentAddress, keys::TransparentKeyScope},
22    zcash_keys::encoding::TransparentCodecError,
23};
24
25/// The primary error type for the SQLite wallet backend.
26#[derive(Debug)]
27pub enum SqliteClientError {
28    /// Decoding of a stored value from its serialized form has failed.
29    CorruptedData(String),
30
31    /// An error occurred decoding a protobuf message.
32    Protobuf(prost::DecodeError),
33
34    /// The rcm value for a note cannot be decoded to a valid JubJub point.
35    InvalidNote,
36
37    /// Illegal attempt to reinitialize an already-initialized wallet database.
38    TableNotEmpty,
39
40    /// A Zcash key or address decoding error
41    DecodingError(ParseError),
42
43    /// An error produced in legacy transparent address derivation
44    #[cfg(feature = "transparent-inputs")]
45    TransparentDerivation(bip32::Error),
46
47    /// An error encountered in decoding a transparent address from its
48    /// serialized form.
49    #[cfg(feature = "transparent-inputs")]
50    TransparentAddress(TransparentCodecError),
51
52    /// Wrapper for rusqlite errors.
53    DbError(rusqlite::Error),
54
55    /// Wrapper for errors from the IO subsystem
56    Io(std::io::Error),
57
58    /// A received memo cannot be interpreted as a UTF-8 string.
59    InvalidMemo(zcash_protocol::memo::Error),
60
61    /// An attempt to update block data would overwrite the current hash for a block with a
62    /// different hash. This indicates that a required rewind was not performed.
63    BlockConflict(BlockHeight),
64
65    /// A range of blocks provided to the database as a unit was non-sequential
66    NonSequentialBlocks,
67
68    /// A requested rewind would violate invariants of the storage layer. The payload returned with
69    /// this error is (safe rewind height, requested height). If no safe rewind height can be
70    /// determined, the safe rewind height member will be `None`.
71    RequestedRewindInvalid {
72        safe_rewind_height: Option<BlockHeight>,
73        requested_height: BlockHeight,
74    },
75
76    /// An error occurred in generating a Zcash address.
77    AddressGeneration(AddressGenerationError),
78
79    /// The account for which information was requested does not belong to the wallet.
80    AccountUnknown,
81
82    /// The account being added collides with an existing account in the wallet with the given ID.
83    /// The collision can be on the seed and ZIP-32 account index, or a shared FVK component.
84    AccountCollision(AccountUuid),
85
86    /// The account was imported, and ZIP-32 derivation information is not known for it.
87    UnknownZip32Derivation,
88
89    /// An error occurred deriving a spending key from a seed and a ZIP-32 account index.
90    KeyDerivationError(zip32::AccountId),
91
92    /// An error occurred while processing an account due to a failure in deriving the account's keys.
93    BadAccountData(String),
94
95    /// A caller attempted to construct a new account with an invalid ZIP 32 account identifier.
96    Zip32AccountIndexOutOfRange,
97
98    /// The address associated with a record being inserted was not recognized as
99    /// belonging to the wallet.
100    #[cfg(feature = "transparent-inputs")]
101    AddressNotRecognized(TransparentAddress),
102
103    /// An error occurred in inserting data into or accessing data from one of the wallet's note
104    /// commitment trees.
105    CommitmentTree(ShardTreeError<commitment_tree::Error>),
106
107    /// The block at the specified height was not available from the block cache.
108    CacheMiss(BlockHeight),
109
110    /// The height of the chain was not available; a call to [`WalletWrite::update_chain_tip`] is
111    /// required before the requested operation can succeed.
112    ///
113    /// [`WalletWrite::update_chain_tip`]:
114    /// zcash_client_backend::data_api::WalletWrite::update_chain_tip
115    ChainHeightUnknown,
116
117    /// Unsupported pool type
118    UnsupportedPoolType(PoolType),
119
120    /// An error occurred in computing wallet balance
121    BalanceError(BalanceError),
122
123    /// A note selection query contained an invalid constant or was otherwise not supported.
124    NoteFilterInvalid(NoteFilter),
125
126    /// An address cannot be reserved, or a proposal cannot be constructed until a transaction
127    /// containing outputs belonging to a previously reserved address has been mined. The error
128    /// contains the index that could not safely be reserved.
129    #[cfg(feature = "transparent-inputs")]
130    ReachedGapLimit(TransparentKeyScope, u32),
131
132    /// The backend encountered an attempt to reuse a diversifier index to generate an address
133    /// having different receivers from an address that had previously been exposed for that
134    /// diversifier index. Returns the previously exposed address.
135    DiversifierIndexReuse(DiversifierIndex, Box<UnifiedAddress>),
136
137    /// The wallet attempted to create a transaction that would use of one of the wallet's
138    /// previously-used addresses, potentially creating a problem with on-chain transaction
139    /// linkability. The returned value contains the string encoding of the address and the txid(s)
140    /// of the transactions in which it is known to have been used.
141    AddressReuse(String, NonEmpty<TxId>),
142
143    /// The wallet encountered an error when attempting to schedule wallet operations.
144    #[cfg(feature = "transparent-inputs")]
145    Scheduling(SchedulingError),
146}
147
148impl error::Error for SqliteClientError {
149    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
150        match &self {
151            SqliteClientError::InvalidMemo(e) => Some(e),
152            SqliteClientError::DbError(e) => Some(e),
153            SqliteClientError::Io(e) => Some(e),
154            SqliteClientError::BalanceError(e) => Some(e),
155            SqliteClientError::AddressGeneration(e) => Some(e),
156            _ => None,
157        }
158    }
159}
160
161impl fmt::Display for SqliteClientError {
162    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
163        match &self {
164            SqliteClientError::CorruptedData(reason) => {
165                write!(f, "Data DB is corrupted: {}", reason)
166            }
167            SqliteClientError::Protobuf(e) => write!(f, "Failed to parse protobuf-encoded record: {}", e),
168            SqliteClientError::InvalidNote => write!(f, "Invalid note"),
169            SqliteClientError::RequestedRewindInvalid { safe_rewind_height,  requested_height } => write!(
170                f,
171                "A rewind for your wallet may only target height {} or greater; the requested height was {}.",
172                safe_rewind_height.map_or("<unavailable>".to_owned(), |h0| format!("{}", h0)),
173               requested_height
174            ),
175            SqliteClientError::DecodingError(e) => write!(f, "{}", e),
176            #[cfg(feature = "transparent-inputs")]
177            SqliteClientError::TransparentDerivation(e) => write!(f, "{:?}", e),
178            #[cfg(feature = "transparent-inputs")]
179            SqliteClientError::TransparentAddress(e) => write!(f, "{}", e),
180            SqliteClientError::TableNotEmpty => write!(f, "Table is not empty"),
181            SqliteClientError::DbError(e) => write!(f, "{}", e),
182            SqliteClientError::Io(e) => write!(f, "{}", e),
183            SqliteClientError::InvalidMemo(e) => write!(f, "{}", e),
184            SqliteClientError::BlockConflict(h) => write!(f, "A block hash conflict occurred at height {}; rewind required.", u32::from(*h)),
185            SqliteClientError::NonSequentialBlocks => write!(f, "`put_blocks` requires that the provided block range be sequential"),
186            SqliteClientError::AddressGeneration(e) => write!(f, "{}", e),
187            SqliteClientError::AccountUnknown => write!(f, "The account with the given ID does not belong to this wallet."),
188            SqliteClientError::UnknownZip32Derivation => write!(f, "ZIP-32 derivation information is not known for this account."),
189            SqliteClientError::KeyDerivationError(zip32_index) => write!(f, "Key derivation failed for ZIP 32 account index {}", u32::from(*zip32_index)),
190            SqliteClientError::BadAccountData(e) => write!(f, "Failed to add account: {}", e),
191            SqliteClientError::Zip32AccountIndexOutOfRange => write!(f, "ZIP 32 account identifiers must be less than 0x7FFFFFFF."),
192            SqliteClientError::AccountCollision(account_uuid) => write!(f, "An account corresponding to the data provided already exists in the wallet with UUID {account_uuid:?}."),
193            #[cfg(feature = "transparent-inputs")]
194            SqliteClientError::AddressNotRecognized(_) => write!(f, "The address associated with a received txo is not identifiable as belonging to the wallet."),
195            SqliteClientError::CommitmentTree(err) => write!(f, "An error occurred accessing or updating note commitment tree data: {}.", err),
196            SqliteClientError::CacheMiss(height) => write!(f, "Requested height {} does not exist in the block cache.", height),
197            SqliteClientError::ChainHeightUnknown => write!(f, "Chain height unknown; please call `update_chain_tip`"),
198            SqliteClientError::UnsupportedPoolType(t) => write!(f, "Pool type is not currently supported: {}", t),
199            SqliteClientError::BalanceError(e) => write!(f, "Balance error: {}", e),
200            SqliteClientError::NoteFilterInvalid(s) => write!(f, "Could not evaluate filter query: {:?}", s),
201            #[cfg(feature = "transparent-inputs")]
202            SqliteClientError::ReachedGapLimit(key_scope, bad_index) => write!(f,
203                "The proposal cannot be constructed until a transaction with outputs to a previously reserved {} address has been mined. \
204                 The address at index {bad_index} could not be safely reserved.",
205                 match *key_scope {
206                     TransparentKeyScope::EXTERNAL => "external transparent",
207                     TransparentKeyScope::INTERNAL => "transparent change",
208                     TransparentKeyScope::EPHEMERAL => "ephemeral transparent",
209                     _ => panic!("Unsupported transparent key scope.")
210                 }
211            ),
212            SqliteClientError::DiversifierIndexReuse(i, _) => {
213                write!(
214                    f,
215                    "An address has already been exposed for diversifier index {}",
216                    u128::from(*i)
217                )
218            }
219            SqliteClientError::AddressReuse(address_str, txids) => {
220                write!(f, "The address {address_str} previously used in txid(s) {:?} would be reused.", txids)
221            }
222            #[cfg(feature = "transparent-inputs")]
223            SqliteClientError::Scheduling(err) => {
224                write!(f, "The wallet was unable to schedule an event: {}", err)
225            }
226        }
227    }
228}
229
230impl From<rusqlite::Error> for SqliteClientError {
231    fn from(e: rusqlite::Error) -> Self {
232        SqliteClientError::DbError(e)
233    }
234}
235
236impl From<std::io::Error> for SqliteClientError {
237    fn from(e: std::io::Error) -> Self {
238        SqliteClientError::Io(e)
239    }
240}
241impl From<ParseError> for SqliteClientError {
242    fn from(e: ParseError) -> Self {
243        SqliteClientError::DecodingError(e)
244    }
245}
246
247impl From<prost::DecodeError> for SqliteClientError {
248    fn from(e: prost::DecodeError) -> Self {
249        SqliteClientError::Protobuf(e)
250    }
251}
252
253#[cfg(feature = "transparent-inputs")]
254impl From<bip32::Error> for SqliteClientError {
255    fn from(e: bip32::Error) -> Self {
256        SqliteClientError::TransparentDerivation(e)
257    }
258}
259
260#[cfg(feature = "transparent-inputs")]
261impl From<TransparentCodecError> for SqliteClientError {
262    fn from(e: TransparentCodecError) -> Self {
263        SqliteClientError::TransparentAddress(e)
264    }
265}
266
267impl From<zcash_protocol::memo::Error> for SqliteClientError {
268    fn from(e: zcash_protocol::memo::Error) -> Self {
269        SqliteClientError::InvalidMemo(e)
270    }
271}
272
273impl From<ShardTreeError<commitment_tree::Error>> for SqliteClientError {
274    fn from(e: ShardTreeError<commitment_tree::Error>) -> Self {
275        SqliteClientError::CommitmentTree(e)
276    }
277}
278
279impl From<BalanceError> for SqliteClientError {
280    fn from(e: BalanceError) -> Self {
281        SqliteClientError::BalanceError(e)
282    }
283}
284
285impl From<AddressGenerationError> for SqliteClientError {
286    fn from(e: AddressGenerationError) -> Self {
287        SqliteClientError::AddressGeneration(e)
288    }
289}
290
291#[cfg(feature = "transparent-inputs")]
292impl From<SchedulingError> for SqliteClientError {
293    fn from(value: SchedulingError) -> Self {
294        SqliteClientError::Scheduling(value)
295    }
296}