use std::sync::Arc;
use zebra_chain::{
amount::Amount,
block::Block,
primitives::{ed25519, x25519, Groth16Proof},
sapling,
serialization::{ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize},
sprout::{self, JoinSplit},
transaction::{JoinSplitData, 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_descs: Vec<_> = tx.sprout_joinsplit_descriptions().collect();
let pub_key = tx
.sprout_joinsplit_pub_key()
.expect("V2 transactions with joinsplit data must have a pub key");
let new_joinsplits: Vec<JoinSplit<Groth16Proof>> = joinsplit_descs
.iter()
.map(|js| {
let anchor = sprout::tree::Root::from(js.anchor());
let raw_nullifiers = js.nullifiers();
let nullifiers = [
sprout::note::Nullifier::from(raw_nullifiers[0]),
sprout::note::Nullifier::from(raw_nullifiers[1]),
];
let raw_commitments = js.commitments();
let commitments = [
sprout::commitment::NoteCommitment::from(raw_commitments[0]),
sprout::commitment::NoteCommitment::from(raw_commitments[1]),
];
let raw_seed = js.random_seed();
let random_seed = sprout::RandomSeed::from(*raw_seed);
let raw_macs = js.macs();
let vmacs = [
sprout::note::Mac::from(raw_macs[0]),
sprout::note::Mac::from(raw_macs[1]),
];
JoinSplit {
vpub_old: Amount::zero(),
vpub_new: Amount::zero(),
anchor,
nullifiers,
commitments,
ephemeral_key: x25519::PublicKey::from([0u8; 32]),
random_seed,
vmacs,
zkproof: Groth16Proof::from([0; 192]),
enc_ciphertexts: [
sprout::note::EncryptedNote([0u8; 601]),
sprout::note::EncryptedNote([0u8; 601]),
],
}
})
.collect();
let joinsplit_data = new_joinsplits
.split_first()
.map(|(first, rest)| JoinSplitData {
first: first.clone(),
rest: rest.to_vec(),
pub_key,
sig: ed25519::Signature::from_bytes(&[0u8; 64]),
});
let new_tx = build_v4_tx_with_joinsplit_data(joinsplit_data);
block_to_prepare.transactions.push(Arc::new(new_tx));
});
Arc::new(block_to_prepare).prepare()
}
fn build_v4_tx_with_joinsplit_data(
joinsplit_data: Option<JoinSplitData<Groth16Proof>>,
) -> Transaction {
Transaction::test_v4_with_joinsplit_data(joinsplit_data.as_ref())
}
fn build_v4_tx_with_sapling_data_zero_balance(tx: &Transaction) -> Transaction {
let mut tx_bytes = tx
.zcash_serialize_to_vec()
.expect("transaction serialization should succeed");
let mut pos = 8usize;
let n_inputs = parse_compact_size_bytes(&tx_bytes, &mut pos);
for _ in 0..n_inputs {
pos += 36; let script_len = parse_compact_size_bytes(&tx_bytes, &mut pos);
pos += script_len as usize + 4; }
let n_outputs = parse_compact_size_bytes(&tx_bytes, &mut pos);
for _ in 0..n_outputs {
pos += 8; let script_len = parse_compact_size_bytes(&tx_bytes, &mut pos);
pos += script_len as usize; }
pos += 8;
assert!(
pos + 8 <= tx_bytes.len(),
"transaction too short to contain valueBalanceSapling"
);
tx_bytes[pos..pos + 8].copy_from_slice(&0i64.to_le_bytes());
let tx = Transaction::zcash_deserialize(tx_bytes.as_slice())
.expect("patched V4 transaction should deserialize");
tx.with_transparent_inputs(vec![])
.with_transparent_outputs(vec![])
}
fn parse_compact_size_bytes(bytes: &[u8], pos: &mut usize) -> u64 {
let first = bytes[*pos];
*pos += 1;
match first {
0xfd => {
let val = u16::from_le_bytes([bytes[*pos], bytes[*pos + 1]]);
*pos += 2;
val as u64
}
0xfe => {
let val = u32::from_le_bytes([
bytes[*pos],
bytes[*pos + 1],
bytes[*pos + 2],
bytes[*pos + 3],
]);
*pos += 4;
val as u64
}
0xff => {
let val = u64::from_le_bytes([
bytes[*pos],
bytes[*pos + 1],
bytes[*pos + 2],
bytes[*pos + 3],
bytes[*pos + 4],
bytes[*pos + 5],
bytes[*pos + 6],
bytes[*pos + 7],
]);
*pos += 8;
val
}
n => n as u64,
}
}
#[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 new_tx = build_v4_tx_with_sapling_data_zero_balance(tx.as_ref());
block1.transactions.push(Arc::new(new_tx));
});
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 new_tx = build_v4_tx_with_sapling_data_zero_balance(tx.as_ref());
block2.transactions.push(Arc::new(new_tx));
});
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(())
);
}