From fa89d902ad26acb7a1163b3145d52b5d91dcd416 Mon Sep 17 00:00:00 2001 From: Hazel OHearn Date: Mon, 9 Feb 2026 15:03:45 -0400 Subject: [PATCH 1/2] wip --- zaino-state/src/chain_index.rs | 35 ++++++++++++++++++ zaino-state/src/chain_index/source.rs | 52 +++++++++++++++++++++++++-- 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/zaino-state/src/chain_index.rs b/zaino-state/src/chain_index.rs index bd0217e89..f7ba7f0b8 100644 --- a/zaino-state/src/chain_index.rs +++ b/zaino-state/src/chain_index.rs @@ -268,6 +268,14 @@ pub trait ChainIndex { hash: &types::BlockHash, ) -> impl std::future::Future>, Option>), Self::Error>>; + /// Returns the subtree roots + fn get_subtree_roots( + &self, + pool: ShieldedPool, + start_index: u16, + max_entries: Option, + ) -> impl std::future::Future>; + /// given a transaction id, returns the transaction, along with /// its consensus branch ID if available #[allow(clippy::type_complexity)] @@ -1471,6 +1479,33 @@ impl ChainIndex for NodeBackedChainIndexSubscriber MempoolInfo { self.mempool.get_mempool_info().await } + + async fn get_subtree_roots( + &self, + pool: ShieldedPool, + start_index: u16, + max_entries: Option, + ) -> Result<(), Self::Error> { + self.source().get_subtree_roots() + } +} + +/// The available shielded pools +#[non_exhaustive] +pub enum ShieldedPool { + /// Sapling + Sapling, + /// Orchard + Orchard, +} + +impl ShieldedPool { + fn pool_string(&self) -> String { + match self { + ShieldedPool::Sapling => "sapling".to_string(), + ShieldedPool::Orchard => "orchard".to_string(), + } + } } impl NonFinalizedSnapshot for Arc diff --git a/zaino-state/src/chain_index/source.rs b/zaino-state/src/chain_index/source.rs index e2864d003..45bed3cbe 100644 --- a/zaino-state/src/chain_index/source.rs +++ b/zaino-state/src/chain_index/source.rs @@ -2,7 +2,10 @@ use std::{error::Error, str::FromStr as _, sync::Arc}; -use crate::chain_index::types::{BlockHash, TransactionHash}; +use crate::chain_index::{ + types::{BlockHash, TransactionHash}, + ShieldedPool, +}; use async_trait::async_trait; use futures::{future::join, TryFutureExt as _}; use tower::{Service, ServiceExt as _}; @@ -12,7 +15,9 @@ use zaino_fetch::jsonrpsee::{ response::{GetBlockError, GetBlockResponse, GetTransactionResponse, GetTreestateResponse}, }; use zcash_primitives::merkle_tree::read_commitment_tree; -use zebra_chain::{block::TryIntoHeight, serialization::ZcashDeserialize}; +use zebra_chain::{ + block::TryIntoHeight, serialization::ZcashDeserialize, subtree::NoteCommitmentSubtreeIndex, +}; use zebra_state::{HashOrHeight, ReadRequest, ReadResponse, ReadStateService}; macro_rules! expected_read_response { @@ -85,6 +90,13 @@ pub trait BlockchainSource: Clone + Send + Sync + 'static { >, Box, >; + + async fn get_subtree_roots( + &self, + pool: ShieldedPool, + start_index: u16, + max_entries: Option, + ); } /// An error originating from a blockchain source. @@ -664,6 +676,42 @@ impl BlockchainSource for ValidatorConnector { ValidatorConnector::Fetch(_fetch) => Ok(None), } } + + async fn get_subtree_roots( + &self, + pool: ShieldedPool, + start_index: u16, + max_entries: Option, + ) -> Result<(), Box> { + match self { + ValidatorConnector::State(state) => { + let start_index = NoteCommitmentSubtreeIndex(start_index); + let limit = max_entries.map(NoteCommitmentSubtreeIndex); + let request = match pool { + ShieldedPool::Sapling => ReadRequest::SaplingSubtrees { start_index, limit }, + ShieldedPool::Orchard => ReadRequest::OrchardSubtrees { start_index, limit }, + }; + state + .read_state_service + .clone() + .call(request) + .await + .and_then(|response| match pool { + ShieldedPool::Sapling => { + expected_read_response!(response, SaplingSubtrees) + } + ShieldedPool::Orchard => { + expected_read_response!(response, OrchardSubtrees) + } + })? + } + + ValidatorConnector::Fetch(json_rp_see_connector) => Ok(json_rp_see_connector + .get_subtrees_by_index(pool.pool_string(), start_index, max_entries) + .await + .map_err(|e| e)?), + } + } } /// The location of a transaction returned by From c07da9f4121929e27684b8632170edb114108c54 Mon Sep 17 00:00:00 2001 From: Hazel OHearn Date: Wed, 18 Feb 2026 18:07:43 -0400 Subject: [PATCH 2/2] finish adding get subtree roots to chainindex --- zaino-state/src/chain_index.rs | 11 ++- zaino-state/src/chain_index/source.rs | 77 +++++++++++++++---- .../chain_index/tests/proptest_blockgen.rs | 9 +++ 3 files changed, 80 insertions(+), 17 deletions(-) diff --git a/zaino-state/src/chain_index.rs b/zaino-state/src/chain_index.rs index f7ba7f0b8..522abc2cd 100644 --- a/zaino-state/src/chain_index.rs +++ b/zaino-state/src/chain_index.rs @@ -274,7 +274,7 @@ pub trait ChainIndex { pool: ShieldedPool, start_index: u16, max_entries: Option, - ) -> impl std::future::Future>; + ) -> impl std::future::Future, Self::Error>>; /// given a transaction id, returns the transaction, along with /// its consensus branch ID if available @@ -1480,13 +1480,18 @@ impl ChainIndex for NodeBackedChainIndexSubscriber, - ) -> Result<(), Self::Error> { - self.source().get_subtree_roots() + ) -> Result, Self::Error> { + self.source() + .get_subtree_roots(pool, start_index, max_entries) + .await + .map_err(ChainIndexError::backing_validator) } } diff --git a/zaino-state/src/chain_index/source.rs b/zaino-state/src/chain_index/source.rs index 45bed3cbe..cdf2c7908 100644 --- a/zaino-state/src/chain_index/source.rs +++ b/zaino-state/src/chain_index/source.rs @@ -91,12 +91,14 @@ pub trait BlockchainSource: Clone + Send + Sync + 'static { Box, >; + /// Gets the subtree roots of a given pool and the end heights of each root, + /// starting at the provided index, up to an optional maximum number of roots. async fn get_subtree_roots( &self, pool: ShieldedPool, start_index: u16, max_entries: Option, - ); + ) -> BlockchainSourceResult>; } /// An error originating from a blockchain source. @@ -682,7 +684,7 @@ impl BlockchainSource for ValidatorConnector { pool: ShieldedPool, start_index: u16, max_entries: Option, - ) -> Result<(), Box> { + ) -> BlockchainSourceResult> { match self { ValidatorConnector::State(state) => { let start_index = NoteCommitmentSubtreeIndex(start_index); @@ -696,20 +698,58 @@ impl BlockchainSource for ValidatorConnector { .clone() .call(request) .await - .and_then(|response| match pool { - ShieldedPool::Sapling => { - expected_read_response!(response, SaplingSubtrees) - } - ShieldedPool::Orchard => { - expected_read_response!(response, OrchardSubtrees) - } - })? + .map(|response| match pool { + ShieldedPool::Sapling => expected_read_response!(response, SaplingSubtrees) + .iter() + .map(|(_index, subtree)| { + (subtree.root.to_bytes(), subtree.end_height.0) + }) + .collect(), + ShieldedPool::Orchard => expected_read_response!(response, OrchardSubtrees) + .iter() + .map(|(_index, subtree)| (subtree.root.to_repr(), subtree.end_height.0)) + .collect(), + }) + .map_err(|e| { + BlockchainSourceError::Unrecoverable(format!( + "could not get subtrees from validator: {e}" + )) + }) } - ValidatorConnector::Fetch(json_rp_see_connector) => Ok(json_rp_see_connector - .get_subtrees_by_index(pool.pool_string(), start_index, max_entries) - .await - .map_err(|e| e)?), + ValidatorConnector::Fetch(json_rp_see_connector) => { + let subtrees = json_rp_see_connector + .get_subtrees_by_index(pool.pool_string(), start_index, max_entries) + .await + .map_err(|e| { + BlockchainSourceError::Unrecoverable(format!( + "could not get subtrees from validator: {e}" + )) + })?; + + Ok(subtrees + .subtrees + .iter() + .map(|subtree| { + Ok::<_, Box>(( + <[u8; 32]>::try_from(hex::decode(&subtree.root)?).map_err( + |_subtree| { + std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "received subtree root not 32 bytes", + ) + }, + )?, + subtree.end_height.0, + )) + }) + .collect::, _>>() + .map_err(|e| { + BlockchainSourceError::Unrecoverable(format!( + "could not get subtrees from validator: {e}" + )) + })?) + } } } } @@ -1035,5 +1075,14 @@ pub(crate) mod test { > { Ok(None) } + + async fn get_subtree_roots( + &self, + _pool: ShieldedPool, + _start_index: u16, + _max_entries: Option, + ) -> BlockchainSourceResult> { + todo!() + } } } diff --git a/zaino-state/src/chain_index/tests/proptest_blockgen.rs b/zaino-state/src/chain_index/tests/proptest_blockgen.rs index 4abc970c4..b26156e40 100644 --- a/zaino-state/src/chain_index/tests/proptest_blockgen.rs +++ b/zaino-state/src/chain_index/tests/proptest_blockgen.rs @@ -664,6 +664,15 @@ impl BlockchainSource for ProptestMockchain { .unwrap(); Ok(Some(receiver)) } + + async fn get_subtree_roots( + &self, + _pool: crate::chain_index::ShieldedPool, + _start_index: u16, + _max_entries: Option, + ) -> BlockchainSourceResult> { + todo!() + } } type ChainSegment = SummaryDebug>>;