From ade882d01cb74062d7cb70671e20c67575d04e02 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 2 Jun 2023 07:53:26 -0600 Subject: [PATCH] zcash_client_sqlite: Add shard & checkpoint insertion. --- zcash_client_sqlite/src/wallet.rs | 2 +- zcash_client_sqlite/src/wallet/init.rs | 4 +- .../init/migrations/add_transaction_views.rs | 4 +- .../migrations/received_notes_nullable_nf.rs | 2 +- .../init/migrations/shardtree_support.rs | 13 ++- .../init/migrations/v_transactions_net.rs | 8 +- .../src/wallet/sapling/commitment_tree.rs | 88 +++++++++++++++++-- 7 files changed, 98 insertions(+), 23 deletions(-) diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 406db08c7..8f071d55d 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -549,7 +549,7 @@ pub(crate) fn fully_scanned_height( |row| { let max_height: u32 = row.get(0)?; let sapling_tree_size: Option = row.get(1)?; - let sapling_tree: Vec = row.get(0)?; + let sapling_tree: Vec = row.get(2)?; Ok(( BlockHeight::from(max_height), sapling_tree_size, diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index e3485c8ab..6fba9396c 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -875,7 +875,7 @@ mod tests { // add a sapling sent note wdb.conn.execute( - "INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (0, 0, 0, '')", + "INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (0, 0, 0, x'00')", [], )?; @@ -1039,7 +1039,7 @@ mod tests { RecipientAddress::Transparent(*ufvk.default_address().0.transparent().unwrap()) .encode(&tests::network()); wdb.conn.execute( - "INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (0, 0, 0, '')", + "INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (0, 0, 0, x'00')", [], )?; wdb.conn.execute( diff --git a/zcash_client_sqlite/src/wallet/init/migrations/add_transaction_views.rs b/zcash_client_sqlite/src/wallet/init/migrations/add_transaction_views.rs index 70694f842..06efb1327 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/add_transaction_views.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/add_transaction_views.rs @@ -327,7 +327,7 @@ mod tests { .unwrap(); db_data.conn.execute_batch( - "INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (0, 0, 0, ''); + "INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (0, 0, 0, x'00'); INSERT INTO transactions (block, id_tx, txid) VALUES (0, 0, ''); INSERT INTO sent_notes (tx, output_pool, output_index, from_account, address, value) @@ -460,7 +460,7 @@ mod tests { db_data .conn .execute_batch( - "INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (0, 0, 0, '');", + "INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (0, 0, 0, x'00');", ) .unwrap(); db_data.conn.execute( diff --git a/zcash_client_sqlite/src/wallet/init/migrations/received_notes_nullable_nf.rs b/zcash_client_sqlite/src/wallet/init/migrations/received_notes_nullable_nf.rs index 811a1a0e5..5567d60dc 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/received_notes_nullable_nf.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/received_notes_nullable_nf.rs @@ -262,7 +262,7 @@ mod tests { // Tx 0 contains two received notes of 2 and 5 zatoshis that are controlled by account 0. db_data.conn.execute_batch( - "INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (0, 0, 0, ''); + "INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (0, 0, 0, x'00'); INSERT INTO transactions (block, id_tx, txid) VALUES (0, 0, 'tx0'); INSERT INTO received_notes (tx, output_index, account, diversifier, value, rcm, nf, is_change) diff --git a/zcash_client_sqlite/src/wallet/init/migrations/shardtree_support.rs b/zcash_client_sqlite/src/wallet/init/migrations/shardtree_support.rs index f46d63ec3..e16c36c8c 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/shardtree_support.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/shardtree_support.rs @@ -65,7 +65,7 @@ impl RusqliteMigration for Migration { subtree_end_height INTEGER, root_hash BLOB, shard_data BLOB, - contains_marked INTEGER NOT NULL, + contains_marked INTEGER, CONSTRAINT root_unique UNIQUE (root_hash) ); CREATE TABLE sapling_tree_cap ( @@ -101,19 +101,24 @@ impl RusqliteMigration for Migration { let mut block_rows = stmt_blocks.query([])?; while let Some(row) = block_rows.next()? { let block_height: u32 = row.get(0)?; - let row_data: Vec = row.get(1)?; + let sapling_tree_data: Vec = row.get(1)?; + if sapling_tree_data == vec![0x00] { + continue; + } + let block_end_tree = read_commitment_tree::< sapling::Node, _, { sapling::NOTE_COMMITMENT_TREE_DEPTH }, - >(&row_data[..]) + >(&sapling_tree_data[..]) .map_err(|e| { rusqlite::Error::FromSqlConversionFailure( - row_data.len(), + sapling_tree_data.len(), rusqlite::types::Type::Blob, Box::new(e), ) })?; + stmt_update_block_sapling_tree_size .execute(params![block_end_tree.size(), block_height])?; diff --git a/zcash_client_sqlite/src/wallet/init/migrations/v_transactions_net.rs b/zcash_client_sqlite/src/wallet/init/migrations/v_transactions_net.rs index 10c3a26a9..fc3ab7378 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/v_transactions_net.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/v_transactions_net.rs @@ -253,7 +253,7 @@ mod tests { // - Tx 0 contains two received notes of 2 and 5 zatoshis that are controlled by account 0. db_data.conn.execute_batch( - "INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (0, 0, 0, ''); + "INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (0, 0, 0, x'00'); INSERT INTO transactions (block, id_tx, txid) VALUES (0, 0, 'tx0'); INSERT INTO received_notes (tx, output_index, account, diversifier, value, rcm, nf, is_change) @@ -265,7 +265,7 @@ mod tests { // of 2 zatoshis. This is representative of a historic transaction where no `sent_notes` // entry was created for the change value. db_data.conn.execute_batch( - "INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (1, 1, 1, ''); + "INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (1, 1, 1, x'00'); INSERT INTO transactions (block, id_tx, txid) VALUES (1, 1, 'tx1'); UPDATE received_notes SET spent = 1 WHERE tx = 0; INSERT INTO sent_notes (tx, output_pool, output_index, from_account, to_account, to_address, value) @@ -279,7 +279,7 @@ mod tests { // other half to the sending account as change. Also there's a random transparent utxo, // received, who knows where it came from but it's for account 0. db_data.conn.execute_batch( - "INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (2, 2, 2, ''); + "INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (2, 2, 2, x'00'); INSERT INTO transactions (block, id_tx, txid) VALUES (2, 2, 'tx2'); UPDATE received_notes SET spent = 2 WHERE tx = 1; INSERT INTO utxos (received_by_account, address, prevout_txid, prevout_idx, script, value_zat, height) @@ -297,7 +297,7 @@ mod tests { // - Tx 3 just receives transparent funds and does nothing else. For this to work, the // transaction must be retrieved by the wallet. db_data.conn.execute_batch( - "INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (3, 3, 3, ''); + "INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (3, 3, 3, x'00'); INSERT INTO transactions (block, id_tx, txid) VALUES (3, 3, 'tx3'); INSERT INTO utxos (received_by_account, address, prevout_txid, prevout_idx, script, value_zat, height) diff --git a/zcash_client_sqlite/src/wallet/sapling/commitment_tree.rs b/zcash_client_sqlite/src/wallet/sapling/commitment_tree.rs index ee7438ad6..80c800fe6 100644 --- a/zcash_client_sqlite/src/wallet/sapling/commitment_tree.rs +++ b/zcash_client_sqlite/src/wallet/sapling/commitment_tree.rs @@ -1,12 +1,14 @@ use either::Either; + use incrementalmerkletree::Address; use rusqlite::{self, named_params, OptionalExtension}; use shardtree::{Checkpoint, LocatedPrunableTree, PrunableTree, ShardStore}; + use std::io::{self, Cursor}; -use zcash_primitives::{consensus::BlockHeight, sapling}; +use zcash_primitives::{consensus::BlockHeight, merkle_tree::HashSer, sapling}; -use crate::serialization::read_shard; +use crate::serialization::{read_shard, write_shard_v1}; pub struct WalletDbSaplingShardStore<'conn, 'a> { pub(crate) conn: &'a rusqlite::Transaction<'conn>, @@ -37,8 +39,8 @@ impl<'conn, 'a: 'conn> ShardStore for WalletDbSaplingShardStore<'conn, 'a> { todo!() } - fn put_shard(&mut self, _subtree: LocatedPrunableTree) -> Result<(), Self::Error> { - todo!() + fn put_shard(&mut self, subtree: LocatedPrunableTree) -> Result<(), Self::Error> { + put_shard(self.conn, subtree) } fn get_shard_roots(&self) -> Result, Self::Error> { @@ -68,14 +70,14 @@ impl<'conn, 'a: 'conn> ShardStore for WalletDbSaplingShardStore<'conn, 'a> { fn add_checkpoint( &mut self, - _checkpoint_id: Self::CheckpointId, - _checkpoint: Checkpoint, + checkpoint_id: Self::CheckpointId, + checkpoint: Checkpoint, ) -> Result<(), Self::Error> { - todo!() + add_checkpoint(self.conn, checkpoint_id, checkpoint) } fn checkpoint_count(&self) -> Result { - todo!() + checkpoint_count(self.conn) } fn get_checkpoint_at_depth( @@ -125,10 +127,12 @@ impl<'conn, 'a: 'conn> ShardStore for WalletDbSaplingShardStore<'conn, 'a> { } } +type Error = Either; + pub(crate) fn get_shard( conn: &rusqlite::Connection, shard_root: Address, -) -> Result>, Either> { +) -> Result>, Error> { conn.query_row( "SELECT shard_data FROM sapling_tree_shards @@ -144,3 +148,69 @@ pub(crate) fn get_shard( }) .transpose() } + +pub(crate) fn put_shard( + conn: &rusqlite::Connection, + subtree: LocatedPrunableTree, +) -> Result<(), Error> { + let subtree_root_hash = subtree + .root() + .annotation() + .and_then(|ann| { + ann.as_ref().map(|rc| { + let mut root_hash = vec![]; + rc.write(&mut root_hash)?; + Ok(root_hash) + }) + }) + .transpose() + .map_err(Either::Left)?; + + let mut subtree_data = vec![]; + write_shard_v1(&mut subtree_data, subtree.root()).map_err(Either::Left)?; + + conn.prepare_cached( + "INSERT INTO sapling_tree_shards (shard_index, root_hash, shard_data) + VALUES (:shard_index, :root_hash, :shard_data) + ON CONFLICT (shard_index) DO UPDATE + SET root_hash = :root_hash, + shard_data = :shard_data", + ) + .and_then(|mut stmt_put_shard| { + stmt_put_shard.execute(named_params![ + ":shard_index": subtree.root_addr().index(), + ":root_hash": subtree_root_hash, + ":shard_data": subtree_data + ]) + }) + .map_err(Either::Right)?; + + Ok(()) +} + +pub(crate) fn add_checkpoint( + conn: &rusqlite::Transaction<'_>, + checkpoint_id: BlockHeight, + checkpoint: Checkpoint, +) -> Result<(), Error> { + conn.prepare_cached( + "INSERT INTO sapling_tree_checkpoints (checkpoint_id, position) + VALUES (:checkpoint_id, :position)", + ) + .and_then(|mut stmt_insert_checkpoint| { + stmt_insert_checkpoint.execute(named_params![ + ":checkpoint_id": u32::from(checkpoint_id), + ":position": checkpoint.position().map(u64::from) + ]) + }) + .map_err(Either::Right)?; + + Ok(()) +} + +pub(crate) fn checkpoint_count(conn: &rusqlite::Connection) -> Result { + conn.query_row("SELECT COUNT(*) FROM sapling_tree_checkpoints", [], |row| { + row.get::<_, usize>(0) + }) + .map_err(Either::Right) +}