build(deps): update ecc dependencies for `zcashd` 5.6.0, and create legacy state format compatibility layer (#7053)

* update ecc dependencies no serialization

* use zcash_primitives merke tree functions for serialization/deserialization in orchard and sapling trees

* some more work in sapling/orchard serialization/deserialization

* clippy

* fix doc links

* fix missing doc

* make orchard trees serializa/deserialize as they were before upgrade

* make sapling trees serialize/deserialize as they were before upgrade

* use legacy for sprout

* remove unused code

* readd snapshot tests

* repalce some code

* upgrade zcash_proofs

* remove legacy code for sprout

* fix the count method

* add root to serialize

* fixes in as_bytes and from_bytes

* use legacy code

* add todo about pow2 tests

* remove unused sprout code

* fix doc typos

* Add a recalculate_root() method to trees for tests

* Rename test tree types to make them easier to change

* Add TODOs for tests for old and new serialization formats

* fix doc typos

* add more test to note commitment trees

* fix comment

* fix leaf serializatiuon, reverse to old hashes in pow2 tests

* fix serialization

* put sapling SerializedTree code back

* put orchard SerializedTree code back

* clippy

* add duplicated dependencies until zebra_script updates

* fix a doc link

* minor cleanup

* remove todo comment from tests

* add one more check to tests

* update zebra_script

* update deny.toml

* replace custom function with library

* fix some tests

* update docs

* Remove duplicate dependencies from deny.toml

Based on the list at:
https://github.com/ZcashFoundation/zebra/actions/runs/5557139662/jobs/10150543673?pr=7053#step:5:14

---------

Co-authored-by: teor <teor@riseup.net>
This commit is contained in:
Alfredo Garcia 2023-07-17 19:06:27 -03:00 committed by GitHub
parent 3faef29d23
commit e2f010eb4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 718 additions and 340 deletions

View File

@ -77,17 +77,6 @@ dependencies = [
"cpufeatures",
]
[[package]]
name = "ahash"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom 0.2.10",
"once_cell",
"version_check",
]
[[package]]
name = "ahash"
version = "0.8.3"
@ -550,12 +539,12 @@ dependencies = [
]
[[package]]
name = "bs58"
version = "0.4.0"
name = "bridgetree"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
checksum = "3a813dadc684e4c78a4547757debd99666282545d90e4ccc3210913ed4337ad2"
dependencies = [
"sha2 0.9.9",
"incrementalmerkletree",
]
[[package]]
@ -1072,9 +1061,9 @@ dependencies = [
[[package]]
name = "cxx"
version = "1.0.94"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93"
checksum = "109308c20e8445959c2792e81871054c6a17e6976489a93d2769641a2ba5839c"
dependencies = [
"cc",
"cxxbridge-flags",
@ -1096,15 +1085,15 @@ dependencies = [
[[package]]
name = "cxxbridge-flags"
version = "1.0.94"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb"
checksum = "882074421238e84fe3b4c65d0081de34e5b323bf64555d3e61991f76eb64a7bb"
[[package]]
name = "cxxbridge-macro"
version = "1.0.94"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
checksum = "4a076022ece33e7686fb76513518e219cca4fce5750a8ae6d1ce6c0f48fd1af9"
dependencies = [
"proc-macro2 1.0.63",
"quote 1.0.29",
@ -1211,15 +1200,6 @@ dependencies = [
"subtle",
]
[[package]]
name = "directories"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs"
version = "5.0.1"
@ -1764,7 +1744,7 @@ version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash 0.8.3",
"ahash",
]
[[package]]
@ -1773,7 +1753,7 @@ version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
dependencies = [
"ahash 0.8.3",
"ahash",
"allocator-api2",
]
@ -1792,14 +1772,15 @@ dependencies = [
[[package]]
name = "hdwallet"
version = "0.3.1"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cd89bf343be18dbe1e505100e48168bbd084760e842a8fed0317d2361470193"
checksum = "5a03ba7d4c9ea41552cd4351965ff96883e629693ae85005c501bb4b9e1c48a7"
dependencies = [
"lazy_static",
"rand_core 0.6.4",
"ring",
"secp256k1",
"secp256k1 0.26.0",
"thiserror",
]
[[package]]
@ -1850,6 +1831,15 @@ dependencies = [
"digest 0.10.7",
]
[[package]]
name = "home"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "hostname"
version = "0.3.1"
@ -2045,11 +2035,11 @@ dependencies = [
[[package]]
name = "incrementalmerkletree"
version = "0.3.1"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5ad43a3f5795945459d577f6589cf62a476e92c79b75e70cd954364e14ce17b"
checksum = "2eb91780c91bfc79769006a55c49127b83e1c1a6cf2b3b149ce3f247cbe342f0"
dependencies = [
"serde",
"either",
]
[[package]]
@ -2089,7 +2079,7 @@ dependencies = [
"console",
"instant",
"number_prefix",
"portable-atomic 1.3.3",
"portable-atomic",
"unicode-width",
]
@ -2099,7 +2089,7 @@ version = "0.11.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fb7c1b80a1dfa604bb4a649a5c5aeef3d913f7c520cb42b40e534e8a61bcdfc"
dependencies = [
"ahash 0.8.3",
"ahash",
"is-terminal",
"itoa",
"log",
@ -2291,6 +2281,15 @@ dependencies = [
"subtle",
]
[[package]]
name = "known-folders"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b6f1427d9c43b1cce87434c4d9eca33f43bdbb6246a762aa823a582f74c1684"
dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -2478,26 +2477,15 @@ dependencies = [
"nonempty",
]
[[package]]
name = "metrics"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b9b8653cec6897f73b519a43fba5ee3d50f62fe9af80b428accdcc093b4a849"
dependencies = [
"ahash 0.7.6",
"metrics-macros 0.6.0",
"portable-atomic 0.3.20",
]
[[package]]
name = "metrics"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fde3af1a009ed76a778cb84fdef9e7dbbdf5775ae3e4cc1f434a6a307f6f76c5"
dependencies = [
"ahash 0.8.3",
"metrics-macros 0.7.0",
"portable-atomic 1.3.3",
"ahash",
"metrics-macros",
"portable-atomic",
]
[[package]]
@ -2510,24 +2498,13 @@ dependencies = [
"hyper",
"indexmap 1.9.3",
"ipnet",
"metrics 0.21.1",
"metrics",
"metrics-util",
"quanta",
"thiserror",
"tokio",
]
[[package]]
name = "metrics-macros"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "731f8ecebd9f3a4aa847dfe75455e4757a45da40a7793d2f0b1f9b6ed18b23f3"
dependencies = [
"proc-macro2 1.0.63",
"quote 1.0.29",
"syn 1.0.109",
]
[[package]]
name = "metrics-macros"
version = "0.7.0"
@ -2548,7 +2525,7 @@ dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
"hashbrown 0.13.2",
"metrics 0.21.1",
"metrics",
"num_cpus",
"quanta",
"sketches-ddsketch",
@ -2829,9 +2806,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "orchard"
version = "0.4.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6f418f2c25573923f81a091f38b4b19bc20f6c92b5070fb8f0711e64a2b998"
checksum = "5f4e7a52f510cb8c39e639e662a353adbaf86025478af89ae54a0551f8ca35e2"
dependencies = [
"aes",
"bitvec",
@ -3156,15 +3133,6 @@ dependencies = [
"universal-hash",
]
[[package]]
name = "portable-atomic"
version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e30165d31df606f5726b090ec7592c308a0eaf61721ff64c9a3018e344a8753e"
dependencies = [
"portable-atomic 1.3.3",
]
[[package]]
name = "portable-atomic"
version = "1.3.3"
@ -3911,10 +3879,19 @@ version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c42e6f1735c5f00f51e43e28d6634141f2bcad10931b2609ddd74a86d751260"
dependencies = [
"secp256k1-sys",
"secp256k1-sys 0.4.2",
"serde",
]
[[package]]
name = "secp256k1"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4124a35fe33ae14259c490fd70fa199a32b9ce9502f2ee6bc4f81ec06fa65894"
dependencies = [
"secp256k1-sys 0.8.1",
]
[[package]]
name = "secp256k1-sys"
version = "0.4.2"
@ -3924,6 +3901,15 @@ dependencies = [
"cc",
]
[[package]]
name = "secp256k1-sys"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e"
dependencies = [
"cc",
]
[[package]]
name = "secrecy"
version = "0.8.0"
@ -5538,6 +5524,15 @@ dependencies = [
"zeroize",
]
[[package]]
name = "xdg"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "688597db5a750e9cad4511cb94729a078e274308099a0382b5b8203bbc767fee"
dependencies = [
"home",
]
[[package]]
name = "yaml-rust"
version = "0.4.5"
@ -5549,12 +5544,12 @@ dependencies = [
[[package]]
name = "zcash_address"
version = "0.2.1"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52be35a205369d480378646bff9c9fedafd8efe8af1e0e54bb858f405883f2b2"
checksum = "8944af5c206cf2e37020ad54618e1825501b98548d35a638b73e0ec5762df8d5"
dependencies = [
"bech32",
"bs58 0.4.0",
"bs58",
"f4jumble",
"zcash_encoding",
]
@ -5582,9 +5577,9 @@ dependencies = [
[[package]]
name = "zcash_note_encryption"
version = "0.3.0"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eb2149e6cd5fbee36c5b87c601715a8c35554602f7fe84af38b636afa2db318"
checksum = "5b4580cd6cee12e44421dac43169be8d23791650816bdb34e6ddfa70ac89c1c5"
dependencies = [
"chacha20",
"chacha20poly1305",
@ -5595,9 +5590,9 @@ dependencies = [
[[package]]
name = "zcash_primitives"
version = "0.11.0"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "914d2195a478d5b63191584dff126f552751115181857b290211ec88e68acc3e"
checksum = "de1a231e6a58d3dcdd6e21d229db33d7c10f9b54d8c170e122b267f6826bb48f"
dependencies = [
"aes",
"bip0039",
@ -5621,7 +5616,7 @@ dependencies = [
"rand 0.8.5",
"rand_core 0.6.4",
"ripemd",
"secp256k1",
"secp256k1 0.26.0",
"sha2 0.10.6",
"subtle",
"zcash_address",
@ -5631,34 +5626,38 @@ dependencies = [
[[package]]
name = "zcash_proofs"
version = "0.11.0"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5c8147884952748b00aa443d36511ae2d7b49acfec74cfd39c0959fbb61ef14"
checksum = "59d2e066a717f28451a081f2ebd483ddda896cf00d572972c10979d645ffa6c4"
dependencies = [
"bellman",
"blake2b_simd",
"bls12_381",
"directories",
"group",
"home",
"incrementalmerkletree",
"jubjub",
"known-folders",
"lazy_static",
"minreq",
"rand_core 0.6.4",
"redjubjub",
"tracing",
"xdg",
"zcash_primitives",
]
[[package]]
name = "zcash_script"
version = "0.1.12"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f5d794b254efc2759d249b477f53faa751f67543a4b4d1c7a5ff7df212d4ba5"
checksum = "8c4f95043fd34d402b8d5debb0e54a28c2b84fc99591f5973b4999e9c5b01bfd"
dependencies = [
"bellman",
"bindgen",
"blake2b_simd",
"bls12_381",
"bridgetree",
"byteorder",
"cc",
"crossbeam-channel",
@ -5669,8 +5668,9 @@ dependencies = [
"jubjub",
"libc",
"memuse",
"metrics 0.20.1",
"metrics",
"orchard",
"rand 0.8.5",
"rand_core 0.6.4",
"rayon",
"subtle",
@ -5692,7 +5692,8 @@ dependencies = [
"bitvec",
"blake2b_simd",
"blake2s_simd",
"bs58 0.5.0",
"bridgetree",
"bs58",
"byteorder",
"chrono",
"color-eyre",
@ -5721,7 +5722,7 @@ dependencies = [
"reddsa",
"redjubjub",
"ripemd",
"secp256k1",
"secp256k1 0.21.3",
"serde",
"serde-big-array",
"serde_json",
@ -5760,7 +5761,7 @@ dependencies = [
"howudoin",
"jubjub",
"lazy_static",
"metrics 0.21.1",
"metrics",
"num-integer",
"once_cell",
"orchard",
@ -5804,7 +5805,7 @@ dependencies = [
"indexmap 2.0.0",
"itertools 0.11.0",
"lazy_static",
"metrics 0.21.1",
"metrics",
"num-integer",
"ordered-map",
"pin-project",
@ -5904,7 +5905,7 @@ dependencies = [
"itertools 0.11.0",
"jubjub",
"lazy_static",
"metrics 0.21.1",
"metrics",
"mset",
"once_cell",
"proptest",
@ -5998,7 +5999,7 @@ dependencies = [
"jsonrpc-core",
"lazy_static",
"log",
"metrics 0.21.1",
"metrics",
"metrics-exporter-prometheus",
"num-integer",
"once_cell",

View File

@ -70,26 +70,24 @@ skip-tree = [
# wait for zcashd and zcash_script to upgrade
# https://github.com/ZcashFoundation/zcash_script/pulls
{ name = "metrics", version = "=0.20.1" },
{ name = "sha2", version = "=0.9.9" },
# wait for ed25519-zebra, indexmap, metrics-util, and metrics to upgrade
# ed25519-zebra/hashbrown: https://github.com/ZcashFoundation/ed25519-zebra/pull/65
{ name = "ahash", version = "=0.7.6" },
# wait for indexmap, toml_edit, serde_json, tower to upgrade
{ name = "hashbrown", version = "=0.12.3" },
# wait for metrics-exporter-prometheus to upgrade
{ name = "hashbrown", version = "=0.13.2" },
# wait for zebra-chain to upgrade
{ name = "secp256k1", version = "=0.21.3" },
# wait for zebra-chain to upgrade `secp256k1`
{ name = "secp256k1-sys", version = "=0.4.2" },
# ECC crates
# wait for zcash_primitives to remove duplicated dependencies
{ name = "block-buffer", version = "=0.9.0" },
# wait for zcash_address to upgrade
{ name = "bs58", version = "=0.4.0" },
# wait for minreq and zcash_proofs to upgrade
{ name = "rustls", version = "=0.20.8" },

View File

@ -53,11 +53,12 @@ bitflags = "2.3.3"
bitflags-serde-legacy = "0.1.1"
blake2b_simd = "1.0.1"
blake2s_simd = "1.0.1"
bridgetree = "0.3.0"
bs58 = { version = "0.5.0", features = ["check"] }
byteorder = "1.4.3"
equihash = "0.2.0"
group = "0.13.0"
incrementalmerkletree = "0.3.1"
incrementalmerkletree = "0.4.0"
jubjub = "0.10.0"
lazy_static = "1.4.0"
num-integer = "0.1.45"
@ -72,11 +73,11 @@ x25519-dalek = { version = "2.0.0-rc.3", features = ["serde"] }
# ECC deps
halo2 = { package = "halo2_proofs", version = "0.3.0" }
orchard = "0.4.0"
orchard = "0.5.0"
zcash_encoding = "0.2.0"
zcash_history = "0.3.0"
zcash_note_encryption = "0.3.0"
zcash_primitives = { version = "0.11.0", features = ["transparent-inputs"] }
zcash_note_encryption = "0.4.0"
zcash_primitives = { version = "0.12.0", features = ["transparent-inputs"] }
# Time
chrono = { version = "0.4.26", default-features = false, features = ["clock", "std", "serde"] }
@ -108,7 +109,7 @@ reddsa = "0.5.0"
serde_json = { version = "1.0.100", optional = true }
# Experimental feature getblocktemplate-rpcs
zcash_address = { version = "0.2.1", optional = true }
zcash_address = { version = "0.3.0", optional = true }
# Optional testing dependencies
proptest = { version = "1.2.0", optional = true }

View File

@ -18,11 +18,12 @@ use std::{
};
use bitvec::prelude::*;
use bridgetree;
use halo2::pasta::{group::ff::PrimeField, pallas};
use incrementalmerkletree::{bridgetree, Frontier};
use incrementalmerkletree::Hashable;
use lazy_static::lazy_static;
use thiserror::Error;
use zcash_primitives::merkle_tree::{self, CommitmentTree};
use zcash_primitives::merkle_tree::{write_commitment_tree, HashSer};
use super::sinsemilla::*;
@ -30,6 +31,9 @@ use crate::serialization::{
serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize,
};
pub mod legacy;
use legacy::LegacyNoteCommitmentTree;
/// The type that is used to update the note commitment tree.
///
/// Unfortunately, this is not the same as `orchard::NoteCommitment`.
@ -164,18 +168,18 @@ impl ZcashDeserialize for Root {
/// A node of the Orchard Incremental Note Commitment Tree.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
struct Node(pallas::Base);
pub 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.
/// [`HashSer`] 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 {
/// [3]: incrementalmerkletree::frontier::CommitmentTree
impl HashSer for Node {
fn read<R: io::Read>(mut reader: R) -> io::Result<Self> {
let mut repr = [0u8; 32];
reader.read_exact(&mut repr)?;
@ -192,24 +196,9 @@ impl merkle_tree::Hashable for Node {
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 - 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::from(MERKLE_DEPTH) - level;
Self(EMPTY_ROOTS[layer_below])
}
}
impl incrementalmerkletree::Hashable for Node {
impl Hashable for Node {
fn empty_leaf() -> Self {
Self(NoteCommitmentTree::uncommitted())
}
@ -217,13 +206,13 @@ impl incrementalmerkletree::Hashable for Node {
/// Combine two nodes to generate a new node in the given level.
/// Level 0 is the layer above the leaves (layer 31).
/// Level 31 is the root (layer 0).
fn combine(level: incrementalmerkletree::Altitude, a: &Self, b: &Self) -> Self {
fn combine(level: incrementalmerkletree::Level, a: &Self, b: &Self) -> Self {
let layer = MERKLE_DEPTH - 1 - u8::from(level);
Self(merkle_crh_orchard(layer, a.0, b.0))
}
/// Return the node for the level below the given level. (A quirk of the API)
fn empty_root(level: incrementalmerkletree::Altitude) -> Self {
fn empty_root(level: incrementalmerkletree::Level) -> Self {
let layer_below = usize::from(MERKLE_DEPTH) - usize::from(level);
Self(EMPTY_ROOTS[layer_below])
}
@ -265,6 +254,8 @@ pub enum NoteCommitmentTreeError {
/// Orchard Incremental Note Commitment Tree
#[derive(Debug, Serialize, Deserialize)]
#[serde(into = "LegacyNoteCommitmentTree")]
#[serde(from = "LegacyNoteCommitmentTree")]
pub struct NoteCommitmentTree {
/// The tree represented as a Frontier.
///
@ -311,7 +302,7 @@ impl NoteCommitmentTree {
/// Returns an error if the tree is full.
#[allow(clippy::unwrap_in_result)]
pub fn append(&mut self, cm_x: NoteCommitmentUpdate) -> Result<(), NoteCommitmentTreeError> {
if self.inner.append(&cm_x.into()) {
if self.inner.append(cm_x.into()) {
// Invalidate cached root
let cached_root = self
.cached_root
@ -385,7 +376,9 @@ impl NoteCommitmentTree {
///
/// For Orchard, the tree is capped at 2^32.
pub fn count(&self) -> u64 {
self.inner.position().map_or(0, |pos| u64::from(pos) + 1)
self.inner
.value()
.map_or(0, |x| u64::from(x.position()) + 1)
}
/// Checks if the tree roots and inner data structures of `self` and `other` are equal.
@ -459,7 +452,7 @@ impl From<Vec<pallas::Base>> for NoteCommitmentTree {
/// A serialized Orchard note commitment tree.
///
/// The format of the serialized data is compatible with
/// [`CommitmentTree`](merkle_tree::CommitmentTree) from `librustzcash` and not
/// [`CommitmentTree`](incrementalmerkletree::frontier::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
@ -468,7 +461,7 @@ impl From<Vec<pallas::Base>> for NoteCommitmentTree {
/// 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),
/// overall tree); whereas in [`CommitmentTree`](incrementalmerkletree::frontier::CommitmentTree),
/// the vector of parent hashes is sparse with [`None`] values in the gaps.
///
/// The sparse format, used in this implementation, allows representing invalid
@ -498,8 +491,9 @@ impl From<&NoteCommitmentTree> for SerializedTree {
// 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)
let tree = incrementalmerkletree::frontier::CommitmentTree::from_frontier(&tree.inner);
write_commitment_tree(&tree, &mut serialized_tree)
.expect("note commitment tree should be serializable");
Self(serialized_tree)
}

View File

@ -0,0 +1,122 @@
//! Orchard serialization legacy code.
//!
//! We create a [`LegacyNoteCommitmentTree`] which is a copy of [`NoteCommitmentTree`] but where serialization and
//! deserialization can be derived.
//! To do this we create a [`LegacyFrontier`] which is a legacy `Frontier` structure that can be found in [1],
//! In order to make [`LegacyFrontier`] serializable we also have our own versions of `NonEmptyFrontier` ([`LegacyNonEmptyFrontier`]),
//! `Leaf`([`LegacyLeaf`]) and `Position`([`LegacyPosition`]) that can be found in [1] or [2].
//!
//! Conversions methods to/from [`LegacyNoteCommitmentTree`] to/from [`NoteCommitmentTree`] are defined also in this file.
//!
//! [1]: https://github.com/zcash/incrementalmerkletree/blob/incrementalmerkletree-v0.3.1/src/bridgetree.rs
//! [2]: https://github.com/zcash/incrementalmerkletree/blob/incrementalmerkletree-v0.3.1/src/lib.rs
use incrementalmerkletree::{frontier::Frontier, Position};
use super::{Node, NoteCommitmentTree, Root, MERKLE_DEPTH};
/// A legacy version of [`NoteCommitmentTree`].
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename = "NoteCommitmentTree")]
#[allow(missing_docs)]
pub struct LegacyNoteCommitmentTree {
pub inner: LegacyFrontier<Node, MERKLE_DEPTH>,
cached_root: std::sync::RwLock<Option<Root>>,
}
impl From<NoteCommitmentTree> for LegacyNoteCommitmentTree {
fn from(nct: NoteCommitmentTree) -> Self {
LegacyNoteCommitmentTree {
inner: nct.inner.into(),
cached_root: nct.cached_root,
}
}
}
impl From<LegacyNoteCommitmentTree> for NoteCommitmentTree {
fn from(legacy_nct: LegacyNoteCommitmentTree) -> Self {
NoteCommitmentTree {
inner: legacy_nct.inner.into(),
cached_root: legacy_nct.cached_root,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename = "Frontier")]
#[allow(missing_docs)]
pub struct LegacyFrontier<H, const DEPTH: u8> {
frontier: Option<LegacyNonEmptyFrontier<H>>,
}
impl From<LegacyFrontier<Node, MERKLE_DEPTH>> for Frontier<Node, MERKLE_DEPTH> {
fn from(legacy_frontier: LegacyFrontier<Node, MERKLE_DEPTH>) -> Self {
if let Some(legacy_frontier_data) = legacy_frontier.frontier {
let mut ommers = legacy_frontier_data.ommers;
let position = Position::from(
u64::try_from(legacy_frontier_data.position.0)
.expect("old `usize` always fits in `u64`"),
);
let leaf = match legacy_frontier_data.leaf {
LegacyLeaf::Left(a) => a,
LegacyLeaf::Right(a, b) => {
ommers.insert(0, a);
b
}
};
Frontier::from_parts(
position,
leaf,
ommers,
)
.expect("We should be able to construct a frontier from parts given legacy frontier is not empty")
} else {
Frontier::empty()
}
}
}
impl From<Frontier<Node, MERKLE_DEPTH>> for LegacyFrontier<Node, MERKLE_DEPTH> {
fn from(frontier: Frontier<Node, MERKLE_DEPTH>) -> Self {
if let Some(frontier_data) = frontier.value() {
let leaf_from_frontier = *frontier_data.leaf();
let mut leaf = LegacyLeaf::Left(leaf_from_frontier);
let mut ommers = frontier_data.ommers().to_vec();
let position = usize::try_from(u64::from(frontier_data.position()))
.expect("new position should fit in a `usize`");
if frontier_data.position().is_odd() {
let left = ommers.remove(0);
leaf = LegacyLeaf::Right(left, leaf_from_frontier);
}
LegacyFrontier {
frontier: Some(LegacyNonEmptyFrontier {
position: LegacyPosition(position),
leaf,
ommers: ommers.to_vec(),
}),
}
} else {
LegacyFrontier { frontier: None }
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename = "NonEmptyFrontier")]
struct LegacyNonEmptyFrontier<H> {
position: LegacyPosition,
leaf: LegacyLeaf<H>,
ommers: Vec<H>,
}
/// A set of leaves of a Merkle tree.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename = "Leaf")]
enum LegacyLeaf<A> {
Left(A),
Right(A, A),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[repr(transparent)]
struct LegacyPosition(usize);

View File

@ -18,14 +18,13 @@ use std::{
};
use bitvec::prelude::*;
use incrementalmerkletree::{
bridgetree::{self, Leaf},
Frontier,
};
use bridgetree::{self};
use incrementalmerkletree::{frontier::Frontier, Hashable};
use lazy_static::lazy_static;
use thiserror::Error;
use zcash_encoding::{Optional, Vector};
use zcash_primitives::merkle_tree::{self, Hashable};
use zcash_primitives::merkle_tree::HashSer;
use super::commitment::pedersen_hashes::pedersen_hash;
@ -33,6 +32,9 @@ use crate::serialization::{
serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize,
};
pub mod legacy;
use legacy::{LegacyLeaf, LegacyNoteCommitmentTree};
/// The type that is used to update the note commitment tree.
///
/// Unfortunately, this is not the same as `sapling::NoteCommitment`.
@ -85,12 +87,6 @@ lazy_static! {
};
}
/// The index of a note's commitment at the leafmost layer of its Note
/// Commitment Tree.
///
/// <https://zips.z.cash/protocol/protocol.pdf#merkletree>
pub struct Position(pub(crate) u64);
/// Sapling note commitment tree root node hash.
///
/// The root hash in LEBS2OSP256(rt) encoding of the Sapling note
@ -167,7 +163,7 @@ 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(Copy, Clone, Eq, PartialEq)]
struct Node([u8; 32]);
pub struct Node([u8; 32]);
impl fmt::Debug for Node {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -179,12 +175,12 @@ impl fmt::Debug for Node {
///
/// 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.
/// [`incrementalmerkletree::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 {
/// [3]: incrementalmerkletree::frontier::CommitmentTree
impl HashSer for Node {
fn read<R: io::Read>(mut reader: R) -> io::Result<Self> {
let mut node = [0u8; 32];
reader.read_exact(&mut node)?;
@ -194,24 +190,9 @@ impl merkle_tree::Hashable for 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 - 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 = usize::from(MERKLE_DEPTH) - level;
Self(EMPTY_ROOTS[layer_below])
}
}
impl incrementalmerkletree::Hashable for Node {
impl Hashable for Node {
fn empty_leaf() -> Self {
Self(NoteCommitmentTree::uncommitted())
}
@ -219,13 +200,13 @@ impl incrementalmerkletree::Hashable for Node {
/// Combine two nodes to generate a new node in the given level.
/// Level 0 is the layer above the leaves (layer 31).
/// Level 31 is the root (layer 0).
fn combine(level: incrementalmerkletree::Altitude, a: &Self, b: &Self) -> Self {
fn combine(level: incrementalmerkletree::Level, a: &Self, b: &Self) -> Self {
let layer = MERKLE_DEPTH - 1 - u8::from(level);
Self(merkle_crh_sapling(layer, a.0, b.0))
}
/// Return the node for the level below the given level. (A quirk of the API)
fn empty_root(level: incrementalmerkletree::Altitude) -> Self {
fn empty_root(level: incrementalmerkletree::Level) -> Self {
let layer_below = usize::from(MERKLE_DEPTH) - usize::from(level);
Self(EMPTY_ROOTS[layer_below])
}
@ -267,6 +248,8 @@ pub enum NoteCommitmentTreeError {
/// Sapling Incremental Note Commitment Tree.
#[derive(Debug, Serialize, Deserialize)]
#[serde(into = "LegacyNoteCommitmentTree")]
#[serde(from = "LegacyNoteCommitmentTree")]
pub struct NoteCommitmentTree {
/// The tree represented as a [`Frontier`](bridgetree::Frontier).
///
@ -284,7 +267,7 @@ pub struct NoteCommitmentTree {
/// <https://zips.z.cash/protocol/protocol.pdf#merkletree>
///
/// Note: MerkleDepth^Sapling = MERKLE_DEPTH = 32.
inner: bridgetree::Frontier<Node, MERKLE_DEPTH>,
inner: Frontier<Node, MERKLE_DEPTH>,
/// A cached root of the tree.
///
@ -314,7 +297,7 @@ impl NoteCommitmentTree {
/// Returns an error if the tree is full.
#[allow(clippy::unwrap_in_result)]
pub fn append(&mut self, cm_u: NoteCommitmentUpdate) -> Result<(), NoteCommitmentTreeError> {
if self.inner.append(&cm_u.into()) {
if self.inner.append(cm_u.into()) {
// Invalidate cached root
let cached_root = self
.cached_root
@ -388,7 +371,9 @@ impl NoteCommitmentTree {
///
/// For Sapling, the tree is capped at 2^32.
pub fn count(&self) -> u64 {
self.inner.position().map_or(0, |pos| u64::from(pos) + 1)
self.inner
.value()
.map_or(0, |x| u64::from(x.position()) + 1)
}
/// Checks if the tree roots and inner data structures of `self` and `other` are equal.
@ -463,7 +448,7 @@ impl From<Vec<jubjub::Fq>> for NoteCommitmentTree {
/// A serialized Sapling note commitment tree.
///
/// The format of the serialized data is compatible with
/// [`CommitmentTree`](merkle_tree::CommitmentTree) from `librustzcash` and not
/// [`CommitmentTree`](incrementalmerkletree::frontier::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
@ -472,7 +457,7 @@ impl From<Vec<jubjub::Fq>> for NoteCommitmentTree {
/// 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),
/// overall tree); whereas in [`CommitmentTree`](incrementalmerkletree::frontier::CommitmentTree),
/// the vector of parent hashes is sparse with [`None`] values in the gaps.
///
/// The sparse format, used in this implementation, allows representing invalid
@ -489,6 +474,9 @@ impl From<&NoteCommitmentTree> for SerializedTree {
fn from(tree: &NoteCommitmentTree) -> Self {
let mut serialized_tree = vec![];
//
let legacy_tree = LegacyNoteCommitmentTree::from(tree.clone());
// Convert the note commitment tree represented as a frontier into the
// format compatible with `zcashd`.
//
@ -502,20 +490,22 @@ impl From<&NoteCommitmentTree> for SerializedTree {
// 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)),
if let Some(frontier) = legacy_tree.inner.frontier {
let (left_leaf, right_leaf) = match frontier.leaf {
LegacyLeaf::Left(left_value) => (Some(left_value), None),
LegacyLeaf::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();
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();
let mut position: u64 = (frontier.position.0)
.try_into()
.expect("old usize position always fit in u64");
// 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
@ -552,7 +542,6 @@ impl From<&NoteCommitmentTree> for SerializedTree {
}
// Serialize the converted note commitment tree.
Optional::write(&mut serialized_tree, left_leaf, |tree, leaf| {
leaf.write(tree)
})

View File

@ -0,0 +1,125 @@
//! Sapling serialization legacy code.
//!
//! We create a [`LegacyNoteCommitmentTree`] which is a copy of [`NoteCommitmentTree`] but where serialization and
//! deserialization can be derived.
//! To do this we create a [`LegacyFrontier`] which is a legacy `Frontier` structure that can be found in [1],
//! In order to make [`LegacyFrontier`] serializable we also have our own versions of `NonEmptyFrontier` ([`LegacyNonEmptyFrontier`]),
//! `Leaf`([`LegacyLeaf`]) and `Position`([`LegacyPosition`]) that can be found in [1] or [2].
//!
//! Conversions methods to/from [`LegacyNoteCommitmentTree`] to/from [`NoteCommitmentTree`] are defined also in this file.
//!
//! [1]: https://github.com/zcash/incrementalmerkletree/blob/incrementalmerkletree-v0.3.1/src/bridgetree.rs
//! [2]: https://github.com/zcash/incrementalmerkletree/blob/incrementalmerkletree-v0.3.1/src/lib.rs
use incrementalmerkletree::{frontier::Frontier, Position};
use super::{Node, NoteCommitmentTree, Root, MERKLE_DEPTH};
/// A legacy version of [`NoteCommitmentTree`].
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename = "NoteCommitmentTree")]
#[allow(missing_docs)]
pub struct LegacyNoteCommitmentTree {
pub inner: LegacyFrontier<Node, MERKLE_DEPTH>,
cached_root: std::sync::RwLock<Option<Root>>,
}
impl From<NoteCommitmentTree> for LegacyNoteCommitmentTree {
fn from(nct: NoteCommitmentTree) -> Self {
LegacyNoteCommitmentTree {
inner: nct.inner.into(),
cached_root: nct.cached_root,
}
}
}
impl From<LegacyNoteCommitmentTree> for NoteCommitmentTree {
fn from(nct: LegacyNoteCommitmentTree) -> Self {
NoteCommitmentTree {
inner: nct.inner.into(),
cached_root: nct.cached_root,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename = "Frontier")]
#[allow(missing_docs)]
pub struct LegacyFrontier<H, const DEPTH: u8> {
pub frontier: Option<LegacyNonEmptyFrontier<H>>,
}
impl From<LegacyFrontier<Node, MERKLE_DEPTH>> for Frontier<Node, MERKLE_DEPTH> {
fn from(legacy_frontier: LegacyFrontier<Node, MERKLE_DEPTH>) -> Self {
if let Some(legacy_frontier_data) = legacy_frontier.frontier {
let mut ommers = legacy_frontier_data.ommers;
let position = Position::from(
u64::try_from(legacy_frontier_data.position.0)
.expect("old `usize` always fits in `u64`"),
);
let leaf = match legacy_frontier_data.leaf {
LegacyLeaf::Left(a) => a,
LegacyLeaf::Right(a, b) => {
ommers.insert(0, a);
b
}
};
Frontier::from_parts(
position,
leaf,
ommers,
)
.expect("We should be able to construct a frontier from parts given legacy frontier is not empty")
} else {
Frontier::empty()
}
}
}
impl From<Frontier<Node, MERKLE_DEPTH>> for LegacyFrontier<Node, MERKLE_DEPTH> {
fn from(frontier: Frontier<Node, MERKLE_DEPTH>) -> Self {
if let Some(frontier_data) = frontier.value() {
let leaf_from_frontier = *frontier_data.leaf();
let mut leaf = LegacyLeaf::Left(leaf_from_frontier);
let mut ommers = frontier_data.ommers().to_vec();
let position = usize::try_from(u64::from(frontier_data.position()))
.expect("new position should fit in a `usize`");
if frontier_data.position().is_odd() {
let left = ommers.remove(0);
leaf = LegacyLeaf::Right(left, leaf_from_frontier);
}
LegacyFrontier {
frontier: Some(LegacyNonEmptyFrontier {
position: LegacyPosition(position),
leaf,
ommers: ommers.to_vec(),
}),
}
} else {
LegacyFrontier { frontier: None }
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename = "NonEmptyFrontier")]
#[allow(missing_docs)]
pub struct LegacyNonEmptyFrontier<H> {
pub position: LegacyPosition,
pub leaf: LegacyLeaf<H>,
pub ommers: Vec<H>,
}
/// A set of leaves of a Merkle tree.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename = "Leaf")]
#[allow(missing_docs)]
pub enum LegacyLeaf<A> {
Left(A),
Right(A, A),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[repr(transparent)]
#[allow(missing_docs)]
pub struct LegacyPosition(pub usize);

View File

@ -13,13 +13,16 @@
use std::fmt;
use byteorder::{BigEndian, ByteOrder};
use incrementalmerkletree::{bridgetree, Frontier};
use incrementalmerkletree::frontier::Frontier;
use lazy_static::lazy_static;
use sha2::digest::generic_array::GenericArray;
use thiserror::Error;
use super::commitment::NoteCommitment;
pub mod legacy;
use legacy::LegacyNoteCommitmentTree;
#[cfg(any(test, feature = "proptest-impl"))]
use proptest_derive::Arbitrary;
@ -128,7 +131,7 @@ impl From<&Root> for [u8; 32] {
/// A node of the Sprout note commitment tree.
#[derive(Clone, Copy, Eq, PartialEq)]
struct Node([u8; 32]);
pub struct Node([u8; 32]);
impl fmt::Debug for Node {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -147,12 +150,12 @@ impl incrementalmerkletree::Hashable for Node {
/// Note that Sprout does not use the `level` argument.
///
/// [MerkleCRH^Sprout]: https://zips.z.cash/protocol/protocol.pdf#sproutmerklecrh
fn combine(_level: incrementalmerkletree::Altitude, a: &Self, b: &Self) -> Self {
fn combine(_level: incrementalmerkletree::Level, a: &Self, b: &Self) -> Self {
Self(merkle_crh_sprout(a.0, b.0))
}
/// Returns the node for the level below the given level. (A quirk of the API)
fn empty_root(level: incrementalmerkletree::Altitude) -> Self {
fn empty_root(level: incrementalmerkletree::Level) -> Self {
let layer_below = usize::from(MERKLE_DEPTH) - usize::from(level);
Self(EMPTY_ROOTS[layer_below])
}
@ -200,16 +203,18 @@ pub enum NoteCommitmentTreeError {
/// job of this tree to protect against double-spending, as it is append-only; double-spending
/// is prevented by maintaining the [nullifier set] for each shielded pool.
///
/// Internally this wraps [`incrementalmerkletree::bridgetree::Frontier`], so that we can maintain and increment
/// Internally this wraps [`bridgetree::Frontier`], so that we can maintain and increment
/// the full tree with only the minimal amount of non-empty nodes/leaves required.
///
/// [Sprout Note Commitment Tree]: https://zips.z.cash/protocol/protocol.pdf#merkletree
/// [nullifier set]: https://zips.z.cash/protocol/protocol.pdf#nullifierset
#[derive(Debug, Serialize, Deserialize)]
#[serde(into = "LegacyNoteCommitmentTree")]
#[serde(from = "LegacyNoteCommitmentTree")]
pub struct NoteCommitmentTree {
/// The tree represented as a [`incrementalmerkletree::bridgetree::Frontier`].
/// The tree represented as a [`bridgetree::Frontier`].
///
/// A [`incrementalmerkletree::Frontier`] is a subset of the tree that allows to fully specify it. It
/// A [`bridgetree::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
/// has non-empty nodes. Upper (near root) empty nodes of the branch are not
/// stored.
@ -222,7 +227,7 @@ pub struct NoteCommitmentTree {
/// <https://zips.z.cash/protocol/protocol.pdf#merkletree>
///
/// Note: MerkleDepth^Sprout = MERKLE_DEPTH = 29.
inner: bridgetree::Frontier<Node, MERKLE_DEPTH>,
inner: Frontier<Node, MERKLE_DEPTH>,
/// A cached root of the tree.
///
@ -248,7 +253,7 @@ impl NoteCommitmentTree {
/// Returns an error if the tree is full.
#[allow(clippy::unwrap_in_result)]
pub fn append(&mut self, cm: NoteCommitment) -> Result<(), NoteCommitmentTreeError> {
if self.inner.append(&cm.into()) {
if self.inner.append(cm.into()) {
// Invalidate cached root
let cached_root = self
.cached_root
@ -323,7 +328,9 @@ impl NoteCommitmentTree {
///
/// [spec]: https://zips.z.cash/protocol/protocol.pdf#merkletree
pub fn count(&self) -> u64 {
self.inner.position().map_or(0, |pos| u64::from(pos) + 1)
self.inner
.value()
.map_or(0, |x| u64::from(x.position()) + 1)
}
/// Checks if the tree roots and inner data structures of `self` and `other` are equal.
@ -360,7 +367,7 @@ impl Clone for NoteCommitmentTree {
impl Default for NoteCommitmentTree {
fn default() -> Self {
Self {
inner: bridgetree::Frontier::empty(),
inner: Frontier::empty(),
cached_root: Default::default(),
}
}

View File

@ -0,0 +1,121 @@
//! Sprout serialization legacy code.
//!
//! We create a [`LegacyNoteCommitmentTree`] which is a copy of [`NoteCommitmentTree`] but where serialization and
//! deserialization can be derived.
//! To do this we create a [`LegacyFrontier`] which is a legacy `Frontier` structure that can be found in [1],
//! In order to make [`LegacyFrontier`] serializable we also have our own versions of `NonEmptyFrontier` ([`LegacyNonEmptyFrontier`]),
//! `Leaf`([`LegacyLeaf`]) and `Position`([`LegacyPosition`]) that can be found in [1] or [2].
//!
//! Conversions methods to/from [`LegacyNoteCommitmentTree`] to/from [`NoteCommitmentTree`] are defined also in this file.
//!
//! [1]: https://github.com/zcash/incrementalmerkletree/blob/incrementalmerkletree-v0.3.1/src/bridgetree.rs
//! [2]: https://github.com/zcash/incrementalmerkletree/blob/incrementalmerkletree-v0.3.1/src/lib.rs
use incrementalmerkletree::{frontier::Frontier, Position};
use super::{Node, NoteCommitmentTree, Root, MERKLE_DEPTH};
/// A legacy version of [`NoteCommitmentTree`].
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename = "NoteCommitmentTree")]
pub struct LegacyNoteCommitmentTree {
inner: LegacyFrontier<Node, MERKLE_DEPTH>,
cached_root: std::sync::RwLock<Option<Root>>,
}
impl From<NoteCommitmentTree> for LegacyNoteCommitmentTree {
fn from(nct: NoteCommitmentTree) -> Self {
LegacyNoteCommitmentTree {
inner: nct.inner.into(),
cached_root: nct.cached_root,
}
}
}
impl From<LegacyNoteCommitmentTree> for NoteCommitmentTree {
fn from(nct: LegacyNoteCommitmentTree) -> Self {
NoteCommitmentTree {
inner: nct.inner.into(),
cached_root: nct.cached_root,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename = "Frontier")]
#[allow(missing_docs)]
pub struct LegacyFrontier<H, const DEPTH: u8> {
frontier: Option<LegacyNonEmptyFrontier<H>>,
}
impl From<LegacyFrontier<Node, MERKLE_DEPTH>> for Frontier<Node, MERKLE_DEPTH> {
fn from(legacy_frontier: LegacyFrontier<Node, MERKLE_DEPTH>) -> Self {
if let Some(legacy_frontier_data) = legacy_frontier.frontier {
let mut ommers = legacy_frontier_data.ommers;
let position = Position::from(
u64::try_from(legacy_frontier_data.position.0)
.expect("old `usize` always fits in `u64`"),
);
let leaf = match legacy_frontier_data.leaf {
LegacyLeaf::Left(a) => a,
LegacyLeaf::Right(a, b) => {
ommers.insert(0, a);
b
}
};
Frontier::from_parts(
position,
leaf,
ommers,
)
.expect("We should be able to construct a frontier from parts given legacy frontier is not empty")
} else {
Frontier::empty()
}
}
}
impl From<Frontier<Node, MERKLE_DEPTH>> for LegacyFrontier<Node, MERKLE_DEPTH> {
fn from(frontier: Frontier<Node, MERKLE_DEPTH>) -> Self {
if let Some(frontier_data) = frontier.value() {
let leaf_from_frontier = *frontier_data.leaf();
let mut leaf = LegacyLeaf::Left(leaf_from_frontier);
let mut ommers = frontier_data.ommers().to_vec();
let position = usize::try_from(u64::from(frontier_data.position()))
.expect("new position should fit in a `usize`");
if frontier_data.position().is_odd() {
let left = ommers.remove(0);
leaf = LegacyLeaf::Right(left, leaf_from_frontier);
}
LegacyFrontier {
frontier: Some(LegacyNonEmptyFrontier {
position: LegacyPosition(position),
leaf,
ommers: ommers.to_vec(),
}),
}
} else {
LegacyFrontier { frontier: None }
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename = "NonEmptyFrontier")]
struct LegacyNonEmptyFrontier<H> {
position: LegacyPosition,
leaf: LegacyLeaf<H>,
ommers: Vec<H>,
}
/// A set of leaves of a Merkle tree.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename = "Leaf")]
enum LegacyLeaf<A> {
Left(A),
Right(A, A),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[repr(transparent)]
struct LegacyPosition(usize);

View File

@ -58,9 +58,9 @@ tower = { version = "0.4.13", features = ["timeout", "util", "buffer"] }
tracing = "0.1.37"
tracing-futures = "0.2.5"
orchard = "0.4.0"
orchard = "0.5.0"
zcash_proofs = { version = "0.11.0", features = ["local-prover", "multicore", "download-params"] }
zcash_proofs = { version = "0.12.1", features = ["local-prover", "multicore", "download-params"] }
tower-fallback = { path = "../tower-fallback/", version = "0.2.41-beta.3" }
tower-batch-control = { path = "../tower-batch-control/", version = "0.2.41-beta.3" }

View File

@ -65,7 +65,7 @@ serde = { version = "1.0.168", features = ["serde_derive"] }
# Experimental feature getblocktemplate-rpcs
rand = { version = "0.8.5", optional = true }
# ECC deps used by getblocktemplate-rpcs feature
zcash_address = { version = "0.2.1", optional = true }
zcash_address = { version = "0.3.0", optional = true }
# Test-only feature proptest-impl
proptest = { version = "1.2.0", optional = true }

View File

@ -15,7 +15,7 @@ keywords = ["zebra", "zcash"]
categories = ["api-bindings", "cryptography::cryptocurrencies"]
[dependencies]
zcash_script = "0.1.12"
zcash_script = "0.1.13"
zebra-chain = { path = "../zebra-chain", version = "1.0.0-beta.27" }

View File

@ -10,9 +10,16 @@ use rand::random;
use halo2::pasta::{group::ff::PrimeField, pallas};
use zebra_chain::{
orchard::tree::NoteCommitmentTree as OrchardNoteCommitmentTree,
sapling::tree::NoteCommitmentTree as SaplingNoteCommitmentTree,
orchard::{
tree::legacy::LegacyNoteCommitmentTree as LegacyOrchardNoteCommitmentTree,
tree::NoteCommitmentTree as OrchardNoteCommitmentTree,
},
sapling::{
tree::legacy::LegacyNoteCommitmentTree as LegacySaplingNoteCommitmentTree,
tree::NoteCommitmentTree as SaplingNoteCommitmentTree,
},
sprout::{
tree::legacy::LegacyNoteCommitmentTree as LegacySproutNoteCommitmentTree,
tree::NoteCommitmentTree as SproutNoteCommitmentTree,
NoteCommitment as SproutNoteCommitment,
},
@ -20,26 +27,6 @@ use zebra_chain::{
use crate::service::finalized_state::disk_format::{FromDisk, IntoDisk};
// Currently, these tests check these structs are equal:
// * commitments -> tree struct
// * commitments -> tree struct -> serialize -> deserialize -> tree struct
// And these serialized formats are equal:
// * fixed serialized test vector
// * commitments -> tree struct -> serialize
// * commitments -> tree struct -> serialize -> deserialize -> tree struct -> serialize
//
// TODO: apply these tests to the new tree structs, and update the serialization format
// (keeping the tests for the old format is optional, because the tests below cover it)
//
// TODO: test that old and new serializations produce the same format:
// Tree roots built from the same commitments should match:
// * commitments -> old tree struct -> new tree struct -> un-cached root
// * commitments -> new tree struct -> un-cached root
// Even when serialized and deserialized:
// * commitments -> old tree struct -> old serialize -> old deserialize -> old tree struct -> new tree struct -> un-cached root
// * commitments -> new tree struct -> new serialize -> new deserialize -> new tree struct -> un-cached root
// * commitments -> new tree struct -> un-cached root
/// Check that the sprout tree database serialization format has not changed.
#[test]
fn sprout_note_commitment_tree_serialization() {
@ -73,21 +60,8 @@ fn sprout_note_commitment_tree_serialization() {
// The purpose of this test is to make sure the serialization format does
// not change by accident.
let expected_serialized_tree_hex = "010200836045484077cf6390184ea7cd48b460e2d0f22b2293b69633bb152314a692fb019f5b2b1e4bf7e7318d0a1f417ca6bca36077025b3d11e074b94cd55ce9f3861801c45297124f50dcd3f78eed017afd1e30764cd74cdf0a57751978270fd0721359";
let serialized_tree = incremental_tree.as_bytes();
assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex);
let deserialized_tree = SproutNoteCommitmentTree::from_bytes(&serialized_tree);
// This check isn't enough to show that the entire struct is the same, because it just compares
// the cached serialized/deserialized roots. (NoteCommitmentTree::eq() also just compares
// roots.)
assert_eq!(incremental_tree.root(), deserialized_tree.root());
incremental_tree.assert_frontier_eq(&deserialized_tree);
// Double-check that the internal format is the same by re-serializing the tree.
let re_serialized_tree = deserialized_tree.as_bytes();
assert_eq!(serialized_tree, re_serialized_tree);
sprout_checks(incremental_tree, expected_serialized_tree_hex);
}
/// Check that the sprout tree database serialization format has not changed for one commitment.
@ -119,21 +93,8 @@ fn sprout_note_commitment_tree_serialization_one() {
// The purpose of this test is to make sure the serialization format does
// not change by accident.
let expected_serialized_tree_hex = "010000836045484077cf6390184ea7cd48b460e2d0f22b2293b69633bb152314a692fb000193e5f97ce1d5d94d0c6e1b66a4a262c9ae89e56e28f3f6e4a557b6fb70e173a8";
let serialized_tree = incremental_tree.as_bytes();
assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex);
let deserialized_tree = SproutNoteCommitmentTree::from_bytes(&serialized_tree);
// This check isn't enough to show that the entire struct is the same, because it just compares
// the cached serialized/deserialized roots. (NoteCommitmentTree::eq() also just compares
// roots.)
assert_eq!(incremental_tree.root(), deserialized_tree.root());
incremental_tree.assert_frontier_eq(&deserialized_tree);
// Double-check that the internal format is the same by re-serializing the tree.
let re_serialized_tree = deserialized_tree.as_bytes();
assert_eq!(serialized_tree, re_serialized_tree);
sprout_checks(incremental_tree, expected_serialized_tree_hex);
}
/// Check that the sprout tree database serialization format has not changed when the number of
@ -174,21 +135,8 @@ fn sprout_note_commitment_tree_serialization_pow2() {
// The purpose of this test is to make sure the serialization format does
// not change by accident.
let expected_serialized_tree_hex = "010301836045484077cf6390184ea7cd48b460e2d0f22b2293b69633bb152314a692fb92498a8295ea36d593eaee7cb8b55be3a3e37b8185d3807693184054cd574ae4019f5b2b1e4bf7e7318d0a1f417ca6bca36077025b3d11e074b94cd55ce9f3861801b61f588fcba9cea79e94376adae1c49583f716d2f20367141f1369a235b95c98";
let serialized_tree = incremental_tree.as_bytes();
assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex);
let deserialized_tree = SproutNoteCommitmentTree::from_bytes(&serialized_tree);
// This check isn't enough to show that the entire struct is the same, because it just compares
// the cached serialized/deserialized roots. (NoteCommitmentTree::eq() also just compares
// roots.)
assert_eq!(incremental_tree.root(), deserialized_tree.root());
incremental_tree.assert_frontier_eq(&deserialized_tree);
// Double-check that the internal format is the same by re-serializing the tree.
let re_serialized_tree = deserialized_tree.as_bytes();
assert_eq!(serialized_tree, re_serialized_tree);
sprout_checks(incremental_tree, expected_serialized_tree_hex);
}
/// Check that the sapling tree database serialization format has not changed.
@ -224,21 +172,8 @@ fn sapling_note_commitment_tree_serialization() {
// The purpose of this test is to make sure the serialization format does
// not change by accident.
let expected_serialized_tree_hex = "0102007c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e036c0162324ff2c329e99193a74d28a585a3c167a93bf41a255135529c913bd9b1e66601ddaa1ab86de5c153993414f34ba97e9674c459dfadde112b89eeeafa0e5a204c";
let serialized_tree = incremental_tree.as_bytes();
assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex);
let deserialized_tree = SaplingNoteCommitmentTree::from_bytes(&serialized_tree);
// This check isn't enough to show that the entire struct is the same, because it just compares
// the cached serialized/deserialized roots. (NoteCommitmentTree::eq() also just compares
// roots.)
assert_eq!(incremental_tree.root(), deserialized_tree.root());
incremental_tree.assert_frontier_eq(&deserialized_tree);
// Double-check that the internal format is the same by re-serializing the tree.
let re_serialized_tree = deserialized_tree.as_bytes();
assert_eq!(serialized_tree, re_serialized_tree);
sapling_checks(incremental_tree, expected_serialized_tree_hex);
}
/// Check that the sapling tree database serialization format has not changed for one commitment.
@ -270,21 +205,8 @@ fn sapling_note_commitment_tree_serialization_one() {
// The purpose of this test is to make sure the serialization format does
// not change by accident.
let expected_serialized_tree_hex = "010000225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b1145800012c60c7de033d7539d123fb275011edfe08d57431676981d162c816372063bc71";
let serialized_tree = incremental_tree.as_bytes();
assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex);
let deserialized_tree = SaplingNoteCommitmentTree::from_bytes(&serialized_tree);
// This check isn't enough to show that the entire struct is the same, because it just compares
// the cached serialized/deserialized roots. (NoteCommitmentTree::eq() also just compares
// roots.)
assert_eq!(incremental_tree.root(), deserialized_tree.root());
incremental_tree.assert_frontier_eq(&deserialized_tree);
// Double-check that the internal format is the same by re-serializing the tree.
let re_serialized_tree = deserialized_tree.as_bytes();
assert_eq!(serialized_tree, re_serialized_tree);
sapling_checks(incremental_tree, expected_serialized_tree_hex);
}
/// Check that the sapling tree database serialization format has not changed when the number of
@ -329,21 +251,8 @@ fn sapling_note_commitment_tree_serialization_pow2() {
// The purpose of this test is to make sure the serialization format does
// not change by accident.
let expected_serialized_tree_hex = "010701f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c3a3661bc12b72646c94bc6c92796e81953985ee62d80a9ec3645a9a95740ac15025991131c5c25911b35fcea2a8343e2dfd7a4d5b45493390e0cb184394d91c349002df68503da9247dfde6585cb8c9fa94897cf21735f8fc1b32116ef474de05c01d23765f3d90dfd97817ed6d995bd253d85967f77b9f1eaef6ecbcb0ef6796812";
let serialized_tree = incremental_tree.as_bytes();
assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex);
let deserialized_tree = SaplingNoteCommitmentTree::from_bytes(&serialized_tree);
// This check isn't enough to show that the entire struct is the same, because it just compares
// the cached serialized/deserialized roots. (NoteCommitmentTree::eq() also just compares
// roots.)
assert_eq!(incremental_tree.root(), deserialized_tree.root());
incremental_tree.assert_frontier_eq(&deserialized_tree);
// Double-check that the internal format is the same by re-serializing the tree.
let re_serialized_tree = deserialized_tree.as_bytes();
assert_eq!(serialized_tree, re_serialized_tree);
sapling_checks(incremental_tree, expected_serialized_tree_hex);
}
/// Check that the orchard tree database serialization format has not changed.
@ -389,21 +298,8 @@ fn orchard_note_commitment_tree_serialization() {
// The purpose of this test is to make sure the serialization format does
// not change by accident.
let expected_serialized_tree_hex = "010200ee9488053a30c596b43014105d3477e6f578c89240d1d1ee1743b77bb6adc40a01a34b69a4e4d9ccf954d46e5da1004d361a5497f511aeb4d481d23c0be177813301a0be6dab19bc2c65d8299258c16e14d48ec4d4959568c6412aa85763c222a702";
let serialized_tree = incremental_tree.as_bytes();
assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex);
let deserialized_tree = OrchardNoteCommitmentTree::from_bytes(&serialized_tree);
// This check isn't enough to show that the entire struct is the same, because it just compares
// the cached serialized/deserialized roots. (NoteCommitmentTree::eq() also just compares
// roots.)
assert_eq!(incremental_tree.root(), deserialized_tree.root());
incremental_tree.assert_frontier_eq(&deserialized_tree);
// Double-check that the internal format is the same by re-serializing the tree.
let re_serialized_tree = deserialized_tree.as_bytes();
assert_eq!(serialized_tree, re_serialized_tree);
orchard_checks(incremental_tree, expected_serialized_tree_hex);
}
/// Check that the orchard tree database serialization format has not changed for one commitment.
@ -437,21 +333,8 @@ fn orchard_note_commitment_tree_serialization_one() {
// The purpose of this test is to make sure the serialization format does
// not change by accident.
let expected_serialized_tree_hex = "01000068135cf49933229099a44ec99a75e1e1cb4640f9b5bdec6b3223856fea16390a000178afd4da59c541e9c2f317f9aff654f1fb38d14dc99431cbbfa93601c7068117";
let serialized_tree = incremental_tree.as_bytes();
assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex);
let deserialized_tree = OrchardNoteCommitmentTree::from_bytes(&serialized_tree);
// This check isn't enough to show that the entire struct is the same, because it just compares
// the cached serialized/deserialized roots. (NoteCommitmentTree::eq() also just compares
// roots.)
assert_eq!(incremental_tree.root(), deserialized_tree.root());
incremental_tree.assert_frontier_eq(&deserialized_tree);
// Double-check that the internal format is the same by re-serializing the tree.
let re_serialized_tree = deserialized_tree.as_bytes();
assert_eq!(serialized_tree, re_serialized_tree);
orchard_checks(incremental_tree, expected_serialized_tree_hex);
}
/// Check that the orchard tree database serialization format has not changed when the number of
@ -496,19 +379,156 @@ fn orchard_note_commitment_tree_serialization_pow2() {
// The purpose of this test is to make sure the serialization format does
// not change by accident.
let expected_serialized_tree_hex = "01010178315008fb2998b430a5731d6726207dc0f0ec81ea64af5cf612956901e72f0eee9488053a30c596b43014105d3477e6f578c89240d1d1ee1743b77bb6adc40a0001d3d525931005e45f5a29bc82524e871e5ee1b6d77839deb741a6e50cd99fdf1a";
orchard_checks(incremental_tree, expected_serialized_tree_hex);
}
fn sprout_checks(incremental_tree: SproutNoteCommitmentTree, expected_serialized_tree_hex: &str) {
let serialized_tree = incremental_tree.as_bytes();
assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex);
let deserialized_tree = SproutNoteCommitmentTree::from_bytes(&serialized_tree);
// Get a legacy deserialized tree from the deserialized tree.
let deserialized_legacy_tree = LegacySproutNoteCommitmentTree::from(deserialized_tree.clone());
// Get a deserialized tree from a legacy deserialized tree.
let deserialized_legacy_tree_as_new = deserialized_legacy_tree.into();
// Check frontiers are the same.
incremental_tree.assert_frontier_eq(&deserialized_tree);
incremental_tree.assert_frontier_eq(&deserialized_legacy_tree_as_new);
// Check cached roots are the same.
assert_eq!(incremental_tree.root(), deserialized_tree.root());
assert_eq!(
incremental_tree.root(),
deserialized_legacy_tree_as_new.root()
);
// Check recalculated roots are the same
assert_eq!(
incremental_tree.recalculate_root(),
deserialized_tree.recalculate_root()
);
assert_eq!(
incremental_tree.recalculate_root(),
deserialized_legacy_tree_as_new.recalculate_root()
);
// Check reclaculated and cached roots are the same
assert_eq!(
incremental_tree.recalculate_root(),
deserialized_tree
.cached_root()
.expect("cached root was serialized")
);
// Double-check that the internal format is the same by re-serializing the tree.
let re_serialized_tree = deserialized_tree.as_bytes();
let re_serialized_legacy_tree = deserialized_legacy_tree_as_new.as_bytes();
assert_eq!(serialized_tree, re_serialized_tree);
assert_eq!(re_serialized_legacy_tree, re_serialized_tree);
}
fn sapling_checks(incremental_tree: SaplingNoteCommitmentTree, expected_serialized_tree_hex: &str) {
let serialized_tree = incremental_tree.as_bytes();
assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex);
let deserialized_tree = SaplingNoteCommitmentTree::from_bytes(&serialized_tree);
// Get a legacy deserialized tree from the deserialized tree.
let deserialized_legacy_tree = LegacySaplingNoteCommitmentTree::from(deserialized_tree.clone());
// Get a deserialized tree from a legacy deserialized tree.
let deserialized_legacy_tree_as_new = deserialized_legacy_tree.into();
// Check frontiers are the same.
incremental_tree.assert_frontier_eq(&deserialized_tree);
incremental_tree.assert_frontier_eq(&deserialized_legacy_tree_as_new);
// Check cached roots are the same.
assert_eq!(incremental_tree.root(), deserialized_tree.root());
assert_eq!(
incremental_tree.root(),
deserialized_legacy_tree_as_new.root()
);
// Check recalculated roots are the same
assert_eq!(
incremental_tree.recalculate_root(),
deserialized_tree.recalculate_root()
);
assert_eq!(
incremental_tree.recalculate_root(),
deserialized_legacy_tree_as_new.recalculate_root()
);
// Check reclaculated and cached roots are the same
assert_eq!(
incremental_tree.recalculate_root(),
deserialized_tree
.cached_root()
.expect("cached root was serialized")
);
// Double-check that the internal format is the same by re-serializing the tree.
let re_serialized_tree = deserialized_tree.as_bytes();
let re_serialized_legacy_tree = deserialized_legacy_tree_as_new.as_bytes();
assert_eq!(serialized_tree, re_serialized_tree);
assert_eq!(re_serialized_legacy_tree, re_serialized_tree);
}
fn orchard_checks(incremental_tree: OrchardNoteCommitmentTree, expected_serialized_tree_hex: &str) {
let serialized_tree = incremental_tree.as_bytes();
assert_eq!(hex::encode(&serialized_tree), expected_serialized_tree_hex);
let deserialized_tree = OrchardNoteCommitmentTree::from_bytes(&serialized_tree);
// This check isn't enough to show that the entire struct is the same, because it just compares
// the cached serialized/deserialized roots. (NoteCommitmentTree::eq() also just compares
// roots.)
assert_eq!(incremental_tree.root(), deserialized_tree.root());
// Get a legacy deserialized tree from the deserialized tree.
let deserialized_legacy_tree = LegacyOrchardNoteCommitmentTree::from(deserialized_tree.clone());
// Get a deserialized tree from a legacy deserialized tree.
let deserialized_legacy_tree_as_new = deserialized_legacy_tree.into();
// Check frontiers are the same.
incremental_tree.assert_frontier_eq(&deserialized_tree);
incremental_tree.assert_frontier_eq(&deserialized_legacy_tree_as_new);
// Check cached roots are the same.
assert_eq!(incremental_tree.root(), deserialized_tree.root());
assert_eq!(
incremental_tree.root(),
deserialized_legacy_tree_as_new.root()
);
// Check recalculated roots are the same
assert_eq!(
incremental_tree.recalculate_root(),
deserialized_tree.recalculate_root()
);
assert_eq!(
incremental_tree.recalculate_root(),
deserialized_legacy_tree_as_new.recalculate_root()
);
// Check reclaculated and cached roots are the same
assert_eq!(
incremental_tree.recalculate_root(),
deserialized_tree
.cached_root()
.expect("cached root was serialized")
);
// Double-check that the internal format is the same by re-serializing the tree.
let re_serialized_tree = deserialized_tree.as_bytes();
let re_serialized_legacy_tree = deserialized_legacy_tree_as_new.as_bytes();
assert_eq!(serialized_tree, re_serialized_tree);
assert_eq!(re_serialized_legacy_tree, re_serialized_tree);
}