Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

zcash_client_sqlite: Ensure that target and anchor heights are relative to the chain tip. #896

Merged
merged 1 commit into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions zcash_client_backend/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ and this library adheres to Rust's notion of
- `ScannedBlock`
- `ShieldedProtocol`
- `WalletCommitmentTrees`
- `WalletRead::{block_metadata, block_fully_scanned, suggest_scan_ranges}`
- `WalletRead::{chain_height, block_metadata, block_fully_scanned, suggest_scan_ranges}`
- `WalletWrite::{put_blocks, update_chain_tip}`
- `chain::CommitmentTreeRoot`
- `scanning` A new module containing types required for `suggest_scan_ranges`
Expand Down Expand Up @@ -89,10 +89,13 @@ and this library adheres to Rust's notion of

### Removed
- `zcash_client_backend::data_api`:
- `WalletRead::get_all_nullifiers`
- `WalletRead::{get_commitment_tree, get_witnesses}` have been removed
without replacement. The utility of these methods is now subsumed
by those available from the `WalletCommitmentTrees` trait.
- `WalletRead::block_height_extrema` has been removed. Use `chain_height`
nuttycom marked this conversation as resolved.
Show resolved Hide resolved
instead to obtain the wallet's view of the chain tip instead, or
`suggest_scan_ranges` to obtain information about blocks that need to be
scanned.
- `WalletRead::{get_all_nullifiers, get_commitment_tree, get_witnesses}` have
been removed without replacement. The utility of these methods is now
subsumed by those available from the `WalletCommitmentTrees` trait.
- `WalletWrite::advance_by_block` (use `WalletWrite::put_blocks` instead).
- `PrunedBlock` has been replaced by `ScannedBlock`
- `testing::MockWalletDb`, which is available under the `test-dependencies`
Expand All @@ -107,6 +110,9 @@ and this library adheres to Rust's notion of
have been removed as individual incremental witnesses are no longer tracked on a
per-note basis. The global note commitment tree for the wallet should be used
to obtain witnesses for spend operations instead.
- Default implementations of `zcash_client_backend::data_api::WalletRead::{
get_target_and_anchor_heights, get_max_height_hash
}` have been removed. These should be implemented in a backend-specific fashion.


## [0.9.0] - 2023-04-28
Expand Down
58 changes: 21 additions & 37 deletions zcash_client_backend/src/data_api.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//! Interfaces for wallet data persistence & low-level wallet utilities.

use std::cmp;
use std::collections::HashMap;
use std::fmt::Debug;
use std::num::NonZeroU32;
Expand Down Expand Up @@ -60,10 +59,11 @@ pub trait WalletRead {
/// or a UUID.
type NoteRef: Copy + Debug + Eq + Ord;

/// Returns the minimum and maximum block heights for stored blocks.
/// Returns the height of the chain as known to the wallet as of the most recent call to
/// [`WalletWrite::update_chain_tip`].
///
/// This will return `Ok(None)` if no block data is present in the database.
fn block_height_extrema(&self) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error>;
/// This will return `Ok(None)` if the height of the current consensus chain tip is unknown.
fn chain_height(&self) -> Result<Option<BlockHeight>, Self::Error>;

/// Returns the available block metadata for the block at the specified height, if any.
fn block_metadata(&self, height: BlockHeight) -> Result<Option<BlockMetadata>, Self::Error>;
Expand Down Expand Up @@ -98,22 +98,7 @@ pub trait WalletRead {
fn get_target_and_anchor_heights(
&self,
min_confirmations: NonZeroU32,
) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error> {
self.block_height_extrema().map(|heights| {
heights.map(|(min_height, max_height)| {
let target_height = max_height + 1;

// Select an anchor min_confirmations back from the target block,
// unless that would be before the earliest block we have.
let anchor_height = BlockHeight::from(cmp::max(
u32::from(target_height).saturating_sub(min_confirmations.into()),
u32::from(min_height),
));

(target_height, anchor_height)
})
})
}
) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error>;
nuttycom marked this conversation as resolved.
Show resolved Hide resolved

/// Returns the minimum block height corresponding to an unspent note in the wallet.
fn get_min_unspent_height(&self) -> Result<Option<BlockHeight>, Self::Error>;
Expand All @@ -123,22 +108,10 @@ pub trait WalletRead {
/// is not found in the database.
fn get_block_hash(&self, block_height: BlockHeight) -> Result<Option<BlockHash>, Self::Error>;

/// Returns the block hash for the block at the maximum height known
/// in stored data.
/// Returns the block height and hash for the block at the maximum scanned block height.
///
/// This will return `Ok(None)` if no block data is present in the database.
fn get_max_height_hash(&self) -> Result<Option<(BlockHeight, BlockHash)>, Self::Error> {
nuttycom marked this conversation as resolved.
Show resolved Hide resolved
self.block_height_extrema()
.and_then(|extrema_opt| {
extrema_opt
.map(|(_, max_height)| {
self.get_block_hash(max_height)
.map(|hash_opt| hash_opt.map(move |hash| (max_height, hash)))
})
.transpose()
})
.map(|oo| oo.flatten())
}
/// This will return `Ok(None)` if no blocks have been scanned.
fn get_max_height_hash(&self) -> Result<Option<(BlockHeight, BlockHash)>, Self::Error>;

/// Returns the block height in which the specified transaction was mined, or `Ok(None)` if the
/// transaction is not in the main chain.
Expand Down Expand Up @@ -613,7 +586,7 @@ pub mod testing {
use incrementalmerkletree::Address;
use secrecy::{ExposeSecret, SecretVec};
use shardtree::{error::ShardTreeError, store::memory::MemoryShardStore, ShardTree};
use std::{collections::HashMap, convert::Infallible};
use std::{collections::HashMap, convert::Infallible, num::NonZeroU32};

use zcash_primitives::{
block::BlockHash,
Expand Down Expand Up @@ -662,7 +635,14 @@ pub mod testing {
type Error = ();
type NoteRef = u32;

fn block_height_extrema(&self) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error> {
fn chain_height(&self) -> Result<Option<BlockHeight>, Self::Error> {
Ok(None)
}

fn get_target_and_anchor_heights(
&self,
_min_confirmations: NonZeroU32,
) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error> {
Ok(None)
}

Expand Down Expand Up @@ -692,6 +672,10 @@ pub mod testing {
Ok(None)
}

fn get_max_height_hash(&self) -> Result<Option<(BlockHeight, BlockHash)>, Self::Error> {
Ok(None)
}

fn get_tx_height(&self, _txid: TxId) -> Result<Option<BlockHeight>, Self::Error> {
Ok(None)
}
Expand Down
4 changes: 1 addition & 3 deletions zcash_client_backend/src/data_api/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ where
// for mempool transactions.
let height = data
.get_tx_height(tx.txid())?
.or(data
.block_height_extrema()?
.map(|(_, max_height)| max_height + 1))
.or(data.chain_height()?.map(|max_height| max_height + 1))
.or_else(|| params.activation_height(NetworkUpgrade::Sapling))
.expect("Sapling activation height must be known.");

Expand Down
2 changes: 1 addition & 1 deletion zcash_client_sqlite/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ mod tests {
let (dfvk, _taddr) = init_test_accounts_table(&mut db_data);

// Empty chain should return None
assert_matches!(db_data.get_max_height_hash(), Ok(None));
assert_matches!(db_data.chain_height(), Ok(None));

// Create a fake CompactBlock sending value to the address
let (cb, _) = fake_compact_block(
Expand Down
23 changes: 20 additions & 3 deletions zcash_client_sqlite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ use maybe_rayon::{
};
use rusqlite::{self, Connection};
use secrecy::{ExposeSecret, SecretVec};
use std::{borrow::Borrow, collections::HashMap, convert::AsRef, fmt, ops::Range, path::Path};
use std::{
borrow::Borrow, collections::HashMap, convert::AsRef, fmt, num::NonZeroU32, ops::Range,
path::Path,
};

use incrementalmerkletree::Position;
use shardtree::{error::ShardTreeError, ShardTree};
Expand Down Expand Up @@ -157,8 +160,10 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> WalletRead for W
type Error = SqliteClientError;
type NoteRef = ReceivedNoteId;

fn block_height_extrema(&self) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error> {
wallet::block_height_extrema(self.conn.borrow()).map_err(SqliteClientError::from)
fn chain_height(&self) -> Result<Option<BlockHeight>, Self::Error> {
wallet::scan_queue_extrema(self.conn.borrow())
.map(|h| h.map(|(_, max)| max))
.map_err(SqliteClientError::from)
}

fn block_metadata(&self, height: BlockHeight) -> Result<Option<BlockMetadata>, Self::Error> {
Expand All @@ -174,6 +179,14 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> WalletRead for W
.map_err(SqliteClientError::from)
}

fn get_target_and_anchor_heights(
&self,
min_confirmations: NonZeroU32,
) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error> {
wallet::get_target_and_anchor_heights(self.conn.borrow(), min_confirmations)
.map_err(SqliteClientError::from)
}

fn get_min_unspent_height(&self) -> Result<Option<BlockHeight>, Self::Error> {
wallet::get_min_unspent_height(self.conn.borrow()).map_err(SqliteClientError::from)
}
Expand All @@ -182,6 +195,10 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> WalletRead for W
wallet::get_block_hash(self.conn.borrow(), block_height).map_err(SqliteClientError::from)
}

fn get_max_height_hash(&self) -> Result<Option<(BlockHeight, BlockHash)>, Self::Error> {
wallet::get_max_height_hash(self.conn.borrow()).map_err(SqliteClientError::from)
}

fn get_tx_height(&self, txid: TxId) -> Result<Option<BlockHeight>, Self::Error> {
wallet::get_tx_height(self.conn.borrow(), txid).map_err(SqliteClientError::from)
}
Expand Down
62 changes: 59 additions & 3 deletions zcash_client_sqlite/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,11 @@
//! - `memo` the shielded memo associated with the output, if any.

use rusqlite::{self, named_params, OptionalExtension, ToSql};
use std::cmp;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::io::{self, Cursor};
use std::num::NonZeroU32;
use zcash_client_backend::data_api::scanning::{ScanPriority, ScanRange};

use zcash_client_backend::data_api::{NoteId, ShieldedProtocol};
Expand Down Expand Up @@ -623,6 +625,45 @@ pub(crate) fn block_height_extrema(
})
}

/// Returns the minimum and maximum heights of blocks in the chain which may be scanned.
pub(crate) fn scan_queue_extrema(
conn: &rusqlite::Connection,
) -> Result<Option<(BlockHeight, BlockHeight)>, rusqlite::Error> {
conn.query_row(
"SELECT MIN(block_range_start), MAX(block_range_end) FROM scan_queue",
[],
|row| {
let min_height: Option<u32> = row.get(0)?;
let max_height: Option<u32> = row.get(1)?;

// Scan ranges are end-exclusive, so we subtract 1 from `max_height` to obtain the
// height of the last known chain tip;
Ok(min_height
.map(BlockHeight::from)
.zip(max_height.map(|h| BlockHeight::from(h.saturating_sub(1)))))
},
)
}

pub(crate) fn get_target_and_anchor_heights(
conn: &rusqlite::Connection,
min_confirmations: NonZeroU32,
) -> Result<Option<(BlockHeight, BlockHeight)>, rusqlite::Error> {
scan_queue_extrema(conn).map(|heights| {
heights.map(|(min_height, max_height)| {
let target_height = max_height + 1;
// Select an anchor min_confirmations back from the target block,
// unless that would be before the earliest block we have.
let anchor_height = BlockHeight::from(cmp::max(
u32::from(target_height).saturating_sub(min_confirmations.into()),
u32::from(min_height),
));

(target_height, anchor_height)
})
})
}

fn parse_block_metadata(
row: (BlockHeight, Vec<u8>, Option<u32>, Vec<u8>),
) -> Result<BlockMetadata, SqliteClientError> {
Expand Down Expand Up @@ -765,16 +806,31 @@ pub(crate) fn get_block_hash(
.optional()
}

pub(crate) fn get_max_height_hash(
conn: &rusqlite::Connection,
) -> Result<Option<(BlockHeight, BlockHash)>, rusqlite::Error> {
conn.query_row(
"SELECT height, hash FROM blocks ORDER BY height DESC LIMIT 1",
[],
|row| {
let height = row.get::<_, u32>(0).map(BlockHeight::from)?;
let row_data = row.get::<_, Vec<_>>(1)?;
Ok((height, BlockHash::from_slice(&row_data)))
},
)
.optional()
}

/// Gets the height to which the database must be truncated if any truncation that would remove a
/// number of blocks greater than the pruning height is attempted.
pub(crate) fn get_min_unspent_height(
conn: &rusqlite::Connection,
) -> Result<Option<BlockHeight>, SqliteClientError> {
conn.query_row(
"SELECT MIN(tx.block)
FROM sapling_received_notes n
JOIN transactions tx ON tx.id_tx = n.tx
WHERE n.spent IS NULL",
FROM sapling_received_notes n
JOIN transactions tx ON tx.id_tx = n.tx
WHERE n.spent IS NULL",
[],
|row| {
row.get(0)
Expand Down
Loading
Loading