1use std::{
4 collections::HashMap,
5 error,
6 fmt::{self, Debug, Display, Write},
7 hash::Hash,
8};
9
10use shardtree::error::ShardTreeError;
11use zcash_address::ConversionError;
12use zcash_keys::address::UnifiedAddress;
13use zcash_primitives::transaction::builder;
14use zcash_protocol::{
15 PoolType,
16 consensus::BlockHeight,
17 value::{BalanceError, Zatoshis},
18};
19
20use crate::{
21 data_api::wallet::input_selection::InputSelectorError, fees::ChangeError,
22 proposal::ProposalError, wallet::NoteId,
23};
24
25#[cfg(feature = "transparent-inputs")]
26use ::transparent::address::TransparentAddress;
27
28#[derive(Debug)]
30#[non_exhaustive]
31pub enum Error<DataSourceError, CommitmentTreeError, SelectionError, FeeError, ChangeErrT, NoteRefT>
32{
33 DataSource(DataSourceError),
35
36 CommitmentTree(ShardTreeError<CommitmentTreeError>),
38
39 NoteSelection(SelectionError),
41
42 Change(ChangeError<ChangeErrT, NoteRefT>),
44
45 Proposal(ProposalError),
47
48 ProposalNotSupported,
54
55 AccountIdNotRecognized,
57
58 KeyNotRecognized,
60
61 AccountCannotSpend,
64
65 BalanceError(BalanceError),
67
68 InsufficientFunds {
70 available: Zatoshis,
71 required: Zatoshis,
72 },
73
74 ScanRequired,
77
78 Builder(builder::Error<FeeError>),
80
81 Payment(zip321::PaymentError),
83
84 UnsupportedChangeType(PoolType),
90
91 NoSupportedReceivers(Box<UnifiedAddress>),
93
94 KeyNotAvailable(PoolType),
97
98 NoteMismatch(NoteId),
101
102 Address(ConversionError<&'static str>),
104
105 #[cfg(feature = "transparent-inputs")]
108 AddressNotRecognized(TransparentAddress),
109
110 ExpiryHeightBelowTargetHeight {
113 expiry_height: BlockHeight,
114 min_target_height: BlockHeight,
115 },
116
117 ExpiryHeightConflictsWithCanonicalCrossing { requested: BlockHeight },
125
126 #[cfg(feature = "pczt")]
128 Pczt(PcztError),
129}
130
131#[non_exhaustive]
133pub enum RewindError<AccountId: Hash + Eq, E> {
134 DataSource(E),
136 RewindBeyondBirthdays(HashMap<AccountId, BlockHeight>),
150}
151
152impl<AccountId: Hash + Eq + Debug, E: Debug> Debug for RewindError<AccountId, E> {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 match self {
155 RewindError::DataSource(e) => f.debug_tuple("DataSource").field(e).finish(),
156 RewindError::RewindBeyondBirthdays(birthdays) => f
157 .debug_tuple("RewindBeyondBirthdays")
158 .field(birthdays)
159 .finish(),
160 }
161 }
162}
163
164impl<AccountId: Hash + Eq + Debug, E: Display> Display for RewindError<AccountId, E> {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 match self {
167 RewindError::DataSource(e) => write!(f, "Wallet data source error: {e}"),
168 RewindError::RewindBeyondBirthdays(birthdays) => write!(
169 f,
170 "Rewind would precede the birthday height of one or more accounts: {birthdays:?}"
171 ),
172 }
173 }
174}
175
176impl<AccountId: Hash + Eq + Debug, E: error::Error + 'static> error::Error
177 for RewindError<AccountId, E>
178{
179 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
180 match self {
181 RewindError::DataSource(e) => Some(e),
182 RewindError::RewindBeyondBirthdays(_) => None,
183 }
184 }
185}
186
187#[cfg(feature = "pczt")]
189#[derive(Debug)]
190#[non_exhaustive]
191pub enum PcztError {
192 Build,
194
195 IoFinalization(pczt::roles::io_finalizer::Error),
197
198 UpdateOrchard(pczt::roles::updater::OrchardError),
200
201 UpdateSapling(pczt::roles::updater::SaplingError),
203
204 UpdateTransparent(pczt::roles::updater::TransparentError),
206
207 SpendFinalization(pczt::roles::spend_finalizer::Error),
209
210 Extraction(pczt::roles::tx_extractor::Error),
212
213 Invalid(String),
215}
216
217impl<DE, TE, SE, FE, CE, N> fmt::Display for Error<DE, TE, SE, FE, CE, N>
218where
219 DE: fmt::Display,
220 TE: fmt::Display,
221 SE: fmt::Display,
222 FE: fmt::Display,
223 CE: fmt::Display,
224 N: fmt::Display,
225{
226 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
227 match self {
228 Error::DataSource(e) => {
229 write!(
230 f,
231 "The underlying datasource produced the following error: {e}"
232 )
233 }
234 Error::CommitmentTree(e) => {
235 write!(
236 f,
237 "An error occurred in querying or updating a note commitment tree: {e}"
238 )
239 }
240 Error::NoteSelection(e) => {
241 write!(f, "Note selection encountered the following error: {e}")
242 }
243 Error::Change(e) => {
244 write!(f, "Change output generation failed: {e}")
245 }
246 Error::Proposal(e) => {
247 write!(
248 f,
249 "Input selection attempted to construct an invalid proposal: {e}"
250 )
251 }
252 Error::ProposalNotSupported => write!(
253 f,
254 "The proposal was valid but tried to do something that is not supported \
255 (spend shielded outputs of prior transaction steps or use a feature that \
256 is not enabled).",
257 ),
258 Error::KeyNotRecognized => {
259 write!(
260 f,
261 "Wallet does not contain an account corresponding to the provided spending key"
262 )
263 }
264 Error::AccountCannotSpend => {
265 write!(
266 f,
267 "The given account cannot be used for spending, because it is unable to maintain an accurate balance.",
268 )
269 }
270 Error::AccountIdNotRecognized => {
271 write!(
272 f,
273 "Wallet does not contain an account corresponding to the provided ID"
274 )
275 }
276 Error::BalanceError(e) => write!(
277 f,
278 "The value lies outside the valid range of Zcash amounts: {e:?}."
279 ),
280 Error::InsufficientFunds {
281 available,
282 required,
283 } => write!(
284 f,
285 "Insufficient balance (have {}, need {} including fee)",
286 u64::from(*available),
287 u64::from(*required)
288 ),
289 Error::ScanRequired => write!(f, "Must scan blocks first"),
290 Error::Builder(e) => write!(f, "An error occurred building the transaction: {e}"),
291 Error::Payment(e) => write!(f, "An error occurred constructing a payment: {e}"),
292 Error::UnsupportedChangeType(t) => write!(
293 f,
294 "Attempted to send change to an unsupported pool type: {t}"
295 ),
296 Error::NoSupportedReceivers(ua) => write!(
297 f,
298 "A recipient's unified address does not contain any receivers to which the wallet can send funds; required one of {}",
299 ua.receiver_types()
300 .iter()
301 .enumerate()
302 .fold(String::new(), |mut acc, (i, tc)| {
303 let _ = write!(acc, "{}{:?}", if i > 0 { ", " } else { "" }, tc);
304 acc
305 })
306 ),
307 Error::KeyNotAvailable(pool) => write!(
308 f,
309 "A key required for transaction construction was not available for pool type {pool}"
310 ),
311 Error::NoteMismatch(n) => write!(
312 f,
313 "A note being spent ({n:?}) does not correspond to either the internal or external full viewing key for the provided spending key."
314 ),
315
316 Error::Address(e) => {
317 write!(
318 f,
319 "An error occurred decoding the address from a payment request: {e}."
320 )
321 }
322 #[cfg(feature = "transparent-inputs")]
323 Error::AddressNotRecognized(_) => {
324 write!(
325 f,
326 "The specified transparent address was not recognized as belonging to the wallet."
327 )
328 }
329 Error::ExpiryHeightConflictsWithCanonicalCrossing { requested } => write!(
330 f,
331 "An expiry height of {requested} was requested for a canonical ZIP 318 crossing, \
332 which takes the ZIP 318 rolling expiry; pass `None` to accept it."
333 ),
334 Error::ExpiryHeightBelowTargetHeight {
335 expiry_height,
336 min_target_height,
337 } => write!(
338 f,
339 "The requested expiry height {expiry_height} is below the proposal's \
340 minimum target height {min_target_height}; the transaction would already be \
341 expired at the earliest height at which it could be mined."
342 ),
343 #[cfg(feature = "pczt")]
344 Error::Pczt(e) => write!(f, "PCZT error: {e}"),
345 }
346 }
347}
348
349#[cfg(feature = "pczt")]
350impl fmt::Display for PcztError {
351 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
352 match self {
353 PcztError::Build => {
354 write!(
355 f,
356 "Failed to generate the PCZT prior to proving or signing."
357 )
358 }
359 PcztError::IoFinalization(e) => {
360 write!(f, "Failed to finalize IO: {e:?}.")
361 }
362 PcztError::UpdateOrchard(e) => {
363 write!(f, "Failed to updating Orchard PCZT data: {e:?}.")
364 }
365 PcztError::UpdateSapling(e) => {
366 write!(f, "Failed to updating Sapling PCZT data: {e:?}.")
367 }
368 PcztError::UpdateTransparent(e) => {
369 write!(f, "Failed to updating transparent PCZT data: {e:?}.")
370 }
371 PcztError::SpendFinalization(e) => {
372 write!(f, "Failed to finalize the PCZT spends: {e:?}.")
373 }
374 PcztError::Extraction(e) => {
375 write!(f, "Failed to extract the final transaction: {e:?}.")
376 }
377 PcztError::Invalid(e) => {
378 write!(f, "PCZT parsing resulted in an invalid condition: {e}.")
379 }
380 }
381 }
382}
383
384impl<DE, TE, SE, FE, CE, N> error::Error for Error<DE, TE, SE, FE, CE, N>
385where
386 DE: Debug + Display + error::Error + 'static,
387 TE: Debug + Display + error::Error + 'static,
388 SE: Debug + Display + error::Error + 'static,
389 FE: Debug + Display + 'static,
390 CE: Debug + Display + error::Error + 'static,
391 N: Debug + Display + 'static,
392{
393 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
394 match &self {
395 Error::DataSource(e) => Some(e),
396 Error::CommitmentTree(e) => Some(e),
397 Error::NoteSelection(e) => Some(e),
398 Error::Proposal(e) => Some(e),
399 Error::Builder(e) => Some(e),
400 #[cfg(feature = "pczt")]
401 Error::Pczt(e) => Some(e),
402 _ => None,
403 }
404 }
405}
406
407#[cfg(feature = "pczt")]
408impl error::Error for PcztError {}
409
410impl<DE, TE, SE, FE, CE, N> From<builder::Error<FE>> for Error<DE, TE, SE, FE, CE, N> {
411 fn from(e: builder::Error<FE>) -> Self {
412 Error::Builder(e)
413 }
414}
415
416impl<DE, TE, SE, FE, CE, N> From<ProposalError> for Error<DE, TE, SE, FE, CE, N> {
417 fn from(e: ProposalError) -> Self {
418 Error::Proposal(e)
419 }
420}
421
422impl<DE, TE, SE, FE, CE, N> From<BalanceError> for Error<DE, TE, SE, FE, CE, N> {
423 fn from(e: BalanceError) -> Self {
424 Error::BalanceError(e)
425 }
426}
427
428impl<DE, TE, SE, FE, CE, N> From<ConversionError<&'static str>> for Error<DE, TE, SE, FE, CE, N> {
429 fn from(value: ConversionError<&'static str>) -> Self {
430 Error::Address(value)
431 }
432}
433
434impl<DE, TE, SE, FE, CE, N> From<InputSelectorError<DE, SE, CE, N>>
435 for Error<DE, TE, SE, FE, CE, N>
436{
437 fn from(e: InputSelectorError<DE, SE, CE, N>) -> Self {
438 match e {
439 InputSelectorError::DataSource(e) => Error::DataSource(e),
440 InputSelectorError::Selection(e) => Error::NoteSelection(e),
441 InputSelectorError::Change(e) => Error::Change(e),
442 InputSelectorError::Proposal(e) => Error::Proposal(e),
443 InputSelectorError::InsufficientFunds {
444 available,
445 required,
446 } => Error::InsufficientFunds {
447 available,
448 required,
449 },
450 InputSelectorError::SyncRequired => Error::ScanRequired,
451 InputSelectorError::Address(e) => Error::Address(e),
452 }
453 }
454}
455
456impl<DE, TE, SE, FE, CE, N> From<sapling::builder::Error> for Error<DE, TE, SE, FE, CE, N> {
457 fn from(e: sapling::builder::Error) -> Self {
458 Error::Builder(builder::Error::SaplingBuild(e))
459 }
460}
461
462impl<DE, TE, SE, FE, CE, N> From<transparent::builder::Error> for Error<DE, TE, SE, FE, CE, N> {
463 fn from(e: ::transparent::builder::Error) -> Self {
464 Error::Builder(builder::Error::TransparentBuild(e))
465 }
466}
467
468impl<DE, TE, SE, FE, CE, N> From<ShardTreeError<TE>> for Error<DE, TE, SE, FE, CE, N> {
469 fn from(e: ShardTreeError<TE>) -> Self {
470 Error::CommitmentTree(e)
471 }
472}
473
474#[cfg(feature = "pczt")]
475impl<DE, TE, SE, FE, CE, N> From<PcztError> for Error<DE, TE, SE, FE, CE, N> {
476 fn from(e: PcztError) -> Self {
477 Error::Pczt(e)
478 }
479}
480
481#[cfg(feature = "pczt")]
482impl<DE, TE, SE, FE, CE, N> From<pczt::roles::io_finalizer::Error>
483 for Error<DE, TE, SE, FE, CE, N>
484{
485 fn from(e: pczt::roles::io_finalizer::Error) -> Self {
486 Error::Pczt(PcztError::IoFinalization(e))
487 }
488}
489
490#[cfg(feature = "pczt")]
491impl<DE, TE, SE, FE, CE, N> From<pczt::roles::updater::OrchardError>
492 for Error<DE, TE, SE, FE, CE, N>
493{
494 fn from(e: pczt::roles::updater::OrchardError) -> Self {
495 Error::Pczt(PcztError::UpdateOrchard(e))
496 }
497}
498
499#[cfg(feature = "pczt")]
500impl<DE, TE, SE, FE, CE, N> From<pczt::roles::updater::SaplingError>
501 for Error<DE, TE, SE, FE, CE, N>
502{
503 fn from(e: pczt::roles::updater::SaplingError) -> Self {
504 Error::Pczt(PcztError::UpdateSapling(e))
505 }
506}
507
508#[cfg(feature = "pczt")]
509impl<DE, TE, SE, FE, CE, N> From<pczt::roles::updater::TransparentError>
510 for Error<DE, TE, SE, FE, CE, N>
511{
512 fn from(e: pczt::roles::updater::TransparentError) -> Self {
513 Error::Pczt(PcztError::UpdateTransparent(e))
514 }
515}
516
517#[cfg(feature = "pczt")]
518impl<DE, TE, SE, FE, CE, N> From<pczt::roles::spend_finalizer::Error>
519 for Error<DE, TE, SE, FE, CE, N>
520{
521 fn from(e: pczt::roles::spend_finalizer::Error) -> Self {
522 Error::Pczt(PcztError::SpendFinalization(e))
523 }
524}
525
526#[cfg(feature = "pczt")]
527impl<DE, TE, SE, FE, CE, N> From<pczt::roles::tx_extractor::Error>
528 for Error<DE, TE, SE, FE, CE, N>
529{
530 fn from(e: pczt::roles::tx_extractor::Error) -> Self {
531 Error::Pczt(PcztError::Extraction(e))
532 }
533}
534
535#[derive(Debug)]
537#[non_exhaustive]
538pub enum FindAccountForAddressError<E> {
539 Backend(E),
541
542 UnifiedAddressConflict,
544}
545
546impl<E> From<E> for FindAccountForAddressError<E> {
547 fn from(err: E) -> Self {
548 Self::Backend(err)
549 }
550}
551
552impl<E: Display> Display for FindAccountForAddressError<E> {
553 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
554 match self {
555 FindAccountForAddressError::Backend(e) => {
556 write!(f, "Wallet backend error: {e}")
557 }
558 FindAccountForAddressError::UnifiedAddressConflict => write!(
559 f,
560 "Receivers of the provided Unified Address map to different wallet accounts."
561 ),
562 }
563 }
564}
565
566impl<E: error::Error + 'static> error::Error for FindAccountForAddressError<E> {
567 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
568 match self {
569 FindAccountForAddressError::Backend(e) => Some(e),
570 FindAccountForAddressError::UnifiedAddressConflict => None,
571 }
572 }
573}
574
575pub use super::locking::LockError;