Fix interstitial sprout anchors check (#3283)
* Fix interstitial Sprout anchors check * Update state docs; add sprout_trees_by_anchor to comparisons * Update book/src/dev/rfcs/0005-state-updates.md Co-authored-by: Marek <mail@marek.onl> * Rename `interstitial_roots` to `interstitial_trees` * Document consensus rules * Refactor the docs * Improve the docs for consensus rules * Update reference to cached state * Update zebra-state/src/service/check/anchors.rs Co-authored-by: Deirdre Connolly <deirdre@zfnd.org> * Fix formatting Co-authored-by: Marek <mail@marek.onl> Co-authored-by: Deirdre Connolly <deirdre@zfnd.org> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
parent
9b9e49953e
commit
4aeabd0b52
|
|
@ -54,7 +54,7 @@ jobs:
|
||||||
--container-image rust:buster \
|
--container-image rust:buster \
|
||||||
--container-mount-disk mount-path='/mainnet',name="zebrad-cache-$SHORT_SHA-mainnet-canopy" \
|
--container-mount-disk mount-path='/mainnet',name="zebrad-cache-$SHORT_SHA-mainnet-canopy" \
|
||||||
--container-restart-policy never \
|
--container-restart-policy never \
|
||||||
--create-disk name="zebrad-cache-$SHORT_SHA-mainnet-canopy",image=zebrad-cache-13c6a826-mainnet-canopy \
|
--create-disk name="zebrad-cache-$SHORT_SHA-mainnet-canopy",image=zebrad-cache-1558f3378-mainnet-canopy \
|
||||||
--machine-type n2-standard-8 \
|
--machine-type n2-standard-8 \
|
||||||
--service-account cos-vm@zealous-zebra.iam.gserviceaccount.com \
|
--service-account cos-vm@zealous-zebra.iam.gserviceaccount.com \
|
||||||
--scopes cloud-platform \
|
--scopes cloud-platform \
|
||||||
|
|
|
||||||
|
|
@ -600,29 +600,29 @@ order on byte strings is the numeric ordering).
|
||||||
|
|
||||||
We use the following rocksdb column families:
|
We use the following rocksdb column families:
|
||||||
|
|
||||||
| Column Family | Keys | Values | Updates |
|
| Column Family | Keys | Values | Updates |
|
||||||
|--------------------------------|------------------------|--------------------------------------|---------|
|
| ------------------------------ | ---------------------- | ----------------------------------- | ------- |
|
||||||
| `hash_by_height` | `block::Height` | `block::Hash` | Never |
|
| `hash_by_height` | `block::Height` | `block::Hash` | Never |
|
||||||
| `height_tx_count_by_hash` | `block::Hash` | `BlockTransactionCount` | Never |
|
| `height_tx_count_by_hash` | `block::Hash` | `BlockTransactionCount` | Never |
|
||||||
| `block_header_by_height` | `block::Height` | `block::Header` | Never |
|
| `block_header_by_height` | `block::Height` | `block::Header` | Never |
|
||||||
| `tx_by_location` | `TransactionLocation` | `Transaction` | Never |
|
| `tx_by_location` | `TransactionLocation` | `Transaction` | Never |
|
||||||
| `hash_by_tx` | `TransactionLocation` | `transaction::Hash` | Never |
|
| `hash_by_tx` | `TransactionLocation` | `transaction::Hash` | Never |
|
||||||
| `tx_by_hash` | `transaction::Hash` | `TransactionLocation` | Never |
|
| `tx_by_hash` | `transaction::Hash` | `TransactionLocation` | Never |
|
||||||
| `utxo_by_outpoint` | `OutLocation` | `transparent::Output` | Delete |
|
| `utxo_by_outpoint` | `OutLocation` | `transparent::Output` | Delete |
|
||||||
| `balance_by_transparent_addr` | `transparent::Address` | `Amount \|\| FirstOutLocation` | Update |
|
| `balance_by_transparent_addr` | `transparent::Address` | `Amount \|\| FirstOutLocation` | Update |
|
||||||
| `utxo_by_transparent_addr_loc` | `FirstOutLocation` | `AtLeastOne<OutLocation>` | Up/Del |
|
| `utxo_by_transparent_addr_loc` | `FirstOutLocation` | `AtLeastOne<OutLocation>` | Up/Del |
|
||||||
| `tx_by_transparent_addr_loc` | `FirstOutLocation` | `AtLeastOne<TransactionLocation>` | Append |
|
| `tx_by_transparent_addr_loc` | `FirstOutLocation` | `AtLeastOne<TransactionLocation>` | Append |
|
||||||
| `sprout_nullifiers` | `sprout::Nullifier` | `()` | Never |
|
| `sprout_nullifiers` | `sprout::Nullifier` | `()` | Never |
|
||||||
| `sprout_anchors` | `sprout::tree::Root` | `()` | Never |
|
| `sprout_anchors` | `sprout::tree::Root` | `sprout::tree::NoteCommitmentTree` | Never |
|
||||||
| `sprout_note_commitment_tree` | `block::Height` | `sprout::tree::NoteCommitmentTree` | Delete |
|
| `sprout_note_commitment_tree` | `block::Height` | `sprout::tree::NoteCommitmentTree` | Delete |
|
||||||
| `sapling_nullifiers` | `sapling::Nullifier` | `()` | Never |
|
| `sapling_nullifiers` | `sapling::Nullifier` | `()` | Never |
|
||||||
| `sapling_anchors` | `sapling::tree::Root` | `()` | Never |
|
| `sapling_anchors` | `sapling::tree::Root` | `()` | Never |
|
||||||
| `sapling_note_commitment_tree` | `block::Height` | `sapling::tree::NoteCommitmentTree` | Delete |
|
| `sapling_note_commitment_tree` | `block::Height` | `sapling::tree::NoteCommitmentTree` | Delete |
|
||||||
| `orchard_nullifiers` | `orchard::Nullifier` | `()` | Never |
|
| `orchard_nullifiers` | `orchard::Nullifier` | `()` | Never |
|
||||||
| `orchard_anchors` | `orchard::tree::Root` | `()` | Never |
|
| `orchard_anchors` | `orchard::tree::Root` | `()` | Never |
|
||||||
| `orchard_note_commitment_tree` | `block::Height` | `orchard::tree::NoteCommitmentTree` | Delete |
|
| `orchard_note_commitment_tree` | `block::Height` | `orchard::tree::NoteCommitmentTree` | Delete |
|
||||||
| `history_tree` | `block::Height` | `NonEmptyHistoryTree` | Delete |
|
| `history_tree` | `block::Height` | `NonEmptyHistoryTree` | Delete |
|
||||||
| `tip_chain_value_pool` | `()` | `ValueBalance` | Update |
|
| `tip_chain_value_pool` | `()` | `ValueBalance` | Update |
|
||||||
|
|
||||||
Zcash structures are encoded using `ZcashSerialize`/`ZcashDeserialize`.
|
Zcash structures are encoded using `ZcashSerialize`/`ZcashDeserialize`.
|
||||||
Other structures are encoded using `IntoDisk`/`FromDisk`.
|
Other structures are encoded using `IntoDisk`/`FromDisk`.
|
||||||
|
|
@ -753,15 +753,25 @@ So they should not be used for consensus-critical checks.
|
||||||
It also includes the `TransactionLocation` from the `FirstOutLocation`.
|
It also includes the `TransactionLocation` from the `FirstOutLocation`.
|
||||||
(This duplicate data is small, and helps simplify the code.)
|
(This duplicate data is small, and helps simplify the code.)
|
||||||
|
|
||||||
- Each incremental tree consists of nodes for a small number of peaks.
|
- Each `*_note_commitment_tree` stores the note commitment tree state
|
||||||
Peaks are written once, then deleted when they are no longer required.
|
at the tip of the finalized state, for the specific pool. There is always
|
||||||
New incremental tree nodes can be added each time the finalized tip changes,
|
a single entry for those; they are indexed by height just to make testing
|
||||||
and unused nodes can be deleted.
|
and debugging easier (so for each block committed, the old tree is
|
||||||
We only keep the nodes needed for the incremental tree for the finalized tip.
|
deleted and a new one is inserted by its new height). Each tree is stored
|
||||||
TODO: update this description based on the incremental merkle tree code
|
as a "Merkle tree frontier" which is basically a (logarithmic) subset of
|
||||||
|
the Merkle tree nodes as required to insert new items.
|
||||||
|
|
||||||
- The history tree indexes its peaks using blocks since the last network upgrade.
|
- `history_tree` stores the ZIP-221 history tree state at the tip of the finalized
|
||||||
But we map those peak indexes to heights, to make testing and debugging easier.
|
state. There is always a single entry for it; it is indexed by height just
|
||||||
|
to make testing and debugging easier. The tree is stored as the set of "peaks"
|
||||||
|
of the "Merkle mountain range" tree structure, which is what is required to
|
||||||
|
insert new items.
|
||||||
|
|
||||||
|
- Each `*_anchors` stores the anchor (the root of a Merkle tree) of the note commitment
|
||||||
|
tree of a certain block. We only use the keys since we just need the set of anchors,
|
||||||
|
regardless of where they come from. The exception is `sprout_anchors` which also maps
|
||||||
|
the anchor to the matching note commitment tree. This is required to support interstitial
|
||||||
|
treestates, which are unique to Sprout.
|
||||||
|
|
||||||
- The value pools are only stored for the finalized tip.
|
- The value pools are only stored for the finalized tip.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ pub use zebra_chain::transparent::MIN_TRANSPARENT_COINBASE_MATURITY;
|
||||||
pub const MAX_BLOCK_REORG_HEIGHT: u32 = MIN_TRANSPARENT_COINBASE_MATURITY - 1;
|
pub const MAX_BLOCK_REORG_HEIGHT: u32 = MIN_TRANSPARENT_COINBASE_MATURITY - 1;
|
||||||
|
|
||||||
/// The database format version, incremented each time the database format changes.
|
/// The database format version, incremented each time the database format changes.
|
||||||
pub const DATABASE_FORMAT_VERSION: u32 = 11;
|
pub const DATABASE_FORMAT_VERSION: u32 = 12;
|
||||||
|
|
||||||
/// The maximum number of blocks to check for NU5 transactions,
|
/// The maximum number of blocks to check for NU5 transactions,
|
||||||
/// before we assume we are on a pre-NU5 legacy chain.
|
/// before we assume we are on a pre-NU5 legacy chain.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
//! Checks for whether cited anchors are previously-computed note commitment
|
//! Checks for whether cited anchors are previously-computed note commitment
|
||||||
//! tree roots.
|
//! tree roots.
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use zebra_chain::sprout;
|
use zebra_chain::sprout;
|
||||||
|
|
||||||
|
|
@ -10,34 +10,14 @@ use crate::{
|
||||||
PreparedBlock, ValidateContextError,
|
PreparedBlock, ValidateContextError,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Check that the Sprout, Sapling, and Orchard anchors specified by
|
/// Checks that the Sprout, Sapling, and Orchard anchors specified by
|
||||||
/// transactions in this block have been computed previously within the context
|
/// transactions in this block have been computed previously within the context
|
||||||
/// of its parent chain. We do not check any anchors in checkpointed blocks, which avoids
|
/// of its parent chain. We do not check any anchors in checkpointed blocks,
|
||||||
/// JoinSplits<BCTV14Proof>
|
/// which avoids JoinSplits<BCTV14Proof>
|
||||||
///
|
///
|
||||||
/// Sprout anchors may refer to some earlier block's final treestate (like
|
/// Sprout anchors may refer to some earlier block's final treestate (like
|
||||||
/// Sapling and Orchard do exclusively) _or_ to the interstisial output
|
/// Sapling and Orchard do exclusively) _or_ to the interstitial output
|
||||||
/// treestate of any prior `JoinSplit` _within the same transaction_.
|
/// 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))]
|
#[tracing::instrument(skip(finalized_state, parent_chain, prepared))]
|
||||||
pub(crate) fn anchors_refer_to_earlier_treestates(
|
pub(crate) fn anchors_refer_to_earlier_treestates(
|
||||||
finalized_state: &FinalizedState,
|
finalized_state: &FinalizedState,
|
||||||
|
|
@ -46,55 +26,80 @@ pub(crate) fn anchors_refer_to_earlier_treestates(
|
||||||
) -> Result<(), ValidateContextError> {
|
) -> Result<(), ValidateContextError> {
|
||||||
for transaction in prepared.block.transactions.iter() {
|
for transaction in prepared.block.transactions.iter() {
|
||||||
// Sprout JoinSplits, with interstitial treestates to check as well.
|
// 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 transaction.has_sprout_joinsplit_data() {
|
if transaction.has_sprout_joinsplit_data() {
|
||||||
// > The anchor of each JoinSplit description in a transaction MUST refer to
|
let mut interstitial_trees: HashMap<
|
||||||
// > either some earlier block’s final Sprout treestate, or to the interstitial
|
sprout::tree::Root,
|
||||||
// > output treestate of any prior JoinSplit description in the same transaction.
|
sprout::tree::NoteCommitmentTree,
|
||||||
//
|
> = HashMap::new();
|
||||||
// https://zips.z.cash/protocol/protocol.pdf#joinsplit
|
|
||||||
let mut interstitial_roots: HashSet<sprout::tree::Root> = HashSet::new();
|
|
||||||
|
|
||||||
let mut interstitial_note_commitment_tree = parent_chain.sprout_note_commitment_tree();
|
|
||||||
|
|
||||||
for joinsplit in transaction.sprout_groth16_joinsplits() {
|
for joinsplit in transaction.sprout_groth16_joinsplits() {
|
||||||
// Check all anchor sets, including the one for interstitial anchors.
|
// Check all anchor sets, including the one for interstitial
|
||||||
|
// anchors.
|
||||||
//
|
//
|
||||||
// Note that [`interstitial_roots`] is always empty in the first
|
// The anchor is checked and the matching tree is obtained,
|
||||||
// iteration of the loop. This is because:
|
// which is used to create the interstitial tree state for this
|
||||||
|
// JoinSplit:
|
||||||
//
|
//
|
||||||
// > "The anchor of each JoinSplit description in a transaction
|
// > For each JoinSplit description in a transaction, an
|
||||||
// > MUST refer to [...] to the interstitial output treestate of
|
// > interstitial output treestate is constructed which adds the
|
||||||
// > any **prior** JoinSplit description in the same transaction."
|
// > note commitments and nullifiers specified in that JoinSplit
|
||||||
if !parent_chain.sprout_anchors.contains(&joinsplit.anchor)
|
// > description to the input treestate referred to by its
|
||||||
&& !finalized_state.contains_sprout_anchor(&joinsplit.anchor)
|
// > anchor. This interstitial output treestate is available for
|
||||||
&& (!interstitial_roots.contains(&joinsplit.anchor))
|
// > use as the anchor of subsequent JoinSplit descriptions in
|
||||||
{
|
// > the same transaction.
|
||||||
// TODO: This check fails near:
|
//
|
||||||
// - mainnet block 1_047_908
|
// <https://zips.z.cash/protocol/protocol.pdf#joinsplit>
|
||||||
// with anchor 019c435cd1e8aca9a4165f7e126ac6e548952439d50213f4d15c546df9d49b61
|
//
|
||||||
// - testnet block 1_057_737
|
// # Consensus
|
||||||
// with anchor 3ad623811ffa4fe8498b23f3d6bb4e086dca32269afef6c8e572fd9ee6d0c0ea
|
//
|
||||||
//
|
// > The anchor of each JoinSplit description in a transaction
|
||||||
// Restore after finding the cause and fixing it.
|
// > MUST refer to either some earlier block’s final Sprout
|
||||||
// return Err(ValidateContextError::UnknownSproutAnchor {
|
// > treestate, or to the interstitial output treestate of any
|
||||||
// anchor: joinsplit.anchor,
|
// > prior JoinSplit description in the same transaction.
|
||||||
// });
|
//
|
||||||
tracing::warn!(?joinsplit.anchor, ?prepared.height, ?prepared.hash, "failed to find sprout anchor")
|
// > For the first JoinSplit description of a transaction, the
|
||||||
}
|
// > anchor MUST be the output Sprout treestate of a previous
|
||||||
|
// > block.
|
||||||
|
//
|
||||||
|
// <https://zips.z.cash/protocol/protocol.pdf#joinsplit>
|
||||||
|
//
|
||||||
|
// Note that in order to satisfy the latter consensus rule above,
|
||||||
|
// [`interstitial_trees`] is always empty in the first iteration
|
||||||
|
// of the loop.
|
||||||
|
let input_tree = interstitial_trees
|
||||||
|
.get(&joinsplit.anchor)
|
||||||
|
.cloned()
|
||||||
|
.or_else(|| {
|
||||||
|
parent_chain
|
||||||
|
.sprout_trees_by_anchor
|
||||||
|
.get(&joinsplit.anchor)
|
||||||
|
.cloned()
|
||||||
|
.or_else(|| {
|
||||||
|
finalized_state
|
||||||
|
.sprout_note_commitment_tree_by_anchor(&joinsplit.anchor)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut input_tree = match input_tree {
|
||||||
|
Some(tree) => tree,
|
||||||
|
None => {
|
||||||
|
tracing::warn!(?joinsplit.anchor, ?prepared.height, ?prepared.hash, "failed to find sprout anchor");
|
||||||
|
return Err(ValidateContextError::UnknownSproutAnchor {
|
||||||
|
anchor: joinsplit.anchor,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
tracing::debug!(?joinsplit.anchor, "validated sprout anchor");
|
tracing::debug!(?joinsplit.anchor, "validated sprout anchor");
|
||||||
|
|
||||||
// Add new anchors to the interstitial note commitment tree.
|
// Add new anchors to the interstitial note commitment tree.
|
||||||
for cm in joinsplit.commitments {
|
for cm in joinsplit.commitments {
|
||||||
interstitial_note_commitment_tree
|
input_tree
|
||||||
.append(cm)
|
.append(cm)
|
||||||
.expect("note commitment should be appendable to the tree");
|
.expect("note commitment should be appendable to the tree");
|
||||||
}
|
}
|
||||||
|
|
||||||
interstitial_roots.insert(interstitial_note_commitment_tree.root());
|
interstitial_trees.insert(input_tree.root(), input_tree);
|
||||||
|
|
||||||
tracing::debug!(?joinsplit.anchor, "observed sprout anchor");
|
tracing::debug!(?joinsplit.anchor, "observed sprout anchor");
|
||||||
}
|
}
|
||||||
|
|
@ -103,6 +108,20 @@ pub(crate) fn anchors_refer_to_earlier_treestates(
|
||||||
// Sapling Spends
|
// Sapling Spends
|
||||||
//
|
//
|
||||||
// MUST refer to some earlier block’s final Sapling treestate.
|
// MUST refer to some earlier block’s final Sapling treestate.
|
||||||
|
//
|
||||||
|
// # Consensus
|
||||||
|
//
|
||||||
|
// > 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.
|
||||||
|
//
|
||||||
|
// <https://zips.z.cash/protocol/protocol.pdf#spendsandoutputs>
|
||||||
|
//
|
||||||
|
// This rule is also implemented in
|
||||||
|
// [`zebra_chain::sapling::shielded_data`].
|
||||||
|
//
|
||||||
|
// The "earlier treestate" check is implemented here.
|
||||||
if transaction.has_sapling_shielded_data() {
|
if transaction.has_sapling_shielded_data() {
|
||||||
for anchor in transaction.sapling_anchors() {
|
for anchor in transaction.sapling_anchors() {
|
||||||
tracing::debug!(?anchor, "observed sapling anchor");
|
tracing::debug!(?anchor, "observed sapling anchor");
|
||||||
|
|
@ -120,6 +139,14 @@ pub(crate) fn anchors_refer_to_earlier_treestates(
|
||||||
// Orchard Actions
|
// Orchard Actions
|
||||||
//
|
//
|
||||||
// MUST refer to some earlier block’s final Orchard treestate.
|
// MUST refer to some earlier block’s final Orchard treestate.
|
||||||
|
//
|
||||||
|
// # Consensus
|
||||||
|
//
|
||||||
|
// > 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.
|
||||||
|
//
|
||||||
|
// <https://zips.z.cash/protocol/protocol.pdf#actions>
|
||||||
if let Some(orchard_shielded_data) = transaction.orchard_shielded_data() {
|
if let Some(orchard_shielded_data) = transaction.orchard_shielded_data() {
|
||||||
tracing::debug!(?orchard_shielded_data.shared_anchor, "observed orchard anchor");
|
tracing::debug!(?orchard_shielded_data.shared_anchor, "observed orchard anchor");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -438,7 +438,7 @@ impl FinalizedState {
|
||||||
|
|
||||||
// Compute the new anchors and index them
|
// Compute the new anchors and index them
|
||||||
// Note: if the root hasn't changed, we write the same value again.
|
// Note: if the root hasn't changed, we write the same value again.
|
||||||
batch.zs_insert(sprout_anchors, sprout_root, ());
|
batch.zs_insert(sprout_anchors, sprout_root, &sprout_note_commitment_tree);
|
||||||
batch.zs_insert(sapling_anchors, sapling_root, ());
|
batch.zs_insert(sapling_anchors, sapling_root, ());
|
||||||
batch.zs_insert(orchard_anchors, orchard_root, ());
|
batch.zs_insert(orchard_anchors, orchard_root, ());
|
||||||
|
|
||||||
|
|
@ -632,6 +632,7 @@ impl FinalizedState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the finalized state contains `sprout_anchor`.
|
/// Returns `true` if the finalized state contains `sprout_anchor`.
|
||||||
|
#[allow(unused)]
|
||||||
pub fn contains_sprout_anchor(&self, sprout_anchor: &sprout::tree::Root) -> bool {
|
pub fn contains_sprout_anchor(&self, sprout_anchor: &sprout::tree::Root) -> bool {
|
||||||
let sprout_anchors = self.db.cf_handle("sprout_anchors").unwrap();
|
let sprout_anchors = self.db.cf_handle("sprout_anchors").unwrap();
|
||||||
self.db.zs_contains(sprout_anchors, &sprout_anchor)
|
self.db.zs_contains(sprout_anchors, &sprout_anchor)
|
||||||
|
|
@ -684,6 +685,18 @@ impl FinalizedState {
|
||||||
.expect("Sprout note commitment tree must exist if there is a finalized tip")
|
.expect("Sprout note commitment tree must exist if there is a finalized tip")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the Sprout note commitment tree matching the given anchor.
|
||||||
|
///
|
||||||
|
/// This is used for interstitial tree building, which is unique to Sprout.
|
||||||
|
pub fn sprout_note_commitment_tree_by_anchor(
|
||||||
|
&self,
|
||||||
|
sprout_anchor: &sprout::tree::Root,
|
||||||
|
) -> Option<sprout::tree::NoteCommitmentTree> {
|
||||||
|
let sprout_anchors = self.db.cf_handle("sprout_anchors").unwrap();
|
||||||
|
|
||||||
|
self.db.zs_get(sprout_anchors, sprout_anchor)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the Sapling note commitment tree of the finalized tip
|
/// Returns the Sapling note commitment tree of the finalized tip
|
||||||
/// or the empty tree if the state is empty.
|
/// or the empty tree if the state is empty.
|
||||||
pub fn sapling_note_commitment_tree(&self) -> sapling::tree::NoteCommitmentTree {
|
pub fn sapling_note_commitment_tree(&self) -> sapling::tree::NoteCommitmentTree {
|
||||||
|
|
@ -797,7 +810,11 @@ impl FinalizedState {
|
||||||
for transaction in block.transactions.iter() {
|
for transaction in block.transactions.iter() {
|
||||||
// Sprout
|
// Sprout
|
||||||
for joinsplit in transaction.sprout_groth16_joinsplits() {
|
for joinsplit in transaction.sprout_groth16_joinsplits() {
|
||||||
batch.zs_insert(sprout_anchors, joinsplit.anchor, ());
|
batch.zs_insert(
|
||||||
|
sprout_anchors,
|
||||||
|
joinsplit.anchor,
|
||||||
|
sprout::tree::NoteCommitmentTree::default(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sapling
|
// Sapling
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,10 @@ pub struct Chain {
|
||||||
pub(crate) sprout_anchors: HashMultiSet<sprout::tree::Root>,
|
pub(crate) sprout_anchors: HashMultiSet<sprout::tree::Root>,
|
||||||
/// The Sprout anchors created by each block in `blocks`.
|
/// The Sprout anchors created by each block in `blocks`.
|
||||||
pub(crate) sprout_anchors_by_height: BTreeMap<block::Height, sprout::tree::Root>,
|
pub(crate) sprout_anchors_by_height: BTreeMap<block::Height, sprout::tree::Root>,
|
||||||
|
/// The Sprout note commitment tree for each anchor.
|
||||||
|
/// This is required for interstitial states.
|
||||||
|
pub(crate) sprout_trees_by_anchor:
|
||||||
|
HashMap<sprout::tree::Root, sprout::tree::NoteCommitmentTree>,
|
||||||
/// The Sapling anchors created by `blocks`.
|
/// The Sapling anchors created by `blocks`.
|
||||||
pub(crate) sapling_anchors: HashMultiSet<sapling::tree::Root>,
|
pub(crate) sapling_anchors: HashMultiSet<sapling::tree::Root>,
|
||||||
/// The Sapling anchors created by each block in `blocks`.
|
/// The Sapling anchors created by each block in `blocks`.
|
||||||
|
|
@ -117,6 +121,7 @@ impl Chain {
|
||||||
spent_utxos: Default::default(),
|
spent_utxos: Default::default(),
|
||||||
sprout_anchors: HashMultiSet::new(),
|
sprout_anchors: HashMultiSet::new(),
|
||||||
sprout_anchors_by_height: Default::default(),
|
sprout_anchors_by_height: Default::default(),
|
||||||
|
sprout_trees_by_anchor: Default::default(),
|
||||||
sapling_anchors: HashMultiSet::new(),
|
sapling_anchors: HashMultiSet::new(),
|
||||||
sapling_anchors_by_height: Default::default(),
|
sapling_anchors_by_height: Default::default(),
|
||||||
orchard_anchors: HashMultiSet::new(),
|
orchard_anchors: HashMultiSet::new(),
|
||||||
|
|
@ -155,6 +160,7 @@ impl Chain {
|
||||||
|
|
||||||
// note commitment trees
|
// note commitment trees
|
||||||
self.sprout_note_commitment_tree.root() == other.sprout_note_commitment_tree.root() &&
|
self.sprout_note_commitment_tree.root() == other.sprout_note_commitment_tree.root() &&
|
||||||
|
self.sprout_trees_by_anchor == other.sprout_trees_by_anchor &&
|
||||||
self.sapling_note_commitment_tree.root() == other.sapling_note_commitment_tree.root() &&
|
self.sapling_note_commitment_tree.root() == other.sapling_note_commitment_tree.root() &&
|
||||||
self.orchard_note_commitment_tree.root() == other.orchard_note_commitment_tree.root() &&
|
self.orchard_note_commitment_tree.root() == other.orchard_note_commitment_tree.root() &&
|
||||||
|
|
||||||
|
|
@ -383,6 +389,7 @@ impl Chain {
|
||||||
sprout_anchors: self.sprout_anchors.clone(),
|
sprout_anchors: self.sprout_anchors.clone(),
|
||||||
sapling_anchors: self.sapling_anchors.clone(),
|
sapling_anchors: self.sapling_anchors.clone(),
|
||||||
orchard_anchors: self.orchard_anchors.clone(),
|
orchard_anchors: self.orchard_anchors.clone(),
|
||||||
|
sprout_trees_by_anchor: self.sprout_trees_by_anchor.clone(),
|
||||||
sprout_anchors_by_height: self.sprout_anchors_by_height.clone(),
|
sprout_anchors_by_height: self.sprout_anchors_by_height.clone(),
|
||||||
sapling_anchors_by_height: self.sapling_anchors_by_height.clone(),
|
sapling_anchors_by_height: self.sapling_anchors_by_height.clone(),
|
||||||
orchard_anchors_by_height: self.orchard_anchors_by_height.clone(),
|
orchard_anchors_by_height: self.orchard_anchors_by_height.clone(),
|
||||||
|
|
@ -394,14 +401,6 @@ impl Chain {
|
||||||
chain_value_pools: self.chain_value_pools,
|
chain_value_pools: self.chain_value_pools,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a clone of the Sprout note commitment tree for this chain.
|
|
||||||
///
|
|
||||||
/// Useful when calculating interstitial note commitment trees for each JoinSplit in a Sprout
|
|
||||||
/// shielded transaction.
|
|
||||||
pub fn sprout_note_commitment_tree(&self) -> sprout::tree::NoteCommitmentTree {
|
|
||||||
self.sprout_note_commitment_tree.clone()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The revert position being performed on a chain.
|
/// The revert position being performed on a chain.
|
||||||
|
|
@ -528,6 +527,8 @@ impl UpdateWith<ContextuallyValidBlock> for Chain {
|
||||||
let sprout_root = self.sprout_note_commitment_tree.root();
|
let sprout_root = self.sprout_note_commitment_tree.root();
|
||||||
self.sprout_anchors.insert(sprout_root);
|
self.sprout_anchors.insert(sprout_root);
|
||||||
self.sprout_anchors_by_height.insert(height, sprout_root);
|
self.sprout_anchors_by_height.insert(height, sprout_root);
|
||||||
|
self.sprout_trees_by_anchor
|
||||||
|
.insert(sprout_root, self.sprout_note_commitment_tree.clone());
|
||||||
let sapling_root = self.sapling_note_commitment_tree.root();
|
let sapling_root = self.sapling_note_commitment_tree.root();
|
||||||
self.sapling_anchors.insert(sapling_root);
|
self.sapling_anchors.insert(sapling_root);
|
||||||
self.sapling_anchors_by_height.insert(height, sapling_root);
|
self.sapling_anchors_by_height.insert(height, sapling_root);
|
||||||
|
|
@ -643,6 +644,9 @@ impl UpdateWith<ContextuallyValidBlock> for Chain {
|
||||||
self.sprout_anchors.remove(&anchor),
|
self.sprout_anchors.remove(&anchor),
|
||||||
"Sprout anchor must be present if block was added to chain"
|
"Sprout anchor must be present if block was added to chain"
|
||||||
);
|
);
|
||||||
|
if !self.sprout_anchors.contains(&anchor) {
|
||||||
|
self.sprout_trees_by_anchor.remove(&anchor);
|
||||||
|
}
|
||||||
|
|
||||||
let anchor = self
|
let anchor = self
|
||||||
.sapling_anchors_by_height
|
.sapling_anchors_by_height
|
||||||
|
|
|
||||||
|
|
@ -607,6 +607,7 @@ fn different_blocks_different_chains() -> Result<()> {
|
||||||
// anchors
|
// anchors
|
||||||
chain1.sprout_anchors = chain2.sprout_anchors.clone();
|
chain1.sprout_anchors = chain2.sprout_anchors.clone();
|
||||||
chain1.sprout_anchors_by_height = chain2.sprout_anchors_by_height.clone();
|
chain1.sprout_anchors_by_height = chain2.sprout_anchors_by_height.clone();
|
||||||
|
chain1.sprout_trees_by_anchor = chain2.sprout_trees_by_anchor.clone();
|
||||||
chain1.sapling_anchors = chain2.sapling_anchors.clone();
|
chain1.sapling_anchors = chain2.sapling_anchors.clone();
|
||||||
chain1.sapling_anchors_by_height = chain2.sapling_anchors_by_height.clone();
|
chain1.sapling_anchors_by_height = chain2.sapling_anchors_by_height.clone();
|
||||||
chain1.orchard_anchors = chain2.orchard_anchors.clone();
|
chain1.orchard_anchors = chain2.orchard_anchors.clone();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue