use std::{ops::Deref, sync::Arc};
use zebra_chain::{
amount::Amount,
block::{Block, Height},
primitives::Groth16Proof,
sapling,
serialization::ZcashDeserializeInto,
sprout::{self, 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},
DiskWriteBatch, SemanticallyVerifiedBlock, ValidateContextError,
};
#[test]
fn check_sprout_anchors() {
let _init_guard = zebra_test::init();
let (finalized_state, mut non_finalized_state, _genesis) = new_state_with_mainnet_genesis();
let mut batch = DiskWriteBatch::new();
batch.delete_sprout_anchor(
&finalized_state,
&sprout::tree::NoteCommitmentTree::default().root(),
);
finalized_state
.write_batch(batch)
.expect("unexpected I/O error");
let block_1 = zebra_test::vectors::BLOCK_MAINNET_1_BYTES
.zcash_deserialize_into::<Block>()
.expect("block should deserialize");
let block_395 = zebra_test::vectors::BLOCK_MAINNET_395_BYTES
.zcash_deserialize_into::<Block>()
.expect("block should deserialize");
let block_1 = prepare_sprout_block(block_1, block_395);
let block_2 = zebra_test::vectors::BLOCK_MAINNET_2_BYTES
.zcash_deserialize_into::<Block>()
.expect("block should deserialize");
let block_396 = zebra_test::vectors::BLOCK_MAINNET_396_BYTES
.zcash_deserialize_into::<Block>()
.expect("block should deserialize");
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 { .. }),
),
"unexpected result: {check_unmined_tx_anchors_result:?}",
);
assert!(validate_and_commit_non_finalized(
&finalized_state.db,
&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());
assert_eq!(
validate_and_commit_non_finalized(&finalized_state.db, &mut non_finalized_state, block_2),
Ok(())
);
}
fn prepare_sprout_block(
mut block_to_prepare: Block,
reference_block: Block,
) -> SemanticallyVerifiedBlock {
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"),
};
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,
},
}
});
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()
}
#[test]
fn check_sapling_anchors() {
let _init_guard = zebra_test::init();
let (finalized_state, mut non_finalized_state, _genesis) = new_state_with_mainnet_genesis();
let mut batch = DiskWriteBatch::new();
batch.delete_sapling_anchor(
&finalized_state,
&sapling::tree::NoteCommitmentTree::default().root(),
);
finalized_state
.write_batch(batch)
.expect("unexpected I/O error");
let mut block1 = zebra_test::vectors::BLOCK_MAINNET_1_BYTES
.zcash_deserialize_into::<Block>()
.expect("block should deserialize");
block1.transactions[0] = transaction_v4_from_coinbase(&block1.transactions[0]).into();
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"),
};
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();
let mut block2 = zebra_test::vectors::BLOCK_MAINNET_2_BYTES
.zcash_deserialize_into::<Block>()
.expect("block should deserialize");
block2.transactions[0] = transaction_v4_from_coinbase(&block2.transactions[0]).into();
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"),
};
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.db,
&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.db, &mut non_finalized_state, block2),
Ok(())
);
}