Validate sapling, orchard anchors (#3084)

* Add Transaction::sprout_joinsplits()

* Add Anchor variants to ValidateContextError

* Make Chain anchor collections pub(crate)

* tracing::instrument several methods in state

* Add contains_*_anchors methods to FinalizedState

* Add check::anchors module and function

* Verify that anchors_refer_to_earlier_treestates in when updating chains in non-finalized state

* Update zebra-state/src/service/check/anchors.rs

Co-authored-by: teor <teor@riseup.net>

* Add anchors() to sapling::ShieldedData

* Add sapling_anchors() to Transaction

* Use Transaction::sapling_anchors() in the anchors_refer_to_earlier_treestates() check

* Whoops, itertools

* Add a comment for improvement

Co-authored-by: teor <teor@riseup.net>

* Add & use a cfg(test) method on FinalizedState to prep test state with anchors to allow other tests to pass contextual checks

* Allow test nullifier checks to pass by populating anchor sets, allowing test anchor checks to pass

* Add mainnet block 419202 and its sapling note commitment tree root to test vectors

* Test sapling anchor verification using the first few Sapling blocks data

* Correct comment

* assert_eq instead of assert(bool)

Co-authored-by: Conrado Gouvea <conrado@zfnd.org>

* Update zebra-state/src/service/non_finalized_state.rs

Co-authored-by: teor <teor@riseup.net>

Co-authored-by: teor <teor@riseup.net>
Co-authored-by: Marek <mail@marek.onl>
Co-authored-by: Conrado Gouvea <conrado@zfnd.org>
This commit is contained in:
Deirdre Connolly 2021-11-30 11:05:35 -05:00 committed by GitHub
parent 3c9ad89018
commit e6ffe374d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 458 additions and 26 deletions

View File

@ -9,7 +9,7 @@ edition = "2018"
[features]
default = []
proptest-impl = ["proptest", "proptest-derive", "itertools", "zebra-test", "rand", "rand_chacha"]
proptest-impl = ["proptest", "proptest-derive", "zebra-test", "rand", "rand_chacha"]
bench = ["zebra-test"]
[dependencies]
@ -31,6 +31,7 @@ group = "0.11.0"
halo2 = "=0.1.0-beta.1"
hex = "0.4"
incrementalmerkletree = "0.1.0"
itertools = "0.10.1"
jubjub = "0.8.0"
lazy_static = "1.4.0"
rand_core = "0.6"
@ -56,7 +57,7 @@ zcash_history = { git = "https://github.com/ZcashFoundation/librustzcash.git", r
proptest = { version = "0.10", optional = true }
proptest-derive = { version = "0.3.0", optional = true }
itertools = { version = "0.10.1", optional = true }
rand = { version = "0.8", optional = true }
rand_chacha = { version = "0.3", optional = true }

View File

@ -4,6 +4,12 @@
//! The `value_balance` change is handled using the default zero value.
//! The anchor change is handled using the `AnchorVariant` type trait.
use std::{
cmp::{max, Eq, PartialEq},
fmt::{self, Debug},
};
use itertools::Itertools;
#[cfg(any(test, feature = "proptest-impl"))]
use proptest_derive::Arbitrary;
use serde::{de::DeserializeOwned, Serialize};
@ -21,11 +27,6 @@ use crate::{
serialization::{AtLeastOne, TrustedPreallocate},
};
use std::{
cmp::{max, Eq, PartialEq},
fmt::{self, Debug},
};
/// Per-Spend Sapling anchors, used in Transaction V4 and the
/// `spends_per_anchor` method.
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
@ -205,6 +206,16 @@ where
AnchorV: AnchorVariant + Clone,
Spend<PerSpendAnchor>: From<(Spend<AnchorV>, AnchorV::Shared)>,
{
/// Iterate over the [`Spend`]s for this transaction, returning deduplicated
/// [`tree::Root`]s, regardless of the underlying transaction version.
pub fn anchors(&self) -> impl Iterator<Item = tree::Root> + '_ {
// TODO: use TransferData::shared_anchor to improve performance for V5 transactions
self.spends_per_anchor()
.map(|spend| spend.per_spend_anchor)
.sorted()
.dedup()
}
/// Iterate over the [`Spend`]s for this transaction, returning
/// `Spend<PerSpendAnchor>` regardless of the underlying transaction version.
///

View File

@ -81,7 +81,7 @@ pub struct Position(pub(crate) u64);
/// commitment tree corresponding to the final Sapling treestate of
/// this block. A root of a note commitment tree is associated with
/// each treestate.
#[derive(Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize, Hash)]
#[derive(Clone, Copy, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct Root(pub [u8; 32]);
impl fmt::Debug for Root {

View File

@ -515,6 +515,29 @@ impl Transaction {
// sprout
/// Returns the Sprout `JoinSplit<Groth16Proof>`s in this transaction, regardless of version.
pub fn sprout_groth16_joinsplits(
&self,
) -> Box<dyn Iterator<Item = &sprout::JoinSplit<Groth16Proof>> + '_> {
match self {
// JoinSplits with Groth16 Proofs
Transaction::V4 {
joinsplit_data: Some(joinsplit_data),
..
} => Box::new(joinsplit_data.joinsplits()),
// No JoinSplits / JoinSplits with BCTV14 proofs
Transaction::V1 { .. }
| Transaction::V2 { .. }
| Transaction::V3 { .. }
| Transaction::V4 {
joinsplit_data: None,
..
}
| Transaction::V5 { .. } => Box::new(std::iter::empty()),
}
}
/// Returns the number of `JoinSplit`s in this transaction, regardless of version.
pub fn joinsplit_count(&self) -> usize {
match self {
@ -591,6 +614,37 @@ impl Transaction {
// sapling
/// Access the deduplicated [`sapling::tree::Root`]s in this transaction,
/// regardless of version.
pub fn sapling_anchors(&self) -> Box<dyn Iterator<Item = sapling::tree::Root> + '_> {
// This function returns a boxed iterator because the different
// transaction variants end up having different iterator types
match self {
Transaction::V4 {
sapling_shielded_data: Some(sapling_shielded_data),
..
} => Box::new(sapling_shielded_data.anchors()),
Transaction::V5 {
sapling_shielded_data: Some(sapling_shielded_data),
..
} => Box::new(sapling_shielded_data.anchors()),
// No Spends
Transaction::V1 { .. }
| Transaction::V2 { .. }
| Transaction::V3 { .. }
| Transaction::V4 {
sapling_shielded_data: None,
..
}
| Transaction::V5 {
sapling_shielded_data: None,
..
} => Box::new(std::iter::empty()),
}
}
/// Iterate over the sapling [`Spend`](sapling::Spend)s for this transaction,
/// returning `Spend<PerSpendAnchor>` regardless of the underlying
/// transaction version.

View File

@ -228,6 +228,18 @@ pub enum ValidateContextError {
#[error("block contains an invalid commitment")]
InvalidBlockCommitment(#[from] block::CommitmentError),
#[error("unknown Sprout anchor: {anchor:?}")]
#[non_exhaustive]
UnknownSproutAnchor { anchor: sprout::tree::Root },
#[error("unknown Sapling anchor: {anchor:?}")]
#[non_exhaustive]
UnknownSaplingAnchor { anchor: sapling::tree::Root },
#[error("unknown Orchard anchor: {anchor:?}")]
#[non_exhaustive]
UnknownOrchardAnchor { anchor: orchard::tree::Root },
}
/// Trait for creating the corresponding duplicate nullifier error from a nullifier.

View File

@ -225,6 +225,7 @@ impl StateService {
/// Run contextual validation on the prepared block and add it to the
/// non-finalized state if it is contextually valid.
#[tracing::instrument(level = "debug", skip(self, prepared))]
fn validate_and_commit(&mut self, prepared: PreparedBlock) -> Result<(), CommitBlockError> {
self.check_contextual_validity(&prepared)?;
let parent_hash = prepared.block.header.previous_block_hash;
@ -245,6 +246,7 @@ impl StateService {
/// Attempt to validate and commit all queued blocks whose parents have
/// recently arrived starting from `new_parent`, in breadth-first ordering.
#[tracing::instrument(level = "debug", skip(self, new_parent))]
fn process_queued(&mut self, new_parent: block::Hash) {
let mut new_parents: Vec<(block::Hash, Result<(), CloneError>)> =
vec![(new_parent, Ok(()))];

View File

@ -18,6 +18,7 @@ use super::check;
use difficulty::{AdjustedDifficulty, POW_MEDIAN_BLOCK_SPAN};
pub(crate) mod anchors;
pub(crate) mod difficulty;
pub(crate) mod nullifier;
pub(crate) mod utxo;

View File

@ -0,0 +1,104 @@
//! Checks for whether cited anchors are previously-computed note commitment
//! tree roots.
use crate::{
service::{finalized_state::FinalizedState, non_finalized_state::Chain},
PreparedBlock, ValidateContextError,
};
/// Check that all the Sprout, Sapling, and Orchard anchors specified by
/// transactions in this block have been computed previously within the context
/// of its parent chain.
///
/// Sprout anchors may refer to some earlier block's final treestate (like
/// Sapling and Orchard do exclusively) _or_ to the interstisial output
/// treestate of any prior `JoinSplit` _within the same transaction_.
///
/// > For the first JoinSplit description of a transaction, the anchor MUST be
/// > the output Sprout treestate of a previous block.[^sprout]
///
/// > The anchor of each JoinSplit description in a transaction MUST refer to
/// > either some earlier blocks final Sprout treestate, or to the interstitial
/// > output treestate of any prior JoinSplit description in the same transaction.[^sprout]
///
/// > The anchor of each Spend description MUST refer to some earlier
/// > blocks final Sapling treestate. The anchor is encoded separately in
/// > each Spend description for v4 transactions, or encoded once and
/// > shared between all Spend descriptions in a v5 transaction.[^sapling]
///
/// > The anchorOrchard field of the transaction, whenever it exists (i.e. when
/// > there are any Action descriptions), MUST refer to some earlier blocks
/// > final Orchard treestate.[^orchard]
///
/// [^sprout]: <https://zips.z.cash/protocol/protocol.pdf#joinsplit>
/// [^sapling]: <https://zips.z.cash/protocol/protocol.pdf#spendsandoutputs>
/// [^orchard]: <https://zips.z.cash/protocol/protocol.pdf#actions>
#[tracing::instrument(skip(finalized_state, parent_chain, prepared))]
pub(crate) fn anchors_refer_to_earlier_treestates(
finalized_state: &FinalizedState,
parent_chain: &Chain,
prepared: &PreparedBlock,
) -> Result<(), ValidateContextError> {
for transaction in prepared.block.transactions.iter() {
// Sprout JoinSplits, with interstitial treestates to check as well
//
// The FIRST JOINSPLIT in a transaction MUST refer to the output treestate
// of a previous block.
// if let Some(sprout_shielded_data) = transaction.joinsplit_data {
// for joinsplit in transaction.sprout_groth16_joinsplits() {
// if !parent_chain.sprout_anchors.contains(joinsplit.anchor)
// && !finalized_state.contains_sprout_anchor(&joinsplit.anchor)
// {
// if !(joinsplit == &sprout_shielded_data.first) {
// // TODO: check interstitial treestates of the earlier JoinSplits
// // in this transaction against this anchor
// unimplemented!()
// } else {
// return Err(ValidateContextError::UnknownSproutAnchor {
// anchor: joinsplit.anchor,
// });
// }
// }
// }
// }
// Sapling Spends
//
// MUST refer to some earlier blocks final Sapling treestate.
if transaction.has_sapling_shielded_data() {
for anchor in transaction.sapling_anchors() {
tracing::debug!(?anchor, "observed sapling anchor");
if !parent_chain.sapling_anchors.contains(&anchor)
&& !finalized_state.contains_sapling_anchor(&anchor)
{
return Err(ValidateContextError::UnknownSaplingAnchor { anchor });
}
tracing::debug!(?anchor, "validated sapling anchor");
}
}
// Orchard Actions
//
// MUST refer to some earlier blocks final Orchard treestate.
if let Some(orchard_shielded_data) = transaction.orchard_shielded_data() {
tracing::debug!(?orchard_shielded_data.shared_anchor, "observed orchard anchor");
if !parent_chain
.orchard_anchors
.contains(&orchard_shielded_data.shared_anchor)
&& !finalized_state.contains_orchard_anchor(&orchard_shielded_data.shared_anchor)
{
return Err(ValidateContextError::UnknownOrchardAnchor {
anchor: orchard_shielded_data.shared_anchor,
});
}
tracing::debug!(?orchard_shielded_data.shared_anchor, "validated orchard anchor");
}
}
Ok(())
}

View File

@ -1,5 +1,6 @@
//! Tests for state contextual validation checks.
mod anchors;
mod nullifier;
mod utxo;
mod vectors;

View File

@ -0,0 +1,116 @@
use std::{convert::TryInto, ops::Deref, sync::Arc};
use zebra_chain::{
block::{Block, Height},
serialization::ZcashDeserializeInto,
transaction::{LockTime, Transaction},
};
use crate::{
arbitrary::Prepare,
tests::setup::{new_state_with_mainnet_genesis, transaction_v4_from_coinbase},
};
// sapling
/// Check that, when primed with the first Sapling blocks, a Sapling Spend's referenced anchor is
/// validated.
#[test]
fn check_sapling_anchors() {
zebra_test::init();
let (mut state, _genesis) = new_state_with_mainnet_genesis();
// Bootstrap a block at height == 1 that has the first Sapling note commitments
let mut block1 = zebra_test::vectors::BLOCK_MAINNET_1_BYTES
.zcash_deserialize_into::<Block>()
.expect("block should deserialize");
// convert the coinbase transaction to a version that the non-finalized state will accept
block1.transactions[0] = transaction_v4_from_coinbase(&block1.transactions[0]).into();
// Prime finalized state with the Sapling start + 1, which has the first
// Sapling note commitment
let block_419201 = zebra_test::vectors::BLOCK_MAINNET_419201_BYTES
.zcash_deserialize_into::<Block>()
.expect("block should deserialize");
block_419201
.transactions
.into_iter()
.filter(|tx| tx.has_sapling_shielded_data())
.for_each(|tx| {
let sapling_shielded_data = match tx.deref() {
Transaction::V4 {
sapling_shielded_data,
..
} => sapling_shielded_data.clone(),
_ => unreachable!("These are known v4 transactions"),
};
// set value balance to 0 to pass the chain value pool checks
let sapling_shielded_data = sapling_shielded_data.map(|mut s| {
s.value_balance = 0.try_into().expect("unexpected invalid zero amount");
s
});
block1.transactions.push(Arc::new(Transaction::V4 {
inputs: Vec::new(),
outputs: Vec::new(),
lock_time: LockTime::min_lock_time(),
expiry_height: Height(0),
joinsplit_data: None,
sapling_shielded_data,
}))
});
let block1 = Arc::new(block1).prepare();
assert!(state.validate_and_commit(block1).is_ok());
// Bootstrap a block at height == 2 that references the Sapling note commitment tree state
// from earlier block
let mut block2 = zebra_test::vectors::BLOCK_MAINNET_2_BYTES
.zcash_deserialize_into::<Block>()
.expect("block should deserialize");
// convert the coinbase transaction to a version that the non-finalized state will accept
block2.transactions[0] = transaction_v4_from_coinbase(&block2.transactions[0]).into();
// Exercise Sapling anchor checking with Sapling start + 2, which refers to the note commitment
// tree as of the last transaction of the previous block
let block_419202 = zebra_test::vectors::BLOCK_MAINNET_419202_BYTES
.zcash_deserialize_into::<Block>()
.expect("block should deserialize");
block_419202
.transactions
.into_iter()
.filter(|tx| tx.has_sapling_shielded_data())
.for_each(|tx| {
let sapling_shielded_data = match tx.deref() {
Transaction::V4 {
sapling_shielded_data,
..
} => (sapling_shielded_data.clone()),
_ => unreachable!("These are known v4 transactions"),
};
// set value balance to 0 to pass the chain value pool checks
let sapling_shielded_data = sapling_shielded_data.map(|mut s| {
s.value_balance = 0.try_into().expect("unexpected invalid zero amount");
s
});
block2.transactions.push(Arc::new(Transaction::V4 {
inputs: Vec::new(),
outputs: Vec::new(),
lock_time: LockTime::min_lock_time(),
expiry_height: Height(0),
joinsplit_data: None,
sapling_shielded_data,
}))
});
let block2 = Arc::new(block2).prepare();
assert_eq!(state.validate_and_commit(block2), Ok(()));
}

View File

@ -73,6 +73,10 @@ proptest! {
block1.transactions.push(transaction.into());
let (mut state, _genesis) = new_state_with_mainnet_genesis();
// Allows anchor checks to pass
state.disk.populate_with_anchors(&block1);
let previous_mem = state.mem.clone();
// randomly choose to commit the block to the finalized or non-finalized state
@ -141,6 +145,10 @@ proptest! {
block1.transactions.push(transaction.into());
let (mut state, genesis) = new_state_with_mainnet_genesis();
// Allows anchor checks to pass
state.disk.populate_with_anchors(&block1);
let previous_mem = state.mem.clone();
let block1 = Arc::new(block1).prepare();
@ -194,6 +202,10 @@ proptest! {
block1.transactions.push(transaction.into());
let (mut state, genesis) = new_state_with_mainnet_genesis();
// Allows anchor checks to pass
state.disk.populate_with_anchors(&block1);
let previous_mem = state.mem.clone();
let block1 = Arc::new(block1).prepare();
@ -247,6 +259,10 @@ proptest! {
.extend([transaction1.into(), transaction2.into()]);
let (mut state, genesis) = new_state_with_mainnet_genesis();
// Allows anchor checks to pass
state.disk.populate_with_anchors(&block1);
let previous_mem = state.mem.clone();
let block1 = Arc::new(block1).prepare();
@ -305,6 +321,10 @@ proptest! {
block2.transactions.push(transaction2.into());
let (mut state, _genesis) = new_state_with_mainnet_genesis();
// Allows anchor checks to pass
state.disk.populate_with_anchors(&block1);
let mut previous_mem = state.mem.clone();
let block1_hash;
@ -386,6 +406,10 @@ proptest! {
block1.transactions.push(transaction.into());
let (mut state, _genesis) = new_state_with_mainnet_genesis();
// Allows anchor checks to pass
state.disk.populate_with_anchors(&block1);
let previous_mem = state.mem.clone();
// randomly choose to commit the block to the finalized or non-finalized state
@ -438,6 +462,10 @@ proptest! {
block1.transactions.push(transaction.into());
let (mut state, genesis) = new_state_with_mainnet_genesis();
// Allows anchor checks to pass
state.disk.populate_with_anchors(&block1);
let previous_mem = state.mem.clone();
let block1 = Arc::new(block1).prepare();
@ -486,6 +514,10 @@ proptest! {
.extend([transaction1.into(), transaction2.into()]);
let (mut state, genesis) = new_state_with_mainnet_genesis();
// Allows anchor checks to pass
state.disk.populate_with_anchors(&block1);
let previous_mem = state.mem.clone();
let block1 = Arc::new(block1).prepare();
@ -538,6 +570,11 @@ proptest! {
block2.transactions.push(transaction2.into());
let (mut state, _genesis) = new_state_with_mainnet_genesis();
// Allows anchor checks to pass
state.disk.populate_with_anchors(&block1);
state.disk.populate_with_anchors(&block2);
let mut previous_mem = state.mem.clone();
let block1_hash;
@ -613,6 +650,10 @@ proptest! {
block1.transactions.push(transaction.into());
let (mut state, _genesis) = new_state_with_mainnet_genesis();
// Allows anchor checks to pass
state.disk.populate_with_anchors(&block1);
let previous_mem = state.mem.clone();
// randomly choose to commit the block to the finalized or non-finalized state
@ -665,6 +706,10 @@ proptest! {
block1.transactions.push(transaction.into());
let (mut state, genesis) = new_state_with_mainnet_genesis();
// Allows anchor checks to pass
state.disk.populate_with_anchors(&block1);
let previous_mem = state.mem.clone();
let block1 = Arc::new(block1).prepare();
@ -717,6 +762,10 @@ proptest! {
.extend([transaction1.into(), transaction2.into()]);
let (mut state, genesis) = new_state_with_mainnet_genesis();
// Allows anchor checks to pass
state.disk.populate_with_anchors(&block1);
let previous_mem = state.mem.clone();
let block1 = Arc::new(block1).prepare();
@ -773,6 +822,11 @@ proptest! {
block2.transactions.push(transaction2.into());
let (mut state, _genesis) = new_state_with_mainnet_genesis();
// Allows anchor checks to pass
state.disk.populate_with_anchors(&block1);
state.disk.populate_with_anchors(&block2);
let mut previous_mem = state.mem.clone();
let block1_hash;

View File

@ -632,6 +632,24 @@ impl FinalizedState {
self.db.zs_contains(orchard_nullifiers, &orchard_nullifier)
}
// /// Returns `true` if the finalized state contains `sprout_anchor`.
// pub fn contains_sprout_anchor(&self, sprout_anchor: &sprout::tree::Root) -> bool {
// let sprout_anchors = self.db.cf_handle("sprout_anchors").unwrap();
// self.db.zs_contains(sprout_anchors, &sprout_anchor)
// }
/// Returns `true` if the finalized state contains `sapling_anchor`.
pub fn contains_sapling_anchor(&self, sapling_anchor: &sapling::tree::Root) -> bool {
let sapling_anchors = self.db.cf_handle("sapling_anchors").unwrap();
self.db.zs_contains(sapling_anchors, &sapling_anchor)
}
/// Returns `true` if the finalized state contains `orchard_anchor`.
pub fn contains_orchard_anchor(&self, orchard_anchor: &orchard::tree::Root) -> bool {
let orchard_anchors = self.db.cf_handle("orchard_anchors").unwrap();
self.db.zs_contains(orchard_anchors, &orchard_anchor)
}
/// Returns the finalized hash for a given `block::Height` if it is present.
pub fn hash(&self, height: block::Height) -> Option<block::Hash> {
let hash_by_height = self.db.cf_handle("hash_by_height").unwrap();
@ -766,6 +784,36 @@ impl FinalizedState {
batch.zs_insert(value_pool_cf, (), fake_value_pool);
self.db.write(batch).unwrap();
}
/// Artificially prime the note commitment tree anchor sets with anchors
/// referenced in a block, for testing purposes _only_.
#[cfg(test)]
pub fn populate_with_anchors(&self, block: &Block) {
let mut batch = rocksdb::WriteBatch::default();
// let sprout_anchors = self.db.cf_handle("sprout_anchors").unwrap();
let sapling_anchors = self.db.cf_handle("sapling_anchors").unwrap();
let orchard_anchors = self.db.cf_handle("orchard_anchors").unwrap();
for transaction in block.transactions.iter() {
// Sprout
// for joinsplit in transaction.sprout_groth16_joinsplits() {
// batch.zs_insert(sprout_anchors, joinsplit.anchor, ());
// }
// Sapling
for anchor in transaction.sapling_anchors() {
batch.zs_insert(sapling_anchors, anchor, ());
}
// Orchard
if let Some(orchard_shielded_data) = transaction.orchard_shielded_data() {
batch.zs_insert(orchard_anchors, orchard_shielded_data.shared_anchor, ());
}
}
self.db.write(batch).unwrap();
}
}
// Drop isn't guaranteed to run, such as when we panic, or if someone stored

View File

@ -27,7 +27,7 @@ use crate::{
ValidateContextError,
};
use self::chain::Chain;
pub(crate) use self::chain::Chain;
use super::{check, finalized_state::FinalizedState};
@ -122,6 +122,7 @@ impl NonFinalizedState {
/// Commit block to the non-finalized state, on top of:
/// - an existing chain's tip, or
/// - a newly forked chain.
#[tracing::instrument(level = "debug", skip(self, finalized_state, prepared))]
pub fn commit_block(
&mut self,
prepared: PreparedBlock,
@ -162,6 +163,7 @@ impl NonFinalizedState {
/// Commit block to the non-finalized state as a new chain where its parent
/// is the finalized tip.
#[tracing::instrument(level = "debug", skip(self, finalized_state, prepared))]
pub fn commit_new_chain(
&mut self,
prepared: PreparedBlock,
@ -186,6 +188,7 @@ impl NonFinalizedState {
/// Contextually validate `prepared` using `finalized_state`.
/// If validation succeeds, push `prepared` onto `parent_chain`.
#[tracing::instrument(level = "debug", skip(self, finalized_state, parent_chain))]
fn validate_and_commit(
&self,
parent_chain: Chain,
@ -198,12 +201,19 @@ impl NonFinalizedState {
&parent_chain.spent_utxos,
finalized_state,
)?;
check::prepared_block_commitment_is_valid_for_chain_history(
&prepared,
self.network,
&parent_chain.history_tree,
)?;
check::anchors::anchors_refer_to_earlier_treestates(
finalized_state,
&parent_chain,
&prepared,
)?;
let contextual = ContextuallyValidBlock::with_block_and_spent_utxos(
prepared.clone(),
spent_utxos.clone(),

View File

@ -64,13 +64,13 @@ pub struct Chain {
/// The Sprout anchors created by each block in `blocks`.
pub(super) sprout_anchors_by_height: BTreeMap<block::Height, sprout::tree::Root>,
/// The Sapling anchors created by `blocks`.
pub(super) sapling_anchors: HashMultiSet<sapling::tree::Root>,
pub(crate) sapling_anchors: HashMultiSet<sapling::tree::Root>,
/// The Sapling anchors created by each block in `blocks`.
pub(super) sapling_anchors_by_height: BTreeMap<block::Height, sapling::tree::Root>,
pub(crate) sapling_anchors_by_height: BTreeMap<block::Height, sapling::tree::Root>,
/// The Orchard anchors created by `blocks`.
pub(super) orchard_anchors: HashMultiSet<orchard::tree::Root>,
pub(crate) orchard_anchors: HashMultiSet<orchard::tree::Root>,
/// The Orchard anchors created by each block in `blocks`.
pub(super) orchard_anchors_by_height: BTreeMap<block::Height, orchard::tree::Root>,
pub(crate) orchard_anchors_by_height: BTreeMap<block::Height, orchard::tree::Root>,
/// The Sprout nullifiers revealed by `blocks`.
pub(super) sprout_nullifiers: HashSet<sprout::Nullifier>,
@ -523,6 +523,7 @@ impl UpdateWith<ContextuallyValidBlock> for Chain {
let sapling_root = self.sapling_note_commitment_tree.root();
self.sapling_anchors.insert(sapling_root);
self.sapling_anchors_by_height.insert(height, sapling_root);
let orchard_root = self.orchard_note_commitment_tree.root();
self.orchard_anchors.insert(orchard_root);
self.orchard_anchors_by_height.insert(height, orchard_root);

View File

@ -476,6 +476,9 @@ fn rejection_restores_internal_state_genesis() -> Result<()> {
prop_assert!(state.eq_internal_state(&state));
if let Some(first_block) = chain.next() {
// Allows anchor checks to pass
finalized_state.populate_with_anchors(&first_block.block);
let result = state.commit_new_chain(first_block, &finalized_state);
prop_assert_eq!(
result,
@ -486,6 +489,9 @@ fn rejection_restores_internal_state_genesis() -> Result<()> {
}
for block in chain {
// Allows anchor checks to pass
finalized_state.populate_with_anchors(&block.block);
let result = state.commit_block(block.clone(), &finalized_state);
prop_assert_eq!(
result,

File diff suppressed because one or more lines are too long

View File

@ -29,8 +29,8 @@ lazy_static! {
/// Continuous mainnet blocks, indexed by height
///
/// Contains the continuous blockchain from genesis onwards.
/// Stops at the first gap in the chain.
/// Contains the continuous blockchain from genesis onwards. Stops at the
/// first gap in the chain.
pub static ref CONTINUOUS_MAINNET_BLOCKS: BTreeMap<u32, &'static [u8]> = MAINNET_BLOCKS
.iter()
.enumerate()
@ -40,8 +40,8 @@ lazy_static! {
/// Continuous testnet blocks, indexed by height
///
/// Contains the continuous blockchain from genesis onwards.
/// Stops at the first gap in the chain.
/// Contains the continuous blockchain from genesis onwards. Stops at the
/// first gap in the chain.
pub static ref CONTINUOUS_TESTNET_BLOCKS: BTreeMap<u32, &'static [u8]> = TESTNET_BLOCKS
.iter()
.enumerate()
@ -86,6 +86,7 @@ lazy_static! {
// Sapling
(419_200, BLOCK_MAINNET_419200_BYTES.as_ref()),
(419_201, BLOCK_MAINNET_419201_BYTES.as_ref()),
(419_202, BLOCK_MAINNET_419202_BYTES.as_ref()),
// A bad version field
(434_873, BLOCK_MAINNET_434873_BYTES.as_ref()),
@ -116,7 +117,8 @@ lazy_static! {
/// Mainnet final Sprout roots, indexed by height.
///
/// If there are no Sprout inputs or outputs in a block, the final Sprout root is the same as the previous block.
/// If there are no Sprout inputs or outputs in a block, the final Sprout root is the same as
/// the previous block.
pub static ref MAINNET_FINAL_SPROUT_ROOTS: BTreeMap<u32, &'static [u8; 32]> = [
// Genesis
(0, SPROUT_FINAL_ROOT_MAINNET_0_BYTES.as_ref().try_into().unwrap()),
@ -129,14 +131,15 @@ lazy_static! {
(347_501, SPROUT_FINAL_ROOT_MAINNET_347501_BYTES.as_ref().try_into().unwrap()),
].iter().cloned().collect();
/// Mainnet final sapling roots, indexed by height
/// 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.
/// Pre-Sapling roots are all-zeroes. If there are no Sapling Outputs in a block, the final
/// Sapling root is the same as the previous block.
pub static ref MAINNET_FINAL_SAPLING_ROOTS: BTreeMap<u32, &'static [u8; 32]> = [
// 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()),
(419_202, SAPLING_FINAL_ROOT_MAINNET_419202_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()),
@ -228,7 +231,8 @@ lazy_static! {
/// Testnet final Sprout roots, indexed by height.
///
/// If there are no Sprout inputs or outputs in a block, the final Sprout root is the same as the previous block.
/// If there are no Sprout inputs or outputs in a block, the final Sprout root is the same as
/// the previous block.
pub static ref TESTNET_FINAL_SPROUT_ROOTS: BTreeMap<u32, &'static [u8; 32]> = [
// Genesis
(0, SPROUT_FINAL_ROOT_TESTNET_0_BYTES.as_ref().try_into().unwrap()),
@ -236,10 +240,10 @@ lazy_static! {
(2259, SPROUT_FINAL_ROOT_TESTNET_2259_BYTES.as_ref().try_into().unwrap()),
].iter().cloned().collect();
/// Testnet final sapling roots, indexed by height
/// 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.
/// Pre-sapling roots are all-zeroes. If there are no Sapling Outputs in a block, the final
/// sapling root is the same as the previous block.
pub static ref TESTNET_FINAL_SAPLING_ROOTS: BTreeMap<u32, &'static [u8; 32]> = [
// Sapling
(280_000, SAPLING_FINAL_ROOT_TESTNET_280000_BYTES.as_ref().try_into().unwrap()),
@ -390,7 +394,7 @@ lazy_static! {
.expect("Block bytes are in valid hex representation");
// Sapling transition
// for i in 419199 419200 419201; do
// for i in 419199 419200 419201 419202; do
// zcash-cli getblock $i 0 > block-main-$[i/1000000]-$[i/1000%1000]-$[i%1000].txt
// done
//
@ -412,12 +416,18 @@ lazy_static! {
pub static ref BLOCK_MAINNET_419201_BYTES: Vec<u8> =
<Vec<u8>>::from_hex(include_str!("block-main-0-419-201.txt").trim())
.expect("Block bytes are in valid hex representation");
pub static ref BLOCK_MAINNET_419202_BYTES: Vec<u8> =
<Vec<u8>>::from_hex(include_str!("block-main-0-419-202.txt").trim())
.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();
pub static ref SAPLING_FINAL_ROOT_MAINNET_419202_BYTES: [u8; 32] =
<[u8; 32]>::from_hex("54393f89293c8af01eb985398f5a984c446dd2974bf6ab63fdacbaf32d27a107")
.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