use std::{
collections::HashMap,
error,
fmt::{self, Debug, Display, Write},
hash::Hash,
};
use shardtree::error::ShardTreeError;
use zcash_address::ConversionError;
use zcash_keys::address::UnifiedAddress;
use zcash_primitives::transaction::builder;
use zcash_protocol::{
PoolType,
consensus::BlockHeight,
value::{BalanceError, Zatoshis},
};
use crate::{
data_api::wallet::input_selection::InputSelectorError, fees::ChangeError,
proposal::ProposalError, wallet::NoteId,
};
#[cfg(feature = "transparent-inputs")]
use ::transparent::address::TransparentAddress;
#[derive(Debug)]
#[non_exhaustive]
pub enum Error<DataSourceError, CommitmentTreeError, SelectionError, FeeError, ChangeErrT, NoteRefT>
{
DataSource(DataSourceError),
CommitmentTree(ShardTreeError<CommitmentTreeError>),
NoteSelection(SelectionError),
Change(ChangeError<ChangeErrT, NoteRefT>),
Proposal(ProposalError),
ProposalNotSupported,
AccountIdNotRecognized,
KeyNotRecognized,
AccountCannotSpend,
BalanceError(BalanceError),
InsufficientFunds {
available: Zatoshis,
required: Zatoshis,
},
ScanRequired,
Builder(builder::Error<FeeError>),
Payment(zip321::PaymentError),
UnsupportedChangeType(PoolType),
NoSupportedReceivers(Box<UnifiedAddress>),
KeyNotAvailable(PoolType),
NoteMismatch(NoteId),
Address(ConversionError<&'static str>),
#[cfg(feature = "transparent-inputs")]
AddressNotRecognized(TransparentAddress),
ExpiryHeightBelowTargetHeight {
expiry_height: BlockHeight,
min_target_height: BlockHeight,
},
ExpiryHeightConflictsWithCanonicalCrossing { requested: BlockHeight },
#[cfg(feature = "pczt")]
Pczt(PcztError),
}
#[non_exhaustive]
pub enum RewindError<AccountId: Hash + Eq, E> {
DataSource(E),
RewindBeyondBirthdays(HashMap<AccountId, BlockHeight>),
}
impl<AccountId: Hash + Eq + Debug, E: Debug> Debug for RewindError<AccountId, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RewindError::DataSource(e) => f.debug_tuple("DataSource").field(e).finish(),
RewindError::RewindBeyondBirthdays(birthdays) => f
.debug_tuple("RewindBeyondBirthdays")
.field(birthdays)
.finish(),
}
}
}
impl<AccountId: Hash + Eq + Debug, E: Display> Display for RewindError<AccountId, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RewindError::DataSource(e) => write!(f, "Wallet data source error: {e}"),
RewindError::RewindBeyondBirthdays(birthdays) => write!(
f,
"Rewind would precede the birthday height of one or more accounts: {birthdays:?}"
),
}
}
}
impl<AccountId: Hash + Eq + Debug, E: error::Error + 'static> error::Error
for RewindError<AccountId, E>
{
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
RewindError::DataSource(e) => Some(e),
RewindError::RewindBeyondBirthdays(_) => None,
}
}
}
#[cfg(feature = "pczt")]
#[derive(Debug)]
#[non_exhaustive]
pub enum PcztError {
Build,
IoFinalization(pczt::roles::io_finalizer::Error),
UpdateOrchard(pczt::roles::updater::OrchardError),
UpdateSapling(pczt::roles::updater::SaplingError),
UpdateTransparent(pczt::roles::updater::TransparentError),
SpendFinalization(pczt::roles::spend_finalizer::Error),
Extraction(pczt::roles::tx_extractor::Error),
Invalid(String),
}
impl<DE, TE, SE, FE, CE, N> fmt::Display for Error<DE, TE, SE, FE, CE, N>
where
DE: fmt::Display,
TE: fmt::Display,
SE: fmt::Display,
FE: fmt::Display,
CE: fmt::Display,
N: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::DataSource(e) => {
write!(
f,
"The underlying datasource produced the following error: {e}"
)
}
Error::CommitmentTree(e) => {
write!(
f,
"An error occurred in querying or updating a note commitment tree: {e}"
)
}
Error::NoteSelection(e) => {
write!(f, "Note selection encountered the following error: {e}")
}
Error::Change(e) => {
write!(f, "Change output generation failed: {e}")
}
Error::Proposal(e) => {
write!(
f,
"Input selection attempted to construct an invalid proposal: {e}"
)
}
Error::ProposalNotSupported => write!(
f,
"The proposal was valid but tried to do something that is not supported \
(spend shielded outputs of prior transaction steps or use a feature that \
is not enabled).",
),
Error::KeyNotRecognized => {
write!(
f,
"Wallet does not contain an account corresponding to the provided spending key"
)
}
Error::AccountCannotSpend => {
write!(
f,
"The given account cannot be used for spending, because it is unable to maintain an accurate balance.",
)
}
Error::AccountIdNotRecognized => {
write!(
f,
"Wallet does not contain an account corresponding to the provided ID"
)
}
Error::BalanceError(e) => write!(
f,
"The value lies outside the valid range of Zcash amounts: {e:?}."
),
Error::InsufficientFunds {
available,
required,
} => write!(
f,
"Insufficient balance (have {}, need {} including fee)",
u64::from(*available),
u64::from(*required)
),
Error::ScanRequired => write!(f, "Must scan blocks first"),
Error::Builder(e) => write!(f, "An error occurred building the transaction: {e}"),
Error::Payment(e) => write!(f, "An error occurred constructing a payment: {e}"),
Error::UnsupportedChangeType(t) => write!(
f,
"Attempted to send change to an unsupported pool type: {t}"
),
Error::NoSupportedReceivers(ua) => write!(
f,
"A recipient's unified address does not contain any receivers to which the wallet can send funds; required one of {}",
ua.receiver_types()
.iter()
.enumerate()
.fold(String::new(), |mut acc, (i, tc)| {
let _ = write!(acc, "{}{:?}", if i > 0 { ", " } else { "" }, tc);
acc
})
),
Error::KeyNotAvailable(pool) => write!(
f,
"A key required for transaction construction was not available for pool type {pool}"
),
Error::NoteMismatch(n) => write!(
f,
"A note being spent ({n:?}) does not correspond to either the internal or external full viewing key for the provided spending key."
),
Error::Address(e) => {
write!(
f,
"An error occurred decoding the address from a payment request: {e}."
)
}
#[cfg(feature = "transparent-inputs")]
Error::AddressNotRecognized(_) => {
write!(
f,
"The specified transparent address was not recognized as belonging to the wallet."
)
}
Error::ExpiryHeightConflictsWithCanonicalCrossing { requested } => write!(
f,
"An expiry height of {requested} was requested for a canonical ZIP 318 crossing, \
which takes the ZIP 318 rolling expiry; pass `None` to accept it."
),
Error::ExpiryHeightBelowTargetHeight {
expiry_height,
min_target_height,
} => write!(
f,
"The requested expiry height {expiry_height} is below the proposal's \
minimum target height {min_target_height}; the transaction would already be \
expired at the earliest height at which it could be mined."
),
#[cfg(feature = "pczt")]
Error::Pczt(e) => write!(f, "PCZT error: {e}"),
}
}
}
#[cfg(feature = "pczt")]
impl fmt::Display for PcztError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
PcztError::Build => {
write!(
f,
"Failed to generate the PCZT prior to proving or signing."
)
}
PcztError::IoFinalization(e) => {
write!(f, "Failed to finalize IO: {e:?}.")
}
PcztError::UpdateOrchard(e) => {
write!(f, "Failed to updating Orchard PCZT data: {e:?}.")
}
PcztError::UpdateSapling(e) => {
write!(f, "Failed to updating Sapling PCZT data: {e:?}.")
}
PcztError::UpdateTransparent(e) => {
write!(f, "Failed to updating transparent PCZT data: {e:?}.")
}
PcztError::SpendFinalization(e) => {
write!(f, "Failed to finalize the PCZT spends: {e:?}.")
}
PcztError::Extraction(e) => {
write!(f, "Failed to extract the final transaction: {e:?}.")
}
PcztError::Invalid(e) => {
write!(f, "PCZT parsing resulted in an invalid condition: {e}.")
}
}
}
}
impl<DE, TE, SE, FE, CE, N> error::Error for Error<DE, TE, SE, FE, CE, N>
where
DE: Debug + Display + error::Error + 'static,
TE: Debug + Display + error::Error + 'static,
SE: Debug + Display + error::Error + 'static,
FE: Debug + Display + 'static,
CE: Debug + Display + error::Error + 'static,
N: Debug + Display + 'static,
{
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match &self {
Error::DataSource(e) => Some(e),
Error::CommitmentTree(e) => Some(e),
Error::NoteSelection(e) => Some(e),
Error::Proposal(e) => Some(e),
Error::Builder(e) => Some(e),
#[cfg(feature = "pczt")]
Error::Pczt(e) => Some(e),
_ => None,
}
}
}
#[cfg(feature = "pczt")]
impl error::Error for PcztError {}
impl<DE, TE, SE, FE, CE, N> From<builder::Error<FE>> for Error<DE, TE, SE, FE, CE, N> {
fn from(e: builder::Error<FE>) -> Self {
Error::Builder(e)
}
}
impl<DE, TE, SE, FE, CE, N> From<ProposalError> for Error<DE, TE, SE, FE, CE, N> {
fn from(e: ProposalError) -> Self {
Error::Proposal(e)
}
}
impl<DE, TE, SE, FE, CE, N> From<BalanceError> for Error<DE, TE, SE, FE, CE, N> {
fn from(e: BalanceError) -> Self {
Error::BalanceError(e)
}
}
impl<DE, TE, SE, FE, CE, N> From<ConversionError<&'static str>> for Error<DE, TE, SE, FE, CE, N> {
fn from(value: ConversionError<&'static str>) -> Self {
Error::Address(value)
}
}
impl<DE, TE, SE, FE, CE, N> From<InputSelectorError<DE, SE, CE, N>>
for Error<DE, TE, SE, FE, CE, N>
{
fn from(e: InputSelectorError<DE, SE, CE, N>) -> Self {
match e {
InputSelectorError::DataSource(e) => Error::DataSource(e),
InputSelectorError::Selection(e) => Error::NoteSelection(e),
InputSelectorError::Change(e) => Error::Change(e),
InputSelectorError::Proposal(e) => Error::Proposal(e),
InputSelectorError::InsufficientFunds {
available,
required,
} => Error::InsufficientFunds {
available,
required,
},
InputSelectorError::SyncRequired => Error::ScanRequired,
InputSelectorError::Address(e) => Error::Address(e),
}
}
}
impl<DE, TE, SE, FE, CE, N> From<sapling::builder::Error> for Error<DE, TE, SE, FE, CE, N> {
fn from(e: sapling::builder::Error) -> Self {
Error::Builder(builder::Error::SaplingBuild(e))
}
}
impl<DE, TE, SE, FE, CE, N> From<transparent::builder::Error> for Error<DE, TE, SE, FE, CE, N> {
fn from(e: ::transparent::builder::Error) -> Self {
Error::Builder(builder::Error::TransparentBuild(e))
}
}
impl<DE, TE, SE, FE, CE, N> From<ShardTreeError<TE>> for Error<DE, TE, SE, FE, CE, N> {
fn from(e: ShardTreeError<TE>) -> Self {
Error::CommitmentTree(e)
}
}
#[cfg(feature = "pczt")]
impl<DE, TE, SE, FE, CE, N> From<PcztError> for Error<DE, TE, SE, FE, CE, N> {
fn from(e: PcztError) -> Self {
Error::Pczt(e)
}
}
#[cfg(feature = "pczt")]
impl<DE, TE, SE, FE, CE, N> From<pczt::roles::io_finalizer::Error>
for Error<DE, TE, SE, FE, CE, N>
{
fn from(e: pczt::roles::io_finalizer::Error) -> Self {
Error::Pczt(PcztError::IoFinalization(e))
}
}
#[cfg(feature = "pczt")]
impl<DE, TE, SE, FE, CE, N> From<pczt::roles::updater::OrchardError>
for Error<DE, TE, SE, FE, CE, N>
{
fn from(e: pczt::roles::updater::OrchardError) -> Self {
Error::Pczt(PcztError::UpdateOrchard(e))
}
}
#[cfg(feature = "pczt")]
impl<DE, TE, SE, FE, CE, N> From<pczt::roles::updater::SaplingError>
for Error<DE, TE, SE, FE, CE, N>
{
fn from(e: pczt::roles::updater::SaplingError) -> Self {
Error::Pczt(PcztError::UpdateSapling(e))
}
}
#[cfg(feature = "pczt")]
impl<DE, TE, SE, FE, CE, N> From<pczt::roles::updater::TransparentError>
for Error<DE, TE, SE, FE, CE, N>
{
fn from(e: pczt::roles::updater::TransparentError) -> Self {
Error::Pczt(PcztError::UpdateTransparent(e))
}
}
#[cfg(feature = "pczt")]
impl<DE, TE, SE, FE, CE, N> From<pczt::roles::spend_finalizer::Error>
for Error<DE, TE, SE, FE, CE, N>
{
fn from(e: pczt::roles::spend_finalizer::Error) -> Self {
Error::Pczt(PcztError::SpendFinalization(e))
}
}
#[cfg(feature = "pczt")]
impl<DE, TE, SE, FE, CE, N> From<pczt::roles::tx_extractor::Error>
for Error<DE, TE, SE, FE, CE, N>
{
fn from(e: pczt::roles::tx_extractor::Error) -> Self {
Error::Pczt(PcztError::Extraction(e))
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum FindAccountForAddressError<E> {
Backend(E),
UnifiedAddressConflict,
}
impl<E> From<E> for FindAccountForAddressError<E> {
fn from(err: E) -> Self {
Self::Backend(err)
}
}
impl<E: Display> Display for FindAccountForAddressError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FindAccountForAddressError::Backend(e) => {
write!(f, "Wallet backend error: {e}")
}
FindAccountForAddressError::UnifiedAddressConflict => write!(
f,
"Receivers of the provided Unified Address map to different wallet accounts."
),
}
}
}
impl<E: error::Error + 'static> error::Error for FindAccountForAddressError<E> {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
FindAccountForAddressError::Backend(e) => Some(e),
FindAccountForAddressError::UnifiedAddressConflict => None,
}
}
}
pub use super::locking::LockError;