311 lines
11 KiB
Rust
311 lines
11 KiB
Rust
//! Tests for whether cited anchors are checked properly.
|
|
|
|
use std::{ops::Deref, sync::Arc};
|
|
|
|
use zebra_chain::{
|
|
amount::Amount,
|
|
block::{Block, Height},
|
|
primitives::Groth16Proof,
|
|
serialization::ZcashDeserializeInto,
|
|
sprout::JoinSplit,
|
|
transaction::{JoinSplitData, LockTime, Transaction, UnminedTx},
|
|
};
|
|
|
|
use crate::{
|
|
arbitrary::Prepare,
|
|
service::{
|
|
check::anchors::tx_anchors_refer_to_final_treestates,
|
|
write::validate_and_commit_non_finalized,
|
|
},
|
|
tests::setup::{new_state_with_mainnet_genesis, transaction_v4_from_coinbase},
|
|
PreparedBlock, ValidateContextError,
|
|
};
|
|
|
|
// Sprout
|
|
|
|
/// Check that, when primed with the first blocks that contain Sprout anchors, a
|
|
/// Sprout Spend's referenced anchor is validated.
|
|
#[test]
|
|
fn check_sprout_anchors() {
|
|
let _init_guard = zebra_test::init();
|
|
|
|
let (finalized_state, mut non_finalized_state, _genesis) = new_state_with_mainnet_genesis();
|
|
|
|
// Bootstrap a block at height == 1.
|
|
let block_1 = zebra_test::vectors::BLOCK_MAINNET_1_BYTES
|
|
.zcash_deserialize_into::<Block>()
|
|
.expect("block should deserialize");
|
|
|
|
// Bootstrap a block just before the first Sprout anchors.
|
|
let block_395 = zebra_test::vectors::BLOCK_MAINNET_395_BYTES
|
|
.zcash_deserialize_into::<Block>()
|
|
.expect("block should deserialize");
|
|
|
|
// Add initial transactions to [`block_1`].
|
|
let block_1 = prepare_sprout_block(block_1, block_395);
|
|
|
|
// Bootstrap a block at height == 2 that references the Sprout note commitment tree state
|
|
// from [`block_1`].
|
|
let block_2 = zebra_test::vectors::BLOCK_MAINNET_2_BYTES
|
|
.zcash_deserialize_into::<Block>()
|
|
.expect("block should deserialize");
|
|
|
|
// Exercise Sprout anchor checking with the first shielded transactions with
|
|
// anchors.
|
|
let block_396 = zebra_test::vectors::BLOCK_MAINNET_396_BYTES
|
|
.zcash_deserialize_into::<Block>()
|
|
.expect("block should deserialize");
|
|
|
|
// Add the transactions with the first anchors to [`block_2`].
|
|
let block_2 = prepare_sprout_block(block_2, block_396);
|
|
|
|
let unmined_txs: Vec<_> = block_2
|
|
.block
|
|
.transactions
|
|
.iter()
|
|
.map(UnminedTx::from)
|
|
.collect();
|
|
|
|
let check_unmined_tx_anchors_result = unmined_txs.iter().try_for_each(|unmined_tx| {
|
|
tx_anchors_refer_to_final_treestates(
|
|
&finalized_state.db,
|
|
non_finalized_state.best_chain(),
|
|
unmined_tx,
|
|
)
|
|
});
|
|
|
|
assert!(matches!(
|
|
check_unmined_tx_anchors_result,
|
|
Err(ValidateContextError::UnknownSproutAnchor { .. })
|
|
));
|
|
|
|
// Validate and commit [`block_1`]. This will add an anchor referencing the
|
|
// empty note commitment tree to the state.
|
|
assert!(
|
|
validate_and_commit_non_finalized(&finalized_state, &mut non_finalized_state, block_1)
|
|
.is_ok()
|
|
);
|
|
|
|
let check_unmined_tx_anchors_result = unmined_txs.iter().try_for_each(|unmined_tx| {
|
|
tx_anchors_refer_to_final_treestates(
|
|
&finalized_state.db,
|
|
non_finalized_state.best_chain(),
|
|
unmined_tx,
|
|
)
|
|
});
|
|
|
|
assert!(check_unmined_tx_anchors_result.is_ok());
|
|
|
|
// Validate and commit [`block_2`]. This will also check the anchors.
|
|
assert_eq!(
|
|
validate_and_commit_non_finalized(&finalized_state, &mut non_finalized_state, block_2),
|
|
Ok(())
|
|
);
|
|
}
|
|
|
|
fn prepare_sprout_block(mut block_to_prepare: Block, reference_block: Block) -> PreparedBlock {
|
|
// Convert the coinbase transaction to a version that the non-finalized state will accept.
|
|
block_to_prepare.transactions[0] =
|
|
transaction_v4_from_coinbase(&block_to_prepare.transactions[0]).into();
|
|
|
|
reference_block
|
|
.transactions
|
|
.into_iter()
|
|
.filter(|tx| tx.has_sprout_joinsplit_data())
|
|
.for_each(|tx| {
|
|
let joinsplit_data = match tx.deref() {
|
|
Transaction::V2 { joinsplit_data, .. } => joinsplit_data.clone(),
|
|
_ => unreachable!("These are known v2 transactions"),
|
|
};
|
|
|
|
// Change [`joinsplit_data`] so that the transaction passes the
|
|
// semantic validation. Namely, set the value balance to zero, and
|
|
// use a dummy Groth16 proof instead of a BCTV14 one.
|
|
let joinsplit_data = joinsplit_data.map(|s| {
|
|
let mut new_joinsplits: Vec<JoinSplit<Groth16Proof>> = Vec::new();
|
|
|
|
for old_joinsplit in s.joinsplits() {
|
|
new_joinsplits.push(JoinSplit {
|
|
vpub_old: Amount::zero(),
|
|
vpub_new: Amount::zero(),
|
|
anchor: old_joinsplit.anchor,
|
|
nullifiers: old_joinsplit.nullifiers,
|
|
commitments: old_joinsplit.commitments,
|
|
ephemeral_key: old_joinsplit.ephemeral_key,
|
|
random_seed: old_joinsplit.random_seed.clone(),
|
|
vmacs: old_joinsplit.vmacs.clone(),
|
|
zkproof: Groth16Proof::from([0; 192]),
|
|
enc_ciphertexts: old_joinsplit.enc_ciphertexts,
|
|
})
|
|
}
|
|
|
|
match new_joinsplits.split_first() {
|
|
None => unreachable!("the new joinsplits are never empty"),
|
|
|
|
Some((first, rest)) => JoinSplitData {
|
|
first: first.clone(),
|
|
rest: rest.to_vec(),
|
|
pub_key: s.pub_key,
|
|
sig: s.sig,
|
|
},
|
|
}
|
|
});
|
|
|
|
// Add the new adjusted transaction to [`block_to_prepare`].
|
|
block_to_prepare
|
|
.transactions
|
|
.push(Arc::new(Transaction::V4 {
|
|
inputs: Vec::new(),
|
|
outputs: Vec::new(),
|
|
lock_time: LockTime::min_lock_time_timestamp(),
|
|
expiry_height: Height(0),
|
|
joinsplit_data,
|
|
sapling_shielded_data: None,
|
|
}))
|
|
});
|
|
|
|
Arc::new(block_to_prepare).prepare()
|
|
}
|
|
|
|
// Sapling
|
|
|
|
/// Check that, when primed with the first Sapling blocks, a Sapling Spend's referenced anchor is
|
|
/// validated.
|
|
#[test]
|
|
fn check_sapling_anchors() {
|
|
let _init_guard = zebra_test::init();
|
|
|
|
let (finalized_state, mut non_finalized_state, _genesis) = new_state_with_mainnet_genesis();
|
|
|
|
// Bootstrap a block at height == 1 that has the first Sapling note commitments
|
|
let mut block1 = zebra_test::vectors::BLOCK_MAINNET_1_BYTES
|
|
.zcash_deserialize_into::<Block>()
|
|
.expect("block should deserialize");
|
|
|
|
// convert the coinbase transaction to a version that the non-finalized state will accept
|
|
block1.transactions[0] = transaction_v4_from_coinbase(&block1.transactions[0]).into();
|
|
|
|
// Prime finalized state with the Sapling start + 1, which has the first
|
|
// Sapling note commitment
|
|
let block_419201 = zebra_test::vectors::BLOCK_MAINNET_419201_BYTES
|
|
.zcash_deserialize_into::<Block>()
|
|
.expect("block should deserialize");
|
|
|
|
block_419201
|
|
.transactions
|
|
.into_iter()
|
|
.filter(|tx| tx.has_sapling_shielded_data())
|
|
.for_each(|tx| {
|
|
let sapling_shielded_data = match tx.deref() {
|
|
Transaction::V4 {
|
|
sapling_shielded_data,
|
|
..
|
|
} => sapling_shielded_data.clone(),
|
|
_ => unreachable!("These are known v4 transactions"),
|
|
};
|
|
|
|
// set value balance to 0 to pass the chain value pool checks
|
|
let sapling_shielded_data = sapling_shielded_data.map(|mut s| {
|
|
s.value_balance = 0.try_into().expect("unexpected invalid zero amount");
|
|
s
|
|
});
|
|
|
|
block1.transactions.push(Arc::new(Transaction::V4 {
|
|
inputs: Vec::new(),
|
|
outputs: Vec::new(),
|
|
lock_time: LockTime::min_lock_time_timestamp(),
|
|
expiry_height: Height(0),
|
|
joinsplit_data: None,
|
|
sapling_shielded_data,
|
|
}))
|
|
});
|
|
|
|
let block1 = Arc::new(block1).prepare();
|
|
|
|
// Bootstrap a block at height == 2 that references the Sapling note commitment tree state
|
|
// from earlier block
|
|
let mut block2 = zebra_test::vectors::BLOCK_MAINNET_2_BYTES
|
|
.zcash_deserialize_into::<Block>()
|
|
.expect("block should deserialize");
|
|
|
|
// convert the coinbase transaction to a version that the non-finalized state will accept
|
|
block2.transactions[0] = transaction_v4_from_coinbase(&block2.transactions[0]).into();
|
|
|
|
// Exercise Sapling anchor checking with Sapling start + 2, which refers to the note commitment
|
|
// tree as of the last transaction of the previous block
|
|
let block_419202 = zebra_test::vectors::BLOCK_MAINNET_419202_BYTES
|
|
.zcash_deserialize_into::<Block>()
|
|
.expect("block should deserialize");
|
|
|
|
block_419202
|
|
.transactions
|
|
.into_iter()
|
|
.filter(|tx| tx.has_sapling_shielded_data())
|
|
.for_each(|tx| {
|
|
let sapling_shielded_data = match tx.deref() {
|
|
Transaction::V4 {
|
|
sapling_shielded_data,
|
|
..
|
|
} => sapling_shielded_data.clone(),
|
|
_ => unreachable!("These are known v4 transactions"),
|
|
};
|
|
|
|
// set value balance to 0 to pass the chain value pool checks
|
|
let sapling_shielded_data = sapling_shielded_data.map(|mut s| {
|
|
s.value_balance = 0.try_into().expect("unexpected invalid zero amount");
|
|
s
|
|
});
|
|
|
|
block2.transactions.push(Arc::new(Transaction::V4 {
|
|
inputs: Vec::new(),
|
|
outputs: Vec::new(),
|
|
lock_time: LockTime::min_lock_time_timestamp(),
|
|
expiry_height: Height(0),
|
|
joinsplit_data: None,
|
|
sapling_shielded_data,
|
|
}))
|
|
});
|
|
|
|
let block2 = Arc::new(block2).prepare();
|
|
|
|
let unmined_txs: Vec<_> = block2
|
|
.block
|
|
.transactions
|
|
.iter()
|
|
.map(UnminedTx::from)
|
|
.collect();
|
|
|
|
let check_unmined_tx_anchors_result = unmined_txs.iter().try_for_each(|unmined_tx| {
|
|
tx_anchors_refer_to_final_treestates(
|
|
&finalized_state.db,
|
|
non_finalized_state.best_chain(),
|
|
unmined_tx,
|
|
)
|
|
});
|
|
|
|
assert!(matches!(
|
|
check_unmined_tx_anchors_result,
|
|
Err(ValidateContextError::UnknownSaplingAnchor { .. })
|
|
));
|
|
|
|
assert!(
|
|
validate_and_commit_non_finalized(&finalized_state, &mut non_finalized_state, block1)
|
|
.is_ok()
|
|
);
|
|
|
|
let check_unmined_tx_anchors_result = unmined_txs.iter().try_for_each(|unmined_tx| {
|
|
tx_anchors_refer_to_final_treestates(
|
|
&finalized_state.db,
|
|
non_finalized_state.best_chain(),
|
|
unmined_tx,
|
|
)
|
|
});
|
|
|
|
assert!(check_unmined_tx_anchors_result.is_ok());
|
|
|
|
assert_eq!(
|
|
validate_and_commit_non_finalized(&finalized_state, &mut non_finalized_state, block2),
|
|
Ok(())
|
|
);
|
|
}
|