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 RecipientAddressExpiry {
130 payment_index: usize,
131 error: AddressExpiryError,
132 },
133
134 #[cfg(feature = "pczt")]
136 Pczt(PcztError),
137}
138
139#[non_exhaustive]
141pub enum RewindError<AccountId: Hash + Eq, E> {
142 DataSource(E),
144 RewindBeyondBirthdays(HashMap<AccountId, BlockHeight>),
158}
159
160impl<AccountId: Hash + Eq + Debug, E: Debug> Debug for RewindError<AccountId, E> {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 match self {
163 RewindError::DataSource(e) => f.debug_tuple("DataSource").field(e).finish(),
164 RewindError::RewindBeyondBirthdays(birthdays) => f
165 .debug_tuple("RewindBeyondBirthdays")
166 .field(birthdays)
167 .finish(),
168 }
169 }
170}
171
172impl<AccountId: Hash + Eq + Debug, E: Display> Display for RewindError<AccountId, E> {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 match self {
175 RewindError::DataSource(e) => write!(f, "Wallet data source error: {e}"),
176 RewindError::RewindBeyondBirthdays(birthdays) => write!(
177 f,
178 "Rewind would precede the birthday height of one or more accounts: {birthdays:?}"
179 ),
180 }
181 }
182}
183
184impl<AccountId: Hash + Eq + Debug, E: error::Error + 'static> error::Error
185 for RewindError<AccountId, E>
186{
187 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
188 match self {
189 RewindError::DataSource(e) => Some(e),
190 RewindError::RewindBeyondBirthdays(_) => None,
191 }
192 }
193}
194
195#[cfg(feature = "pczt")]
197#[derive(Debug)]
198#[non_exhaustive]
199pub enum PcztError {
200 Build,
202
203 IoFinalization(pczt::roles::io_finalizer::Error),
205
206 UpdateOrchard(pczt::roles::updater::OrchardError),
208
209 UpdateSapling(pczt::roles::updater::SaplingError),
211
212 UpdateTransparent(pczt::roles::updater::TransparentError),
214
215 SpendFinalization(pczt::roles::spend_finalizer::Error),
217
218 Extraction(pczt::roles::tx_extractor::Error),
220
221 Invalid(String),
223}
224
225#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228#[non_exhaustive]
229pub enum AddressExpiryError {
230 TimeExpired {
232 expiry_time: u64,
234 },
235 HeightExpired {
237 address_expiry: BlockHeight,
239 chain_height: BlockHeight,
241 },
242 ExpiryHeightConflict {
248 address_expiry: BlockHeight,
249 transaction_expiry: BlockHeight,
250 },
251 TransactionExpiryDisabled { address_expiry: BlockHeight },
254}
255
256impl fmt::Display for AddressExpiryError {
257 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258 match self {
259 AddressExpiryError::TimeExpired { expiry_time } => write!(
260 f,
261 "the address expired at {expiry_time} (seconds since the Unix epoch)"
262 ),
263 AddressExpiryError::HeightExpired {
264 address_expiry,
265 chain_height,
266 } => write!(
267 f,
268 "the address expired at height {address_expiry}, and the chain has \
269 reached height {chain_height}"
270 ),
271 AddressExpiryError::ExpiryHeightConflict {
272 address_expiry,
273 transaction_expiry,
274 } => write!(
275 f,
276 "the transaction would expire at height {transaction_expiry}, after the \
277 address's expiry height {address_expiry}"
278 ),
279 AddressExpiryError::TransactionExpiryDisabled { address_expiry } => write!(
280 f,
281 "the address expires at height {address_expiry}, which requires the \
282 transaction to carry a nonzero expiry height, but transaction expiry \
283 was disabled"
284 ),
285 }
286 }
287}
288
289impl error::Error for AddressExpiryError {}
290
291impl<DE, TE, SE, FE, CE, N> fmt::Display for Error<DE, TE, SE, FE, CE, N>
292where
293 DE: fmt::Display,
294 TE: fmt::Display,
295 SE: fmt::Display,
296 FE: fmt::Display,
297 CE: fmt::Display,
298 N: fmt::Display,
299{
300 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
301 match self {
302 Error::DataSource(e) => {
303 write!(
304 f,
305 "The underlying datasource produced the following error: {e}"
306 )
307 }
308 Error::CommitmentTree(e) => {
309 write!(
310 f,
311 "An error occurred in querying or updating a note commitment tree: {e}"
312 )
313 }
314 Error::NoteSelection(e) => {
315 write!(f, "Note selection encountered the following error: {e}")
316 }
317 Error::Change(e) => {
318 write!(f, "Change output generation failed: {e}")
319 }
320 Error::Proposal(e) => {
321 write!(
322 f,
323 "Input selection attempted to construct an invalid proposal: {e}"
324 )
325 }
326 Error::ProposalNotSupported => write!(
327 f,
328 "The proposal was valid but tried to do something that is not supported \
329 (spend shielded outputs of prior transaction steps or use a feature that \
330 is not enabled).",
331 ),
332 Error::KeyNotRecognized => {
333 write!(
334 f,
335 "Wallet does not contain an account corresponding to the provided spending key"
336 )
337 }
338 Error::AccountCannotSpend => {
339 write!(
340 f,
341 "The given account cannot be used for spending, because it is unable to maintain an accurate balance.",
342 )
343 }
344 Error::AccountIdNotRecognized => {
345 write!(
346 f,
347 "Wallet does not contain an account corresponding to the provided ID"
348 )
349 }
350 Error::BalanceError(e) => write!(
351 f,
352 "The value lies outside the valid range of Zcash amounts: {e:?}."
353 ),
354 Error::InsufficientFunds {
355 available,
356 required,
357 } => write!(
358 f,
359 "Insufficient balance (have {}, need {} including fee)",
360 u64::from(*available),
361 u64::from(*required)
362 ),
363 Error::ScanRequired => write!(f, "Must scan blocks first"),
364 Error::Builder(e) => write!(f, "An error occurred building the transaction: {e}"),
365 Error::Payment(e) => write!(f, "An error occurred constructing a payment: {e}"),
366 Error::UnsupportedChangeType(t) => write!(
367 f,
368 "Attempted to send change to an unsupported pool type: {t}"
369 ),
370 Error::NoSupportedReceivers(ua) => write!(
371 f,
372 "A recipient's unified address does not contain any receivers to which the wallet can send funds; required one of {}",
373 ua.receiver_types()
374 .iter()
375 .enumerate()
376 .fold(String::new(), |mut acc, (i, tc)| {
377 let _ = write!(acc, "{}{:?}", if i > 0 { ", " } else { "" }, tc);
378 acc
379 })
380 ),
381 Error::KeyNotAvailable(pool) => write!(
382 f,
383 "A key required for transaction construction was not available for pool type {pool}"
384 ),
385 Error::NoteMismatch(n) => write!(
386 f,
387 "A note being spent ({n:?}) does not correspond to either the internal or external full viewing key for the provided spending key."
388 ),
389
390 Error::Address(e) => {
391 write!(
392 f,
393 "An error occurred decoding the address from a payment request: {e}."
394 )
395 }
396 #[cfg(feature = "transparent-inputs")]
397 Error::AddressNotRecognized(_) => {
398 write!(
399 f,
400 "The specified transparent address was not recognized as belonging to the wallet."
401 )
402 }
403 Error::ExpiryHeightConflictsWithCanonicalCrossing { requested } => write!(
404 f,
405 "An expiry height of {requested} was requested for a canonical ZIP 318 crossing, \
406 which takes the ZIP 318 rolling expiry; pass `None` to accept it."
407 ),
408 Error::RecipientAddressExpiry {
409 payment_index,
410 error,
411 } => write!(
412 f,
413 "The recipient address of the payment at index {payment_index} cannot be \
414 paid: {error}. Try to obtain a currently-valid address for the intended \
415 recipient."
416 ),
417 Error::ExpiryHeightBelowTargetHeight {
418 expiry_height,
419 min_target_height,
420 } => write!(
421 f,
422 "The requested expiry height {expiry_height} is below the proposal's \
423 minimum target height {min_target_height}; the transaction would already be \
424 expired at the earliest height at which it could be mined."
425 ),
426 #[cfg(feature = "pczt")]
427 Error::Pczt(e) => write!(f, "PCZT error: {e}"),
428 }
429 }
430}
431
432#[cfg(feature = "pczt")]
433impl fmt::Display for PcztError {
434 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
435 match self {
436 PcztError::Build => {
437 write!(
438 f,
439 "Failed to generate the PCZT prior to proving or signing."
440 )
441 }
442 PcztError::IoFinalization(e) => {
443 write!(f, "Failed to finalize IO: {e:?}.")
444 }
445 PcztError::UpdateOrchard(e) => {
446 write!(f, "Failed to updating Orchard PCZT data: {e:?}.")
447 }
448 PcztError::UpdateSapling(e) => {
449 write!(f, "Failed to updating Sapling PCZT data: {e:?}.")
450 }
451 PcztError::UpdateTransparent(e) => {
452 write!(f, "Failed to updating transparent PCZT data: {e:?}.")
453 }
454 PcztError::SpendFinalization(e) => {
455 write!(f, "Failed to finalize the PCZT spends: {e:?}.")
456 }
457 PcztError::Extraction(e) => {
458 write!(f, "Failed to extract the final transaction: {e:?}.")
459 }
460 PcztError::Invalid(e) => {
461 write!(f, "PCZT parsing resulted in an invalid condition: {e}.")
462 }
463 }
464 }
465}
466
467impl<DE, TE, SE, FE, CE, N> error::Error for Error<DE, TE, SE, FE, CE, N>
468where
469 DE: Debug + Display + error::Error + 'static,
470 TE: Debug + Display + error::Error + 'static,
471 SE: Debug + Display + error::Error + 'static,
472 FE: Debug + Display + 'static,
473 CE: Debug + Display + error::Error + 'static,
474 N: Debug + Display + 'static,
475{
476 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
477 match &self {
478 Error::DataSource(e) => Some(e),
479 Error::CommitmentTree(e) => Some(e),
480 Error::NoteSelection(e) => Some(e),
481 Error::Proposal(e) => Some(e),
482 Error::Builder(e) => Some(e),
483 #[cfg(feature = "pczt")]
484 Error::Pczt(e) => Some(e),
485 _ => None,
486 }
487 }
488}
489
490#[cfg(feature = "pczt")]
491impl error::Error for PcztError {}
492
493impl<DE, TE, SE, FE, CE, N> From<builder::Error<FE>> for Error<DE, TE, SE, FE, CE, N> {
494 fn from(e: builder::Error<FE>) -> Self {
495 Error::Builder(e)
496 }
497}
498
499impl<DE, TE, SE, FE, CE, N> From<ProposalError> for Error<DE, TE, SE, FE, CE, N> {
500 fn from(e: ProposalError) -> Self {
501 Error::Proposal(e)
502 }
503}
504
505impl<DE, TE, SE, FE, CE, N> From<BalanceError> for Error<DE, TE, SE, FE, CE, N> {
506 fn from(e: BalanceError) -> Self {
507 Error::BalanceError(e)
508 }
509}
510
511impl<DE, TE, SE, FE, CE, N> From<ConversionError<&'static str>> for Error<DE, TE, SE, FE, CE, N> {
512 fn from(value: ConversionError<&'static str>) -> Self {
513 Error::Address(value)
514 }
515}
516
517impl<DE, TE, SE, FE, CE, N> From<InputSelectorError<DE, SE, CE, N>>
518 for Error<DE, TE, SE, FE, CE, N>
519{
520 fn from(e: InputSelectorError<DE, SE, CE, N>) -> Self {
521 match e {
522 InputSelectorError::DataSource(e) => Error::DataSource(e),
523 InputSelectorError::Selection(e) => Error::NoteSelection(e),
524 InputSelectorError::Change(e) => Error::Change(e),
525 InputSelectorError::Proposal(e) => Error::Proposal(e),
526 InputSelectorError::InsufficientFunds {
527 available,
528 required,
529 } => Error::InsufficientFunds {
530 available,
531 required,
532 },
533 InputSelectorError::SyncRequired => Error::ScanRequired,
534 InputSelectorError::Address(e) => Error::Address(e),
535 }
536 }
537}
538
539impl<DE, TE, SE, FE, CE, N> From<sapling::builder::Error> for Error<DE, TE, SE, FE, CE, N> {
540 fn from(e: sapling::builder::Error) -> Self {
541 Error::Builder(builder::Error::SaplingBuild(e))
542 }
543}
544
545impl<DE, TE, SE, FE, CE, N> From<transparent::builder::Error> for Error<DE, TE, SE, FE, CE, N> {
546 fn from(e: ::transparent::builder::Error) -> Self {
547 Error::Builder(builder::Error::TransparentBuild(e))
548 }
549}
550
551impl<DE, TE, SE, FE, CE, N> From<ShardTreeError<TE>> for Error<DE, TE, SE, FE, CE, N> {
552 fn from(e: ShardTreeError<TE>) -> Self {
553 Error::CommitmentTree(e)
554 }
555}
556
557#[cfg(feature = "pczt")]
558impl<DE, TE, SE, FE, CE, N> From<PcztError> for Error<DE, TE, SE, FE, CE, N> {
559 fn from(e: PcztError) -> Self {
560 Error::Pczt(e)
561 }
562}
563
564#[cfg(feature = "pczt")]
565impl<DE, TE, SE, FE, CE, N> From<pczt::roles::io_finalizer::Error>
566 for Error<DE, TE, SE, FE, CE, N>
567{
568 fn from(e: pczt::roles::io_finalizer::Error) -> Self {
569 Error::Pczt(PcztError::IoFinalization(e))
570 }
571}
572
573#[cfg(feature = "pczt")]
574impl<DE, TE, SE, FE, CE, N> From<pczt::roles::updater::OrchardError>
575 for Error<DE, TE, SE, FE, CE, N>
576{
577 fn from(e: pczt::roles::updater::OrchardError) -> Self {
578 Error::Pczt(PcztError::UpdateOrchard(e))
579 }
580}
581
582#[cfg(feature = "pczt")]
583impl<DE, TE, SE, FE, CE, N> From<pczt::roles::updater::SaplingError>
584 for Error<DE, TE, SE, FE, CE, N>
585{
586 fn from(e: pczt::roles::updater::SaplingError) -> Self {
587 Error::Pczt(PcztError::UpdateSapling(e))
588 }
589}
590
591#[cfg(feature = "pczt")]
592impl<DE, TE, SE, FE, CE, N> From<pczt::roles::updater::TransparentError>
593 for Error<DE, TE, SE, FE, CE, N>
594{
595 fn from(e: pczt::roles::updater::TransparentError) -> Self {
596 Error::Pczt(PcztError::UpdateTransparent(e))
597 }
598}
599
600#[cfg(feature = "pczt")]
601impl<DE, TE, SE, FE, CE, N> From<pczt::roles::spend_finalizer::Error>
602 for Error<DE, TE, SE, FE, CE, N>
603{
604 fn from(e: pczt::roles::spend_finalizer::Error) -> Self {
605 Error::Pczt(PcztError::SpendFinalization(e))
606 }
607}
608
609#[cfg(feature = "pczt")]
610impl<DE, TE, SE, FE, CE, N> From<pczt::roles::tx_extractor::Error>
611 for Error<DE, TE, SE, FE, CE, N>
612{
613 fn from(e: pczt::roles::tx_extractor::Error) -> Self {
614 Error::Pczt(PcztError::Extraction(e))
615 }
616}
617
618#[derive(Debug)]
620#[non_exhaustive]
621pub enum FindAccountForAddressError<E> {
622 Backend(E),
624
625 UnifiedAddressConflict,
627}
628
629impl<E> From<E> for FindAccountForAddressError<E> {
630 fn from(err: E) -> Self {
631 Self::Backend(err)
632 }
633}
634
635impl<E: Display> Display for FindAccountForAddressError<E> {
636 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
637 match self {
638 FindAccountForAddressError::Backend(e) => {
639 write!(f, "Wallet backend error: {e}")
640 }
641 FindAccountForAddressError::UnifiedAddressConflict => write!(
642 f,
643 "Receivers of the provided Unified Address map to different wallet accounts."
644 ),
645 }
646 }
647}
648
649impl<E: error::Error + 'static> error::Error for FindAccountForAddressError<E> {
650 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
651 match self {
652 FindAccountForAddressError::Backend(e) => Some(e),
653 FindAccountForAddressError::UnifiedAddressConflict => None,
654 }
655 }
656}
657
658pub use super::locking::LockError;