zcash_client_sqlite: Initialize the scan queue as part of `init_blocks_table`

Fixes #902
Fixes #898
This commit is contained in:
Kris Nuttycombe 2023-08-18 22:08:37 -06:00
parent f61d60bb96
commit b3be0318c6
6 changed files with 164 additions and 25 deletions

View File

@ -6,7 +6,9 @@ use zcash_primitives::consensus::BlockHeight;
/// Scanning range priority levels. /// Scanning range priority levels.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ScanPriority { pub enum ScanPriority {
/// Block ranges that have already been scanned have lowest priority. /// Block ranges that are ignored have lowest priority.
Ignored,
/// Block ranges that have already been scanned will not be re-scanned.
Scanned, Scanned,
/// Block ranges to be scanned to advance the fully-scanned height. /// Block ranges to be scanned to advance the fully-scanned height.
Historic, Historic,

View File

@ -4,7 +4,7 @@ use incrementalmerkletree::Retention;
use std::{collections::HashMap, fmt}; use std::{collections::HashMap, fmt};
use tracing::debug; use tracing::debug;
use rusqlite::{self, types::ToSql}; use rusqlite::{self, named_params};
use schemer::{Migrator, MigratorError}; use schemer::{Migrator, MigratorError};
use schemer_rusqlite::RusqliteAdapter; use schemer_rusqlite::RusqliteAdapter;
use secrecy::SecretVec; use secrecy::SecretVec;
@ -13,18 +13,27 @@ use uuid::Uuid;
use zcash_primitives::{ use zcash_primitives::{
block::BlockHash, block::BlockHash,
consensus::{self, BlockHeight}, consensus::{self, BlockHeight, NetworkUpgrade},
merkle_tree::read_commitment_tree, merkle_tree::read_commitment_tree,
sapling, sapling,
transaction::components::amount::BalanceError, transaction::components::amount::BalanceError,
zip32::AccountId, zip32::AccountId,
}; };
use zcash_client_backend::{data_api::SAPLING_SHARD_HEIGHT, keys::UnifiedFullViewingKey}; use zcash_client_backend::{
data_api::{
scanning::{ScanPriority, ScanRange},
SAPLING_SHARD_HEIGHT,
},
keys::UnifiedFullViewingKey,
};
use crate::{error::SqliteClientError, wallet, WalletDb, PRUNING_DEPTH, SAPLING_TABLES_PREFIX}; use crate::{error::SqliteClientError, wallet, WalletDb, PRUNING_DEPTH, SAPLING_TABLES_PREFIX};
use super::commitment_tree::{self, SqliteShardStore}; use super::{
commitment_tree::{self, SqliteShardStore},
scanning::insert_queue_entries,
};
mod migrations; mod migrations;
@ -309,15 +318,28 @@ pub fn init_blocks_table<P: consensus::Parameters>(
wdb.conn.0.execute( wdb.conn.0.execute(
"INSERT INTO blocks (height, hash, time, sapling_tree) "INSERT INTO blocks (height, hash, time, sapling_tree)
VALUES (?, ?, ?, ?)", VALUES (:height, :hash, :time, :sapling_tree)",
[ named_params![
u32::from(height).to_sql()?, ":height": u32::from(height),
hash.0.to_sql()?, ":hash": hash.0,
time.to_sql()?, ":time": time,
sapling_tree.to_sql()?, ":sapling_tree": sapling_tree,
], ],
)?; )?;
if let Some(sapling_activation) = wdb.params.activation_height(NetworkUpgrade::Sapling) {
let scan_range_start = std::cmp::min(sapling_activation, height);
let scan_range_end = height + 1;
insert_queue_entries(
wdb.conn.0,
Some(ScanRange::from_parts(
scan_range_start..scan_range_end,
ScanPriority::Ignored,
))
.iter(),
)?;
}
if let Some(nonempty_frontier) = block_end_tree.to_frontier().value() { if let Some(nonempty_frontier) = block_end_tree.to_frontier().value() {
debug!("Inserting frontier into ShardTree: {:?}", nonempty_frontier); debug!("Inserting frontier into ShardTree: {:?}", nonempty_frontier);
let shard_store = let shard_store =
@ -582,7 +604,7 @@ mod tests {
scan_queue.block_range_start <= prev_shard.subtree_end_height scan_queue.block_range_start <= prev_shard.subtree_end_height
AND (scan_queue.block_range_end - 1) >= shard.subtree_end_height AND (scan_queue.block_range_end - 1) >= shard.subtree_end_height
) )
WHERE scan_queue.priority != {}", WHERE scan_queue.priority > {}",
u32::from(tests::network().activation_height(NetworkUpgrade::Sapling).unwrap()), u32::from(tests::network().activation_height(NetworkUpgrade::Sapling).unwrap()),
priority_code(&ScanPriority::Scanned), priority_code(&ScanPriority::Scanned),
), ),

View File

@ -56,7 +56,9 @@ pub(super) fn all_migrations<P: consensus::Parameters + 'static>(
Box::new(add_transaction_views::Migration), Box::new(add_transaction_views::Migration),
Box::new(v_transactions_net::Migration), Box::new(v_transactions_net::Migration),
Box::new(received_notes_nullable_nf::Migration), Box::new(received_notes_nullable_nf::Migration),
Box::new(shardtree_support::Migration), Box::new(shardtree_support::Migration {
params: params.clone(),
}),
Box::new(nullifier_map::Migration), Box::new(nullifier_map::Migration),
Box::new(sapling_memo_consistency::Migration { Box::new(sapling_memo_consistency::Migration {
params: params.clone(), params: params.clone(),

View File

@ -17,7 +17,7 @@ use zcash_client_backend::data_api::{
SAPLING_SHARD_HEIGHT, SAPLING_SHARD_HEIGHT,
}; };
use zcash_primitives::{ use zcash_primitives::{
consensus::BlockHeight, consensus::{self, BlockHeight, NetworkUpgrade},
merkle_tree::{read_commitment_tree, read_incremental_witness}, merkle_tree::{read_commitment_tree, read_incremental_witness},
sapling, sapling,
}; };
@ -39,9 +39,11 @@ pub(super) const MIGRATION_ID: Uuid = Uuid::from_fields(
b"\x8b\xe5\xf5\x12\xbc\xce\x6c\xbf", b"\x8b\xe5\xf5\x12\xbc\xce\x6c\xbf",
); );
pub(super) struct Migration; pub(super) struct Migration<P> {
pub(super) params: P,
}
impl schemer::Migration for Migration { impl<P> schemer::Migration for Migration<P> {
fn id(&self) -> Uuid { fn id(&self) -> Uuid {
MIGRATION_ID MIGRATION_ID
} }
@ -57,7 +59,7 @@ impl schemer::Migration for Migration {
} }
} }
impl RusqliteMigration for Migration { impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
type Error = WalletMigrationError; type Error = WalletMigrationError;
fn up(&self, transaction: &rusqlite::Transaction) -> Result<(), WalletMigrationError> { fn up(&self, transaction: &rusqlite::Transaction) -> Result<(), WalletMigrationError> {
@ -262,13 +264,17 @@ impl RusqliteMigration for Migration {
if let Some((start, end)) = block_height_extrema { if let Some((start, end)) = block_height_extrema {
// `ScanRange` uses an exclusive upper bound. // `ScanRange` uses an exclusive upper bound.
let chain_end = end + 1; let chain_end = end + 1;
let ignored_range =
self.params
.activation_height(NetworkUpgrade::Sapling)
.map(|sapling_activation| {
let ignored_range_start = std::cmp::min(sapling_activation, start);
ScanRange::from_parts(ignored_range_start..start, ScanPriority::Ignored)
});
let scanned_range = ScanRange::from_parts(start..chain_end, ScanPriority::Scanned);
insert_queue_entries( insert_queue_entries(
transaction, transaction,
Some(ScanRange::from_parts( ignored_range.iter().chain(Some(scanned_range).iter()),
start..chain_end,
ScanPriority::Scanned,
))
.iter(),
)?; )?;
} }

View File

@ -59,7 +59,7 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
scan_queue.block_range_start <= prev_shard.subtree_end_height scan_queue.block_range_start <= prev_shard.subtree_end_height
AND (scan_queue.block_range_end - 1) >= shard.subtree_end_height AND (scan_queue.block_range_end - 1) >= shard.subtree_end_height
) )
WHERE scan_queue.priority != {}", WHERE scan_queue.priority > {}",
SAPLING_SHARD_HEIGHT, SAPLING_SHARD_HEIGHT,
SAPLING_SHARD_HEIGHT, SAPLING_SHARD_HEIGHT,
u32::from(self.params.activation_height(consensus::NetworkUpgrade::Sapling).unwrap()), u32::from(self.params.activation_height(consensus::NetworkUpgrade::Sapling).unwrap()),

View File

@ -52,6 +52,7 @@ impl From<Insert> for Dominance {
pub(crate) fn parse_priority_code(code: i64) -> Option<ScanPriority> { pub(crate) fn parse_priority_code(code: i64) -> Option<ScanPriority> {
use ScanPriority::*; use ScanPriority::*;
match code { match code {
0 => Some(Ignored),
10 => Some(Scanned), 10 => Some(Scanned),
20 => Some(Historic), 20 => Some(Historic),
30 => Some(OpenAdjacent), 30 => Some(OpenAdjacent),
@ -65,6 +66,7 @@ pub(crate) fn parse_priority_code(code: i64) -> Option<ScanPriority> {
pub(crate) fn priority_code(priority: &ScanPriority) -> i64 { pub(crate) fn priority_code(priority: &ScanPriority) -> i64 {
use ScanPriority::*; use ScanPriority::*;
match priority { match priority {
Ignored => 0,
Scanned => 10, Scanned => 10,
Historic => 20, Historic => 20,
OpenAdjacent => 30, OpenAdjacent => 30,
@ -745,7 +747,10 @@ mod tests {
WalletCommitmentTrees, WalletRead, WalletWrite, WalletCommitmentTrees, WalletRead, WalletWrite,
}; };
use zcash_primitives::{ use zcash_primitives::{
block::BlockHash, consensus::BlockHeight, sapling::Node, transaction::components::Amount, block::BlockHash,
consensus::{BlockHeight, NetworkUpgrade, Parameters},
sapling::Node,
transaction::components::Amount,
}; };
use crate::{ use crate::{
@ -754,7 +759,10 @@ mod tests {
self, fake_compact_block, init_test_accounts_table, insert_into_cache, self, fake_compact_block, init_test_accounts_table, insert_into_cache,
sapling_activation_height, AddressType, sapling_activation_height, AddressType,
}, },
wallet::{init::init_wallet_db, scanning::suggest_scan_ranges}, wallet::{
init::{init_blocks_table, init_wallet_db},
scanning::suggest_scan_ranges,
},
BlockDb, WalletDb, BlockDb, WalletDb,
}; };
@ -1212,4 +1220,103 @@ mod tests {
] ]
); );
} }
#[test]
fn init_blocks_table_creates_ignored_range() {
use ScanPriority::*;
let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap();
let sap_active = db_data
.params
.activation_height(NetworkUpgrade::Sapling)
.unwrap();
// Initialise the blocks table. We use Canopy activation as an arbitrary birthday height
// that's greater than Sapling activation.
let birthday_height = db_data
.params
.activation_height(NetworkUpgrade::Canopy)
.unwrap();
init_blocks_table(
&mut db_data,
birthday_height,
BlockHash([1; 32]),
1,
&[0x0, 0x0, 0x0],
)
.unwrap();
let expected = vec![
// The range below the wallet's birthday height is ignored
scan_range(
u32::from(sap_active)..u32::from(birthday_height + 1),
Ignored,
),
];
assert_matches!(
suggest_scan_ranges(&db_data.conn, Ignored),
Ok(scan_ranges) if scan_ranges == expected
);
// Set up some shard history
db_data
.put_sapling_subtree_roots(
0,
&[
// Add the end of a commitment tree below the wallet birthday. We currently
// need to scan from this height up to the tip to make notes spendable, though
// this should not be necessary as we have added a frontier that should
// complete the left-hand side of the required shard; this can be fixed once we
// have proper account birthdays.
CommitmentTreeRoot::from_parts(
birthday_height - 1000,
// fake a hash, the value doesn't matter
Node::empty_leaf(),
),
],
)
.unwrap();
// Update the chain tip
let tip_height = db_data
.params
.activation_height(NetworkUpgrade::Nu5)
.unwrap();
db_data.update_chain_tip(tip_height).unwrap();
// Verify that the suggested scan ranges match what is expected
let expected = vec![
// The birthday height was "last scanned" (as the wallet birthday) so we verify 10
// blocks starting at that height.
scan_range(
u32::from(birthday_height)..u32::from(birthday_height + 10),
Verify,
),
// The remainder of the shard after the verify segment is required in order to make
// notes spendable, so it has priority `ChainTip`
scan_range(
u32::from(birthday_height + 10)..u32::from(tip_height + 1),
ChainTip,
),
// The remainder of the shard prior to the birthday height must be scanned because the
// wallet doesn't know that it already has enough data from the initial frontier to
// avoid having to scan this range.
scan_range(
u32::from(birthday_height - 1000)..u32::from(birthday_height),
ChainTip,
),
// The range below the wallet's birthday height is ignored
scan_range(
u32::from(sap_active)..u32::from(birthday_height - 1000),
Ignored,
),
];
assert_matches!(
suggest_scan_ranges(&db_data.conn, Ignored),
Ok(scan_ranges) if scan_ranges == expected
);
}
} }