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:
parent
3c9ad89018
commit
e6ffe374d4
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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(()))];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 block’s 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
|
||||
/// > block’s 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 block’s
|
||||
/// > 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 block’s 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 block’s 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(())
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
//! Tests for state contextual validation checks.
|
||||
|
||||
mod anchors;
|
||||
mod nullifier;
|
||||
mod utxo;
|
||||
mod vectors;
|
||||
|
|
|
@ -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(()));
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue