Documentation updates, fixes, and cleanups

Co-authored-by: Daira Hopwood <daira@jacaranda.org>
This commit is contained in:
Jack Grigg 2023-07-13 18:25:56 +01:00
parent 2a98f94f05
commit a87dca00e2
10 changed files with 54 additions and 42 deletions

View File

@ -85,6 +85,8 @@ and this library adheres to Rust's notion of
feature flag, has been modified by the addition of a `sapling_tree` property. feature flag, has been modified by the addition of a `sapling_tree` property.
- `wallet::input_selection`: - `wallet::input_selection`:
- `Proposal::target_height` (use `Proposal::min_target_height` instead). - `Proposal::target_height` (use `Proposal::min_target_height` instead).
- `zcash_client_backend::data_api::chain::validate_chain` (logic merged into
`chain::scan_cached_blocks`.
- `zcash_client_backend::data_api::chain::error::{ChainError, Cause}` have been - `zcash_client_backend::data_api::chain::error::{ChainError, Cause}` have been
replaced by `zcash_client_backend::scanning::ScanError` replaced by `zcash_client_backend::scanning::ScanError`
- `zcash_client_backend::wallet::WalletSaplingOutput::{witness, witness_mut}` - `zcash_client_backend::wallet::WalletSaplingOutput::{witness, witness_mut}`

View File

@ -89,6 +89,8 @@ pub trait WalletRead {
/// tree size information for each block; or else the scan is likely to fail if notes belonging /// tree size information for each block; or else the scan is likely to fail if notes belonging
/// to the wallet are detected. /// to the wallet are detected.
/// ///
/// The returned range(s) may include block heights beyond the current chain tip.
///
/// [`CompactBlock`]: crate::proto::compact_formats::CompactBlock /// [`CompactBlock`]: crate::proto::compact_formats::CompactBlock
fn suggest_scan_ranges(&self) -> Result<Vec<ScanRange>, Self::Error>; fn suggest_scan_ranges(&self) -> Result<Vec<ScanRange>, Self::Error>;
@ -502,8 +504,8 @@ pub trait WalletWrite: WalletRead {
/// Updates the wallet's view of the blockchain. /// Updates the wallet's view of the blockchain.
/// ///
/// This method is used to provide the wallet with information about the state of the /// This method is used to provide the wallet with information about the state of the
/// blockchain, and detect any previously scanned that needs to be re-validated before /// blockchain, and detect any previously scanned data that needs to be re-validated
/// proceeding with scanning. It should be called at wallet startup prior to calling /// before proceeding with scanning. It should be called at wallet startup prior to calling
/// [`WalletRead::suggest_scan_ranges`] in order to provide the wallet with the information it /// [`WalletRead::suggest_scan_ranges`] in order to provide the wallet with the information it
/// needs to correctly prioritize scanning operations. /// needs to correctly prioritize scanning operations.
fn update_chain_tip(&mut self, tip_height: BlockHeight) -> Result<(), Self::Error>; fn update_chain_tip(&mut self, tip_height: BlockHeight) -> Result<(), Self::Error>;

View File

@ -104,8 +104,7 @@
//! } //! }
//! } //! }
//! //!
//! // Truncation will have updated the suggested scan ranges, so we now //! // In case we updated the suggested scan ranges, now re-request.
//! // re_request
//! scan_ranges = wallet_db.suggest_scan_ranges().map_err(Error::Wallet)?; //! scan_ranges = wallet_db.suggest_scan_ranges().map_err(Error::Wallet)?;
//! } //! }
//! _ => { //! _ => {
@ -123,10 +122,13 @@
//! // Download the blocks in `scan_range` into the block source. While in this example this //! // Download the blocks in `scan_range` into the block source. While in this example this
//! // step is performed in-line, it's fine for the download of scan ranges to be asynchronous //! // step is performed in-line, it's fine for the download of scan ranges to be asynchronous
//! // and for the scanner to process the downloaded ranges as they become available in a //! // and for the scanner to process the downloaded ranges as they become available in a
//! // separate thread. //! // separate thread. The scan ranges should also be broken down into smaller chunks as
//! // appropriate, and for ranges with priority `Historic` it can be useful to download and
//! // scan the range in reverse order (to discover more recent unspent notes sooner), or from
//! // the start and end of the range inwards.
//! unimplemented!(); //! unimplemented!();
//! //!
//! // Scan the downloaded blocks, //! // Scan the downloaded blocks.
//! let scan_result = scan_cached_blocks( //! let scan_result = scan_cached_blocks(
//! &network, //! &network,
//! &block_source, //! &block_source,

View File

@ -10,13 +10,14 @@ pub enum ScanPriority {
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,
/// Block ranges adjacent to wallet open heights. /// Block ranges adjacent to heights at which the user opened the wallet.
OpenAdjacent, OpenAdjacent,
/// Blocks that must be scanned to complete note commitment tree shards adjacent to found notes. /// Blocks that must be scanned to complete note commitment tree shards adjacent to found notes.
FoundNote, FoundNote,
/// Blocks that must be scanned to complete the latest note commitment tree shard. /// Blocks that must be scanned to complete the latest note commitment tree shard.
ChainTip, ChainTip,
/// A previously-scanned range that must be verified has highest priority. /// A previously scanned range that must be verified to check it is still in the
/// main chain, has highest priority.
Verify, Verify,
} }

View File

@ -175,7 +175,7 @@ impl fmt::Display for ScanError {
at_height at_height
), ),
BlockHeightDiscontinuity { prev_height, new_height } => { BlockHeightDiscontinuity { prev_height, new_height } => {
write!(f, "Block height discontinuity at height {}; next height is : {}", prev_height, new_height) write!(f, "Block height discontinuity at height {}; previous height was: {}", new_height, prev_height)
} }
TreeSizeMismatch { protocol, at_height, given, computed } => { TreeSizeMismatch { protocol, at_height, given, computed } => {
write!(f, "The {:?} note commitment tree size provided by a compact block did not match the expected size at height {}; given {}, expected {}", protocol, at_height, given, computed) write!(f, "The {:?} note commitment tree size provided by a compact block did not match the expected size at height {}; given {}, expected {}", protocol, at_height, given, computed)

View File

@ -26,10 +26,6 @@ pub mod migrations;
/// Starting at `from_height`, the `with_row` callback is invoked with each block retrieved from /// Starting at `from_height`, the `with_row` callback is invoked with each block retrieved from
/// the backing store. If the `limit` value provided is `None`, all blocks are traversed up to the /// the backing store. If the `limit` value provided is `None`, all blocks are traversed up to the
/// maximum height. /// maximum height.
///
/// # Panics
///
/// Panics if the provided `limit` value exceeds the range of a u32
pub(crate) fn blockdb_with_blocks<F, DbErrT>( pub(crate) fn blockdb_with_blocks<F, DbErrT>(
block_source: &BlockDb, block_source: &BlockDb,
from_height: Option<BlockHeight>, from_height: Option<BlockHeight>,
@ -200,10 +196,6 @@ pub(crate) fn blockmetadb_find_block(
/// Starting at `from_height`, the `with_row` callback is invoked with each block retrieved from /// Starting at `from_height`, the `with_row` callback is invoked with each block retrieved from
/// the backing store. If the `limit` value provided is `None`, all blocks are traversed up to the /// the backing store. If the `limit` value provided is `None`, all blocks are traversed up to the
/// maximum height for which metadata is available. /// maximum height for which metadata is available.
///
/// # Panics
///
/// Panics if the provided `limit` value exceeds the range of a u32
#[cfg(feature = "unstable")] #[cfg(feature = "unstable")]
pub(crate) fn fsblockdb_with_blocks<F, DbErrT>( pub(crate) fn fsblockdb_with_blocks<F, DbErrT>(
cache: &FsBlockDb, cache: &FsBlockDb,

View File

@ -748,7 +748,7 @@ pub(crate) fn truncate_to_height<P: consensus::Parameters>(
) -> Result<(), SqliteClientError> { ) -> Result<(), SqliteClientError> {
let sapling_activation_height = params let sapling_activation_height = params
.activation_height(NetworkUpgrade::Sapling) .activation_height(NetworkUpgrade::Sapling)
.expect("Sapling activation height mutst be available."); .expect("Sapling activation height must be available.");
// Recall where we synced up to previously. // Recall where we synced up to previously.
let last_scanned_height = conn.query_row("SELECT MAX(height) FROM blocks", [], |row| { let last_scanned_height = conn.query_row("SELECT MAX(height) FROM blocks", [], |row| {

View File

@ -772,9 +772,9 @@ pub(crate) fn put_shard_roots<
return Ok(()); return Ok(());
} }
// We treat the cap as a DEPTH-SHARD_HEIGHT tree so that we can make a batch insertion of // We treat the cap as a tree with `DEPTH - SHARD_HEIGHT` levels, so that we can make a
// root data using `Position::from(start_index)` as the starting position and treating the // batch insertion of root data using `Position::from(start_index)` as the starting position
// roots as level-0 leaves. // and treating the roots as level-0 leaves.
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
struct LevelShifter<H, const SHARD_HEIGHT: u8>(H); struct LevelShifter<H, const SHARD_HEIGHT: u8>(H);
impl<H: Hashable, const SHARD_HEIGHT: u8> Hashable for LevelShifter<H, SHARD_HEIGHT> { impl<H: Hashable, const SHARD_HEIGHT: u8> Hashable for LevelShifter<H, SHARD_HEIGHT> {

View File

@ -207,7 +207,8 @@ impl RusqliteMigration for Migration {
} }
} }
// Establish the scan queue & wallet history table // Establish the scan queue & wallet history table.
// block_range_end is exclusive.
debug!("Creating table for scan queue"); debug!("Creating table for scan queue");
transaction.execute_batch( transaction.execute_batch(
"CREATE TABLE scan_queue ( "CREATE TABLE scan_queue (

View File

@ -107,7 +107,8 @@ pub(crate) fn suggest_scan_ranges(
// This implements the dominance rule for range priority. If the inserted range's priority is // This implements the dominance rule for range priority. If the inserted range's priority is
// `Verify`, this replaces any existing priority. Otherwise, if the current priority is // `Verify`, this replaces any existing priority. Otherwise, if the current priority is
// `Scanned`, this overwrites any priority // `Scanned`, it remains as `Scanned`; and if the new priority is `Scanned`, it
// overrides any existing priority.
fn dominance(current: &ScanPriority, inserted: &ScanPriority, insert: Insert) -> Dominance { fn dominance(current: &ScanPriority, inserted: &ScanPriority, insert: Insert) -> Dominance {
match (current.cmp(inserted), (current, inserted)) { match (current.cmp(inserted), (current, inserted)) {
(Ordering::Equal, _) => Dominance::Equal, (Ordering::Equal, _) => Dominance::Equal,
@ -118,14 +119,25 @@ fn dominance(current: &ScanPriority, inserted: &ScanPriority, insert: Insert) ->
} }
} }
/// In the comments for each alternative, `()` represents the left range and `[]` represents the right range.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RangeOrdering { enum RangeOrdering {
/// `( ) [ ]`
LeftFirstDisjoint, LeftFirstDisjoint,
/// `( [ ) ]`
LeftFirstOverlap, LeftFirstOverlap,
/// `[ ( ) ]`
LeftContained, LeftContained,
/// ```text
/// ( )
/// [ ]
/// ```
Equal, Equal,
/// `( [ ] )`
RightContained, RightContained,
/// `[ ( ] )`
RightFirstOverlap, RightFirstOverlap,
/// `[ ] ( )`
RightFirstDisjoint, RightFirstDisjoint,
} }
@ -307,10 +319,11 @@ impl SpanningTree {
match self { match self {
SpanningTree::Leaf(cur) => Self::from_joined(insert(cur, to_insert)), SpanningTree::Leaf(cur) => Self::from_joined(insert(cur, to_insert)),
SpanningTree::Parent { span, left, right } => { SpanningTree::Parent { span, left, right } => {
// TODO: this algorithm always preserves the existing partition point, and does not // This algorithm always preserves the existing partition point, and does not do
// do any rebalancing or unification of ranges within the tree; `into_vec` // any rebalancing or unification of ranges within the tree. This should be okay
// performes such unification and the tree being unbalanced should be fine given // because `into_vec` performs such unification, and the tree being unbalanced
// the relatively small number of ranges we should ordinarily be concerned with. // should be fine given the relatively small number of ranges we should ordinarily
// be concerned with.
use RangeOrdering::*; use RangeOrdering::*;
match RangeOrdering::cmp(&span, to_insert.block_range()) { match RangeOrdering::cmp(&span, to_insert.block_range()) {
LeftFirstDisjoint => { LeftFirstDisjoint => {
@ -417,7 +430,7 @@ pub(crate) fn insert_queue_entries<'a>(
trace!("Inserting queue entry {}", entry); trace!("Inserting queue entry {}", entry);
if !entry.is_empty() { if !entry.is_empty() {
stmt.execute(named_params![ stmt.execute(named_params![
":block_range_start": u32::from(entry.block_range().start) , ":block_range_start": u32::from(entry.block_range().start),
":block_range_end": u32::from(entry.block_range().end), ":block_range_end": u32::from(entry.block_range().end),
":priority": priority_code(&entry.priority()) ":priority": priority_code(&entry.priority())
])?; ])?;
@ -459,7 +472,7 @@ pub(crate) fn replace_queue_entries(
":end": u32::from(query_range.end), ":end": u32::from(query_range.end),
])?; ])?;
// Iterate over the ranges in the scan queue that overlaps the range that we have // Iterate over the ranges in the scan queue that overlap the range that we have
// identified as needing to be fully scanned. For each such range add it to the // identified as needing to be fully scanned. For each such range add it to the
// spanning tree (these should all be nonoverlapping ranges, but we might coalesce // spanning tree (these should all be nonoverlapping ranges, but we might coalesce
// some in the process). // some in the process).
@ -554,9 +567,9 @@ pub(crate) fn scan_complete<P: consensus::Parameters>(
.flatten()) .flatten())
}; };
// if no notes belonging to the wallet were found, so don't need to extend the scanning // If no notes belonging to the wallet were found, we don't need to extend the scanning
// range suggestions to include the associated subtrees, and our bounds are just the // range suggestions to include the associated subtrees, and our bounds are just the
// scanned range // scanned range.
subtree_bounds subtree_bounds
.map(|(min_idx, max_idx)| { .map(|(min_idx, max_idx)| {
let range_min = if *min_idx > 0 { let range_min = if *min_idx > 0 {
@ -617,13 +630,12 @@ pub(crate) fn update_chain_tip<P: consensus::Parameters>(
)?; )?;
// Create a scanning range for the fragment of the last shard leading up to new tip. // Create a scanning range for the fragment of the last shard leading up to new tip.
// However, only do so if the start of the shard is at a stable height.
let shard_entry = shard_start_height let shard_entry = shard_start_height
.filter(|h| h < &chain_end) .filter(|h| h < &chain_end)
.map(|h| ScanRange::from_parts(h..chain_end, ScanPriority::ChainTip)); .map(|h| ScanRange::from_parts(h..chain_end, ScanPriority::ChainTip));
// Create scanning ranges to either validate potentially invalid blocks at the wallet's view // Create scanning ranges to either validate potentially invalid blocks at the wallet's view
// of the chain tip, // of the chain tip, or connect the prior tip to the new tip.
let tip_entry = block_height_extrema(conn)?.map(|(_, prior_tip)| { let tip_entry = block_height_extrema(conn)?.map(|(_, prior_tip)| {
// If we don't have shard metadata, this means we're doing linear scanning, so create a // If we don't have shard metadata, this means we're doing linear scanning, so create a
// scan range from the prior tip to the current tip with `Historic` priority. // scan range from the prior tip to the current tip with `Historic` priority.
@ -634,7 +646,7 @@ pub(crate) fn update_chain_tip<P: consensus::Parameters>(
// and not subject to being reorg'ed. // and not subject to being reorg'ed.
let stable_height = new_tip.saturating_sub(PRUNING_DEPTH); let stable_height = new_tip.saturating_sub(PRUNING_DEPTH);
// if the wallet's prior tip is above the stable height, prioritize the range between // If the wallet's prior tip is above the stable height, prioritize the range between
// it and the new tip as `ChainTip`. Otherwise, prioritize the `VALIDATION_DEPTH` // it and the new tip as `ChainTip`. Otherwise, prioritize the `VALIDATION_DEPTH`
// blocks above the wallet's prior tip as `Verify`. Since `scan_cached_blocks` // blocks above the wallet's prior tip as `Verify`. Since `scan_cached_blocks`
// retrieves the metadata for the block being connected to, the connectivity to the // retrieves the metadata for the block being connected to, the connectivity to the
@ -954,12 +966,12 @@ mod tests {
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).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(); init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap();
// Add an account to the wallet // Add an account to the wallet.
let (dfvk, _taddr) = init_test_accounts_table(&mut db_data); let (dfvk, _taddr) = init_test_accounts_table(&mut db_data);
assert_matches!( assert_matches!(
// in the following, we don't care what the root hashes are, they just need to be // In the following, we don't care what the root hashes are, they just need to be
// distinct // distinct.
db_data.put_sapling_subtree_roots( db_data.put_sapling_subtree_roots(
0, 0,
&[ &[
@ -1021,7 +1033,7 @@ mod tests {
Ok(()) Ok(())
); );
// Verify the that adjacent range needed to make the note spendable has been prioritized // Verify the that adjacent range needed to make the note spendable has been prioritized.
let sap_active = u32::from(sapling_activation_height()); let sap_active = u32::from(sapling_activation_height());
assert_matches!( assert_matches!(
db_data.suggest_scan_ranges(), db_data.suggest_scan_ranges(),
@ -1030,7 +1042,7 @@ mod tests {
] ]
); );
// Check that the scanned range has been properly persisted // Check that the scanned range has been properly persisted.
assert_matches!( assert_matches!(
suggest_scan_ranges(&db_data.conn, Scanned), suggest_scan_ranges(&db_data.conn, Scanned),
Ok(scan_ranges) if scan_ranges == vec![ Ok(scan_ranges) if scan_ranges == vec![
@ -1039,8 +1051,8 @@ mod tests {
] ]
); );
// simulate the wallet going offline for a bit, update the chain tip to 30 blocks in the // Simulate the wallet going offline for a bit, update the chain tip to 20 blocks in the
// future // future.
assert_matches!( assert_matches!(
db_data.update_chain_tip(sapling_activation_height() + 340), db_data.update_chain_tip(sapling_activation_height() + 340),
Ok(()) Ok(())
@ -1056,7 +1068,7 @@ mod tests {
] ]
); );
// Now simulate a jump ahead more than 100 blocks // Now simulate a jump ahead more than 100 blocks.
assert_matches!( assert_matches!(
db_data.update_chain_tip(sapling_activation_height() + 450), db_data.update_chain_tip(sapling_activation_height() + 450),
Ok(()) Ok(())