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
This commit is contained in:
teor 2022-03-19 06:30:16 +10:00 committed by GitHub
parent 9a8ab9468d
commit b4deca2912
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 490 additions and 134 deletions

View File

@ -9,7 +9,8 @@ edition = "2021"
[features] [features]
default = [] 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"] bench = ["zebra-test"]
[dependencies] [dependencies]
@ -76,6 +77,8 @@ itertools = "0.10.3"
spandoc = "0.2.1" spandoc = "0.2.1"
tracing = "0.1.31" tracing = "0.1.31"
hex = { version = "0.4.3", features = ["serde"] }
proptest = "0.10.1" proptest = "0.10.1"
proptest-derive = "0.3.0" proptest-derive = "0.3.0"
rand = { version = "0.8.5", package = "rand" } rand = { version = "0.8.5", package = "rand" }

View File

@ -28,7 +28,18 @@ type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Clone, Copy, Serialize, Deserialize)] #[derive(Clone, Copy, Serialize, Deserialize)]
#[serde(try_from = "i64")] #[serde(try_from = "i64")]
#[serde(bound = "C: Constraint")] #[serde(bound = "C: Constraint")]
pub struct Amount<C = NegativeAllowed>(i64, PhantomData<C>); pub struct Amount<C = NegativeAllowed>(
/// 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<C>,
);
impl<C> std::fmt::Debug for Amount<C> { impl<C> std::fmt::Debug for Amount<C> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

View File

@ -14,7 +14,7 @@ pub mod arbitrary;
#[cfg(any(test, feature = "bench", feature = "proptest-impl"))] #[cfg(any(test, feature = "bench", feature = "proptest-impl"))]
pub mod tests; pub mod tests;
use std::{collections::HashMap, convert::TryInto, fmt, ops::Neg}; use std::{collections::HashMap, fmt, ops::Neg};
pub use commitment::{ pub use commitment::{
ChainHistoryBlockTxAuthCommitmentHash, ChainHistoryMmrRootHash, Commitment, CommitmentError, ChainHistoryBlockTxAuthCommitmentHash, ChainHistoryMmrRootHash, Commitment, CommitmentError,
@ -27,8 +27,6 @@ pub use serialize::{SerializedBlock, MAX_BLOCK_BYTES};
#[cfg(any(test, feature = "proptest-impl"))] #[cfg(any(test, feature = "proptest-impl"))]
pub use arbitrary::LedgerState; pub use arbitrary::LedgerState;
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
amount::NegativeAllowed, amount::NegativeAllowed,
block::merkle::AuthDataRoot, block::merkle::AuthDataRoot,
@ -44,7 +42,8 @@ use crate::{
}; };
/// A Zcash block, containing a header and a list of transactions. /// 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 { pub struct Block {
/// The block header, containing block metadata. /// The block header, containing block metadata.
pub header: Header, pub header: Header,

View File

@ -1,7 +1,8 @@
//! Transactions and transaction-related structures. //! Transactions and transaction-related structures.
use std::{collections::HashMap, fmt, iter};
use halo2::pasta::pallas; use halo2::pasta::pallas;
use serde::{Deserialize, Serialize};
mod auth_digest; mod auth_digest;
mod hash; mod hash;
@ -40,8 +41,6 @@ use crate::{
value_balance::{ValueBalance, ValueBalanceError}, value_balance::{ValueBalance, ValueBalanceError},
}; };
use std::{collections::HashMap, fmt, iter};
/// A Zcash transaction. /// A Zcash transaction.
/// ///
/// A transaction is an encoded data structure that facilitates the transfer of /// 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 /// Zcash has a number of different transaction formats. They are represented
/// internally by different enum variants. Because we checkpoint on Canopy /// internally by different enum variants. Because we checkpoint on Canopy
/// activation, we do not validate any pre-Sapling transaction types. /// 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 // XXX consider boxing the Optional fields of V4 and V5 txs
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub enum Transaction { pub enum Transaction {

View File

@ -48,7 +48,8 @@ use std::{collections::HashMap, fmt, iter};
pub const MIN_TRANSPARENT_COINBASE_MATURITY: u32 = 100; pub const MIN_TRANSPARENT_COINBASE_MATURITY: u32 = 100;
/// Arbitrary data inserted by miners into a coinbase transaction. /// 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( pub struct CoinbaseData(
/// Invariant: this vec, together with the coinbase height, must be less than /// Invariant: this vec, together with the coinbase height, must be less than
/// 100 bytes. We enforce this by only constructing CoinbaseData fields by /// 100 bytes. We enforce this by only constructing CoinbaseData fields by
@ -92,10 +93,16 @@ impl std::fmt::Debug for CoinbaseData {
/// OutPoint /// OutPoint
/// ///
/// A particular transaction output reference. /// A particular transaction output reference.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] #[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary, Serialize))]
pub struct OutPoint { pub struct OutPoint {
/// References the transaction that contains the UTXO being spent. /// 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, pub hash: transaction::Hash,
/// Identifies which UTXO from that transaction is referenced; the /// Identifies which UTXO from that transaction is referenced; the
@ -103,8 +110,25 @@ pub struct OutPoint {
pub index: u32, 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. /// 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 { pub enum Input {
/// A reference to an output of a previous transaction. /// A reference to an output of a previous transaction.
PrevOut { PrevOut {
@ -289,8 +313,8 @@ impl Input {
/// I only own one UTXO worth 2 ZEC, I would construct a transaction /// 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 /// that spends my UTXO and sends 1 ZEC to you and 1 ZEC back to me
/// (just like receiving change). /// (just like receiving change).
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] #[derive(Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] #[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary, Serialize))]
pub struct Output { pub struct Output {
/// Transaction value. /// Transaction value.
// At https://en.bitcoin.it/wiki/Protocol_documentation#tx, this is an i64. // At https://en.bitcoin.it/wiki/Protocol_documentation#tx, this is an i64.

View File

@ -7,12 +7,19 @@ use crate::serialization::{
}; };
/// An encoding of a Bitcoin script. /// An encoding of a Bitcoin script.
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Hash)] #[derive(Clone, Eq, PartialEq, Hash)]
#[cfg_attr( #[cfg_attr(
any(test, feature = "proptest-impl"), any(test, feature = "proptest-impl"),
derive(proptest_derive::Arbitrary) derive(proptest_derive::Arbitrary, serde::Serialize)
)] )]
pub struct Script(Vec<u8>); 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<u8>,
);
impl Script { impl Script {
/// Create a new Bitcoin script from its raw bytes. /// Create a new Bitcoin script from its raw bytes.

View File

@ -12,7 +12,7 @@ use crate::{
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr( #[cfg_attr(
any(test, feature = "proptest-impl"), any(test, feature = "proptest-impl"),
derive(proptest_derive::Arbitrary) derive(proptest_derive::Arbitrary, serde::Serialize)
)] )]
pub struct Utxo { pub struct Utxo {
/// The output itself. /// The output itself.

View File

@ -35,12 +35,15 @@ zebra-test = { path = "../zebra-test/", optional = true }
[dev-dependencies] [dev-dependencies]
color-eyre = "0.6.0" color-eyre = "0.6.0"
once_cell = "1.10.0"
insta = { version = "1.13.0", features = ["ron"] }
itertools = "0.10.3" 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 = "0.10.1"
proptest-derive = "0.3.0" proptest-derive = "0.3.0"
spandoc = "0.2.1"
# TODO: replace w/ crate version when released: https://github.com/ZcashFoundation/zebra/issues/2083 # 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. # Note: if updating this, also update the workspace Cargo.toml to match.

View File

@ -10,9 +10,16 @@ use zebra_chain::{
}; };
use crate::service::finalized_state::{ 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::<block::Height>())| assert_value_properties(val));
}
#[test] #[test]
fn roundtrip_transaction_location() { fn roundtrip_transaction_location() {
zebra_test::init(); zebra_test::init();
@ -20,15 +27,15 @@ fn roundtrip_transaction_location() {
} }
#[test] #[test]
fn roundtrip_block_hash() { fn roundtrip_output_location() {
zebra_test::init(); zebra_test::init();
proptest!(|(val in any::<block::Hash>())| assert_value_properties(val)); proptest!(|(val in any::<OutputLocation>())| assert_value_properties(val));
} }
#[test] #[test]
fn roundtrip_block_height() { fn roundtrip_block_hash() {
zebra_test::init(); zebra_test::init();
proptest!(|(val in any::<block::Height>())| assert_value_properties(val)); proptest!(|(val in any::<block::Hash>())| assert_value_properties(val));
} }
#[test] #[test]

View File

@ -102,12 +102,17 @@ impl ZebraDb {
// Read transaction methods // 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<TransactionLocation> {
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`], /// Returns the [`Transaction`] with [`transaction::Hash`],
/// if it exists in the finalized chain. /// if it exists in the finalized chain.
pub fn transaction(&self, hash: transaction::Hash) -> Option<Arc<Transaction>> { pub fn transaction(&self, hash: transaction::Hash) -> Option<Arc<Transaction>> {
let tx_by_hash = self.db.cf_handle("tx_by_hash").unwrap(); self.transaction_location(hash)
self.db
.zs_get(tx_by_hash, &hash)
.map(|TransactionLocation { index, height }| { .map(|TransactionLocation { index, height }| {
let block = self let block = self
.block(height.into()) .block(height.into())

View File

@ -29,27 +29,26 @@
//! cargo insta test --review --delete-unreferenced-snapshots //! cargo insta test --review --delete-unreferenced-snapshots
//! ``` //! ```
//! to update the test snapshots, then commit the `test_*.snap` files using git. //! 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 std::sync::Arc;
use serde::{Deserialize, Serialize}; use serde::Serialize;
use zebra_chain::{ use zebra_chain::{
block::{self, Block, Height}, block::{self, Block, Height, SerializedBlock},
orchard, orchard,
parameters::Network::{self, *}, parameters::Network::{self, *},
sapling, sapling,
serialization::{ZcashDeserializeInto, ZcashSerialize}, serialization::{ZcashDeserializeInto, ZcashSerialize},
transaction::Transaction, transaction::{self, Transaction},
transparent,
}; };
use crate::{ use crate::{
service::finalized_state::{disk_format::TransactionLocation, FinalizedState}, service::finalized_state::{
disk_format::{block::TransactionIndex, transparent::OutputLocation, TransactionLocation},
FinalizedState,
},
Config, Config,
}; };
@ -57,7 +56,7 @@ use crate::{
/// ///
/// This structure snapshots the height and hash on separate lines, /// This structure snapshots the height and hash on separate lines,
/// which looks good for a single entry. /// which looks good for a single entry.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, Serialize)]
struct Tip { struct Tip {
height: u32, height: u32,
block_hash: String, 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, /// This structure is used to snapshot the height and hash on the same line,
/// which looks good for a vector of heights and hashes. /// 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); struct BlockHash(String);
/// Block data structure for RON snapshots. /// Block data structure for RON snapshots.
/// ///
/// This structure is used to snapshot the height and block data on separate lines, /// 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. /// 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 { struct BlockData {
height: u32, height: u32,
block: String, #[serde(with = "hex")]
block: SerializedBlock,
} }
impl BlockData { impl BlockData {
pub fn new(height: Height, block: &Block) -> BlockData { pub fn new(height: Height, block: &Block) -> BlockData {
let block = block
.zcash_serialize_to_vec()
.expect("serialization of stored block succeeds");
BlockData { BlockData {
height: height.0, 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, /// 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. /// which looks good for a vector of locations and transaction hashes.
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, Serialize)]
struct TransactionHash { struct TransactionHashByLocation {
loc: TransactionLocation, loc: Option<TransactionLocation>,
hash: String, #[serde(with = "hex")]
hash: transaction::Hash,
} }
impl TransactionHash { impl TransactionHashByLocation {
pub fn new(loc: TransactionLocation, transaction: &Transaction) -> TransactionHash { pub fn new(
TransactionHash { loc: Option<TransactionLocation>,
loc, hash: transaction::Hash,
hash: transaction.hash().to_string(), ) -> TransactionHashByLocation {
} TransactionHashByLocation { loc, hash }
} }
} }
@ -125,9 +122,12 @@ impl TransactionHash {
/// ///
/// This structure is used to snapshot the location and transaction data on separate lines, /// 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. /// 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 { struct TransactionData {
loc: TransactionLocation, loc: TransactionLocation,
// TODO: after #3145, replace with:
// #[serde(with = "hex")]
// transaction: SerializedTransaction,
transaction: String, 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. // 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 { for height in 0..=2 {
let block: Arc<Block> = blocks let block: Arc<Block> = blocks
.get(&height) .get(&height)
@ -223,10 +225,22 @@ fn snapshot_block_and_transaction_data(state: &FinalizedState) {
let mut stored_transaction_hashes = Vec::new(); let mut stored_transaction_hashes = Vec::new();
let mut stored_transactions = 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 { for query_height in 0..=max_height.0 {
let query_height = Height(query_height); 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 let stored_block_hash = state
.hash(query_height) .hash(query_height)
.expect("heights up to tip have hashes"); .expect("heights up to tip have hashes");
@ -237,14 +251,15 @@ fn snapshot_block_and_transaction_data(state: &FinalizedState) {
.block(query_height.into()) .block(query_height.into())
.expect("heights up to tip have blocks"); .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 let sapling_tree_by_height = state
.sapling_note_commitment_tree_by_height(&query_height) .sapling_note_commitment_tree_by_height(&query_height)
.expect("heights up to tip have Sapling trees"); .expect("heights up to tip have Sapling trees");
let orchard_tree_by_height = state let orchard_tree_by_height = state
.orchard_note_commitment_tree_by_height(&query_height) .orchard_note_commitment_tree_by_height(&query_height)
.expect("heights up to tip have Orchard trees"); .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, // We don't need to snapshot the heights,
// because they are fully determined by the tip and block hashes. // 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!(sapling_tree_at_tip, sapling_tree_by_height);
assert_eq!(orchard_tree_at_tip, orchard_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!( assert_eq!(
@ -277,11 +298,55 @@ fn snapshot_block_and_transaction_data(state: &FinalizedState) {
let transaction = &stored_block.transactions[tx_index]; let transaction = &stored_block.transactions[tx_index];
let transaction_location = TransactionLocation::from_usize(query_height, 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); 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); 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: {:?}", "unsorted: {:?}",
stored_block_hashes stored_block_hashes
); );
assert!(is_sorted(&stored_blocks), "unsorted: {:?}", stored_blocks);
assert!(
is_sorted(&stored_transaction_hashes),
"unsorted: {:?}",
stored_transaction_hashes
);
assert!( assert!(
is_sorted(&stored_transactions), is_sorted(&stored_transactions),
"unsorted: {:?}", "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!("block_hashes", stored_block_hashes);
insta::assert_ron_snapshot!("blocks", stored_blocks); 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. // These snapshots will change if the trees do not have cached roots.
// But we expect them to always have cached roots, // But we expect them to always have cached roots,
// because those roots are used to populate the anchor column families. // because those roots are used to populate the anchor column families.
insta::assert_ron_snapshot!("sapling_trees", stored_sapling_trees); insta::assert_ron_snapshot!("sapling_trees", stored_sapling_trees);
insta::assert_ron_snapshot!("orchard_trees", stored_orchard_trees); insta::assert_ron_snapshot!("orchard_trees", stored_orchard_trees);
insta::assert_ron_snapshot!("transaction_hashes", stored_transaction_hashes); // The zcash_history types used in this tree don't support serde.
insta::assert_ron_snapshot!("transactions", stored_transactions); insta::assert_debug_snapshot!("history_tree", (max_height, history_tree_at_tip));
} }
} }

View File

@ -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,
),
)

View File

@ -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,
),
)

View File

@ -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,
),
)

View File

@ -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,
),
)

View File

@ -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,
),
)

View File

@ -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,
),
)

View File

@ -1,15 +1,10 @@
--- ---
source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs
assertion_line: 274
expression: stored_transaction_hashes expression: stored_transaction_hashes
--- ---
[ [
TransactionHash( TransactionHashByLocation(
loc: TransactionLocation( loc: None,
height: Height(0),
index: TransactionIndex(0),
),
hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb", hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb",
), ),
] ]

View File

@ -1,22 +1,17 @@
--- ---
source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs
assertion_line: 274
expression: stored_transaction_hashes expression: stored_transaction_hashes
--- ---
[ [
TransactionHash( TransactionHashByLocation(
loc: TransactionLocation( loc: None,
height: Height(0),
index: TransactionIndex(0),
),
hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb", hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb",
), ),
TransactionHash( TransactionHashByLocation(
loc: TransactionLocation( loc: Some(TransactionLocation(
height: Height(1), height: Height(1),
index: TransactionIndex(0), index: TransactionIndex(0),
), )),
hash: "851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609", hash: "851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609",
), ),
] ]

View File

@ -1,29 +1,24 @@
--- ---
source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs
assertion_line: 274
expression: stored_transaction_hashes expression: stored_transaction_hashes
--- ---
[ [
TransactionHash( TransactionHashByLocation(
loc: TransactionLocation( loc: None,
height: Height(0),
index: TransactionIndex(0),
),
hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb", hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb",
), ),
TransactionHash( TransactionHashByLocation(
loc: TransactionLocation( loc: Some(TransactionLocation(
height: Height(1), height: Height(1),
index: TransactionIndex(0), index: TransactionIndex(0),
), )),
hash: "851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609", hash: "851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609",
), ),
TransactionHash( TransactionHashByLocation(
loc: TransactionLocation( loc: Some(TransactionLocation(
height: Height(2), height: Height(2),
index: TransactionIndex(0), index: TransactionIndex(0),
), )),
hash: "8974d08d1c5f9c860d8b629d582a56659a4a1dcb2b5f98a25a5afcc2a784b0f4", hash: "8974d08d1c5f9c860d8b629d582a56659a4a1dcb2b5f98a25a5afcc2a784b0f4",
), ),
] ]

View File

@ -1,15 +1,10 @@
--- ---
source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs
assertion_line: 274
expression: stored_transaction_hashes expression: stored_transaction_hashes
--- ---
[ [
TransactionHash( TransactionHashByLocation(
loc: TransactionLocation( loc: None,
height: Height(0),
index: TransactionIndex(0),
),
hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb", hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb",
), ),
] ]

View File

@ -1,22 +1,17 @@
--- ---
source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs
assertion_line: 274
expression: stored_transaction_hashes expression: stored_transaction_hashes
--- ---
[ [
TransactionHash( TransactionHashByLocation(
loc: TransactionLocation( loc: None,
height: Height(0),
index: TransactionIndex(0),
),
hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb", hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb",
), ),
TransactionHash( TransactionHashByLocation(
loc: TransactionLocation( loc: Some(TransactionLocation(
height: Height(1), height: Height(1),
index: TransactionIndex(0), index: TransactionIndex(0),
), )),
hash: "f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75", hash: "f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75",
), ),
] ]

View File

@ -1,29 +1,24 @@
--- ---
source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs source: zebra-state/src/service/finalized_state/zebra_db/block/tests/snapshot.rs
assertion_line: 274
expression: stored_transaction_hashes expression: stored_transaction_hashes
--- ---
[ [
TransactionHash( TransactionHashByLocation(
loc: TransactionLocation( loc: None,
height: Height(0),
index: TransactionIndex(0),
),
hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb", hash: "c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb",
), ),
TransactionHash( TransactionHashByLocation(
loc: TransactionLocation( loc: Some(TransactionLocation(
height: Height(1), height: Height(1),
index: TransactionIndex(0), index: TransactionIndex(0),
), )),
hash: "f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75", hash: "f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75",
), ),
TransactionHash( TransactionHashByLocation(
loc: TransactionLocation( loc: Some(TransactionLocation(
height: Height(2), height: Height(2),
index: TransactionIndex(0), index: TransactionIndex(0),
), )),
hash: "5822c0532da8a008259ac39933d3210e508c17e3ba21d2b2c428785efdccb3d5", hash: "5822c0532da8a008259ac39933d3210e508c17e3ba21d2b2c428785efdccb3d5",
), ),
] ]

View File

@ -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),
]

View File

@ -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,
))),
]

View File

@ -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,
))),
]

View File

@ -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),
]

View File

@ -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,
))),
]

View File

@ -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,
))),
]