Skip to content

Commit 8be259c

Browse files
authored
Merge pull request #1930 from zcash/feature/zallet_listunspent
zcash_client_backend: Add `InputSource::select_unspent_notes`
2 parents 7760237 + 4722a17 commit 8be259c

File tree

13 files changed

+172
-58
lines changed

13 files changed

+172
-58
lines changed

zcash_client_backend/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ workspace.
1818
- `AccountBirthday::{from_parts, prior_chain_state}`
1919
- `Balance::uneconomic_value`
2020
- `MaxSpendMode`
21+
- `ReceivedNotes` (replaces `SpendableNotes` globally)
2122
- `TransactionsInvolvingAddress`
2223
- `TransactionDataRequest::transactions_involving_address`
2324
- `wallet::ConfirmationsPolicy`
@@ -61,6 +62,7 @@ workspace.
6162
as its argument instead of its parts. This minimizes the API complexity
6263
that would otherwise arise due to the presence of the `zcashd-compat`
6364
feature flag.
65+
- `InputSource` has added method `select_unspent_notes`.
6466
- The following methods now take `TargetHeight` and `ConfirmationsPolicy`
6567
arguments instead of an anchor height:
6668
- `InputSource::select_spendable_notes`
@@ -117,6 +119,9 @@ workspace.
117119
- `zcash_client_backend::wallet::ReceivedNote::from_parts` takes an additional
118120
`mined_height` argument.
119121

122+
### Removed
123+
- `zcash_client_backend::data_api::SpendableNotes` (renamed to `ReceivedNotes`)
124+
120125
## [0.18.1, 0.19.1] - 2025-07-19
121126

122127
### Changed

zcash_client_backend/src/data_api.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -839,16 +839,16 @@ impl<NoteRef> NoteRetention<NoteRef> for SimpleNoteRetention {
839839
}
840840
}
841841

842-
/// Spendable shielded outputs controlled by the wallet.
842+
/// Shielded outputs that were received by the wallet.
843843
#[derive(Debug)]
844-
pub struct SpendableNotes<NoteRef> {
844+
pub struct ReceivedNotes<NoteRef> {
845845
sapling: Vec<ReceivedNote<NoteRef, sapling::Note>>,
846846
#[cfg(feature = "orchard")]
847847
orchard: Vec<ReceivedNote<NoteRef, orchard::note::Note>>,
848848
}
849849

850-
impl<NoteRef> SpendableNotes<NoteRef> {
851-
/// Construct a new empty [`SpendableNotes`].
850+
impl<NoteRef> ReceivedNotes<NoteRef> {
851+
/// Construct a new empty [`ReceivedNotes`].
852852
pub fn empty() -> Self {
853853
Self::new(
854854
vec![],
@@ -857,7 +857,7 @@ impl<NoteRef> SpendableNotes<NoteRef> {
857857
)
858858
}
859859

860-
/// Construct a new [`SpendableNotes`] from its constituent parts.
860+
/// Construct a new [`ReceivedNotes`] from its constituent parts.
861861
pub fn new(
862862
sapling: Vec<ReceivedNote<NoteRef, sapling::Note>>,
863863
#[cfg(feature = "orchard")] orchard: Vec<ReceivedNote<NoteRef, orchard::note::Note>>,
@@ -915,7 +915,7 @@ impl<NoteRef> SpendableNotes<NoteRef> {
915915
return (self.sapling_value()? + self.orchard_value()?).ok_or(BalanceError::Overflow);
916916
}
917917

918-
/// Consumes this [`SpendableNotes`] value and produces a vector of
918+
/// Consumes this [`ReceivedNotes`] value and produces a vector of
919919
/// [`ReceivedNote<NoteRef, Note>`] values.
920920
pub fn into_vec(
921921
self,
@@ -1364,7 +1364,17 @@ pub trait InputSource {
13641364
target_height: TargetHeight,
13651365
confirmations_policy: ConfirmationsPolicy,
13661366
exclude: &[Self::NoteRef],
1367-
) -> Result<SpendableNotes<Self::NoteRef>, Self::Error>;
1367+
) -> Result<ReceivedNotes<Self::NoteRef>, Self::Error>;
1368+
1369+
/// Returns the list of notes belonging to the wallet that are unspent as of the specified
1370+
/// target height.
1371+
fn select_unspent_notes(
1372+
&self,
1373+
account: Self::AccountId,
1374+
sources: &[ShieldedProtocol],
1375+
target_height: TargetHeight,
1376+
exclude: &[Self::NoteRef],
1377+
) -> Result<ReceivedNotes<Self::NoteRef>, Self::Error>;
13681378

13691379
/// Returns metadata describing the structure of the wallet for the specified account.
13701380
///

zcash_client_backend/src/data_api/testing.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ use super::{
5656
},
5757
Account, AccountBalance, AccountBirthday, AccountMeta, AccountPurpose, AccountSource,
5858
AddressInfo, BlockMetadata, DecryptedTransaction, InputSource, NoteFilter, NullifierQuery,
59-
ScannedBlock, SeedRelevance, SentTransaction, SpendableNotes, TransactionDataRequest,
59+
ReceivedNotes, ScannedBlock, SeedRelevance, SentTransaction, TransactionDataRequest,
6060
TransactionStatus, WalletCommitmentTrees, WalletRead, WalletSummary, WalletTest, WalletWrite,
6161
Zip32Derivation, SAPLING_SHARD_HEIGHT,
6262
};
@@ -2555,8 +2555,18 @@ impl InputSource for MockWalletDb {
25552555
_target_height: TargetHeight,
25562556
_confirmations_policy: ConfirmationsPolicy,
25572557
_exclude: &[Self::NoteRef],
2558-
) -> Result<SpendableNotes<Self::NoteRef>, Self::Error> {
2559-
Ok(SpendableNotes::empty())
2558+
) -> Result<ReceivedNotes<Self::NoteRef>, Self::Error> {
2559+
Ok(ReceivedNotes::empty())
2560+
}
2561+
2562+
fn select_unspent_notes(
2563+
&self,
2564+
_account: Self::AccountId,
2565+
_sources: &[ShieldedProtocol],
2566+
_target_height: TargetHeight,
2567+
_exclude: &[Self::NoteRef],
2568+
) -> Result<ReceivedNotes<Self::NoteRef>, Self::Error> {
2569+
Err(())
25602570
}
25612571

25622572
fn get_account_metadata(

zcash_client_backend/src/data_api/wallet.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -395,10 +395,8 @@ impl Default for ConfirmationsPolicy {
395395

396396
impl ConfirmationsPolicy {
397397
/// A policy to use the minimum number of confirmations possible: 1 confirmation for shielded
398-
/// notes irrespective of origin, and 0 confirmations for transparent UTXOs.
399-
///
400-
/// Test-only.
401-
#[cfg(any(test, feature = "test-dependencies"))]
398+
/// notes irrespective of origin, and 0 confirmations for spends of transparent UTXOs in
399+
/// wallet-internal shielding transactions.
402400
pub const MIN: Self = ConfirmationsPolicy {
403401
trusted: NonZeroU32::MIN,
404402
untrusted: NonZeroU32::MIN,

zcash_client_backend/src/data_api/wallet/input_selection.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use zip321::TransactionRequest;
2525

2626
use crate::{
2727
data_api::{
28-
wallet::TargetHeight, InputSource, MaxSpendMode, SimpleNoteRetention, SpendableNotes,
28+
wallet::TargetHeight, InputSource, MaxSpendMode, ReceivedNotes, SimpleNoteRetention,
2929
TargetValue,
3030
},
3131
fees::{sapling, ChangeError, ChangeStrategy, EphemeralBalance, TransactionBalance},
@@ -478,7 +478,7 @@ impl<DbT: InputSource> InputSelector for GreedyInputSelector<DbT> {
478478
}
479479
}
480480

481-
let mut shielded_inputs = SpendableNotes::empty();
481+
let mut shielded_inputs = ReceivedNotes::empty();
482482
let mut prior_available = Zatoshis::ZERO;
483483
let mut amount_required = Zatoshis::ZERO;
484484
let mut exclude: Vec<DbT::NoteRef> = vec![];

zcash_client_backend/src/wallet.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,8 +377,8 @@ impl Note {
377377
}
378378
}
379379

380-
/// Information about a note that is tracked by the wallet that is available for spending,
381-
/// with sufficient information for use in note selection.
380+
/// A note that was received by the wallet, along with contextual information about the output that
381+
/// generated the note and the key that is required to spend it.
382382
#[derive(Clone, PartialEq, Eq)]
383383
pub struct ReceivedNote<NoteRef, NoteT> {
384384
note_id: NoteRef,

zcash_client_memory/src/input_source.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::num::NonZeroU32;
33
use zcash_client_backend::{
44
data_api::{
55
wallet::{ConfirmationsPolicy, TargetHeight},
6-
AccountMeta, InputSource, NoteFilter, PoolMeta, TargetValue, WalletRead,
6+
AccountMeta, InputSource, NoteFilter, PoolMeta, ReceivedNotes, TargetValue, WalletRead,
77
},
88
wallet::NoteId,
99
};
@@ -107,7 +107,7 @@ impl<P: consensus::Parameters> InputSource for MemoryWalletDb<P> {
107107
target_height: TargetHeight,
108108
confirmations_policy: ConfirmationsPolicy,
109109
exclude: &[Self::NoteRef],
110-
) -> Result<zcash_client_backend::data_api::SpendableNotes<Self::NoteRef>, Self::Error> {
110+
) -> Result<zcash_client_backend::data_api::ReceivedNotes<Self::NoteRef>, Self::Error> {
111111
let sapling_eligible_notes = if sources.contains(&Sapling) {
112112
self.select_spendable_notes_from_pool(
113113
account,
@@ -142,6 +142,16 @@ impl<P: consensus::Parameters> InputSource for MemoryWalletDb<P> {
142142
)
143143
}
144144

145+
fn select_unspent_notes(
146+
&self,
147+
_account: Self::AccountId,
148+
_sources: &[ShieldedProtocol],
149+
_target_height: TargetHeight,
150+
_exclude: &[Self::NoteRef],
151+
) -> Result<ReceivedNotes<Self::NoteRef>, Self::Error> {
152+
unimplemented!()
153+
}
154+
145155
/// Returns the list of spendable transparent outputs received by this wallet at `address`
146156
/// such that, at height `target_height`:
147157
/// * the transaction that produced the output had or will have at least `min_confirmations`

zcash_client_memory/src/types/notes/received.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use zcash_primitives::transaction::TxId;
1111
use zcash_protocol::{memo::Memo, PoolType, ShieldedProtocol::Sapling};
1212

1313
use zcash_client_backend::{
14-
data_api::{SentTransactionOutput, SpendableNotes},
14+
data_api::{ReceivedNotes, SentTransactionOutput},
1515
wallet::{Note, NoteId, Recipient, WalletSaplingOutput},
1616
};
1717

@@ -294,7 +294,7 @@ impl DerefMut for ReceivedNoteTable {
294294
pub(crate) fn to_spendable_notes(
295295
sapling_received_notes: &[&ReceivedNote],
296296
#[cfg(feature = "orchard")] orchard_received_notes: &[&ReceivedNote],
297-
) -> Result<SpendableNotes<NoteId>, Error> {
297+
) -> Result<ReceivedNotes<NoteId>, Error> {
298298
let sapling = sapling_received_notes
299299
.iter()
300300
.map(|note| {
@@ -341,7 +341,7 @@ pub(crate) fn to_spendable_notes(
341341
})
342342
.collect::<Result<Vec<_>, _>>()?;
343343

344-
Ok(SpendableNotes::new(
344+
Ok(ReceivedNotes::new(
345345
sapling,
346346
#[cfg(feature = "orchard")]
347347
orchard,

zcash_client_sqlite/src/lib.rs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ use zcash_client_backend::{
6161
scanning::{ScanPriority, ScanRange},
6262
wallet::{ConfirmationsPolicy, TargetHeight},
6363
Account, AccountBirthday, AccountMeta, AccountPurpose, AccountSource, AddressInfo,
64-
BlockMetadata, DecryptedTransaction, InputSource, NoteFilter, NullifierQuery, ScannedBlock,
65-
SeedRelevance, SentTransaction, SpendableNotes, TargetValue, TransactionDataRequest,
66-
WalletCommitmentTrees, WalletRead, WalletSummary, WalletWrite, Zip32Derivation,
67-
SAPLING_SHARD_HEIGHT,
64+
BlockMetadata, DecryptedTransaction, InputSource, NoteFilter, NullifierQuery,
65+
ReceivedNotes, ScannedBlock, SeedRelevance, SentTransaction, TargetValue,
66+
TransactionDataRequest, WalletCommitmentTrees, WalletRead, WalletSummary, WalletWrite,
67+
Zip32Derivation, SAPLING_SHARD_HEIGHT,
6868
},
6969
proto::compact_formats::CompactBlock,
7070
wallet::{Note, NoteId, ReceivedNote, WalletTransparentOutput},
@@ -581,8 +581,8 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters, CL, R> InputSour
581581
target_height: TargetHeight,
582582
confirmations_policy: ConfirmationsPolicy,
583583
exclude: &[Self::NoteRef],
584-
) -> Result<SpendableNotes<Self::NoteRef>, Self::Error> {
585-
Ok(SpendableNotes::new(
584+
) -> Result<ReceivedNotes<Self::NoteRef>, Self::Error> {
585+
Ok(ReceivedNotes::new(
586586
if sources.contains(&ShieldedProtocol::Sapling) {
587587
wallet::sapling::select_spendable_sapling_notes(
588588
self.conn.borrow(),
@@ -613,6 +613,48 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters, CL, R> InputSour
613613
))
614614
}
615615

616+
fn select_unspent_notes(
617+
&self,
618+
account: Self::AccountId,
619+
sources: &[ShieldedProtocol],
620+
target_height: TargetHeight,
621+
exclude: &[Self::NoteRef],
622+
) -> Result<ReceivedNotes<Self::NoteRef>, Self::Error> {
623+
Ok(ReceivedNotes::new(
624+
if sources.contains(&ShieldedProtocol::Sapling) {
625+
wallet::common::select_unspent_notes(
626+
self.conn.borrow(),
627+
&self.params,
628+
account,
629+
target_height,
630+
ConfirmationsPolicy::MIN,
631+
exclude,
632+
ShieldedProtocol::Sapling,
633+
wallet::sapling::to_received_note,
634+
wallet::common::NoteRequest::Unspent,
635+
)?
636+
} else {
637+
vec![]
638+
},
639+
#[cfg(feature = "orchard")]
640+
if sources.contains(&ShieldedProtocol::Orchard) {
641+
wallet::common::select_unspent_notes(
642+
self.conn.borrow(),
643+
&self.params,
644+
account,
645+
target_height,
646+
ConfirmationsPolicy::MIN,
647+
exclude,
648+
ShieldedProtocol::Orchard,
649+
wallet::orchard::to_received_note,
650+
wallet::common::NoteRequest::Unspent,
651+
)?
652+
} else {
653+
vec![]
654+
},
655+
))
656+
}
657+
616658
#[cfg(feature = "transparent-inputs")]
617659
fn get_unspent_transparent_output(
618660
&self,

0 commit comments

Comments
 (0)