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| {
let max_height: u32 = row.get(0)?;
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((
BlockHeight::from(max_height),
sapling_tree_size,

View File

@ -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(

View File

@ -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(

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.
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)

View File

@ -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<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::<
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])?;

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.
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)

View File

@ -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<Self::H>) -> Result<(), Self::Error> {
todo!()
fn put_shard(&mut self, subtree: LocatedPrunableTree<Self::H>) -> Result<(), Self::Error> {
put_shard(self.conn, subtree)
}
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(
&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<usize, Self::Error> {
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<io::Error, rusqlite::Error>;
pub(crate) fn get_shard(
conn: &rusqlite::Connection,
shard_root: Address,
) -> Result<Option<LocatedPrunableTree<sapling::Node>>, Either<io::Error, rusqlite::Error>> {
) -> Result<Option<LocatedPrunableTree<sapling::Node>>, 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<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)
}