zcash_client_backend: Add Orchard spends to `create_proposed_transaction`
This commit is contained in:
parent
c6656c108b
commit
f27f601b7d
|
@ -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.
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue