Merge remote-tracking branch 'upstream/master' into zip-tzes

This commit is contained in:
Kris Nuttycombe 2020-09-21 09:57:50 -06:00
commit ab2d5bf603
28 changed files with 927 additions and 488 deletions

View File

@ -1,5 +0,0 @@
//! Zcash global and per-network constants.
pub mod mainnet;
pub mod regtest;
pub mod testnet;

View File

@ -1,6 +1,6 @@
use group::cofactor::CofactorGroup; use group::cofactor::CofactorGroup;
use zcash_primitives::{ use zcash_primitives::{
consensus, consensus::{self, BlockHeight},
note_encryption::{try_sapling_note_decryption, try_sapling_output_recovery, Memo}, note_encryption::{try_sapling_note_decryption, try_sapling_output_recovery, Memo},
primitives::{Note, PaymentAddress}, primitives::{Note, PaymentAddress},
transaction::Transaction, transaction::Transaction,
@ -31,7 +31,8 @@ pub struct DecryptedOutput {
/// Scans a [`Transaction`] for any information that can be decrypted by the set of /// Scans a [`Transaction`] for any information that can be decrypted by the set of
/// [`ExtendedFullViewingKey`]s. /// [`ExtendedFullViewingKey`]s.
pub fn decrypt_transaction<P: consensus::Parameters>( pub fn decrypt_transaction<P: consensus::Parameters>(
height: u32, params: &P,
height: BlockHeight,
tx: &Transaction, tx: &Transaction,
extfvks: &[ExtendedFullViewingKey], extfvks: &[ExtendedFullViewingKey],
) -> Vec<DecryptedOutput> { ) -> Vec<DecryptedOutput> {
@ -51,7 +52,8 @@ pub fn decrypt_transaction<P: consensus::Parameters>(
let epk = epk.unwrap(); let epk = epk.unwrap();
for (account, (ivk, ovk)) in vks.iter().enumerate() { for (account, (ivk, ovk)) in vks.iter().enumerate() {
let ((note, to, memo), outgoing) = match try_sapling_note_decryption::<P>( let ((note, to, memo), outgoing) = match try_sapling_note_decryption(
params,
height, height,
ivk, ivk,
&epk, &epk,
@ -59,7 +61,8 @@ pub fn decrypt_transaction<P: consensus::Parameters>(
&output.enc_ciphertext, &output.enc_ciphertext,
) { ) {
Some(ret) => (ret, false), Some(ret) => (ret, false),
None => match try_sapling_output_recovery::<P>( None => match try_sapling_output_recovery(
params,
height, height,
ovk, ovk,
&output.cv, &output.cv,

View File

@ -1,9 +1,9 @@
//! Encoding and decoding functions for Zcash key and address structs. //! Encoding and decoding functions for Zcash key and address structs.
//! //!
//! Human-Readable Prefixes (HRPs) for Bech32 encodings are located in the [`constants`] //! Human-Readable Prefixes (HRPs) for Bech32 encodings are located in the [`zcash_primitives::constants`]
//! module. //! module.
//! //!
//! [`constants`]: crate::constants //! [`constants`]: zcash_primitives::constants
use bech32::{self, Error, FromBase32, ToBase32}; use bech32::{self, Error, FromBase32, ToBase32};
use bs58::{self, decode::Error as Bs58Error}; use bs58::{self, decode::Error as Bs58Error};
@ -41,8 +41,10 @@ where
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use zcash_client_backend::{ /// use zcash_primitives::{
/// constants::testnet::{COIN_TYPE, HRP_SAPLING_EXTENDED_SPENDING_KEY}, /// constants::testnet::{COIN_TYPE, HRP_SAPLING_EXTENDED_SPENDING_KEY},
/// };
/// use zcash_client_backend::{
/// encoding::encode_extended_spending_key, /// encoding::encode_extended_spending_key,
/// keys::spending_key, /// keys::spending_key,
/// }; /// };
@ -67,8 +69,10 @@ pub fn decode_extended_spending_key(
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use zcash_client_backend::{ /// use zcash_primitives::{
/// constants::testnet::{COIN_TYPE, HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY}, /// constants::testnet::{COIN_TYPE, HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY},
/// };
/// use zcash_client_backend::{
/// encoding::encode_extended_full_viewing_key, /// encoding::encode_extended_full_viewing_key,
/// keys::spending_key, /// keys::spending_key,
/// }; /// };
@ -100,10 +104,12 @@ pub fn decode_extended_full_viewing_key(
/// use rand_core::SeedableRng; /// use rand_core::SeedableRng;
/// use rand_xorshift::XorShiftRng; /// use rand_xorshift::XorShiftRng;
/// use zcash_client_backend::{ /// use zcash_client_backend::{
/// constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS,
/// encoding::encode_payment_address, /// encoding::encode_payment_address,
/// }; /// };
/// use zcash_primitives::primitives::{Diversifier, PaymentAddress}; /// use zcash_primitives::{
/// constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS,
/// primitives::{Diversifier, PaymentAddress},
/// };
/// ///
/// let rng = &mut XorShiftRng::from_seed([ /// let rng = &mut XorShiftRng::from_seed([
/// 0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, /// 0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06,
@ -135,10 +141,12 @@ pub fn encode_payment_address(hrp: &str, addr: &PaymentAddress) -> String {
/// use rand_core::SeedableRng; /// use rand_core::SeedableRng;
/// use rand_xorshift::XorShiftRng; /// use rand_xorshift::XorShiftRng;
/// use zcash_client_backend::{ /// use zcash_client_backend::{
/// constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS,
/// encoding::decode_payment_address, /// encoding::decode_payment_address,
/// }; /// };
/// use zcash_primitives::primitives::{Diversifier, PaymentAddress}; /// use zcash_primitives::{
/// constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS,
/// primitives::{Diversifier, PaymentAddress},
/// };
/// ///
/// let rng = &mut XorShiftRng::from_seed([ /// let rng = &mut XorShiftRng::from_seed([
/// 0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, /// 0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06,
@ -177,10 +185,12 @@ pub fn decode_payment_address(hrp: &str, s: &str) -> Result<Option<PaymentAddres
/// ///
/// ``` /// ```
/// use zcash_client_backend::{ /// use zcash_client_backend::{
/// constants::testnet::{B58_PUBKEY_ADDRESS_PREFIX, B58_SCRIPT_ADDRESS_PREFIX},
/// encoding::encode_transparent_address, /// encoding::encode_transparent_address,
/// }; /// };
/// use zcash_primitives::legacy::TransparentAddress; /// use zcash_primitives::{
/// constants::testnet::{B58_PUBKEY_ADDRESS_PREFIX, B58_SCRIPT_ADDRESS_PREFIX},
/// legacy::TransparentAddress,
/// };
/// ///
/// assert_eq!( /// assert_eq!(
/// encode_transparent_address( /// encode_transparent_address(
@ -227,8 +237,10 @@ pub fn encode_transparent_address(
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use zcash_client_backend::{ /// use zcash_primitives::{
/// constants::testnet::{B58_PUBKEY_ADDRESS_PREFIX, B58_SCRIPT_ADDRESS_PREFIX}, /// constants::testnet::{B58_PUBKEY_ADDRESS_PREFIX, B58_SCRIPT_ADDRESS_PREFIX},
/// };
/// use zcash_client_backend::{
/// encoding::decode_transparent_address, /// encoding::decode_transparent_address,
/// }; /// };
/// use zcash_primitives::legacy::TransparentAddress; /// use zcash_primitives::legacy::TransparentAddress;
@ -279,6 +291,7 @@ mod tests {
use rand_core::SeedableRng; use rand_core::SeedableRng;
use rand_xorshift::XorShiftRng; use rand_xorshift::XorShiftRng;
use zcash_primitives::{ use zcash_primitives::{
constants,
primitives::{Diversifier, PaymentAddress}, primitives::{Diversifier, PaymentAddress},
zip32::ExtendedSpendingKey, zip32::ExtendedSpendingKey,
}; };
@ -287,7 +300,6 @@ mod tests {
decode_extended_full_viewing_key, decode_extended_spending_key, decode_payment_address, decode_extended_full_viewing_key, decode_extended_spending_key, decode_payment_address,
encode_extended_full_viewing_key, encode_extended_spending_key, encode_payment_address, encode_extended_full_viewing_key, encode_extended_spending_key, encode_payment_address,
}; };
use crate::constants;
#[test] #[test]
fn extended_spending_key() { fn extended_spending_key() {

View File

@ -12,7 +12,8 @@ use zcash_primitives::zip32::{ChildIndex, ExtendedSpendingKey};
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use zcash_client_backend::{constants::testnet::COIN_TYPE, keys::spending_key}; /// use zcash_primitives::{constants::testnet::COIN_TYPE};
/// use zcash_client_backend::{keys::spending_key};
/// ///
/// let extsk = spending_key(&[0; 32][..], COIN_TYPE, 0); /// let extsk = spending_key(&[0; 32][..], COIN_TYPE, 0);
/// ``` /// ```

View File

@ -6,7 +6,6 @@
// Catch documentation errors caused by code changes. // Catch documentation errors caused by code changes.
#![deny(intra_doc_link_resolution_failure)] #![deny(intra_doc_link_resolution_failure)]
pub mod constants;
mod decrypt; mod decrypt;
pub mod encoding; pub mod encoding;
pub mod keys; pub mod keys;

View File

@ -3,7 +3,11 @@
use ff::PrimeField; use ff::PrimeField;
use group::GroupEncoding; use group::GroupEncoding;
use std::convert::TryInto; use std::convert::TryInto;
use zcash_primitives::block::{BlockHash, BlockHeader};
use zcash_primitives::{
block::{BlockHash, BlockHeader},
consensus::BlockHeight,
};
pub mod compact_formats; pub mod compact_formats;
@ -54,6 +58,15 @@ impl compact_formats::CompactBlock {
BlockHeader::read(&self.header[..]).ok() BlockHeader::read(&self.header[..]).ok()
} }
} }
/// Returns the [`BlockHeight`] for this block.
///
/// A convenience method that wraps [`CompactBlock.height`]
///
/// [`CompactBlock.height`]: #structfield.height
pub fn height(&self) -> BlockHeight {
BlockHeight::from(self.height)
}
} }
impl compact_formats::CompactOutput { impl compact_formats::CompactOutput {

View File

@ -4,7 +4,7 @@ use ff::PrimeField;
use std::collections::HashSet; use std::collections::HashSet;
use subtle::{ConditionallySelectable, ConstantTimeEq, CtOption}; use subtle::{ConditionallySelectable, ConstantTimeEq, CtOption};
use zcash_primitives::{ use zcash_primitives::{
consensus, consensus::{self, BlockHeight},
merkle_tree::{CommitmentTree, IncrementalWitness}, merkle_tree::{CommitmentTree, IncrementalWitness},
note_encryption::try_sapling_compact_note_decryption, note_encryption::try_sapling_compact_note_decryption,
sapling::Node, sapling::Node,
@ -23,7 +23,8 @@ use crate::wallet::{WalletShieldedOutput, WalletShieldedSpend, WalletTx};
/// The given [`CommitmentTree`] and existing [`IncrementalWitness`]es are incremented /// The given [`CommitmentTree`] and existing [`IncrementalWitness`]es are incremented
/// with this output's commitment. /// with this output's commitment.
fn scan_output<P: consensus::Parameters>( fn scan_output<P: consensus::Parameters>(
height: u32, params: &P,
height: BlockHeight,
(index, output): (usize, CompactOutput), (index, output): (usize, CompactOutput),
ivks: &[jubjub::Fr], ivks: &[jubjub::Fr],
spent_from_accounts: &HashSet<usize>, spent_from_accounts: &HashSet<usize>,
@ -51,7 +52,7 @@ fn scan_output<P: consensus::Parameters>(
for (account, ivk) in ivks.iter().enumerate() { for (account, ivk) in ivks.iter().enumerate() {
let (note, to) = let (note, to) =
match try_sapling_compact_note_decryption::<P>(height, ivk, &epk, &cmu, &ct) { match try_sapling_compact_note_decryption(params, height, ivk, &epk, &cmu, &ct) {
Some(ret) => ret, Some(ret) => ret,
None => continue, None => continue,
}; };
@ -86,6 +87,7 @@ fn scan_output<P: consensus::Parameters>(
/// The given [`CommitmentTree`] and existing [`IncrementalWitness`]es are /// The given [`CommitmentTree`] and existing [`IncrementalWitness`]es are
/// incremented appropriately. /// incremented appropriately.
pub fn scan_block<P: consensus::Parameters>( pub fn scan_block<P: consensus::Parameters>(
params: &P,
block: CompactBlock, block: CompactBlock,
extfvks: &[ExtendedFullViewingKey], extfvks: &[ExtendedFullViewingKey],
nullifiers: &[(&[u8], usize)], nullifiers: &[(&[u8], usize)],
@ -94,6 +96,7 @@ pub fn scan_block<P: consensus::Parameters>(
) -> Vec<WalletTx> { ) -> Vec<WalletTx> {
let mut wtxs: Vec<WalletTx> = vec![]; let mut wtxs: Vec<WalletTx> = vec![];
let ivks: Vec<_> = extfvks.iter().map(|extfvk| extfvk.fvk.vk.ivk()).collect(); let ivks: Vec<_> = extfvks.iter().map(|extfvk| extfvk.fvk.vk.ivk()).collect();
let block_height = block.height();
for tx in block.vtx.into_iter() { for tx in block.vtx.into_iter() {
let num_spends = tx.spends.len(); let num_spends = tx.spends.len();
@ -152,8 +155,9 @@ pub fn scan_block<P: consensus::Parameters>(
.map(|output| &mut output.witness) .map(|output| &mut output.witness)
.collect(); .collect();
if let Some(output) = scan_output::<P>( if let Some(output) = scan_output(
block.height as u32, params,
block_height,
to_scan, to_scan,
&ivks, &ivks,
&spent_from_accounts, &spent_from_accounts,
@ -190,7 +194,7 @@ mod tests {
use group::GroupEncoding; use group::GroupEncoding;
use rand_core::{OsRng, RngCore}; use rand_core::{OsRng, RngCore};
use zcash_primitives::{ use zcash_primitives::{
consensus::TestNetwork, consensus::{BlockHeight, Network},
constants::SPENDING_KEY_GENERATOR, constants::SPENDING_KEY_GENERATOR,
merkle_tree::CommitmentTree, merkle_tree::CommitmentTree,
note_encryption::{Memo, SaplingNoteEncryption}, note_encryption::{Memo, SaplingNoteEncryption},
@ -239,7 +243,7 @@ mod tests {
/// single spend of the given nullifier and a single output paying the given address. /// single spend of the given nullifier and a single output paying the given address.
/// Returns the CompactBlock. /// Returns the CompactBlock.
fn fake_compact_block( fn fake_compact_block(
height: i32, height: BlockHeight,
nf: [u8; 32], nf: [u8; 32],
extfvk: ExtendedFullViewingKey, extfvk: ExtendedFullViewingKey,
value: Amount, value: Amount,
@ -249,7 +253,7 @@ mod tests {
// Create a fake Note for the account // Create a fake Note for the account
let mut rng = OsRng; let mut rng = OsRng;
let rseed = generate_random_rseed::<TestNetwork, OsRng>(height as u32, &mut rng); let rseed = generate_random_rseed(&Network::TestNetwork, height, &mut rng);
let note = Note { let note = Note {
g_d: to.diversifier().g_d().unwrap(), g_d: to.diversifier().g_d().unwrap(),
pk_d: to.pk_d().clone(), pk_d: to.pk_d().clone(),
@ -269,7 +273,7 @@ mod tests {
// Create a fake CompactBlock containing the note // Create a fake CompactBlock containing the note
let mut cb = CompactBlock::new(); let mut cb = CompactBlock::new();
cb.set_height(height as u64); cb.set_height(height.into());
// Add a random Sapling tx before ours // Add a random Sapling tx before ours
{ {
@ -309,7 +313,7 @@ mod tests {
let extfvk = ExtendedFullViewingKey::from(&extsk); let extfvk = ExtendedFullViewingKey::from(&extsk);
let cb = fake_compact_block( let cb = fake_compact_block(
1, 1u32.into(),
[0; 32], [0; 32],
extfvk.clone(), extfvk.clone(),
Amount::from_u64(5).unwrap(), Amount::from_u64(5).unwrap(),
@ -318,7 +322,14 @@ mod tests {
assert_eq!(cb.vtx.len(), 2); assert_eq!(cb.vtx.len(), 2);
let mut tree = CommitmentTree::new(); let mut tree = CommitmentTree::new();
let txs = scan_block::<TestNetwork>(cb, &[extfvk], &[], &mut tree, &mut []); let txs = scan_block(
&Network::TestNetwork,
cb,
&[extfvk],
&[],
&mut tree,
&mut [],
);
assert_eq!(txs.len(), 1); assert_eq!(txs.len(), 1);
let tx = &txs[0]; let tx = &txs[0];
@ -341,7 +352,7 @@ mod tests {
let extfvk = ExtendedFullViewingKey::from(&extsk); let extfvk = ExtendedFullViewingKey::from(&extsk);
let cb = fake_compact_block( let cb = fake_compact_block(
1, 1u32.into(),
[0; 32], [0; 32],
extfvk.clone(), extfvk.clone(),
Amount::from_u64(5).unwrap(), Amount::from_u64(5).unwrap(),
@ -350,7 +361,14 @@ mod tests {
assert_eq!(cb.vtx.len(), 3); assert_eq!(cb.vtx.len(), 3);
let mut tree = CommitmentTree::new(); let mut tree = CommitmentTree::new();
let txs = scan_block::<TestNetwork>(cb, &[extfvk], &[], &mut tree, &mut []); let txs = scan_block(
&Network::TestNetwork,
cb,
&[extfvk],
&[],
&mut tree,
&mut [],
);
assert_eq!(txs.len(), 1); assert_eq!(txs.len(), 1);
let tx = &txs[0]; let tx = &txs[0];
@ -374,11 +392,18 @@ mod tests {
let nf = [7; 32]; let nf = [7; 32];
let account = 12; let account = 12;
let cb = fake_compact_block(1, nf, extfvk, Amount::from_u64(5).unwrap(), false); let cb = fake_compact_block(1u32.into(), nf, extfvk, Amount::from_u64(5).unwrap(), false);
assert_eq!(cb.vtx.len(), 2); assert_eq!(cb.vtx.len(), 2);
let mut tree = CommitmentTree::new(); let mut tree = CommitmentTree::new();
let txs = scan_block::<TestNetwork>(cb, &[], &[(&nf, account)], &mut tree, &mut []); let txs = scan_block(
&Network::TestNetwork,
cb,
&[],
&[(&nf, account)],
&mut tree,
&mut [],
);
assert_eq!(txs.len(), 1); assert_eq!(txs.len(), 1);
let tx = &txs[0]; let tx = &txs[0];

View File

@ -4,17 +4,7 @@ use zcash_client_backend::encoding::{
decode_payment_address, decode_transparent_address, encode_payment_address, decode_payment_address, decode_transparent_address, encode_payment_address,
encode_transparent_address, encode_transparent_address,
}; };
use zcash_primitives::{legacy::TransparentAddress, primitives::PaymentAddress}; use zcash_primitives::{consensus, legacy::TransparentAddress, primitives::PaymentAddress};
#[cfg(feature = "mainnet")]
use zcash_client_backend::constants::mainnet::{
B58_PUBKEY_ADDRESS_PREFIX, B58_SCRIPT_ADDRESS_PREFIX, HRP_SAPLING_PAYMENT_ADDRESS,
};
#[cfg(not(feature = "mainnet"))]
use zcash_client_backend::constants::testnet::{
B58_PUBKEY_ADDRESS_PREFIX, B58_SCRIPT_ADDRESS_PREFIX, HRP_SAPLING_PAYMENT_ADDRESS,
};
/// An address that funds can be sent to. /// An address that funds can be sent to.
pub enum RecipientAddress { pub enum RecipientAddress {
@ -35,26 +25,28 @@ impl From<TransparentAddress> for RecipientAddress {
} }
impl RecipientAddress { impl RecipientAddress {
pub fn from_str(s: &str) -> Option<Self> { pub fn decode<P: consensus::Parameters>(params: &P, s: &str) -> Option<Self> {
if let Ok(Some(pa)) = decode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, s) { if let Ok(Some(pa)) = decode_payment_address(params.hrp_sapling_payment_address(), s) {
Some(pa.into()) Some(pa.into())
} else if let Ok(Some(addr)) = } else if let Ok(Some(addr)) = decode_transparent_address(
decode_transparent_address(&B58_PUBKEY_ADDRESS_PREFIX, &B58_SCRIPT_ADDRESS_PREFIX, s) &params.b58_pubkey_address_prefix(),
{ &params.b58_script_address_prefix(),
s,
) {
Some(addr.into()) Some(addr.into())
} else { } else {
None None
} }
} }
pub fn to_string(&self) -> String { pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
match self { match self {
RecipientAddress::Shielded(pa) => { RecipientAddress::Shielded(pa) => {
encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, pa) encode_payment_address(params.hrp_sapling_payment_address(), pa)
} }
RecipientAddress::Transparent(addr) => encode_transparent_address( RecipientAddress::Transparent(addr) => encode_transparent_address(
&B58_PUBKEY_ADDRESS_PREFIX, &params.b58_pubkey_address_prefix(),
&B58_SCRIPT_ADDRESS_PREFIX, &params.b58_script_address_prefix(),
addr, addr,
), ),
} }

View File

@ -3,12 +3,18 @@
//! # Examples //! # Examples
//! //!
//! ``` //! ```
//! use rusqlite::Connection;
//! use zcash_primitives::{
//! consensus::{BlockHeight, Network, Parameters}
//! };
//!
//! use zcash_client_sqlite::{ //! use zcash_client_sqlite::{
//! chain::{rewind_to_height, validate_combined_chain}, //! chain::{rewind_to_height, validate_combined_chain},
//! error::ErrorKind, //! error::ErrorKind,
//! scan::scan_cached_blocks, //! scan::scan_cached_blocks,
//! }; //! };
//! //!
//! let network = Network::TestNetwork;
//! let db_cache = "/path/to/cache.db"; //! let db_cache = "/path/to/cache.db";
//! let db_data = "/path/to/data.db"; //! let db_data = "/path/to/data.db";
//! //!
@ -18,7 +24,7 @@
//! // //! //
//! // Given that we assume the server always gives us correct-at-the-time blocks, any //! // Given that we assume the server always gives us correct-at-the-time blocks, any
//! // errors are in the blocks we have previously cached or scanned. //! // errors are in the blocks we have previously cached or scanned.
//! if let Err(e) = validate_combined_chain(&db_cache, &db_data) { //! if let Err(e) = validate_combined_chain(network, &db_cache, &db_data) {
//! match e.kind() { //! match e.kind() {
//! ErrorKind::InvalidChain(upper_bound, _) => { //! ErrorKind::InvalidChain(upper_bound, _) => {
//! // a) Pick a height to rewind to. //! // a) Pick a height to rewind to.
@ -26,10 +32,10 @@
//! // This might be informed by some external chain reorg information, or //! // This might be informed by some external chain reorg information, or
//! // heuristics such as the platform, available bandwidth, size of recent //! // heuristics such as the platform, available bandwidth, size of recent
//! // CompactBlocks, etc. //! // CompactBlocks, etc.
//! let rewind_height = upper_bound - 10; //! let rewind_height = *upper_bound - 10;
//! //!
//! // b) Rewind scanned block information. //! // b) Rewind scanned block information.
//! rewind_to_height(&db_data, rewind_height); //! rewind_to_height(network, &db_data, rewind_height);
//! //!
//! // c) Delete cached blocks from rewind_height onwards. //! // c) Delete cached blocks from rewind_height onwards.
//! // //! //
@ -52,28 +58,28 @@
//! // At this point, the cache and scanned data are locally consistent (though not //! // At this point, the cache and scanned data are locally consistent (though not
//! // necessarily consistent with the latest chain tip - this would be discovered the //! // necessarily consistent with the latest chain tip - this would be discovered the
//! // next time this codepath is executed after new blocks are received). //! // next time this codepath is executed after new blocks are received).
//! scan_cached_blocks(&db_cache, &db_data, None); //! scan_cached_blocks(&network, &db_cache, &db_data, None);
//! ``` //! ```
use protobuf::parse_from_bytes; use protobuf::parse_from_bytes;
use rusqlite::{Connection, NO_PARAMS}; use rusqlite::{Connection, NO_PARAMS};
use std::path::Path; use std::path::Path;
use zcash_primitives::consensus::{self, BlockHeight, NetworkUpgrade};
use zcash_client_backend::proto::compact_formats::CompactBlock; use zcash_client_backend::proto::compact_formats::CompactBlock;
use crate::{ use crate::error::{Error, ErrorKind};
error::{Error, ErrorKind},
SAPLING_ACTIVATION_HEIGHT,
};
#[derive(Debug)] #[derive(Debug)]
pub enum ChainInvalidCause { pub enum ChainInvalidCause {
PrevHashMismatch, PrevHashMismatch,
/// (expected_height, actual_height) /// (expected_height, actual_height)
HeightMismatch(i32, i32), HeightMismatch(BlockHeight, BlockHeight),
} }
struct CompactBlockRow { struct CompactBlockRow {
height: i32, height: BlockHeight,
data: Vec<u8>, data: Vec<u8>,
} }
@ -93,28 +99,32 @@ struct CompactBlockRow {
/// - `Err(e)` if there was an error during validation unrelated to chain validity. /// - `Err(e)` if there was an error during validation unrelated to chain validity.
/// ///
/// This function does not mutate either of the databases. /// This function does not mutate either of the databases.
pub fn validate_combined_chain<P: AsRef<Path>, Q: AsRef<Path>>( pub fn validate_combined_chain<Params: consensus::Parameters, P: AsRef<Path>, Q: AsRef<Path>>(
parameters: Params,
db_cache: P, db_cache: P,
db_data: Q, db_data: Q,
) -> Result<(), Error> { ) -> Result<(), Error> {
let cache = Connection::open(db_cache)?; let cache = Connection::open(db_cache)?;
let data = Connection::open(db_data)?; let data = Connection::open(db_data)?;
let sapling_activation_height = parameters
.activation_height(NetworkUpgrade::Sapling)
.ok_or(Error(ErrorKind::SaplingNotActive))?;
// Recall where we synced up to previously. // Recall where we synced up to previously.
// If we have never synced, use Sapling activation height to select all cached CompactBlocks. // If we have never synced, use Sapling activation height to select all cached CompactBlocks.
let (have_scanned, last_scanned_height) = let (have_scanned, last_scanned_height) =
data.query_row("SELECT MAX(height) FROM blocks", NO_PARAMS, |row| { data.query_row("SELECT MAX(height) FROM blocks", NO_PARAMS, |row| {
row.get(0) row.get(0)
.map(|h| (true, h)) .map(|h: u32| (true, h.into()))
.or(Ok((false, SAPLING_ACTIVATION_HEIGHT - 1))) .or(Ok((false, sapling_activation_height - 1)))
})?; })?;
// Fetch the CompactBlocks we need to validate // Fetch the CompactBlocks we need to validate
let mut stmt_blocks = cache let mut stmt_blocks = cache
.prepare("SELECT height, data FROM compactblocks WHERE height > ? ORDER BY height DESC")?; .prepare("SELECT height, data FROM compactblocks WHERE height > ? ORDER BY height DESC")?;
let mut rows = stmt_blocks.query_map(&[last_scanned_height], |row| { let mut rows = stmt_blocks.query_map(&[u32::from(last_scanned_height)], |row| {
Ok(CompactBlockRow { Ok(CompactBlockRow {
height: row.get(0)?, height: row.get(0).map(u32::into)?,
data: row.get(1)?, data: row.get(1)?,
}) })
})?; })?;
@ -126,13 +136,12 @@ pub fn validate_combined_chain<P: AsRef<Path>, Q: AsRef<Path>>(
None => { None => {
// No cached blocks, and we've already validated the blocks we've scanned, // No cached blocks, and we've already validated the blocks we've scanned,
// so there's nothing to validate. // so there's nothing to validate.
// TODO: Maybe we still want to check if there are cached blocks that are // TODO: Maybe we still want to check if there are cached blocks that are at heights we previously scanned? Check scanning flow again.
// at heights we previously scanned? Check scanning flow again.
return Ok(()); return Ok(());
} }
}; };
let block: CompactBlock = parse_from_bytes(&assumed_correct.data)?; let block: CompactBlock = parse_from_bytes(&assumed_correct.data)?;
(block.height as i32, block.prev_hash()) (block.height(), block.prev_hash())
}; };
for row in rows { for row in rows {
@ -163,7 +172,7 @@ pub fn validate_combined_chain<P: AsRef<Path>, Q: AsRef<Path>>(
// Cached blocks MUST hash-chain to the last scanned block. // Cached blocks MUST hash-chain to the last scanned block.
let last_scanned_hash = data.query_row( let last_scanned_hash = data.query_row(
"SELECT hash FROM blocks WHERE height = ?", "SELECT hash FROM blocks WHERE height = ?",
&[last_scanned_height], &[u32::from(last_scanned_height)],
|row| row.get::<_, Vec<_>>(0), |row| row.get::<_, Vec<_>>(0),
)?; )?;
if &last_scanned_hash[..] != &last_prev_hash.0[..] { if &last_scanned_hash[..] != &last_prev_hash.0[..] {
@ -182,14 +191,23 @@ pub fn validate_combined_chain<P: AsRef<Path>, Q: AsRef<Path>>(
/// ///
/// If the requested height is greater than or equal to the height of the last scanned /// If the requested height is greater than or equal to the height of the last scanned
/// block, this function does nothing. /// block, this function does nothing.
pub fn rewind_to_height<P: AsRef<Path>>(db_data: P, height: i32) -> Result<(), Error> { pub fn rewind_to_height<Params: consensus::Parameters, P: AsRef<Path>>(
parameters: Params,
db_data: P,
height: BlockHeight,
) -> Result<(), Error> {
let data = Connection::open(db_data)?; let data = Connection::open(db_data)?;
let sapling_activation_height = parameters
.activation_height(NetworkUpgrade::Sapling)
.ok_or(Error(ErrorKind::SaplingNotActive))?;
// Recall where we synced up to previously. // Recall where we synced up to previously.
// If we have never synced, use Sapling activation height. // If we have never synced, use Sapling activation height.
let last_scanned_height = let last_scanned_height =
data.query_row("SELECT MAX(height) FROM blocks", NO_PARAMS, |row| { data.query_row("SELECT MAX(height) FROM blocks", NO_PARAMS, |row| {
row.get(0).or(Ok(SAPLING_ACTIVATION_HEIGHT - 1)) row.get(0)
.map(u32::into)
.or(Ok(sapling_activation_height - 1))
})?; })?;
if height >= last_scanned_height { if height >= last_scanned_height {
@ -201,16 +219,19 @@ pub fn rewind_to_height<P: AsRef<Path>>(db_data: P, height: i32) -> Result<(), E
data.execute("BEGIN IMMEDIATE", NO_PARAMS)?; data.execute("BEGIN IMMEDIATE", NO_PARAMS)?;
// Decrement witnesses. // Decrement witnesses.
data.execute("DELETE FROM sapling_witnesses WHERE block > ?", &[height])?; data.execute(
"DELETE FROM sapling_witnesses WHERE block > ?",
&[u32::from(height)],
)?;
// Un-mine transactions. // Un-mine transactions.
data.execute( data.execute(
"UPDATE transactions SET block = NULL, tx_index = NULL WHERE block > ?", "UPDATE transactions SET block = NULL, tx_index = NULL WHERE block > ?",
&[height], &[u32::from(height)],
)?; )?;
// Now that they aren't depended on, delete scanned blocks. // Now that they aren't depended on, delete scanned blocks.
data.execute("DELETE FROM blocks WHERE height > ?", &[height])?; data.execute("DELETE FROM blocks WHERE height > ?", &[u32::from(height)])?;
// Commit the SQL transaction, rewinding atomically. // Commit the SQL transaction, rewinding atomically.
data.execute("COMMIT", NO_PARAMS)?; data.execute("COMMIT", NO_PARAMS)?;
@ -228,13 +249,13 @@ mod tests {
}; };
use super::{rewind_to_height, validate_combined_chain}; use super::{rewind_to_height, validate_combined_chain};
use crate::{ use crate::{
error::ErrorKind, error::ErrorKind,
init::{init_accounts_table, init_cache_database, init_data_database}, init::{init_accounts_table, init_cache_database, init_data_database},
query::get_balance, query::get_balance,
scan::scan_cached_blocks, scan::scan_cached_blocks,
tests::{fake_compact_block, insert_into_cache}, tests::{self, fake_compact_block, insert_into_cache, sapling_activation_height},
SAPLING_ACTIVATION_HEIGHT,
}; };
#[test] #[test]
@ -250,14 +271,14 @@ mod tests {
// Add an account to the wallet // Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]); let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk); let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap(); init_accounts_table(&db_data, &tests::network(), &[extfvk.clone()]).unwrap();
// Empty chain should be valid // Empty chain should be valid
validate_combined_chain(db_cache, db_data).unwrap(); validate_combined_chain(tests::network(), db_cache, db_data).unwrap();
// 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(
SAPLING_ACTIVATION_HEIGHT, sapling_activation_height(),
BlockHash([0; 32]), BlockHash([0; 32]),
extfvk.clone(), extfvk.clone(),
Amount::from_u64(5).unwrap(), Amount::from_u64(5).unwrap(),
@ -265,17 +286,17 @@ mod tests {
insert_into_cache(db_cache, &cb); insert_into_cache(db_cache, &cb);
// Cache-only chain should be valid // Cache-only chain should be valid
validate_combined_chain(db_cache, db_data).unwrap(); validate_combined_chain(tests::network(), db_cache, db_data).unwrap();
// Scan the cache // Scan the cache
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&tests::network(), db_cache, db_data, None).unwrap();
// Data-only chain should be valid // Data-only chain should be valid
validate_combined_chain(db_cache, db_data).unwrap(); validate_combined_chain(tests::network(), db_cache, db_data).unwrap();
// Create a second fake CompactBlock sending more value to the address // Create a second fake CompactBlock sending more value to the address
let (cb2, _) = fake_compact_block( let (cb2, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT + 1, sapling_activation_height() + 1,
cb.hash(), cb.hash(),
extfvk, extfvk,
Amount::from_u64(7).unwrap(), Amount::from_u64(7).unwrap(),
@ -283,13 +304,13 @@ mod tests {
insert_into_cache(db_cache, &cb2); insert_into_cache(db_cache, &cb2);
// Data+cache chain should be valid // Data+cache chain should be valid
validate_combined_chain(db_cache, db_data).unwrap(); validate_combined_chain(tests::network(), db_cache, db_data).unwrap();
// Scan the cache again // Scan the cache again
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&tests::network(), db_cache, db_data, None).unwrap();
// Data-only chain should be valid // Data-only chain should be valid
validate_combined_chain(db_cache, db_data).unwrap(); validate_combined_chain(tests::network(), db_cache, db_data).unwrap();
} }
#[test] #[test]
@ -305,17 +326,17 @@ mod tests {
// Add an account to the wallet // Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]); let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk); let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap(); init_accounts_table(&db_data, &tests::network(), &[extfvk.clone()]).unwrap();
// Create some fake CompactBlocks // Create some fake CompactBlocks
let (cb, _) = fake_compact_block( let (cb, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT, sapling_activation_height(),
BlockHash([0; 32]), BlockHash([0; 32]),
extfvk.clone(), extfvk.clone(),
Amount::from_u64(5).unwrap(), Amount::from_u64(5).unwrap(),
); );
let (cb2, _) = fake_compact_block( let (cb2, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT + 1, sapling_activation_height() + 1,
cb.hash(), cb.hash(),
extfvk.clone(), extfvk.clone(),
Amount::from_u64(7).unwrap(), Amount::from_u64(7).unwrap(),
@ -324,20 +345,20 @@ mod tests {
insert_into_cache(db_cache, &cb2); insert_into_cache(db_cache, &cb2);
// Scan the cache // Scan the cache
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&tests::network(), db_cache, db_data, None).unwrap();
// Data-only chain should be valid // Data-only chain should be valid
validate_combined_chain(db_cache, db_data).unwrap(); validate_combined_chain(tests::network(), db_cache, db_data).unwrap();
// Create more fake CompactBlocks that don't connect to the scanned ones // Create more fake CompactBlocks that don't connect to the scanned ones
let (cb3, _) = fake_compact_block( let (cb3, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT + 2, sapling_activation_height() + 2,
BlockHash([1; 32]), BlockHash([1; 32]),
extfvk.clone(), extfvk.clone(),
Amount::from_u64(8).unwrap(), Amount::from_u64(8).unwrap(),
); );
let (cb4, _) = fake_compact_block( let (cb4, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT + 3, sapling_activation_height() + 3,
cb3.hash(), cb3.hash(),
extfvk.clone(), extfvk.clone(),
Amount::from_u64(3).unwrap(), Amount::from_u64(3).unwrap(),
@ -346,10 +367,10 @@ mod tests {
insert_into_cache(db_cache, &cb4); insert_into_cache(db_cache, &cb4);
// Data+cache chain should be invalid at the data/cache boundary // Data+cache chain should be invalid at the data/cache boundary
match validate_combined_chain(db_cache, db_data) { match validate_combined_chain(tests::network(), db_cache, db_data) {
Err(e) => match e.kind() { Err(e) => match e.kind() {
ErrorKind::InvalidChain(upper_bound, _) => { ErrorKind::InvalidChain(upper_bound, _) => {
assert_eq!(*upper_bound, SAPLING_ACTIVATION_HEIGHT + 1) assert_eq!(*upper_bound, sapling_activation_height() + 1)
} }
_ => panic!(), _ => panic!(),
}, },
@ -370,17 +391,17 @@ mod tests {
// Add an account to the wallet // Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]); let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk); let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap(); init_accounts_table(&db_data, &tests::network(), &[extfvk.clone()]).unwrap();
// Create some fake CompactBlocks // Create some fake CompactBlocks
let (cb, _) = fake_compact_block( let (cb, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT, sapling_activation_height(),
BlockHash([0; 32]), BlockHash([0; 32]),
extfvk.clone(), extfvk.clone(),
Amount::from_u64(5).unwrap(), Amount::from_u64(5).unwrap(),
); );
let (cb2, _) = fake_compact_block( let (cb2, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT + 1, sapling_activation_height() + 1,
cb.hash(), cb.hash(),
extfvk.clone(), extfvk.clone(),
Amount::from_u64(7).unwrap(), Amount::from_u64(7).unwrap(),
@ -389,20 +410,20 @@ mod tests {
insert_into_cache(db_cache, &cb2); insert_into_cache(db_cache, &cb2);
// Scan the cache // Scan the cache
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&tests::network(), db_cache, db_data, None).unwrap();
// Data-only chain should be valid // Data-only chain should be valid
validate_combined_chain(db_cache, db_data).unwrap(); validate_combined_chain(tests::network(), db_cache, db_data).unwrap();
// Create more fake CompactBlocks that contain a reorg // Create more fake CompactBlocks that contain a reorg
let (cb3, _) = fake_compact_block( let (cb3, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT + 2, sapling_activation_height() + 2,
cb2.hash(), cb2.hash(),
extfvk.clone(), extfvk.clone(),
Amount::from_u64(8).unwrap(), Amount::from_u64(8).unwrap(),
); );
let (cb4, _) = fake_compact_block( let (cb4, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT + 3, sapling_activation_height() + 3,
BlockHash([1; 32]), BlockHash([1; 32]),
extfvk.clone(), extfvk.clone(),
Amount::from_u64(3).unwrap(), Amount::from_u64(3).unwrap(),
@ -411,10 +432,10 @@ mod tests {
insert_into_cache(db_cache, &cb4); insert_into_cache(db_cache, &cb4);
// Data+cache chain should be invalid inside the cache // Data+cache chain should be invalid inside the cache
match validate_combined_chain(db_cache, db_data) { match validate_combined_chain(tests::network(), db_cache, db_data) {
Err(e) => match e.kind() { Err(e) => match e.kind() {
ErrorKind::InvalidChain(upper_bound, _) => { ErrorKind::InvalidChain(upper_bound, _) => {
assert_eq!(*upper_bound, SAPLING_ACTIVATION_HEIGHT + 2) assert_eq!(*upper_bound, sapling_activation_height() + 2)
} }
_ => panic!(), _ => panic!(),
}, },
@ -435,7 +456,7 @@ mod tests {
// Add an account to the wallet // Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]); let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk); let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap(); init_accounts_table(&db_data, &tests::network(), &[extfvk.clone()]).unwrap();
// Account balance should be zero // Account balance should be zero
assert_eq!(get_balance(db_data, 0).unwrap(), Amount::zero()); assert_eq!(get_balance(db_data, 0).unwrap(), Amount::zero());
@ -444,35 +465,36 @@ mod tests {
let value = Amount::from_u64(5).unwrap(); let value = Amount::from_u64(5).unwrap();
let value2 = Amount::from_u64(7).unwrap(); let value2 = Amount::from_u64(7).unwrap();
let (cb, _) = fake_compact_block( let (cb, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT, sapling_activation_height(),
BlockHash([0; 32]), BlockHash([0; 32]),
extfvk.clone(), extfvk.clone(),
value, value,
); );
let (cb2, _) = fake_compact_block(SAPLING_ACTIVATION_HEIGHT + 1, cb.hash(), extfvk, value2); let (cb2, _) =
fake_compact_block(sapling_activation_height() + 1, cb.hash(), extfvk, value2);
insert_into_cache(db_cache, &cb); insert_into_cache(db_cache, &cb);
insert_into_cache(db_cache, &cb2); insert_into_cache(db_cache, &cb2);
// Scan the cache // Scan the cache
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&tests::network(), db_cache, db_data, None).unwrap();
// Account balance should reflect both received notes // Account balance should reflect both received notes
assert_eq!(get_balance(db_data, 0).unwrap(), value + value2); assert_eq!(get_balance(db_data, 0).unwrap(), value + value2);
// "Rewind" to height of last scanned block // "Rewind" to height of last scanned block
rewind_to_height(db_data, SAPLING_ACTIVATION_HEIGHT + 1).unwrap(); rewind_to_height(tests::network(), db_data, sapling_activation_height() + 1).unwrap();
// Account balance should be unaltered // Account balance should be unaltered
assert_eq!(get_balance(db_data, 0).unwrap(), value + value2); assert_eq!(get_balance(db_data, 0).unwrap(), value + value2);
// Rewind so that one block is dropped // Rewind so that one block is dropped
rewind_to_height(db_data, SAPLING_ACTIVATION_HEIGHT).unwrap(); rewind_to_height(tests::network(), db_data, sapling_activation_height()).unwrap();
// Account balance should only contain the first received note // Account balance should only contain the first received note
assert_eq!(get_balance(db_data, 0).unwrap(), value); assert_eq!(get_balance(db_data, 0).unwrap(), value);
// Scan the cache again // Scan the cache again
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&tests::network(), db_cache, db_data, None).unwrap();
// Account balance should again reflect both received notes // Account balance should again reflect both received notes
assert_eq!(get_balance(db_data, 0).unwrap(), value + value2); assert_eq!(get_balance(db_data, 0).unwrap(), value + value2);

View File

@ -1,6 +1,7 @@
use std::error; use std::error;
use std::fmt; use std::fmt;
use zcash_primitives::{ use zcash_primitives::{
consensus::BlockHeight,
sapling::Node, sapling::Node,
transaction::{builder, TxId}, transaction::{builder, TxId},
}; };
@ -10,13 +11,13 @@ pub enum ErrorKind {
CorruptedData(&'static str), CorruptedData(&'static str),
IncorrectHRPExtFVK, IncorrectHRPExtFVK,
InsufficientBalance(u64, u64), InsufficientBalance(u64, u64),
InvalidChain(i32, crate::chain::ChainInvalidCause), InvalidChain(BlockHeight, crate::chain::ChainInvalidCause),
InvalidExtSK(u32), InvalidExtSK(u32),
InvalidHeight(i32, i32), InvalidHeight(BlockHeight, BlockHeight),
InvalidMemo(std::str::Utf8Error), InvalidMemo(std::str::Utf8Error),
InvalidNewWitnessAnchor(usize, TxId, i32, Node), InvalidNewWitnessAnchor(usize, TxId, BlockHeight, Node),
InvalidNote, InvalidNote,
InvalidWitnessAnchor(i64, i32), InvalidWitnessAnchor(i64, BlockHeight),
ScanRequired, ScanRequired,
TableNotEmpty, TableNotEmpty,
Bech32(bech32::Error), Bech32(bech32::Error),
@ -25,6 +26,7 @@ pub enum ErrorKind {
Database(rusqlite::Error), Database(rusqlite::Error),
Io(std::io::Error), Io(std::io::Error),
Protobuf(protobuf::ProtobufError), Protobuf(protobuf::ProtobufError),
SaplingNotActive,
} }
#[derive(Debug)] #[derive(Debug)]
@ -71,6 +73,7 @@ impl fmt::Display for Error {
ErrorKind::Database(e) => write!(f, "{}", e), ErrorKind::Database(e) => write!(f, "{}", e),
ErrorKind::Io(e) => write!(f, "{}", e), ErrorKind::Io(e) => write!(f, "{}", e),
ErrorKind::Protobuf(e) => write!(f, "{}", e), ErrorKind::Protobuf(e) => write!(f, "{}", e),
ErrorKind::SaplingNotActive => write!(f, "Sapling activation height not specified for network."),
} }
} }
} }

View File

@ -3,12 +3,12 @@
use rusqlite::{types::ToSql, Connection, NO_PARAMS}; use rusqlite::{types::ToSql, Connection, NO_PARAMS};
use std::path::Path; use std::path::Path;
use zcash_client_backend::encoding::encode_extended_full_viewing_key; use zcash_client_backend::encoding::encode_extended_full_viewing_key;
use zcash_primitives::{block::BlockHash, zip32::ExtendedFullViewingKey};
use zcash_primitives::{block::BlockHash, consensus, zip32::ExtendedFullViewingKey};
use crate::{ use crate::{
address_from_extfvk, address_from_extfvk,
error::{Error, ErrorKind}, error::{Error, ErrorKind},
HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
}; };
/// Sets up the internal structure of the cache database. /// Sets up the internal structure of the cache database.
@ -141,7 +141,10 @@ pub fn init_data_database<P: AsRef<Path>>(db_data: P) -> Result<(), Error> {
/// ``` /// ```
/// use tempfile::NamedTempFile; /// use tempfile::NamedTempFile;
/// use zcash_client_sqlite::init::{init_accounts_table, init_data_database}; /// use zcash_client_sqlite::init::{init_accounts_table, init_data_database};
/// use zcash_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}; /// use zcash_primitives::{
/// consensus::Network,
/// zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}
/// };
/// ///
/// let data_file = NamedTempFile::new().unwrap(); /// let data_file = NamedTempFile::new().unwrap();
/// let db_data = data_file.path(); /// let db_data = data_file.path();
@ -149,14 +152,15 @@ pub fn init_data_database<P: AsRef<Path>>(db_data: P) -> Result<(), Error> {
/// ///
/// let extsk = ExtendedSpendingKey::master(&[]); /// let extsk = ExtendedSpendingKey::master(&[]);
/// let extfvks = [ExtendedFullViewingKey::from(&extsk)]; /// let extfvks = [ExtendedFullViewingKey::from(&extsk)];
/// init_accounts_table(&db_data, &extfvks).unwrap(); /// init_accounts_table(&db_data, &Network::TestNetwork, &extfvks).unwrap();
/// ``` /// ```
/// ///
/// [`get_address`]: crate::query::get_address /// [`get_address`]: crate::query::get_address
/// [`scan_cached_blocks`]: crate::scan::scan_cached_blocks /// [`scan_cached_blocks`]: crate::scan::scan_cached_blocks
/// [`create_to_address`]: crate::transact::create_to_address /// [`create_to_address`]: crate::transact::create_to_address
pub fn init_accounts_table<P: AsRef<Path>>( pub fn init_accounts_table<D: AsRef<Path>, P: consensus::Parameters>(
db_data: P, db_data: D,
params: &P,
extfvks: &[ExtendedFullViewingKey], extfvks: &[ExtendedFullViewingKey],
) -> Result<(), Error> { ) -> Result<(), Error> {
let data = Connection::open(db_data)?; let data = Connection::open(db_data)?;
@ -169,9 +173,11 @@ pub fn init_accounts_table<P: AsRef<Path>>(
// Insert accounts atomically // Insert accounts atomically
data.execute("BEGIN IMMEDIATE", NO_PARAMS)?; data.execute("BEGIN IMMEDIATE", NO_PARAMS)?;
for (account, extfvk) in extfvks.iter().enumerate() { for (account, extfvk) in extfvks.iter().enumerate() {
let address = address_from_extfvk(extfvk); let address = address_from_extfvk(params, extfvk);
let extfvk = let extfvk = encode_extended_full_viewing_key(
encode_extended_full_viewing_key(HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, extfvk); params.hrp_sapling_extended_full_viewing_key(),
extfvk,
);
data.execute( data.execute(
"INSERT INTO accounts (account, extfvk, address) "INSERT INTO accounts (account, extfvk, address)
VALUES (?, ?, ?)", VALUES (?, ?, ?)",
@ -244,11 +250,12 @@ mod tests {
use zcash_client_backend::encoding::decode_payment_address; use zcash_client_backend::encoding::decode_payment_address;
use zcash_primitives::{ use zcash_primitives::{
block::BlockHash, block::BlockHash,
consensus::Parameters,
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
}; };
use super::{init_accounts_table, init_blocks_table, init_data_database}; use super::{init_accounts_table, init_blocks_table, init_data_database};
use crate::{query::get_address, HRP_SAPLING_PAYMENT_ADDRESS}; use crate::{query::get_address, tests};
#[test] #[test]
fn init_accounts_table_only_works_once() { fn init_accounts_table_only_works_once() {
@ -257,18 +264,18 @@ mod tests {
init_data_database(&db_data).unwrap(); init_data_database(&db_data).unwrap();
// We can call the function as many times as we want with no data // We can call the function as many times as we want with no data
init_accounts_table(&db_data, &[]).unwrap(); init_accounts_table(&db_data, &tests::network(), &[]).unwrap();
init_accounts_table(&db_data, &[]).unwrap(); init_accounts_table(&db_data, &tests::network(), &[]).unwrap();
// First call with data should initialise the accounts table // First call with data should initialise the accounts table
let extfvks = [ExtendedFullViewingKey::from(&ExtendedSpendingKey::master( let extfvks = [ExtendedFullViewingKey::from(&ExtendedSpendingKey::master(
&[], &[],
))]; ))];
init_accounts_table(&db_data, &extfvks).unwrap(); init_accounts_table(&db_data, &tests::network(), &extfvks).unwrap();
// Subsequent calls should return an error // Subsequent calls should return an error
init_accounts_table(&db_data, &[]).unwrap_err(); init_accounts_table(&db_data, &tests::network(), &[]).unwrap_err();
init_accounts_table(&db_data, &extfvks).unwrap_err(); init_accounts_table(&db_data, &tests::network(), &extfvks).unwrap_err();
} }
#[test] #[test]
@ -293,11 +300,12 @@ mod tests {
// Add an account to the wallet // Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]); let extsk = ExtendedSpendingKey::master(&[]);
let extfvks = [ExtendedFullViewingKey::from(&extsk)]; let extfvks = [ExtendedFullViewingKey::from(&extsk)];
init_accounts_table(&db_data, &extfvks).unwrap(); init_accounts_table(&db_data, &tests::network(), &extfvks).unwrap();
// The account's address should be in the data DB // The account's address should be in the data DB
let addr = get_address(&db_data, 0).unwrap(); let addr = get_address(&db_data, 0).unwrap();
let pa = decode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, &addr).unwrap(); let pa =
decode_payment_address(tests::network().hrp_sapling_payment_address(), &addr).unwrap();
assert_eq!(pa.unwrap(), extsk.default_address().unwrap().1); assert_eq!(pa.unwrap(), extsk.default_address().unwrap().1);
} }
} }

View File

@ -26,23 +26,13 @@
use rusqlite::{Connection, NO_PARAMS}; use rusqlite::{Connection, NO_PARAMS};
use std::cmp; use std::cmp;
use zcash_primitives::{
consensus::{self, BlockHeight},
zip32::ExtendedFullViewingKey,
};
use zcash_client_backend::encoding::encode_payment_address; use zcash_client_backend::encoding::encode_payment_address;
use zcash_primitives::zip32::ExtendedFullViewingKey;
#[cfg(feature = "mainnet")]
use zcash_client_backend::constants::mainnet::{
HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, HRP_SAPLING_PAYMENT_ADDRESS,
};
#[cfg(not(feature = "mainnet"))]
use zcash_client_backend::constants::testnet::{
HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, HRP_SAPLING_PAYMENT_ADDRESS,
};
#[cfg(feature = "mainnet")]
pub use zcash_primitives::consensus::MainNetwork as Network;
#[cfg(not(feature = "mainnet"))]
pub use zcash_primitives::consensus::TestNetwork as Network;
pub mod address; pub mod address;
pub mod chain; pub mod chain;
@ -54,20 +44,19 @@ pub mod transact;
const ANCHOR_OFFSET: u32 = 10; const ANCHOR_OFFSET: u32 = 10;
#[cfg(feature = "mainnet")] fn address_from_extfvk<P: consensus::Parameters>(
const SAPLING_ACTIVATION_HEIGHT: i32 = 419_200; params: &P,
extfvk: &ExtendedFullViewingKey,
#[cfg(not(feature = "mainnet"))] ) -> String {
const SAPLING_ACTIVATION_HEIGHT: i32 = 280_000;
fn address_from_extfvk(extfvk: &ExtendedFullViewingKey) -> String {
let addr = extfvk.default_address().unwrap().1; let addr = extfvk.default_address().unwrap().1;
encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, &addr) encode_payment_address(params.hrp_sapling_payment_address(), &addr)
} }
/// Determines the target height for a transaction, and the height from which to /// Determines the target height for a transaction, and the height from which to
/// select anchors, based on the current synchronised block chain. /// select anchors, based on the current synchronised block chain.
fn get_target_and_anchor_heights(data: &Connection) -> Result<(u32, u32), error::Error> { fn get_target_and_anchor_heights(
data: &Connection,
) -> Result<(BlockHeight, BlockHeight), error::Error> {
data.query_row_and_then( data.query_row_and_then(
"SELECT MIN(height), MAX(height) FROM blocks", "SELECT MIN(height), MAX(height) FROM blocks",
NO_PARAMS, NO_PARAMS,
@ -86,7 +75,10 @@ fn get_target_and_anchor_heights(data: &Connection) -> Result<(u32, u32), error:
let anchor_height = let anchor_height =
cmp::max(target_height.saturating_sub(ANCHOR_OFFSET), min_height); cmp::max(target_height.saturating_sub(ANCHOR_OFFSET), min_height);
Ok((target_height, anchor_height)) Ok((
BlockHeight::from(target_height),
BlockHeight::from(anchor_height),
))
} }
}, },
) )
@ -94,18 +86,20 @@ fn get_target_and_anchor_heights(data: &Connection) -> Result<(u32, u32), error:
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::Network;
use ff::PrimeField; use ff::PrimeField;
use group::GroupEncoding; use group::GroupEncoding;
use protobuf::Message; use protobuf::Message;
use rand_core::{OsRng, RngCore}; use rand_core::{OsRng, RngCore};
use rusqlite::{types::ToSql, Connection}; use rusqlite::{types::ToSql, Connection};
use std::path::Path; use std::path::Path;
use zcash_client_backend::proto::compact_formats::{ use zcash_client_backend::proto::compact_formats::{
CompactBlock, CompactOutput, CompactSpend, CompactTx, CompactBlock, CompactOutput, CompactSpend, CompactTx,
}; };
use zcash_primitives::{ use zcash_primitives::{
block::BlockHash, block::BlockHash,
consensus::{BlockHeight, Network, NetworkUpgrade, Parameters},
note_encryption::{Memo, SaplingNoteEncryption}, note_encryption::{Memo, SaplingNoteEncryption},
primitives::{Note, PaymentAddress}, primitives::{Note, PaymentAddress},
transaction::components::Amount, transaction::components::Amount,
@ -113,10 +107,34 @@ mod tests {
zip32::ExtendedFullViewingKey, zip32::ExtendedFullViewingKey,
}; };
#[cfg(feature = "mainnet")]
pub(crate) fn network() -> Network {
Network::MainNetwork
}
#[cfg(not(feature = "mainnet"))]
pub(crate) fn network() -> Network {
Network::TestNetwork
}
#[cfg(feature = "mainnet")]
pub(crate) fn sapling_activation_height() -> BlockHeight {
Network::MainNetwork
.activation_height(NetworkUpgrade::Sapling)
.unwrap()
}
#[cfg(not(feature = "mainnet"))]
pub(crate) fn sapling_activation_height() -> BlockHeight {
Network::TestNetwork
.activation_height(NetworkUpgrade::Sapling)
.unwrap()
}
/// Create a fake CompactBlock at the given height, containing a single output paying /// Create a fake CompactBlock at the given height, containing a single output paying
/// the given address. Returns the CompactBlock and the nullifier for the new note. /// the given address. Returns the CompactBlock and the nullifier for the new note.
pub(crate) fn fake_compact_block( pub(crate) fn fake_compact_block(
height: i32, height: BlockHeight,
prev_hash: BlockHash, prev_hash: BlockHash,
extfvk: ExtendedFullViewingKey, extfvk: ExtendedFullViewingKey,
value: Amount, value: Amount,
@ -125,7 +143,7 @@ mod tests {
// Create a fake Note for the account // Create a fake Note for the account
let mut rng = OsRng; let mut rng = OsRng;
let rseed = generate_random_rseed::<Network, OsRng>(height as u32, &mut rng); let rseed = generate_random_rseed(&network(), height, &mut rng);
let note = Note { let note = Note {
g_d: to.diversifier().g_d().unwrap(), g_d: to.diversifier().g_d().unwrap(),
pk_d: to.pk_d().clone(), pk_d: to.pk_d().clone(),
@ -154,7 +172,7 @@ mod tests {
ctx.set_hash(txid); ctx.set_hash(txid);
ctx.outputs.push(cout); ctx.outputs.push(cout);
let mut cb = CompactBlock::new(); let mut cb = CompactBlock::new();
cb.set_height(height as u64); cb.set_height(u64::from(height));
cb.hash.resize(32, 0); cb.hash.resize(32, 0);
rng.fill_bytes(&mut cb.hash); rng.fill_bytes(&mut cb.hash);
cb.prevHash.extend_from_slice(&prev_hash.0); cb.prevHash.extend_from_slice(&prev_hash.0);
@ -165,7 +183,7 @@ mod tests {
/// Create a fake CompactBlock at the given height, spending a single note from the /// Create a fake CompactBlock at the given height, spending a single note from the
/// given address. /// given address.
pub(crate) fn fake_compact_block_spending( pub(crate) fn fake_compact_block_spending(
height: i32, height: BlockHeight,
prev_hash: BlockHash, prev_hash: BlockHash,
(nf, in_value): (Vec<u8>, Amount), (nf, in_value): (Vec<u8>, Amount),
extfvk: ExtendedFullViewingKey, extfvk: ExtendedFullViewingKey,
@ -173,7 +191,7 @@ mod tests {
value: Amount, value: Amount,
) -> CompactBlock { ) -> CompactBlock {
let mut rng = OsRng; let mut rng = OsRng;
let rseed = generate_random_rseed::<Network, OsRng>(height as u32, &mut rng); let rseed = generate_random_rseed(&network(), height, &mut rng);
// Create a fake CompactBlock containing the note // Create a fake CompactBlock containing the note
let mut cspend = CompactSpend::new(); let mut cspend = CompactSpend::new();
@ -213,7 +231,7 @@ mod tests {
// Create a fake Note for the change // Create a fake Note for the change
ctx.outputs.push({ ctx.outputs.push({
let change_addr = extfvk.default_address().unwrap().1; let change_addr = extfvk.default_address().unwrap().1;
let rseed = generate_random_rseed::<Network, OsRng>(height as u32, &mut rng); let rseed = generate_random_rseed(&network(), height, &mut rng);
let note = Note { let note = Note {
g_d: change_addr.diversifier().g_d().unwrap(), g_d: change_addr.diversifier().g_d().unwrap(),
pk_d: change_addr.pk_d().clone(), pk_d: change_addr.pk_d().clone(),
@ -239,7 +257,7 @@ mod tests {
}); });
let mut cb = CompactBlock::new(); let mut cb = CompactBlock::new();
cb.set_height(height as u64); cb.set_height(u64::from(height));
cb.hash.resize(32, 0); cb.hash.resize(32, 0);
rng.fill_bytes(&mut cb.hash); rng.fill_bytes(&mut cb.hash);
cb.prevHash.extend_from_slice(&prev_hash.0); cb.prevHash.extend_from_slice(&prev_hash.0);
@ -255,7 +273,7 @@ mod tests {
.prepare("INSERT INTO compactblocks (height, data) VALUES (?, ?)") .prepare("INSERT INTO compactblocks (height, data) VALUES (?, ?)")
.unwrap() .unwrap()
.execute(&[ .execute(&[
(cb.height as i32).to_sql().unwrap(), u32::from(cb.height()).to_sql().unwrap(),
cb_bytes.to_sql().unwrap(), cb_bytes.to_sql().unwrap(),
]) ])
.unwrap(); .unwrap();

View File

@ -84,7 +84,7 @@ pub fn get_verified_balance<P: AsRef<Path>>(db_data: P, account: u32) -> Result<
"SELECT SUM(value) FROM received_notes "SELECT SUM(value) FROM received_notes
INNER JOIN transactions ON transactions.id_tx = received_notes.tx INNER JOIN transactions ON transactions.id_tx = received_notes.tx
WHERE account = ? AND spent IS NULL AND transactions.block <= ?", WHERE account = ? AND spent IS NULL AND transactions.block <= ?",
&[account, anchor_height], &[account, u32::from(anchor_height)],
|row| row.get(0).or(Ok(0)), |row| row.get(0).or(Ok(0)),
)?; )?;
@ -176,6 +176,7 @@ mod tests {
use crate::{ use crate::{
error::ErrorKind, error::ErrorKind,
init::{init_accounts_table, init_data_database}, init::{init_accounts_table, init_data_database},
tests,
}; };
#[test] #[test]
@ -187,7 +188,7 @@ mod tests {
// Add an account to the wallet // Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]); let extsk = ExtendedSpendingKey::master(&[]);
let extfvks = [ExtendedFullViewingKey::from(&extsk)]; let extfvks = [ExtendedFullViewingKey::from(&extsk)];
init_accounts_table(&db_data, &extfvks).unwrap(); init_accounts_table(&db_data, &tests::network(), &extfvks).unwrap();
// The account should be empty // The account should be empty
assert_eq!(get_balance(db_data, 0).unwrap(), Amount::zero()); assert_eq!(get_balance(db_data, 0).unwrap(), Amount::zero());

View File

@ -1,14 +1,17 @@
//! Functions for scanning the chain and extracting relevant information. //! Functions for scanning the chain and extracting relevant information.
use std::path::Path;
use ff::PrimeField; use ff::PrimeField;
use protobuf::parse_from_bytes; use protobuf::parse_from_bytes;
use rusqlite::{types::ToSql, Connection, OptionalExtension, NO_PARAMS}; use rusqlite::{types::ToSql, Connection, OptionalExtension, NO_PARAMS};
use std::path::Path;
use zcash_client_backend::{ use zcash_client_backend::{
decrypt_transaction, encoding::decode_extended_full_viewing_key, decrypt_transaction, encoding::decode_extended_full_viewing_key,
proto::compact_formats::CompactBlock, welding_rig::scan_block, proto::compact_formats::CompactBlock, welding_rig::scan_block,
}; };
use zcash_primitives::{ use zcash_primitives::{
consensus::{self, BlockHeight, NetworkUpgrade},
merkle_tree::{CommitmentTree, IncrementalWitness}, merkle_tree::{CommitmentTree, IncrementalWitness},
sapling::Node, sapling::Node,
transaction::Transaction, transaction::Transaction,
@ -17,11 +20,10 @@ use zcash_primitives::{
use crate::{ use crate::{
address::RecipientAddress, address::RecipientAddress,
error::{Error, ErrorKind}, error::{Error, ErrorKind},
Network, HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, SAPLING_ACTIVATION_HEIGHT,
}; };
struct CompactBlockRow { struct CompactBlockRow {
height: i32, height: BlockHeight,
data: Vec<u8>, data: Vec<u8>,
} }
@ -54,43 +56,60 @@ struct WitnessRow {
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use zcash_primitives::consensus::{
/// Network,
/// Parameters,
/// };
/// use zcash_client_sqlite::scan::scan_cached_blocks; /// use zcash_client_sqlite::scan::scan_cached_blocks;
/// ///
/// scan_cached_blocks("/path/to/cache.db", "/path/to/data.db", None); /// scan_cached_blocks(&Network::TestNetwork, "/path/to/cache.db", "/path/to/data.db", None);
/// ``` /// ```
/// ///
/// [`init_blocks_table`]: crate::init::init_blocks_table /// [`init_blocks_table`]: crate::init::init_blocks_table
pub fn scan_cached_blocks<P: AsRef<Path>, Q: AsRef<Path>>( pub fn scan_cached_blocks<Params: consensus::Parameters, P: AsRef<Path>, Q: AsRef<Path>>(
params: &Params,
db_cache: P, db_cache: P,
db_data: Q, db_data: Q,
limit: Option<i32>, limit: Option<u32>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let cache = Connection::open(db_cache)?; let cache = Connection::open(db_cache)?;
let data = Connection::open(db_data)?; let data = Connection::open(db_data)?;
let sapling_activation_height = params
.activation_height(NetworkUpgrade::Sapling)
.ok_or(Error(ErrorKind::SaplingNotActive))?;
// Recall where we synced up to previously. // Recall where we synced up to previously.
// If we have never synced, use sapling activation height to select all cached CompactBlocks. // If we have never synced, use sapling activation height to select all cached CompactBlocks.
let mut last_height = data.query_row("SELECT MAX(height) FROM blocks", NO_PARAMS, |row| { let mut last_height: BlockHeight =
row.get(0).or(Ok(SAPLING_ACTIVATION_HEIGHT - 1)) data.query_row("SELECT MAX(height) FROM blocks", NO_PARAMS, |row| {
})?; row.get::<_, u32>(0)
.map(BlockHeight::from)
.or(Ok(sapling_activation_height - 1))
})?;
// Fetch the CompactBlocks we need to scan // Fetch the CompactBlocks we need to scan
let mut stmt_blocks = cache.prepare( let mut stmt_blocks = cache.prepare(
"SELECT height, data FROM compactblocks WHERE height > ? ORDER BY height ASC LIMIT ?", "SELECT height, data FROM compactblocks WHERE height > ? ORDER BY height ASC LIMIT ?",
)?; )?;
let rows = stmt_blocks.query_map(&[last_height, limit.unwrap_or(i32::max_value())], |row| { let rows = stmt_blocks.query_map(
Ok(CompactBlockRow { &[u32::from(last_height), limit.unwrap_or(u32::max_value())],
height: row.get(0)?, |row| {
data: row.get(1)?, Ok(CompactBlockRow {
}) height: row.get::<_, u32>(0).map(BlockHeight::from)?,
})?; data: row.get(1)?,
})
},
)?;
// Fetch the ExtendedFullViewingKeys we are tracking // Fetch the ExtendedFullViewingKeys we are tracking
let mut stmt_fetch_accounts = let mut stmt_fetch_accounts =
data.prepare("SELECT extfvk FROM accounts ORDER BY account ASC")?; data.prepare("SELECT extfvk FROM accounts ORDER BY account ASC")?;
let extfvks = stmt_fetch_accounts.query_map(NO_PARAMS, |row| { let extfvks = stmt_fetch_accounts.query_map(NO_PARAMS, |row| {
row.get(0).map(|extfvk: String| { row.get(0).map(|extfvk: String| {
decode_extended_full_viewing_key(HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, &extfvk) decode_extended_full_viewing_key(
params.hrp_sapling_extended_full_viewing_key(),
&extfvk,
)
}) })
})?; })?;
// Raise SQL errors from the query, IO errors from parsing, and incorrect HRP errors. // Raise SQL errors from the query, IO errors from parsing, and incorrect HRP errors.
@ -101,7 +120,7 @@ pub fn scan_cached_blocks<P: AsRef<Path>, Q: AsRef<Path>>(
// Get the most recent CommitmentTree // Get the most recent CommitmentTree
let mut stmt_fetch_tree = data.prepare("SELECT sapling_tree FROM blocks WHERE height = ?")?; let mut stmt_fetch_tree = data.prepare("SELECT sapling_tree FROM blocks WHERE height = ?")?;
let mut tree = stmt_fetch_tree let mut tree = stmt_fetch_tree
.query_row(&[last_height], |row| { .query_row(&[u32::from(last_height)], |row| {
row.get(0).map(|data: Vec<_>| { row.get(0).map(|data: Vec<_>| {
CommitmentTree::read(&data[..]).unwrap_or_else(|_| CommitmentTree::new()) CommitmentTree::read(&data[..]).unwrap_or_else(|_| CommitmentTree::new())
}) })
@ -111,7 +130,7 @@ pub fn scan_cached_blocks<P: AsRef<Path>, Q: AsRef<Path>>(
// Get most recent incremental witnesses for the notes we are tracking // Get most recent incremental witnesses for the notes we are tracking
let mut stmt_fetch_witnesses = let mut stmt_fetch_witnesses =
data.prepare("SELECT note, witness FROM sapling_witnesses WHERE block = ?")?; data.prepare("SELECT note, witness FROM sapling_witnesses WHERE block = ?")?;
let witnesses = stmt_fetch_witnesses.query_map(&[last_height], |row| { let witnesses = stmt_fetch_witnesses.query_map(&[u32::from(last_height)], |row| {
let id_note = row.get(0)?; let id_note = row.get(0)?;
let data: Vec<_> = row.get(1)?; let data: Vec<_> = row.get(1)?;
Ok(IncrementalWitness::read(&data[..]).map(|witness| WitnessRow { id_note, witness })) Ok(IncrementalWitness::read(&data[..]).map(|witness| WitnessRow { id_note, witness }))
@ -186,7 +205,8 @@ pub fn scan_cached_blocks<P: AsRef<Path>, Q: AsRef<Path>>(
let txs = { let txs = {
let nf_refs: Vec<_> = nullifiers.iter().map(|(nf, acc)| (&nf[..], *acc)).collect(); let nf_refs: Vec<_> = nullifiers.iter().map(|(nf, acc)| (&nf[..], *acc)).collect();
let mut witness_refs: Vec<_> = witnesses.iter_mut().map(|w| &mut w.witness).collect(); let mut witness_refs: Vec<_> = witnesses.iter_mut().map(|w| &mut w.witness).collect();
scan_block::<Network>( scan_block(
params,
block, block,
&extfvks[..], &extfvks[..],
&nf_refs, &nf_refs,
@ -226,7 +246,7 @@ pub fn scan_cached_blocks<P: AsRef<Path>, Q: AsRef<Path>>(
tree.write(&mut encoded_tree) tree.write(&mut encoded_tree)
.expect("Should be able to write to a Vec"); .expect("Should be able to write to a Vec");
stmt_insert_block.execute(&[ stmt_insert_block.execute(&[
row.height.to_sql()?, u32::from(row.height).to_sql()?,
block_hash.to_sql()?, block_hash.to_sql()?,
block_time.to_sql()?, block_time.to_sql()?,
encoded_tree.to_sql()?, encoded_tree.to_sql()?,
@ -236,7 +256,7 @@ pub fn scan_cached_blocks<P: AsRef<Path>, Q: AsRef<Path>>(
// First try update an existing transaction in the database. // First try update an existing transaction in the database.
let txid = tx.txid.0.to_vec(); let txid = tx.txid.0.to_vec();
let tx_row = if stmt_update_tx.execute(&[ let tx_row = if stmt_update_tx.execute(&[
row.height.to_sql()?, u32::from(row.height).to_sql()?,
(tx.index as i64).to_sql()?, (tx.index as i64).to_sql()?,
txid.to_sql()?, txid.to_sql()?,
])? == 0 ])? == 0
@ -244,7 +264,7 @@ pub fn scan_cached_blocks<P: AsRef<Path>, Q: AsRef<Path>>(
// It isn't there, so insert our transaction into the database. // It isn't there, so insert our transaction into the database.
stmt_insert_tx.execute(&[ stmt_insert_tx.execute(&[
txid.to_sql()?, txid.to_sql()?,
row.height.to_sql()?, u32::from(row.height).to_sql()?,
(tx.index as i64).to_sql()?, (tx.index as i64).to_sql()?,
])?; ])?;
data.last_insert_rowid() data.last_insert_rowid()
@ -331,16 +351,16 @@ pub fn scan_cached_blocks<P: AsRef<Path>, Q: AsRef<Path>>(
.expect("Should be able to write to a Vec"); .expect("Should be able to write to a Vec");
stmt_insert_witness.execute(&[ stmt_insert_witness.execute(&[
witness_row.id_note.to_sql()?, witness_row.id_note.to_sql()?,
last_height.to_sql()?, u32::from(last_height).to_sql()?,
encoded.to_sql()?, encoded.to_sql()?,
])?; ])?;
} }
// Prune the stored witnesses (we only expect rollbacks of at most 100 blocks). // Prune the stored witnesses (we only expect rollbacks of at most 100 blocks).
stmt_prune_witnesses.execute(&[last_height - 100])?; stmt_prune_witnesses.execute(&[u32::from(last_height - 100)])?;
// Update now-expired transactions that didn't get mined. // Update now-expired transactions that didn't get mined.
stmt_update_expired.execute(&[last_height])?; stmt_update_expired.execute(&[u32::from(last_height)])?;
// Commit the SQL transaction, writing this block's data atomically. // Commit the SQL transaction, writing this block's data atomically.
data.execute("COMMIT", NO_PARAMS)?; data.execute("COMMIT", NO_PARAMS)?;
@ -351,8 +371,9 @@ pub fn scan_cached_blocks<P: AsRef<Path>, Q: AsRef<Path>>(
/// Scans a [`Transaction`] for any information that can be decrypted by the accounts in /// Scans a [`Transaction`] for any information that can be decrypted by the accounts in
/// the wallet, and saves it to the wallet. /// the wallet, and saves it to the wallet.
pub fn decrypt_and_store_transaction<P: AsRef<Path>>( pub fn decrypt_and_store_transaction<D: AsRef<Path>, P: consensus::Parameters>(
db_data: P, db_data: D,
params: &P,
tx: &Transaction, tx: &Transaction,
) -> Result<(), Error> { ) -> Result<(), Error> {
let data = Connection::open(db_data)?; let data = Connection::open(db_data)?;
@ -362,7 +383,10 @@ pub fn decrypt_and_store_transaction<P: AsRef<Path>>(
data.prepare("SELECT extfvk FROM accounts ORDER BY account ASC")?; data.prepare("SELECT extfvk FROM accounts ORDER BY account ASC")?;
let extfvks = stmt_fetch_accounts.query_map(NO_PARAMS, |row| { let extfvks = stmt_fetch_accounts.query_map(NO_PARAMS, |row| {
row.get(0).map(|extfvk: String| { row.get(0).map(|extfvk: String| {
decode_extended_full_viewing_key(HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, &extfvk) decode_extended_full_viewing_key(
params.hrp_sapling_extended_full_viewing_key(),
&extfvk,
)
}) })
})?; })?;
// Raise SQL errors from the query, IO errors from parsing, and incorrect HRP errors. // Raise SQL errors from the query, IO errors from parsing, and incorrect HRP errors.
@ -373,7 +397,9 @@ pub fn decrypt_and_store_transaction<P: AsRef<Path>>(
// Height is block height for mined transactions, and the "mempool height" (chain height + 1) for mempool transactions. // Height is block height for mined transactions, and the "mempool height" (chain height + 1) for mempool transactions.
let mut stmt_select_block = data.prepare("SELECT block FROM transactions WHERE txid = ?")?; let mut stmt_select_block = data.prepare("SELECT block FROM transactions WHERE txid = ?")?;
let height = match stmt_select_block let height = match stmt_select_block
.query_row(&[tx.txid().0.to_vec()], |row| row.get(0)) .query_row(&[tx.txid().0.to_vec()], |row| {
row.get::<_, u32>(0).map(BlockHeight::from)
})
.optional()? .optional()?
{ {
Some(height) => height, Some(height) => height,
@ -382,11 +408,12 @@ pub fn decrypt_and_store_transaction<P: AsRef<Path>>(
row.get(0) row.get(0)
}) })
.optional()? .optional()?
.map(|last_height: u32| last_height + 1) .map(|last_height: u32| BlockHeight::from(last_height + 1))
.unwrap_or(SAPLING_ACTIVATION_HEIGHT as u32), .or_else(|| params.activation_height(NetworkUpgrade::Sapling))
.ok_or(Error(ErrorKind::SaplingNotActive))?,
}; };
let outputs = decrypt_transaction::<Network>(height as u32, tx, &extfvks); let outputs = decrypt_transaction(params, height, tx, &extfvks);
if outputs.is_empty() { if outputs.is_empty() {
// Nothing to see here // Nothing to see here
@ -429,13 +456,17 @@ pub fn decrypt_and_store_transaction<P: AsRef<Path>>(
let mut raw_tx = vec![]; let mut raw_tx = vec![];
tx.write(&mut raw_tx)?; tx.write(&mut raw_tx)?;
let tx_row = if stmt_update_tx.execute(&[ let tx_row = if stmt_update_tx.execute(&[
tx.expiry_height.to_sql()?, u32::from(tx.expiry_height).to_sql()?,
raw_tx.to_sql()?, raw_tx.to_sql()?,
txid.to_sql()?, txid.to_sql()?,
])? == 0 ])? == 0
{ {
// It isn't there, so insert our transaction into the database. // It isn't there, so insert our transaction into the database.
stmt_insert_tx.execute(&[txid.to_sql()?, tx.expiry_height.to_sql()?, raw_tx.to_sql()?])?; stmt_insert_tx.execute(&[
txid.to_sql()?,
u32::from(tx.expiry_height).to_sql()?,
raw_tx.to_sql()?,
])?;
data.last_insert_rowid() data.last_insert_rowid()
} else { } else {
// It was there, so grab its row number. // It was there, so grab its row number.
@ -448,7 +479,7 @@ pub fn decrypt_and_store_transaction<P: AsRef<Path>>(
let value = output.note.value as i64; let value = output.note.value as i64;
if output.outgoing { if output.outgoing {
let to_str = RecipientAddress::from(output.to).to_string(); let to_str = RecipientAddress::from(output.to).encode(params);
// Try updating an existing sent note. // Try updating an existing sent note.
if stmt_update_sent_note.execute(&[ if stmt_update_sent_note.execute(&[
@ -516,8 +547,10 @@ mod tests {
use crate::{ use crate::{
init::{init_accounts_table, init_cache_database, init_data_database}, init::{init_accounts_table, init_cache_database, init_data_database},
query::get_balance, query::get_balance,
tests::{fake_compact_block, fake_compact_block_spending, insert_into_cache}, tests::{
SAPLING_ACTIVATION_HEIGHT, self, fake_compact_block, fake_compact_block_spending, insert_into_cache,
sapling_activation_height,
},
}; };
#[test] #[test]
@ -533,49 +566,49 @@ mod tests {
// Add an account to the wallet // Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]); let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk); let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap(); init_accounts_table(&db_data, &tests::network(), &[extfvk.clone()]).unwrap();
// Create a block with height SAPLING_ACTIVATION_HEIGHT // Create a block with height SAPLING_ACTIVATION_HEIGHT
let value = Amount::from_u64(50000).unwrap(); let value = Amount::from_u64(50000).unwrap();
let (cb1, _) = fake_compact_block( let (cb1, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT, sapling_activation_height(),
BlockHash([0; 32]), BlockHash([0; 32]),
extfvk.clone(), extfvk.clone(),
value, value,
); );
insert_into_cache(db_cache, &cb1); insert_into_cache(db_cache, &cb1);
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&tests::network(), db_cache, db_data, None).unwrap();
assert_eq!(get_balance(db_data, 0).unwrap(), value); assert_eq!(get_balance(db_data, 0).unwrap(), value);
// We cannot scan a block of height SAPLING_ACTIVATION_HEIGHT + 2 next // We cannot scan a block of height SAPLING_ACTIVATION_HEIGHT + 2 next
let (cb2, _) = fake_compact_block( let (cb2, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT + 1, sapling_activation_height() + 1,
cb1.hash(), cb1.hash(),
extfvk.clone(), extfvk.clone(),
value, value,
); );
let (cb3, _) = fake_compact_block( let (cb3, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT + 2, sapling_activation_height() + 2,
cb2.hash(), cb2.hash(),
extfvk.clone(), extfvk.clone(),
value, value,
); );
insert_into_cache(db_cache, &cb3); insert_into_cache(db_cache, &cb3);
match scan_cached_blocks(db_cache, db_data, None) { match scan_cached_blocks(&tests::network(), db_cache, db_data, None) {
Ok(_) => panic!("Should have failed"), Ok(_) => panic!("Should have failed"),
Err(e) => assert_eq!( Err(e) => assert_eq!(
e.to_string(), e.to_string(),
format!( format!(
"Expected height of next CompactBlock to be {}, but was {}", "Expected height of next CompactBlock to be {}, but was {}",
SAPLING_ACTIVATION_HEIGHT + 1, sapling_activation_height() + 1,
SAPLING_ACTIVATION_HEIGHT + 2 sapling_activation_height() + 2
) )
), ),
} }
// If we add a block of height SAPLING_ACTIVATION_HEIGHT + 1, we can now scan both // If we add a block of height SAPLING_ACTIVATION_HEIGHT + 1, we can now scan both
insert_into_cache(db_cache, &cb2); insert_into_cache(db_cache, &cb2);
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&tests::network(), db_cache, db_data, None).unwrap();
assert_eq!( assert_eq!(
get_balance(db_data, 0).unwrap(), get_balance(db_data, 0).unwrap(),
Amount::from_u64(150_000).unwrap() Amount::from_u64(150_000).unwrap()
@ -595,7 +628,7 @@ mod tests {
// Add an account to the wallet // Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]); let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk); let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap(); init_accounts_table(&db_data, &tests::network(), &[extfvk.clone()]).unwrap();
// Account balance should be zero // Account balance should be zero
assert_eq!(get_balance(db_data, 0).unwrap(), Amount::zero()); assert_eq!(get_balance(db_data, 0).unwrap(), Amount::zero());
@ -603,7 +636,7 @@ mod tests {
// Create a fake CompactBlock sending value to the address // Create a fake CompactBlock sending value to the address
let value = Amount::from_u64(5).unwrap(); let value = Amount::from_u64(5).unwrap();
let (cb, _) = fake_compact_block( let (cb, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT, sapling_activation_height(),
BlockHash([0; 32]), BlockHash([0; 32]),
extfvk.clone(), extfvk.clone(),
value, value,
@ -611,18 +644,19 @@ mod tests {
insert_into_cache(db_cache, &cb); insert_into_cache(db_cache, &cb);
// Scan the cache // Scan the cache
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&tests::network(), db_cache, db_data, None).unwrap();
// Account balance should reflect the received note // Account balance should reflect the received note
assert_eq!(get_balance(db_data, 0).unwrap(), value); assert_eq!(get_balance(db_data, 0).unwrap(), value);
// Create a second fake CompactBlock sending more value to the address // Create a second fake CompactBlock sending more value to the address
let value2 = Amount::from_u64(7).unwrap(); let value2 = Amount::from_u64(7).unwrap();
let (cb2, _) = fake_compact_block(SAPLING_ACTIVATION_HEIGHT + 1, cb.hash(), extfvk, value2); let (cb2, _) =
fake_compact_block(sapling_activation_height() + 1, cb.hash(), extfvk, value2);
insert_into_cache(db_cache, &cb2); insert_into_cache(db_cache, &cb2);
// Scan the cache again // Scan the cache again
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&tests::network(), db_cache, db_data, None).unwrap();
// Account balance should reflect both received notes // Account balance should reflect both received notes
assert_eq!(get_balance(db_data, 0).unwrap(), value + value2); assert_eq!(get_balance(db_data, 0).unwrap(), value + value2);
@ -641,7 +675,7 @@ mod tests {
// Add an account to the wallet // Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]); let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk); let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap(); init_accounts_table(&db_data, &tests::network(), &[extfvk.clone()]).unwrap();
// Account balance should be zero // Account balance should be zero
assert_eq!(get_balance(db_data, 0).unwrap(), Amount::zero()); assert_eq!(get_balance(db_data, 0).unwrap(), Amount::zero());
@ -649,7 +683,7 @@ mod tests {
// Create a fake CompactBlock sending value to the address // Create a fake CompactBlock sending value to the address
let value = Amount::from_u64(5).unwrap(); let value = Amount::from_u64(5).unwrap();
let (cb, nf) = fake_compact_block( let (cb, nf) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT, sapling_activation_height(),
BlockHash([0; 32]), BlockHash([0; 32]),
extfvk.clone(), extfvk.clone(),
value, value,
@ -657,7 +691,7 @@ mod tests {
insert_into_cache(db_cache, &cb); insert_into_cache(db_cache, &cb);
// Scan the cache // Scan the cache
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&tests::network(), db_cache, db_data, None).unwrap();
// Account balance should reflect the received note // Account balance should reflect the received note
assert_eq!(get_balance(db_data, 0).unwrap(), value); assert_eq!(get_balance(db_data, 0).unwrap(), value);
@ -669,7 +703,7 @@ mod tests {
insert_into_cache( insert_into_cache(
db_cache, db_cache,
&fake_compact_block_spending( &fake_compact_block_spending(
SAPLING_ACTIVATION_HEIGHT + 1, sapling_activation_height() + 1,
cb.hash(), cb.hash(),
(nf, value), (nf, value),
extfvk, extfvk,
@ -679,7 +713,7 @@ mod tests {
); );
// Scan the cache again // Scan the cache again
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&tests::network(), db_cache, db_data, None).unwrap();
// Account balance should equal the change // Account balance should equal the change
assert_eq!(get_balance(db_data, 0).unwrap(), value - value2); assert_eq!(get_balance(db_data, 0).unwrap(), value - value2);

View File

@ -1,13 +1,12 @@
//! Functions for creating transactions. //! Functions for creating transactions.
use ff::PrimeField; use ff::PrimeField;
use rand_core::OsRng;
use rusqlite::{types::ToSql, Connection, NO_PARAMS}; use rusqlite::{types::ToSql, Connection, NO_PARAMS};
use std::convert::TryInto; use std::convert::TryInto;
use std::path::Path; use std::path::Path;
use zcash_client_backend::encoding::encode_extended_full_viewing_key; use zcash_client_backend::encoding::encode_extended_full_viewing_key;
use zcash_primitives::{ use zcash_primitives::{
consensus::{self, NetworkUpgrade, Parameters}, consensus::{self, NetworkUpgrade},
keys::OutgoingViewingKey, keys::OutgoingViewingKey,
merkle_tree::{IncrementalWitness, MerklePath}, merkle_tree::{IncrementalWitness, MerklePath},
note_encryption::Memo, note_encryption::Memo,
@ -24,7 +23,7 @@ use zcash_primitives::{
use crate::{ use crate::{
address::RecipientAddress, address::RecipientAddress,
error::{Error, ErrorKind}, error::{Error, ErrorKind},
get_target_and_anchor_heights, Network, HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, get_target_and_anchor_heights,
}; };
/// Describes a policy for which outgoing viewing key should be able to decrypt /// Describes a policy for which outgoing viewing key should be able to decrypt
@ -88,13 +87,16 @@ struct SelectedNoteRow {
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use zcash_client_backend::{ /// use zcash_primitives::{
/// consensus::{self, Network},
/// constants::testnet::COIN_TYPE, /// constants::testnet::COIN_TYPE,
/// transaction::components::Amount
/// };
/// use zcash_proofs::prover::LocalTxProver;
/// use zcash_client_backend::{
/// keys::spending_key, /// keys::spending_key,
/// }; /// };
/// use zcash_client_sqlite::transact::{create_to_address, OvkPolicy}; /// use zcash_client_sqlite::transact::{create_to_address, OvkPolicy};
/// use zcash_primitives::{consensus, transaction::components::Amount};
/// use zcash_proofs::prover::LocalTxProver;
/// ///
/// let tx_prover = match LocalTxProver::with_default_location() { /// let tx_prover = match LocalTxProver::with_default_location() {
/// Some(tx_prover) => tx_prover, /// Some(tx_prover) => tx_prover,
@ -108,6 +110,7 @@ struct SelectedNoteRow {
/// let to = extsk.default_address().unwrap().1.into(); /// let to = extsk.default_address().unwrap().1.into();
/// match create_to_address( /// match create_to_address(
/// "/path/to/data.db", /// "/path/to/data.db",
/// &Network::TestNetwork,
/// consensus::BranchId::Sapling, /// consensus::BranchId::Sapling,
/// tx_prover, /// tx_prover,
/// (account, &extsk), /// (account, &extsk),
@ -120,8 +123,9 @@ struct SelectedNoteRow {
/// Err(e) => (), /// Err(e) => (),
/// } /// }
/// ``` /// ```
pub fn create_to_address<P: AsRef<Path>>( pub fn create_to_address<DB: AsRef<Path>, P: consensus::Parameters>(
db_data: P, db_data: DB,
params: &P,
consensus_branch_id: consensus::BranchId, consensus_branch_id: consensus::BranchId,
prover: impl TxProver, prover: impl TxProver,
(account, extsk): (u32, &ExtendedSpendingKey), (account, extsk): (u32, &ExtendedSpendingKey),
@ -139,8 +143,11 @@ pub fn create_to_address<P: AsRef<Path>>(
.prepare("SELECT * FROM accounts WHERE account = ? AND extfvk = ?")? .prepare("SELECT * FROM accounts WHERE account = ? AND extfvk = ?")?
.exists(&[ .exists(&[
account.to_sql()?, account.to_sql()?,
encode_extended_full_viewing_key(HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, &extfvk) encode_extended_full_viewing_key(
.to_sql()?, params.hrp_sapling_extended_full_viewing_key(),
&extfvk,
)
.to_sql()?,
])? ])?
{ {
return Err(Error(ErrorKind::InvalidExtSK(account))); return Err(Error(ErrorKind::InvalidExtSK(account)));
@ -154,10 +161,7 @@ pub fn create_to_address<P: AsRef<Path>>(
}; };
// Target the next block, assuming we are up-to-date. // Target the next block, assuming we are up-to-date.
let (height, anchor_height) = { let (height, anchor_height) = get_target_and_anchor_heights(&data)?;
let (target_height, anchor_height) = get_target_and_anchor_heights(&data)?;
(target_height, i64::from(anchor_height))
};
// The goal of this SQL statement is to select the oldest notes until the required // The goal of this SQL statement is to select the oldest notes until the required
// value has been reached, and then fetch the witnesses at the desired height for the // value has been reached, and then fetch the witnesses at the desired height for the
@ -204,10 +208,10 @@ pub fn create_to_address<P: AsRef<Path>>(
let notes = stmt_select_notes.query_and_then::<_, Error, _, _>( let notes = stmt_select_notes.query_and_then::<_, Error, _, _>(
&[ &[
i64::from(account), i64::from(account),
anchor_height, i64::from(anchor_height),
target_value, target_value,
target_value, target_value,
anchor_height, i64::from(anchor_height),
], ],
|row| { |row| {
let diversifier = { let diversifier = {
@ -227,7 +231,7 @@ pub fn create_to_address<P: AsRef<Path>>(
let rseed = { let rseed = {
let d: Vec<_> = row.get(2)?; let d: Vec<_> = row.get(2)?;
if Network::is_nu_active(NetworkUpgrade::Canopy, height) { if params.is_nu_active(NetworkUpgrade::Canopy, height) {
let mut r = [0u8; 32]; let mut r = [0u8; 32];
r.copy_from_slice(&d[..]); r.copy_from_slice(&d[..]);
Rseed::AfterZip212(r) Rseed::AfterZip212(r)
@ -273,7 +277,7 @@ pub fn create_to_address<P: AsRef<Path>>(
} }
// Create the transaction // Create the transaction
let mut builder = Builder::<Network, OsRng>::new(height); let mut builder = Builder::new(params.clone(), height);
for selected in notes { for selected in notes {
builder.add_sapling_spend( builder.add_sapling_spend(
extsk.clone(), extsk.clone(),
@ -309,7 +313,7 @@ pub fn create_to_address<P: AsRef<Path>>(
stmt_insert_tx.execute(&[ stmt_insert_tx.execute(&[
tx.txid().0.to_sql()?, tx.txid().0.to_sql()?,
created.to_sql()?, created.to_sql()?,
tx.expiry_height.to_sql()?, i64::from(tx.expiry_height).to_sql()?,
raw_tx.to_sql()?, raw_tx.to_sql()?,
])?; ])?;
let id_tx = data.last_insert_rowid(); let id_tx = data.last_insert_rowid();
@ -330,7 +334,7 @@ pub fn create_to_address<P: AsRef<Path>>(
// Save the sent note in the database. // Save the sent note in the database.
// TODO: Decide how to save transparent output information. // TODO: Decide how to save transparent output information.
let to_str = to.to_string(); let to_str = to.encode(params);
if let Some(memo) = memo { if let Some(memo) = memo {
let mut stmt_insert_sent_note = data.prepare( let mut stmt_insert_sent_note = data.prepare(
"INSERT INTO sent_notes (tx, output_index, from_account, address, value, memo) "INSERT INTO sent_notes (tx, output_index, from_account, address, value, memo)
@ -369,6 +373,7 @@ mod tests {
use group::cofactor::CofactorGroup; use group::cofactor::CofactorGroup;
use rusqlite::Connection; use rusqlite::Connection;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
use zcash_primitives::{ use zcash_primitives::{
block::BlockHash, block::BlockHash,
consensus, consensus,
@ -377,17 +382,18 @@ mod tests {
transaction::{components::Amount, Transaction}, transaction::{components::Amount, Transaction},
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
}; };
use zcash_proofs::prover::LocalTxProver; use zcash_proofs::prover::LocalTxProver;
use super::{create_to_address, OvkPolicy};
use crate::{ use crate::{
init::{init_accounts_table, init_blocks_table, init_cache_database, init_data_database}, init::{init_accounts_table, init_blocks_table, init_cache_database, init_data_database},
query::{get_balance, get_verified_balance}, query::{get_balance, get_verified_balance},
scan::scan_cached_blocks, scan::scan_cached_blocks,
tests::{fake_compact_block, insert_into_cache}, tests::{self, fake_compact_block, insert_into_cache, sapling_activation_height},
Network, SAPLING_ACTIVATION_HEIGHT,
}; };
use super::{create_to_address, OvkPolicy};
fn test_prover() -> impl TxProver { fn test_prover() -> impl TxProver {
match LocalTxProver::with_default_location() { match LocalTxProver::with_default_location() {
Some(tx_prover) => tx_prover, Some(tx_prover) => tx_prover,
@ -410,12 +416,13 @@ mod tests {
ExtendedFullViewingKey::from(&extsk0), ExtendedFullViewingKey::from(&extsk0),
ExtendedFullViewingKey::from(&extsk1), ExtendedFullViewingKey::from(&extsk1),
]; ];
init_accounts_table(&db_data, &extfvks).unwrap(); init_accounts_table(&db_data, &tests::network(), &extfvks).unwrap();
let to = extsk0.default_address().unwrap().1.into(); let to = extsk0.default_address().unwrap().1.into();
// Invalid extsk for the given account should cause an error // Invalid extsk for the given account should cause an error
match create_to_address( match create_to_address(
db_data, db_data,
&tests::network(),
consensus::BranchId::Blossom, consensus::BranchId::Blossom,
test_prover(), test_prover(),
(0, &extsk1), (0, &extsk1),
@ -429,6 +436,7 @@ mod tests {
} }
match create_to_address( match create_to_address(
db_data, db_data,
&tests::network(),
consensus::BranchId::Blossom, consensus::BranchId::Blossom,
test_prover(), test_prover(),
(1, &extsk0), (1, &extsk0),
@ -451,12 +459,13 @@ mod tests {
// Add an account to the wallet // Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]); let extsk = ExtendedSpendingKey::master(&[]);
let extfvks = [ExtendedFullViewingKey::from(&extsk)]; let extfvks = [ExtendedFullViewingKey::from(&extsk)];
init_accounts_table(&db_data, &extfvks).unwrap(); init_accounts_table(&db_data, &tests::network(), &extfvks).unwrap();
let to = extsk.default_address().unwrap().1.into(); let to = extsk.default_address().unwrap().1.into();
// We cannot do anything if we aren't synchronised // We cannot do anything if we aren't synchronised
match create_to_address( match create_to_address(
db_data, db_data,
&tests::network(),
consensus::BranchId::Blossom, consensus::BranchId::Blossom,
test_prover(), test_prover(),
(0, &extsk), (0, &extsk),
@ -480,7 +489,7 @@ mod tests {
// Add an account to the wallet // Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]); let extsk = ExtendedSpendingKey::master(&[]);
let extfvks = [ExtendedFullViewingKey::from(&extsk)]; let extfvks = [ExtendedFullViewingKey::from(&extsk)];
init_accounts_table(&db_data, &extfvks).unwrap(); init_accounts_table(&db_data, &tests::network(), &extfvks).unwrap();
let to = extsk.default_address().unwrap().1.into(); let to = extsk.default_address().unwrap().1.into();
// Account balance should be zero // Account balance should be zero
@ -489,6 +498,7 @@ mod tests {
// We cannot spend anything // We cannot spend anything
match create_to_address( match create_to_address(
db_data, db_data,
&tests::network(),
consensus::BranchId::Blossom, consensus::BranchId::Blossom,
test_prover(), test_prover(),
(0, &extsk), (0, &extsk),
@ -518,18 +528,18 @@ mod tests {
// Add an account to the wallet // Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]); let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk); let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap(); init_accounts_table(&db_data, &tests::network(), &[extfvk.clone()]).unwrap();
// Add funds to the wallet in a single note // Add funds to the wallet in a single note
let value = Amount::from_u64(50000).unwrap(); let value = Amount::from_u64(50000).unwrap();
let (cb, _) = fake_compact_block( let (cb, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT, sapling_activation_height(),
BlockHash([0; 32]), BlockHash([0; 32]),
extfvk.clone(), extfvk.clone(),
value, value,
); );
insert_into_cache(db_cache, &cb); insert_into_cache(db_cache, &cb);
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&tests::network(), db_cache, db_data, None).unwrap();
// Verified balance matches total balance // Verified balance matches total balance
assert_eq!(get_balance(db_data, 0).unwrap(), value); assert_eq!(get_balance(db_data, 0).unwrap(), value);
@ -537,13 +547,13 @@ mod tests {
// Add more funds to the wallet in a second note // Add more funds to the wallet in a second note
let (cb, _) = fake_compact_block( let (cb, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT + 1, sapling_activation_height() + 1,
cb.hash(), cb.hash(),
extfvk.clone(), extfvk.clone(),
value, value,
); );
insert_into_cache(db_cache, &cb); insert_into_cache(db_cache, &cb);
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&tests::network(), db_cache, db_data, None).unwrap();
// Verified balance does not include the second note // Verified balance does not include the second note
assert_eq!(get_balance(db_data, 0).unwrap(), value + value); assert_eq!(get_balance(db_data, 0).unwrap(), value + value);
@ -554,6 +564,7 @@ mod tests {
let to = extsk2.default_address().unwrap().1.into(); let to = extsk2.default_address().unwrap().1.into();
match create_to_address( match create_to_address(
db_data, db_data,
&tests::network(),
consensus::BranchId::Blossom, consensus::BranchId::Blossom,
test_prover(), test_prover(),
(0, &extsk), (0, &extsk),
@ -573,18 +584,19 @@ mod tests {
// note is verified // note is verified
for i in 2..10 { for i in 2..10 {
let (cb, _) = fake_compact_block( let (cb, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT + i, sapling_activation_height() + i,
cb.hash(), cb.hash(),
extfvk.clone(), extfvk.clone(),
value, value,
); );
insert_into_cache(db_cache, &cb); insert_into_cache(db_cache, &cb);
} }
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&tests::network(), db_cache, db_data, None).unwrap();
// Second spend still fails // Second spend still fails
match create_to_address( match create_to_address(
db_data, db_data,
&tests::network(),
consensus::BranchId::Blossom, consensus::BranchId::Blossom,
test_prover(), test_prover(),
(0, &extsk), (0, &extsk),
@ -602,17 +614,18 @@ mod tests {
// Mine block 11 so that the second note becomes verified // Mine block 11 so that the second note becomes verified
let (cb, _) = fake_compact_block( let (cb, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT + 10, sapling_activation_height() + 10,
cb.hash(), cb.hash(),
extfvk.clone(), extfvk.clone(),
value, value,
); );
insert_into_cache(db_cache, &cb); insert_into_cache(db_cache, &cb);
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&tests::network(), db_cache, db_data, None).unwrap();
// Second spend should now succeed // Second spend should now succeed
create_to_address( create_to_address(
db_data, db_data,
&tests::network(),
consensus::BranchId::Blossom, consensus::BranchId::Blossom,
test_prover(), test_prover(),
(0, &extsk), (0, &extsk),
@ -637,18 +650,18 @@ mod tests {
// Add an account to the wallet // Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]); let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk); let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap(); init_accounts_table(&db_data, &tests::network(), &[extfvk.clone()]).unwrap();
// Add funds to the wallet in a single note // Add funds to the wallet in a single note
let value = Amount::from_u64(50000).unwrap(); let value = Amount::from_u64(50000).unwrap();
let (cb, _) = fake_compact_block( let (cb, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT, sapling_activation_height(),
BlockHash([0; 32]), BlockHash([0; 32]),
extfvk.clone(), extfvk.clone(),
value, value,
); );
insert_into_cache(db_cache, &cb); insert_into_cache(db_cache, &cb);
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&tests::network(), db_cache, db_data, None).unwrap();
assert_eq!(get_balance(db_data, 0).unwrap(), value); assert_eq!(get_balance(db_data, 0).unwrap(), value);
// Send some of the funds to another address // Send some of the funds to another address
@ -656,6 +669,7 @@ mod tests {
let to = extsk2.default_address().unwrap().1.into(); let to = extsk2.default_address().unwrap().1.into();
create_to_address( create_to_address(
db_data, db_data,
&tests::network(),
consensus::BranchId::Blossom, consensus::BranchId::Blossom,
test_prover(), test_prover(),
(0, &extsk), (0, &extsk),
@ -669,6 +683,7 @@ mod tests {
// A second spend fails because there are no usable notes // A second spend fails because there are no usable notes
match create_to_address( match create_to_address(
db_data, db_data,
&tests::network(),
consensus::BranchId::Blossom, consensus::BranchId::Blossom,
test_prover(), test_prover(),
(0, &extsk), (0, &extsk),
@ -688,18 +703,19 @@ mod tests {
// until just before the first transaction expires // until just before the first transaction expires
for i in 1..22 { for i in 1..22 {
let (cb, _) = fake_compact_block( let (cb, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT + i, sapling_activation_height() + i,
cb.hash(), cb.hash(),
ExtendedFullViewingKey::from(&ExtendedSpendingKey::master(&[i as u8])), ExtendedFullViewingKey::from(&ExtendedSpendingKey::master(&[i as u8])),
value, value,
); );
insert_into_cache(db_cache, &cb); insert_into_cache(db_cache, &cb);
} }
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&tests::network(), db_cache, db_data, None).unwrap();
// Second spend still fails // Second spend still fails
match create_to_address( match create_to_address(
db_data, db_data,
&tests::network(),
consensus::BranchId::Blossom, consensus::BranchId::Blossom,
test_prover(), test_prover(),
(0, &extsk), (0, &extsk),
@ -717,17 +733,18 @@ mod tests {
// Mine block SAPLING_ACTIVATION_HEIGHT + 22 so that the first transaction expires // Mine block SAPLING_ACTIVATION_HEIGHT + 22 so that the first transaction expires
let (cb, _) = fake_compact_block( let (cb, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT + 22, sapling_activation_height() + 22,
cb.hash(), cb.hash(),
ExtendedFullViewingKey::from(&ExtendedSpendingKey::master(&[22])), ExtendedFullViewingKey::from(&ExtendedSpendingKey::master(&[22])),
value, value,
); );
insert_into_cache(db_cache, &cb); insert_into_cache(db_cache, &cb);
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&tests::network(), db_cache, db_data, None).unwrap();
// Second spend should now succeed // Second spend should now succeed
create_to_address( create_to_address(
db_data, db_data,
&tests::network(),
consensus::BranchId::Blossom, consensus::BranchId::Blossom,
test_prover(), test_prover(),
(0, &extsk), (0, &extsk),
@ -741,6 +758,7 @@ mod tests {
#[test] #[test]
fn ovk_policy_prevents_recovery_from_chain() { fn ovk_policy_prevents_recovery_from_chain() {
let network = tests::network();
let cache_file = NamedTempFile::new().unwrap(); let cache_file = NamedTempFile::new().unwrap();
let db_cache = cache_file.path(); let db_cache = cache_file.path();
init_cache_database(&db_cache).unwrap(); init_cache_database(&db_cache).unwrap();
@ -752,18 +770,18 @@ mod tests {
// Add an account to the wallet // Add an account to the wallet
let extsk = ExtendedSpendingKey::master(&[]); let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk); let extfvk = ExtendedFullViewingKey::from(&extsk);
init_accounts_table(&db_data, &[extfvk.clone()]).unwrap(); init_accounts_table(&db_data, &network, &[extfvk.clone()]).unwrap();
// Add funds to the wallet in a single note // Add funds to the wallet in a single note
let value = Amount::from_u64(50000).unwrap(); let value = Amount::from_u64(50000).unwrap();
let (cb, _) = fake_compact_block( let (cb, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT, sapling_activation_height(),
BlockHash([0; 32]), BlockHash([0; 32]),
extfvk.clone(), extfvk.clone(),
value, value,
); );
insert_into_cache(db_cache, &cb); insert_into_cache(db_cache, &cb);
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&network, db_cache, db_data, None).unwrap();
assert_eq!(get_balance(db_data, 0).unwrap(), value); assert_eq!(get_balance(db_data, 0).unwrap(), value);
let extsk2 = ExtendedSpendingKey::master(&[]); let extsk2 = ExtendedSpendingKey::master(&[]);
@ -773,6 +791,7 @@ mod tests {
let send_and_recover_with_policy = |ovk_policy| { let send_and_recover_with_policy = |ovk_policy| {
let tx_row = create_to_address( let tx_row = create_to_address(
db_data, db_data,
&network,
consensus::BranchId::Blossom, consensus::BranchId::Blossom,
test_prover(), test_prover(),
(0, &extsk), (0, &extsk),
@ -807,8 +826,9 @@ mod tests {
.unwrap(); .unwrap();
let output = &tx.shielded_outputs[output_index as usize]; let output = &tx.shielded_outputs[output_index as usize];
try_sapling_output_recovery::<Network>( try_sapling_output_recovery(
SAPLING_ACTIVATION_HEIGHT as u32, &network,
sapling_activation_height(),
&extfvk.fvk.ovk, &extfvk.fvk.ovk,
&output.cv, &output.cv,
&output.cmu, &output.cmu,
@ -827,14 +847,14 @@ mod tests {
// so that the first transaction expires // so that the first transaction expires
for i in 1..=22 { for i in 1..=22 {
let (cb, _) = fake_compact_block( let (cb, _) = fake_compact_block(
SAPLING_ACTIVATION_HEIGHT + i, sapling_activation_height() + i,
cb.hash(), cb.hash(),
ExtendedFullViewingKey::from(&ExtendedSpendingKey::master(&[i as u8])), ExtendedFullViewingKey::from(&ExtendedSpendingKey::master(&[i as u8])),
value, value,
); );
insert_into_cache(db_cache, &cb); insert_into_cache(db_cache, &cb);
} }
scan_cached_blocks(db_cache, db_data, None).unwrap(); scan_cached_blocks(&network, db_cache, db_data, None).unwrap();
// 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.

View File

@ -84,7 +84,7 @@ pub enum Error {
NonTzeTxn, NonTzeTxn,
/// Verification error indicating that the witness being verified did not satisfy the /// Verification error indicating that the witness being verified did not satisfy the
/// precondition under inspection. /// precondition under inspection.
HashMismatch, HashMismatch,
/// Verification error indicating that the mode requested by the witness value did not /// Verification error indicating that the mode requested by the witness value did not
/// conform to that of the precondition under inspection. /// conform to that of the precondition under inspection.
ModeMismatch, ModeMismatch,
@ -92,7 +92,7 @@ pub enum Error {
/// when a `Close` was expected. /// when a `Close` was expected.
ExpectedClose, ExpectedClose,
/// Verification error indicating that an unexpected number of TZE outputs (more than /// Verification error indicating that an unexpected number of TZE outputs (more than
/// one) was encountered in the transaction under inspection, in violation of /// one) was encountered in the transaction under inspection, in violation of
/// the extension's invariants. /// the extension's invariants.
InvalidOutputQty(usize), InvalidOutputQty(usize),
} }
@ -242,7 +242,7 @@ pub trait Context {
fn tx_tze_outputs(&self) -> &[TzeOut]; fn tx_tze_outputs(&self) -> &[TzeOut];
} }
/// Marker type for the demo extension. /// Marker type for the demo extension.
/// ///
/// A value of this type will be used as the receiver for /// A value of this type will be used as the receiver for
/// `zcash_primitives::extensions::transparent::Extension` method invocations. /// `zcash_primitives::extensions::transparent::Extension` method invocations.
@ -340,7 +340,7 @@ pub struct DemoBuilder<B> {
/// The assigned identifier for this extension. This is necessary as the author /// The assigned identifier for this extension. This is necessary as the author
/// of the demo extension will not know ahead of time what identifier will be /// of the demo extension will not know ahead of time what identifier will be
/// assigned to it at the time of inclusion in the Zcash consensus rules. /// assigned to it at the time of inclusion in the Zcash consensus rules.
pub extension_id: u32, pub extension_id: u32,
} }
@ -467,7 +467,7 @@ mod tests {
use zcash_proofs::prover::LocalTxProver; use zcash_proofs::prover::LocalTxProver;
use zcash_primitives::{ use zcash_primitives::{
consensus::{BranchId, TestNetwork}, consensus::{BranchId, H0, TEST_NETWORK},
extensions::transparent::{self as tze, Extension, FromPayload, ToPayload}, extensions::transparent::{self as tze, Extension, FromPayload, ToPayload},
legacy::TransparentAddress, legacy::TransparentAddress,
merkle_tree::{CommitmentTree, IncrementalWitness}, merkle_tree::{CommitmentTree, IncrementalWitness},
@ -677,7 +677,7 @@ mod tests {
// //
let mut rng = OsRng; let mut rng = OsRng;
let mut builder_a: Builder<'_, TestNetwork, OsRng> = Builder::new_future(0); let mut builder_a = Builder::new_with_rng_future(TEST_NETWORK, H0, rng);
// create some inputs to spend // create some inputs to spend
let extsk = ExtendedSpendingKey::master(&[]); let extsk = ExtendedSpendingKey::master(&[]);
@ -720,7 +720,7 @@ mod tests {
// Transfer // Transfer
// //
let mut builder_b: Builder<'_, TestNetwork, OsRng> = Builder::new_future(0); let mut builder_b = Builder::new_with_rng_future(TEST_NETWORK, H0, rng);
let mut db_b = DemoBuilder { let mut db_b = DemoBuilder {
txn_builder: &mut builder_b, txn_builder: &mut builder_b,
extension_id: 0, extension_id: 0,
@ -739,7 +739,7 @@ mod tests {
// Closing transaction // Closing transaction
// //
let mut builder_c: Builder<'_, TestNetwork, OsRng> = Builder::new_future(0); let mut builder_c = Builder::new_with_rng_future(TEST_NETWORK, H0, rng);
let mut db_c = DemoBuilder { let mut db_c = DemoBuilder {
txn_builder: &mut builder_c, txn_builder: &mut builder_c,
extension_id: 0, extension_id: 0,

View File

@ -1,52 +1,266 @@
//! Consensus logic and parameters. //! Consensus logic and parameters.
use std::cmp::{Ord, Ordering};
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt; use std::fmt;
use std::ops::{Add, Sub};
use crate::constants;
/// A wrapper type representing blockchain heights. Safe conversion from
/// various integer types, as well as addition and subtraction, are provided.
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct BlockHeight(u32);
pub const H0: BlockHeight = BlockHeight(0);
impl BlockHeight {
pub const fn from_u32(v: u32) -> BlockHeight {
BlockHeight(v)
}
}
impl fmt::Display for BlockHeight {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(formatter)
}
}
impl Ord for BlockHeight {
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
impl PartialOrd for BlockHeight {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl From<u32> for BlockHeight {
fn from(value: u32) -> Self {
BlockHeight(value)
}
}
impl From<u64> for BlockHeight {
fn from(value: u64) -> Self {
BlockHeight(value as u32)
}
}
impl TryFrom<i32> for BlockHeight {
type Error = std::num::TryFromIntError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
u32::try_from(value).map(BlockHeight)
}
}
impl TryFrom<i64> for BlockHeight {
type Error = std::num::TryFromIntError;
fn try_from(value: i64) -> Result<Self, Self::Error> {
u32::try_from(value).map(BlockHeight)
}
}
impl From<BlockHeight> for u32 {
fn from(value: BlockHeight) -> u32 {
value.0
}
}
impl From<BlockHeight> for u64 {
fn from(value: BlockHeight) -> u64 {
value.0 as u64
}
}
impl From<BlockHeight> for i64 {
fn from(value: BlockHeight) -> i64 {
value.0 as i64
}
}
impl Add<u32> for BlockHeight {
type Output = Self;
fn add(self, other: u32) -> Self {
BlockHeight(self.0 + other)
}
}
impl Add for BlockHeight {
type Output = Self;
fn add(self, other: Self) -> Self {
self + other.0
}
}
impl Sub<u32> for BlockHeight {
type Output = Self;
fn sub(self, other: u32) -> Self {
if other > self.0 {
panic!("Subtraction resulted in negative block height.");
}
BlockHeight(self.0 - other)
}
}
impl Sub for BlockHeight {
type Output = Self;
fn sub(self, other: Self) -> Self {
self - other.0
}
}
/// Zcash consensus parameters. /// Zcash consensus parameters.
pub trait Parameters { pub trait Parameters: Clone {
fn activation_height(nu: NetworkUpgrade) -> Option<u32>; /// Returns the activation height for a particular network upgrade,
/// if an activation height has been set.
fn activation_height(&self, nu: NetworkUpgrade) -> Option<BlockHeight>;
fn is_nu_active(nu: NetworkUpgrade, height: u32) -> bool { /// Returns the human-readable prefix for Sapling extended full
match Self::activation_height(nu) { /// viewing keys for the network to which this Parameters value applies.
Some(h) if h <= height => true, fn hrp_sapling_extended_full_viewing_key(&self) -> &str;
_ => false,
} /// Returns the human-readable prefix for Sapling payment addresses
/// viewing keys for the network to which this Parameters value applies.
fn hrp_sapling_payment_address(&self) -> &str;
/// Returns the human-readable prefix for transparent pay-to-public-key-hash
/// payment addresses for the network to which this Parameters value applies.
fn b58_pubkey_address_prefix(&self) -> [u8; 2];
/// Returns the human-readable prefix for transparent pay-to-script-hash
/// payment addresses for the network to which this Parameters value applies.
fn b58_script_address_prefix(&self) -> [u8; 2];
/// Determines whether the specified network upgrade is active as of the
/// provided block height on the network to which this Parameters value applies.
fn is_nu_active(&self, nu: NetworkUpgrade, height: BlockHeight) -> bool {
self.activation_height(nu).map_or(false, |h| h <= height)
} }
} }
/// Marker struct for the production network. /// Marker struct for the production network.
#[derive(Clone, Copy, Debug)] #[derive(PartialEq, Copy, Clone, Debug)]
pub struct MainNetwork; pub struct MainNetwork;
pub const MAIN_NETWORK: MainNetwork = MainNetwork;
impl Parameters for MainNetwork { impl Parameters for MainNetwork {
fn activation_height(nu: NetworkUpgrade) -> Option<u32> { fn activation_height(&self, nu: NetworkUpgrade) -> Option<BlockHeight> {
match nu { match nu {
NetworkUpgrade::Overwinter => Some(347_500), NetworkUpgrade::Overwinter => Some(BlockHeight(347_500)),
NetworkUpgrade::Sapling => Some(419_200), NetworkUpgrade::Sapling => Some(BlockHeight(419_200)),
NetworkUpgrade::Blossom => Some(653_600), NetworkUpgrade::Blossom => Some(BlockHeight(653_600)),
NetworkUpgrade::Heartwood => Some(903_000), NetworkUpgrade::Heartwood => Some(BlockHeight(903_000)),
NetworkUpgrade::Canopy => Some(1_046_400), NetworkUpgrade::Canopy => Some(BlockHeight(1_046_400)),
NetworkUpgrade::Future => None, NetworkUpgrade::Future => None,
} }
} }
fn hrp_sapling_extended_full_viewing_key(&self) -> &str {
constants::mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY
}
fn hrp_sapling_payment_address(&self) -> &str {
constants::mainnet::HRP_SAPLING_PAYMENT_ADDRESS
}
fn b58_pubkey_address_prefix(&self) -> [u8; 2] {
constants::mainnet::B58_PUBKEY_ADDRESS_PREFIX
}
fn b58_script_address_prefix(&self) -> [u8; 2] {
constants::mainnet::B58_SCRIPT_ADDRESS_PREFIX
}
} }
/// Marker struct for the test network. /// Marker struct for the test network.
#[derive(Clone, Copy, Debug)] #[derive(PartialEq, Copy, Clone, Debug)]
pub struct TestNetwork; pub struct TestNetwork;
pub const TEST_NETWORK: TestNetwork = TestNetwork;
impl Parameters for TestNetwork { impl Parameters for TestNetwork {
fn activation_height(nu: NetworkUpgrade) -> Option<u32> { fn activation_height(&self, nu: NetworkUpgrade) -> Option<BlockHeight> {
match nu { match nu {
NetworkUpgrade::Overwinter => Some(207_500), NetworkUpgrade::Overwinter => Some(BlockHeight(207_500)),
NetworkUpgrade::Sapling => Some(280_000), NetworkUpgrade::Sapling => Some(BlockHeight(280_000)),
NetworkUpgrade::Blossom => Some(584_000), NetworkUpgrade::Blossom => Some(BlockHeight(584_000)),
NetworkUpgrade::Heartwood => Some(903_800), NetworkUpgrade::Heartwood => Some(BlockHeight(903_800)),
NetworkUpgrade::Canopy => Some(1_028_500), NetworkUpgrade::Canopy => Some(BlockHeight(1_028_500)),
NetworkUpgrade::Future => None, NetworkUpgrade::Future => None,
} }
} }
fn hrp_sapling_extended_full_viewing_key(&self) -> &str {
constants::testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY
}
fn hrp_sapling_payment_address(&self) -> &str {
constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS
}
fn b58_pubkey_address_prefix(&self) -> [u8; 2] {
constants::testnet::B58_PUBKEY_ADDRESS_PREFIX
}
fn b58_script_address_prefix(&self) -> [u8; 2] {
constants::testnet::B58_SCRIPT_ADDRESS_PREFIX
}
}
#[derive(PartialEq, Copy, Clone, Debug)]
pub enum Network {
MainNetwork,
TestNetwork,
}
impl Parameters for Network {
fn activation_height(&self, nu: NetworkUpgrade) -> Option<BlockHeight> {
match self {
Network::MainNetwork => MAIN_NETWORK.activation_height(nu),
Network::TestNetwork => TEST_NETWORK.activation_height(nu),
}
}
fn hrp_sapling_extended_full_viewing_key(&self) -> &str {
match self {
Network::MainNetwork => MAIN_NETWORK.hrp_sapling_extended_full_viewing_key(),
Network::TestNetwork => TEST_NETWORK.hrp_sapling_extended_full_viewing_key(),
}
}
fn hrp_sapling_payment_address(&self) -> &str {
match self {
Network::MainNetwork => MAIN_NETWORK.hrp_sapling_payment_address(),
Network::TestNetwork => TEST_NETWORK.hrp_sapling_payment_address(),
}
}
fn b58_pubkey_address_prefix(&self) -> [u8; 2] {
match self {
Network::MainNetwork => MAIN_NETWORK.b58_pubkey_address_prefix(),
Network::TestNetwork => TEST_NETWORK.b58_pubkey_address_prefix(),
}
}
fn b58_script_address_prefix(&self) -> [u8; 2] {
match self {
Network::MainNetwork => MAIN_NETWORK.b58_script_address_prefix(),
Network::TestNetwork => TEST_NETWORK.b58_script_address_prefix(),
}
}
} }
/// An event that occurs at a specified height on the Zcash chain, at which point the /// An event that occurs at a specified height on the Zcash chain, at which point the
@ -191,9 +405,9 @@ impl BranchId {
/// the given height. /// the given height.
/// ///
/// This is the branch ID that should be used when creating transactions. /// This is the branch ID that should be used when creating transactions.
pub fn for_height<C: Parameters>(height: u32) -> Self { pub fn for_height<P: Parameters>(parameters: &P, height: BlockHeight) -> Self {
for nu in UPGRADES_IN_ORDER.iter().rev() { for nu in UPGRADES_IN_ORDER.iter().rev() {
if C::is_nu_active(*nu, height) { if parameters.is_nu_active(*nu, height) {
return nu.branch_id(); return nu.branch_id();
} }
} }
@ -207,7 +421,9 @@ impl BranchId {
mod tests { mod tests {
use std::convert::TryFrom; use std::convert::TryFrom;
use super::{BranchId, MainNetwork, NetworkUpgrade, Parameters, UPGRADES_IN_ORDER}; use super::{
BlockHeight, BranchId, NetworkUpgrade, Parameters, MAIN_NETWORK, UPGRADES_IN_ORDER,
};
#[test] #[test]
fn nu_ordering() { fn nu_ordering() {
@ -215,12 +431,10 @@ mod tests {
let nu_a = UPGRADES_IN_ORDER[i - 1]; let nu_a = UPGRADES_IN_ORDER[i - 1];
let nu_b = UPGRADES_IN_ORDER[i]; let nu_b = UPGRADES_IN_ORDER[i];
match ( match (
MainNetwork::activation_height(nu_a), MAIN_NETWORK.activation_height(nu_a),
MainNetwork::activation_height(nu_b), MAIN_NETWORK.activation_height(nu_b),
) { ) {
(Some(a), Some(b)) if a < b => (), (a, b) if a < b => (),
(Some(_), None) => (),
(None, None) => (),
_ => panic!( _ => panic!(
"{} should not be before {} in UPGRADES_IN_ORDER", "{} should not be before {} in UPGRADES_IN_ORDER",
nu_a, nu_b nu_a, nu_b
@ -231,15 +445,9 @@ mod tests {
#[test] #[test]
fn nu_is_active() { fn nu_is_active() {
assert!(!MainNetwork::is_nu_active(NetworkUpgrade::Overwinter, 0)); assert!(!MAIN_NETWORK.is_nu_active(NetworkUpgrade::Overwinter, BlockHeight(0)));
assert!(!MainNetwork::is_nu_active( assert!(!MAIN_NETWORK.is_nu_active(NetworkUpgrade::Overwinter, BlockHeight(347_499)));
NetworkUpgrade::Overwinter, assert!(MAIN_NETWORK.is_nu_active(NetworkUpgrade::Overwinter, BlockHeight(347_500)));
347_499
));
assert!(MainNetwork::is_nu_active(
NetworkUpgrade::Overwinter,
347_500
));
} }
#[test] #[test]
@ -250,25 +458,28 @@ mod tests {
#[test] #[test]
fn branch_id_for_height() { fn branch_id_for_height() {
assert_eq!(BranchId::for_height::<MainNetwork>(0), BranchId::Sprout,);
assert_eq!( assert_eq!(
BranchId::for_height::<MainNetwork>(419_199), BranchId::for_height(&MAIN_NETWORK, BlockHeight(0)),
BranchId::Sprout,
);
assert_eq!(
BranchId::for_height(&MAIN_NETWORK, BlockHeight(419_199)),
BranchId::Overwinter, BranchId::Overwinter,
); );
assert_eq!( assert_eq!(
BranchId::for_height::<MainNetwork>(419_200), BranchId::for_height(&MAIN_NETWORK, BlockHeight(419_200)),
BranchId::Sapling, BranchId::Sapling,
); );
assert_eq!( assert_eq!(
BranchId::for_height::<MainNetwork>(903_000), BranchId::for_height(&MAIN_NETWORK, BlockHeight(903_000)),
BranchId::Heartwood, BranchId::Heartwood,
); );
assert_eq!( assert_eq!(
BranchId::for_height::<MainNetwork>(1_046_400), BranchId::for_height(&MAIN_NETWORK, BlockHeight(1_046_400)),
BranchId::Canopy, BranchId::Canopy,
); );
assert_eq!( assert_eq!(
BranchId::for_height::<MainNetwork>(5_000_000), BranchId::for_height(&MAIN_NETWORK, BlockHeight(5_000_000)),
BranchId::Canopy, BranchId::Canopy,
); );
} }

View File

@ -5,6 +5,10 @@ use group::Group;
use jubjub::SubgroupPoint; use jubjub::SubgroupPoint;
use lazy_static::lazy_static; use lazy_static::lazy_static;
pub mod mainnet;
pub mod regtest;
pub mod testnet;
/// First 64 bytes of the BLAKE2s input during group hash. /// First 64 bytes of the BLAKE2s input during group hash.
/// This is chosen to be some random string that we couldn't have anticipated when we designed /// This is chosen to be some random string that we couldn't have anticipated when we designed
/// the algorithm, for rigidity purposes. /// the algorithm, for rigidity purposes.

View File

@ -113,12 +113,12 @@ impl<E: fmt::Display> fmt::Display for Error<E> {
/// This is the primary trait which must be implemented by an extension type /// This is the primary trait which must be implemented by an extension type
/// for that type to be eligible for inclusion in Zcash consensus rules. /// for that type to be eligible for inclusion in Zcash consensus rules.
pub trait Extension<C> { pub trait Extension<C> {
/// Extension-specific precondition type. The extension will need to implement /// Extension-specific precondition type. The extension will need to implement
/// [`FromPayload<Error = Self::Error>`] for this type in order for their /// [`FromPayload<Error = Self::Error>`] for this type in order for their
/// extension to be eligible for integration into consensus rules. /// extension to be eligible for integration into consensus rules.
type P; type P;
/// Extension-specific witness type. The extension will need to implement /// Extension-specific witness type. The extension will need to implement
/// [`FromPayload<Error = Self::Error>`] for this type in order for their /// [`FromPayload<Error = Self::Error>`] for this type in order for their
/// extension to be eligible for integration into consensus rules. /// extension to be eligible for integration into consensus rules.
type W; type W;
@ -137,7 +137,7 @@ pub trait Extension<C> {
) -> Result<(), Self::Error>; ) -> Result<(), Self::Error>;
/// This is a convenience method intended for use by consensus nodes at the integration /// This is a convenience method intended for use by consensus nodes at the integration
/// point to provide easy interoperation with the opaque, cross-extension /// point to provide easy interoperation with the opaque, cross-extension
/// `Precondition` and `Witness` types. /// `Precondition` and `Witness` types.
fn verify( fn verify(
&self, &self,

View File

@ -1,7 +1,7 @@
//! Implementation of in-band secret distribution for Zcash transactions. //! Implementation of in-band secret distribution for Zcash transactions.
use crate::{ use crate::{
consensus::{self, NetworkUpgrade, ZIP212_GRACE_PERIOD}, consensus::{self, BlockHeight, NetworkUpgrade::Canopy, ZIP212_GRACE_PERIOD},
primitives::{Diversifier, Note, PaymentAddress, Rseed}, primitives::{Diversifier, Note, PaymentAddress, Rseed},
}; };
use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams}; use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
@ -359,14 +359,15 @@ impl<R: RngCore + CryptoRng> SaplingNoteEncryption<R> {
} }
fn parse_note_plaintext_without_memo<P: consensus::Parameters>( fn parse_note_plaintext_without_memo<P: consensus::Parameters>(
height: u32, params: &P,
height: BlockHeight,
ivk: &jubjub::Fr, ivk: &jubjub::Fr,
epk: &jubjub::SubgroupPoint, epk: &jubjub::SubgroupPoint,
cmu: &bls12_381::Scalar, cmu: &bls12_381::Scalar,
plaintext: &[u8], plaintext: &[u8],
) -> Option<(Note, PaymentAddress)> { ) -> Option<(Note, PaymentAddress)> {
// Check note plaintext version // Check note plaintext version
if !plaintext_version_is_valid::<P>(height, plaintext[0]) { if !plaintext_version_is_valid(params, height, plaintext[0]) {
return None; return None;
} }
@ -406,11 +407,14 @@ fn parse_note_plaintext_without_memo<P: consensus::Parameters>(
Some((note, to)) Some((note, to))
} }
pub fn plaintext_version_is_valid<P: consensus::Parameters>(height: u32, leadbyte: u8) -> bool { pub fn plaintext_version_is_valid<P: consensus::Parameters>(
if P::is_nu_active(NetworkUpgrade::Canopy, height) { params: &P,
let grace_period_end_height = P::activation_height(NetworkUpgrade::Canopy) height: BlockHeight,
.expect("Should have Canopy activation height") leadbyte: u8,
+ ZIP212_GRACE_PERIOD; ) -> bool {
if params.is_nu_active(Canopy, height) {
let grace_period_end_height =
params.activation_height(Canopy).unwrap() + ZIP212_GRACE_PERIOD;
if height < grace_period_end_height && leadbyte != 0x01 && leadbyte != 0x02 { if height < grace_period_end_height && leadbyte != 0x01 && leadbyte != 0x02 {
// non-{0x01,0x02} received after Canopy activation and before grace period has elapsed // non-{0x01,0x02} received after Canopy activation and before grace period has elapsed
@ -435,7 +439,8 @@ pub fn plaintext_version_is_valid<P: consensus::Parameters>(height: u32, leadbyt
/// ///
/// Implements section 4.17.2 of the Zcash Protocol Specification. /// Implements section 4.17.2 of the Zcash Protocol Specification.
pub fn try_sapling_note_decryption<P: consensus::Parameters>( pub fn try_sapling_note_decryption<P: consensus::Parameters>(
height: u32, params: &P,
height: BlockHeight,
ivk: &jubjub::Fr, ivk: &jubjub::Fr,
epk: &jubjub::SubgroupPoint, epk: &jubjub::SubgroupPoint,
cmu: &bls12_381::Scalar, cmu: &bls12_381::Scalar,
@ -460,7 +465,7 @@ pub fn try_sapling_note_decryption<P: consensus::Parameters>(
NOTE_PLAINTEXT_SIZE NOTE_PLAINTEXT_SIZE
); );
let (note, to) = parse_note_plaintext_without_memo::<P>(height, ivk, epk, cmu, &plaintext)?; let (note, to) = parse_note_plaintext_without_memo(params, height, ivk, epk, cmu, &plaintext)?;
let mut memo = [0u8; 512]; let mut memo = [0u8; 512];
memo.copy_from_slice(&plaintext[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE]); memo.copy_from_slice(&plaintext[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE]);
@ -478,7 +483,8 @@ pub fn try_sapling_note_decryption<P: consensus::Parameters>(
/// ///
/// [`ZIP 307`]: https://zips.z.cash/zip-0307 /// [`ZIP 307`]: https://zips.z.cash/zip-0307
pub fn try_sapling_compact_note_decryption<P: consensus::Parameters>( pub fn try_sapling_compact_note_decryption<P: consensus::Parameters>(
height: u32, params: &P,
height: BlockHeight,
ivk: &jubjub::Fr, ivk: &jubjub::Fr,
epk: &jubjub::SubgroupPoint, epk: &jubjub::SubgroupPoint,
cmu: &bls12_381::Scalar, cmu: &bls12_381::Scalar,
@ -494,7 +500,7 @@ pub fn try_sapling_compact_note_decryption<P: consensus::Parameters>(
plaintext.copy_from_slice(&enc_ciphertext); plaintext.copy_from_slice(&enc_ciphertext);
ChaCha20Ietf::xor(key.as_bytes(), &[0u8; 12], 1, &mut plaintext); ChaCha20Ietf::xor(key.as_bytes(), &[0u8; 12], 1, &mut plaintext);
parse_note_plaintext_without_memo::<P>(height, ivk, epk, cmu, &plaintext) parse_note_plaintext_without_memo(params, height, ivk, epk, cmu, &plaintext)
} }
/// Recovery of the full note plaintext by the sender. /// Recovery of the full note plaintext by the sender.
@ -506,7 +512,8 @@ pub fn try_sapling_compact_note_decryption<P: consensus::Parameters>(
/// Implements part of section 4.17.3 of the Zcash Protocol Specification. /// Implements part of section 4.17.3 of the Zcash Protocol Specification.
/// For decryption using a Full Viewing Key see [`try_sapling_output_recovery`]. /// For decryption using a Full Viewing Key see [`try_sapling_output_recovery`].
pub fn try_sapling_output_recovery_with_ock<P: consensus::Parameters>( pub fn try_sapling_output_recovery_with_ock<P: consensus::Parameters>(
height: u32, params: &P,
height: BlockHeight,
ock: &OutgoingCipherKey, ock: &OutgoingCipherKey,
cmu: &bls12_381::Scalar, cmu: &bls12_381::Scalar,
epk: &jubjub::SubgroupPoint, epk: &jubjub::SubgroupPoint,
@ -558,7 +565,7 @@ pub fn try_sapling_output_recovery_with_ock<P: consensus::Parameters>(
); );
// Check note plaintext version // Check note plaintext version
if !plaintext_version_is_valid::<P>(height, plaintext[0]) { if !plaintext_version_is_valid(params, height, plaintext[0]) {
return None; return None;
} }
@ -612,7 +619,8 @@ pub fn try_sapling_output_recovery_with_ock<P: consensus::Parameters>(
/// ///
/// Implements section 4.17.3 of the Zcash Protocol Specification. /// Implements section 4.17.3 of the Zcash Protocol Specification.
pub fn try_sapling_output_recovery<P: consensus::Parameters>( pub fn try_sapling_output_recovery<P: consensus::Parameters>(
height: u32, params: &P,
height: BlockHeight,
ovk: &OutgoingViewingKey, ovk: &OutgoingViewingKey,
cv: &jubjub::ExtendedPoint, cv: &jubjub::ExtendedPoint,
cmu: &bls12_381::Scalar, cmu: &bls12_381::Scalar,
@ -621,6 +629,7 @@ pub fn try_sapling_output_recovery<P: consensus::Parameters>(
out_ciphertext: &[u8], out_ciphertext: &[u8],
) -> Option<(Note, PaymentAddress, Memo)> { ) -> Option<(Note, PaymentAddress, Memo)> {
try_sapling_output_recovery_with_ock::<P>( try_sapling_output_recovery_with_ock::<P>(
params,
height, height,
&prf_ock(&ovk, &cv, &cmu, &epk), &prf_ock(&ovk, &cv, &cmu, &epk),
cmu, cmu,
@ -648,11 +657,12 @@ mod tests {
COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE,
OUT_PLAINTEXT_SIZE, OUT_PLAINTEXT_SIZE,
}; };
use crate::{ use crate::{
consensus::{ consensus::{
NetworkUpgrade, BlockHeight,
NetworkUpgrade::{Canopy, Sapling}, NetworkUpgrade::{Canopy, Sapling},
Parameters, TestNetwork, ZIP212_GRACE_PERIOD, Parameters, TEST_NETWORK, ZIP212_GRACE_PERIOD,
}, },
keys::OutgoingViewingKey, keys::OutgoingViewingKey,
primitives::{Diversifier, PaymentAddress, Rseed, ValueCommitment}, primitives::{Diversifier, PaymentAddress, Rseed, ValueCommitment},
@ -776,7 +786,7 @@ mod tests {
} }
fn random_enc_ciphertext<R: RngCore + CryptoRng>( fn random_enc_ciphertext<R: RngCore + CryptoRng>(
height: u32, height: BlockHeight,
mut rng: &mut R, mut rng: &mut R,
) -> ( ) -> (
OutgoingViewingKey, OutgoingViewingKey,
@ -793,7 +803,8 @@ mod tests {
let (ovk, ock, ivk, cv, cmu, epk, enc_ciphertext, out_ciphertext) = let (ovk, ock, ivk, cv, cmu, epk, enc_ciphertext, out_ciphertext) =
random_enc_ciphertext_with(height, ivk, rng); random_enc_ciphertext_with(height, ivk, rng);
assert!(try_sapling_note_decryption::<TestNetwork>( assert!(try_sapling_note_decryption(
&TEST_NETWORK,
height, height,
&ivk, &ivk,
&epk, &epk,
@ -801,7 +812,8 @@ mod tests {
&enc_ciphertext &enc_ciphertext
) )
.is_some()); .is_some());
assert!(try_sapling_compact_note_decryption::<TestNetwork>( assert!(try_sapling_compact_note_decryption(
&TEST_NETWORK,
height, height,
&ivk, &ivk,
&epk, &epk,
@ -810,7 +822,8 @@ mod tests {
) )
.is_some()); .is_some());
let ovk_output_recovery = try_sapling_output_recovery::<TestNetwork>( let ovk_output_recovery = try_sapling_output_recovery(
&TEST_NETWORK,
height, height,
&ovk, &ovk,
&cv, &cv,
@ -819,7 +832,9 @@ mod tests {
&enc_ciphertext, &enc_ciphertext,
&out_ciphertext, &out_ciphertext,
); );
let ock_output_recovery = try_sapling_output_recovery_with_ock::<TestNetwork>(
let ock_output_recovery = try_sapling_output_recovery_with_ock(
&TEST_NETWORK,
height, height,
&ock, &ock,
&cmu, &cmu,
@ -835,7 +850,7 @@ mod tests {
} }
fn random_enc_ciphertext_with<R: RngCore + CryptoRng>( fn random_enc_ciphertext_with<R: RngCore + CryptoRng>(
height: u32, height: BlockHeight,
ivk: jubjub::Fr, ivk: jubjub::Fr,
mut rng: &mut R, mut rng: &mut R,
) -> ( ) -> (
@ -860,7 +875,7 @@ mod tests {
}; };
let cv = value_commitment.commitment().into(); let cv = value_commitment.commitment().into();
let rseed = generate_random_rseed::<TestNetwork, R>(height, &mut rng); let rseed = generate_random_rseed(&TEST_NETWORK, height, &mut rng);
let note = pa.create_note(value, rseed).unwrap(); let note = pa.create_note(value, rseed).unwrap();
let cmu = note.cmu(); let cmu = note.cmu();
@ -962,15 +977,16 @@ mod tests {
fn decryption_with_invalid_ivk() { fn decryption_with_invalid_ivk() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
let (_, _, _, _, cmu, epk, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng); let (_, _, _, _, cmu, epk, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng);
assert_eq!( assert_eq!(
try_sapling_note_decryption::<TestNetwork>( try_sapling_note_decryption(
&TEST_NETWORK,
height, height,
&jubjub::Fr::random(&mut rng), &jubjub::Fr::random(&mut rng),
&epk, &epk,
@ -986,15 +1002,16 @@ mod tests {
fn decryption_with_invalid_epk() { fn decryption_with_invalid_epk() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
let (_, _, ivk, _, cmu, _, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng); let (_, _, ivk, _, cmu, _, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng);
assert_eq!( assert_eq!(
try_sapling_note_decryption::<TestNetwork>( try_sapling_note_decryption(
&TEST_NETWORK,
height, height,
&ivk, &ivk,
&jubjub::SubgroupPoint::random(&mut rng), &jubjub::SubgroupPoint::random(&mut rng),
@ -1010,15 +1027,16 @@ mod tests {
fn decryption_with_invalid_cmu() { fn decryption_with_invalid_cmu() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
let (_, _, ivk, _, _, epk, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng); let (_, _, ivk, _, _, epk, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng);
assert_eq!( assert_eq!(
try_sapling_note_decryption::<TestNetwork>( try_sapling_note_decryption(
&TEST_NETWORK,
height, height,
&ivk, &ivk,
&epk, &epk,
@ -1034,8 +1052,8 @@ mod tests {
fn decryption_with_invalid_tag() { fn decryption_with_invalid_tag() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
@ -1044,7 +1062,8 @@ mod tests {
enc_ciphertext[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff; enc_ciphertext[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff;
assert_eq!( assert_eq!(
try_sapling_note_decryption::<TestNetwork>( try_sapling_note_decryption(
&TEST_NETWORK,
height, height,
&ivk, &ivk,
&epk, &epk,
@ -1059,7 +1078,7 @@ mod tests {
#[test] #[test]
fn decryption_with_invalid_version_byte() { fn decryption_with_invalid_version_byte() {
let mut rng = OsRng; let mut rng = OsRng;
let canopy_activation_height = TestNetwork::activation_height(Canopy).unwrap(); let canopy_activation_height = TEST_NETWORK.activation_height(Canopy).unwrap();
let heights = [ let heights = [
canopy_activation_height - 1, canopy_activation_height - 1,
canopy_activation_height, canopy_activation_height,
@ -1081,7 +1100,8 @@ mod tests {
|pt| pt[0] = leadbyte, |pt| pt[0] = leadbyte,
); );
assert_eq!( assert_eq!(
try_sapling_note_decryption::<TestNetwork>( try_sapling_note_decryption(
&TEST_NETWORK,
height, height,
&ivk, &ivk,
&epk, &epk,
@ -1097,8 +1117,8 @@ mod tests {
fn decryption_with_invalid_diversifier() { fn decryption_with_invalid_diversifier() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
@ -1115,7 +1135,8 @@ mod tests {
|pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0), |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0),
); );
assert_eq!( assert_eq!(
try_sapling_note_decryption::<TestNetwork>( try_sapling_note_decryption(
&TEST_NETWORK,
height, height,
&ivk, &ivk,
&epk, &epk,
@ -1131,8 +1152,8 @@ mod tests {
fn decryption_with_incorrect_diversifier() { fn decryption_with_incorrect_diversifier() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
@ -1148,8 +1169,10 @@ mod tests {
&out_ciphertext, &out_ciphertext,
|pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0), |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0),
); );
assert_eq!( assert_eq!(
try_sapling_note_decryption::<TestNetwork>( try_sapling_note_decryption(
&TEST_NETWORK,
height, height,
&ivk, &ivk,
&epk, &epk,
@ -1165,15 +1188,16 @@ mod tests {
fn compact_decryption_with_invalid_ivk() { fn compact_decryption_with_invalid_ivk() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
let (_, _, _, _, cmu, epk, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng); let (_, _, _, _, cmu, epk, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng);
assert_eq!( assert_eq!(
try_sapling_compact_note_decryption::<TestNetwork>( try_sapling_compact_note_decryption(
&TEST_NETWORK,
height, height,
&jubjub::Fr::random(&mut rng), &jubjub::Fr::random(&mut rng),
&epk, &epk,
@ -1189,15 +1213,16 @@ mod tests {
fn compact_decryption_with_invalid_epk() { fn compact_decryption_with_invalid_epk() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
let (_, _, ivk, _, cmu, _, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng); let (_, _, ivk, _, cmu, _, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng);
assert_eq!( assert_eq!(
try_sapling_compact_note_decryption::<TestNetwork>( try_sapling_compact_note_decryption(
&TEST_NETWORK,
height, height,
&ivk, &ivk,
&jubjub::SubgroupPoint::random(&mut rng), &jubjub::SubgroupPoint::random(&mut rng),
@ -1213,15 +1238,16 @@ mod tests {
fn compact_decryption_with_invalid_cmu() { fn compact_decryption_with_invalid_cmu() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
let (_, _, ivk, _, _, epk, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng); let (_, _, ivk, _, _, epk, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng);
assert_eq!( assert_eq!(
try_sapling_compact_note_decryption::<TestNetwork>( try_sapling_compact_note_decryption(
&TEST_NETWORK,
height, height,
&ivk, &ivk,
&epk, &epk,
@ -1236,7 +1262,7 @@ mod tests {
#[test] #[test]
fn compact_decryption_with_invalid_version_byte() { fn compact_decryption_with_invalid_version_byte() {
let mut rng = OsRng; let mut rng = OsRng;
let canopy_activation_height = TestNetwork::activation_height(Canopy).unwrap(); let canopy_activation_height = TEST_NETWORK.activation_height(Canopy).unwrap();
let heights = [ let heights = [
canopy_activation_height - 1, canopy_activation_height - 1,
canopy_activation_height, canopy_activation_height,
@ -1258,7 +1284,8 @@ mod tests {
|pt| pt[0] = leadbyte, |pt| pt[0] = leadbyte,
); );
assert_eq!( assert_eq!(
try_sapling_compact_note_decryption::<TestNetwork>( try_sapling_compact_note_decryption(
&TEST_NETWORK,
height, height,
&ivk, &ivk,
&epk, &epk,
@ -1274,8 +1301,8 @@ mod tests {
fn compact_decryption_with_invalid_diversifier() { fn compact_decryption_with_invalid_diversifier() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
@ -1292,7 +1319,8 @@ mod tests {
|pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0), |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0),
); );
assert_eq!( assert_eq!(
try_sapling_compact_note_decryption::<TestNetwork>( try_sapling_compact_note_decryption(
&TEST_NETWORK,
height, height,
&ivk, &ivk,
&epk, &epk,
@ -1308,8 +1336,8 @@ mod tests {
fn compact_decryption_with_incorrect_diversifier() { fn compact_decryption_with_incorrect_diversifier() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
@ -1326,7 +1354,8 @@ mod tests {
|pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0), |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0),
); );
assert_eq!( assert_eq!(
try_sapling_compact_note_decryption::<TestNetwork>( try_sapling_compact_note_decryption(
&TEST_NETWORK,
height, height,
&ivk, &ivk,
&epk, &epk,
@ -1342,8 +1371,8 @@ mod tests {
fn recovery_with_invalid_ovk() { fn recovery_with_invalid_ovk() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
@ -1352,7 +1381,8 @@ mod tests {
ovk.0[0] ^= 0xff; ovk.0[0] ^= 0xff;
assert_eq!( assert_eq!(
try_sapling_output_recovery::<TestNetwork>( try_sapling_output_recovery(
&TEST_NETWORK,
height, height,
&ovk, &ovk,
&cv, &cv,
@ -1370,8 +1400,8 @@ mod tests {
fn recovery_with_invalid_ock() { fn recovery_with_invalid_ock() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
@ -1379,7 +1409,8 @@ mod tests {
random_enc_ciphertext(height, &mut rng); random_enc_ciphertext(height, &mut rng);
assert_eq!( assert_eq!(
try_sapling_output_recovery_with_ock::<TestNetwork>( try_sapling_output_recovery_with_ock(
&TEST_NETWORK,
height, height,
&OutgoingCipherKey([0u8; 32]), &OutgoingCipherKey([0u8; 32]),
&cmu, &cmu,
@ -1396,8 +1427,8 @@ mod tests {
fn recovery_with_invalid_cv() { fn recovery_with_invalid_cv() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
@ -1405,7 +1436,8 @@ mod tests {
random_enc_ciphertext(height, &mut rng); random_enc_ciphertext(height, &mut rng);
assert_eq!( assert_eq!(
try_sapling_output_recovery::<TestNetwork>( try_sapling_output_recovery(
&TEST_NETWORK,
height, height,
&ovk, &ovk,
&jubjub::ExtendedPoint::random(&mut rng), &jubjub::ExtendedPoint::random(&mut rng),
@ -1423,8 +1455,8 @@ mod tests {
fn recovery_with_invalid_cmu() { fn recovery_with_invalid_cmu() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
@ -1432,7 +1464,8 @@ mod tests {
random_enc_ciphertext(height, &mut rng); random_enc_ciphertext(height, &mut rng);
assert_eq!( assert_eq!(
try_sapling_output_recovery::<TestNetwork>( try_sapling_output_recovery(
&TEST_NETWORK,
height, height,
&ovk, &ovk,
&cv, &cv,
@ -1443,8 +1476,10 @@ mod tests {
), ),
None None
); );
assert_eq!( assert_eq!(
try_sapling_output_recovery_with_ock::<TestNetwork>( try_sapling_output_recovery_with_ock(
&TEST_NETWORK,
height, height,
&ock, &ock,
&bls12_381::Scalar::random(&mut rng), &bls12_381::Scalar::random(&mut rng),
@ -1461,8 +1496,8 @@ mod tests {
fn recovery_with_invalid_epk() { fn recovery_with_invalid_epk() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
@ -1470,7 +1505,8 @@ mod tests {
random_enc_ciphertext(height, &mut rng); random_enc_ciphertext(height, &mut rng);
assert_eq!( assert_eq!(
try_sapling_output_recovery::<TestNetwork>( try_sapling_output_recovery(
&TEST_NETWORK,
height, height,
&ovk, &ovk,
&cv, &cv,
@ -1481,8 +1517,10 @@ mod tests {
), ),
None None
); );
assert_eq!( assert_eq!(
try_sapling_output_recovery_with_ock::<TestNetwork>( try_sapling_output_recovery_with_ock(
&TEST_NETWORK,
height, height,
&ock, &ock,
&cmu, &cmu,
@ -1499,8 +1537,8 @@ mod tests {
fn recovery_with_invalid_enc_tag() { fn recovery_with_invalid_enc_tag() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
@ -1509,7 +1547,8 @@ mod tests {
enc_ciphertext[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff; enc_ciphertext[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff;
assert_eq!( assert_eq!(
try_sapling_output_recovery::<TestNetwork>( try_sapling_output_recovery(
&TEST_NETWORK,
height, height,
&ovk, &ovk,
&cv, &cv,
@ -1521,7 +1560,8 @@ mod tests {
None None
); );
assert_eq!( assert_eq!(
try_sapling_output_recovery_with_ock::<TestNetwork>( try_sapling_output_recovery_with_ock(
&TEST_NETWORK,
height, height,
&ock, &ock,
&cmu, &cmu,
@ -1538,8 +1578,8 @@ mod tests {
fn recovery_with_invalid_out_tag() { fn recovery_with_invalid_out_tag() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
@ -1548,7 +1588,8 @@ mod tests {
out_ciphertext[OUT_CIPHERTEXT_SIZE - 1] ^= 0xff; out_ciphertext[OUT_CIPHERTEXT_SIZE - 1] ^= 0xff;
assert_eq!( assert_eq!(
try_sapling_output_recovery::<TestNetwork>( try_sapling_output_recovery(
&TEST_NETWORK,
height, height,
&ovk, &ovk,
&cv, &cv,
@ -1560,7 +1601,8 @@ mod tests {
None None
); );
assert_eq!( assert_eq!(
try_sapling_output_recovery_with_ock::<TestNetwork>( try_sapling_output_recovery_with_ock(
&TEST_NETWORK,
height, height,
&ock, &ock,
&cmu, &cmu,
@ -1576,7 +1618,7 @@ mod tests {
#[test] #[test]
fn recovery_with_invalid_version_byte() { fn recovery_with_invalid_version_byte() {
let mut rng = OsRng; let mut rng = OsRng;
let canopy_activation_height = TestNetwork::activation_height(Canopy).unwrap(); let canopy_activation_height = TEST_NETWORK.activation_height(Canopy).unwrap();
let heights = [ let heights = [
canopy_activation_height - 1, canopy_activation_height - 1,
canopy_activation_height, canopy_activation_height,
@ -1598,7 +1640,8 @@ mod tests {
|pt| pt[0] = leadbyte, |pt| pt[0] = leadbyte,
); );
assert_eq!( assert_eq!(
try_sapling_output_recovery::<TestNetwork>( try_sapling_output_recovery(
&TEST_NETWORK,
height, height,
&ovk, &ovk,
&cv, &cv,
@ -1610,7 +1653,8 @@ mod tests {
None None
); );
assert_eq!( assert_eq!(
try_sapling_output_recovery_with_ock::<TestNetwork>( try_sapling_output_recovery_with_ock(
&TEST_NETWORK,
height, height,
&ock, &ock,
&cmu, &cmu,
@ -1627,8 +1671,8 @@ mod tests {
fn recovery_with_invalid_diversifier() { fn recovery_with_invalid_diversifier() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
@ -1645,7 +1689,8 @@ mod tests {
|pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0), |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0),
); );
assert_eq!( assert_eq!(
try_sapling_output_recovery::<TestNetwork>( try_sapling_output_recovery(
&TEST_NETWORK,
height, height,
&ovk, &ovk,
&cv, &cv,
@ -1657,7 +1702,8 @@ mod tests {
None None
); );
assert_eq!( assert_eq!(
try_sapling_output_recovery_with_ock::<TestNetwork>( try_sapling_output_recovery_with_ock(
&TEST_NETWORK,
height, height,
&ock, &ock,
&cmu, &cmu,
@ -1674,8 +1720,8 @@ mod tests {
fn recovery_with_incorrect_diversifier() { fn recovery_with_incorrect_diversifier() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
@ -1692,7 +1738,8 @@ mod tests {
|pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0), |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0),
); );
assert_eq!( assert_eq!(
try_sapling_output_recovery::<TestNetwork>( try_sapling_output_recovery(
&TEST_NETWORK,
height, height,
&ovk, &ovk,
&cv, &cv,
@ -1704,7 +1751,8 @@ mod tests {
None None
); );
assert_eq!( assert_eq!(
try_sapling_output_recovery_with_ock::<TestNetwork>( try_sapling_output_recovery_with_ock(
&TEST_NETWORK,
height, height,
&ock, &ock,
&cmu, &cmu,
@ -1721,8 +1769,8 @@ mod tests {
fn recovery_with_invalid_pk_d() { fn recovery_with_invalid_pk_d() {
let mut rng = OsRng; let mut rng = OsRng;
let heights = [ let heights = [
TestNetwork::activation_height(Sapling).unwrap(), TEST_NETWORK.activation_height(Sapling).unwrap(),
TestNetwork::activation_height(Canopy).unwrap(), TEST_NETWORK.activation_height(Canopy).unwrap(),
]; ];
for &height in heights.iter() { for &height in heights.iter() {
@ -1731,7 +1779,8 @@ mod tests {
random_enc_ciphertext_with(height, ivk, &mut rng); random_enc_ciphertext_with(height, ivk, &mut rng);
assert_eq!( assert_eq!(
try_sapling_output_recovery::<TestNetwork>( try_sapling_output_recovery(
&TEST_NETWORK,
height, height,
&ovk, &ovk,
&cv, &cv,
@ -1743,7 +1792,8 @@ mod tests {
None None
); );
assert_eq!( assert_eq!(
try_sapling_output_recovery_with_ock::<TestNetwork>( try_sapling_output_recovery_with_ock(
&TEST_NETWORK,
height, height,
&ock, &ock,
&cmu, &cmu,
@ -1778,8 +1828,7 @@ mod tests {
}; };
} }
let height = TestNetwork::activation_height(NetworkUpgrade::Sapling) let height = TEST_NETWORK.activation_height(Sapling).unwrap();
.expect("Should have Sapling activation height");
for tv in test_vectors { for tv in test_vectors {
// //
@ -1817,7 +1866,7 @@ mod tests {
// (Tested first because it only requires immutable references.) // (Tested first because it only requires immutable references.)
// //
match try_sapling_note_decryption::<TestNetwork>(height, &ivk, &epk, &cmu, &tv.c_enc) { match try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &epk, &cmu, &tv.c_enc) {
Some((decrypted_note, decrypted_to, decrypted_memo)) => { Some((decrypted_note, decrypted_to, decrypted_memo)) => {
assert_eq!(decrypted_note, note); assert_eq!(decrypted_note, note);
assert_eq!(decrypted_to, to); assert_eq!(decrypted_to, to);
@ -1826,7 +1875,8 @@ mod tests {
None => panic!("Note decryption failed"), None => panic!("Note decryption failed"),
} }
match try_sapling_compact_note_decryption::<TestNetwork>( match try_sapling_compact_note_decryption(
&TEST_NETWORK,
height, height,
&ivk, &ivk,
&epk, &epk,
@ -1840,8 +1890,15 @@ mod tests {
None => panic!("Compact note decryption failed"), None => panic!("Compact note decryption failed"),
} }
match try_sapling_output_recovery::<TestNetwork>( match try_sapling_output_recovery(
height, &ovk, &cv, &cmu, &epk, &tv.c_enc, &tv.c_out, &TEST_NETWORK,
height,
&ovk,
&cv,
&cmu,
&epk,
&tv.c_enc,
&tv.c_out,
) { ) {
Some((decrypted_note, decrypted_to, decrypted_memo)) => { Some((decrypted_note, decrypted_to, decrypted_memo)) => {
assert_eq!(decrypted_note, note); assert_eq!(decrypted_note, note);

View File

@ -9,7 +9,7 @@ use ff::Field;
use rand::{rngs::OsRng, seq::SliceRandom, CryptoRng, RngCore}; use rand::{rngs::OsRng, seq::SliceRandom, CryptoRng, RngCore};
use crate::{ use crate::{
consensus, consensus::{self, BlockHeight},
extensions::transparent::{self as tze, ExtensionTxBuilder, ToPayload}, extensions::transparent::{self as tze, ExtensionTxBuilder, ToPayload},
keys::OutgoingViewingKey, keys::OutgoingViewingKey,
legacy::TransparentAddress, legacy::TransparentAddress,
@ -94,7 +94,8 @@ pub struct SaplingOutput {
impl SaplingOutput { impl SaplingOutput {
pub fn new<R: RngCore + CryptoRng, P: consensus::Parameters>( pub fn new<R: RngCore + CryptoRng, P: consensus::Parameters>(
height: u32, params: &P,
height: BlockHeight,
rng: &mut R, rng: &mut R,
ovk: Option<OutgoingViewingKey>, ovk: Option<OutgoingViewingKey>,
to: PaymentAddress, to: PaymentAddress,
@ -109,7 +110,7 @@ impl SaplingOutput {
return Err(Error::InvalidAmount); return Err(Error::InvalidAmount);
} }
let rseed = generate_random_rseed::<P, R>(height, rng); let rseed = generate_random_rseed(params, height, rng);
let note = Note { let note = Note {
g_d, g_d,
@ -332,8 +333,9 @@ impl TransactionMetadata {
/// Generates a [`Transaction`] from its inputs and outputs. /// Generates a [`Transaction`] from its inputs and outputs.
pub struct Builder<'a, P: consensus::Parameters, R: RngCore + CryptoRng> { pub struct Builder<'a, P: consensus::Parameters, R: RngCore + CryptoRng> {
params: P,
rng: R, rng: R,
height: u32, height: BlockHeight,
mtx: TransactionData, mtx: TransactionData,
fee: Amount, fee: Amount,
anchor: Option<bls12_381::Scalar>, anchor: Option<bls12_381::Scalar>,
@ -355,8 +357,8 @@ impl<'a, P: consensus::Parameters> Builder<'a, P, OsRng> {
/// expiry delta (20 blocks). /// expiry delta (20 blocks).
/// ///
/// The fee will be set to the default fee (0.0001 ZEC). /// The fee will be set to the default fee (0.0001 ZEC).
pub fn new(height: u32) -> Self { pub fn new(params: P, height: BlockHeight) -> Self {
Builder::new_with_rng(height, OsRng) Builder::new_with_rng(params, height, OsRng)
} }
/// Creates a new `Builder` targeted for inclusion in the block with the given height, /// Creates a new `Builder` targeted for inclusion in the block with the given height,
@ -373,8 +375,8 @@ impl<'a, P: consensus::Parameters> Builder<'a, P, OsRng> {
/// The transaction will be constructed and serialized according to the Future /// The transaction will be constructed and serialized according to the Future
/// network upgrade rules. This is intended only for use in integration testing of /// network upgrade rules. This is intended only for use in integration testing of
/// new features. /// new features.
pub fn new_future(height: u32) -> Self { pub fn new_future(params: P, height: BlockHeight) -> Self {
Builder::new_with_rng_future(height, OsRng) Builder::new_with_rng_future(params, height, OsRng)
} }
} }
@ -388,8 +390,8 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
/// expiry delta (20 blocks). /// expiry delta (20 blocks).
/// ///
/// The fee will be set to the default fee (0.0001 ZEC). /// The fee will be set to the default fee (0.0001 ZEC).
pub fn new_with_rng(height: u32, rng: R) -> Builder<'a, P, R> { pub fn new_with_rng(params: P, height: BlockHeight, rng: R) -> Builder<'a, P, R> {
Self::new_with_mtx(height, rng, TransactionData::new()) Self::new_with_mtx(params, height, rng, TransactionData::new())
} }
/// Creates a new `Builder` targeted for inclusion in the block with the given height, /// Creates a new `Builder` targeted for inclusion in the block with the given height,
@ -406,15 +408,21 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
/// The transaction will be constructed and serialized according to the Future /// The transaction will be constructed and serialized according to the Future
/// network upgrade rules. This is intended only for use in integration testing of /// network upgrade rules. This is intended only for use in integration testing of
/// new features. /// new features.
pub fn new_with_rng_future(height: u32, rng: R) -> Builder<'a, P, R> { pub fn new_with_rng_future(params: P, height: BlockHeight, rng: R) -> Builder<'a, P, R> {
Self::new_with_mtx(height, rng, TransactionData::future()) Self::new_with_mtx(params, height, rng, TransactionData::future())
} }
/// Common utility function for builder construction. /// Common utility function for builder construction.
fn new_with_mtx(height: u32, rng: R, mut mtx: TransactionData) -> Builder<'a, P, R> { fn new_with_mtx(
params: P,
height: BlockHeight,
rng: R,
mut mtx: TransactionData,
) -> Builder<'a, P, R> {
mtx.expiry_height = height + DEFAULT_TX_EXPIRY_DELTA; mtx.expiry_height = height + DEFAULT_TX_EXPIRY_DELTA;
Builder { Builder {
params,
rng, rng,
height, height,
mtx, mtx,
@ -474,7 +482,15 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
value: Amount, value: Amount,
memo: Option<Memo>, memo: Option<Memo>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let output = SaplingOutput::new::<R, P>(self.height, &mut self.rng, ovk, to, value, memo)?; let output = SaplingOutput::new(
&self.params,
self.height,
&mut self.rng,
ovk,
to,
value,
memo,
)?;
self.mtx.value_balance -= value; self.mtx.value_balance -= value;
@ -695,7 +711,7 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
} }
}; };
let rseed = generate_random_rseed::<P, R>(self.height, &mut self.rng); let rseed = generate_random_rseed(&self.params, self.height, &mut self.rng);
( (
payment_address, payment_address,
@ -856,8 +872,7 @@ mod tests {
use std::marker::PhantomData; use std::marker::PhantomData;
use crate::{ use crate::{
consensus, consensus::{self, Parameters, H0, TEST_NETWORK},
consensus::TestNetwork,
legacy::TransparentAddress, legacy::TransparentAddress,
merkle_tree::{CommitmentTree, IncrementalWitness}, merkle_tree::{CommitmentTree, IncrementalWitness},
primitives::Rseed, primitives::Rseed,
@ -876,7 +891,7 @@ mod tests {
let ovk = extfvk.fvk.ovk; let ovk = extfvk.fvk.ovk;
let to = extfvk.default_address().unwrap().1; let to = extfvk.default_address().unwrap().1;
let mut builder = Builder::<TestNetwork, OsRng>::new(0); let mut builder = Builder::new(TEST_NETWORK, H0);
assert_eq!( assert_eq!(
builder.add_sapling_output(Some(ovk), to, Amount::from_i64(-1).unwrap(), None), builder.add_sapling_output(Some(ovk), to, Amount::from_i64(-1).unwrap(), None),
Err(Error::InvalidAmount) Err(Error::InvalidAmount)
@ -885,17 +900,19 @@ mod tests {
#[test] #[test]
fn binding_sig_absent_if_no_shielded_spend_or_output() { fn binding_sig_absent_if_no_shielded_spend_or_output() {
use crate::consensus::{NetworkUpgrade, Parameters}; use crate::consensus::NetworkUpgrade;
use crate::transaction::{ use crate::transaction::{
builder::{self, TransparentInputs}, builder::{self, TransparentInputs},
TransactionData, TransactionData,
}; };
let sapling_activation_height = let sapling_activation_height = TEST_NETWORK
TestNetwork::activation_height(NetworkUpgrade::Sapling).unwrap(); .activation_height(NetworkUpgrade::Sapling)
.unwrap();
// Create a builder with 0 fee, so we can construct t outputs // Create a builder with 0 fee, so we can construct t outputs
let mut builder = builder::Builder::<'_, TestNetwork, OsRng> { let mut builder = builder::Builder {
params: TEST_NETWORK,
rng: OsRng, rng: OsRng,
height: sapling_activation_height, height: sapling_activation_height,
mtx: TransactionData::new(), mtx: TransactionData::new(),
@ -937,7 +954,7 @@ mod tests {
tree.append(cmu1).unwrap(); tree.append(cmu1).unwrap();
let witness1 = IncrementalWitness::from_tree(&tree); let witness1 = IncrementalWitness::from_tree(&tree);
let mut builder = Builder::<TestNetwork, OsRng>::new(0); let mut builder = Builder::new(TEST_NETWORK, H0);
// Create a tx with a sapling spend. binding_sig should be present // Create a tx with a sapling spend. binding_sig should be present
builder builder
@ -963,7 +980,7 @@ mod tests {
#[test] #[test]
fn fails_on_negative_transparent_output() { fn fails_on_negative_transparent_output() {
let mut builder = Builder::<TestNetwork, OsRng>::new(0); let mut builder = Builder::new(TEST_NETWORK, H0);
assert_eq!( assert_eq!(
builder.add_transparent_output( builder.add_transparent_output(
&TransparentAddress::PublicKey([0; 20]), &TransparentAddress::PublicKey([0; 20]),
@ -983,7 +1000,7 @@ mod tests {
// Fails with no inputs or outputs // Fails with no inputs or outputs
// 0.0001 t-ZEC fee // 0.0001 t-ZEC fee
{ {
let builder = Builder::<TestNetwork, OsRng>::new(0); let builder = Builder::new(TEST_NETWORK, H0);
assert_eq!( assert_eq!(
builder.build(consensus::BranchId::Sapling, &MockTxProver), builder.build(consensus::BranchId::Sapling, &MockTxProver),
Err(Error::ChangeIsNegative(Amount::from_i64(-10000).unwrap())) Err(Error::ChangeIsNegative(Amount::from_i64(-10000).unwrap()))
@ -997,7 +1014,7 @@ mod tests {
// Fail if there is only a Sapling output // Fail if there is only a Sapling output
// 0.0005 z-ZEC out, 0.0001 t-ZEC fee // 0.0005 z-ZEC out, 0.0001 t-ZEC fee
{ {
let mut builder = Builder::<TestNetwork, OsRng>::new(0); let mut builder = Builder::new(TEST_NETWORK, H0);
builder builder
.add_sapling_output( .add_sapling_output(
ovk.clone(), ovk.clone(),
@ -1015,7 +1032,7 @@ mod tests {
// Fail if there is only a transparent output // Fail if there is only a transparent output
// 0.0005 t-ZEC out, 0.0001 t-ZEC fee // 0.0005 t-ZEC out, 0.0001 t-ZEC fee
{ {
let mut builder = Builder::<TestNetwork, OsRng>::new(0); let mut builder = Builder::new(TEST_NETWORK, H0);
builder builder
.add_transparent_output( .add_transparent_output(
&TransparentAddress::PublicKey([0; 20]), &TransparentAddress::PublicKey([0; 20]),
@ -1039,7 +1056,7 @@ mod tests {
// Fail if there is insufficient input // Fail if there is insufficient input
// 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.0001 t-ZEC fee, 0.00059999 z-ZEC in // 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.0001 t-ZEC fee, 0.00059999 z-ZEC in
{ {
let mut builder = Builder::<TestNetwork, OsRng>::new(0); let mut builder = Builder::new(TEST_NETWORK, H0);
builder builder
.add_sapling_spend( .add_sapling_spend(
extsk.clone(), extsk.clone(),
@ -1082,7 +1099,7 @@ mod tests {
// (Still fails because we are using a MockTxProver which doesn't correctly // (Still fails because we are using a MockTxProver which doesn't correctly
// compute bindingSig.) // compute bindingSig.)
{ {
let mut builder = Builder::<TestNetwork, OsRng>::new(0); let mut builder = Builder::new(TEST_NETWORK, H0);
builder builder
.add_sapling_spend( .add_sapling_spend(
extsk.clone(), extsk.clone(),

View File

@ -7,8 +7,7 @@ use std::fmt;
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
use std::ops::Deref; use std::ops::Deref;
use crate::redjubjub::Signature; use crate::{consensus::BlockHeight, redjubjub::Signature, serialize::Vector};
use crate::serialize::Vector;
pub mod builder; pub mod builder;
pub mod components; pub mod components;
@ -79,7 +78,7 @@ pub struct TransactionData {
pub tze_inputs: Vec<TzeIn>, pub tze_inputs: Vec<TzeIn>,
pub tze_outputs: Vec<TzeOut>, pub tze_outputs: Vec<TzeOut>,
pub lock_time: u32, pub lock_time: u32,
pub expiry_height: u32, pub expiry_height: BlockHeight,
pub value_balance: Amount, pub value_balance: Amount,
pub shielded_spends: Vec<SpendDescription>, pub shielded_spends: Vec<SpendDescription>,
pub shielded_outputs: Vec<OutputDescription>, pub shielded_outputs: Vec<OutputDescription>,
@ -139,7 +138,7 @@ impl TransactionData {
tze_inputs: vec![], tze_inputs: vec![],
tze_outputs: vec![], tze_outputs: vec![],
lock_time: 0, lock_time: 0,
expiry_height: 0, expiry_height: 0u32.into(),
value_balance: Amount::zero(), value_balance: Amount::zero(),
shielded_spends: vec![], shielded_spends: vec![],
shielded_outputs: vec![], shielded_outputs: vec![],
@ -160,7 +159,7 @@ impl TransactionData {
tze_inputs: vec![], tze_inputs: vec![],
tze_outputs: vec![], tze_outputs: vec![],
lock_time: 0, lock_time: 0,
expiry_height: 0, expiry_height: 0u32.into(),
value_balance: Amount::zero(), value_balance: Amount::zero(),
shielded_spends: vec![], shielded_spends: vec![],
shielded_outputs: vec![], shielded_outputs: vec![],
@ -241,10 +240,10 @@ impl Transaction {
}; };
let lock_time = reader.read_u32::<LittleEndian>()?; let lock_time = reader.read_u32::<LittleEndian>()?;
let expiry_height = if is_overwinter_v3 || is_sapling_v4 || has_tze { let expiry_height: BlockHeight = if is_overwinter_v3 || is_sapling_v4 || has_tze {
reader.read_u32::<LittleEndian>()? reader.read_u32::<LittleEndian>()?.into()
} else { } else {
0 0u32.into()
}; };
let (value_balance, shielded_spends, shielded_outputs) = if is_sapling_v4 || has_tze { let (value_balance, shielded_spends, shielded_outputs) = if is_sapling_v4 || has_tze {
@ -338,7 +337,7 @@ impl Transaction {
} }
writer.write_u32::<LittleEndian>(self.lock_time)?; writer.write_u32::<LittleEndian>(self.lock_time)?;
if is_overwinter_v3 || is_sapling_v4 || has_tze { if is_overwinter_v3 || is_sapling_v4 || has_tze {
writer.write_u32::<LittleEndian>(self.expiry_height)?; writer.write_u32::<LittleEndian>(u32::from(self.expiry_height))?;
} }
if is_sapling_v4 || has_tze { if is_sapling_v4 || has_tze {

View File

@ -272,7 +272,7 @@ pub fn signature_hash_data<'a>(
); );
} }
update_u32!(h, tx.lock_time, tmp); update_u32!(h, tx.lock_time, tmp);
update_u32!(h, tx.expiry_height, tmp); update_u32!(h, tx.expiry_height.into(), tmp);
if sigversion == SigHashVersion::Sapling || sigversion == SigHashVersion::Future { if sigversion == SigHashVersion::Sapling || sigversion == SigHashVersion::Future {
h.update(&tx.value_balance.to_i64_le_bytes()); h.update(&tx.value_balance.to_i64_le_bytes());
} }

View File

@ -101,7 +101,7 @@ prop_compose! {
tze_inputs: if branch_id == BranchId::Future { tze_inputs } else { vec![] }, tze_inputs: if branch_id == BranchId::Future { tze_inputs } else { vec![] },
tze_outputs: if branch_id == BranchId::Future { tze_outputs } else { vec![] }, tze_outputs: if branch_id == BranchId::Future { tze_outputs } else { vec![] },
lock_time, lock_time,
expiry_height, expiry_height: expiry_height.into(),
value_balance, value_balance,
shielded_spends: vec![], //FIXME shielded_spends: vec![], //FIXME
shielded_outputs: vec![], //FIXME shielded_outputs: vec![], //FIXME

View File

@ -1,6 +1,10 @@
use blake2b_simd::Params; use blake2b_simd::Params;
use crate::{consensus, consensus::NetworkUpgrade, primitives::Rseed}; use crate::{
consensus::{self, BlockHeight, NetworkUpgrade},
primitives::Rseed,
};
use ff::Field; use ff::Field;
use rand_core::{CryptoRng, RngCore}; use rand_core::{CryptoRng, RngCore};
@ -13,10 +17,11 @@ pub fn hash_to_scalar(persona: &[u8], a: &[u8], b: &[u8]) -> jubjub::Fr {
} }
pub fn generate_random_rseed<P: consensus::Parameters, R: RngCore + CryptoRng>( pub fn generate_random_rseed<P: consensus::Parameters, R: RngCore + CryptoRng>(
height: u32, params: &P,
height: BlockHeight,
rng: &mut R, rng: &mut R,
) -> Rseed { ) -> Rseed {
if P::is_nu_active(NetworkUpgrade::Canopy, height) { if params.is_nu_active(NetworkUpgrade::Canopy, height) {
let mut buffer = [0u8; 32]; let mut buffer = [0u8; 32];
&rng.fill_bytes(&mut buffer); &rng.fill_bytes(&mut buffer);
Rseed::AfterZip212(buffer) Rseed::AfterZip212(buffer)