zcash_client_backend: Add Orchard spends to `create_proposed_transaction`

This commit is contained in:
Kris Nuttycombe 2024-01-05 16:33:52 -07:00
parent c6656c108b
commit f27f601b7d
4 changed files with 228 additions and 27 deletions

View File

@ -17,6 +17,8 @@ and this library adheres to Rust's notion of
- `BlockMetadata::orchard_tree_size`
- `ScannedBlock::orchard`
- `ScannedBlockCommitments::orchard`
- `ORCHARD_SHARD_HEIGHT`
- `BlockMetadata::orchard_tree_size`
- `zcash_client_backend::fees::orchard`
- `zcash_client_backend::fees::ChangeValue::orchard`
- `zcash_client_backend::wallet`:
@ -29,6 +31,10 @@ and this library adheres to Rust's notion of
- Changes to the `WalletRead` trait:
- Added `get_orchard_nullifiers`
- `ShieldedProtocol` has a new `Orchard` variant.
- `WalletCommitmentTrees` has new members when the `orchard` feature is enabled:
- `type OrchardShardStore`
- `fn with_orchard_tree_mut`
- `fn put_orchard_subtree_roots`
- `zcash_client_backend::fees`:
- Arguments to `ChangeStrategy::compute_balance` have changed.

View File

@ -8,7 +8,7 @@ use std::{
};
use incrementalmerkletree::{frontier::Frontier, Retention};
use sapling::{Node, NOTE_COMMITMENT_TREE_DEPTH};
use sapling;
use secrecy::SecretVec;
use shardtree::{error::ShardTreeError, store::ShardStore, ShardTree};
use zcash_primitives::{
@ -51,6 +51,13 @@ pub mod wallet;
/// `lightwalletd` when using the `GetSubtreeRoots` GRPC call.
pub const SAPLING_SHARD_HEIGHT: u8 = sapling::NOTE_COMMITMENT_TREE_DEPTH / 2;
/// The height of subtree roots in the Orchard note commitment tree.
///
/// This conforms to the structure of subtree data returned by
/// `lightwalletd` when using the `GetSubtreeRoots` GRPC call.
#[cfg(feature = "orchard")]
pub const ORCHARD_SHARD_HEIGHT: u8 = { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 } / 2;
/// An enumeration of constraints that can be applied when querying for nullifiers for notes
/// belonging to the wallet.
pub enum NullifierQuery {
@ -870,7 +877,7 @@ impl SentTransactionOutput {
#[derive(Clone, Debug)]
pub struct AccountBirthday {
height: BlockHeight,
sapling_frontier: Frontier<Node, NOTE_COMMITMENT_TREE_DEPTH>,
sapling_frontier: Frontier<sapling::Node, { sapling::NOTE_COMMITMENT_TREE_DEPTH }>,
recover_until: Option<BlockHeight>,
}
@ -911,7 +918,7 @@ impl AccountBirthday {
#[cfg(feature = "test-dependencies")]
pub fn from_parts(
height: BlockHeight,
sapling_frontier: Frontier<Node, NOTE_COMMITMENT_TREE_DEPTH>,
sapling_frontier: Frontier<sapling::Node, { sapling::NOTE_COMMITMENT_TREE_DEPTH }>,
recover_until: Option<BlockHeight>,
) -> Self {
Self {
@ -944,7 +951,9 @@ impl AccountBirthday {
/// Returns the Sapling note commitment tree frontier as of the end of the block at
/// [`Self::height`].
pub fn sapling_frontier(&self) -> &Frontier<Node, NOTE_COMMITMENT_TREE_DEPTH> {
pub fn sapling_frontier(
&self,
) -> &Frontier<sapling::Node, { sapling::NOTE_COMMITMENT_TREE_DEPTH }> {
&self.sapling_frontier
}
@ -1081,13 +1090,15 @@ pub trait WalletWrite: WalletRead {
/// also provide operations related to Orchard note commitment trees in the future.
pub trait WalletCommitmentTrees {
type Error;
/// The type of the backing [`ShardStore`] for the Sapling note commitment tree.
type SaplingShardStore<'a>: ShardStore<
H = sapling::Node,
CheckpointId = BlockHeight,
Error = Self::Error,
>;
///
/// Evaluates the given callback function with a reference to the Sapling
/// note commitment tree maintained by the wallet.
fn with_sapling_tree_mut<F, A, E>(&mut self, callback: F) -> Result<A, E>
where
for<'a> F: FnMut(
@ -1099,12 +1110,48 @@ pub trait WalletCommitmentTrees {
) -> Result<A, E>,
E: From<ShardTreeError<Self::Error>>;
/// Adds a sequence of note commitment tree subtree roots to the data store.
/// Adds a sequence of Sapling note commitment tree subtree roots to the data store.
///
/// Each such value should be the Merkle root of a subtree of the Sapling note commitment tree
/// containing 2^[`SAPLING_SHARD_HEIGHT`] note commitments.
fn put_sapling_subtree_roots(
&mut self,
start_index: u64,
roots: &[CommitmentTreeRoot<sapling::Node>],
) -> Result<(), ShardTreeError<Self::Error>>;
/// The type of the backing [`ShardStore`] for the Orchard note commitment tree.
#[cfg(feature = "orchard")]
type OrchardShardStore<'a>: ShardStore<
H = orchard::tree::MerkleHashOrchard,
CheckpointId = BlockHeight,
Error = Self::Error,
>;
/// Evaluates the given callback function with a reference to the Orchard
/// note commitment tree maintained by the wallet.
#[cfg(feature = "orchard")]
fn with_orchard_tree_mut<F, A, E>(&mut self, callback: F) -> Result<A, E>
where
for<'a> F: FnMut(
&'a mut ShardTree<
Self::OrchardShardStore<'a>,
{ ORCHARD_SHARD_HEIGHT * 2 },
ORCHARD_SHARD_HEIGHT,
>,
) -> Result<A, E>,
E: From<ShardTreeError<Self::Error>>;
/// Adds a sequence of Orchard note commitment tree subtree roots to the data store.
///
/// Each such value should be the Merkle root of a subtree of the Orchard note commitment tree
/// containing 2^[`ORCHARD_SHARD_HEIGHT`] note commitments.
#[cfg(feature = "orchard")]
fn put_orchard_subtree_roots(
&mut self,
start_index: u64,
roots: &[CommitmentTreeRoot<orchard::tree::MerkleHashOrchard>],
) -> Result<(), ShardTreeError<Self::Error>>;
}
#[cfg(feature = "test-dependencies")]
@ -1138,6 +1185,9 @@ pub mod testing {
#[cfg(feature = "transparent-inputs")]
use {crate::wallet::TransparentAddressMetadata, zcash_primitives::legacy::TransparentAddress};
#[cfg(feature = "orchard")]
use super::ORCHARD_SHARD_HEIGHT;
pub struct MockWalletDb {
pub network: Network,
pub sapling_tree: ShardTree<
@ -1145,6 +1195,12 @@ pub mod testing {
{ SAPLING_SHARD_HEIGHT * 2 },
SAPLING_SHARD_HEIGHT,
>,
#[cfg(feature = "orchard")]
pub orchard_tree: ShardTree<
MemoryShardStore<orchard::tree::MerkleHashOrchard, BlockHeight>,
{ ORCHARD_SHARD_HEIGHT * 2 },
ORCHARD_SHARD_HEIGHT,
>,
}
impl MockWalletDb {
@ -1152,6 +1208,8 @@ pub mod testing {
Self {
network,
sapling_tree: ShardTree::new(MemoryShardStore::empty(), 100),
#[cfg(feature = "orchard")]
orchard_tree: ShardTree::new(MemoryShardStore::empty(), 100),
}
}
}
@ -1406,5 +1464,43 @@ pub mod testing {
Ok(())
}
#[cfg(feature = "orchard")]
type OrchardShardStore<'a> =
MemoryShardStore<orchard::tree::MerkleHashOrchard, BlockHeight>;
#[cfg(feature = "orchard")]
fn with_orchard_tree_mut<F, A, E>(&mut self, mut callback: F) -> Result<A, E>
where
for<'a> F: FnMut(
&'a mut ShardTree<
Self::OrchardShardStore<'a>,
{ ORCHARD_SHARD_HEIGHT * 2 },
ORCHARD_SHARD_HEIGHT,
>,
) -> Result<A, E>,
E: From<ShardTreeError<Self::Error>>,
{
callback(&mut self.orchard_tree)
}
/// Adds a sequence of note commitment tree subtree roots to the data store.
#[cfg(feature = "orchard")]
fn put_orchard_subtree_roots(
&mut self,
start_index: u64,
roots: &[CommitmentTreeRoot<orchard::tree::MerkleHashOrchard>],
) -> Result<(), ShardTreeError<Self::Error>> {
self.with_orchard_tree_mut(|t| {
for (root, i) in roots.iter().zip(0u64..) {
let root_addr =
Address::from_parts(ORCHARD_SHARD_HEIGHT.into(), start_index + i);
t.insert(root_addr, *root.root_hash())?;
}
Ok::<_, ShardTreeError<Self::Error>>(())
})?;
Ok(())
}
}
}

View File

@ -682,28 +682,24 @@ where
let sapling_inputs = inputs
.notes()
.iter()
.map(|selected| {
match selected.note() {
Note::Sapling(note) => {
let key = match selected.spending_key_scope() {
Scope::External => usk.sapling().clone(),
Scope::Internal => usk.sapling().derive_internal(),
};
.filter_map(|selected| match selected.note() {
Note::Sapling(note) => {
let key = match selected.spending_key_scope() {
Scope::External => usk.sapling().clone(),
Scope::Internal => usk.sapling().derive_internal(),
};
let merkle_path = sapling_tree.witness_at_checkpoint_id_caching(
sapling_tree
.witness_at_checkpoint_id_caching(
selected.note_commitment_tree_position(),
&inputs.anchor_height(),
)?;
Ok((key, note, merkle_path))
}
#[cfg(feature = "orchard")]
Note::Orchard(_) => {
// FIXME: Implement this once `Proposal` has been refactored to
// include Orchard notes.
panic!("Orchard spends are not yet supported");
}
)
.map(|merkle_path| Some((key, note, merkle_path)))
.map_err(Error::from)
.transpose()
}
#[cfg(feature = "orchard")]
Note::Orchard(_) => None,
})
.collect::<Result<Vec<_>, Error<_, _, _, _>>>()?;
@ -712,6 +708,39 @@ where
},
)?;
#[cfg(feature = "orchard")]
let (orchard_anchor, orchard_inputs) = proposal_step.shielded_inputs().map_or_else(
|| Ok((Some(orchard::Anchor::empty_tree()), vec![])),
|inputs| {
wallet_db.with_orchard_tree_mut::<_, _, Error<_, _, _, _>>(|orchard_tree| {
let anchor = orchard_tree
.root_at_checkpoint_id(&inputs.anchor_height())?
.into();
let orchard_inputs = inputs
.notes()
.iter()
.filter_map(|selected| match selected.note() {
#[cfg(feature = "orchard")]
Note::Orchard(note) => orchard_tree
.witness_at_checkpoint_id_caching(
selected.note_commitment_tree_position(),
&inputs.anchor_height(),
)
.map(|merkle_path| Some((note, merkle_path)))
.map_err(Error::from)
.transpose(),
Note::Sapling(_) => None,
})
.collect::<Result<Vec<_>, Error<_, _, _, _>>>()?;
Ok((Some(anchor), orchard_inputs))
})
},
)?;
#[cfg(not(feature = "orchard"))]
let orchard_anchor = None;
// Create the transaction. The type of the proposal ensures that there
// are no possible transparent inputs, so we ignore those
let mut builder = Builder::new(
@ -719,12 +748,17 @@ where
min_target_height,
BuildConfig::Standard {
sapling_anchor: Some(sapling_anchor),
orchard_anchor: None,
orchard_anchor,
},
);
for (key, note, merkle_path) in sapling_inputs.into_iter() {
builder.add_sapling_spend(&key, note.clone(), merkle_path)?;
for (sapling_key, sapling_note, merkle_path) in sapling_inputs.into_iter() {
builder.add_sapling_spend(&sapling_key, sapling_note.clone(), merkle_path)?;
}
#[cfg(feature = "orchard")]
for (orchard_note, merkle_path) in orchard_inputs.into_iter() {
builder.add_orchard_spend(usk.orchard(), *orchard_note, merkle_path.into())?;
}
#[cfg(feature = "transparent-inputs")]

View File

@ -74,6 +74,9 @@ use zcash_client_backend::{
use crate::{error::SqliteClientError, wallet::commitment_tree::SqliteShardStore};
#[cfg(feature = "orchard")]
use zcash_client_backend::data_api::ORCHARD_SHARD_HEIGHT;
#[cfg(feature = "transparent-inputs")]
use {
zcash_client_backend::wallet::TransparentAddressMetadata,
@ -805,6 +808,37 @@ impl<P: consensus::Parameters> WalletCommitmentTrees for WalletDb<rusqlite::Conn
.map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
Ok(())
}
#[cfg(feature = "orchard")]
type OrchardShardStore<'a> = SqliteShardStore<
&'a rusqlite::Transaction<'a>,
orchard::tree::MerkleHashOrchard,
ORCHARD_SHARD_HEIGHT,
>;
#[cfg(feature = "orchard")]
fn with_orchard_tree_mut<F, A, E>(&mut self, _callback: F) -> Result<A, E>
where
for<'a> F: FnMut(
&'a mut ShardTree<
Self::OrchardShardStore<'a>,
{ ORCHARD_SHARD_HEIGHT * 2 },
ORCHARD_SHARD_HEIGHT,
>,
) -> Result<A, E>,
E: From<ShardTreeError<Self::Error>>,
{
todo!()
}
#[cfg(feature = "orchard")]
fn put_orchard_subtree_roots(
&mut self,
_start_index: u64,
_roots: &[CommitmentTreeRoot<orchard::tree::MerkleHashOrchard>],
) -> Result<(), ShardTreeError<Self::Error>> {
todo!()
}
}
impl<'conn, P: consensus::Parameters> WalletCommitmentTrees for WalletDb<SqlTransaction<'conn>, P> {
@ -845,6 +879,37 @@ impl<'conn, P: consensus::Parameters> WalletCommitmentTrees for WalletDb<SqlTran
roots,
)
}
#[cfg(feature = "orchard")]
type OrchardShardStore<'a> = SqliteShardStore<
&'a rusqlite::Transaction<'a>,
orchard::tree::MerkleHashOrchard,
ORCHARD_SHARD_HEIGHT,
>;
#[cfg(feature = "orchard")]
fn with_orchard_tree_mut<F, A, E>(&mut self, _callback: F) -> Result<A, E>
where
for<'a> F: FnMut(
&'a mut ShardTree<
Self::OrchardShardStore<'a>,
{ ORCHARD_SHARD_HEIGHT * 2 },
ORCHARD_SHARD_HEIGHT,
>,
) -> Result<A, E>,
E: From<ShardTreeError<Self::Error>>,
{
todo!()
}
#[cfg(feature = "orchard")]
fn put_orchard_subtree_roots(
&mut self,
_start_index: u64,
_roots: &[CommitmentTreeRoot<orchard::tree::MerkleHashOrchard>],
) -> Result<(), ShardTreeError<Self::Error>> {
todo!()
}
}
/// A handle for the SQLite block source.