1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
//! Error types for problems that may arise when reading or storing wallet data to SQLite.
use std::error;
use std::fmt;
use std::ops::Range;
#[cfg(feature = "orchard")]
use incrementalmerkletree::Position;
use nonempty::NonEmpty;
#[cfg(feature = "orchard")]
use shardtree::error::InsertionError;
use shardtree::error::ShardTreeError;
#[cfg(feature = "transparent-key-import")]
use uuid::Uuid;
use zcash_address::ParseError;
use zcash_client_backend::data_api::NoteFilter;
use zcash_client_backend::data_api::error::RewindError;
use zcash_client_backend::data_api::ll;
use zcash_client_backend::data_api::ll::wallet::PutBlocksError;
#[cfg(feature = "transparent-inputs")]
use zcash_client_backend::data_api::transparent_ledger::CommitRejection;
#[cfg(feature = "transparent-inputs")]
use zcash_client_backend::data_api::transparent_ledger::RecoveryBlocker;
use zcash_client_backend::wallet::OutputRef;
use zcash_keys::address::UnifiedAddress;
use zcash_keys::keys::AddressGenerationError;
use zcash_protocol::{PoolType, ShieldedPool, TxId, consensus::BlockHeight, value::BalanceError};
use zip32::DiversifierIndex;
use crate::{
AccountUuid,
wallet::{commitment_tree, common::ErrUnsupportedPool},
};
#[cfg(feature = "transparent-inputs")]
use {
crate::wallet::transparent::SchedulingError,
::transparent::{address::TransparentAddress, keys::TransparentKeyScope},
zcash_keys::{
encoding::TransparentCodecError, keys::transparent::gap_limits::GapAddressesError,
},
};
/// The primary error type for the SQLite wallet backend.
#[derive(Debug)]
#[non_exhaustive]
pub enum SqliteClientError {
/// Status work requires an explicit disclosure policy.
StatusModeNotConfigured,
/// Payload-work enumeration requires an explicit enhancement mode on this handle.
#[cfg(feature = "orchard")]
EnhancementModeNotConfigured,
/// Transparent ledger APIs require an explicit mode on this handle.
TransparentLedgerModeNotConfigured,
/// Consuming transparent inputs requires transparent authority, which is not available.
/// The [`AuthorityRefusal`] says whether waiting for private recovery can change that.
TransparentAuthorityUnavailable(AuthorityRefusal),
/// Public transparent discovery is forbidden by the handle's transparent ledger mode.
PublicTransparentDiscoveryForbidden,
/// The wallet's transparent ledger state requires a newer reader than this build.
TransparentLedgerIncompatible {
/// The minimum reader version the wallet requires.
required: i64,
},
/// An operation captured a durable policy generation that no longer matches the wallet.
/// Another connection applied a policy transition first; the queue is left unchanged.
StaleTransparentPolicy {
/// The generation the operation captured at the start of its SQLite transaction.
expected: u64,
/// The generation currently stored on the wallet.
applied: u64,
},
/// Recovery commits and activation require a `PrivateRequired` policy, both durably applied
/// and configured on the handle.
TransparentRecoveryNotEnabled,
/// A candidate recovery commit was refused; none of its facts were applied.
#[cfg(feature = "transparent-inputs")]
TransparentLedgerCommitRejected(CommitRejection),
/// An account could not be activated to private transparent authority for the listed
/// reasons; nothing was changed.
#[cfg(feature = "transparent-inputs")]
TransparentActivationBlocked(Vec<RecoveryBlocker>),
/// Decoding of a stored value from its serialized form has failed.
CorruptedData(String),
/// An error occurred decoding a protobuf message.
Protobuf(prost::DecodeError),
/// The rcm value for a note cannot be decoded to a valid JubJub point.
InvalidNote,
/// Illegal attempt to reinitialize an already-initialized wallet database.
TableNotEmpty,
/// A Zcash key or address decoding error
DecodingError(ParseError),
/// An error produced in legacy transparent address derivation
#[cfg(feature = "transparent-inputs")]
TransparentDerivation(bip32::Error),
/// An error encountered in decoding a transparent address from its
/// serialized form.
#[cfg(feature = "transparent-inputs")]
TransparentAddress(TransparentCodecError),
/// Wrapper for rusqlite errors.
DbError(rusqlite::Error),
/// Wrapper for errors from the IO subsystem
Io(std::io::Error),
/// A received memo cannot be interpreted as a UTF-8 string.
InvalidMemo(zcash_protocol::memo::Error),
/// An attempt to update block data would overwrite the current hash for a block with a
/// different hash. This indicates that a required rewind was not performed.
BlockConflict(BlockHeight),
/// A range of blocks provided to the database as a unit was non-sequential
NonSequentialBlocks,
/// A requested rewind would violate invariants of the storage layer. The payload returned with
/// this error is (safe rewind height, requested height). If no safe rewind height can be
/// determined, the safe rewind height member will be `None`.
RequestedRewindInvalid {
/// The height to which it is possible to safely rewind, or `None` if no safe
/// rewind height could be determined.
safe_rewind_height: Option<BlockHeight>,
/// The block height that was requested for the rewind.
requested_height: BlockHeight,
},
/// An error occurred in generating a Zcash address.
AddressGeneration(AddressGenerationError),
/// The account for which information was requested does not belong to the wallet.
AccountUnknown,
/// The account being added collides with an existing account in the wallet with the given ID.
/// The collision can be on the seed and ZIP-32 account index, or a shared IVK component.
AccountCollision(AccountUuid),
/// The account was imported, and ZIP-32 derivation information is not known for it.
UnknownZip32Derivation,
/// An error occurred deriving a spending key from a seed and a ZIP-32 account index.
KeyDerivationError(zip32::AccountId),
/// An error occurred while processing an account due to a failure in deriving the account's keys.
BadAccountData(String),
/// A caller attempted to construct a new account with an invalid ZIP 32 account identifier.
Zip32AccountIndexOutOfRange,
/// The address associated with a record being inserted was not recognized as
/// belonging to the wallet.
#[cfg(feature = "transparent-inputs")]
AddressNotRecognized(TransparentAddress),
/// An error occurred in inserting data into or accessing data from one of the wallet's note
/// commitment trees.
CommitmentTree(ShardTreeError<commitment_tree::Error>),
/// An error occurred while inserting the note commitment data for a range of scanned blocks
/// into one of the wallet's note commitment trees during a `put_blocks` operation. The `pool`
/// and `block_range` fields record the shielded pool whose note commitment tree was being
/// updated and the range of block heights (start-inclusive, end-exclusive) that were being
/// added to the wallet when the error occurred.
PutBlocksCommitmentTree {
/// The shielded pool whose note commitment tree was being updated when the error occurred.
pool: ShieldedPool,
/// The range of block heights that were being added to the wallet when the error
/// occurred.
block_range: Range<BlockHeight>,
/// The underlying note commitment tree error.
error: ShardTreeError<commitment_tree::Error>,
},
/// An error occurred in one of the wallet's note commitment trees while truncating the wallet
/// to the specified block height. The `pool` and `height` fields record the shielded pool
/// whose note commitment tree was being updated and the block height that the wallet was being
/// truncated to when the error occurred.
TruncateCommitmentTree {
/// The shielded pool whose note commitment tree was being updated when the error occurred.
pool: ShieldedPool,
/// The block height that the wallet was being truncated to when the error occurred.
height: BlockHeight,
/// The underlying note commitment tree error.
error: ShardTreeError<commitment_tree::Error>,
},
/// The caller-supplied frontier passed to an Orchard or Ironwood
/// historical witness generation helper is inconsistent with the shard
/// data reconstructed from the wallet at the requested height.
///
/// [`WalletDb::generate_orchard_witnesses_at_historical_height`]:
/// crate::WalletDb::generate_orchard_witnesses_at_historical_height
/// [`WalletDb::generate_ironwood_witnesses_at_historical_height`]:
/// crate::WalletDb::generate_ironwood_witnesses_at_historical_height
#[cfg(feature = "orchard")]
HistoricalFrontierInvalid(InsertionError),
/// A witness could not be generated for the specified position at the
/// specified historical height in a call to an Orchard or Ironwood
/// historical witness generation helper.
///
/// The wallet most likely has not synced through `height`, the checkpoint
/// at `height` has been pruned, or `position` does not belong to the
/// wallet.
///
/// [`WalletDb::generate_orchard_witnesses_at_historical_height`]:
/// crate::WalletDb::generate_orchard_witnesses_at_historical_height
/// [`WalletDb::generate_ironwood_witnesses_at_historical_height`]:
/// crate::WalletDb::generate_ironwood_witnesses_at_historical_height
#[cfg(feature = "orchard")]
HistoricalWitnessUnavailable {
/// The note commitment tree position for which a witness was
/// requested.
position: Position,
/// The historical height at which the witness was requested.
height: BlockHeight,
},
/// The block at the specified height was not available from the block cache.
CacheMiss(BlockHeight),
/// The height of the chain was not available; a call to [`WalletWrite::update_chain_tip`] is
/// required before the requested operation can succeed.
///
/// [`WalletWrite::update_chain_tip`]:
/// zcash_client_backend::data_api::WalletWrite::update_chain_tip
ChainHeightUnknown,
/// Unsupported pool type
UnsupportedPoolType(PoolType),
/// An error occurred in computing wallet balance
BalanceError(BalanceError),
/// A note selection query contained an invalid constant or was otherwise not supported.
NoteFilterInvalid(NoteFilter),
/// An address cannot be reserved, or a proposal cannot be constructed until a transaction
/// containing outputs belonging to a previously reserved address has been mined. The error
/// contains the index that could not safely be reserved.
#[cfg(feature = "transparent-inputs")]
ReachedGapLimit(TransparentKeyScope, u32),
/// The backend encountered an attempt to reuse a diversifier index to generate an address
/// having different receivers from an address that had previously been exposed for that
/// diversifier index. Returns the previously exposed address.
DiversifierIndexReuse(DiversifierIndex, Box<UnifiedAddress>),
/// The wallet attempted to create a transaction that would use of one of the wallet's
/// previously-used addresses, potentially creating a problem with on-chain transaction
/// linkability. The returned value contains the string encoding of the address and the txid(s)
/// of the transactions in which it is known to have been used.
AddressReuse(String, NonEmpty<TxId>),
/// The wallet found one or more notes that given a certain context would be
/// ineligible and shouldn't be considered in the involved db operation.
IneligibleNotes,
/// The wallet encountered an error when attempting to schedule wallet operations.
#[cfg(feature = "transparent-inputs")]
Scheduling(SchedulingError),
/// The caller responded to a [`TransactionsInvolvingAddress`] request by querying a range of
/// block heights ending at a height that did not match the (exclusive) end of the requested
/// range.
///
/// [`TransactionsInvolvingAddress`]: zcash_client_backend::data_api::TransactionsInvolvingAddress
#[cfg(feature = "transparent-inputs")]
NotificationMismatch {
/// The expected ending block height.
expected: BlockHeight,
/// The actual ending block height returned.
actual: BlockHeight,
},
/// An attempt to import a standalone transparent address failed because it had already been
/// imported to a different account.
#[cfg(feature = "transparent-key-import")]
StandaloneImportConflict(Uuid),
/// An error returned by a [`FeeRule`] during transparent input selection. The underlying
/// error is boxed so the storage layer does not need to know every fee rule's error type.
///
/// [`FeeRule`]: zcash_primitives::transaction::fees::FeeRule
#[cfg(feature = "transparent-inputs")]
FeeRuleError(Box<dyn error::Error + Send + Sync>),
/// A `zcash_client_backend` error value carried a variant that this crate has no
/// translation for; the wrapped value carries the error itself.
///
/// Those error types are `#[non_exhaustive]`, so a variant introduced by a future
/// `zcash_client_backend` release can reach this crate before there is a specific
/// [`SqliteClientError`] counterpart for it. This error is therefore unreachable with
/// the `zcash_client_backend` release this crate is built against; encountering it
/// means this crate needs updating to translate the new variant.
BackendError(BackendError),
}
/// Why transparent authority refused to admit inputs: whether waiting for private recovery can
/// change the answer, and if not, why.
///
/// Neither form names an outpoint, transaction or address, so its [`Display`](fmt::Display)
/// text is safe to log.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AuthorityRefusal {
/// Private authority does not cover these accounts yet, and that alone kept the inputs
/// out.
///
/// Every refused input is an output the wallet holds, owned by one of `accounts`, that
/// nothing but authority excludes at the target: it is unspent (and, for
/// `WalletDb::check_transparent_transaction_inputs`, has no competing spend), its
/// transaction is mined or unexpired, it is mature, and it is not withdrawn. Each of those
/// accounts is active and not suspended from recovery, its only blockers are
/// `RecoveryBlocker::Recovery(CandidateBlocker::IncompleteCoverage)` and
/// `RecoveryBlocker::ChainBehindTip`, and it has at least one of them unless the target is
/// above the block after the chain tip. Any other blocker, including open recovery pages,
/// unresolved spends or an unknown local chain point, is [`RefusalReason::NotAdmitted`],
/// even when coverage also lags.
///
/// Retrying once the chain tip is the block before the target and private recovery covers
/// it may succeed; nothing else will. That is not a promise: recovery may find an input
/// spent or outside the ledger, and if the chain moves past the target first, the target is
/// stale and the transaction must be rebuilt.
Pending {
/// The accounts owning the refused inputs, each listed once, in input order.
accounts: Vec<AccountUuid>,
},
/// Waiting for private recovery would not change the refusal.
Refused(RefusalReason),
}
/// Why transparent authority refused inputs for a reason that waiting for private recovery
/// does not resolve. A transaction refused for an input reason must be rebuilt.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum RefusalReason {
/// No chain tip is known, so no authority applies.
ChainUnknown,
/// This build cannot read transparent state, or a required-private policy applies and
/// the operation can establish no per-account private authority: legacy public balances,
/// or a build without transparent support.
NoAuthority,
/// The wallet holds no such output.
UnknownOutput,
/// Another unexpired transaction spends the output.
CompetingSpend,
/// An earlier transaction of the same batch lacks the output a later one spends.
ChainedOutputMissing,
/// The output is spent, or is a wallet-internal ephemeral output that is likely spent.
Spent,
/// The transaction that created the output expired unmined. Never an output only a
/// recovered receive evidences, which a rewind left unplaced: it was mined, and waits as
/// [`Withdrawn`](Self::Withdrawn) says.
Expired,
/// The output is coinbase that is immature at the target.
ImmatureCoinbase,
/// The output exists only through a recovered receive that was withdrawn, or that a rewind
/// left unplaced and the owning account's recovery, covering the tip, did not place again.
/// While that recovery catches up, an unplaced receive is [`AuthorityRefusal::Pending`].
Withdrawn,
/// The owning account holds private authority, but the output is not one of its ledger's
/// placed outputs: legacy public and local-only outputs never fund a private spend.
NotInLedger,
/// The owning account is not admitted for a reason other than recovery catching up: it
/// is not active, is suspended from recovery, or its ledger has a blocker other than incomplete
/// coverage and a local chain behind the tip, such as open pages or unresolved spends.
NotAdmitted,
/// The target is at or below the chain tip; private authority covers only the block after
/// it.
StaleTarget,
}
impl fmt::Display for RefusalReason {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match self {
RefusalReason::ChainUnknown => "the chain tip is unknown",
RefusalReason::NoAuthority => "no transparent authority applies",
RefusalReason::UnknownOutput => "an input is not a wallet output",
RefusalReason::CompetingSpend => "another transaction spends an input",
RefusalReason::ChainedOutputMissing => {
"an earlier transaction of the batch lacks a spent output"
}
RefusalReason::Spent => "an input is spent",
RefusalReason::Expired => "an input's transaction expired unmined",
RefusalReason::ImmatureCoinbase => "an input is immature coinbase",
RefusalReason::Withdrawn => "an input's recovered receive was withdrawn",
RefusalReason::NotInLedger => "an input is not a private ledger output",
RefusalReason::NotAdmitted => "an input's account lacks private authority",
RefusalReason::StaleTarget => "the target is not the block after the chain tip",
})
}
}
impl fmt::Display for AuthorityRefusal {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AuthorityRefusal::Pending { accounts } => write!(
f,
"private recovery has not yet covered the chain tip for {} account(s)",
accounts.len()
),
AuthorityRefusal::Refused(reason) => reason.fmt(f),
}
}
}
/// A `zcash_client_backend` error carrying a variant that this crate has no translation for,
/// as reported by [`SqliteClientError::BackendError`].
///
/// Each variant names the operation whose error type is wrapped. The errors are boxed because
/// both are generic over [`SqliteClientError`] itself.
#[derive(Debug)]
#[non_exhaustive]
pub enum BackendError {
/// An error reported while inserting scanned blocks into the wallet.
PutBlocks(Box<PutBlocksError<SqliteClientError, commitment_tree::Error>>),
/// An error reported while rewinding the wallet to a previous chain state.
Rewind(Box<RewindError<AccountUuid, SqliteClientError>>),
}
impl fmt::Display for BackendError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
BackendError::PutBlocks(_) => write!(f, "block insertion"),
BackendError::Rewind(_) => write!(f, "rewind to a previous chain state"),
}
}
}
impl error::Error for SqliteClientError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match &self {
SqliteClientError::InvalidMemo(e) => Some(e),
SqliteClientError::DbError(e) => Some(e),
SqliteClientError::Io(e) => Some(e),
SqliteClientError::BalanceError(e) => Some(e),
SqliteClientError::AddressGeneration(e) => Some(e),
#[cfg(feature = "orchard")]
SqliteClientError::HistoricalFrontierInvalid(e) => Some(e),
#[cfg(feature = "transparent-inputs")]
SqliteClientError::FeeRuleError(e) => Some(&**e),
_ => None,
}
}
}
#[cfg(feature = "transparent-inputs")]
impl From<GapAddressesError<SqliteClientError>> for SqliteClientError {
fn from(err: GapAddressesError<SqliteClientError>) -> Self {
match err {
GapAddressesError::Storage(e) => e,
GapAddressesError::AddressGeneration(e) => SqliteClientError::AddressGeneration(e),
GapAddressesError::AccountUnknown => SqliteClientError::AccountUnknown,
}
}
}
impl fmt::Display for SqliteClientError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
SqliteClientError::StatusModeNotConfigured => write!(
f,
"Status mode is not configured; call set_status_mode before obtaining status work"
),
#[cfg(feature = "orchard")]
SqliteClientError::EnhancementModeNotConfigured => write!(
f,
"Enhancement mode is not configured; call set_enhancement_mode before enumerating enhancement work"
),
SqliteClientError::TransparentLedgerModeNotConfigured => write!(
f,
"Transparent ledger mode is not configured; call set_transparent_ledger_mode first"
),
SqliteClientError::PublicTransparentDiscoveryForbidden => write!(
f,
"Public transparent discovery is forbidden by the configured transparent ledger mode"
),
SqliteClientError::TransparentLedgerIncompatible { required } => write!(
f,
"Transparent ledger state requires reader version {required}; this build cannot operate on this wallet's transparent funds"
),
SqliteClientError::StaleTransparentPolicy { expected, applied } => write!(
f,
"Transparent policy generation {expected} is stale; the wallet is now at generation {applied}"
),
SqliteClientError::TransparentAuthorityUnavailable(refusal) => {
write!(f, "Transparent funds are unavailable: {refusal}")
}
SqliteClientError::TransparentRecoveryNotEnabled => write!(
f,
"Transparent recovery is not enabled for this operation: recovery commits and activation need PrivateRequired, both durably and on the handle"
),
#[cfg(feature = "transparent-inputs")]
SqliteClientError::TransparentLedgerCommitRejected(rejection) => {
write!(f, "Transparent ledger commit rejected: {rejection:?}")
}
#[cfg(feature = "transparent-inputs")]
SqliteClientError::TransparentActivationBlocked(blockers) => {
write!(f, "Transparent ledger activation blocked: {blockers:?}")
}
SqliteClientError::CorruptedData(reason) => {
write!(f, "Data DB is corrupted: {reason}")
}
SqliteClientError::Protobuf(e) => {
write!(f, "Failed to parse protobuf-encoded record: {e}")
}
SqliteClientError::InvalidNote => write!(f, "Invalid note"),
SqliteClientError::RequestedRewindInvalid {
safe_rewind_height,
requested_height,
} => write!(
f,
"A rewind for your wallet may only target height {} or greater; the requested height was {}.",
safe_rewind_height.map_or("<unavailable>".to_owned(), |h0| format!("{h0}")),
requested_height
),
SqliteClientError::DecodingError(e) => write!(f, "{e}"),
#[cfg(feature = "transparent-inputs")]
SqliteClientError::TransparentDerivation(e) => write!(f, "{e:?}"),
#[cfg(feature = "transparent-inputs")]
SqliteClientError::TransparentAddress(e) => write!(f, "{e}"),
SqliteClientError::TableNotEmpty => write!(f, "Table is not empty"),
SqliteClientError::DbError(e) => write!(f, "{e}"),
SqliteClientError::Io(e) => write!(f, "{e}"),
SqliteClientError::InvalidMemo(e) => write!(f, "{e}"),
SqliteClientError::BlockConflict(h) => write!(
f,
"A block hash conflict occurred at height {}; rewind required.",
u32::from(*h)
),
SqliteClientError::NonSequentialBlocks => write!(
f,
"`put_blocks` requires that the provided block range be sequential"
),
SqliteClientError::AddressGeneration(e) => write!(f, "{e}"),
SqliteClientError::AccountUnknown => write!(
f,
"The account with the given ID does not belong to this wallet."
),
SqliteClientError::UnknownZip32Derivation => write!(
f,
"ZIP-32 derivation information is not known for this account."
),
SqliteClientError::KeyDerivationError(zip32_index) => write!(
f,
"Key derivation failed for ZIP 32 account index {}",
u32::from(*zip32_index)
),
SqliteClientError::BadAccountData(e) => write!(f, "Failed to add account: {e}"),
SqliteClientError::Zip32AccountIndexOutOfRange => write!(
f,
"ZIP 32 account identifiers must be less than 0x7FFFFFFF."
),
SqliteClientError::AccountCollision(account_uuid) => write!(
f,
"An account corresponding to the data provided already exists in the wallet with UUID {account_uuid:?}."
),
#[cfg(feature = "transparent-inputs")]
SqliteClientError::AddressNotRecognized(_) => write!(
f,
"The address associated with a received txo is not identifiable as belonging to the wallet."
),
SqliteClientError::CommitmentTree(err) => write!(
f,
"An error occurred accessing or updating note commitment tree data: {err}."
),
SqliteClientError::PutBlocksCommitmentTree {
pool,
block_range,
error,
} => write!(
f,
"An error occurred updating the {pool:?} note commitment tree while adding blocks in the range {}..{}: {error}.",
u32::from(block_range.start),
u32::from(block_range.end),
),
SqliteClientError::TruncateCommitmentTree {
pool,
height,
error,
} => write!(
f,
"An error occurred updating the {pool:?} note commitment tree while truncating the wallet to height {}: {error}.",
u32::from(*height),
),
#[cfg(feature = "orchard")]
SqliteClientError::HistoricalFrontierInvalid(err) => write!(
f,
"The frontier supplied to historical witness generation is inconsistent with the wallet's shard data: {err}"
),
#[cfg(feature = "orchard")]
SqliteClientError::HistoricalWitnessUnavailable { position, height } => write!(
f,
"No witness is available for position {} at height {height} (the wallet may need to sync through this height).",
u64::from(*position),
),
SqliteClientError::CacheMiss(height) => write!(
f,
"Requested height {height} does not exist in the block cache."
),
SqliteClientError::ChainHeightUnknown => {
write!(f, "Chain height unknown; please call `update_chain_tip`")
}
SqliteClientError::UnsupportedPoolType(t) => {
write!(f, "Pool type is not currently supported: {t}")
}
SqliteClientError::BalanceError(e) => write!(f, "Balance error: {e}"),
SqliteClientError::NoteFilterInvalid(s) => {
write!(f, "Could not evaluate filter query: {s:?}")
}
#[cfg(feature = "transparent-inputs")]
SqliteClientError::ReachedGapLimit(key_scope, bad_index) => write!(
f,
"The proposal cannot be constructed until a transaction with outputs to a previously reserved {} address has been mined. \
The address at index {bad_index} could not be safely reserved.",
match *key_scope {
TransparentKeyScope::EXTERNAL => "external transparent",
TransparentKeyScope::INTERNAL => "transparent change",
TransparentKeyScope::EPHEMERAL => "ephemeral transparent",
_ => panic!("Unsupported transparent key scope."),
}
),
SqliteClientError::DiversifierIndexReuse(i, _) => {
write!(
f,
"An address has already been exposed for diversifier index {}",
u128::from(*i)
)
}
SqliteClientError::AddressReuse(address_str, txids) => {
write!(
f,
"The address {address_str} previously used in txid(s) {txids:?} would be reused."
)
}
#[cfg(feature = "transparent-inputs")]
SqliteClientError::Scheduling(err) => {
write!(f, "The wallet was unable to schedule an event: {err}")
}
#[cfg(feature = "transparent-inputs")]
SqliteClientError::NotificationMismatch { expected, actual } => {
write!(
f,
"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}"
)
}
SqliteClientError::IneligibleNotes => {
write!(
f,
"Query found notes that are considered ineligible in its context"
)
}
#[cfg(feature = "transparent-key-import")]
SqliteClientError::StandaloneImportConflict(uuid) => {
write!(
f,
"The given standalone transparent address is already managed by account {uuid}"
)
}
#[cfg(feature = "transparent-inputs")]
SqliteClientError::FeeRuleError(e) => write!(f, "Fee rule error: {e}"),
SqliteClientError::BackendError(e) => write!(
f,
"The zcash_client_backend error reported for {e} is not one this version of \
zcash_client_sqlite recognizes; this crate must be updated to handle it."
),
}
}
}
impl From<rusqlite::Error> for SqliteClientError {
fn from(e: rusqlite::Error) -> Self {
SqliteClientError::DbError(e)
}
}
impl From<std::io::Error> for SqliteClientError {
fn from(e: std::io::Error) -> Self {
SqliteClientError::Io(e)
}
}
impl From<ParseError> for SqliteClientError {
fn from(e: ParseError) -> Self {
SqliteClientError::DecodingError(e)
}
}
impl From<prost::DecodeError> for SqliteClientError {
fn from(e: prost::DecodeError) -> Self {
SqliteClientError::Protobuf(e)
}
}
#[cfg(feature = "transparent-inputs")]
impl From<bip32::Error> for SqliteClientError {
fn from(e: bip32::Error) -> Self {
SqliteClientError::TransparentDerivation(e)
}
}
#[cfg(feature = "transparent-inputs")]
impl From<TransparentCodecError> for SqliteClientError {
fn from(e: TransparentCodecError) -> Self {
SqliteClientError::TransparentAddress(e)
}
}
impl From<zcash_protocol::memo::Error> for SqliteClientError {
fn from(e: zcash_protocol::memo::Error) -> Self {
SqliteClientError::InvalidMemo(e)
}
}
impl From<ShardTreeError<commitment_tree::Error>> for SqliteClientError {
fn from(e: ShardTreeError<commitment_tree::Error>) -> Self {
SqliteClientError::CommitmentTree(e)
}
}
impl From<BalanceError> for SqliteClientError {
fn from(e: BalanceError) -> Self {
SqliteClientError::BalanceError(e)
}
}
impl From<AddressGenerationError> for SqliteClientError {
fn from(e: AddressGenerationError) -> Self {
SqliteClientError::AddressGeneration(e)
}
}
/// `zip317::FeeError` does not implement `std::error::Error`, so we wrap it in order to box
/// it as the payload of [`SqliteClientError::FeeRuleError`].
#[cfg(feature = "transparent-inputs")]
#[derive(Debug)]
struct FeeErrorWrapper(zcash_primitives::transaction::fees::zip317::FeeError);
#[cfg(feature = "transparent-inputs")]
impl fmt::Display for FeeErrorWrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
#[cfg(feature = "transparent-inputs")]
impl error::Error for FeeErrorWrapper {}
#[cfg(feature = "transparent-inputs")]
impl From<zcash_primitives::transaction::fees::zip317::FeeError> for SqliteClientError {
fn from(e: zcash_primitives::transaction::fees::zip317::FeeError) -> Self {
SqliteClientError::FeeRuleError(Box::new(FeeErrorWrapper(e)))
}
}
#[cfg(feature = "transparent-inputs")]
impl From<SchedulingError> for SqliteClientError {
fn from(value: SchedulingError) -> Self {
SqliteClientError::Scheduling(value)
}
}
impl From<PutBlocksError<SqliteClientError, commitment_tree::Error>> for SqliteClientError {
fn from(value: PutBlocksError<SqliteClientError, commitment_tree::Error>) -> Self {
match value {
ll::wallet::PutBlocksError::NonSequentialBlocks { .. } => {
SqliteClientError::NonSequentialBlocks
}
ll::wallet::PutBlocksError::Storage(e) => e,
ll::wallet::PutBlocksError::ShardTree(e) => SqliteClientError::from(e),
ll::wallet::PutBlocksError::ShardTreeForBlockRange {
pool,
block_range,
error,
} => SqliteClientError::PutBlocksCommitmentTree {
pool,
block_range,
error,
},
#[cfg(feature = "transparent-inputs")]
ll::wallet::PutBlocksError::GapAddresses(e) => SqliteClientError::from(e),
// `PutBlocksError` is `#[non_exhaustive]`, so a variant introduced by a future
// `zcash_client_backend` release reaches this conversion with no counterpart
// here until this crate is updated to map it. Report it rather than panicking.
other => SqliteClientError::BackendError(BackendError::PutBlocks(Box::new(other))),
}
}
}
impl ErrUnsupportedPool for SqliteClientError {
fn unsupported_pool_type(pool_type: PoolType) -> Self {
SqliteClientError::UnsupportedPoolType(pool_type)
}
}
/// A local LockError type for which we can write a From<rusqlite::Error> impl.
pub(crate) enum LockError {
/// Wrapper for storage errors.
Storage(rusqlite::Error),
/// The wrapped output reference was not found, or the output it refers to was already locked.
LockFailure(OutputRef),
}
impl From<rusqlite::Error> for LockError {
fn from(value: rusqlite::Error) -> Self {
LockError::Storage(value)
}
}
impl From<LockError> for zcash_client_backend::data_api::error::LockError<SqliteClientError> {
fn from(value: LockError) -> Self {
match value {
LockError::Storage(error) => zcash_client_backend::data_api::error::LockError::Storage(
SqliteClientError::from(error),
),
LockError::LockFailure(output) => {
zcash_client_backend::data_api::error::LockError::LockFailure(output)
}
}
}
}