Merge pull request #896 from nuttycom/chain_tip_target_height
zcash_client_sqlite: Ensure that target and anchor heights are relative to the chain tip.
This commit is contained in:
commit
104577c108
|
@ -17,7 +17,7 @@ and this library adheres to Rust's notion of
|
||||||
- `ScannedBlock`
|
- `ScannedBlock`
|
||||||
- `ShieldedProtocol`
|
- `ShieldedProtocol`
|
||||||
- `WalletCommitmentTrees`
|
- `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}`
|
- `WalletWrite::{put_blocks, update_chain_tip}`
|
||||||
- `chain::CommitmentTreeRoot`
|
- `chain::CommitmentTreeRoot`
|
||||||
- `scanning` A new module containing types required for `suggest_scan_ranges`
|
- `scanning` A new module containing types required for `suggest_scan_ranges`
|
||||||
|
@ -89,10 +89,13 @@ and this library adheres to Rust's notion of
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- `zcash_client_backend::data_api`:
|
- `zcash_client_backend::data_api`:
|
||||||
- `WalletRead::get_all_nullifiers`
|
- `WalletRead::block_height_extrema` has been removed. Use `chain_height`
|
||||||
- `WalletRead::{get_commitment_tree, get_witnesses}` have been removed
|
instead to obtain the wallet's view of the chain tip instead, or
|
||||||
without replacement. The utility of these methods is now subsumed
|
`suggest_scan_ranges` to obtain information about blocks that need to be
|
||||||
by those available from the `WalletCommitmentTrees` trait.
|
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).
|
- `WalletWrite::advance_by_block` (use `WalletWrite::put_blocks` instead).
|
||||||
- `PrunedBlock` has been replaced by `ScannedBlock`
|
- `PrunedBlock` has been replaced by `ScannedBlock`
|
||||||
- `testing::MockWalletDb`, which is available under the `test-dependencies`
|
- `testing::MockWalletDb`, which is available under the `test-dependencies`
|
||||||
|
@ -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
|
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
|
per-note basis. The global note commitment tree for the wallet should be used
|
||||||
to obtain witnesses for spend operations instead.
|
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
|
## [0.9.0] - 2023-04-28
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
//! Interfaces for wallet data persistence & low-level wallet utilities.
|
//! Interfaces for wallet data persistence & low-level wallet utilities.
|
||||||
|
|
||||||
use std::cmp;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
|
@ -60,10 +59,11 @@ pub trait WalletRead {
|
||||||
/// or a UUID.
|
/// or a UUID.
|
||||||
type NoteRef: Copy + Debug + Eq + Ord;
|
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.
|
/// This will return `Ok(None)` if the height of the current consensus chain tip is unknown.
|
||||||
fn block_height_extrema(&self) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error>;
|
fn chain_height(&self) -> Result<Option<BlockHeight>, Self::Error>;
|
||||||
|
|
||||||
/// Returns the available block metadata for the block at the specified height, if any.
|
/// 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>;
|
fn block_metadata(&self, height: BlockHeight) -> Result<Option<BlockMetadata>, Self::Error>;
|
||||||
|
@ -98,22 +98,7 @@ pub trait WalletRead {
|
||||||
fn get_target_and_anchor_heights(
|
fn get_target_and_anchor_heights(
|
||||||
&self,
|
&self,
|
||||||
min_confirmations: NonZeroU32,
|
min_confirmations: NonZeroU32,
|
||||||
) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error> {
|
) -> 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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the minimum block height corresponding to an unspent note in the wallet.
|
/// Returns the minimum block height corresponding to an unspent note in the wallet.
|
||||||
fn get_min_unspent_height(&self) -> Result<Option<BlockHeight>, Self::Error>;
|
fn get_min_unspent_height(&self) -> Result<Option<BlockHeight>, Self::Error>;
|
||||||
|
@ -123,22 +108,10 @@ pub trait WalletRead {
|
||||||
/// is not found in the database.
|
/// is not found in the database.
|
||||||
fn get_block_hash(&self, block_height: BlockHeight) -> Result<Option<BlockHash>, Self::Error>;
|
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
|
/// Returns the block height and hash for the block at the maximum scanned block height.
|
||||||
/// in stored data.
|
|
||||||
///
|
///
|
||||||
/// This will return `Ok(None)` if no block data is present in the database.
|
/// This will return `Ok(None)` if no blocks have been scanned.
|
||||||
fn get_max_height_hash(&self) -> Result<Option<(BlockHeight, BlockHash)>, Self::Error> {
|
fn get_max_height_hash(&self) -> Result<Option<(BlockHeight, BlockHash)>, Self::Error>;
|
||||||
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())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the block height in which the specified transaction was mined, or `Ok(None)` if the
|
/// Returns the block height in which the specified transaction was mined, or `Ok(None)` if the
|
||||||
/// transaction is not in the main chain.
|
/// transaction is not in the main chain.
|
||||||
|
@ -613,7 +586,7 @@ pub mod testing {
|
||||||
use incrementalmerkletree::Address;
|
use incrementalmerkletree::Address;
|
||||||
use secrecy::{ExposeSecret, SecretVec};
|
use secrecy::{ExposeSecret, SecretVec};
|
||||||
use shardtree::{error::ShardTreeError, store::memory::MemoryShardStore, ShardTree};
|
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::{
|
use zcash_primitives::{
|
||||||
block::BlockHash,
|
block::BlockHash,
|
||||||
|
@ -662,7 +635,14 @@ pub mod testing {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
type NoteRef = u32;
|
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)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -692,6 +672,10 @@ pub mod testing {
|
||||||
Ok(None)
|
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> {
|
fn get_tx_height(&self, _txid: TxId) -> Result<Option<BlockHeight>, Self::Error> {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,9 +66,7 @@ where
|
||||||
// for mempool transactions.
|
// for mempool transactions.
|
||||||
let height = data
|
let height = data
|
||||||
.get_tx_height(tx.txid())?
|
.get_tx_height(tx.txid())?
|
||||||
.or(data
|
.or(data.chain_height()?.map(|max_height| max_height + 1))
|
||||||
.block_height_extrema()?
|
|
||||||
.map(|(_, max_height)| max_height + 1))
|
|
||||||
.or_else(|| params.activation_height(NetworkUpgrade::Sapling))
|
.or_else(|| params.activation_height(NetworkUpgrade::Sapling))
|
||||||
.expect("Sapling activation height must be known.");
|
.expect("Sapling activation height must be known.");
|
||||||
|
|
||||||
|
|
|
@ -370,7 +370,7 @@ mod tests {
|
||||||
let (dfvk, _taddr) = init_test_accounts_table(&mut db_data);
|
let (dfvk, _taddr) = init_test_accounts_table(&mut db_data);
|
||||||
|
|
||||||
// Empty chain should return None
|
// 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
|
// Create a fake CompactBlock sending value to the address
|
||||||
let (cb, _) = fake_compact_block(
|
let (cb, _) = fake_compact_block(
|
||||||
|
|
|
@ -38,7 +38,10 @@ use maybe_rayon::{
|
||||||
};
|
};
|
||||||
use rusqlite::{self, Connection};
|
use rusqlite::{self, Connection};
|
||||||
use secrecy::{ExposeSecret, SecretVec};
|
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 incrementalmerkletree::Position;
|
||||||
use shardtree::{error::ShardTreeError, ShardTree};
|
use shardtree::{error::ShardTreeError, ShardTree};
|
||||||
|
@ -157,8 +160,10 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> WalletRead for W
|
||||||
type Error = SqliteClientError;
|
type Error = SqliteClientError;
|
||||||
type NoteRef = ReceivedNoteId;
|
type NoteRef = ReceivedNoteId;
|
||||||
|
|
||||||
fn block_height_extrema(&self) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error> {
|
fn chain_height(&self) -> Result<Option<BlockHeight>, Self::Error> {
|
||||||
wallet::block_height_extrema(self.conn.borrow()).map_err(SqliteClientError::from)
|
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> {
|
fn block_metadata(&self, height: BlockHeight) -> Result<Option<BlockMetadata>, Self::Error> {
|
||||||
|
@ -174,6 +179,14 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> WalletRead for W
|
||||||
.map_err(SqliteClientError::from)
|
.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> {
|
fn get_min_unspent_height(&self) -> Result<Option<BlockHeight>, Self::Error> {
|
||||||
wallet::get_min_unspent_height(self.conn.borrow()).map_err(SqliteClientError::from)
|
wallet::get_min_unspent_height(self.conn.borrow()).map_err(SqliteClientError::from)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
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> {
|
fn get_tx_height(&self, txid: TxId) -> Result<Option<BlockHeight>, Self::Error> {
|
||||||
wallet::get_tx_height(self.conn.borrow(), txid).map_err(SqliteClientError::from)
|
wallet::get_tx_height(self.conn.borrow(), txid).map_err(SqliteClientError::from)
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,9 +65,11 @@
|
||||||
//! - `memo` the shielded memo associated with the output, if any.
|
//! - `memo` the shielded memo associated with the output, if any.
|
||||||
|
|
||||||
use rusqlite::{self, named_params, OptionalExtension, ToSql};
|
use rusqlite::{self, named_params, OptionalExtension, ToSql};
|
||||||
|
use std::cmp;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::io::{self, Cursor};
|
use std::io::{self, Cursor};
|
||||||
|
use std::num::NonZeroU32;
|
||||||
use zcash_client_backend::data_api::scanning::{ScanPriority, ScanRange};
|
use zcash_client_backend::data_api::scanning::{ScanPriority, ScanRange};
|
||||||
|
|
||||||
use zcash_client_backend::data_api::{NoteId, ShieldedProtocol};
|
use zcash_client_backend::data_api::{NoteId, ShieldedProtocol};
|
||||||
|
@ -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(
|
fn parse_block_metadata(
|
||||||
row: (BlockHeight, Vec<u8>, Option<u32>, Vec<u8>),
|
row: (BlockHeight, Vec<u8>, Option<u32>, Vec<u8>),
|
||||||
) -> Result<BlockMetadata, SqliteClientError> {
|
) -> Result<BlockMetadata, SqliteClientError> {
|
||||||
|
@ -765,6 +806,21 @@ pub(crate) fn get_block_hash(
|
||||||
.optional()
|
.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
|
/// 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.
|
/// number of blocks greater than the pruning height is attempted.
|
||||||
pub(crate) fn get_min_unspent_height(
|
pub(crate) fn get_min_unspent_height(
|
||||||
|
@ -772,9 +828,9 @@ pub(crate) fn get_min_unspent_height(
|
||||||
) -> Result<Option<BlockHeight>, SqliteClientError> {
|
) -> Result<Option<BlockHeight>, SqliteClientError> {
|
||||||
conn.query_row(
|
conn.query_row(
|
||||||
"SELECT MIN(tx.block)
|
"SELECT MIN(tx.block)
|
||||||
FROM sapling_received_notes n
|
FROM sapling_received_notes n
|
||||||
JOIN transactions tx ON tx.id_tx = n.tx
|
JOIN transactions tx ON tx.id_tx = n.tx
|
||||||
WHERE n.spent IS NULL",
|
WHERE n.spent IS NULL",
|
||||||
[],
|
[],
|
||||||
|row| {
|
|row| {
|
||||||
row.get(0)
|
row.get(0)
|
||||||
|
|
|
@ -368,12 +368,14 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
block::BlockHash,
|
block::BlockHash,
|
||||||
consensus::{BlockHeight, BranchId},
|
consensus::BranchId,
|
||||||
legacy::TransparentAddress,
|
legacy::TransparentAddress,
|
||||||
memo::Memo,
|
memo::Memo,
|
||||||
sapling::{note_encryption::try_sapling_output_recovery, prover::TxProver},
|
sapling::{
|
||||||
|
note_encryption::try_sapling_output_recovery, prover::TxProver, Note, PaymentAddress,
|
||||||
|
},
|
||||||
transaction::{
|
transaction::{
|
||||||
components::Amount,
|
components::{amount::BalanceError, Amount},
|
||||||
fees::{fixed::FeeRule as FixedFeeRule, zip317::FeeRule as Zip317FeeRule},
|
fees::{fixed::FeeRule as FixedFeeRule, zip317::FeeRule as Zip317FeeRule},
|
||||||
Transaction,
|
Transaction,
|
||||||
},
|
},
|
||||||
|
@ -388,7 +390,8 @@ pub(crate) mod tests {
|
||||||
error::Error,
|
error::Error,
|
||||||
wallet::{
|
wallet::{
|
||||||
create_proposed_transaction, create_spend_to_address,
|
create_proposed_transaction, create_spend_to_address,
|
||||||
input_selection::GreedyInputSelector, propose_transfer, spend,
|
input_selection::{GreedyInputSelector, GreedyInputSelectorError},
|
||||||
|
propose_transfer, spend,
|
||||||
},
|
},
|
||||||
ShieldedProtocol, WalletRead, WalletWrite,
|
ShieldedProtocol, WalletRead, WalletWrite,
|
||||||
},
|
},
|
||||||
|
@ -401,15 +404,13 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
chain::init::init_cache_database,
|
chain::init::init_cache_database,
|
||||||
|
error::SqliteClientError,
|
||||||
tests::{
|
tests::{
|
||||||
self, fake_compact_block, insert_into_cache, network, sapling_activation_height,
|
self, fake_compact_block, insert_into_cache, network, sapling_activation_height,
|
||||||
AddressType,
|
AddressType,
|
||||||
},
|
},
|
||||||
wallet::{
|
wallet::{commitment_tree, get_balance, get_balance_at, init::init_wallet_db},
|
||||||
get_balance, get_balance_at,
|
AccountId, BlockDb, NoteId, ReceivedNoteId, WalletDb,
|
||||||
init::{init_blocks_table, init_wallet_db},
|
|
||||||
},
|
|
||||||
AccountId, BlockDb, NoteId, WalletDb,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
@ -651,6 +652,12 @@ pub(crate) mod tests {
|
||||||
let dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
let dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||||
let to = dfvk.default_address().1.into();
|
let to = dfvk.default_address().1.into();
|
||||||
|
|
||||||
|
// Account balance should be zero
|
||||||
|
assert_eq!(
|
||||||
|
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
||||||
|
Amount::zero()
|
||||||
|
);
|
||||||
|
|
||||||
// We cannot do anything if we aren't synchronised
|
// We cannot do anything if we aren't synchronised
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
create_spend_to_address(
|
create_spend_to_address(
|
||||||
|
@ -668,53 +675,6 @@ pub(crate) mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn create_to_address_fails_on_insufficient_balance() {
|
|
||||||
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, None).unwrap();
|
|
||||||
init_blocks_table(
|
|
||||||
&mut db_data,
|
|
||||||
BlockHeight::from(1u32),
|
|
||||||
BlockHash([1; 32]),
|
|
||||||
1,
|
|
||||||
&[0x0, 0x0, 0x0],
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Add an account to the wallet
|
|
||||||
let seed = Secret::new([0u8; 32].to_vec());
|
|
||||||
let (_, usk) = db_data.create_account(&seed).unwrap();
|
|
||||||
let dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
|
||||||
let to = dfvk.default_address().1.into();
|
|
||||||
|
|
||||||
// Account balance should be zero
|
|
||||||
assert_eq!(
|
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
|
||||||
Amount::zero()
|
|
||||||
);
|
|
||||||
|
|
||||||
// We cannot spend anything
|
|
||||||
assert_matches!(
|
|
||||||
create_spend_to_address(
|
|
||||||
&mut db_data,
|
|
||||||
&tests::network(),
|
|
||||||
test_prover(),
|
|
||||||
&usk,
|
|
||||||
&to,
|
|
||||||
Amount::from_u64(1).unwrap(),
|
|
||||||
None,
|
|
||||||
OvkPolicy::Sender,
|
|
||||||
NonZeroU32::new(1).unwrap(),
|
|
||||||
),
|
|
||||||
Err(data_api::error::Error::InsufficientFunds {
|
|
||||||
available,
|
|
||||||
required
|
|
||||||
})
|
|
||||||
if available == Amount::zero() && required == Amount::from_u64(10001).unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_to_address_fails_on_unverified_notes() {
|
fn create_to_address_fails_on_unverified_notes() {
|
||||||
let cache_file = NamedTempFile::new().unwrap();
|
let cache_file = NamedTempFile::new().unwrap();
|
||||||
|
@ -1101,7 +1061,19 @@ pub(crate) mod tests {
|
||||||
let addr2 = extsk2.default_address().1;
|
let addr2 = extsk2.default_address().1;
|
||||||
let to = addr2.into();
|
let to = addr2.into();
|
||||||
|
|
||||||
let send_and_recover_with_policy = |db_data: &mut WalletDb<Connection, _>, ovk_policy| {
|
#[allow(clippy::type_complexity)]
|
||||||
|
let send_and_recover_with_policy = |db_data: &mut WalletDb<Connection, _>,
|
||||||
|
ovk_policy|
|
||||||
|
-> Result<
|
||||||
|
Option<(Note, PaymentAddress, MemoBytes)>,
|
||||||
|
Error<
|
||||||
|
SqliteClientError,
|
||||||
|
commitment_tree::Error,
|
||||||
|
GreedyInputSelectorError<BalanceError, ReceivedNoteId>,
|
||||||
|
Infallible,
|
||||||
|
ReceivedNoteId,
|
||||||
|
>,
|
||||||
|
> {
|
||||||
let txid = create_spend_to_address(
|
let txid = create_spend_to_address(
|
||||||
db_data,
|
db_data,
|
||||||
&tests::network(),
|
&tests::network(),
|
||||||
|
@ -1112,8 +1084,7 @@ pub(crate) mod tests {
|
||||||
None,
|
None,
|
||||||
ovk_policy,
|
ovk_policy,
|
||||||
NonZeroU32::new(1).unwrap(),
|
NonZeroU32::new(1).unwrap(),
|
||||||
)
|
)?;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Fetch the transaction from the database
|
// Fetch the transaction from the database
|
||||||
let raw_tx: Vec<_> = db_data
|
let raw_tx: Vec<_> = db_data
|
||||||
|
@ -1137,18 +1108,19 @@ pub(crate) mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
if result.is_some() {
|
if result.is_some() {
|
||||||
return result;
|
return Ok(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
Ok(None)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Send some of the funds to another address, keeping history.
|
// Send some of the funds to another address, keeping history.
|
||||||
// The recipient output is decryptable by the sender.
|
// The recipient output is decryptable by the sender.
|
||||||
let (_, recovered_to, _) =
|
assert_matches!(
|
||||||
send_and_recover_with_policy(&mut db_data, OvkPolicy::Sender).unwrap();
|
send_and_recover_with_policy(&mut db_data, OvkPolicy::Sender),
|
||||||
assert_eq!(&recovered_to, &addr2);
|
Ok(Some((_, recovered_to, _))) if recovered_to == addr2
|
||||||
|
);
|
||||||
|
|
||||||
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 1 to 42 (that don't send us funds)
|
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 1 to 42 (that don't send us funds)
|
||||||
// so that the first transaction expires
|
// so that the first transaction expires
|
||||||
|
@ -1175,7 +1147,10 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
// Send the funds again, discarding history.
|
// Send the funds again, discarding history.
|
||||||
// Neither transaction output is decryptable by the sender.
|
// Neither transaction output is decryptable by the sender.
|
||||||
assert!(send_and_recover_with_policy(&mut db_data, OvkPolicy::Discard).is_none());
|
assert_matches!(
|
||||||
|
send_and_recover_with_policy(&mut db_data, OvkPolicy::Discard),
|
||||||
|
Ok(None)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -619,12 +619,12 @@ pub(crate) fn update_chain_tip<P: consensus::Parameters>(
|
||||||
params: &P,
|
params: &P,
|
||||||
new_tip: BlockHeight,
|
new_tip: BlockHeight,
|
||||||
) -> Result<(), SqliteClientError> {
|
) -> Result<(), SqliteClientError> {
|
||||||
// Read the previous tip height from the blocks table.
|
// Read the previous max scanned height from the blocks table
|
||||||
let prior_tip = block_height_extrema(conn)?.map(|(_, prior_tip)| prior_tip);
|
let prior_tip = block_height_extrema(conn)?.map(|(_, prior_tip)| prior_tip);
|
||||||
|
|
||||||
// If the chain tip is below the prior tip height, then the caller has caught the
|
// If the chain tip is below the prior max scanned height, then the caller has caught
|
||||||
// chain in the middle of a reorg. Do nothing; the caller will continue using the old
|
// the chain in the middle of a reorg. Do nothing; the caller will continue using the
|
||||||
// scan ranges and either:
|
// old scan ranges and either:
|
||||||
// - encounter an error trying to fetch the blocks (and thus trigger the same handling
|
// - encounter an error trying to fetch the blocks (and thus trigger the same handling
|
||||||
// logic as if this happened with the old linear scanning code); or
|
// logic as if this happened with the old linear scanning code); or
|
||||||
// - encounter a discontinuity error in `scan_cached_blocks`, at which point they will
|
// - encounter a discontinuity error in `scan_cached_blocks`, at which point they will
|
||||||
|
|
Loading…
Reference in New Issue