Move related functions into the same modules.

This commit is contained in:
Kris Nuttycombe 2020-08-25 15:20:12 -06:00
parent eab2951c99
commit a181203179
6 changed files with 334 additions and 376 deletions

View File

@ -18,10 +18,9 @@
//! }
//! };
//!
//! use zcash_client_sqlite::{
//! DataConnection,
//! use crate::{
//! CacheConnection,
//! chain::{rewind_to_height},
//! wallet::{rewind_to_height},
//! };
//!
//! let network = Network::TestNetwork;
@ -73,20 +72,17 @@
//! scan_cached_blocks(&network, &db_cache, &db_data, None);
//! ```
use protobuf::parse_from_bytes;
use rusqlite::{OptionalExtension, NO_PARAMS};
use zcash_primitives::{
block::BlockHash,
consensus::{self, BlockHeight, NetworkUpgrade},
transaction::TxId,
};
use rusqlite::types::ToSql;
use zcash_primitives::{block::BlockHash, consensus::BlockHeight};
use zcash_client_backend::{
data_api::error::{ChainInvalid, Error},
proto::compact_formats::CompactBlock,
};
use crate::{error::SqliteClientError, CacheConnection, DataConnection};
use crate::{error::SqliteClientError, CacheConnection};
struct CompactBlockRow {
height: BlockHeight,
@ -148,105 +144,37 @@ where
Ok(Some(current_block.hash()))
}
pub fn block_height_extrema(
conn: &DataConnection,
) -> Result<Option<(BlockHeight, BlockHeight)>, rusqlite::Error> {
conn.0
.query_row(
"SELECT MIN(height), MAX(height) FROM blocks",
NO_PARAMS,
|row| {
let min_height: u32 = row.get(0)?;
let max_height: u32 = row.get(1)?;
Ok(Some((min_height.into(), max_height.into())))
},
)
//.optional() doesn't work here because a failed aggregate function
//produces a runtime error, not an empty set of rows.
.or(Ok(None))
}
pub fn with_cached_blocks<F>(
cache: &CacheConnection,
from_height: BlockHeight,
limit: Option<u32>,
mut with_row: F,
) -> Result<(), SqliteClientError>
where
F: FnMut(BlockHeight, CompactBlock) -> Result<(), SqliteClientError>,
{
// Fetch the CompactBlocks we need to scan
let mut stmt_blocks = cache.0.prepare(
"SELECT height, data FROM compactblocks WHERE height > ? ORDER BY height ASC LIMIT ?",
)?;
let rows = stmt_blocks.query_map(
&[
u32::from(from_height).to_sql()?,
limit.unwrap_or(u32::max_value()).to_sql()?,
],
|row| {
Ok(CompactBlockRow {
height: BlockHeight::from_u32(row.get(0)?),
data: row.get(1)?,
})
},
)?;
pub fn get_tx_height(
conn: &DataConnection,
txid: TxId,
) -> Result<Option<BlockHeight>, rusqlite::Error> {
conn.0
.query_row(
"SELECT block FROM transactions WHERE txid = ?",
&[txid.0.to_vec()],
|row| row.get(0).map(u32::into),
)
.optional()
}
pub fn get_block_hash(
conn: &DataConnection,
block_height: BlockHeight,
) -> Result<Option<BlockHash>, rusqlite::Error> {
conn.0
.query_row(
"SELECT hash FROM blocks WHERE height = ?",
&[u32::from(block_height)],
|row| {
let row_data = row.get::<_, Vec<_>>(0)?;
Ok(BlockHash::from_slice(&row_data))
},
)
.optional()
}
/// Rewinds the database to the given height.
///
/// If the requested height is greater than or equal to the height of the last scanned
/// block, this function does nothing.
pub fn rewind_to_height<P: consensus::Parameters>(
conn: &DataConnection,
parameters: &P,
block_height: BlockHeight,
) -> Result<(), SqliteClientError> {
let sapling_activation_height = parameters
.activation_height(NetworkUpgrade::Sapling)
.ok_or(SqliteClientError(Error::SaplingNotActive))?;
// Recall where we synced up to previously.
// If we have never synced, use Sapling activation height.
let last_scanned_height =
conn.0
.query_row("SELECT MAX(height) FROM blocks", NO_PARAMS, |row| {
row.get(0)
.map(u32::into)
.or(Ok(sapling_activation_height - 1))
})?;
if block_height >= last_scanned_height {
// Nothing to do.
return Ok(());
for row_result in rows {
let row = row_result?;
with_row(row.height, parse_from_bytes(&row.data)?)?;
}
// Start an SQL transaction for rewinding.
conn.0.execute("BEGIN IMMEDIATE", NO_PARAMS)?;
// Decrement witnesses.
conn.0.execute(
"DELETE FROM sapling_witnesses WHERE block > ?",
&[u32::from(block_height)],
)?;
// Un-mine transactions.
conn.0.execute(
"UPDATE transactions SET block = NULL, tx_index = NULL WHERE block > ?",
&[u32::from(block_height)],
)?;
// Now that they aren't depended on, delete scanned blocks.
conn.0.execute(
"DELETE FROM blocks WHERE height > ?",
&[u32::from(block_height)],
)?;
// Commit the SQL transaction, rewinding atomically.
conn.0.execute("COMMIT", NO_PARAMS)?;
Ok(())
}
@ -263,18 +191,19 @@ mod tests {
use zcash_client_backend::data_api::{
chain::{scan_cached_blocks, validate_combined_chain},
error::Error,
error::{ChainInvalid, Error},
};
use crate::{
init::{init_accounts_table, init_cache_database, init_data_database},
query::get_balance,
tests::{self, fake_compact_block, insert_into_cache, sapling_activation_height},
AccountId, CacheConnection, DataConnection,
tests::{
self, fake_compact_block, fake_compact_block_spending, insert_into_cache,
sapling_activation_height,
},
wallet::{get_balance, rewind_to_height},
AccountId, CacheConnection, DataConnection, NoteId,
};
use super::rewind_to_height;
#[test]
fn valid_chain_states() {
let cache_file = NamedTempFile::new().unwrap();
@ -511,4 +440,172 @@ mod tests {
// Account balance should again reflect both received notes
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), value + value2);
}
#[test]
fn scan_cached_blocks_requires_sequential_blocks() {
let cache_file = NamedTempFile::new().unwrap();
let db_cache = CacheConnection(Connection::open(cache_file.path()).unwrap());
init_cache_database(&db_cache).unwrap();
let data_file = NamedTempFile::new().unwrap();
let db_data = DataConnection(Connection::open(data_file.path()).unwrap());
init_data_database(&db_data).unwrap();
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &tests::network(), &[extfvk.clone()]).unwrap();
// Create a block with height SAPLING_ACTIVATION_HEIGHT
let value = Amount::from_u64(50000).unwrap();
let (cb1, _) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
extfvk.clone(),
value,
);
insert_into_cache(&db_cache, &cb1);
scan_cached_blocks(&tests::network(), &db_cache, &db_data, None).unwrap();
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), value);
// We cannot scan a block of height SAPLING_ACTIVATION_HEIGHT + 2 next
let (cb2, _) = fake_compact_block(
sapling_activation_height() + 1,
cb1.hash(),
extfvk.clone(),
value,
);
let (cb3, _) = fake_compact_block(
sapling_activation_height() + 2,
cb2.hash(),
extfvk.clone(),
value,
);
insert_into_cache(&db_cache, &cb3);
match scan_cached_blocks(&tests::network(), &db_cache, &db_data, None) {
Ok(_) => panic!("Should have failed"),
Err(e) => {
assert_eq!(
e.to_string(),
ChainInvalid::block_height_mismatch::<rusqlite::Error, NoteId>(
sapling_activation_height() + 1,
sapling_activation_height() + 2
)
.to_string()
);
}
}
// If we add a block of height SAPLING_ACTIVATION_HEIGHT + 1, we can now scan both
insert_into_cache(&db_cache, &cb2);
scan_cached_blocks(&tests::network(), &db_cache, &db_data, None).unwrap();
assert_eq!(
get_balance(&db_data, AccountId(0)).unwrap(),
Amount::from_u64(150_000).unwrap()
);
}
#[test]
fn scan_cached_blocks_finds_received_notes() {
let cache_file = NamedTempFile::new().unwrap();
let db_cache = CacheConnection(Connection::open(cache_file.path()).unwrap());
init_cache_database(&db_cache).unwrap();
let data_file = NamedTempFile::new().unwrap();
let db_data = DataConnection(Connection::open(data_file.path()).unwrap());
init_data_database(&db_data).unwrap();
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &tests::network(), &[extfvk.clone()]).unwrap();
// Account balance should be zero
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), Amount::zero());
// Create a fake CompactBlock sending value to the address
let value = Amount::from_u64(5).unwrap();
let (cb, _) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
extfvk.clone(),
value,
);
insert_into_cache(&db_cache, &cb);
// Scan the cache
scan_cached_blocks(&tests::network(), &db_cache, &db_data, None).unwrap();
// Account balance should reflect the received note
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), value);
// Create a second fake CompactBlock sending more value to the address
let value2 = Amount::from_u64(7).unwrap();
let (cb2, _) =
fake_compact_block(sapling_activation_height() + 1, cb.hash(), extfvk, value2);
insert_into_cache(&db_cache, &cb2);
// Scan the cache again
scan_cached_blocks(&tests::network(), &db_cache, &db_data, None).unwrap();
// Account balance should reflect both received notes
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), value + value2);
}
#[test]
fn scan_cached_blocks_finds_change_notes() {
let cache_file = NamedTempFile::new().unwrap();
let db_cache = CacheConnection(Connection::open(cache_file.path()).unwrap());
init_cache_database(&db_cache).unwrap();
let data_file = NamedTempFile::new().unwrap();
let db_data = DataConnection(Connection::open(data_file.path()).unwrap());
init_data_database(&db_data).unwrap();
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &tests::network(), &[extfvk.clone()]).unwrap();
// Account balance should be zero
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), Amount::zero());
// Create a fake CompactBlock sending value to the address
let value = Amount::from_u64(5).unwrap();
let (cb, nf) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
extfvk.clone(),
value,
);
insert_into_cache(&db_cache, &cb);
// Scan the cache
scan_cached_blocks(&tests::network(), &db_cache, &db_data, None).unwrap();
// Account balance should reflect the received note
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), value);
// Create a second fake CompactBlock spending value from the address
let extsk2 = ExtendedSpendingKey::master(&[0]);
let to2 = extsk2.default_address().unwrap().1;
let value2 = Amount::from_u64(2).unwrap();
insert_into_cache(
&db_cache,
&fake_compact_block_spending(
sapling_activation_height() + 1,
cb.hash(),
(nf, value),
extfvk,
to2,
value2,
),
);
// Scan the cache again
scan_cached_blocks(&tests::network(), &db_cache, &db_data, None).unwrap();
// Account balance should equal the change
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), value - value2);
}
}

View File

@ -165,7 +165,7 @@ pub fn init_data_database(db_data: &DataConnection) -> Result<(), rusqlite::Erro
/// init_accounts_table(&db_data, &Network::TestNetwork, &extfvks).unwrap();
/// ```
///
/// [`get_address`]: crate::query::get_address
/// [`get_address`]: crate::wallet::get_address
/// [`scan_cached_blocks`]: crate::scan::scan_cached_blocks
/// [`create_to_address`]: crate::transact::create_to_address
pub fn init_accounts_table<P: consensus::Parameters>(
@ -272,7 +272,7 @@ mod tests {
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
};
use crate::{query::get_address, tests, AccountId, DataConnection};
use crate::{tests, wallet::get_address, AccountId, DataConnection};
use super::{init_accounts_table, init_blocks_table, init_data_database};

View File

@ -54,9 +54,8 @@ use crate::error::SqliteClientError;
pub mod chain;
pub mod error;
pub mod init;
pub mod query;
pub mod scan;
pub mod transact;
pub mod wallet;
#[derive(Debug, Copy, Clone)]
pub struct NoteId(pub i64);
@ -103,15 +102,15 @@ impl<'a> DBOps for &'a DataConnection {
}
fn block_height_extrema(&self) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error> {
chain::block_height_extrema(self).map_err(SqliteClientError::from)
wallet::block_height_extrema(self).map_err(SqliteClientError::from)
}
fn get_block_hash(&self, block_height: BlockHeight) -> Result<Option<BlockHash>, Self::Error> {
chain::get_block_hash(self, block_height).map_err(SqliteClientError::from)
wallet::get_block_hash(self, block_height).map_err(SqliteClientError::from)
}
fn get_tx_height(&self, txid: TxId) -> Result<Option<BlockHeight>, Self::Error> {
chain::get_tx_height(self, txid).map_err(SqliteClientError::from)
wallet::get_tx_height(self, txid).map_err(SqliteClientError::from)
}
fn rewind_to_height<P: consensus::Parameters>(
@ -119,7 +118,7 @@ impl<'a> DBOps for &'a DataConnection {
parameters: &P,
block_height: BlockHeight,
) -> Result<(), Self::Error> {
chain::rewind_to_height(self, parameters, block_height)
wallet::rewind_to_height(self, parameters, block_height)
}
fn get_address<P: consensus::Parameters>(
@ -127,7 +126,7 @@ impl<'a> DBOps for &'a DataConnection {
params: &P,
account: AccountId,
) -> Result<Option<PaymentAddress>, Self::Error> {
query::get_address(self, params, account)
wallet::get_address(self, params, account)
}
fn get_account_extfvks<P: consensus::Parameters>(
@ -150,47 +149,47 @@ impl<'a> DBOps for &'a DataConnection {
}
fn get_balance(&self, account: AccountId) -> Result<Amount, Self::Error> {
query::get_balance(self, account)
wallet::get_balance(self, account)
}
fn get_verified_balance(&self, account: AccountId) -> Result<Amount, Self::Error> {
query::get_verified_balance(self, account)
wallet::get_verified_balance(self, account)
}
fn get_received_memo_as_utf8(
&self,
id_note: Self::NoteRef,
) -> Result<Option<String>, Self::Error> {
query::get_received_memo_as_utf8(self, id_note)
wallet::get_received_memo_as_utf8(self, id_note)
}
fn get_sent_memo_as_utf8(&self, id_note: Self::NoteRef) -> Result<Option<String>, Self::Error> {
query::get_sent_memo_as_utf8(self, id_note)
wallet::get_sent_memo_as_utf8(self, id_note)
}
fn get_extended_full_viewing_keys<P: consensus::Parameters>(
&self,
params: &P,
) -> Result<Vec<ExtendedFullViewingKey>, Self::Error> {
query::get_extended_full_viewing_keys(self, params)
wallet::get_extended_full_viewing_keys(self, params)
}
fn get_commitment_tree(
&self,
block_height: BlockHeight,
) -> Result<Option<CommitmentTree<Node>>, Self::Error> {
query::get_commitment_tree(self, block_height)
wallet::get_commitment_tree(self, block_height)
}
fn get_witnesses(
&self,
block_height: BlockHeight,
) -> Result<Vec<(Self::NoteRef, IncrementalWitness<Node>)>, Self::Error> {
query::get_witnesses(self, block_height)
wallet::get_witnesses(self, block_height)
}
fn get_nullifiers(&self) -> Result<Vec<(Vec<u8>, AccountId)>, Self::Error> {
query::get_nullifiers(self)
wallet::get_nullifiers(self)
}
fn get_update_ops(&self) -> Result<Self::UpdateOps, Self::Error> {
@ -558,7 +557,7 @@ impl CacheOps for CacheConnection {
where
F: FnMut(BlockHeight, CompactBlock) -> Result<(), Self::Error>,
{
scan::with_cached_blocks(self, from_height, limit, with_row)
chain::with_cached_blocks(self, from_height, limit, with_row)
}
}

View File

@ -1,243 +0,0 @@
//! Functions for scanning the chain and extracting relevant information.
use protobuf::parse_from_bytes;
use rusqlite::types::ToSql;
use zcash_primitives::consensus::BlockHeight;
use zcash_client_backend::proto::compact_formats::CompactBlock;
use crate::{error::SqliteClientError, CacheConnection};
struct CompactBlockRow {
height: BlockHeight,
data: Vec<u8>,
}
pub fn with_cached_blocks<F>(
cache: &CacheConnection,
from_height: BlockHeight,
limit: Option<u32>,
mut with_row: F,
) -> Result<(), SqliteClientError>
where
F: FnMut(BlockHeight, CompactBlock) -> Result<(), SqliteClientError>,
{
// Fetch the CompactBlocks we need to scan
let mut stmt_blocks = cache.0.prepare(
"SELECT height, data FROM compactblocks WHERE height > ? ORDER BY height ASC LIMIT ?",
)?;
let rows = stmt_blocks.query_map(
&[
u32::from(from_height).to_sql()?,
limit.unwrap_or(u32::max_value()).to_sql()?,
],
|row| {
Ok(CompactBlockRow {
height: BlockHeight::from_u32(row.get(0)?),
data: row.get(1)?,
})
},
)?;
for row_result in rows {
let row = row_result?;
with_row(row.height, parse_from_bytes(&row.data)?)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use rusqlite::Connection;
use tempfile::NamedTempFile;
use zcash_primitives::{
block::BlockHash,
transaction::components::Amount,
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
};
use zcash_client_backend::data_api::{chain::scan_cached_blocks, error::ChainInvalid};
use crate::{
init::{init_accounts_table, init_cache_database, init_data_database},
query::get_balance,
tests::{
self, fake_compact_block, fake_compact_block_spending, insert_into_cache,
sapling_activation_height,
},
AccountId, CacheConnection, DataConnection, NoteId,
};
#[test]
fn scan_cached_blocks_requires_sequential_blocks() {
let cache_file = NamedTempFile::new().unwrap();
let db_cache = CacheConnection(Connection::open(cache_file.path()).unwrap());
init_cache_database(&db_cache).unwrap();
let data_file = NamedTempFile::new().unwrap();
let db_data = DataConnection(Connection::open(data_file.path()).unwrap());
init_data_database(&db_data).unwrap();
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &tests::network(), &[extfvk.clone()]).unwrap();
// Create a block with height SAPLING_ACTIVATION_HEIGHT
let value = Amount::from_u64(50000).unwrap();
let (cb1, _) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
extfvk.clone(),
value,
);
insert_into_cache(&db_cache, &cb1);
scan_cached_blocks(&tests::network(), &db_cache, &db_data, None).unwrap();
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), value);
// We cannot scan a block of height SAPLING_ACTIVATION_HEIGHT + 2 next
let (cb2, _) = fake_compact_block(
sapling_activation_height() + 1,
cb1.hash(),
extfvk.clone(),
value,
);
let (cb3, _) = fake_compact_block(
sapling_activation_height() + 2,
cb2.hash(),
extfvk.clone(),
value,
);
insert_into_cache(&db_cache, &cb3);
match scan_cached_blocks(&tests::network(), &db_cache, &db_data, None) {
Ok(_) => panic!("Should have failed"),
Err(e) => {
assert_eq!(
e.to_string(),
ChainInvalid::block_height_mismatch::<rusqlite::Error, NoteId>(
sapling_activation_height() + 1,
sapling_activation_height() + 2
)
.to_string()
);
}
}
// If we add a block of height SAPLING_ACTIVATION_HEIGHT + 1, we can now scan both
insert_into_cache(&db_cache, &cb2);
scan_cached_blocks(&tests::network(), &db_cache, &db_data, None).unwrap();
assert_eq!(
get_balance(&db_data, AccountId(0)).unwrap(),
Amount::from_u64(150_000).unwrap()
);
}
#[test]
fn scan_cached_blocks_finds_received_notes() {
let cache_file = NamedTempFile::new().unwrap();
let db_cache = CacheConnection(Connection::open(cache_file.path()).unwrap());
init_cache_database(&db_cache).unwrap();
let data_file = NamedTempFile::new().unwrap();
let db_data = DataConnection(Connection::open(data_file.path()).unwrap());
init_data_database(&db_data).unwrap();
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &tests::network(), &[extfvk.clone()]).unwrap();
// Account balance should be zero
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), Amount::zero());
// Create a fake CompactBlock sending value to the address
let value = Amount::from_u64(5).unwrap();
let (cb, _) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
extfvk.clone(),
value,
);
insert_into_cache(&db_cache, &cb);
// Scan the cache
scan_cached_blocks(&tests::network(), &db_cache, &db_data, None).unwrap();
// Account balance should reflect the received note
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), value);
// Create a second fake CompactBlock sending more value to the address
let value2 = Amount::from_u64(7).unwrap();
let (cb2, _) =
fake_compact_block(sapling_activation_height() + 1, cb.hash(), extfvk, value2);
insert_into_cache(&db_cache, &cb2);
// Scan the cache again
scan_cached_blocks(&tests::network(), &db_cache, &db_data, None).unwrap();
// Account balance should reflect both received notes
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), value + value2);
}
#[test]
fn scan_cached_blocks_finds_change_notes() {
let cache_file = NamedTempFile::new().unwrap();
let db_cache = CacheConnection(Connection::open(cache_file.path()).unwrap());
init_cache_database(&db_cache).unwrap();
let data_file = NamedTempFile::new().unwrap();
let db_data = DataConnection(Connection::open(data_file.path()).unwrap());
init_data_database(&db_data).unwrap();
// Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &tests::network(), &[extfvk.clone()]).unwrap();
// Account balance should be zero
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), Amount::zero());
// Create a fake CompactBlock sending value to the address
let value = Amount::from_u64(5).unwrap();
let (cb, nf) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
extfvk.clone(),
value,
);
insert_into_cache(&db_cache, &cb);
// Scan the cache
scan_cached_blocks(&tests::network(), &db_cache, &db_data, None).unwrap();
// Account balance should reflect the received note
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), value);
// Create a second fake CompactBlock spending value from the address
let extsk2 = ExtendedSpendingKey::master(&[0]);
let to2 = extsk2.default_address().unwrap().1;
let value2 = Amount::from_u64(2).unwrap();
insert_into_cache(
&db_cache,
&fake_compact_block_spending(
sapling_activation_height() + 1,
cb.hash(),
(nf, value),
extfvk,
to2,
value2,
),
);
// Scan the cache again
scan_cached_blocks(&tests::network(), &db_cache, &db_data, None).unwrap();
// Account balance should equal the change
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), value - value2);
}
}

View File

@ -390,8 +390,8 @@ mod tests {
use crate::{
init::{init_accounts_table, init_blocks_table, init_cache_database, init_data_database},
query::{get_balance, get_verified_balance},
tests::{self, fake_compact_block, insert_into_cache, sapling_activation_height},
wallet::{get_balance, get_verified_balance},
AccountId, CacheConnection, DataConnection,
};

View File

@ -3,12 +3,13 @@
use rusqlite::{OptionalExtension, NO_PARAMS};
use zcash_primitives::{
consensus::{self, BlockHeight},
block::BlockHash,
consensus::{self, BlockHeight, NetworkUpgrade},
merkle_tree::{CommitmentTree, IncrementalWitness},
note_encryption::Memo,
primitives::PaymentAddress,
sapling::Node,
transaction::components::Amount,
transaction::{components::Amount, TxId},
zip32::ExtendedFullViewingKey,
};
@ -212,6 +213,110 @@ pub fn get_sent_memo_as_utf8(
}
}
pub fn block_height_extrema(
conn: &DataConnection,
) -> Result<Option<(BlockHeight, BlockHeight)>, rusqlite::Error> {
conn.0
.query_row(
"SELECT MIN(height), MAX(height) FROM blocks",
NO_PARAMS,
|row| {
let min_height: u32 = row.get(0)?;
let max_height: u32 = row.get(1)?;
Ok(Some((
BlockHeight::from(min_height),
BlockHeight::from(max_height),
)))
},
)
//.optional() doesn't work here because a failed aggregate function
//produces a runtime error, not an empty set of rows.
.or(Ok(None))
}
pub fn get_tx_height(
conn: &DataConnection,
txid: TxId,
) -> Result<Option<BlockHeight>, rusqlite::Error> {
conn.0
.query_row(
"SELECT block FROM transactions WHERE txid = ?",
&[txid.0.to_vec()],
|row| row.get(0).map(u32::into),
)
.optional()
}
pub fn get_block_hash(
conn: &DataConnection,
block_height: BlockHeight,
) -> Result<Option<BlockHash>, rusqlite::Error> {
conn.0
.query_row(
"SELECT hash FROM blocks WHERE height = ?",
&[u32::from(block_height)],
|row| {
let row_data = row.get::<_, Vec<_>>(0)?;
Ok(BlockHash::from_slice(&row_data))
},
)
.optional()
}
/// Rewinds the database to the given height.
///
/// If the requested height is greater than or equal to the height of the last scanned
/// block, this function does nothing.
pub fn rewind_to_height<P: consensus::Parameters>(
conn: &DataConnection,
parameters: &P,
block_height: BlockHeight,
) -> Result<(), SqliteClientError> {
let sapling_activation_height = parameters
.activation_height(NetworkUpgrade::Sapling)
.ok_or(SqliteClientError(Error::SaplingNotActive))?;
// Recall where we synced up to previously.
// If we have never synced, use Sapling activation height.
let last_scanned_height =
conn.0
.query_row("SELECT MAX(height) FROM blocks", NO_PARAMS, |row| {
row.get(0)
.map(u32::into)
.or(Ok(sapling_activation_height - 1))
})?;
if block_height >= last_scanned_height {
// Nothing to do.
return Ok(());
}
// Start an SQL transaction for rewinding.
conn.0.execute("BEGIN IMMEDIATE", NO_PARAMS)?;
// Decrement witnesses.
conn.0.execute(
"DELETE FROM sapling_witnesses WHERE block > ?",
&[u32::from(block_height)],
)?;
// Un-mine transactions.
conn.0.execute(
"UPDATE transactions SET block = NULL, tx_index = NULL WHERE block > ?",
&[u32::from(block_height)],
)?;
// Now that they aren't depended on, delete scanned blocks.
conn.0.execute(
"DELETE FROM blocks WHERE height > ?",
&[u32::from(block_height)],
)?;
// Commit the SQL transaction, rewinding atomically.
conn.0.execute("COMMIT", NO_PARAMS)?;
Ok(())
}
pub fn get_extended_full_viewing_keys<P: consensus::Parameters>(
data: &DataConnection,
params: &P,