zcash_client_sqlite: Add shard & checkpoint insertion.

This commit is contained in:
Kris Nuttycombe 2023-06-02 07:53:26 -06:00
parent 9f2bb94a5e
commit ade882d01c
7 changed files with 98 additions and 23 deletions

View File

@ -549,7 +549,7 @@ pub(crate) fn fully_scanned_height(
|row| { |row| {
let max_height: u32 = row.get(0)?; let max_height: u32 = row.get(0)?;
let sapling_tree_size: Option<u64> = row.get(1)?; let sapling_tree_size: Option<u64> = row.get(1)?;
let sapling_tree: Vec<u8> = row.get(0)?; let sapling_tree: Vec<u8> = row.get(2)?;
Ok(( Ok((
BlockHeight::from(max_height), BlockHeight::from(max_height),
sapling_tree_size, sapling_tree_size,

View File

@ -875,7 +875,7 @@ mod tests {
// add a sapling sent note // add a sapling sent note
wdb.conn.execute( 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()) RecipientAddress::Transparent(*ufvk.default_address().0.transparent().unwrap())
.encode(&tests::network()); .encode(&tests::network());
wdb.conn.execute( 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( wdb.conn.execute(

View File

@ -327,7 +327,7 @@ mod tests {
.unwrap(); .unwrap();
db_data.conn.execute_batch( 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 transactions (block, id_tx, txid) VALUES (0, 0, '');
INSERT INTO sent_notes (tx, output_pool, output_index, from_account, address, value) INSERT INTO sent_notes (tx, output_pool, output_index, from_account, address, value)
@ -460,7 +460,7 @@ mod tests {
db_data db_data
.conn .conn
.execute_batch( .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(); .unwrap();
db_data.conn.execute( db_data.conn.execute(

View File

@ -262,7 +262,7 @@ mod tests {
// Tx 0 contains two received notes of 2 and 5 zatoshis that are controlled by account 0. // Tx 0 contains two received notes of 2 and 5 zatoshis that are controlled by account 0.
db_data.conn.execute_batch( 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 transactions (block, id_tx, txid) VALUES (0, 0, 'tx0');
INSERT INTO received_notes (tx, output_index, account, diversifier, value, rcm, nf, is_change) INSERT INTO received_notes (tx, output_index, account, diversifier, value, rcm, nf, is_change)

View File

@ -65,7 +65,7 @@ impl RusqliteMigration for Migration {
subtree_end_height INTEGER, subtree_end_height INTEGER,
root_hash BLOB, root_hash BLOB,
shard_data BLOB, shard_data BLOB,
contains_marked INTEGER NOT NULL, contains_marked INTEGER,
CONSTRAINT root_unique UNIQUE (root_hash) CONSTRAINT root_unique UNIQUE (root_hash)
); );
CREATE TABLE sapling_tree_cap ( CREATE TABLE sapling_tree_cap (
@ -101,19 +101,24 @@ impl RusqliteMigration for Migration {
let mut block_rows = stmt_blocks.query([])?; let mut block_rows = stmt_blocks.query([])?;
while let Some(row) = block_rows.next()? { while let Some(row) = block_rows.next()? {
let block_height: u32 = row.get(0)?; let block_height: u32 = row.get(0)?;
let row_data: Vec<u8> = row.get(1)?; let sapling_tree_data: Vec<u8> = row.get(1)?;
if sapling_tree_data == vec![0x00] {
continue;
}
let block_end_tree = read_commitment_tree::< let block_end_tree = read_commitment_tree::<
sapling::Node, sapling::Node,
_, _,
{ sapling::NOTE_COMMITMENT_TREE_DEPTH }, { sapling::NOTE_COMMITMENT_TREE_DEPTH },
>(&row_data[..]) >(&sapling_tree_data[..])
.map_err(|e| { .map_err(|e| {
rusqlite::Error::FromSqlConversionFailure( rusqlite::Error::FromSqlConversionFailure(
row_data.len(), sapling_tree_data.len(),
rusqlite::types::Type::Blob, rusqlite::types::Type::Blob,
Box::new(e), Box::new(e),
) )
})?; })?;
stmt_update_block_sapling_tree_size stmt_update_block_sapling_tree_size
.execute(params![block_end_tree.size(), block_height])?; .execute(params![block_end_tree.size(), block_height])?;

View File

@ -253,7 +253,7 @@ mod tests {
// - Tx 0 contains two received notes of 2 and 5 zatoshis that are controlled by account 0. // - Tx 0 contains two received notes of 2 and 5 zatoshis that are controlled by account 0.
db_data.conn.execute_batch( 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 transactions (block, id_tx, txid) VALUES (0, 0, 'tx0');
INSERT INTO received_notes (tx, output_index, account, diversifier, value, rcm, nf, is_change) 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` // of 2 zatoshis. This is representative of a historic transaction where no `sent_notes`
// entry was created for the change value. // entry was created for the change value.
db_data.conn.execute_batch( 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'); INSERT INTO transactions (block, id_tx, txid) VALUES (1, 1, 'tx1');
UPDATE received_notes SET spent = 1 WHERE tx = 0; 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) 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, // 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. // received, who knows where it came from but it's for account 0.
db_data.conn.execute_batch( 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'); INSERT INTO transactions (block, id_tx, txid) VALUES (2, 2, 'tx2');
UPDATE received_notes SET spent = 2 WHERE tx = 1; UPDATE received_notes SET spent = 2 WHERE tx = 1;
INSERT INTO utxos (received_by_account, address, prevout_txid, prevout_idx, script, value_zat, height) 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 // - Tx 3 just receives transparent funds and does nothing else. For this to work, the
// transaction must be retrieved by the wallet. // transaction must be retrieved by the wallet.
db_data.conn.execute_batch( 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 transactions (block, id_tx, txid) VALUES (3, 3, 'tx3');
INSERT INTO utxos (received_by_account, address, prevout_txid, prevout_idx, script, value_zat, height) INSERT INTO utxos (received_by_account, address, prevout_txid, prevout_idx, script, value_zat, height)

View File

@ -1,12 +1,14 @@
use either::Either; use either::Either;
use incrementalmerkletree::Address; use incrementalmerkletree::Address;
use rusqlite::{self, named_params, OptionalExtension}; use rusqlite::{self, named_params, OptionalExtension};
use shardtree::{Checkpoint, LocatedPrunableTree, PrunableTree, ShardStore}; use shardtree::{Checkpoint, LocatedPrunableTree, PrunableTree, ShardStore};
use std::io::{self, Cursor}; 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 struct WalletDbSaplingShardStore<'conn, 'a> {
pub(crate) conn: &'a rusqlite::Transaction<'conn>, pub(crate) conn: &'a rusqlite::Transaction<'conn>,
@ -37,8 +39,8 @@ impl<'conn, 'a: 'conn> ShardStore for WalletDbSaplingShardStore<'conn, 'a> {
todo!() todo!()
} }
fn put_shard(&mut self, _subtree: LocatedPrunableTree<Self::H>) -> Result<(), Self::Error> { fn put_shard(&mut self, subtree: LocatedPrunableTree<Self::H>) -> Result<(), Self::Error> {
todo!() put_shard(self.conn, subtree)
} }
fn get_shard_roots(&self) -> Result<Vec<Address>, Self::Error> { fn get_shard_roots(&self) -> Result<Vec<Address>, Self::Error> {
@ -68,14 +70,14 @@ impl<'conn, 'a: 'conn> ShardStore for WalletDbSaplingShardStore<'conn, 'a> {
fn add_checkpoint( fn add_checkpoint(
&mut self, &mut self,
_checkpoint_id: Self::CheckpointId, checkpoint_id: Self::CheckpointId,
_checkpoint: Checkpoint, checkpoint: Checkpoint,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
todo!() add_checkpoint(self.conn, checkpoint_id, checkpoint)
} }
fn checkpoint_count(&self) -> Result<usize, Self::Error> { fn checkpoint_count(&self) -> Result<usize, Self::Error> {
todo!() checkpoint_count(self.conn)
} }
fn get_checkpoint_at_depth( fn get_checkpoint_at_depth(
@ -125,10 +127,12 @@ impl<'conn, 'a: 'conn> ShardStore for WalletDbSaplingShardStore<'conn, 'a> {
} }
} }
type Error = Either<io::Error, rusqlite::Error>;
pub(crate) fn get_shard( pub(crate) fn get_shard(
conn: &rusqlite::Connection, conn: &rusqlite::Connection,
shard_root: Address, shard_root: Address,
) -> Result<Option<LocatedPrunableTree<sapling::Node>>, Either<io::Error, rusqlite::Error>> { ) -> Result<Option<LocatedPrunableTree<sapling::Node>>, Error> {
conn.query_row( conn.query_row(
"SELECT shard_data "SELECT shard_data
FROM sapling_tree_shards FROM sapling_tree_shards
@ -144,3 +148,69 @@ pub(crate) fn get_shard(
}) })
.transpose() .transpose()
} }
pub(crate) fn put_shard(
conn: &rusqlite::Connection,
subtree: LocatedPrunableTree<sapling::Node>,
) -> 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<usize, Error> {
conn.query_row("SELECT COUNT(*) FROM sapling_tree_checkpoints", [], |row| {
row.get::<_, usize>(0)
})
.map_err(Either::Right)
}