From b4deca2912d4477feaa5d8453e115d43acf0875a Mon Sep 17 00:00:00 2001 From: teor Date: Sat, 19 Mar 2022 06:30:16 +1000 Subject: [PATCH] 6. add(test): add more tests for height and transaction location serialization (#3879) * Add round-trip test for OutputLocation disk serialization * Make the transaction snapshot tests more accurate Previously, we were showing the genesis transaction hash at height 0, index 0. But its hash is actually not stored by location in the database, because the genesis transaction is skipped due to a consensus rule. * Update the transaction snapshot data * Add history tree snapshot tests At the current test heights, the history trees are all empty. * Add the history tree snapshot data * Update comments * Simplify snapshot test code * Make some serde::Serialize impls test-only, so we can hex-encode them This should also speed up release compilation a bit. * Add snapshot test code for UTXOs * Add snapshot data for UTXOs --- zebra-chain/Cargo.toml | 5 +- zebra-chain/src/amount.rs | 13 +- zebra-chain/src/block.rs | 7 +- zebra-chain/src/transaction.rs | 8 +- zebra-chain/src/transparent.rs | 36 ++++- zebra-chain/src/transparent/script.rs | 13 +- zebra-chain/src/transparent/utxo.rs | 2 +- zebra-state/Cargo.toml | 9 +- .../finalized_state/disk_format/tests/prop.rs | 17 +- .../service/finalized_state/zebra_db/block.rs | 11 +- .../zebra_db/block/tests/snapshot.rs | 149 +++++++++++++----- .../snapshots/history_tree@mainnet_0.snap | 12 ++ .../snapshots/history_tree@mainnet_1.snap | 12 ++ .../snapshots/history_tree@mainnet_2.snap | 12 ++ .../snapshots/history_tree@testnet_0.snap | 12 ++ .../snapshots/history_tree@testnet_1.snap | 12 ++ .../snapshots/history_tree@testnet_2.snap | 12 ++ .../transaction_hashes@mainnet_0.snap | 9 +- .../transaction_hashes@mainnet_1.snap | 15 +- .../transaction_hashes@mainnet_2.snap | 21 +-- .../transaction_hashes@testnet_0.snap | 9 +- .../transaction_hashes@testnet_1.snap | 15 +- .../transaction_hashes@testnet_2.snap | 21 +-- .../tests/snapshots/utxos@mainnet_0.snap | 10 ++ .../tests/snapshots/utxos@mainnet_1.snap | 32 ++++ .../tests/snapshots/utxos@mainnet_2.snap | 54 +++++++ .../tests/snapshots/utxos@testnet_0.snap | 10 ++ .../tests/snapshots/utxos@testnet_1.snap | 32 ++++ .../tests/snapshots/utxos@testnet_2.snap | 54 +++++++ 29 files changed, 490 insertions(+), 134 deletions(-) create mode 100644 zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@mainnet_0.snap create mode 100644 zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@mainnet_1.snap create mode 100644 zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@mainnet_2.snap create mode 100644 zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@testnet_0.snap create mode 100644 zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@testnet_1.snap create mode 100644 zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@testnet_2.snap create mode 100644 zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@mainnet_0.snap create mode 100644 zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@mainnet_1.snap create mode 100644 zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@mainnet_2.snap create mode 100644 zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@testnet_0.snap create mode 100644 zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@testnet_1.snap create mode 100644 zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@testnet_2.snap diff --git a/zebra-chain/Cargo.toml b/zebra-chain/Cargo.toml index 767fb99b..3a4dd1b4 100644 --- a/zebra-chain/Cargo.toml +++ b/zebra-chain/Cargo.toml @@ -9,7 +9,8 @@ edition = "2021" [features] default = [] -proptest-impl = ["proptest", "proptest-derive", "zebra-test", "rand", "rand_chacha", "tokio"] +proptest-impl = ["proptest", "proptest-derive", "zebra-test", "rand", "rand_chacha", "tokio", +"hex/serde"] bench = ["zebra-test"] [dependencies] @@ -76,6 +77,8 @@ itertools = "0.10.3" spandoc = "0.2.1" tracing = "0.1.31" +hex = { version = "0.4.3", features = ["serde"] } + proptest = "0.10.1" proptest-derive = "0.3.0" rand = { version = "0.8.5", package = "rand" } diff --git a/zebra-chain/src/amount.rs b/zebra-chain/src/amount.rs index f44a9f17..c0f432bb 100644 --- a/zebra-chain/src/amount.rs +++ b/zebra-chain/src/amount.rs @@ -28,7 +28,18 @@ type Result = std::result::Result; #[derive(Clone, Copy, Serialize, Deserialize)] #[serde(try_from = "i64")] #[serde(bound = "C: Constraint")] -pub struct Amount(i64, PhantomData); +pub struct Amount( + /// The inner amount value. + i64, + /// Used for [`Constraint`] type inference. + /// + /// # Correctness + /// + /// This internal Zebra marker type is not consensus-critical. + /// And it should be ignored during testing. (And other internal uses.) + #[serde(skip)] + PhantomData, +); impl std::fmt::Debug for Amount { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/zebra-chain/src/block.rs b/zebra-chain/src/block.rs index 351328f6..6b7f176c 100644 --- a/zebra-chain/src/block.rs +++ b/zebra-chain/src/block.rs @@ -14,7 +14,7 @@ pub mod arbitrary; #[cfg(any(test, feature = "bench", feature = "proptest-impl"))] pub mod tests; -use std::{collections::HashMap, convert::TryInto, fmt, ops::Neg}; +use std::{collections::HashMap, fmt, ops::Neg}; pub use commitment::{ ChainHistoryBlockTxAuthCommitmentHash, ChainHistoryMmrRootHash, Commitment, CommitmentError, @@ -27,8 +27,6 @@ pub use serialize::{SerializedBlock, MAX_BLOCK_BYTES}; #[cfg(any(test, feature = "proptest-impl"))] pub use arbitrary::LedgerState; -use serde::{Deserialize, Serialize}; - use crate::{ amount::NegativeAllowed, block::merkle::AuthDataRoot, @@ -44,7 +42,8 @@ use crate::{ }; /// A Zcash block, containing a header and a list of transactions. -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Serialize))] pub struct Block { /// The block header, containing block metadata. pub header: Header, diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index 1aeaaa9d..c8b65106 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -1,7 +1,8 @@ //! Transactions and transaction-related structures. +use std::{collections::HashMap, fmt, iter}; + use halo2::pasta::pallas; -use serde::{Deserialize, Serialize}; mod auth_digest; mod hash; @@ -40,8 +41,6 @@ use crate::{ value_balance::{ValueBalance, ValueBalanceError}, }; -use std::{collections::HashMap, fmt, iter}; - /// A Zcash transaction. /// /// A transaction is an encoded data structure that facilitates the transfer of @@ -53,7 +52,8 @@ use std::{collections::HashMap, fmt, iter}; /// Zcash has a number of different transaction formats. They are represented /// internally by different enum variants. Because we checkpoint on Canopy /// activation, we do not validate any pre-Sapling transaction types. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Serialize))] // XXX consider boxing the Optional fields of V4 and V5 txs #[allow(clippy::large_enum_variant)] pub enum Transaction { diff --git a/zebra-chain/src/transparent.rs b/zebra-chain/src/transparent.rs index d95360ea..f2b97b11 100644 --- a/zebra-chain/src/transparent.rs +++ b/zebra-chain/src/transparent.rs @@ -48,7 +48,8 @@ use std::{collections::HashMap, fmt, iter}; pub const MIN_TRANSPARENT_COINBASE_MATURITY: u32 = 100; /// Arbitrary data inserted by miners into a coinbase transaction. -#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Eq, PartialEq)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Serialize))] pub struct CoinbaseData( /// Invariant: this vec, together with the coinbase height, must be less than /// 100 bytes. We enforce this by only constructing CoinbaseData fields by @@ -92,10 +93,16 @@ impl std::fmt::Debug for CoinbaseData { /// OutPoint /// /// A particular transaction output reference. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] -#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary, Serialize))] pub struct OutPoint { /// References the transaction that contains the UTXO being spent. + /// + /// # Correctness + /// + /// Consensus-critical serialization uses [`ZcashSerialize`]. + /// [`serde`]-based hex serialization must only be used for testing. + #[cfg_attr(any(test, feature = "proptest-impl"), serde(with = "hex"))] pub hash: transaction::Hash, /// Identifies which UTXO from that transaction is referenced; the @@ -103,8 +110,25 @@ pub struct OutPoint { pub index: u32, } +impl OutPoint { + /// Returns a new OutPoint from an in-memory output `index`. + /// + /// # Panics + /// + /// If `index` doesn't fit in a [`u32`]. + pub fn from_usize(hash: transaction::Hash, index: usize) -> OutPoint { + OutPoint { + hash, + index: index + .try_into() + .expect("valid in-memory output indexes fit in a u32"), + } + } +} + /// A transparent input to a transaction. -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Serialize))] pub enum Input { /// A reference to an output of a previous transaction. PrevOut { @@ -289,8 +313,8 @@ impl Input { /// I only own one UTXO worth 2 ZEC, I would construct a transaction /// that spends my UTXO and sends 1 ZEC to you and 1 ZEC back to me /// (just like receiving change). -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] -#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary, Serialize))] pub struct Output { /// Transaction value. // At https://en.bitcoin.it/wiki/Protocol_documentation#tx, this is an i64. diff --git a/zebra-chain/src/transparent/script.rs b/zebra-chain/src/transparent/script.rs index 90ef5457..c6e02db1 100644 --- a/zebra-chain/src/transparent/script.rs +++ b/zebra-chain/src/transparent/script.rs @@ -7,12 +7,19 @@ use crate::serialization::{ }; /// An encoding of a Bitcoin script. -#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Hash)] +#[derive(Clone, Eq, PartialEq, Hash)] #[cfg_attr( any(test, feature = "proptest-impl"), - derive(proptest_derive::Arbitrary) + derive(proptest_derive::Arbitrary, serde::Serialize) )] -pub struct Script(Vec); +pub struct Script( + /// # Correctness + /// + /// Consensus-critical serialization uses [`ZcashSerialize`]. + /// [`serde`]-based hex serialization must only be used for testing. + #[cfg_attr(any(test, feature = "proptest-impl"), serde(with = "hex"))] + Vec, +); impl Script { /// Create a new Bitcoin script from its raw bytes. diff --git a/zebra-chain/src/transparent/utxo.rs b/zebra-chain/src/transparent/utxo.rs index e7bfda4a..4f836159 100644 --- a/zebra-chain/src/transparent/utxo.rs +++ b/zebra-chain/src/transparent/utxo.rs @@ -12,7 +12,7 @@ use crate::{ #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr( any(test, feature = "proptest-impl"), - derive(proptest_derive::Arbitrary) + derive(proptest_derive::Arbitrary, serde::Serialize) )] pub struct Utxo { /// The output itself. diff --git a/zebra-state/Cargo.toml b/zebra-state/Cargo.toml index 8e653ee9..0e0e0e63 100644 --- a/zebra-state/Cargo.toml +++ b/zebra-state/Cargo.toml @@ -35,12 +35,15 @@ zebra-test = { path = "../zebra-test/", optional = true } [dev-dependencies] color-eyre = "0.6.0" -once_cell = "1.10.0" -insta = { version = "1.13.0", features = ["ron"] } itertools = "0.10.3" +once_cell = "1.10.0" +spandoc = "0.2.1" + +hex = { version = "0.4.3", features = ["serde"] } +insta = { version = "1.13.0", features = ["ron"] } + proptest = "0.10.1" proptest-derive = "0.3.0" -spandoc = "0.2.1" # TODO: replace w/ crate version when released: https://github.com/ZcashFoundation/zebra/issues/2083 # Note: if updating this, also update the workspace Cargo.toml to match. diff --git a/zebra-state/src/service/finalized_state/disk_format/tests/prop.rs b/zebra-state/src/service/finalized_state/disk_format/tests/prop.rs index 59fb1812..8041ac61 100644 --- a/zebra-state/src/service/finalized_state/disk_format/tests/prop.rs +++ b/zebra-state/src/service/finalized_state/disk_format/tests/prop.rs @@ -10,9 +10,16 @@ use zebra_chain::{ }; use crate::service::finalized_state::{ - arbitrary::assert_value_properties, disk_format::TransactionLocation, + arbitrary::assert_value_properties, + disk_format::{transparent::OutputLocation, TransactionLocation}, }; +#[test] +fn roundtrip_block_height() { + zebra_test::init(); + proptest!(|(val in any::())| assert_value_properties(val)); +} + #[test] fn roundtrip_transaction_location() { zebra_test::init(); @@ -20,15 +27,15 @@ fn roundtrip_transaction_location() { } #[test] -fn roundtrip_block_hash() { +fn roundtrip_output_location() { zebra_test::init(); - proptest!(|(val in any::())| assert_value_properties(val)); + proptest!(|(val in any::())| assert_value_properties(val)); } #[test] -fn roundtrip_block_height() { +fn roundtrip_block_hash() { zebra_test::init(); - proptest!(|(val in any::())| assert_value_properties(val)); + proptest!(|(val in any::())| assert_value_properties(val)); } #[test] diff --git a/zebra-state/src/service/finalized_state/zebra_db/block.rs b/zebra-state/src/service/finalized_state/zebra_db/block.rs index 43d446c9..71c76231 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/block.rs @@ -102,12 +102,17 @@ impl ZebraDb { // Read transaction methods + /// Returns the [`TransactionLocation`] for [`transaction::Hash`], + /// if it exists in the finalized chain. + pub fn transaction_location(&self, hash: transaction::Hash) -> Option { + let tx_by_hash = self.db.cf_handle("tx_by_hash").unwrap(); + self.db.zs_get(tx_by_hash, &hash) + } + /// Returns the [`Transaction`] with [`transaction::Hash`], /// if it exists in the finalized chain. pub fn transaction(&self, hash: transaction::Hash) -> Option> { - let tx_by_hash = self.db.cf_handle("tx_by_hash").unwrap(); - self.db - .zs_get(tx_by_hash, &hash) + self.transaction_location(hash) .map(|TransactionLocation { index, height }| { let block = self .block(height.into()) diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs index 448587a1..97dac92c 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs @@ -29,27 +29,26 @@ //! cargo insta test --review --delete-unreferenced-snapshots //! ``` //! to update the test snapshots, then commit the `test_*.snap` files using git. -//! -//! # TODO -//! -//! Test the rest of the shielded data, -//! and data activated in Overwinter and later network upgrades. use std::sync::Arc; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use zebra_chain::{ - block::{self, Block, Height}, + block::{self, Block, Height, SerializedBlock}, orchard, parameters::Network::{self, *}, sapling, serialization::{ZcashDeserializeInto, ZcashSerialize}, - transaction::Transaction, + transaction::{self, Transaction}, + transparent, }; use crate::{ - service::finalized_state::{disk_format::TransactionLocation, FinalizedState}, + service::finalized_state::{ + disk_format::{block::TransactionIndex, transparent::OutputLocation, TransactionLocation}, + FinalizedState, + }, Config, }; @@ -57,7 +56,7 @@ use crate::{ /// /// This structure snapshots the height and hash on separate lines, /// which looks good for a single entry. -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] struct Tip { height: u32, block_hash: String, @@ -76,28 +75,25 @@ impl From<(Height, block::Hash)> for Tip { /// /// This structure is used to snapshot the height and hash on the same line, /// which looks good for a vector of heights and hashes. -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize)] struct BlockHash(String); /// Block data structure for RON snapshots. /// /// This structure is used to snapshot the height and block data on separate lines, /// which looks good for a vector of heights and block data. -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] struct BlockData { height: u32, - block: String, + #[serde(with = "hex")] + block: SerializedBlock, } impl BlockData { pub fn new(height: Height, block: &Block) -> BlockData { - let block = block - .zcash_serialize_to_vec() - .expect("serialization of stored block succeeds"); - BlockData { height: height.0, - block: hex::encode(block), + block: block.into(), } } } @@ -106,18 +102,19 @@ impl BlockData { /// /// This structure is used to snapshot the location and transaction hash on separate lines, /// which looks good for a vector of locations and transaction hashes. -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] -struct TransactionHash { - loc: TransactionLocation, - hash: String, +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +struct TransactionHashByLocation { + loc: Option, + #[serde(with = "hex")] + hash: transaction::Hash, } -impl TransactionHash { - pub fn new(loc: TransactionLocation, transaction: &Transaction) -> TransactionHash { - TransactionHash { - loc, - hash: transaction.hash().to_string(), - } +impl TransactionHashByLocation { + pub fn new( + loc: Option, + hash: transaction::Hash, + ) -> TransactionHashByLocation { + TransactionHashByLocation { loc, hash } } } @@ -125,9 +122,12 @@ impl TransactionHash { /// /// This structure is used to snapshot the location and transaction data on separate lines, /// which looks good for a vector of locations and transaction data. -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize)] struct TransactionData { loc: TransactionLocation, + // TODO: after #3145, replace with: + // #[serde(with = "hex")] + // transaction: SerializedTransaction, transaction: String, } @@ -177,6 +177,8 @@ fn test_block_and_transaction_data_with_network(network: Network) { }; // We limit the number of blocks, because the serialized data is a few kilobytes per block. + // + // TODO: Test data activated in Overwinter and later network upgrades. for height in 0..=2 { let block: Arc = blocks .get(&height) @@ -223,10 +225,22 @@ fn snapshot_block_and_transaction_data(state: &FinalizedState) { let mut stored_transaction_hashes = Vec::new(); let mut stored_transactions = Vec::new(); + let mut stored_utxos = Vec::new(); + + let sapling_tree_at_tip = state.sapling_note_commitment_tree(); + let orchard_tree_at_tip = state.orchard_note_commitment_tree(); + + // Test the history tree. + // + // TODO: test non-empty history trees, using Heartwood or later blocks. + // test the rest of the chain data (value balance). + let history_tree_at_tip = state.history_tree(); + for query_height in 0..=max_height.0 { let query_height = Height(query_height); - // Check block height, block hash, and block database queries. + // Check all the block column families, + // using block height, block hash, and block database queries. let stored_block_hash = state .hash(query_height) .expect("heights up to tip have hashes"); @@ -237,14 +251,15 @@ fn snapshot_block_and_transaction_data(state: &FinalizedState) { .block(query_height.into()) .expect("heights up to tip have blocks"); + // Check the sapling and orchard note commitment trees. + // + // TODO: test the rest of the shielded data (anchors, nullifiers, sprout) let sapling_tree_by_height = state .sapling_note_commitment_tree_by_height(&query_height) .expect("heights up to tip have Sapling trees"); let orchard_tree_by_height = state .orchard_note_commitment_tree_by_height(&query_height) .expect("heights up to tip have Orchard trees"); - let sapling_tree_at_tip = state.sapling_note_commitment_tree(); - let orchard_tree_at_tip = state.db.orchard_note_commitment_tree(); // We don't need to snapshot the heights, // because they are fully determined by the tip and block hashes. @@ -257,6 +272,12 @@ fn snapshot_block_and_transaction_data(state: &FinalizedState) { assert_eq!(sapling_tree_at_tip, sapling_tree_by_height); assert_eq!(orchard_tree_at_tip, orchard_tree_by_height); + + // Skip these checks for empty history trees. + if let Some(history_tree_at_tip) = history_tree_at_tip.as_ref() { + assert_eq!(history_tree_at_tip.current_height(), max_height); + assert_eq!(history_tree_at_tip.network(), state.network()); + } } assert_eq!( @@ -277,11 +298,55 @@ fn snapshot_block_and_transaction_data(state: &FinalizedState) { let transaction = &stored_block.transactions[tx_index]; let transaction_location = TransactionLocation::from_usize(query_height, tx_index); - let transaction_hash = TransactionHash::new(transaction_location, transaction); + let transaction_hash = transaction.hash(); let transaction_data = TransactionData::new(transaction_location, transaction); - stored_transaction_hashes.push(transaction_hash); + // Check all the transaction column families, + // using transaction location queries. + let stored_transaction_location = state.transaction_location(transaction_hash); + + // Consensus: the genesis transaction is not indexed. + if query_height.0 > 0 { + assert_eq!(stored_transaction_location, Some(transaction_location)); + } else { + assert_eq!(stored_transaction_location, None); + } + + let stored_transaction_hash = + TransactionHashByLocation::new(stored_transaction_location, transaction_hash); + + stored_transaction_hashes.push(stored_transaction_hash); stored_transactions.push(transaction_data); + + for output_index in 0..stored_block.transactions[tx_index].outputs().len() { + let output = &stored_block.transactions[tx_index].outputs()[output_index]; + let outpoint = + transparent::OutPoint::from_usize(transaction_hash, output_index); + + let output_location = + OutputLocation::from_usize(transaction_hash, output_index); + + let stored_utxo = state.utxo(&outpoint); + + if let Some(stored_utxo) = &stored_utxo { + assert_eq!(&stored_utxo.output, output); + assert_eq!(stored_utxo.height, query_height); + + assert_eq!( + stored_utxo.from_coinbase, + transaction_location.index == TransactionIndex::from_usize(0), + "coinbase transactions must be the first transaction in a block:\n\ + from_coinbase was: {from_coinbase},\n\ + but transaction index was: {tx_index},\n\ + at: {transaction_location:?},\n\ + {output_location:?}", + from_coinbase = stored_utxo.from_coinbase, + ); + } + + // TODO: use output_location in #3151 + stored_utxos.push((outpoint, stored_utxo)); + } } } @@ -291,13 +356,6 @@ fn snapshot_block_and_transaction_data(state: &FinalizedState) { "unsorted: {:?}", stored_block_hashes ); - assert!(is_sorted(&stored_blocks), "unsorted: {:?}", stored_blocks); - - assert!( - is_sorted(&stored_transaction_hashes), - "unsorted: {:?}", - stored_transaction_hashes - ); assert!( is_sorted(&stored_transactions), "unsorted: {:?}", @@ -310,14 +368,19 @@ fn snapshot_block_and_transaction_data(state: &FinalizedState) { insta::assert_ron_snapshot!("block_hashes", stored_block_hashes); insta::assert_ron_snapshot!("blocks", stored_blocks); + insta::assert_ron_snapshot!("transaction_hashes", stored_transaction_hashes); + insta::assert_ron_snapshot!("transactions", stored_transactions); + + insta::assert_ron_snapshot!("utxos", stored_utxos); + // These snapshots will change if the trees do not have cached roots. // But we expect them to always have cached roots, // because those roots are used to populate the anchor column families. insta::assert_ron_snapshot!("sapling_trees", stored_sapling_trees); insta::assert_ron_snapshot!("orchard_trees", stored_orchard_trees); - insta::assert_ron_snapshot!("transaction_hashes", stored_transaction_hashes); - insta::assert_ron_snapshot!("transactions", stored_transactions); + // The zcash_history types used in this tree don't support serde. + insta::assert_debug_snapshot!("history_tree", (max_height, history_tree_at_tip)); } } diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@mainnet_0.snap b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@mainnet_0.snap new file mode 100644 index 00000000..7338a7f7 --- /dev/null +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@mainnet_0.snap @@ -0,0 +1,12 @@ +--- +source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs +expression: "(max_height, history_tree_at_tip)" +--- +( + Height( + 0, + ), + HistoryTree( + None, + ), +) diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@mainnet_1.snap b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@mainnet_1.snap new file mode 100644 index 00000000..eac875ed --- /dev/null +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@mainnet_1.snap @@ -0,0 +1,12 @@ +--- +source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs +expression: "(max_height, history_tree_at_tip)" +--- +( + Height( + 1, + ), + HistoryTree( + None, + ), +) diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@mainnet_2.snap b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@mainnet_2.snap new file mode 100644 index 00000000..9b3b0f01 --- /dev/null +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@mainnet_2.snap @@ -0,0 +1,12 @@ +--- +source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs +expression: "(max_height, history_tree_at_tip)" +--- +( + Height( + 2, + ), + HistoryTree( + None, + ), +) diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@testnet_0.snap b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@testnet_0.snap new file mode 100644 index 00000000..7338a7f7 --- /dev/null +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@testnet_0.snap @@ -0,0 +1,12 @@ +--- +source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs +expression: "(max_height, history_tree_at_tip)" +--- +( + Height( + 0, + ), + HistoryTree( + None, + ), +) diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@testnet_1.snap b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@testnet_1.snap new file mode 100644 index 00000000..eac875ed --- /dev/null +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@testnet_1.snap @@ -0,0 +1,12 @@ +--- +source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs +expression: "(max_height, history_tree_at_tip)" +--- +( + Height( + 1, + ), + HistoryTree( + None, + ), +) diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@testnet_2.snap b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@testnet_2.snap new file mode 100644 index 00000000..9b3b0f01 --- /dev/null +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/history_tree@testnet_2.snap @@ -0,0 +1,12 @@ +--- +source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs +expression: "(max_height, history_tree_at_tip)" +--- +( + Height( + 2, + ), + HistoryTree( + None, + ), +) diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@mainnet_0.snap b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@mainnet_0.snap index efa98d76..80e54b74 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@mainnet_0.snap +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@mainnet_0.snap @@ -1,15 +1,10 @@ --- source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs -assertion_line: 274 expression: stored_transaction_hashes - --- [ - TransactionHash( - loc: TransactionLocation( - height: Height(0), - index: TransactionIndex(0), - ), + TransactionHashByLocation( + loc: None, hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb", ), ] diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@mainnet_1.snap b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@mainnet_1.snap index eabe89f3..a0549205 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@mainnet_1.snap +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@mainnet_1.snap @@ -1,22 +1,17 @@ --- source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs -assertion_line: 274 expression: stored_transaction_hashes - --- [ - TransactionHash( - loc: TransactionLocation( - height: Height(0), - index: TransactionIndex(0), - ), + TransactionHashByLocation( + loc: None, hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb", ), - TransactionHash( - loc: TransactionLocation( + TransactionHashByLocation( + loc: Some(TransactionLocation( height: Height(1), index: TransactionIndex(0), - ), + )), hash: "851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609", ), ] diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@mainnet_2.snap b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@mainnet_2.snap index a67b01f9..4801bd4f 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@mainnet_2.snap +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@mainnet_2.snap @@ -1,29 +1,24 @@ --- source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs -assertion_line: 274 expression: stored_transaction_hashes - --- [ - TransactionHash( - loc: TransactionLocation( - height: Height(0), - index: TransactionIndex(0), - ), + TransactionHashByLocation( + loc: None, hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb", ), - TransactionHash( - loc: TransactionLocation( + TransactionHashByLocation( + loc: Some(TransactionLocation( height: Height(1), index: TransactionIndex(0), - ), + )), hash: "851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609", ), - TransactionHash( - loc: TransactionLocation( + TransactionHashByLocation( + loc: Some(TransactionLocation( height: Height(2), index: TransactionIndex(0), - ), + )), hash: "8974d08d1c5f9c860d8b629d582a56659a4a1dcb2b5f98a25a5afcc2a784b0f4", ), ] diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@testnet_0.snap b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@testnet_0.snap index efa98d76..80e54b74 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@testnet_0.snap +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@testnet_0.snap @@ -1,15 +1,10 @@ --- source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs -assertion_line: 274 expression: stored_transaction_hashes - --- [ - TransactionHash( - loc: TransactionLocation( - height: Height(0), - index: TransactionIndex(0), - ), + TransactionHashByLocation( + loc: None, hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb", ), ] diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@testnet_1.snap b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@testnet_1.snap index a4f68136..2007dc6e 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@testnet_1.snap +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@testnet_1.snap @@ -1,22 +1,17 @@ --- source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs -assertion_line: 274 expression: stored_transaction_hashes - --- [ - TransactionHash( - loc: TransactionLocation( - height: Height(0), - index: TransactionIndex(0), - ), + TransactionHashByLocation( + loc: None, hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb", ), - TransactionHash( - loc: TransactionLocation( + TransactionHashByLocation( + loc: Some(TransactionLocation( height: Height(1), index: TransactionIndex(0), - ), + )), hash: "f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75", ), ] diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@testnet_2.snap b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@testnet_2.snap index bc1851f7..41a8590d 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@testnet_2.snap +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/transaction_hashes@testnet_2.snap @@ -1,29 +1,24 @@ --- source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs -assertion_line: 274 expression: stored_transaction_hashes - --- [ - TransactionHash( - loc: TransactionLocation( - height: Height(0), - index: TransactionIndex(0), - ), + TransactionHashByLocation( + loc: None, hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb", ), - TransactionHash( - loc: TransactionLocation( + TransactionHashByLocation( + loc: Some(TransactionLocation( height: Height(1), index: TransactionIndex(0), - ), + )), hash: "f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75", ), - TransactionHash( - loc: TransactionLocation( + TransactionHashByLocation( + loc: Some(TransactionLocation( height: Height(2), index: TransactionIndex(0), - ), + )), hash: "5822c0532da8a008259ac39933d3210e508c17e3ba21d2b2c428785efdccb3d5", ), ] diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@mainnet_0.snap b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@mainnet_0.snap new file mode 100644 index 00000000..1b421f4a --- /dev/null +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@mainnet_0.snap @@ -0,0 +1,10 @@ +--- +source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs +expression: stored_utxos +--- +[ + (OutPoint( + hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb", + index: 0, + ), None), +] diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@mainnet_1.snap b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@mainnet_1.snap new file mode 100644 index 00000000..431016c0 --- /dev/null +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@mainnet_1.snap @@ -0,0 +1,32 @@ +--- +source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs +expression: stored_utxos +--- +[ + (OutPoint( + hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb", + index: 0, + ), None), + (OutPoint( + hash: "851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609", + index: 0, + ), Some(Utxo( + output: Output( + value: Amount(50000), + lock_script: Script("21027a46eb513588b01b37ea24303f4b628afd12cc20df789fede0921e43cad3e875ac"), + ), + height: Height(1), + from_coinbase: true, + ))), + (OutPoint( + hash: "851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609", + index: 1, + ), Some(Utxo( + output: Output( + value: Amount(12500), + lock_script: Script("a9147d46a730d31f97b1930d3368a967c309bd4d136a87"), + ), + height: Height(1), + from_coinbase: true, + ))), +] diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@mainnet_2.snap b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@mainnet_2.snap new file mode 100644 index 00000000..4c1e8e41 --- /dev/null +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@mainnet_2.snap @@ -0,0 +1,54 @@ +--- +source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs +expression: stored_utxos +--- +[ + (OutPoint( + hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb", + index: 0, + ), None), + (OutPoint( + hash: "851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609", + index: 0, + ), Some(Utxo( + output: Output( + value: Amount(50000), + lock_script: Script("21027a46eb513588b01b37ea24303f4b628afd12cc20df789fede0921e43cad3e875ac"), + ), + height: Height(1), + from_coinbase: true, + ))), + (OutPoint( + hash: "851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609", + index: 1, + ), Some(Utxo( + output: Output( + value: Amount(12500), + lock_script: Script("a9147d46a730d31f97b1930d3368a967c309bd4d136a87"), + ), + height: Height(1), + from_coinbase: true, + ))), + (OutPoint( + hash: "8974d08d1c5f9c860d8b629d582a56659a4a1dcb2b5f98a25a5afcc2a784b0f4", + index: 0, + ), Some(Utxo( + output: Output( + value: Amount(100000), + lock_script: Script("21027a46eb513588b01b37ea24303f4b628afd12cc20df789fede0921e43cad3e875ac"), + ), + height: Height(2), + from_coinbase: true, + ))), + (OutPoint( + hash: "8974d08d1c5f9c860d8b629d582a56659a4a1dcb2b5f98a25a5afcc2a784b0f4", + index: 1, + ), Some(Utxo( + output: Output( + value: Amount(25000), + lock_script: Script("a9147d46a730d31f97b1930d3368a967c309bd4d136a87"), + ), + height: Height(2), + from_coinbase: true, + ))), +] diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@testnet_0.snap b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@testnet_0.snap new file mode 100644 index 00000000..1b421f4a --- /dev/null +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@testnet_0.snap @@ -0,0 +1,10 @@ +--- +source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs +expression: stored_utxos +--- +[ + (OutPoint( + hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb", + index: 0, + ), None), +] diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@testnet_1.snap b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@testnet_1.snap new file mode 100644 index 00000000..50a4bd7c --- /dev/null +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@testnet_1.snap @@ -0,0 +1,32 @@ +--- +source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs +expression: stored_utxos +--- +[ + (OutPoint( + hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb", + index: 0, + ), None), + (OutPoint( + hash: "f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75", + index: 0, + ), Some(Utxo( + output: Output( + value: Amount(50000), + lock_script: Script("21025229e1240a21004cf8338db05679fa34753706e84f6aebba086ba04317fd8f99ac"), + ), + height: Height(1), + from_coinbase: true, + ))), + (OutPoint( + hash: "f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75", + index: 1, + ), Some(Utxo( + output: Output( + value: Amount(12500), + lock_script: Script("a914ef775f1f997f122a062fff1a2d7443abd1f9c64287"), + ), + height: Height(1), + from_coinbase: true, + ))), +] diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@testnet_2.snap b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@testnet_2.snap new file mode 100644 index 00000000..15aedd05 --- /dev/null +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshots/utxos@testnet_2.snap @@ -0,0 +1,54 @@ +--- +source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs +expression: stored_utxos +--- +[ + (OutPoint( + hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb", + index: 0, + ), None), + (OutPoint( + hash: "f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75", + index: 0, + ), Some(Utxo( + output: Output( + value: Amount(50000), + lock_script: Script("21025229e1240a21004cf8338db05679fa34753706e84f6aebba086ba04317fd8f99ac"), + ), + height: Height(1), + from_coinbase: true, + ))), + (OutPoint( + hash: "f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75", + index: 1, + ), Some(Utxo( + output: Output( + value: Amount(12500), + lock_script: Script("a914ef775f1f997f122a062fff1a2d7443abd1f9c64287"), + ), + height: Height(1), + from_coinbase: true, + ))), + (OutPoint( + hash: "5822c0532da8a008259ac39933d3210e508c17e3ba21d2b2c428785efdccb3d5", + index: 0, + ), Some(Utxo( + output: Output( + value: Amount(100000), + lock_script: Script("2102acce9f6c16986c525fd34759d851ef5b4b85b5019a57bd59747be0ef1ba62523ac"), + ), + height: Height(2), + from_coinbase: true, + ))), + (OutPoint( + hash: "5822c0532da8a008259ac39933d3210e508c17e3ba21d2b2c428785efdccb3d5", + index: 1, + ), Some(Utxo( + output: Output( + value: Amount(25000), + lock_script: Script("a914ef775f1f997f122a062fff1a2d7443abd1f9c64287"), + ), + height: Height(2), + from_coinbase: true, + ))), +]