diff --git a/zebra-chain/src/block/arbitrary.rs b/zebra-chain/src/block/arbitrary.rs index 0ce77130..cd4390c9 100644 --- a/zebra-chain/src/block/arbitrary.rs +++ b/zebra-chain/src/block/arbitrary.rs @@ -8,7 +8,11 @@ use std::sync::Arc; use crate::{ block, fmt::SummaryDebug, - parameters::{Network, NetworkUpgrade, GENESIS_PREVIOUS_BLOCK_HASH}, + parameters::{ + Network, + NetworkUpgrade::{self, *}, + GENESIS_PREVIOUS_BLOCK_HASH, + }, serialization, work::{difficulty::CompactDifficulty, equihash}, }; @@ -171,11 +175,11 @@ impl Default for LedgerStateOverride { let default_network = Network::default(); // TODO: dynamically select any future network upgrade (#1974) - let nu5_activation_height = NetworkUpgrade::Nu5.activation_height(default_network); + let nu5_activation_height = Nu5.activation_height(default_network); let nu5_override = if nu5_activation_height.is_some() { None } else { - Some(NetworkUpgrade::Nu5) + Some(Nu5) }; LedgerStateOverride { @@ -205,11 +209,7 @@ impl Arbitrary for LedgerState { ) .prop_map(move |(height, network, nu5_override, has_coinbase)| { // TODO: dynamically select any future network upgrade (#1974) - let nu5_override = if nu5_override { - Some(NetworkUpgrade::Nu5) - } else { - None - }; + let nu5_override = if nu5_override { Some(Nu5) } else { None }; LedgerState { height: ledger_override.height_override.unwrap_or(height), @@ -280,15 +280,11 @@ impl Arbitrary for Commitment { fn arbitrary_with(_args: ()) -> Self::Strategy { (any::<[u8; 32]>(), any::(), any::()) .prop_map(|(commitment_bytes, network, block_height)| { - match Commitment::from_bytes(commitment_bytes, network, block_height) { - Ok(commitment) => commitment, - // just fix up the reserved values when they fail - Err(_) => Commitment::from_bytes( - super::commitment::RESERVED_BYTES, - network, - block_height, - ) - .expect("from_bytes only fails due to reserved bytes"), + if block_height == Heartwood.activation_height(network).unwrap() { + Commitment::ChainHistoryActivationReserved + } else { + Commitment::from_bytes(commitment_bytes, network, block_height) + .expect("unexpected failure in from_bytes parsing") } }) .boxed() diff --git a/zebra-chain/src/block/commitment.rs b/zebra-chain/src/block/commitment.rs index bd3052b4..a6f6efea 100644 --- a/zebra-chain/src/block/commitment.rs +++ b/zebra-chain/src/block/commitment.rs @@ -16,12 +16,10 @@ use super::super::block; /// activation. #[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] pub enum Commitment { - /// [Pre-Sapling] Reserved field. + /// [Pre-Sapling] "A reserved field, to be ignored." /// - /// The value of this field MUST be all zeroes. - /// - /// This field is verified in `Commitment::from_bytes`. - PreSaplingReserved, + /// This field is not verified. + PreSaplingReserved([u8; 32]), /// [Sapling and Blossom] The final Sapling treestate of this block. /// @@ -93,7 +91,7 @@ pub enum Commitment { } /// The required value of reserved `Commitment`s. -pub(crate) const RESERVED_BYTES: [u8; 32] = [0; 32]; +pub(crate) const CHAIN_HISTORY_ACTIVATION_RESERVED: [u8; 32] = [0; 32]; impl Commitment { /// Returns `bytes` as the Commitment variant for `network` and `height`. @@ -106,16 +104,10 @@ impl Commitment { use CommitmentError::*; match NetworkUpgrade::current(network, height) { - Genesis | BeforeOverwinter | Overwinter => { - if bytes == RESERVED_BYTES { - Ok(PreSaplingReserved) - } else { - Err(InvalidPreSaplingReserved { actual: bytes }) - } - } + Genesis | BeforeOverwinter | Overwinter => Ok(PreSaplingReserved(bytes)), Sapling | Blossom => Ok(FinalSaplingRoot(sapling::tree::Root(bytes))), Heartwood if Some(height) == Heartwood.activation_height(network) => { - if bytes == RESERVED_BYTES { + if bytes == CHAIN_HISTORY_ACTIVATION_RESERVED { Ok(ChainHistoryActivationReserved) } else { Err(InvalidChainHistoryActivationReserved { actual: bytes }) @@ -134,9 +126,9 @@ impl Commitment { use Commitment::*; match self { - PreSaplingReserved => RESERVED_BYTES, + PreSaplingReserved(bytes) => bytes, FinalSaplingRoot(hash) => hash.0, - ChainHistoryActivationReserved => RESERVED_BYTES, + ChainHistoryActivationReserved => CHAIN_HISTORY_ACTIVATION_RESERVED, ChainHistoryRoot(hash) => hash.0, ChainHistoryBlockTxAuthCommitment(hash) => hash.0, } @@ -174,15 +166,10 @@ pub struct ChainHistoryBlockTxAuthCommitmentHash([u8; 32]); #[allow(dead_code, missing_docs)] #[derive(Error, Debug, PartialEq)] pub enum CommitmentError { - #[error("invalid pre-Sapling reserved committment: expected all zeroes, actual: {actual:?}")] - InvalidPreSaplingReserved { - // TODO: are these fields a security risk? If so, open a ticket to remove - // similar fields across Zebra - actual: [u8; 32], - }, - #[error("invalid final sapling root: expected {expected:?}, actual: {actual:?}")] InvalidFinalSaplingRoot { + // TODO: are these fields a security risk? If so, open a ticket to remove + // similar fields across Zebra expected: [u8; 32], actual: [u8; 32], }, diff --git a/zebra-chain/src/block/tests/vectors.rs b/zebra-chain/src/block/tests/vectors.rs index 6d7bbe8b..7893c263 100644 --- a/zebra-chain/src/block/tests/vectors.rs +++ b/zebra-chain/src/block/tests/vectors.rs @@ -1,12 +1,22 @@ -use std::collections::HashSet; -use std::io::{Cursor, Write}; +use std::{ + collections::HashSet, + io::{Cursor, Write}, +}; use chrono::{DateTime, Duration, LocalResult, TimeZone, Utc}; -use crate::serialization::{sha256d, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize}; -use crate::transaction::LockTime; +use crate::{ + block::{ + serialize::MAX_BLOCK_BYTES, Block, BlockTimeError, Commitment::*, Hash, Header, Height, + }, + parameters::{ + Network::{self, *}, + NetworkUpgrade::*, + }, + serialization::{sha256d, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize}, + transaction::LockTime, +}; -use super::super::{serialize::MAX_BLOCK_BYTES, *}; use super::generate; // XXX this should be rewritten as strategies #[test] @@ -129,20 +139,28 @@ fn block_test_vectors_unique() { fn block_test_vectors_height_mainnet() { zebra_test::init(); - block_test_vectors_height(Network::Mainnet); + block_test_vectors_height(Mainnet); } #[test] fn block_test_vectors_height_testnet() { zebra_test::init(); - block_test_vectors_height(Network::Testnet); + 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 = match network { - Network::Mainnet => zebra_test::vectors::MAINNET_BLOCKS.iter(), - Network::Testnet => zebra_test::vectors::TESTNET_BLOCKS.iter(), + let (block_iter, sapling_roots) = match network { + Mainnet => ( + zebra_test::vectors::MAINNET_BLOCKS.iter(), + zebra_test::vectors::MAINNET_FINAL_SAPLING_ROOTS.clone(), + ), + Testnet => ( + zebra_test::vectors::TESTNET_BLOCKS.iter(), + zebra_test::vectors::TESTNET_FINAL_SAPLING_ROOTS.clone(), + ), }; for (&height, block) in block_iter { @@ -154,6 +172,77 @@ fn block_test_vectors_height(network: Network) { 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() { + zebra_test::init(); + + block_commitment(Mainnet); +} + +#[test] +fn block_commitment_testnet() { + 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) = match network { + Mainnet => ( + zebra_test::vectors::MAINNET_BLOCKS.iter(), + zebra_test::vectors::MAINNET_FINAL_SAPLING_ROOTS.clone(), + ), + Testnet => ( + zebra_test::vectors::TESTNET_BLOCKS.iter(), + zebra_test::vectors::TESTNET_FINAL_SAPLING_ROOTS.clone(), + ), + }; + + 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, + expected_final_sapling_root.into(), + "unexpected invalid final sapling root commitment at {} {}", + network, + height + ); + } } } diff --git a/zebra-chain/src/sapling/tree.rs b/zebra-chain/src/sapling/tree.rs index 666105ca..22035c0c 100644 --- a/zebra-chain/src/sapling/tree.rs +++ b/zebra-chain/src/sapling/tree.rs @@ -96,6 +96,18 @@ impl From for [u8; 32] { } } +impl From<&[u8; 32]> for Root { + fn from(bytes: &[u8; 32]) -> Root { + (*bytes).into() + } +} + +impl From<&Root> for [u8; 32] { + fn from(root: &Root) -> Self { + (*root).into() + } +} + /// Sapling Note Commitment Tree #[derive(Clone, Debug, Default, Eq, PartialEq)] struct NoteCommitmentTree { diff --git a/zebra-test/src/vectors/block.rs b/zebra-test/src/vectors/block.rs index 9e0b9a98..170bfafe 100644 --- a/zebra-test/src/vectors/block.rs +++ b/zebra-test/src/vectors/block.rs @@ -5,7 +5,19 @@ use hex::FromHex; use lazy_static::lazy_static; -use std::collections::BTreeMap; +use std::{collections::BTreeMap, convert::TryInto}; + +trait ReverseCollection { + /// Return a reversed copy of this collection + fn rev(self) -> Self; +} + +impl ReverseCollection for [u8; 32] { + fn rev(mut self) -> [u8; 32] { + self.reverse(); + self + } +} lazy_static! { @@ -92,6 +104,36 @@ lazy_static! { (1_180_900, BLOCK_MAINNET_1180900_BYTES.as_ref()), ].iter().cloned().collect(); + /// Mainnet final sapling roots, indexed by height + /// + /// Pre-sapling roots are all-zeroes. + /// If there are no sapling inputs or outputs in a block, the final sapling root is the same as the previous block. + pub static ref MAINNET_FINAL_SAPLING_ROOTS: BTreeMap = [ + // Sapling + (419_200, SAPLING_FINAL_ROOT_MAINNET_419200_BYTES.as_ref().try_into().unwrap()), + (419_201, SAPLING_FINAL_ROOT_MAINNET_419201_BYTES.as_ref().try_into().unwrap()), + // A bad version field + (434_873, SAPLING_FINAL_ROOT_MAINNET_434873_BYTES.as_ref().try_into().unwrap()), + (653_599, SAPLING_FINAL_ROOT_MAINNET_653599_BYTES.as_ref().try_into().unwrap()), + // Blossom + (653_600, SAPLING_FINAL_ROOT_MAINNET_653600_BYTES.as_ref().try_into().unwrap()), + (653_601, SAPLING_FINAL_ROOT_MAINNET_653601_BYTES.as_ref().try_into().unwrap()), + (902_999, SAPLING_FINAL_ROOT_MAINNET_902999_BYTES.as_ref().try_into().unwrap()), + // Heartwood + (903_000, SAPLING_FINAL_ROOT_MAINNET_903000_BYTES.as_ref().try_into().unwrap()), + (903_001, SAPLING_FINAL_ROOT_MAINNET_903001_BYTES.as_ref().try_into().unwrap()), + // Shielded coinbase x3 + (949_496, SAPLING_FINAL_ROOT_MAINNET_949496_BYTES.as_ref().try_into().unwrap()), + (975_066, SAPLING_FINAL_ROOT_MAINNET_975066_BYTES.as_ref().try_into().unwrap()), + (982_681, SAPLING_FINAL_ROOT_MAINNET_982681_BYTES.as_ref().try_into().unwrap()), + // Last Heartwood + (1_046_399, SAPLING_FINAL_ROOT_MAINNET_1046399_BYTES.as_ref().try_into().unwrap()), + // Canopy and First Coinbase Halving + (1_046_400, SAPLING_FINAL_ROOT_MAINNET_1046400_BYTES.as_ref().try_into().unwrap()), + (1_046_401, SAPLING_FINAL_ROOT_MAINNET_1046401_BYTES.as_ref().try_into().unwrap()), + (1_180_900, SAPLING_FINAL_ROOT_MAINNET_1180900_BYTES.as_ref().try_into().unwrap()), + ].iter().cloned().collect(); + /// Testnet blocks, indexed by height /// /// This is actually a bijective map, the tests ensure that values are unique. @@ -153,6 +195,48 @@ lazy_static! { (1_326_100, BLOCK_TESTNET_1326100_BYTES.as_ref()), ].iter().cloned().collect(); + /// Testnet final sapling roots, indexed by height + /// + /// Pre-sapling roots are all-zeroes. + /// If there are no sapling inputs or outputs in a block, the final sapling root is the same as the previous block. + pub static ref TESTNET_FINAL_SAPLING_ROOTS: BTreeMap = [ + // Sapling + (280_000, SAPLING_FINAL_ROOT_TESTNET_280000_BYTES.as_ref().try_into().unwrap()), + (280_001, SAPLING_FINAL_ROOT_TESTNET_280001_BYTES.as_ref().try_into().unwrap()), + (299_187, SAPLING_FINAL_ROOT_TESTNET_299187_BYTES.as_ref().try_into().unwrap()), + // Minimum-difficulty blocks x2 + // See zebra_chain's MINIMUM_DIFFICULTY_HEIGHTS for a full list + (299_188, SAPLING_FINAL_ROOT_TESTNET_299188_BYTES.as_ref().try_into().unwrap()), + (299_189, SAPLING_FINAL_ROOT_TESTNET_299189_BYTES.as_ref().try_into().unwrap()), + (299_201, SAPLING_FINAL_ROOT_TESTNET_299201_BYTES.as_ref().try_into().unwrap()), + // Minimum-difficulty block + (299_202, SAPLING_FINAL_ROOT_TESTNET_299202_BYTES.as_ref().try_into().unwrap()), + (583_999, SAPLING_FINAL_ROOT_TESTNET_583999_BYTES.as_ref().try_into().unwrap()), + // Blossom + (584_000, SAPLING_FINAL_ROOT_TESTNET_584000_BYTES.as_ref().try_into().unwrap()), + (584_001, SAPLING_FINAL_ROOT_TESTNET_584001_BYTES.as_ref().try_into().unwrap()), + (903_799, SAPLING_FINAL_ROOT_TESTNET_903799_BYTES.as_ref().try_into().unwrap()), + // Heartwood + (903_800, SAPLING_FINAL_ROOT_TESTNET_903800_BYTES.as_ref().try_into().unwrap()), + (903_801, SAPLING_FINAL_ROOT_TESTNET_903801_BYTES.as_ref().try_into().unwrap()), + // Shielded coinbase x2 + (914_678, SAPLING_FINAL_ROOT_TESTNET_914678_BYTES.as_ref().try_into().unwrap()), + (925_483, SAPLING_FINAL_ROOT_TESTNET_925483_BYTES.as_ref().try_into().unwrap()), + (1_028_499, SAPLING_FINAL_ROOT_TESTNET_1028499_BYTES.as_ref().try_into().unwrap()), + // Canopy + (1_028_500, SAPLING_FINAL_ROOT_TESTNET_1028500_BYTES.as_ref().try_into().unwrap()), + (1_028_501, SAPLING_FINAL_ROOT_TESTNET_1028501_BYTES.as_ref().try_into().unwrap()), + (1_095_000, SAPLING_FINAL_ROOT_TESTNET_1095000_BYTES.as_ref().try_into().unwrap()), + // Shielded coinbase + (1_101_629, SAPLING_FINAL_ROOT_TESTNET_1101629_BYTES.as_ref().try_into().unwrap()), + // Last Pre-Halving + (1_115_999, SAPLING_FINAL_ROOT_TESTNET_1115999_BYTES.as_ref().try_into().unwrap()), + // First Coinbase Halving + (1_116_000, SAPLING_FINAL_ROOT_TESTNET_1116000_BYTES.as_ref().try_into().unwrap()), + (1_116_001, SAPLING_FINAL_ROOT_TESTNET_1116001_BYTES.as_ref().try_into().unwrap()), + (1_326_100, SAPLING_FINAL_ROOT_TESTNET_1326100_BYTES.as_ref().try_into().unwrap()), + ].iter().cloned().collect(); + // Mainnet // Genesis/BeforeOverwinter @@ -192,16 +276,16 @@ lazy_static! { // zcash-cli getblock 10 0 > block-main-0-000-010.txt pub static ref BLOCK_MAINNET_10_BYTES: Vec = >::from_hex(include_str!("block-main-0-000-010.txt").trim()) - .expect("Block bytes are in valid hex representation"); + .expect("Block bytes are in valid hex representation"); pub static ref BLOCK_MAINNET_202_BYTES: Vec = >::from_hex(include_str!("block-main-0-000-202.txt").trim()) - .expect("Block bytes are in valid hex representation"); + .expect("Block bytes are in valid hex representation"); /// This contains an encoding of block 202 but with an improperly encoded /// coinbase height. pub static ref BAD_BLOCK_MAINNET_202_BYTES: Vec = >::from_hex(include_str!("block-main-0-000-202-bad.txt").trim()) - .expect("Block bytes are in valid hex representation"); + .expect("Block bytes are in valid hex representation"); // Overwinter transition // for i in 347499 347500 347501; do @@ -209,36 +293,56 @@ lazy_static! { // done pub static ref BLOCK_MAINNET_347499_BYTES: Vec = >::from_hex(include_str!("block-main-0-347-499.txt").trim()) - .expect("Block bytes are in valid hex representation"); + .expect("Block bytes are in valid hex representation"); pub static ref BLOCK_MAINNET_347500_BYTES: Vec = >::from_hex(include_str!("block-main-0-347-500.txt").trim()) - .expect("Block bytes are in valid hex representation"); + .expect("Block bytes are in valid hex representation"); pub static ref BLOCK_MAINNET_347501_BYTES: Vec = >::from_hex(include_str!("block-main-0-347-501.txt").trim()) - .expect("Block bytes are in valid hex representation"); + .expect("Block bytes are in valid hex representation"); // zcash-cli getblock 415000 0 > block-main-0-415-000.txt pub static ref BLOCK_MAINNET_415000_BYTES: Vec = >::from_hex(include_str!("block-main-0-415-000.txt").trim()) - .expect("Block bytes are in valid hex representation"); + .expect("Block bytes are in valid hex representation"); // Sapling transition // for i in 419199 419200 419201; do // zcash-cli getblock $i 0 > block-main-$[i/1000000]-$[i/1000%1000]-$[i%1000].txt // done + // + // zcashd provides final sapling roots in big-endian order, but Zebra stores + // that field in block order internally. + // + // for i in `cat post_sapling_mainnet_heights`; do + // zcash-cli z_gettreestate "$i" | \ + // jq --arg i "$i" \ + // --raw-output \ + // '"pub static ref SAPLING_FINAL_ROOT_MAINNET_\($i)_BYTES: [u8; 32] = <[u8; 32]>::from_hex(\"\(.sapling.commitments.finalRoot)\").expect(\"final root bytes are in valid hex representation\").rev();"' + // done pub static ref BLOCK_MAINNET_419199_BYTES: Vec = >::from_hex(include_str!("block-main-0-419-199.txt").trim()) - .expect("Block bytes are in valid hex representation"); + .expect("Block bytes are in valid hex representation"); pub static ref BLOCK_MAINNET_419200_BYTES: Vec = >::from_hex(include_str!("block-main-0-419-200.txt").trim()) - .expect("Block bytes are in valid hex representation"); + .expect("Block bytes are in valid hex representation"); pub static ref BLOCK_MAINNET_419201_BYTES: Vec = >::from_hex(include_str!("block-main-0-419-201.txt").trim()) - .expect("Block bytes are in valid hex representation"); + .expect("Block bytes are in valid hex representation"); + pub static ref SAPLING_FINAL_ROOT_MAINNET_419200_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("3e49b5f954aa9d3545bc6c37744661eea48d7c34e3000d82b7f0010c30f4c2fb") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_MAINNET_419201_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("638d7e5ba37ab7921c51a4f3ae1b32d71c605a0ed9be7477928111a637f7421b") + .expect("final root bytes are in valid hex representation").rev(); + // this one has a bad version field // zcash-cli getblock 434873 0 > block-main-0-434-873.txt pub static ref BLOCK_MAINNET_434873_BYTES: Vec = >::from_hex(include_str!("block-main-0-434-873.txt").trim()) - .expect("Block bytes are in valid hex representation"); + .expect("Block bytes are in valid hex representation"); + pub static ref SAPLING_FINAL_ROOT_MAINNET_434873_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("56e33199bc41d146cb24d24a65db35101248a1d12fff33affef56f90081a9517") + .expect("final root bytes are in valid hex representation").rev(); // Blossom transition // for i in 653599 653600 653601; do @@ -246,29 +350,48 @@ lazy_static! { // done pub static ref BLOCK_MAINNET_653599_BYTES: Vec = >::from_hex(include_str!("block-main-0-653-599.txt").trim()) - .expect("Block bytes are in valid hex representation"); + .expect("Block bytes are in valid hex representation"); pub static ref BLOCK_MAINNET_653600_BYTES: Vec = >::from_hex(include_str!("block-main-0-653-600.txt").trim()) - .expect("Block bytes are in valid hex representation"); + .expect("Block bytes are in valid hex representation"); pub static ref BLOCK_MAINNET_653601_BYTES: Vec = >::from_hex(include_str!("block-main-0-653-601.txt").trim()) - .expect("Block bytes are in valid hex representation"); + .expect("Block bytes are in valid hex representation"); + pub static ref SAPLING_FINAL_ROOT_MAINNET_653599_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("3d532d101b9171769423a9f45a65b6312e28e7aa92b627cb81810f7a6fe21c6a") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_MAINNET_653600_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("3d532d101b9171769423a9f45a65b6312e28e7aa92b627cb81810f7a6fe21c6a") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_MAINNET_653601_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("612c62e54ef55f7bf8a60281debf1df904bf1fa6d1fa65d9656302b44ea98427") + .expect("final root bytes are in valid hex representation").rev(); // Heartwood transition // i=902999 // zcash-cli getblock $i 0 > block-main-$[i/1000000]-$[i/1000%1000]-$[i%1000].txt pub static ref BLOCK_MAINNET_902999_BYTES: Vec = >::from_hex(include_str!("block-main-0-902-999.txt").trim()) - .expect("Block bytes are in valid hex representation"); + .expect("Block bytes are in valid hex representation"); // for i in 903000 903001; do // zcash-cli getblock $i 0 > block-main-$[i/1000000]-$[i/1000%1000]-00$[i%1000].txt // done pub static ref BLOCK_MAINNET_903000_BYTES: Vec = >::from_hex(include_str!("block-main-0-903-000.txt").trim()) - .expect("Block bytes are in valid hex representation"); + .expect("Block bytes are in valid hex representation"); pub static ref BLOCK_MAINNET_903001_BYTES: Vec = >::from_hex(include_str!("block-main-0-903-001.txt").trim()) .expect("Block bytes are in valid hex representation"); + pub static ref SAPLING_FINAL_ROOT_MAINNET_902999_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("49df1a6e62458b0226b9d6c0c28fb7e94d9ca840582878b10d0117fd028b4e91") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_MAINNET_903000_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("11e48300f0e2296d5c413340b26426eddada1155155f4e959ebe307396976c79") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_MAINNET_903001_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("14e3c2b8af239bc4e17486573c20824292d5e1a6670dedf58bf865159e389cce") + .expect("final root bytes are in valid hex representation").rev(); + // Shielded coinbase // for i in 949496 982681; do // zcash-cli getblock $i 0 > block-main-$[i/1000000]-$[i/1000%1000]-$[i%1000].txt @@ -278,7 +401,7 @@ lazy_static! { // First shielded coinbase block pub static ref BLOCK_MAINNET_949496_BYTES: Vec = >::from_hex(include_str!("block-main-0-949-496.txt").trim()) - .expect("Block bytes are in valid hex representation"); + .expect("Block bytes are in valid hex representation"); // Largest shielded coinbase block so far (in bytes) pub static ref BLOCK_MAINNET_975066_BYTES: Vec = >::from_hex(include_str!("block-main-0-975-066.txt").trim()) @@ -287,6 +410,15 @@ lazy_static! { pub static ref BLOCK_MAINNET_982681_BYTES: Vec = >::from_hex(include_str!("block-main-0-982-681.txt").trim()) .expect("Block bytes are in valid hex representation"); + pub static ref SAPLING_FINAL_ROOT_MAINNET_949496_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("4db238913a86284f5bddb9bcfe76f96a46d097ec681aad1da846cc276bfa7263") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_MAINNET_975066_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("2f62381b6decd0e0f937f6aa23faa7d19444b784701be93ad7e4df31bd4da1f9") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_MAINNET_982681_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("4fd1cb6d1e5c479baa44fcb7c3a1c6fafdaa54c0456d254918cd63839805848d") + .expect("final root bytes are in valid hex representation").rev(); // Canopy transition and Coinbase Halving // (On mainnet, Canopy happens at the same block as the first coinbase halving) @@ -295,14 +427,25 @@ lazy_static! { // done pub static ref BLOCK_MAINNET_1046399_BYTES: Vec = >::from_hex(include_str!("block-main-1-046-399.txt").trim()) - .expect("Block bytes are in valid hex representation"); + .expect("Block bytes are in valid hex representation"); pub static ref BLOCK_MAINNET_1046400_BYTES: Vec = >::from_hex(include_str!("block-main-1-046-400.txt").trim()) - .expect("Block bytes are in valid hex representation"); + .expect("Block bytes are in valid hex representation"); // Block 1046401 is 72 kB in size (currently the second-largest test vector), so we store it in binary. // i=1046401 // zcash-cli getblock $i 0 | xxd -revert -plain > block-main-$[i/1000000]-0$[i/1000%1000]-$[i%1000].bin - pub static ref BLOCK_MAINNET_1046401_BYTES: Vec = include_bytes!("block-main-1-046-401.bin").to_vec(); + pub static ref BLOCK_MAINNET_1046401_BYTES: Vec = + include_bytes!("block-main-1-046-401.bin") + .to_vec(); + pub static ref SAPLING_FINAL_ROOT_MAINNET_1046399_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("0f12c92f737e84142792bddc82e36481de4a7679d5778a27389793933c8742e1") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_MAINNET_1046400_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("0f12c92f737e84142792bddc82e36481de4a7679d5778a27389793933c8742e1") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_MAINNET_1046401_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("1cb7a61a31354384957eea0b98661e1cf85a5de8e43f0e3bef522c8b375b26cb") + .expect("final root bytes are in valid hex representation").rev(); // One more Canopy/Post-Halving block // (so that we have at least 3 blocks after canopy/halving) @@ -310,7 +453,10 @@ lazy_static! { // zcash-cli getblock $i 0 > block-main-$[i/1000000]-$[i/1000%1000]-$[i%1000].txt pub static ref BLOCK_MAINNET_1180900_BYTES: Vec = >::from_hex(include_str!("block-main-1-180-900.txt").trim()) - .expect("Block bytes are in valid hex representation"); + .expect("Block bytes are in valid hex representation"); + pub static ref SAPLING_FINAL_ROOT_MAINNET_1180900_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("4a51c1b879f49637873ac4b261e9c625e16d9400b22d8aa4f27cd6fd1138ddda") + .expect("final root bytes are in valid hex representation").rev(); // Testnet @@ -385,6 +531,13 @@ lazy_static! { // for i in 280000 280001; do // zcash-cli -testnet getblock $i 0 > block-test-$[i/1000000]-$[i/1000%1000]-00$[i%1000].txt // done + // + // for i in `cat post_sapling_testnet_heights`; do + // zcash-cli -testnet z_gettreestate "$i" | \ + // jq --arg i "$i" \ + // --raw-output \ + // '"pub static ref SAPLING_FINAL_ROOT_TESTNET_\($i)_BYTES: [u8; 32] = <[u8; 32]>::from_hex(\"\(.sapling.commitments.finalRoot)\").expect(\"final root bytes are in valid hex representation\").rev();"' + // done pub static ref BLOCK_TESTNET_279999_BYTES: Vec = >::from_hex(include_str!("block-test-0-279-999.txt").trim()) .expect("Block bytes are in valid hex representation"); @@ -394,6 +547,12 @@ lazy_static! { pub static ref BLOCK_TESTNET_280001_BYTES: Vec = >::from_hex(include_str!("block-test-0-280-001.txt").trim()) .expect("Block bytes are in valid hex representation"); + pub static ref SAPLING_FINAL_ROOT_TESTNET_280000_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("3e49b5f954aa9d3545bc6c37744661eea48d7c34e3000d82b7f0010c30f4c2fb") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_TESTNET_280001_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("3e49b5f954aa9d3545bc6c37744661eea48d7c34e3000d82b7f0010c30f4c2fb") + .expect("final root bytes are in valid hex representation").rev(); // The first minimum difficulty blocks 299188, 299189, 299202 and their previous blocks for context // (pre-Blossom minimum difficulty) @@ -416,6 +575,21 @@ lazy_static! { pub static ref BLOCK_TESTNET_299202_BYTES: Vec = >::from_hex(include_str!("block-test-0-299-202.txt").trim()) .expect("Block bytes are in valid hex representation"); + pub static ref SAPLING_FINAL_ROOT_TESTNET_299187_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("6815df99f9f7ec9486a0b3a4e992ff9348dba7101c2ac91be41ceab392aa5521") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_TESTNET_299188_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("6815df99f9f7ec9486a0b3a4e992ff9348dba7101c2ac91be41ceab392aa5521") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_TESTNET_299189_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("6815df99f9f7ec9486a0b3a4e992ff9348dba7101c2ac91be41ceab392aa5521") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_TESTNET_299201_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("6815df99f9f7ec9486a0b3a4e992ff9348dba7101c2ac91be41ceab392aa5521") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_TESTNET_299202_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("6815df99f9f7ec9486a0b3a4e992ff9348dba7101c2ac91be41ceab392aa5521") + .expect("final root bytes are in valid hex representation").rev(); // Blossom transition // i=583999 @@ -433,6 +607,15 @@ lazy_static! { pub static ref BLOCK_TESTNET_584001_BYTES: Vec = >::from_hex(include_str!("block-test-0-584-001.txt").trim()) .expect("Block bytes are in valid hex representation"); + pub static ref SAPLING_FINAL_ROOT_TESTNET_583999_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("13746c0c426cdddd05f85d86231f8bc647f5b024277c606c309ef707d85fd652") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_TESTNET_584000_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("13746c0c426cdddd05f85d86231f8bc647f5b024277c606c309ef707d85fd652") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_TESTNET_584001_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("13746c0c426cdddd05f85d86231f8bc647f5b024277c606c309ef707d85fd652") + .expect("final root bytes are in valid hex representation").rev(); // Heartwood transition // for i in 903799 903800 903801; do @@ -447,6 +630,16 @@ lazy_static! { pub static ref BLOCK_TESTNET_903801_BYTES: Vec = >::from_hex(include_str!("block-test-0-903-801.txt").trim()) .expect("Block bytes are in valid hex representation"); + pub static ref SAPLING_FINAL_ROOT_TESTNET_903799_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("626444395cd5963d3dba2652ee5bd73ef57555cca4d9f0d52e887a3bf44488e2") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_TESTNET_903800_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("626444395cd5963d3dba2652ee5bd73ef57555cca4d9f0d52e887a3bf44488e2") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_TESTNET_903801_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("626444395cd5963d3dba2652ee5bd73ef57555cca4d9f0d52e887a3bf44488e2") + .expect("final root bytes are in valid hex representation").rev(); + // Shielded coinbase // for i in 914678 925483; do // zcash-cli -testnet getblock $i 0 > block-test-$[i/1000000]-$[i/1000%1000]-$[i%1000].txt @@ -459,6 +652,12 @@ lazy_static! { pub static ref BLOCK_TESTNET_925483_BYTES: Vec = >::from_hex(include_str!("block-test-0-925-483.txt").trim()) .expect("Block bytes are in valid hex representation"); + pub static ref SAPLING_FINAL_ROOT_TESTNET_914678_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("0ba286a3fb00d8a63b6ea52064bcc58ffb859aaa881745157d9a67d20afd7a8d") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_TESTNET_925483_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("271370ff86c2a9cc452334098d3337cddffc478e66a356dc0c00aebb58a4b6ac") + .expect("final root bytes are in valid hex representation").rev(); // Canopy transition // for i in 1028499 1028500 1028501; do @@ -473,6 +672,16 @@ lazy_static! { pub static ref BLOCK_TESTNET_1028501_BYTES: Vec = >::from_hex(include_str!("block-test-1-028-501.txt").trim()) .expect("Block bytes are in valid hex representation"); + pub static ref SAPLING_FINAL_ROOT_TESTNET_1028499_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("580adc0253cd0545250039267b7b49445ca0550df735920b7466bba1a64f7cf7") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_TESTNET_1028500_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("580adc0253cd0545250039267b7b49445ca0550df735920b7466bba1a64f7cf7") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_TESTNET_1028501_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("580adc0253cd0545250039267b7b49445ca0550df735920b7466bba1a64f7cf7") + .expect("final root bytes are in valid hex representation").rev(); + // One more Canopy block // (so that we have at least 3 blocks from Canopy) // i=1095000 @@ -480,6 +689,10 @@ lazy_static! { pub static ref BLOCK_TESTNET_1095000_BYTES: Vec = >::from_hex(include_str!("block-test-1-095-000.txt").trim()) .expect("Block bytes are in valid hex representation"); + pub static ref SAPLING_FINAL_ROOT_TESTNET_1095000_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("1781d73666ceb7675323130defd5fae426f1ee7d5fbb83adc9393aa8ff7e8a8d") + .expect("final root bytes are in valid hex representation").rev(); + // Shielded coinbase + Canopy // i=1101629 // zcash-cli -testnet getblock $i 0 > block-test-$[i/1000000]-0$[i/1000%1000]-$[i%1000].txt @@ -487,6 +700,9 @@ lazy_static! { pub static ref BLOCK_TESTNET_1101629_BYTES: Vec = >::from_hex(include_str!("block-test-1-101-629.txt").trim()) .expect("Block bytes are in valid hex representation"); + pub static ref SAPLING_FINAL_ROOT_TESTNET_1101629_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("14c7c827cd05c73c052c2a9faa6d7fc7b45ec2e386d8894eb65c420175c43745") + .expect("final root bytes are in valid hex representation").rev(); // Testnet Coinbase Halving // i=1115999 @@ -503,6 +719,15 @@ lazy_static! { pub static ref BLOCK_TESTNET_1116001_BYTES: Vec = >::from_hex(include_str!("block-test-1-116-001.txt").trim()) .expect("Block bytes are in valid hex representation"); + pub static ref SAPLING_FINAL_ROOT_TESTNET_1115999_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("14b41a6dd7cd3c113d98f72543c4f57ff4e444bd5995366e0da420169c861f37") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_TESTNET_1116000_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("14b41a6dd7cd3c113d98f72543c4f57ff4e444bd5995366e0da420169c861f37") + .expect("final root bytes are in valid hex representation").rev(); + pub static ref SAPLING_FINAL_ROOT_TESTNET_1116001_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("14b41a6dd7cd3c113d98f72543c4f57ff4e444bd5995366e0da420169c861f37") + .expect("final root bytes are in valid hex representation").rev(); // One more Post-Halving block // (so that we have at least 3 blocks after the halving) @@ -511,6 +736,9 @@ lazy_static! { pub static ref BLOCK_TESTNET_1326100_BYTES: Vec = >::from_hex(include_str!("block-test-1-326-100.txt").trim()) .expect("Block bytes are in valid hex representation"); + pub static ref SAPLING_FINAL_ROOT_TESTNET_1326100_BYTES: [u8; 32] = + <[u8; 32]>::from_hex("2b30b19f4254709fe365bd0b381b2e3d9d0c933eb4dba4dd1d07f0f6e196a183") + .expect("final root bytes are in valid hex representation").rev(); } #[cfg(test)] @@ -534,6 +762,8 @@ mod test { block_set.len(), "block test vectors must be unique" ); + + // final sapling roots can be duplicated if a block has no sapling spends or outputs } /// Make sure we use all the test vectors in the lists. @@ -544,8 +774,23 @@ mod test { init(); assert!( - BLOCKS.len() > 50, - "there should be a reasonable number of block test vectors" + MAINNET_BLOCKS.len() > 30, + "there should be a reasonable number of mainnet block test vectors" + ); + + assert!( + TESTNET_BLOCKS.len() > 30, + "there should be a reasonable number of testnet block test vectors" + ); + + assert!( + MAINNET_FINAL_SAPLING_ROOTS.len() > 10, + "there should be a reasonable number of mainnet final sapling root test vectors" + ); + + assert!( + TESTNET_FINAL_SAPLING_ROOTS.len() > 10, + "there should be a reasonable number of testnet final sapling root test vectors" ); } }