Expose commitment-tree primitives instead of monolithic witness generation#23
Merged
p0mvn merged 1 commit intoshielded-voting-wallet-supportfrom Apr 10, 2026
Conversation
p0mvn
commented
Apr 10, 2026
Comment on lines
1148
to
+1149
| #[cfg(feature = "orchard")] | ||
| pub fn generate_orchard_witnesses_at_frontier( | ||
| conn: &rusqlite::Connection, | ||
| note_positions: &[Position], | ||
| frontier: incrementalmerkletree::frontier::NonEmptyFrontier<orchard::tree::MerkleHashOrchard>, | ||
| checkpoint_height: BlockHeight, | ||
| ) -> Result< | ||
| Vec< | ||
| incrementalmerkletree::MerklePath< | ||
| orchard::tree::MerkleHashOrchard, | ||
| { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }, | ||
| >, | ||
| >, | ||
| SqliteClientError, | ||
| > { | ||
| use incrementalmerkletree::Marking; | ||
| use shardtree::ShardTree; | ||
| use shardtree::store::Checkpoint; | ||
| use zcash_client_backend::data_api::ORCHARD_SHARD_HEIGHT; | ||
|
|
||
| let frontier_position = frontier.position(); | ||
|
|
||
| // Create in-memory DB with the tree schema | ||
| let mem_conn = rusqlite::Connection::open_in_memory().map_err(SqliteClientError::DbError)?; | ||
|
|
||
| mem_conn | ||
| .execute_batch( | ||
| "CREATE TABLE orchard_tree_shards ( | ||
| shard_index INTEGER PRIMARY KEY, | ||
| subtree_end_height INTEGER, | ||
| root_hash BLOB, | ||
| shard_data BLOB, | ||
| contains_marked INTEGER, | ||
| CONSTRAINT root_unique UNIQUE (root_hash) | ||
| ); | ||
| CREATE TABLE orchard_tree_cap ( | ||
| cap_id INTEGER PRIMARY KEY, | ||
| cap_data BLOB NOT NULL | ||
| ); | ||
| CREATE TABLE orchard_tree_checkpoints ( | ||
| checkpoint_id INTEGER PRIMARY KEY, | ||
| position INTEGER | ||
| ); | ||
| CREATE TABLE orchard_tree_checkpoint_marks_removed ( | ||
| checkpoint_id INTEGER NOT NULL, | ||
| mark_removed_position INTEGER NOT NULL, | ||
| FOREIGN KEY (checkpoint_id) REFERENCES orchard_tree_checkpoints(checkpoint_id) | ||
| ON DELETE CASCADE, | ||
| CONSTRAINT spend_position_unique UNIQUE (checkpoint_id, mark_removed_position) | ||
| );", | ||
| ) | ||
| .map_err(SqliteClientError::DbError)?; | ||
|
|
||
| // Copy shard data from wallet into in-memory DB | ||
| { | ||
| use rusqlite::types::Value; | ||
|
|
||
| let mut stmt = conn | ||
| .prepare( | ||
| "SELECT shard_index, subtree_end_height, root_hash, shard_data, contains_marked | ||
| FROM orchard_tree_shards", | ||
| ) | ||
| .map_err(SqliteClientError::DbError)?; | ||
| let mut rows = stmt.query([]).map_err(SqliteClientError::DbError)?; | ||
| while let Some(row) = rows.next().map_err(SqliteClientError::DbError)? { | ||
| mem_conn | ||
| .execute( | ||
| "INSERT INTO orchard_tree_shards | ||
| (shard_index, subtree_end_height, root_hash, shard_data, contains_marked) | ||
| VALUES (?1, ?2, ?3, ?4, ?5)", | ||
| rusqlite::params![ | ||
| row.get::<_, Value>(0)?, | ||
| row.get::<_, Value>(1)?, | ||
| row.get::<_, Value>(2)?, | ||
| row.get::<_, Value>(3)?, | ||
| row.get::<_, Value>(4)?, | ||
| ], | ||
| ) | ||
| .map_err(SqliteClientError::DbError)?; | ||
| } | ||
| } | ||
|
|
||
| // Copy cap data | ||
| { | ||
| use rusqlite::types::Value; | ||
|
|
||
| let mut stmt = conn | ||
| .prepare("SELECT cap_id, cap_data FROM orchard_tree_cap") | ||
| .map_err(SqliteClientError::DbError)?; | ||
| let mut rows = stmt.query([]).map_err(SqliteClientError::DbError)?; | ||
| while let Some(row) = rows.next().map_err(SqliteClientError::DbError)? { | ||
| mem_conn | ||
| .execute( | ||
| "INSERT INTO orchard_tree_cap (cap_id, cap_data) VALUES (?1, ?2)", | ||
| rusqlite::params![row.get::<_, Value>(0)?, row.get::<_, Value>(1)?,], | ||
| ) | ||
| .map_err(SqliteClientError::DbError)?; | ||
| } | ||
| } | ||
|
|
||
| // Build ShardTree from in-memory store | ||
| let tx = mem_conn | ||
| .unchecked_transaction() | ||
| .map_err(SqliteClientError::DbError)?; | ||
|
|
||
| let store = SqliteShardStore::< | ||
| _, | ||
| orchard::tree::MerkleHashOrchard, | ||
| ORCHARD_SHARD_HEIGHT, | ||
| >::from_connection(&tx, "orchard") | ||
| .map_err(SqliteClientError::DbError)?; | ||
|
|
||
| let mut tree = | ||
| ShardTree::<_, { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }, ORCHARD_SHARD_HEIGHT>::new( | ||
| store, 100, | ||
| ); | ||
|
|
||
| // Insert frontier + checkpoint | ||
| tree.insert_frontier_nodes( | ||
| frontier, | ||
| Retention::Checkpoint { | ||
| id: checkpoint_height, | ||
| marking: Marking::None, | ||
| }, | ||
| ) | ||
| .map_err(|e| { | ||
| SqliteClientError::CorruptedData(format!("failed to insert frontier nodes: {}", e)) | ||
| })?; | ||
|
|
||
| tree.store_mut() | ||
| .add_checkpoint( | ||
| checkpoint_height, | ||
| Checkpoint::at_position(frontier_position), | ||
| ) | ||
| .map_err(|e| { | ||
| SqliteClientError::CorruptedData(format!("failed to add checkpoint: {}", e)) | ||
| })?; | ||
|
|
||
| // Generate witness per note position | ||
| let mut witnesses = Vec::with_capacity(note_positions.len()); | ||
| for &pos in note_positions { | ||
| let merkle_path = tree | ||
| .witness_at_checkpoint_id(pos, &checkpoint_height) | ||
| .map_err(|e| { | ||
| SqliteClientError::CorruptedData(format!( | ||
| "failed to generate witness for position {}: {} \ | ||
| (wallet may need to sync through snapshot height)", | ||
| u64::from(pos), | ||
| e | ||
| )) | ||
| })? | ||
| .ok_or_else(|| { | ||
| SqliteClientError::CorruptedData(format!( | ||
| "no witness available for position {} \ | ||
| (wallet missing shard data — sync through snapshot height)", | ||
| u64::from(pos) | ||
| )) | ||
| })?; | ||
|
|
||
| witnesses.push(merkle_path); | ||
| } | ||
|
|
||
| Ok(witnesses) | ||
| pub fn create_orchard_tree_tables(conn: &rusqlite::Connection) -> Result<(), rusqlite::Error> { |
p0mvn
commented
Apr 10, 2026
Comment on lines
+404
to
+407
| /// Returns a reference to the underlying connection handle. | ||
| pub fn conn(&self) -> &C { | ||
| &self.conn | ||
| } |
p0mvn
commented
Apr 10, 2026
| const SHARD_ROOT_LEVEL: Level = Level::new(SHARD_HEIGHT); | ||
|
|
||
| pub(crate) fn from_connection( | ||
| pub fn from_connection( |
…ation The previous approach bundled witness generation inside zcash_client_sqlite, but the voting crate needs full control over the ephemeral tree lifecycle (shard copying, frontier insertion, checkpoint management) to handle error recovery and progress reporting at the FFI boundary. Replace the monolithic `generate_orchard_witnesses_at_frontier` method with three focused primitives that let the caller compose its own witness pipeline: - `WalletDb::conn()` — access to the underlying connection for shard data queries - `create_orchard_tree_tables(conn)` — stand up the Orchard tree schema in any connection (e.g. in-memory), reusing the canonical DDL - `SqliteShardStore::from_connection` made `pub` — construct a shard store over any connection that has the right schema Made-with: Cursor
781f663 to
4b690e6
Compare
czarcas7ic
approved these changes
Apr 10, 2026
p0mvn
added a commit
that referenced
this pull request
Apr 11, 2026
…ss generation" This reverts merge commit 6858db4 (PR #23). The approach of exposing individual commitment-tree primitives (conn(), create_orchard_tree_tables, pub from_connection) is being reconsidered. Revert to restore the previous API surface on the shielded-voting-wallet-support branch. Made-with: Cursor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
WalletDb::generate_orchard_witnesses_at_frontierwith three focused building blocks:WalletDb::conn(),create_orchard_tree_tables(), and a publicSqliteShardStore::from_connectionlibrustvoting) needs full control over the ephemeral tree lifecycle — shard copying, frontier insertion, checkpoint management — to handle error recovery and progress reporting at the FFI boundary