zcash_client_sqlite: Add multiple output capability to fake compact block construction.

Many important test scenarios for note commitment tree structure require
the use of blocks that contain multiple notes.
This commit is contained in:
Kris Nuttycombe 2024-05-01 12:05:19 -06:00
parent 976a4d2d5c
commit c88cd17929
4 changed files with 120 additions and 65 deletions

View File

@ -461,25 +461,24 @@ where
pub(crate) fn generate_next_block<Fvk: TestFvk>(
&mut self,
fvk: &Fvk,
req: AddressType,
address_type: AddressType,
value: NonNegativeAmount,
) -> (BlockHeight, Cache::InsertResult, Fvk::Nullifier) {
let pre_activation_block = CachedBlock::none(self.sapling_activation_height() - 1);
let prior_cached_block = self.latest_cached_block().unwrap_or(&pre_activation_block);
let height = prior_cached_block.height() + 1;
let (res, nf) = self.generate_block_at(
let (res, nfs) = self.generate_block_at(
height,
prior_cached_block.chain_state.block_hash(),
fvk,
req,
value,
&[FakeCompactOutput::new(address_type, value)],
prior_cached_block.sapling_end_size,
prior_cached_block.orchard_end_size,
false,
);
(height, res, nf)
(height, res, nfs[0])
}
/// Adds an empty block to the cache, advancing the simulated chain height.
@ -528,12 +527,11 @@ where
height: BlockHeight,
prev_hash: BlockHash,
fvk: &Fvk,
req: AddressType,
value: NonNegativeAmount,
outputs: &[FakeCompactOutput],
initial_sapling_tree_size: u32,
initial_orchard_tree_size: u32,
allow_broken_hash_chain: bool,
) -> (Cache::InsertResult, Fvk::Nullifier) {
) -> (Cache::InsertResult, Vec<Fvk::Nullifier>) {
let mut prior_cached_block = self
.latest_cached_block_below_height(height)
.cloned()
@ -587,13 +585,12 @@ where
.insert(height - 1, prior_cached_block.clone());
}
let (cb, nf) = fake_compact_block(
let (cb, nfs) = fake_compact_block(
&self.network(),
height,
prev_hash,
fvk,
req,
value,
outputs,
initial_sapling_tree_size,
initial_orchard_tree_size,
&mut self.rng,
@ -603,7 +600,7 @@ where
let res = self.cache_block(&prior_cached_block, cb);
self.latest_block_height = Some(height);
(res, nf)
(res, nfs)
}
/// Creates a fake block at the expected next height spending the given note, and
@ -798,6 +795,15 @@ impl<Cache> TestState<Cache> {
.expect("Sapling activation height must be known.")
}
/// Convenience method for obtaining the NU5 activation height for the network under test.
#[allow(dead_code)]
pub(crate) fn nu5_activation_height(&self) -> BlockHeight {
self.db_data
.params
.activation_height(NetworkUpgrade::Nu5)
.expect("NU5 activation height must be known.")
}
/// Exposes the test seed, if enabled via [`TestBuilder::with_test_account`].
pub(crate) fn test_seed(&self) -> Option<&SecretVec<u8>> {
self.test_account.as_ref().map(|(seed, _)| seed)
@ -1158,8 +1164,8 @@ impl<Cache> TestState<Cache> {
&self,
) -> Result<Vec<TransactionSummary<AccountId>>, SqliteClientError> {
let mut stmt = self.wallet().conn.prepare_cached(
"SELECT *
FROM v_transactions
"SELECT *
FROM v_transactions
ORDER BY mined_height DESC, tx_index DESC",
)?;
@ -1283,7 +1289,7 @@ impl<AccountId> TransactionSummary<AccountId> {
/// Trait used by tests that require a full viewing key.
pub(crate) trait TestFvk {
type Nullifier;
type Nullifier: Copy;
fn sapling_ovk(&self) -> Option<sapling::keys::OutgoingViewingKey>;
@ -1306,6 +1312,8 @@ pub(crate) trait TestFvk {
req: AddressType,
value: NonNegativeAmount,
initial_sapling_tree_size: u32,
// we don't require an initial Orchard tree size because we don't need it to compute
// the nullifier.
rng: &mut R,
) -> Self::Nullifier;
@ -1319,19 +1327,10 @@ pub(crate) trait TestFvk {
req: AddressType,
value: NonNegativeAmount,
initial_sapling_tree_size: u32,
// we don't require an initial Orchard tree size because we don't need it to compute
// the nullifier.
rng: &mut R,
) -> Self::Nullifier {
self.add_spend(ctx, nf, rng);
self.add_output(
ctx,
params,
height,
req,
value,
initial_sapling_tree_size,
rng,
)
}
) -> Self::Nullifier;
}
impl TestFvk for DiversifiableFullViewingKey {
@ -1380,6 +1379,30 @@ impl TestFvk for DiversifiableFullViewingKey {
note.nf(&self.fvk().vk.nk, position as u64)
}
#[allow(clippy::too_many_arguments)]
fn add_logical_action<P: consensus::Parameters, R: RngCore + CryptoRng>(
&self,
ctx: &mut CompactTx,
params: &P,
height: BlockHeight,
nf: Self::Nullifier,
req: AddressType,
value: NonNegativeAmount,
initial_sapling_tree_size: u32,
rng: &mut R,
) -> Self::Nullifier {
self.add_spend(ctx, nf, rng);
self.add_output(
ctx,
params,
height,
req,
value,
initial_sapling_tree_size,
rng,
)
}
}
#[cfg(feature = "orchard")]
@ -1428,10 +1451,10 @@ impl TestFvk for orchard::keys::FullViewingKey {
_: BlockHeight,
req: AddressType,
value: NonNegativeAmount,
_: u32,
_: u32, // the position is not required for computing the Orchard nullifier
mut rng: &mut R,
) -> Self::Nullifier {
// Generate a dummy nullifier
// Generate a dummy nullifier for the spend
let revealed_spent_note_nullifier =
orchard::note::Nullifier::from_bytes(&pallas::Base::random(&mut rng).to_repr())
.unwrap();
@ -1461,12 +1484,12 @@ impl TestFvk for orchard::keys::FullViewingKey {
_: &P,
_: BlockHeight,
revealed_spent_note_nullifier: Self::Nullifier,
req: AddressType,
address_type: AddressType,
value: NonNegativeAmount,
_: u32,
_: u32, // the position is not required for computing the Orchard nullifier
rng: &mut R,
) -> Self::Nullifier {
let (j, scope) = match req {
let (j, scope) = match address_type {
AddressType::DefaultExternal => (0u32.into(), zip32::Scope::External),
AddressType::DiversifiedExternal(idx) => (idx, zip32::Scope::External),
AddressType::Internal => (0u32.into(), zip32::Scope::Internal),
@ -1486,9 +1509,10 @@ impl TestFvk for orchard::keys::FullViewingKey {
}
}
#[allow(dead_code)]
#[derive(Clone, Copy)]
pub(crate) enum AddressType {
DefaultExternal,
#[allow(dead_code)]
DiversifiedExternal(DiversifierIndex),
Internal,
}
@ -1567,31 +1591,50 @@ fn fake_compact_tx<R: RngCore + CryptoRng>(rng: &mut R) -> CompactTx {
ctx
}
/// Create a fake CompactBlock at the given height, containing a single output paying
/// an address. Returns the CompactBlock and the nullifier for the new note.
pub(crate) struct FakeCompactOutput {
address_type: AddressType,
value: NonNegativeAmount,
}
impl FakeCompactOutput {
pub(crate) fn new(address_type: AddressType, value: NonNegativeAmount) -> Self {
Self {
address_type,
value,
}
}
}
/// Create a fake CompactBlock at the given height, containing the specified fake compact outputs.
///
/// Returns the newly created compact block, along with the nullifier for each note created in that
/// block.
#[allow(clippy::too_many_arguments)]
fn fake_compact_block<P: consensus::Parameters, Fvk: TestFvk>(
params: &P,
height: BlockHeight,
prev_hash: BlockHash,
fvk: &Fvk,
req: AddressType,
value: NonNegativeAmount,
outputs: &[FakeCompactOutput],
initial_sapling_tree_size: u32,
initial_orchard_tree_size: u32,
mut rng: impl RngCore + CryptoRng,
) -> (CompactBlock, Fvk::Nullifier) {
) -> (CompactBlock, Vec<Fvk::Nullifier>) {
// Create a fake CompactBlock containing the note
let mut ctx = fake_compact_tx(&mut rng);
let nf = fvk.add_output(
&mut ctx,
params,
height,
req,
value,
initial_sapling_tree_size,
&mut rng,
);
let mut nfs = vec![];
for output in outputs {
let nf = fvk.add_output(
&mut ctx,
params,
height,
output.address_type,
output.value,
initial_sapling_tree_size,
&mut rng,
);
nfs.push(nf);
}
let cb = fake_compact_block_from_compact_tx(
ctx,
@ -1601,7 +1644,7 @@ fn fake_compact_block<P: consensus::Parameters, Fvk: TestFvk>(
initial_orchard_tree_size,
rng,
);
(cb, nf)
(cb, nfs)
}
/// Create a fake CompactBlock at the given height containing only the given transaction.

View File

@ -52,7 +52,10 @@ use zcash_protocol::consensus::BlockHeight;
use super::TestFvk;
use crate::{
error::SqliteClientError,
testing::{input_selector, AddressType, BlockCache, InitialChainState, TestBuilder, TestState},
testing::{
input_selector, AddressType, BlockCache, FakeCompactOutput, InitialChainState, TestBuilder,
TestState,
},
wallet::{block_max_scanned, commitment_tree, parse_scope, truncate_to_height},
AccountId, NoteId, ReceivedNoteId,
};
@ -1374,8 +1377,10 @@ pub(crate) fn checkpoint_gaps<T: ShieldedPoolTester>() {
account.birthday().height() + 10,
BlockHash([0; 32]),
&not_our_key,
AddressType::DefaultExternal,
not_our_value,
&[FakeCompactOutput::new(
AddressType::DefaultExternal,
not_our_value,
)],
st.latest_cached_block().unwrap().sapling_end_size,
st.latest_cached_block().unwrap().orchard_end_size,
false,
@ -1956,8 +1961,10 @@ pub(crate) fn invalid_chain_cache_disconnected<T: ShieldedPoolTester>() {
disconnect_height,
BlockHash([1; 32]),
&dfvk,
AddressType::DefaultExternal,
NonNegativeAmount::const_from_u64(8),
&[FakeCompactOutput::new(
AddressType::DefaultExternal,
NonNegativeAmount::const_from_u64(8),
)],
2,
2,
true,

View File

@ -2838,7 +2838,7 @@ mod tests {
use zcash_primitives::{block::BlockHash, transaction::components::amount::NonNegativeAmount};
use crate::{
testing::{AddressType, BlockCache, TestBuilder, TestState},
testing::{AddressType, BlockCache, FakeCompactOutput, TestBuilder, TestState},
AccountId,
};
@ -3209,8 +3209,10 @@ mod tests {
start_height,
BlockHash([0; 32]),
&not_our_key,
AddressType::DefaultExternal,
not_our_value,
&[FakeCompactOutput::new(
AddressType::DefaultExternal,
not_our_value,
)],
0,
0,
false,

View File

@ -598,8 +598,8 @@ pub(crate) mod tests {
use crate::{
error::SqliteClientError,
testing::{
pool::ShieldedPoolTester, AddressType, BlockCache, InitialChainState, TestBuilder,
TestState,
pool::ShieldedPoolTester, AddressType, BlockCache, FakeCompactOutput,
InitialChainState, TestBuilder, TestState,
},
wallet::{
sapling::tests::SaplingPoolTester,
@ -695,8 +695,7 @@ pub(crate) mod tests {
initial_height,
prior_block_hash,
&dfvk,
AddressType::DefaultExternal,
value,
&[FakeCompactOutput::new(AddressType::DefaultExternal, value)],
initial_sapling_tree_size,
initial_orchard_tree_size,
false,
@ -1103,9 +1102,11 @@ pub(crate) mod tests {
max_scanned,
BlockHash([1u8; 32]),
&dfvk,
AddressType::DefaultExternal,
// 1235 notes into into the second shard
NonNegativeAmount::const_from_u64(10000),
&[FakeCompactOutput::new(
AddressType::DefaultExternal,
// 1235 notes into into the second shard
NonNegativeAmount::const_from_u64(10000),
)],
frontier_tree_size + 10,
frontier_tree_size + 10,
false,
@ -1291,8 +1292,10 @@ pub(crate) mod tests {
max_scanned,
BlockHash([1; 32]),
&dfvk,
AddressType::DefaultExternal,
NonNegativeAmount::const_from_u64(10000),
&[FakeCompactOutput::new(
AddressType::DefaultExternal,
NonNegativeAmount::const_from_u64(10000),
)],
frontier_tree_size + 10,
frontier_tree_size + 10,
false,