feat(rpc): Implement `z_gettreestate` RPC (#3990)
* Impl the elementary structure of the `z_gettreestate` RPC * Fix merging bugs * Fix a merge bug * Fix a merge bug * Move a derive attribute Co-authored-by: teor <teor@riseup.net> * Clarify the support of negative heights * Add Orchard note commitment trees to the response * Add the time to the response * Finalize the `z_gettreestate` RPC * Add a note that verified blocks have coinbase height * Refactor `from_str` for `HashOrHeight` * Fix a mistake in the docs Co-authored-by: teor <teor@riseup.net> * Clarify request types Co-authored-by: teor <teor@riseup.net> * Simplify `hash_or_height` conversion to height Co-authored-by: teor <teor@riseup.net> * Add a TODO about optimization Co-authored-by: teor <teor@riseup.net> * Add a doc comment * Make sure Sapling & Orchard trees don't get mixed up * Serialize Sapling commitment trees * Refactor some comments * Serialize Orchard commitment trees * Serialize block heights * Simplify the serialization of commitment trees * Remove the block time from the RPC response * Simplify the serialization of block heights * Put Sapling & Orchard requests together * Remove a redundant TODO * Add block times to the RPC response * Derive `Clone, Debug, Eq, PartialEq` for `GetTreestate` Co-authored-by: teor <teor@riseup.net> * Derive `Clone`, `Debug`, `Eq` and `PartialEq` for `SerializedTree` * Document the fields of `GetTreestate` * Skip the serialization of empty trees This ensures compatibility with `zcashd` in the `z_gettreestate` RPC. * Document the `impl` of `merkle_tree::Hashable` for nodes * Make the structure of the JSON response consistent with `zcashd` * Derive `Eq` for nodes Co-authored-by: teor <teor@riseup.net> * Convert Sapling commitment trees to a format compatible with zcashd * Refactor the conversion of Sapling commitment trees * Refactor some comments * Refactor comments * Add a description of the conversion Co-authored-by: Conrado Gouvea <conrado@zfnd.org> * Fix comment indenting * Document the conversion between the dense and sparse formats Co-authored-by: teor <teor@riseup.net> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Conrado Gouvea <conrado@zfnd.org>
This commit is contained in:
parent
fd7f49fb0c
commit
7c726b246d
|
@ -1485,6 +1485,15 @@ dependencies = [
|
|||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equihash"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=d5c5f04#d5c5f048947d211c2fbef23ce986b329b91d1aa5"
|
||||
dependencies = [
|
||||
"blake2b_simd 1.0.0",
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eyre"
|
||||
version = "0.6.7"
|
||||
|
@ -5908,6 +5917,15 @@ dependencies = [
|
|||
"nonempty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zcash_encoding"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=d5c5f04#d5c5f048947d211c2fbef23ce986b329b91d1aa5"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"nonempty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zcash_history"
|
||||
version = "0.2.0"
|
||||
|
@ -5940,6 +5958,17 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zcash_note_encryption"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=d5c5f04#d5c5f048947d211c2fbef23ce986b329b91d1aa5"
|
||||
dependencies = [
|
||||
"chacha20",
|
||||
"chacha20poly1305",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zcash_primitives"
|
||||
version = "0.5.0"
|
||||
|
@ -6008,6 +6037,42 @@ dependencies = [
|
|||
"zcash_note_encryption 0.1.0 (git+https://github.com/zcash/librustzcash.git?rev=d14e7a707ce01cefcbc82651dad48f002185dded)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zcash_primitives"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=d5c5f04#d5c5f048947d211c2fbef23ce986b329b91d1aa5"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"bip0039",
|
||||
"bitvec",
|
||||
"blake2b_simd 1.0.0",
|
||||
"blake2s_simd 1.0.0",
|
||||
"bls12_381",
|
||||
"bs58",
|
||||
"byteorder",
|
||||
"chacha20poly1305",
|
||||
"equihash 0.1.0 (git+https://github.com/zcash/librustzcash.git?rev=d5c5f04)",
|
||||
"ff",
|
||||
"fpe",
|
||||
"group",
|
||||
"hdwallet",
|
||||
"hex",
|
||||
"incrementalmerkletree",
|
||||
"jubjub",
|
||||
"lazy_static",
|
||||
"memuse",
|
||||
"nonempty",
|
||||
"orchard",
|
||||
"rand 0.8.5",
|
||||
"rand_core 0.6.3",
|
||||
"ripemd",
|
||||
"secp256k1",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"zcash_encoding 0.0.0 (git+https://github.com/zcash/librustzcash.git?rev=d5c5f04)",
|
||||
"zcash_note_encryption 0.1.0 (git+https://github.com/zcash/librustzcash.git?rev=d5c5f04)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zcash_proofs"
|
||||
version = "0.5.0"
|
||||
|
@ -6095,9 +6160,10 @@ dependencies = [
|
|||
"tracing",
|
||||
"uint",
|
||||
"x25519-dalek",
|
||||
"zcash_encoding 0.0.0 (git+https://github.com/zcash/librustzcash.git?rev=d5c5f04)",
|
||||
"zcash_history",
|
||||
"zcash_note_encryption 0.1.0 (git+https://github.com/zcash/librustzcash.git?rev=d14e7a707ce01cefcbc82651dad48f002185dded)",
|
||||
"zcash_primitives 0.5.0 (git+https://github.com/zcash/librustzcash.git?rev=d14e7a707ce01cefcbc82651dad48f002185dded)",
|
||||
"zcash_primitives 0.5.0 (git+https://github.com/zcash/librustzcash.git?rev=d5c5f04)",
|
||||
"zebra-test",
|
||||
]
|
||||
|
||||
|
|
|
@ -53,7 +53,8 @@ orchard = "=0.1.0-beta.3"
|
|||
|
||||
equihash = "0.1.0"
|
||||
zcash_note_encryption = "0.1"
|
||||
zcash_primitives = { version = "0.5", features = ["transparent-inputs"] }
|
||||
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "d5c5f04", features = ["transparent-inputs"] }
|
||||
zcash_encoding = { git = "https://github.com/zcash/librustzcash.git", rev = "d5c5f04" }
|
||||
zcash_history = { git = "https://github.com/ZcashFoundation/librustzcash.git", tag = "0.5.1-zebra-v1.0.0-beta.4" }
|
||||
|
||||
proptest = { version = "0.10.1", optional = true }
|
||||
|
|
|
@ -67,6 +67,10 @@ impl fmt::Display for Block {
|
|||
|
||||
impl Block {
|
||||
/// Return the block height reported in the coinbase transaction, if any.
|
||||
///
|
||||
/// Note
|
||||
///
|
||||
/// Verified blocks have a valid height.
|
||||
pub fn coinbase_height(&self) -> Option<Height> {
|
||||
self.transactions
|
||||
.get(0)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! The block header.
|
||||
|
||||
use std::usize;
|
||||
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
|
|
|
@ -18,6 +18,7 @@ use std::{
|
|||
hash::{Hash, Hasher},
|
||||
io,
|
||||
ops::Deref,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use bitvec::prelude::*;
|
||||
|
@ -25,6 +26,7 @@ use halo2::pasta::{group::ff::PrimeField, pallas};
|
|||
use incrementalmerkletree::{bridgetree, Frontier};
|
||||
use lazy_static::lazy_static;
|
||||
use thiserror::Error;
|
||||
use zcash_primitives::merkle_tree::{self, CommitmentTree};
|
||||
|
||||
use super::sinsemilla::*;
|
||||
|
||||
|
@ -153,9 +155,52 @@ impl ZcashDeserialize for Root {
|
|||
}
|
||||
|
||||
/// A node of the Orchard Incremental Note Commitment Tree.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
struct Node(pallas::Base);
|
||||
|
||||
/// Required to convert [`NoteCommitmentTree`] into [`SerializedTree`].
|
||||
///
|
||||
/// Zebra stores Orchard note commitment trees as [`Frontier`][1]s while the
|
||||
/// [`z_gettreestate`][2] RPC requires [`CommitmentTree`][3]s. Implementing
|
||||
/// [`merkle_tree::Hashable`] for [`Node`]s allows the conversion.
|
||||
///
|
||||
/// [1]: bridgetree::Frontier
|
||||
/// [2]: https://zcash.github.io/rpc/z_gettreestate.html
|
||||
/// [3]: merkle_tree::CommitmentTree
|
||||
impl merkle_tree::Hashable for Node {
|
||||
fn read<R: io::Read>(mut reader: R) -> io::Result<Self> {
|
||||
let mut repr = [0u8; 32];
|
||||
reader.read_exact(&mut repr)?;
|
||||
let maybe_node = pallas::Base::from_repr(repr).map(Self);
|
||||
|
||||
<Option<_>>::from(maybe_node).ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Non-canonical encoding of Pallas base field value.",
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn write<W: io::Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
writer.write_all(&self.0.to_repr())
|
||||
}
|
||||
|
||||
fn combine(level: usize, a: &Self, b: &Self) -> Self {
|
||||
let level = u8::try_from(level).expect("level must fit into u8");
|
||||
let layer = (MERKLE_DEPTH - 1) as u8 - level;
|
||||
Self(merkle_crh_orchard(layer, a.0, b.0))
|
||||
}
|
||||
|
||||
fn blank() -> Self {
|
||||
Self(NoteCommitmentTree::uncommitted())
|
||||
}
|
||||
|
||||
fn empty_root(level: usize) -> Self {
|
||||
let layer_below: usize = MERKLE_DEPTH - level;
|
||||
Self(EMPTY_ROOTS[layer_below])
|
||||
}
|
||||
}
|
||||
|
||||
impl incrementalmerkletree::Hashable for Node {
|
||||
fn empty_leaf() -> Self {
|
||||
Self(NoteCommitmentTree::uncommitted())
|
||||
|
@ -372,3 +417,67 @@ impl From<Vec<pallas::Base>> for NoteCommitmentTree {
|
|||
tree
|
||||
}
|
||||
}
|
||||
|
||||
/// A serialized Orchard note commitment tree.
|
||||
///
|
||||
/// The format of the serialized data is compatible with
|
||||
/// [`CommitmentTree`](merkle_tree::CommitmentTree) from `librustzcash` and not
|
||||
/// with [`Frontier`](bridgetree::Frontier) from the crate
|
||||
/// [`incrementalmerkletree`]. Zebra follows the former format in order to stay
|
||||
/// consistent with `zcashd` in RPCs. Note that [`NoteCommitmentTree`] itself is
|
||||
/// represented as [`Frontier`](bridgetree::Frontier).
|
||||
///
|
||||
/// The formats are semantically equivalent. The primary difference between them
|
||||
/// is that in [`Frontier`](bridgetree::Frontier), the vector of parents is
|
||||
/// dense (we know where the gaps are from the position of the leaf in the
|
||||
/// overall tree); whereas in [`CommitmentTree`](merkle_tree::CommitmentTree),
|
||||
/// the vector of parent hashes is sparse with [`None`] values in the gaps.
|
||||
///
|
||||
/// The sparse format, used in this implementation, allows representing invalid
|
||||
/// commitment trees while the dense format allows representing only valid
|
||||
/// commitment trees.
|
||||
///
|
||||
/// It is likely that the dense format will be used in future RPCs, in which
|
||||
/// case the current implementation will have to change and use the format
|
||||
/// compatible with [`Frontier`](bridgetree::Frontier) instead.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
|
||||
pub struct SerializedTree(Vec<u8>);
|
||||
|
||||
impl From<&NoteCommitmentTree> for SerializedTree {
|
||||
fn from(tree: &NoteCommitmentTree) -> Self {
|
||||
let mut serialized_tree = vec![];
|
||||
|
||||
// Skip the serialization of empty trees.
|
||||
//
|
||||
// Note: This ensures compatibility with `zcashd` in the
|
||||
// [`z_gettreestate`][1] RPC.
|
||||
//
|
||||
// [1]: https://zcash.github.io/rpc/z_gettreestate.html
|
||||
if tree.inner == bridgetree::Frontier::empty() {
|
||||
return Self(serialized_tree);
|
||||
}
|
||||
|
||||
// Convert the note commitment tree from
|
||||
// [`Frontier`](bridgetree::Frontier) to
|
||||
// [`CommitmentTree`](merkle_tree::CommitmentTree).
|
||||
let tree = CommitmentTree::from_frontier(&tree.inner);
|
||||
tree.write(&mut serialized_tree)
|
||||
.expect("note commitment tree should be serializable");
|
||||
Self(serialized_tree)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<Arc<NoteCommitmentTree>>> for SerializedTree {
|
||||
fn from(maybe_tree: Option<Arc<NoteCommitmentTree>>) -> Self {
|
||||
match maybe_tree {
|
||||
Some(tree) => tree.as_ref().into(),
|
||||
None => Self(Vec::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for SerializedTree {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,18 +17,27 @@ use std::{
|
|||
hash::{Hash, Hasher},
|
||||
io,
|
||||
ops::Deref,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use bitvec::prelude::*;
|
||||
use incrementalmerkletree::{bridgetree, Frontier};
|
||||
|
||||
use incrementalmerkletree::{
|
||||
bridgetree::{self, Leaf},
|
||||
Frontier,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use thiserror::Error;
|
||||
use zcash_encoding::{Optional, Vector};
|
||||
use zcash_primitives::merkle_tree::{self, Hashable};
|
||||
|
||||
use super::commitment::pedersen_hashes::pedersen_hash;
|
||||
|
||||
use crate::serialization::{
|
||||
serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize,
|
||||
};
|
||||
|
||||
pub(super) const MERKLE_DEPTH: usize = 32;
|
||||
|
||||
/// MerkleCRH^Sapling Hash Function
|
||||
|
@ -157,9 +166,45 @@ impl ZcashDeserialize for Root {
|
|||
///
|
||||
/// Note that it's handled as a byte buffer and not a point coordinate (jubjub::Fq)
|
||||
/// because that's how the spec handles the MerkleCRH^Sapling function inputs and outputs.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
struct Node([u8; 32]);
|
||||
|
||||
/// Required to convert [`NoteCommitmentTree`] into [`SerializedTree`].
|
||||
///
|
||||
/// Zebra stores Sapling note commitment trees as [`Frontier`][1]s while the
|
||||
/// [`z_gettreestate`][2] RPC requires [`CommitmentTree`][3]s. Implementing
|
||||
/// [`merkle_tree::Hashable`] for [`Node`]s allows the conversion.
|
||||
///
|
||||
/// [1]: bridgetree::Frontier
|
||||
/// [2]: https://zcash.github.io/rpc/z_gettreestate.html
|
||||
/// [3]: merkle_tree::CommitmentTree
|
||||
impl merkle_tree::Hashable for Node {
|
||||
fn read<R: io::Read>(mut reader: R) -> io::Result<Self> {
|
||||
let mut node = [0u8; 32];
|
||||
reader.read_exact(&mut node)?;
|
||||
Ok(Self(node))
|
||||
}
|
||||
|
||||
fn write<W: io::Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
writer.write_all(self.0.as_ref())
|
||||
}
|
||||
|
||||
fn combine(level: usize, a: &Self, b: &Self) -> Self {
|
||||
let level = u8::try_from(level).expect("level must fit into u8");
|
||||
let layer = (MERKLE_DEPTH - 1) as u8 - level;
|
||||
Self(merkle_crh_sapling(layer, a.0, b.0))
|
||||
}
|
||||
|
||||
fn blank() -> Self {
|
||||
Self(NoteCommitmentTree::uncommitted())
|
||||
}
|
||||
|
||||
fn empty_root(level: usize) -> Self {
|
||||
let layer_below = MERKLE_DEPTH - level;
|
||||
Self(EMPTY_ROOTS[layer_below])
|
||||
}
|
||||
}
|
||||
|
||||
impl incrementalmerkletree::Hashable for Node {
|
||||
fn empty_leaf() -> Self {
|
||||
Self(NoteCommitmentTree::uncommitted())
|
||||
|
@ -217,7 +262,7 @@ pub enum NoteCommitmentTreeError {
|
|||
/// Sapling Incremental Note Commitment Tree.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct NoteCommitmentTree {
|
||||
/// The tree represented as a Frontier.
|
||||
/// The tree represented as a [`Frontier`](bridgetree::Frontier).
|
||||
///
|
||||
/// A Frontier is a subset of the tree that allows to fully specify it.
|
||||
/// It consists of nodes along the rightmost (newer) branch of the tree that
|
||||
|
@ -226,8 +271,9 @@ pub struct NoteCommitmentTree {
|
|||
///
|
||||
/// # Consensus
|
||||
///
|
||||
/// > [Sapling onward] A block MUST NOT add Sapling note commitments that would result in the Sapling note
|
||||
/// > commitment tree exceeding its capacity of 2^(MerkleDepth^Sapling) leaf nodes.
|
||||
/// > [Sapling onward] A block MUST NOT add Sapling note commitments that
|
||||
/// > would result in the Sapling note commitment tree exceeding its capacity
|
||||
/// > of 2^(MerkleDepth^Sapling) leaf nodes.
|
||||
///
|
||||
/// <https://zips.z.cash/protocol/protocol.pdf#merkletree>
|
||||
///
|
||||
|
@ -236,18 +282,19 @@ pub struct NoteCommitmentTree {
|
|||
|
||||
/// A cached root of the tree.
|
||||
///
|
||||
/// Every time the root is computed by [`Self::root`] it is cached here,
|
||||
/// and the cached value will be returned by [`Self::root`] until the tree is
|
||||
/// changed by [`Self::append`]. This greatly increases performance
|
||||
/// because it avoids recomputing the root when the tree does not change
|
||||
/// between blocks. In the finalized state, the tree is read from
|
||||
/// disk for every block processed, which would also require recomputing
|
||||
/// the root even if it has not changed (note that the cached root is
|
||||
/// serialized with the tree). This is particularly important since we decided
|
||||
/// to instantiate the trees from the genesis block, for simplicity.
|
||||
/// Every time the root is computed by [`Self::root`] it is cached here, and
|
||||
/// the cached value will be returned by [`Self::root`] until the tree is
|
||||
/// changed by [`Self::append`]. This greatly increases performance because
|
||||
/// it avoids recomputing the root when the tree does not change between
|
||||
/// blocks. In the finalized state, the tree is read from disk for every
|
||||
/// block processed, which would also require recomputing the root even if
|
||||
/// it has not changed (note that the cached root is serialized with the
|
||||
/// tree). This is particularly important since we decided to instantiate
|
||||
/// the trees from the genesis block, for simplicity.
|
||||
///
|
||||
/// We use a [`RwLock`] for this cache, because it is only written once per tree update.
|
||||
/// Each tree has its own cached root, a new lock is created for each clone.
|
||||
/// We use a [`RwLock`] for this cache, because it is only written once per
|
||||
/// tree update. Each tree has its own cached root, a new lock is created
|
||||
/// for each clone.
|
||||
cached_root: std::sync::RwLock<Option<Root>>,
|
||||
}
|
||||
|
||||
|
@ -305,7 +352,7 @@ impl NoteCommitmentTree {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the Jubjub-based Pedersen hash of root node of this merkle tree of
|
||||
/// Gets the Jubjub-based Pedersen hash of root node of this merkle tree of
|
||||
/// note commitments.
|
||||
pub fn hash(&self) -> [u8; 32] {
|
||||
self.root().into()
|
||||
|
@ -320,7 +367,7 @@ impl NoteCommitmentTree {
|
|||
jubjub::Fq::one().to_bytes()
|
||||
}
|
||||
|
||||
/// Count of note commitments added to the tree.
|
||||
/// Counts of note commitments added to the tree.
|
||||
///
|
||||
/// For Sapling, the tree is capped at 2^32.
|
||||
pub fn count(&self) -> u64 {
|
||||
|
@ -361,7 +408,7 @@ impl PartialEq for NoteCommitmentTree {
|
|||
}
|
||||
|
||||
impl From<Vec<jubjub::Fq>> for NoteCommitmentTree {
|
||||
/// Compute the tree from a whole bunch of note commitments at once.
|
||||
/// Computes the tree from a whole bunch of note commitments at once.
|
||||
fn from(values: Vec<jubjub::Fq>) -> Self {
|
||||
let mut tree = Self::default();
|
||||
|
||||
|
@ -376,3 +423,131 @@ impl From<Vec<jubjub::Fq>> for NoteCommitmentTree {
|
|||
tree
|
||||
}
|
||||
}
|
||||
|
||||
/// A serialized Sapling note commitment tree.
|
||||
///
|
||||
/// The format of the serialized data is compatible with
|
||||
/// [`CommitmentTree`](merkle_tree::CommitmentTree) from `librustzcash` and not
|
||||
/// with [`Frontier`](bridgetree::Frontier) from the crate
|
||||
/// [`incrementalmerkletree`]. Zebra follows the former format in order to stay
|
||||
/// consistent with `zcashd` in RPCs. Note that [`NoteCommitmentTree`] itself is
|
||||
/// represented as [`Frontier`](bridgetree::Frontier).
|
||||
///
|
||||
/// The formats are semantically equivalent. The primary difference between them
|
||||
/// is that in [`Frontier`](bridgetree::Frontier), the vector of parents is
|
||||
/// dense (we know where the gaps are from the position of the leaf in the
|
||||
/// overall tree); whereas in [`CommitmentTree`](merkle_tree::CommitmentTree),
|
||||
/// the vector of parent hashes is sparse with [`None`] values in the gaps.
|
||||
///
|
||||
/// The sparse format, used in this implementation, allows representing invalid
|
||||
/// commitment trees while the dense format allows representing only valid
|
||||
/// commitment trees.
|
||||
///
|
||||
/// It is likely that the dense format will be used in future RPCs, in which
|
||||
/// case the current implementation will have to change and use the format
|
||||
/// compatible with [`Frontier`](bridgetree::Frontier) instead.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
|
||||
pub struct SerializedTree(Vec<u8>);
|
||||
|
||||
impl From<&NoteCommitmentTree> for SerializedTree {
|
||||
fn from(tree: &NoteCommitmentTree) -> Self {
|
||||
let mut serialized_tree = vec![];
|
||||
|
||||
// Convert the note commitment tree represented as a frontier into the
|
||||
// format compatible with `zcashd`.
|
||||
//
|
||||
// `librustzcash` has a function [`from_frontier()`][1], which returns a
|
||||
// commitment tree in the sparse format. However, the returned tree
|
||||
// always contains [`MERKLE_DEPTH`] parent nodes, even though some
|
||||
// trailing parents are empty. Such trees are incompatible with Sapling
|
||||
// commitment trees returned by `zcashd` because `zcashd` returns
|
||||
// Sapling commitment trees without empty trailing parents. For this
|
||||
// reason, Zebra implements its own conversion between the dense and
|
||||
// sparse formats for Sapling.
|
||||
//
|
||||
// [1]: <https://github.com/zcash/librustzcash/blob/a63a37a/zcash_primitives/src/merkle_tree.rs#L125>
|
||||
if let Some(frontier) = tree.inner.value() {
|
||||
let (left_leaf, right_leaf) = match frontier.leaf() {
|
||||
Leaf::Left(left_value) => (Some(left_value), None),
|
||||
Leaf::Right(left_value, right_value) => (Some(left_value), Some(right_value)),
|
||||
};
|
||||
|
||||
// Ommers are siblings of parent nodes along the branch from the
|
||||
// most recent leaf to the root of the tree.
|
||||
let mut ommers_iter = frontier.ommers().iter();
|
||||
|
||||
// Set bits in the binary representation of the position indicate
|
||||
// the presence of ommers along the branch from the most recent leaf
|
||||
// node to the root of the tree, except for the lowest bit.
|
||||
let mut position: usize = frontier.position().into();
|
||||
|
||||
// The lowest bit does not indicate the presence of any ommers. We
|
||||
// clear it so that we can test if there are no set bits left in
|
||||
// [`position`].
|
||||
position &= !1;
|
||||
|
||||
// Run through the bits of [`position`], and push an ommer for each
|
||||
// set bit, or `None` otherwise. In contrast to the 'zcashd' code
|
||||
// linked above, we want to skip any trailing `None` parents at the
|
||||
// top of the tree. To do that, we clear the bits as we go through
|
||||
// them, and break early if the remaining bits are all zero (i.e.
|
||||
// [`position`] is zero).
|
||||
let mut parents = vec![];
|
||||
for i in 1..MERKLE_DEPTH {
|
||||
// Test each bit in [`position`] individually. Don't test the
|
||||
// lowest bit since it doesn't actually indicate the position of
|
||||
// any ommer.
|
||||
let bit_mask = 1 << i;
|
||||
|
||||
if position & bit_mask == 0 {
|
||||
parents.push(None);
|
||||
} else {
|
||||
parents.push(ommers_iter.next());
|
||||
// Clear the set bit so that we can test if there are no set
|
||||
// bits left.
|
||||
position &= !bit_mask;
|
||||
// If there are no set bits left, exit early so that there
|
||||
// are no empty trailing parent nodes in the serialized
|
||||
// tree.
|
||||
if position == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize the converted note commitment tree.
|
||||
|
||||
Optional::write(&mut serialized_tree, left_leaf, |tree, leaf| {
|
||||
leaf.write(tree)
|
||||
})
|
||||
.expect("A leaf in a note commitment tree should be serializable");
|
||||
|
||||
Optional::write(&mut serialized_tree, right_leaf, |tree, leaf| {
|
||||
leaf.write(tree)
|
||||
})
|
||||
.expect("A leaf in a note commitment tree should be serializable");
|
||||
|
||||
Vector::write(&mut serialized_tree, &parents, |tree, parent| {
|
||||
Optional::write(tree, *parent, |tree, parent| parent.write(tree))
|
||||
})
|
||||
.expect("Parent nodes in a note commitment tree should be serializable");
|
||||
}
|
||||
|
||||
Self(serialized_tree)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<Arc<NoteCommitmentTree>>> for SerializedTree {
|
||||
fn from(maybe_tree: Option<Arc<NoteCommitmentTree>>) -> Self {
|
||||
match maybe_tree {
|
||||
Some(tree) => tree.as_ref().into(),
|
||||
None => Self(vec![]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for SerializedTree {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,9 @@ use tracing::Instrument;
|
|||
use zebra_chain::{
|
||||
block::{self, Height, SerializedBlock},
|
||||
chain_tip::ChainTip,
|
||||
orchard,
|
||||
parameters::{ConsensusBranchId, Network, NetworkUpgrade},
|
||||
sapling,
|
||||
serialization::{SerializationError, ZcashDeserialize},
|
||||
transaction::{self, SerializedTransaction, Transaction, UnminedTx},
|
||||
transparent::{self, Address},
|
||||
|
@ -149,6 +151,23 @@ pub trait Rpc {
|
|||
#[rpc(name = "getrawmempool")]
|
||||
fn get_raw_mempool(&self) -> BoxFuture<Result<Vec<String>>>;
|
||||
|
||||
/// Returns information about the given block's Sapling & Orchard tree state.
|
||||
///
|
||||
/// zcashd reference: [`z_gettreestate`](https://zcash.github.io/rpc/z_gettreestate.html)
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `hash | height`: (string, required) The block hash or height.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// The zcashd doc reference above says that the parameter "`height` can be
|
||||
/// negative where -1 is the last known valid block". On the other hand,
|
||||
/// `lightwalletd` only uses positive heights, so Zebra does not support
|
||||
/// negative heights.
|
||||
#[rpc(name = "z_gettreestate")]
|
||||
fn z_get_treestate(&self, hash_or_height: String) -> BoxFuture<Result<GetTreestate>>;
|
||||
|
||||
/// Returns the raw transaction data, as a [`GetRawTransaction`] JSON string or structure.
|
||||
///
|
||||
/// zcashd reference: [`getrawtransaction`](https://zcash.github.io/rpc/getrawtransaction.html)
|
||||
|
@ -660,6 +679,120 @@ where
|
|||
.boxed()
|
||||
}
|
||||
|
||||
fn z_get_treestate(&self, hash_or_height: String) -> BoxFuture<Result<GetTreestate>> {
|
||||
let mut state = self.state.clone();
|
||||
|
||||
async move {
|
||||
// Convert the [`hash_or_height`] string into an actual hash or height.
|
||||
let hash_or_height = hash_or_height
|
||||
.parse()
|
||||
.map_err(|error: SerializationError| Error {
|
||||
code: ErrorCode::ServerError(0),
|
||||
message: error.to_string(),
|
||||
data: None,
|
||||
})?;
|
||||
|
||||
// Fetch the block referenced by [`hash_or_height`] from the state.
|
||||
|
||||
// TODO: If this RPC is called a lot, just get the block header,
|
||||
// rather than the whole block.
|
||||
let block_request = zebra_state::ReadRequest::Block(hash_or_height);
|
||||
let block_response = state
|
||||
.ready()
|
||||
.and_then(|service| service.call(block_request))
|
||||
.await
|
||||
.map_err(|error| Error {
|
||||
code: ErrorCode::ServerError(0),
|
||||
message: error.to_string(),
|
||||
data: None,
|
||||
})?;
|
||||
|
||||
// The block hash, height, and time are all required fields in the
|
||||
// RPC response. For this reason, we throw an error early if the
|
||||
// state didn't return the requested block so that we prevent
|
||||
// further state queries.
|
||||
let block = match block_response {
|
||||
zebra_state::ReadResponse::Block(Some(block)) => block,
|
||||
zebra_state::ReadResponse::Block(None) => {
|
||||
return Err(Error {
|
||||
code: ErrorCode::ServerError(0),
|
||||
message: "the requested block was not found".to_string(),
|
||||
data: None,
|
||||
})
|
||||
}
|
||||
_ => unreachable!("unmatched response to a block request"),
|
||||
};
|
||||
|
||||
// Fetch the Sapling & Orchard treestates referenced by
|
||||
// [`hash_or_height`] from the state.
|
||||
|
||||
let sapling_request = zebra_state::ReadRequest::SaplingTree(hash_or_height);
|
||||
let sapling_response = state
|
||||
.ready()
|
||||
.and_then(|service| service.call(sapling_request))
|
||||
.await
|
||||
.map_err(|error| Error {
|
||||
code: ErrorCode::ServerError(0),
|
||||
message: error.to_string(),
|
||||
data: None,
|
||||
})?;
|
||||
|
||||
let orchard_request = zebra_state::ReadRequest::OrchardTree(hash_or_height);
|
||||
let orchard_response = state
|
||||
.ready()
|
||||
.and_then(|service| service.call(orchard_request))
|
||||
.await
|
||||
.map_err(|error| Error {
|
||||
code: ErrorCode::ServerError(0),
|
||||
message: error.to_string(),
|
||||
data: None,
|
||||
})?;
|
||||
|
||||
// We've got all the data we need for the RPC response, so we
|
||||
// assemble the response.
|
||||
|
||||
let hash = block.hash();
|
||||
|
||||
let height = block
|
||||
.coinbase_height()
|
||||
.expect("verified blocks have a valid height");
|
||||
|
||||
let time = u32::try_from(block.header.time.timestamp())
|
||||
.expect("Timestamps of valid blocks always fit into u32.");
|
||||
|
||||
let sapling_tree = match sapling_response {
|
||||
zebra_state::ReadResponse::SaplingTree(maybe_tree) => {
|
||||
sapling::tree::SerializedTree::from(maybe_tree)
|
||||
}
|
||||
_ => unreachable!("unmatched response to a sapling tree request"),
|
||||
};
|
||||
|
||||
let orchard_tree = match orchard_response {
|
||||
zebra_state::ReadResponse::OrchardTree(maybe_tree) => {
|
||||
orchard::tree::SerializedTree::from(maybe_tree)
|
||||
}
|
||||
_ => unreachable!("unmatched response to an orchard tree request"),
|
||||
};
|
||||
|
||||
Ok(GetTreestate {
|
||||
hash,
|
||||
height,
|
||||
time,
|
||||
sapling: Treestate {
|
||||
commitments: Commitments {
|
||||
final_state: sapling_tree,
|
||||
},
|
||||
},
|
||||
orchard: Treestate {
|
||||
commitments: Commitments {
|
||||
final_state: orchard_tree,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn get_address_tx_ids(
|
||||
&self,
|
||||
request: GetAddressTxIdsRequest,
|
||||
|
@ -947,6 +1080,65 @@ pub struct GetBlock(#[serde(with = "hex")] SerializedBlock);
|
|||
#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub struct GetBestBlockHash(#[serde(with = "hex")] block::Hash);
|
||||
|
||||
/// Response to a `z_gettreestate` RPC request.
|
||||
///
|
||||
/// Contains the hex-encoded Sapling & Orchard note commitment trees, and their
|
||||
/// corresponding [`block::Hash`], [`Height`], and block time.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
|
||||
pub struct GetTreestate {
|
||||
/// The block hash corresponding to the treestate, hex-encoded.
|
||||
#[serde(with = "hex")]
|
||||
hash: block::Hash,
|
||||
|
||||
/// The block height corresponding to the treestate, numeric.
|
||||
height: Height,
|
||||
|
||||
/// Unix time when the block corresponding to the treestate was mined,
|
||||
/// numeric.
|
||||
///
|
||||
/// UTC seconds since the Unix 1970-01-01 epoch.
|
||||
time: u32,
|
||||
|
||||
/// A treestate containing a Sapling note commitment tree, hex-encoded.
|
||||
#[serde(skip_serializing_if = "Treestate::is_empty")]
|
||||
sapling: Treestate<sapling::tree::SerializedTree>,
|
||||
|
||||
/// A treestate containing an Orchard note commitment tree, hex-encoded.
|
||||
#[serde(skip_serializing_if = "Treestate::is_empty")]
|
||||
orchard: Treestate<orchard::tree::SerializedTree>,
|
||||
}
|
||||
|
||||
/// A treestate that is included in the [`z_gettreestate`][1] RPC response.
|
||||
///
|
||||
/// [1]: https://zcash.github.io/rpc/z_gettreestate.html
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
|
||||
struct Treestate<Tree: AsRef<[u8]>> {
|
||||
/// Contains an Orchard or Sapling serialized note commitment tree,
|
||||
/// hex-encoded.
|
||||
commitments: Commitments<Tree>,
|
||||
}
|
||||
|
||||
/// A wrapper that contains either an Orchard or Sapling note commitment tree.
|
||||
///
|
||||
/// Note that in the original [`z_gettreestate`][1] RPC, [`Commitments`] also
|
||||
/// contains the field `finalRoot`. Zebra does *not* use this field.
|
||||
///
|
||||
/// [1]: https://zcash.github.io/rpc/z_gettreestate.html
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
|
||||
struct Commitments<Tree: AsRef<[u8]>> {
|
||||
/// Orchard or Sapling serialized note commitment tree, hex-encoded.
|
||||
#[serde(with = "hex")]
|
||||
#[serde(rename = "finalState")]
|
||||
final_state: Tree,
|
||||
}
|
||||
|
||||
impl<Tree: AsRef<[u8]>> Treestate<Tree> {
|
||||
/// Returns `true` if there's no serialized commitment tree.
|
||||
fn is_empty(&self) -> bool {
|
||||
self.commitments.final_state.as_ref().is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Response to a `getrawtransaction` RPC request.
|
||||
///
|
||||
/// See the notes for the [`Rpc::get_raw_transaction` method].
|
||||
|
|
|
@ -9,6 +9,7 @@ use std::{
|
|||
use zebra_chain::{
|
||||
amount::NegativeAllowed,
|
||||
block::{self, Block},
|
||||
serialization::SerializationError,
|
||||
transaction,
|
||||
transparent::{self, utxos_from_ordered_utxos},
|
||||
value_balance::{ValueBalance, ValueBalanceError},
|
||||
|
@ -57,6 +58,19 @@ impl From<block::Height> for HashOrHeight {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for HashOrHeight {
|
||||
type Err = SerializationError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
s.parse()
|
||||
.map(Self::Hash)
|
||||
.or_else(|_| s.parse().map(Self::Height))
|
||||
.map_err(|_| {
|
||||
SerializationError::Parse("could not convert the input string to a hash or height")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A block which has undergone semantic validation and has been prepared for
|
||||
/// contextual validation.
|
||||
///
|
||||
|
@ -444,7 +458,25 @@ pub enum ReadRequest {
|
|||
/// Returns an [`Amount`] with the total balance of the set of addresses.
|
||||
AddressBalance(HashSet<transparent::Address>),
|
||||
|
||||
/// Looks up transaction hashes that sent or received from addresses,
|
||||
/// Looks up a Sapling note commitment tree either by a hash or height.
|
||||
///
|
||||
/// Returns
|
||||
///
|
||||
/// * [`ReadResponse::SaplingTree(Some(Arc<NoteCommitmentTree>))`](crate::ReadResponse::SaplingTree)
|
||||
/// if the corresponding block contains a Sapling note commitment tree.
|
||||
/// * [`ReadResponse::SaplingTree(None)`](crate::ReadResponse::SaplingTree) otherwise.
|
||||
SaplingTree(HashOrHeight),
|
||||
|
||||
/// Looks up an Orchard note commitment tree either by a hash or height.
|
||||
///
|
||||
/// Returns
|
||||
///
|
||||
/// * [`ReadResponse::OrchardTree(Some(Arc<NoteCommitmentTree>))`](crate::ReadResponse::OrchardTree)
|
||||
/// if the corresponding block contains a Sapling note commitment tree.
|
||||
/// * [`ReadResponse::OrchardTree(None)`](crate::ReadResponse::OrchardTree) otherwise.
|
||||
OrchardTree(HashOrHeight),
|
||||
|
||||
/// Looks up transaction hashes that were sent or received from addresses,
|
||||
/// in an inclusive blockchain height range.
|
||||
///
|
||||
/// Returns
|
||||
|
|
|
@ -5,6 +5,7 @@ use std::{collections::BTreeMap, sync::Arc};
|
|||
use zebra_chain::{
|
||||
amount::{Amount, NonNegative},
|
||||
block::{self, Block},
|
||||
orchard, sapling,
|
||||
transaction::{self, Transaction},
|
||||
transparent,
|
||||
};
|
||||
|
@ -49,14 +50,28 @@ pub enum Response {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
/// A response to a read-only [`ReadStateService`] [`ReadRequest`].
|
||||
/// A response to a read-only [`ReadStateService`](crate::ReadStateService)'s
|
||||
/// [`ReadRequest`](crate::ReadRequest).
|
||||
pub enum ReadResponse {
|
||||
/// Response to [`ReadRequest::Block`] with the specified block.
|
||||
/// Response to [`ReadRequest::Block`](crate::ReadRequest::Block) with the
|
||||
/// specified block.
|
||||
Block(Option<Arc<Block>>),
|
||||
|
||||
/// Response to [`ReadRequest::Transaction`] with the specified transaction.
|
||||
/// Response to
|
||||
/// [`ReadRequest::Transaction`](crate::ReadRequest::Transaction) with the
|
||||
/// specified transaction.
|
||||
Transaction(Option<(Arc<Transaction>, block::Height)>),
|
||||
|
||||
/// Response to
|
||||
/// [`ReadRequest::SaplingTree`](crate::ReadRequest::SaplingTree) with the
|
||||
/// specified Sapling note commitment tree.
|
||||
SaplingTree(Option<Arc<sapling::tree::NoteCommitmentTree>>),
|
||||
|
||||
/// Response to
|
||||
/// [`ReadRequest::OrchardTree`](crate::ReadRequest::OrchardTree) with the
|
||||
/// specified Orchard note commitment tree.
|
||||
OrchardTree(Option<Arc<orchard::tree::NoteCommitmentTree>>),
|
||||
|
||||
/// Response to [`ReadRequest::AddressBalance`] with the total balance of the addresses.
|
||||
AddressBalance(Amount<NonNegative>),
|
||||
|
||||
|
|
|
@ -940,15 +940,6 @@ impl Service<ReadRequest> for ReadStateService {
|
|||
#[instrument(name = "read_state", skip(self))]
|
||||
fn call(&mut self, req: ReadRequest) -> Self::Future {
|
||||
match req {
|
||||
// TODO: implement these new ReadRequests for lightwalletd, as part of these tickets
|
||||
|
||||
// z_get_tree_state (#3156)
|
||||
|
||||
// depends on transparent address indexes (#3150)
|
||||
// get_address_tx_ids (#3147)
|
||||
// get_address_balance (#3157)
|
||||
// get_address_utxos (#3158)
|
||||
|
||||
// Used by get_block RPC.
|
||||
ReadRequest::Block(hash_or_height) => {
|
||||
metrics::counter!(
|
||||
|
@ -992,6 +983,46 @@ impl Service<ReadRequest> for ReadStateService {
|
|||
.boxed()
|
||||
}
|
||||
|
||||
ReadRequest::SaplingTree(hash_or_height) => {
|
||||
metrics::counter!(
|
||||
"state.requests",
|
||||
1,
|
||||
"service" => "read_state",
|
||||
"type" => "sapling_tree",
|
||||
);
|
||||
|
||||
let state = self.clone();
|
||||
|
||||
async move {
|
||||
let sapling_tree = state.best_chain_receiver.with_watch_data(|best_chain| {
|
||||
read::sapling_tree(best_chain, &state.db, hash_or_height)
|
||||
});
|
||||
|
||||
Ok(ReadResponse::SaplingTree(sapling_tree))
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
ReadRequest::OrchardTree(hash_or_height) => {
|
||||
metrics::counter!(
|
||||
"state.requests",
|
||||
1,
|
||||
"service" => "read_state",
|
||||
"type" => "orchard_tree",
|
||||
);
|
||||
|
||||
let state = self.clone();
|
||||
|
||||
async move {
|
||||
let orchard_tree = state.best_chain_receiver.with_watch_data(|best_chain| {
|
||||
read::orchard_tree(best_chain, &state.db, hash_or_height)
|
||||
});
|
||||
|
||||
Ok(ReadResponse::OrchardTree(orchard_tree))
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
// For the get_address_tx_ids RPC.
|
||||
ReadRequest::TransactionIdsByAddresses {
|
||||
addresses,
|
||||
|
|
|
@ -20,7 +20,9 @@ use zebra_chain::{
|
|||
amount::NonNegative,
|
||||
block::{self, Block, Height},
|
||||
history_tree::HistoryTree,
|
||||
orchard,
|
||||
parameters::{Network, GENESIS_PREVIOUS_BLOCK_HASH},
|
||||
sapling,
|
||||
serialization::TrustedPreallocate,
|
||||
transaction::{self, Transaction},
|
||||
transparent,
|
||||
|
@ -111,6 +113,34 @@ impl ZebraDb {
|
|||
}))
|
||||
}
|
||||
|
||||
/// Returns the Sapling
|
||||
/// [`NoteCommitmentTree`](sapling::tree::NoteCommitmentTree) specified by a
|
||||
/// hash or height, if it exists in the finalized `db`.
|
||||
pub fn sapling_tree(
|
||||
&self,
|
||||
hash_or_height: HashOrHeight,
|
||||
) -> Option<Arc<sapling::tree::NoteCommitmentTree>> {
|
||||
let height = hash_or_height.height_or_else(|hash| self.height(hash))?;
|
||||
|
||||
let sapling_tree_handle = self.db.cf_handle("sapling_note_commitment_tree").unwrap();
|
||||
|
||||
self.db.zs_get(&sapling_tree_handle, &height)
|
||||
}
|
||||
|
||||
/// Returns the Orchard
|
||||
/// [`NoteCommitmentTree`](orchard::tree::NoteCommitmentTree) specified by a
|
||||
/// hash or height, if it exists in the finalized `db`.
|
||||
pub fn orchard_tree(
|
||||
&self,
|
||||
hash_or_height: HashOrHeight,
|
||||
) -> Option<Arc<orchard::tree::NoteCommitmentTree>> {
|
||||
let height = hash_or_height.height_or_else(|hash| self.height(hash))?;
|
||||
|
||||
let orchard_tree_handle = self.db.cf_handle("orchard_note_commitment_tree").unwrap();
|
||||
|
||||
self.db.zs_get(&orchard_tree_handle, &height)
|
||||
}
|
||||
|
||||
// Read tip block methods
|
||||
|
||||
/// Returns the hash of the current finalized tip block.
|
||||
|
|
|
@ -394,6 +394,32 @@ impl Chain {
|
|||
)
|
||||
}
|
||||
|
||||
/// Returns the Sapling
|
||||
/// [`NoteCommitmentTree`](sapling::tree::NoteCommitmentTree) specified by a
|
||||
/// hash or height, if it exists in the non-finalized `chain`.
|
||||
pub fn sapling_tree(
|
||||
&self,
|
||||
hash_or_height: HashOrHeight,
|
||||
) -> Option<&sapling::tree::NoteCommitmentTree> {
|
||||
let height =
|
||||
hash_or_height.height_or_else(|hash| self.height_by_hash.get(&hash).cloned())?;
|
||||
|
||||
self.sapling_trees_by_height.get(&height)
|
||||
}
|
||||
|
||||
/// Returns the Orchard
|
||||
/// [`NoteCommitmentTree`](orchard::tree::NoteCommitmentTree) specified by a
|
||||
/// hash or height, if it exists in the non-finalized `chain`.
|
||||
pub fn orchard_tree(
|
||||
&self,
|
||||
hash_or_height: HashOrHeight,
|
||||
) -> Option<&orchard::tree::NoteCommitmentTree> {
|
||||
let height =
|
||||
hash_or_height.height_or_else(|hash| self.height_by_hash.get(&hash).cloned())?;
|
||||
|
||||
self.orchard_trees_by_height.get(&height)
|
||||
}
|
||||
|
||||
/// Returns the block hash of the tip block.
|
||||
pub fn non_finalized_tip_hash(&self) -> block::Hash {
|
||||
self.blocks
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
//! Shared state reading code.
|
||||
//!
|
||||
//! Used by [`StateService`] and [`ReadStateService`]
|
||||
//! to read from the best [`Chain`] in the [`NonFinalizedState`],
|
||||
//! and the database in the [`FinalizedState`].
|
||||
//! Used by [`StateService`](crate::StateService) and
|
||||
//! [`ReadStateService`](crate::ReadStateService) to read from the best
|
||||
//! [`Chain`] in the
|
||||
//! [`NonFinalizedState`](crate::service::non_finalized_state::NonFinalizedState),
|
||||
//! and the database in the
|
||||
//! [`FinalizedState`](crate::service::finalized_state::FinalizedState).
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet, HashSet},
|
||||
|
@ -13,7 +16,9 @@ use std::{
|
|||
use zebra_chain::{
|
||||
amount::{self, Amount, NegativeAllowed, NonNegative},
|
||||
block::{self, Block, Height},
|
||||
orchard,
|
||||
parameters::Network,
|
||||
sapling,
|
||||
transaction::{self, Transaction},
|
||||
transparent,
|
||||
};
|
||||
|
@ -44,8 +49,8 @@ const FINALIZED_ADDRESS_INDEX_RETRIES: usize = 3;
|
|||
pub const ADDRESS_HEIGHTS_FULL_RANGE: RangeInclusive<Height> = Height(1)..=Height::MAX;
|
||||
|
||||
/// Returns the [`Block`] with [`block::Hash`](zebra_chain::block::Hash) or
|
||||
/// [`Height`](zebra_chain::block::Height),
|
||||
/// if it exists in the non-finalized `chain` or finalized `db`.
|
||||
/// [`Height`](zebra_chain::block::Height), if it exists in the non-finalized
|
||||
/// `chain` or finalized `db`.
|
||||
pub(crate) fn block<C>(
|
||||
chain: Option<C>,
|
||||
db: &ZebraDb,
|
||||
|
@ -56,12 +61,13 @@ where
|
|||
{
|
||||
// # Correctness
|
||||
//
|
||||
// The StateService commits blocks to the finalized state before updating the latest chain,
|
||||
// and it can commit additional blocks after we've cloned this `chain` variable.
|
||||
// The StateService commits blocks to the finalized state before updating
|
||||
// the latest chain, and it can commit additional blocks after we've cloned
|
||||
// this `chain` variable.
|
||||
//
|
||||
// Since blocks are the same in the finalized and non-finalized state,
|
||||
// we check the most efficient alternative first.
|
||||
// (`chain` is always in memory, but `db` stores blocks on disk, with a memory cache.)
|
||||
// Since blocks are the same in the finalized and non-finalized state, we
|
||||
// check the most efficient alternative first. (`chain` is always in memory,
|
||||
// but `db` stores blocks on disk, with a memory cache.)
|
||||
chain
|
||||
.as_ref()
|
||||
.and_then(|chain| chain.as_ref().block(hash_or_height))
|
||||
|
@ -69,8 +75,8 @@ where
|
|||
.or_else(|| db.block(hash_or_height))
|
||||
}
|
||||
|
||||
/// Returns the [`Transaction`] with [`transaction::Hash`],
|
||||
/// if it exists in the non-finalized `chain` or finalized `db`.
|
||||
/// Returns the [`Transaction`] with [`transaction::Hash`], if it exists in the
|
||||
/// non-finalized `chain` or finalized `db`.
|
||||
pub(crate) fn transaction<C>(
|
||||
chain: Option<C>,
|
||||
db: &ZebraDb,
|
||||
|
@ -81,12 +87,13 @@ where
|
|||
{
|
||||
// # Correctness
|
||||
//
|
||||
// The StateService commits blocks to the finalized state before updating the latest chain,
|
||||
// and it can commit additional blocks after we've cloned this `chain` variable.
|
||||
// The StateService commits blocks to the finalized state before updating
|
||||
// the latest chain, and it can commit additional blocks after we've cloned
|
||||
// this `chain` variable.
|
||||
//
|
||||
// Since transactions are the same in the finalized and non-finalized state,
|
||||
// we check the most efficient alternative first.
|
||||
// (`chain` is always in memory, but `db` stores transactions on disk, with a memory cache.)
|
||||
// we check the most efficient alternative first. (`chain` is always in
|
||||
// memory, but `db` stores transactions on disk, with a memory cache.)
|
||||
chain
|
||||
.as_ref()
|
||||
.and_then(|chain| {
|
||||
|
@ -98,6 +105,60 @@ where
|
|||
.or_else(|| db.transaction(hash))
|
||||
}
|
||||
|
||||
/// Returns the Sapling
|
||||
/// [`NoteCommitmentTree`](sapling::tree::NoteCommitmentTree) specified by a
|
||||
/// hash or height, if it exists in the non-finalized `chain` or finalized `db`.
|
||||
pub(crate) fn sapling_tree<C>(
|
||||
chain: Option<C>,
|
||||
db: &ZebraDb,
|
||||
hash_or_height: HashOrHeight,
|
||||
) -> Option<Arc<sapling::tree::NoteCommitmentTree>>
|
||||
where
|
||||
C: AsRef<Chain>,
|
||||
{
|
||||
// # Correctness
|
||||
//
|
||||
// The StateService commits blocks to the finalized state before updating
|
||||
// the latest chain, and it can commit additional blocks after we've cloned
|
||||
// this `chain` variable.
|
||||
//
|
||||
// Since sapling treestates are the same in the finalized and non-finalized
|
||||
// state, we check the most efficient alternative first. (`chain` is always
|
||||
// in memory, but `db` stores blocks on disk, with a memory cache.)
|
||||
chain
|
||||
.as_ref()
|
||||
.and_then(|chain| chain.as_ref().sapling_tree(hash_or_height).cloned())
|
||||
.map(Arc::new)
|
||||
.or_else(|| db.sapling_tree(hash_or_height))
|
||||
}
|
||||
|
||||
/// Returns the Orchard
|
||||
/// [`NoteCommitmentTree`](orchard::tree::NoteCommitmentTree) specified by a
|
||||
/// hash or height, if it exists in the non-finalized `chain` or finalized `db`.
|
||||
pub(crate) fn orchard_tree<C>(
|
||||
chain: Option<C>,
|
||||
db: &ZebraDb,
|
||||
hash_or_height: HashOrHeight,
|
||||
) -> Option<Arc<orchard::tree::NoteCommitmentTree>>
|
||||
where
|
||||
C: AsRef<Chain>,
|
||||
{
|
||||
// # Correctness
|
||||
//
|
||||
// The StateService commits blocks to the finalized state before updating
|
||||
// the latest chain, and it can commit additional blocks after we've cloned
|
||||
// this `chain` variable.
|
||||
//
|
||||
// Since orchard treestates are the same in the finalized and non-finalized
|
||||
// state, we check the most efficient alternative first. (`chain` is always
|
||||
// in memory, but `db` stores blocks on disk, with a memory cache.)
|
||||
chain
|
||||
.as_ref()
|
||||
.and_then(|chain| chain.as_ref().orchard_tree(hash_or_height).cloned())
|
||||
.map(Arc::new)
|
||||
.or_else(|| db.orchard_tree(hash_or_height))
|
||||
}
|
||||
|
||||
/// Returns the total transparent balance for the supplied [`transparent::Address`]es.
|
||||
///
|
||||
/// If the addresses do not exist in the non-finalized `chain` or finalized `db`, returns zero.
|
||||
|
|
Loading…
Reference in New Issue