Skip to main content

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;
5use std::ops::Range;
6
7#[cfg(feature = "orchard")]
8use incrementalmerkletree::Position;
9use nonempty::NonEmpty;
10#[cfg(feature = "orchard")]
11use shardtree::error::InsertionError;
12use shardtree::error::ShardTreeError;
13
14#[cfg(feature = "transparent-key-import")]
15use uuid::Uuid;
16use zcash_address::ParseError;
17use zcash_client_backend::data_api::NoteFilter;
18use zcash_client_backend::data_api::error::RewindError;
19use zcash_client_backend::data_api::ll;
20use zcash_client_backend::data_api::ll::wallet::PutBlocksError;
21use zcash_client_backend::wallet::OutputRef;
22use zcash_keys::address::UnifiedAddress;
23use zcash_keys::keys::AddressGenerationError;
24use zcash_protocol::{PoolType, ShieldedPool, TxId, consensus::BlockHeight, value::BalanceError};
25use zip32::DiversifierIndex;
26
27use crate::{
28    AccountUuid,
29    wallet::{commitment_tree, common::ErrUnsupportedPool},
30};
31
32#[cfg(feature = "transparent-inputs")]
33use {
34    crate::wallet::transparent::SchedulingError,
35    ::transparent::{address::TransparentAddress, keys::TransparentKeyScope},
36    zcash_keys::{
37        encoding::TransparentCodecError, keys::transparent::gap_limits::GapAddressesError,
38    },
39};
40
41/// The primary error type for the SQLite wallet backend.
42#[derive(Debug)]
43#[non_exhaustive]
44pub enum SqliteClientError {
45    /// Decoding of a stored value from its serialized form has failed.
46    CorruptedData(String),
47
48    /// An error occurred decoding a protobuf message.
49    Protobuf(prost::DecodeError),
50
51    /// The rcm value for a note cannot be decoded to a valid JubJub point.
52    InvalidNote,
53
54    /// Illegal attempt to reinitialize an already-initialized wallet database.
55    TableNotEmpty,
56
57    /// A Zcash key or address decoding error
58    DecodingError(ParseError),
59
60    /// An error produced in legacy transparent address derivation
61    #[cfg(feature = "transparent-inputs")]
62    TransparentDerivation(bip32::Error),
63
64    /// An error encountered in decoding a transparent address from its
65    /// serialized form.
66    #[cfg(feature = "transparent-inputs")]
67    TransparentAddress(TransparentCodecError),
68
69    /// Wrapper for rusqlite errors.
70    DbError(rusqlite::Error),
71
72    /// Wrapper for errors from the IO subsystem
73    Io(std::io::Error),
74
75    /// A received memo cannot be interpreted as a UTF-8 string.
76    InvalidMemo(zcash_protocol::memo::Error),
77
78    /// An attempt to update block data would overwrite the current hash for a block with a
79    /// different hash. This indicates that a required rewind was not performed.
80    BlockConflict(BlockHeight),
81
82    /// A range of blocks provided to the database as a unit was non-sequential
83    NonSequentialBlocks,
84
85    /// A requested rewind would violate invariants of the storage layer. The payload returned with
86    /// this error is (safe rewind height, requested height). If no safe rewind height can be
87    /// determined, the safe rewind height member will be `None`.
88    RequestedRewindInvalid {
89        /// The height to which it is possible to safely rewind, or `None` if no safe
90        /// rewind height could be determined.
91        safe_rewind_height: Option<BlockHeight>,
92        /// The block height that was requested for the rewind.
93        requested_height: BlockHeight,
94    },
95
96    /// An error occurred in generating a Zcash address.
97    AddressGeneration(AddressGenerationError),
98
99    /// The account for which information was requested does not belong to the wallet.
100    AccountUnknown,
101
102    /// The account being added collides with an existing account in the wallet with the given ID.
103    /// The collision can be on the seed and ZIP-32 account index, or a shared IVK component.
104    AccountCollision(AccountUuid),
105
106    /// The account was imported, and ZIP-32 derivation information is not known for it.
107    UnknownZip32Derivation,
108
109    /// An error occurred deriving a spending key from a seed and a ZIP-32 account index.
110    KeyDerivationError(zip32::AccountId),
111
112    /// An error occurred while processing an account due to a failure in deriving the account's keys.
113    BadAccountData(String),
114
115    /// A caller attempted to construct a new account with an invalid ZIP 32 account identifier.
116    Zip32AccountIndexOutOfRange,
117
118    /// The address associated with a record being inserted was not recognized as
119    /// belonging to the wallet.
120    #[cfg(feature = "transparent-inputs")]
121    AddressNotRecognized(TransparentAddress),
122
123    /// An error occurred in inserting data into or accessing data from one of the wallet's note
124    /// commitment trees.
125    CommitmentTree(ShardTreeError<commitment_tree::Error>),
126
127    /// An error occurred while inserting the note commitment data for a range of scanned blocks
128    /// into one of the wallet's note commitment trees during a `put_blocks` operation. The `pool`
129    /// and `block_range` fields record the shielded pool whose note commitment tree was being
130    /// updated and the range of block heights (start-inclusive, end-exclusive) that were being
131    /// added to the wallet when the error occurred.
132    PutBlocksCommitmentTree {
133        /// The shielded pool whose note commitment tree was being updated when the error occurred.
134        pool: ShieldedPool,
135        /// The range of block heights that were being added to the wallet when the error
136        /// occurred.
137        block_range: Range<BlockHeight>,
138        /// The underlying note commitment tree error.
139        error: ShardTreeError<commitment_tree::Error>,
140    },
141
142    /// An error occurred in one of the wallet's note commitment trees while truncating the wallet
143    /// to the specified block height. The `pool` and `height` fields record the shielded pool
144    /// whose note commitment tree was being updated and the block height that the wallet was being
145    /// truncated to when the error occurred.
146    TruncateCommitmentTree {
147        /// The shielded pool whose note commitment tree was being updated when the error occurred.
148        pool: ShieldedPool,
149        /// The block height that the wallet was being truncated to when the error occurred.
150        height: BlockHeight,
151        /// The underlying note commitment tree error.
152        error: ShardTreeError<commitment_tree::Error>,
153    },
154
155    /// The caller-supplied frontier passed to an Orchard or Ironwood
156    /// historical witness generation helper is inconsistent with the shard
157    /// data reconstructed from the wallet at the requested height.
158    ///
159    /// [`WalletDb::generate_orchard_witnesses_at_historical_height`]:
160    /// crate::WalletDb::generate_orchard_witnesses_at_historical_height
161    /// [`WalletDb::generate_ironwood_witnesses_at_historical_height`]:
162    /// crate::WalletDb::generate_ironwood_witnesses_at_historical_height
163    #[cfg(feature = "orchard")]
164    HistoricalFrontierInvalid(InsertionError),
165
166    /// A witness could not be generated for the specified position at the
167    /// specified historical height in a call to an Orchard or Ironwood
168    /// historical witness generation helper.
169    ///
170    /// The wallet most likely has not synced through `height`, the checkpoint
171    /// at `height` has been pruned, or `position` does not belong to the
172    /// wallet.
173    ///
174    /// [`WalletDb::generate_orchard_witnesses_at_historical_height`]:
175    /// crate::WalletDb::generate_orchard_witnesses_at_historical_height
176    /// [`WalletDb::generate_ironwood_witnesses_at_historical_height`]:
177    /// crate::WalletDb::generate_ironwood_witnesses_at_historical_height
178    #[cfg(feature = "orchard")]
179    HistoricalWitnessUnavailable {
180        /// The note commitment tree position for which a witness was
181        /// requested.
182        position: Position,
183        /// The historical height at which the witness was requested.
184        height: BlockHeight,
185    },
186
187    /// The block at the specified height was not available from the block cache.
188    CacheMiss(BlockHeight),
189
190    /// The height of the chain was not available; a call to [`WalletWrite::update_chain_tip`] is
191    /// required before the requested operation can succeed.
192    ///
193    /// [`WalletWrite::update_chain_tip`]:
194    /// zcash_client_backend::data_api::WalletWrite::update_chain_tip
195    ChainHeightUnknown,
196
197    /// Unsupported pool type
198    UnsupportedPoolType(PoolType),
199
200    /// An error occurred in computing wallet balance
201    BalanceError(BalanceError),
202
203    /// A note selection query contained an invalid constant or was otherwise not supported.
204    NoteFilterInvalid(NoteFilter),
205
206    /// An address cannot be reserved, or a proposal cannot be constructed until a transaction
207    /// containing outputs belonging to a previously reserved address has been mined. The error
208    /// contains the index that could not safely be reserved.
209    #[cfg(feature = "transparent-inputs")]
210    ReachedGapLimit(TransparentKeyScope, u32),
211
212    /// The backend encountered an attempt to reuse a diversifier index to generate an address
213    /// having different receivers from an address that had previously been exposed for that
214    /// diversifier index. Returns the previously exposed address.
215    DiversifierIndexReuse(DiversifierIndex, Box<UnifiedAddress>),
216
217    /// The wallet attempted to create a transaction that would use of one of the wallet's
218    /// previously-used addresses, potentially creating a problem with on-chain transaction
219    /// linkability. The returned value contains the string encoding of the address and the txid(s)
220    /// of the transactions in which it is known to have been used.
221    AddressReuse(String, NonEmpty<TxId>),
222
223    /// The wallet found one or more notes that given a certain context would be
224    /// ineligible and shouldn't be considered in the involved db operation.
225    IneligibleNotes,
226
227    /// The wallet encountered an error when attempting to schedule wallet operations.
228    #[cfg(feature = "transparent-inputs")]
229    Scheduling(SchedulingError),
230
231    /// The caller responded to a [`TransactionsInvolvingAddress`] request by querying a range of
232    /// block heights ending at a height that did not match the (exclusive) end of the requested
233    /// range.
234    ///
235    /// [`TransactionsInvolvingAddress`]: zcash_client_backend::data_api::TransactionsInvolvingAddress
236    #[cfg(feature = "transparent-inputs")]
237    NotificationMismatch {
238        /// The expected ending block height.
239        expected: BlockHeight,
240        /// The actual ending block height returned.
241        actual: BlockHeight,
242    },
243
244    /// An attempt to import a standalone transparent address failed because it had already been
245    /// imported to a different account.
246    #[cfg(feature = "transparent-key-import")]
247    StandaloneImportConflict(Uuid),
248
249    /// An error returned by a [`FeeRule`] during transparent input selection. The underlying
250    /// error is boxed so the storage layer does not need to know every fee rule's error type.
251    ///
252    /// [`FeeRule`]: zcash_primitives::transaction::fees::FeeRule
253    #[cfg(feature = "transparent-inputs")]
254    FeeRuleError(Box<dyn error::Error + Send + Sync>),
255
256    /// A `zcash_client_backend` error value carried a variant that this crate has no
257    /// translation for; the wrapped value carries the error itself.
258    ///
259    /// Those error types are `#[non_exhaustive]`, so a variant introduced by a future
260    /// `zcash_client_backend` release can reach this crate before there is a specific
261    /// [`SqliteClientError`] counterpart for it. This error is therefore unreachable with
262    /// the `zcash_client_backend` release this crate is built against; encountering it
263    /// means this crate needs updating to translate the new variant.
264    BackendError(BackendError),
265}
266
267/// A `zcash_client_backend` error carrying a variant that this crate has no translation for,
268/// as reported by [`SqliteClientError::BackendError`].
269///
270/// Each variant names the operation whose error type is wrapped. The errors are boxed because
271/// both are generic over [`SqliteClientError`] itself.
272#[derive(Debug)]
273#[non_exhaustive]
274pub enum BackendError {
275    /// An error reported while inserting scanned blocks into the wallet.
276    PutBlocks(Box<PutBlocksError<SqliteClientError, commitment_tree::Error>>),
277    /// An error reported while rewinding the wallet to a previous chain state.
278    Rewind(Box<RewindError<AccountUuid, SqliteClientError>>),
279}
280
281impl fmt::Display for BackendError {
282    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
283        match self {
284            BackendError::PutBlocks(_) => write!(f, "block insertion"),
285            BackendError::Rewind(_) => write!(f, "rewind to a previous chain state"),
286        }
287    }
288}
289
290impl error::Error for SqliteClientError {
291    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
292        match &self {
293            SqliteClientError::InvalidMemo(e) => Some(e),
294            SqliteClientError::DbError(e) => Some(e),
295            SqliteClientError::Io(e) => Some(e),
296            SqliteClientError::BalanceError(e) => Some(e),
297            SqliteClientError::AddressGeneration(e) => Some(e),
298            #[cfg(feature = "orchard")]
299            SqliteClientError::HistoricalFrontierInvalid(e) => Some(e),
300            #[cfg(feature = "transparent-inputs")]
301            SqliteClientError::FeeRuleError(e) => Some(&**e),
302            _ => None,
303        }
304    }
305}
306
307#[cfg(feature = "transparent-inputs")]
308impl From<GapAddressesError<SqliteClientError>> for SqliteClientError {
309    fn from(err: GapAddressesError<SqliteClientError>) -> Self {
310        match err {
311            GapAddressesError::Storage(e) => e,
312            GapAddressesError::AddressGeneration(e) => SqliteClientError::AddressGeneration(e),
313            GapAddressesError::AccountUnknown => SqliteClientError::AccountUnknown,
314        }
315    }
316}
317
318impl fmt::Display for SqliteClientError {
319    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
320        match &self {
321            SqliteClientError::CorruptedData(reason) => {
322                write!(f, "Data DB is corrupted: {reason}")
323            }
324            SqliteClientError::Protobuf(e) => {
325                write!(f, "Failed to parse protobuf-encoded record: {e}")
326            }
327            SqliteClientError::InvalidNote => write!(f, "Invalid note"),
328            SqliteClientError::RequestedRewindInvalid {
329                safe_rewind_height,
330                requested_height,
331            } => write!(
332                f,
333                "A rewind for your wallet may only target height {} or greater; the requested height was {}.",
334                safe_rewind_height.map_or("<unavailable>".to_owned(), |h0| format!("{h0}")),
335                requested_height
336            ),
337            SqliteClientError::DecodingError(e) => write!(f, "{e}"),
338            #[cfg(feature = "transparent-inputs")]
339            SqliteClientError::TransparentDerivation(e) => write!(f, "{e:?}"),
340            #[cfg(feature = "transparent-inputs")]
341            SqliteClientError::TransparentAddress(e) => write!(f, "{e}"),
342            SqliteClientError::TableNotEmpty => write!(f, "Table is not empty"),
343            SqliteClientError::DbError(e) => write!(f, "{e}"),
344            SqliteClientError::Io(e) => write!(f, "{e}"),
345            SqliteClientError::InvalidMemo(e) => write!(f, "{e}"),
346            SqliteClientError::BlockConflict(h) => write!(
347                f,
348                "A block hash conflict occurred at height {}; rewind required.",
349                u32::from(*h)
350            ),
351            SqliteClientError::NonSequentialBlocks => write!(
352                f,
353                "`put_blocks` requires that the provided block range be sequential"
354            ),
355            SqliteClientError::AddressGeneration(e) => write!(f, "{e}"),
356            SqliteClientError::AccountUnknown => write!(
357                f,
358                "The account with the given ID does not belong to this wallet."
359            ),
360            SqliteClientError::UnknownZip32Derivation => write!(
361                f,
362                "ZIP-32 derivation information is not known for this account."
363            ),
364            SqliteClientError::KeyDerivationError(zip32_index) => write!(
365                f,
366                "Key derivation failed for ZIP 32 account index {}",
367                u32::from(*zip32_index)
368            ),
369            SqliteClientError::BadAccountData(e) => write!(f, "Failed to add account: {e}"),
370            SqliteClientError::Zip32AccountIndexOutOfRange => write!(
371                f,
372                "ZIP 32 account identifiers must be less than 0x7FFFFFFF."
373            ),
374            SqliteClientError::AccountCollision(account_uuid) => write!(
375                f,
376                "An account corresponding to the data provided already exists in the wallet with UUID {account_uuid:?}."
377            ),
378            #[cfg(feature = "transparent-inputs")]
379            SqliteClientError::AddressNotRecognized(_) => write!(
380                f,
381                "The address associated with a received txo is not identifiable as belonging to the wallet."
382            ),
383            SqliteClientError::CommitmentTree(err) => write!(
384                f,
385                "An error occurred accessing or updating note commitment tree data: {err}."
386            ),
387            SqliteClientError::PutBlocksCommitmentTree {
388                pool,
389                block_range,
390                error,
391            } => write!(
392                f,
393                "An error occurred updating the {pool:?} note commitment tree while adding blocks in the range {}..{}: {error}.",
394                u32::from(block_range.start),
395                u32::from(block_range.end),
396            ),
397            SqliteClientError::TruncateCommitmentTree {
398                pool,
399                height,
400                error,
401            } => write!(
402                f,
403                "An error occurred updating the {pool:?} note commitment tree while truncating the wallet to height {}: {error}.",
404                u32::from(*height),
405            ),
406            #[cfg(feature = "orchard")]
407            SqliteClientError::HistoricalFrontierInvalid(err) => write!(
408                f,
409                "The frontier supplied to historical witness generation is inconsistent with the wallet's shard data: {err}"
410            ),
411            #[cfg(feature = "orchard")]
412            SqliteClientError::HistoricalWitnessUnavailable { position, height } => write!(
413                f,
414                "No witness is available for position {} at height {height} (the wallet may need to sync through this height).",
415                u64::from(*position),
416            ),
417            SqliteClientError::CacheMiss(height) => write!(
418                f,
419                "Requested height {height} does not exist in the block cache."
420            ),
421            SqliteClientError::ChainHeightUnknown => {
422                write!(f, "Chain height unknown; please call `update_chain_tip`")
423            }
424            SqliteClientError::UnsupportedPoolType(t) => {
425                write!(f, "Pool type is not currently supported: {t}")
426            }
427            SqliteClientError::BalanceError(e) => write!(f, "Balance error: {e}"),
428            SqliteClientError::NoteFilterInvalid(s) => {
429                write!(f, "Could not evaluate filter query: {s:?}")
430            }
431            #[cfg(feature = "transparent-inputs")]
432            SqliteClientError::ReachedGapLimit(key_scope, bad_index) => write!(
433                f,
434                "The proposal cannot be constructed until a transaction with outputs to a previously reserved {} address has been mined. \
435                 The address at index {bad_index} could not be safely reserved.",
436                match *key_scope {
437                    TransparentKeyScope::EXTERNAL => "external transparent",
438                    TransparentKeyScope::INTERNAL => "transparent change",
439                    TransparentKeyScope::EPHEMERAL => "ephemeral transparent",
440                    _ => panic!("Unsupported transparent key scope."),
441                }
442            ),
443            SqliteClientError::DiversifierIndexReuse(i, _) => {
444                write!(
445                    f,
446                    "An address has already been exposed for diversifier index {}",
447                    u128::from(*i)
448                )
449            }
450            SqliteClientError::AddressReuse(address_str, txids) => {
451                write!(
452                    f,
453                    "The address {address_str} previously used in txid(s) {txids:?} would be reused."
454                )
455            }
456            #[cfg(feature = "transparent-inputs")]
457            SqliteClientError::Scheduling(err) => {
458                write!(f, "The wallet was unable to schedule an event: {err}")
459            }
460            #[cfg(feature = "transparent-inputs")]
461            SqliteClientError::NotificationMismatch { expected, actual } => {
462                write!(
463                    f,
464                    "The client performed an address check over a block range that did not match the requested range; expected as_of_height: {expected}, actual as_of_height: {actual}"
465                )
466            }
467            SqliteClientError::IneligibleNotes => {
468                write!(
469                    f,
470                    "Query found notes that are considered ineligible in its context"
471                )
472            }
473            #[cfg(feature = "transparent-key-import")]
474            SqliteClientError::StandaloneImportConflict(uuid) => {
475                write!(
476                    f,
477                    "The given standalone transparent address is already managed by account {uuid}"
478                )
479            }
480            #[cfg(feature = "transparent-inputs")]
481            SqliteClientError::FeeRuleError(e) => write!(f, "Fee rule error: {e}"),
482            SqliteClientError::BackendError(e) => write!(
483                f,
484                "The zcash_client_backend error reported for {e} is not one this version of \
485                 zcash_client_sqlite recognizes; this crate must be updated to handle it."
486            ),
487        }
488    }
489}
490
491impl From<rusqlite::Error> for SqliteClientError {
492    fn from(e: rusqlite::Error) -> Self {
493        SqliteClientError::DbError(e)
494    }
495}
496
497impl From<std::io::Error> for SqliteClientError {
498    fn from(e: std::io::Error) -> Self {
499        SqliteClientError::Io(e)
500    }
501}
502impl From<ParseError> for SqliteClientError {
503    fn from(e: ParseError) -> Self {
504        SqliteClientError::DecodingError(e)
505    }
506}
507
508impl From<prost::DecodeError> for SqliteClientError {
509    fn from(e: prost::DecodeError) -> Self {
510        SqliteClientError::Protobuf(e)
511    }
512}
513
514#[cfg(feature = "transparent-inputs")]
515impl From<bip32::Error> for SqliteClientError {
516    fn from(e: bip32::Error) -> Self {
517        SqliteClientError::TransparentDerivation(e)
518    }
519}
520
521#[cfg(feature = "transparent-inputs")]
522impl From<TransparentCodecError> for SqliteClientError {
523    fn from(e: TransparentCodecError) -> Self {
524        SqliteClientError::TransparentAddress(e)
525    }
526}
527
528impl From<zcash_protocol::memo::Error> for SqliteClientError {
529    fn from(e: zcash_protocol::memo::Error) -> Self {
530        SqliteClientError::InvalidMemo(e)
531    }
532}
533
534impl From<ShardTreeError<commitment_tree::Error>> for SqliteClientError {
535    fn from(e: ShardTreeError<commitment_tree::Error>) -> Self {
536        SqliteClientError::CommitmentTree(e)
537    }
538}
539
540impl From<BalanceError> for SqliteClientError {
541    fn from(e: BalanceError) -> Self {
542        SqliteClientError::BalanceError(e)
543    }
544}
545
546impl From<AddressGenerationError> for SqliteClientError {
547    fn from(e: AddressGenerationError) -> Self {
548        SqliteClientError::AddressGeneration(e)
549    }
550}
551
552/// `zip317::FeeError` does not implement `std::error::Error`, so we wrap it in order to box
553/// it as the payload of [`SqliteClientError::FeeRuleError`].
554#[cfg(feature = "transparent-inputs")]
555#[derive(Debug)]
556struct FeeErrorWrapper(zcash_primitives::transaction::fees::zip317::FeeError);
557
558#[cfg(feature = "transparent-inputs")]
559impl fmt::Display for FeeErrorWrapper {
560    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
561        fmt::Display::fmt(&self.0, f)
562    }
563}
564
565#[cfg(feature = "transparent-inputs")]
566impl error::Error for FeeErrorWrapper {}
567
568#[cfg(feature = "transparent-inputs")]
569impl From<zcash_primitives::transaction::fees::zip317::FeeError> for SqliteClientError {
570    fn from(e: zcash_primitives::transaction::fees::zip317::FeeError) -> Self {
571        SqliteClientError::FeeRuleError(Box::new(FeeErrorWrapper(e)))
572    }
573}
574
575#[cfg(feature = "transparent-inputs")]
576impl From<SchedulingError> for SqliteClientError {
577    fn from(value: SchedulingError) -> Self {
578        SqliteClientError::Scheduling(value)
579    }
580}
581
582impl From<PutBlocksError<SqliteClientError, commitment_tree::Error>> for SqliteClientError {
583    fn from(value: PutBlocksError<SqliteClientError, commitment_tree::Error>) -> Self {
584        match value {
585            ll::wallet::PutBlocksError::NonSequentialBlocks { .. } => {
586                SqliteClientError::NonSequentialBlocks
587            }
588            ll::wallet::PutBlocksError::Storage(e) => e,
589            ll::wallet::PutBlocksError::ShardTree(e) => SqliteClientError::from(e),
590            ll::wallet::PutBlocksError::ShardTreeForBlockRange {
591                pool,
592                block_range,
593                error,
594            } => SqliteClientError::PutBlocksCommitmentTree {
595                pool,
596                block_range,
597                error,
598            },
599            #[cfg(feature = "transparent-inputs")]
600            ll::wallet::PutBlocksError::GapAddresses(e) => SqliteClientError::from(e),
601            // `PutBlocksError` is `#[non_exhaustive]`, so a variant introduced by a future
602            // `zcash_client_backend` release reaches this conversion with no counterpart
603            // here until this crate is updated to map it. Report it rather than panicking.
604            other => SqliteClientError::BackendError(BackendError::PutBlocks(Box::new(other))),
605        }
606    }
607}
608
609impl ErrUnsupportedPool for SqliteClientError {
610    fn unsupported_pool_type(pool_type: PoolType) -> Self {
611        SqliteClientError::UnsupportedPoolType(pool_type)
612    }
613}
614
615/// A local LockError type for which we can write a From<rusqlite::Error> impl.
616pub(crate) enum LockError {
617    /// Wrapper for storage errors.
618    Storage(rusqlite::Error),
619    /// The wrapped output reference was not found, or the output it refers to was already locked.
620    LockFailure(OutputRef),
621}
622
623impl From<rusqlite::Error> for LockError {
624    fn from(value: rusqlite::Error) -> Self {
625        LockError::Storage(value)
626    }
627}
628
629impl From<LockError> for zcash_client_backend::data_api::error::LockError<SqliteClientError> {
630    fn from(value: LockError) -> Self {
631        match value {
632            LockError::Storage(error) => zcash_client_backend::data_api::error::LockError::Storage(
633                SqliteClientError::from(error),
634            ),
635            LockError::LockFailure(output) => {
636                zcash_client_backend::data_api::error::LockError::LockFailure(output)
637            }
638        }
639    }
640}