use std::{ collections::HashSet, io::{Cursor, Write}, }; use chrono::{DateTime, Duration, LocalResult, TimeZone, Utc}; use crate::{ block::{ serialize::MAX_BLOCK_BYTES, Block, BlockTimeError, Commitment::*, Hash, Header, Height, }, parameters::{ Network::{self, *}, NetworkUpgrade::*, }, serialization::{ sha256d, SerializationError, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize, }, transaction::LockTime, }; use super::generate; // TODO: this should be rewritten as strategies #[test] fn blockheaderhash_debug() { let _init_guard = zebra_test::init(); let preimage = b"foo bar baz"; let mut sha_writer = sha256d::Writer::default(); let _ = sha_writer.write_all(preimage); let hash = Hash(sha_writer.finish()); assert_eq!( format!("{hash:?}"), "block::Hash(\"3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf\")" ); } #[test] fn blockheaderhash_from_blockheader() { let _init_guard = zebra_test::init(); let (blockheader, _blockheader_bytes) = generate::block_header(); let hash = Hash::from(&blockheader); assert_eq!( format!("{hash:?}"), "block::Hash(\"d1d6974bbe1d4d127c889119b2fc05724c67588dc72708839727586b8c2bc939\")" ); let mut bytes = Cursor::new(Vec::new()); blockheader .zcash_serialize(&mut bytes) .expect("these bytes to serialize from a blockheader without issue"); bytes.set_position(0); let other_header = bytes .zcash_deserialize_into() .expect("these bytes to deserialize into a blockheader without issue"); assert_eq!(blockheader, other_header); } #[test] fn blockheader_serialization() { let _init_guard = zebra_test::init(); // Includes the 32-byte nonce and 3-byte equihash length field. const BLOCK_HEADER_LENGTH: usize = crate::work::equihash::Solution::INPUT_LENGTH + 32 + 3 + crate::work::equihash::SOLUTION_SIZE; for block in zebra_test::vectors::BLOCKS.iter() { // successful deserialization let header_bytes = &block[..BLOCK_HEADER_LENGTH]; let mut header = header_bytes .zcash_deserialize_into::
() .expect("blockheader test vector should deserialize"); // successful serialization let _serialized_header = header .zcash_serialize_to_vec() .expect("blockheader test vector should serialize"); // deserialiation errors let header_bytes = [&[255; 4], &header_bytes[4..]].concat(); let deserialization_err = header_bytes .zcash_deserialize_into::
() .expect_err("blockheader test vector should fail to deserialize"); let SerializationError::Parse(err_msg) = deserialization_err else { panic!("SerializationError variant should be Parse") }; assert_eq!(err_msg, "high bit was set in version field"); let header_bytes = [&[0; 4], &header_bytes[4..]].concat(); let deserialization_err = header_bytes .zcash_deserialize_into::
() .expect_err("blockheader test vector should fail to deserialize"); let SerializationError::Parse(err_msg) = deserialization_err else { panic!("SerializationError variant should be Parse") }; assert_eq!(err_msg, "version must be at least 4"); // serialiation errors header.version = u32::MAX; let serialization_err = header .zcash_serialize_to_vec() .expect_err("blockheader test vector with modified version should fail to serialize"); assert_eq!( serialization_err.kind(), std::io::ErrorKind::Other, "error kind should be Other" ); let err_msg = serialization_err .into_inner() .expect("there should be an inner error"); assert_eq!(err_msg.to_string(), "high bit was set in version field"); } } #[test] fn round_trip_blocks() { let _init_guard = zebra_test::init(); // this one has a bad version field, but it is still valid zebra_test::vectors::BLOCK_MAINNET_434873_BYTES .zcash_deserialize_into::() .expect("bad version block test vector should deserialize"); // now do a round-trip test on all the block test vectors for block_bytes in zebra_test::vectors::BLOCKS.iter() { let block = block_bytes .zcash_deserialize_into::() .expect("block is structurally valid"); let round_trip_bytes = block .zcash_serialize_to_vec() .expect("vec serialization is infallible"); assert_eq!(&round_trip_bytes[..], *block_bytes); } } #[test] fn coinbase_parsing_rejects_above_0x80() { let _init_guard = zebra_test::init(); zebra_test::vectors::BAD_BLOCK_MAINNET_202_BYTES .zcash_deserialize_into::() .expect_err("parsing fails"); } #[test] fn block_test_vectors_unique() { let _init_guard = zebra_test::init(); let block_count = zebra_test::vectors::BLOCKS.len(); let block_hashes: HashSet<_> = zebra_test::vectors::BLOCKS .iter() .map(|b| { b.zcash_deserialize_into::() .expect("block is structurally valid") .hash() }) .collect(); // putting the same block in two files is an easy mistake to make assert_eq!( block_count, block_hashes.len(), "block test vectors must be unique" ); } #[test] fn block_test_vectors_height_mainnet() { let _init_guard = zebra_test::init(); block_test_vectors_height(Mainnet); } #[test] fn block_test_vectors_height_testnet() { let _init_guard = zebra_test::init(); block_test_vectors_height(Testnet); } /// Test that the block test vector indexes match the heights in the block data, /// and that each post-sapling block has a corresponding final sapling root. fn block_test_vectors_height(network: Network) { let (block_iter, sapling_roots) = network.block_sapling_roots_iter(); for (&height, block) in block_iter { let block = block .zcash_deserialize_into::() .expect("block is structurally valid"); assert_eq!( block.coinbase_height().expect("block height is valid").0, height, "deserialized height must match BTreeMap key height" ); if height >= Sapling .activation_height(network) .expect("sapling activation height is set") .0 { assert!( sapling_roots.contains_key(&height), "post-sapling block test vectors must have matching sapling root test vectors: missing {network} {height}" ); } } } #[test] fn block_commitment_mainnet() { let _init_guard = zebra_test::init(); block_commitment(Mainnet); } #[test] fn block_commitment_testnet() { let _init_guard = zebra_test::init(); block_commitment(Testnet); } /// Check that the block commitment field parses without errors. /// For sapling and blossom blocks, also check the final sapling root value. /// /// TODO: add chain history test vectors? fn block_commitment(network: Network) { let (block_iter, sapling_roots) = network.block_sapling_roots_iter(); for (height, block) in block_iter { let block = block .zcash_deserialize_into::() .expect("block is structurally valid"); let commitment = block.commitment(network).unwrap_or_else(|_| { panic!("unexpected structurally invalid block commitment at {network} {height}") }); if let FinalSaplingRoot(final_sapling_root) = commitment { let expected_final_sapling_root = *sapling_roots .get(height) .expect("unexpected missing final sapling root test vector"); assert_eq!( final_sapling_root, crate::sapling::tree::Root::try_from(*expected_final_sapling_root).unwrap(), "unexpected invalid final sapling root commitment at {network} {height}" ); } } } #[test] fn block_limits_multi_tx() { let _init_guard = zebra_test::init(); // Test multiple small transactions to fill a block max size // Create a block just below the limit let mut block = generate::large_multi_transaction_block(); // Serialize the block let mut data = Vec::new(); block .zcash_serialize(&mut data) .expect("block should serialize as we are not limiting generation yet"); assert!(data.len() <= MAX_BLOCK_BYTES as usize); // Deserialize by now is ok as we are lower than the limit let block2 = Block::zcash_deserialize(&data[..]) .expect("block should deserialize as we are just below limit"); assert_eq!(block, block2); // Add 1 more transaction to the block, limit will be reached block = generate::oversized_multi_transaction_block(); // Serialize will still be fine let mut data = Vec::new(); block .zcash_serialize(&mut data) .expect("block should serialize as we are not limiting generation yet"); assert!(data.len() > MAX_BLOCK_BYTES as usize); // Deserialize will now fail Block::zcash_deserialize(&data[..]).expect_err("block should not deserialize"); } #[test] fn block_limits_single_tx() { let _init_guard = zebra_test::init(); // Test block limit with a big single transaction // Create a block just below the limit let mut block = generate::large_single_transaction_block_many_inputs(); // Serialize the block let mut data = Vec::new(); block .zcash_serialize(&mut data) .expect("block should serialize as we are not limiting generation yet"); assert!(data.len() <= MAX_BLOCK_BYTES as usize); // Deserialize by now is ok as we are lower than the limit Block::zcash_deserialize(&data[..]) .expect("block should deserialize as we are just below limit"); // Add 1 more input to the transaction, limit will be reached block = generate::oversized_single_transaction_block_many_inputs(); let mut data = Vec::new(); block .zcash_serialize(&mut data) .expect("block should serialize as we are not limiting generation yet"); assert!(data.len() > MAX_BLOCK_BYTES as usize); // Will fail as block overall size is above limit Block::zcash_deserialize(&data[..]).expect_err("block should not deserialize"); } /// Test wrapper for `BlockHeader.time_is_valid_at`. /// /// Generates a block header, sets its `time` to `block_header_time`, then /// calls `time_is_valid_at`. fn node_time_check( block_header_time: DateTime, now: DateTime, ) -> Result<(), BlockTimeError> { let (mut header, _header_bytes) = generate::block_header(); header.time = block_header_time; // pass a zero height and hash - they are only used in the returned error header.time_is_valid_at(now, &Height(0), &Hash([0; 32])) } #[test] fn time_check_now() { let _init_guard = zebra_test::init(); // These checks are deterministic, because all the times are offset // from the current time. let now = Utc::now(); let three_hours_in_the_past = now - Duration::hours(3); let two_hours_in_the_future = now + Duration::hours(2); let two_hours_and_one_second_in_the_future = now + Duration::hours(2) + Duration::seconds(1); node_time_check(now, now).expect("the current time should be valid as a block header time"); node_time_check(three_hours_in_the_past, now) .expect("a past time should be valid as a block header time"); node_time_check(two_hours_in_the_future, now) .expect("2 hours in the future should be valid as a block header time"); node_time_check(two_hours_and_one_second_in_the_future, now) .expect_err("2 hours and 1 second in the future should be invalid as a block header time"); // Now invert the tests // 3 hours in the future should fail node_time_check(now, three_hours_in_the_past) .expect_err("3 hours in the future should be invalid as a block header time"); // The past should succeed node_time_check(now, two_hours_in_the_future) .expect("2 hours in the past should be valid as a block header time"); node_time_check(now, two_hours_and_one_second_in_the_future) .expect("2 hours and 1 second in the past should be valid as a block header time"); } /// Valid unix epoch timestamps for blocks, in seconds static BLOCK_HEADER_VALID_TIMESTAMPS: &[i64] = &[ // These times are currently invalid DateTimes, but they could // become valid in future chrono versions i64::MIN, i64::MIN + 1, // These times are valid DateTimes (i32::MIN as i64) - 1, (i32::MIN as i64), (i32::MIN as i64) + 1, -1, 0, 1, LockTime::MIN_TIMESTAMP - 1, LockTime::MIN_TIMESTAMP, LockTime::MIN_TIMESTAMP + 1, ]; /// Invalid unix epoch timestamps for blocks, in seconds static BLOCK_HEADER_INVALID_TIMESTAMPS: &[i64] = &[ (i32::MAX as i64) - 1, (i32::MAX as i64), (i32::MAX as i64) + 1, LockTime::MAX_TIMESTAMP - 1, LockTime::MAX_TIMESTAMP, LockTime::MAX_TIMESTAMP + 1, // These times are currently invalid DateTimes, but they could // become valid in future chrono versions i64::MAX - 1, i64::MAX, ]; #[test] fn time_check_fixed() { let _init_guard = zebra_test::init(); // These checks are non-deterministic, but the times are all in the // distant past or far future. So it's unlikely that the test // machine will have a clock that makes these tests fail. let now = Utc::now(); for valid_timestamp in BLOCK_HEADER_VALID_TIMESTAMPS { let block_header_time = match Utc.timestamp_opt(*valid_timestamp, 0) { LocalResult::Single(time) => time, LocalResult::None => { // Skip the test if the timestamp is invalid continue; } LocalResult::Ambiguous(_, _) => { // Utc doesn't have ambiguous times unreachable!(); } }; node_time_check(block_header_time, now) .expect("the time should be valid as a block header time"); // Invert the check, leading to an invalid time node_time_check(now, block_header_time) .expect_err("the inverse comparison should be invalid"); } for invalid_timestamp in BLOCK_HEADER_INVALID_TIMESTAMPS { let block_header_time = match Utc.timestamp_opt(*invalid_timestamp, 0) { LocalResult::Single(time) => time, LocalResult::None => { // Skip the test if the timestamp is invalid continue; } LocalResult::Ambiguous(_, _) => { // Utc doesn't have ambiguous times unreachable!(); } }; node_time_check(block_header_time, now) .expect_err("the time should be invalid as a block header time"); // Invert the check, leading to a valid time node_time_check(now, block_header_time).expect("the inverse comparison should be valid"); } }