Merge pull request #261 from nuttycom/dyn_network_parameters
Remove static determination of network state.
This commit is contained in:
commit
8f08723712
|
@ -1,5 +0,0 @@
|
||||||
//! Zcash global and per-network constants.
|
|
||||||
|
|
||||||
pub mod mainnet;
|
|
||||||
pub mod regtest;
|
|
||||||
pub mod testnet;
|
|
|
@ -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,
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
/// ```
|
/// ```
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -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)
|
¶ms.b58_pubkey_address_prefix(),
|
||||||
{
|
¶ms.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,
|
¶ms.b58_pubkey_address_prefix(),
|
||||||
&B58_SCRIPT_ADDRESS_PREFIX,
|
¶ms.b58_script_address_prefix(),
|
||||||
addr,
|
addr,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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(
|
||||||
|
&[u32::from(last_height), limit.unwrap_or(u32::max_value())],
|
||||||
|
|row| {
|
||||||
Ok(CompactBlockRow {
|
Ok(CompactBlockRow {
|
||||||
height: row.get(0)?,
|
height: row.get::<_, u32>(0).map(BlockHeight::from)?,
|
||||||
data: row.get(1)?,
|
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);
|
||||||
|
|
|
@ -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,7 +143,10 @@ 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(
|
||||||
|
params.hrp_sapling_extended_full_viewing_key(),
|
||||||
|
&extfvk,
|
||||||
|
)
|
||||||
.to_sql()?,
|
.to_sql()?,
|
||||||
])?
|
])?
|
||||||
{
|
{
|
||||||
|
@ -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.
|
||||||
|
|
|
@ -1,48 +1,262 @@
|
||||||
//! Consensus parameters.
|
//! Consensus 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)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,9 +390,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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,7 +406,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() {
|
||||||
|
@ -200,12 +416,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
|
||||||
|
@ -216,15 +430,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]
|
||||||
|
@ -235,25 +443,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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -9,7 +9,7 @@ use std::fmt;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
consensus,
|
consensus::{self, BlockHeight},
|
||||||
keys::OutgoingViewingKey,
|
keys::OutgoingViewingKey,
|
||||||
legacy::TransparentAddress,
|
legacy::TransparentAddress,
|
||||||
merkle_tree::MerklePath,
|
merkle_tree::MerklePath,
|
||||||
|
@ -85,7 +85,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,
|
||||||
|
@ -100,7 +101,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,
|
||||||
|
@ -302,8 +303,9 @@ impl TransactionMetadata {
|
||||||
|
|
||||||
/// Generates a [`Transaction`] from its inputs and outputs.
|
/// Generates a [`Transaction`] from its inputs and outputs.
|
||||||
pub struct Builder<P: consensus::Parameters, R: RngCore + CryptoRng> {
|
pub struct Builder<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>,
|
||||||
|
@ -324,8 +326,8 @@ impl<P: consensus::Parameters> Builder<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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,11 +341,12 @@ impl<P: consensus::Parameters, R: RngCore + CryptoRng> Builder<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<P, R> {
|
pub fn new_with_rng(params: P, height: BlockHeight, rng: R) -> Builder<P, R> {
|
||||||
let mut mtx = TransactionData::new();
|
let mut mtx = TransactionData::new();
|
||||||
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,
|
||||||
|
@ -402,7 +405,15 @@ impl<P: consensus::Parameters, R: RngCore + CryptoRng> Builder<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;
|
||||||
|
|
||||||
|
@ -613,7 +624,7 @@ impl<P: consensus::Parameters, R: RngCore + CryptoRng> Builder<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,
|
||||||
|
@ -709,8 +720,7 @@ mod tests {
|
||||||
|
|
||||||
use super::{Builder, Error};
|
use super::{Builder, Error};
|
||||||
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,
|
||||||
|
@ -727,7 +737,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)
|
||||||
|
@ -736,17 +746,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(),
|
||||||
|
@ -787,7 +799,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
|
||||||
|
@ -813,7 +825,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]),
|
||||||
|
@ -833,7 +845,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()))
|
||||||
|
@ -847,7 +859,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(),
|
||||||
|
@ -865,7 +877,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]),
|
||||||
|
@ -889,7 +901,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(),
|
||||||
|
@ -932,7 +944,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(),
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -65,7 +64,7 @@ pub struct TransactionData {
|
||||||
pub vin: Vec<TxIn>,
|
pub vin: Vec<TxIn>,
|
||||||
pub vout: Vec<TxOut>,
|
pub vout: Vec<TxOut>,
|
||||||
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>,
|
||||||
|
@ -119,7 +118,7 @@ impl TransactionData {
|
||||||
vin: vec![],
|
vin: vec![],
|
||||||
vout: vec![],
|
vout: 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![],
|
||||||
|
@ -188,10 +187,10 @@ impl Transaction {
|
||||||
let vin = Vector::read(&mut reader, TxIn::read)?;
|
let vin = Vector::read(&mut reader, TxIn::read)?;
|
||||||
let vout = Vector::read(&mut reader, TxOut::read)?;
|
let vout = Vector::read(&mut reader, TxOut::read)?;
|
||||||
let lock_time = reader.read_u32::<LittleEndian>()?;
|
let lock_time = reader.read_u32::<LittleEndian>()?;
|
||||||
let expiry_height = if is_overwinter_v3 || is_sapling_v4 {
|
let expiry_height: BlockHeight = if is_overwinter_v3 || is_sapling_v4 {
|
||||||
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 {
|
let (value_balance, shielded_spends, shielded_outputs) = if is_sapling_v4 {
|
||||||
|
@ -274,7 +273,7 @@ impl Transaction {
|
||||||
Vector::write(&mut writer, &self.vout, |w, e| e.write(w))?;
|
Vector::write(&mut writer, &self.vout, |w, e| e.write(w))?;
|
||||||
writer.write_u32::<LittleEndian>(self.lock_time)?;
|
writer.write_u32::<LittleEndian>(self.lock_time)?;
|
||||||
if is_overwinter_v3 || is_sapling_v4 {
|
if is_overwinter_v3 || is_sapling_v4 {
|
||||||
writer.write_u32::<LittleEndian>(self.expiry_height)?;
|
writer.write_u32::<LittleEndian>(u32::from(self.expiry_height))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_sapling_v4 {
|
if is_sapling_v4 {
|
||||||
|
|
|
@ -206,7 +206,7 @@ pub fn signature_hash_data(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
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 {
|
if sigversion == SigHashVersion::Sapling {
|
||||||
h.update(&tx.value_balance.to_i64_le_bytes());
|
h.update(&tx.value_balance.to_i64_le_bytes());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue