Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Track anchors and note commitment trees in zebra-state #2458

Merged
merged 49 commits into from
Jul 29, 2021
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
65e32db
Tidy chain Cargo.toml
dconnolly Jul 7, 2021
7a457e3
Organize imports
dconnolly Jul 7, 2021
a2c8ec7
Add method to get note commitments from all Actions in Orchard shield…
dconnolly Jul 7, 2021
096e3fc
Add method to get note commitments from all JoinSplits in Sprout Join…
dconnolly Jul 7, 2021
062127a
Add Request and Response variants for awaiting anchors
dconnolly Jul 7, 2021
8036fa1
Add anchors and note commitment trees to finalized state db
dconnolly Jul 7, 2021
8ab5a65
Add (From|Into)Disk impls for tree::Roots and stubs for NoteCommitmen…
dconnolly Jul 7, 2021
e5a9e65
Track anchors and note commitment trees in Chain
dconnolly Jul 7, 2021
8090cbe
Handle errors when appending to note commitment trees
conradoplg Jul 16, 2021
452a36c
Add comments explaining why note commitment are not removed from the …
conradoplg Jul 16, 2021
7e045ce
Implementing note commitments in finalized state
conradoplg Jul 16, 2021
cb56568
Finish serialization of Orchard tree; remove old tree when updating f…
conradoplg Jul 19, 2021
12eb3c2
Add serialization and finalized state updates for Sprout and Sapling …
conradoplg Jul 19, 2021
4e181eb
Partially handle trees in non-finalized state. Use Option for trees i…
conradoplg Jul 19, 2021
b2559cd
Rebuild trees when forking; change finalized state tree getters to no…
conradoplg Jul 20, 2021
7020e04
Pass empty trees to tests; use empty trees by default in Chain
conradoplg Jul 20, 2021
ffb2704
Also rebuild anchor sets when forking
conradoplg Jul 21, 2021
788c502
Use empty tree as default in finalized state tree getters (for now)
conradoplg Jul 21, 2021
350c2ae
Use HashMultiSet for anchors in order to make pop_root() work correctly
conradoplg Jul 21, 2021
c946e59
Reduce DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES and MAX_PARTIAL_CHAIN_BLOCKS
conradoplg Jul 21, 2021
8c23c08
Reduce DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES and MAX_PARTIAL_CHAIN_BLO…
conradoplg Jul 21, 2021
2675ddf
Apply suggestions from code review
conradoplg Jul 22, 2021
75f947f
Add comments about order of note commitments and related methods/fields
conradoplg Jul 22, 2021
b7332ca
Don't use Option for trees
conradoplg Jul 22, 2021
891a9d6
Set DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES=1 and restore MAX_PARTIAL_CH…
conradoplg Jul 22, 2021
68b6b19
Remove unneeded anchor set rebuilding in fork()
conradoplg Jul 22, 2021
afa1cc2
Improve proptest formatting
conradoplg Jul 22, 2021
fa9edaf
Add missing comparisons to eq_internal_state
conradoplg Jul 22, 2021
3e53738
Renamed sprout::tree::NoteCommitmentTree::hash() to root()
conradoplg Jul 22, 2021
453fc1a
Merge remote-tracking branch 'origin/main' into tree-state
conradoplg Jul 22, 2021
94aea0c
Improve comments
conradoplg Jul 22, 2021
18aa875
Add asserts, add issues to TODOs
conradoplg Jul 23, 2021
77b622f
Remove impl Default for Chain since it was only used by tests
conradoplg Jul 23, 2021
a4df422
Improve documentation and assertions; add tree serialization tests
conradoplg Jul 23, 2021
a3665d4
Merge remote-tracking branch 'origin/main' into tree-state
conradoplg Jul 23, 2021
d3a099f
Remove Sprout code, which will be moved to another branch
conradoplg Jul 23, 2021
3d7d252
Add todo! in Sprout tree append()
conradoplg Jul 23, 2021
85f5dd4
Remove stub request, response *Anchor* handling for now
dconnolly Jul 27, 2021
fc22909
Add test for validating Sapling note commitment tree using test blocks
conradoplg Jul 27, 2021
2ee6da7
Increase database version (new columns added for note commitment tree…
conradoplg Jul 27, 2021
6c4d2fa
Merge remote-tracking branch 'origin/main' into tree-state
conradoplg Jul 27, 2021
5904ee8
Update test to make sure the order of sapling_note_commitments() is b…
conradoplg Jul 27, 2021
9703a39
Merge branch 'main' into tree-state
conradoplg Jul 28, 2021
5ae1c53
Improve comments and structure of the test
conradoplg Jul 28, 2021
637f86d
Improve variable names again
conradoplg Jul 28, 2021
bf25af2
Merge branch 'main' into tree-state
teor2345 Jul 29, 2021
4a131dd
Merge branch 'main' into tree-state
teor2345 Jul 29, 2021
7eea269
Merge branch 'main' into tree-state
dconnolly Jul 29, 2021
816e340
Rustfmt
dconnolly Jul 29, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions zebra-chain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ bench = ["zebra-test"]
[dependencies]
aes = "0.6"
bech32 = "0.8.1"
bigint = "4"
bitflags = "1.2.1"
bitvec = "0.22"
blake2b_simd = "0.5.11"
blake2s_simd = "0.5.11"
bls12_381 = "0.5.0"
bs58 = { version = "0.4", features = ["check"] }
byteorder = "1.4"
chrono = { version = "0.4", features = ["serde"] }
Expand All @@ -30,6 +32,7 @@ group = "0.10"
# Note: if updating this, also update the workspace Cargo.toml to match.
halo2 = { git = "https://github.com/zcash/halo2.git", rev = "236115917df9db45282fec24d1e1e36f275f71ab" }
hex = "0.4"
incrementalmerkletree = "0.1.0"
jubjub = "0.7.0"
lazy_static = "1.4.0"
rand_core = "0.6"
Expand All @@ -40,13 +43,10 @@ serde-big-array = "0.3.2"
sha2 = { version = "0.9.5", features=["compress"] }
subtle = "2.4"
thiserror = "1"
uint = "0.9.1"
x25519-dalek = { version = "1.1", features = ["serde"] }
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "0c3ed159985affa774e44d10172d4471d798a85a" }
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "0c3ed159985affa774e44d10172d4471d798a85a" }
bigint = "4"
uint = "0.9.1"
bls12_381 = "0.5.0"
incrementalmerkletree = "0.1.0"

proptest = { version = "0.10", optional = true }
proptest-derive = { version = "0.3.0", optional = true }
Expand Down
28 changes: 18 additions & 10 deletions zebra-chain/src/orchard/shielded_data.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
//! Orchard shielded data for `V5` `Transaction`s.

use std::{
cmp::{Eq, PartialEq},
fmt::Debug,
io,
};

use byteorder::{ReadBytesExt, WriteBytesExt};
use halo2::pasta::pallas;

use crate::{
amount::Amount,
block::MAX_BLOCK_BYTES,
Expand All @@ -13,14 +22,6 @@ use crate::{
},
};

use byteorder::{ReadBytesExt, WriteBytesExt};

use std::{
cmp::{Eq, PartialEq},
fmt::Debug,
io,
};

/// A bundle of [`Action`] descriptions and signature data.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct ShieldedData {
Expand All @@ -32,14 +33,15 @@ pub struct ShieldedData {
pub shared_anchor: tree::Root,
/// The aggregated zk-SNARK proof for all the actions in this transaction.
pub proof: Halo2Proof,
/// The Orchard Actions.
/// The Orchard Actions, in the order they appear in the transaction.
pub actions: AtLeastOne<AuthorizedAction>,
/// A signature on the transaction `sighash`.
pub binding_sig: Signature<Binding>,
}

impl ShieldedData {
/// Iterate over the [`Action`]s for the [`AuthorizedAction`]s in this transaction.
/// Iterate over the [`Action`]s for the [`AuthorizedAction`]s in this
/// transaction, in the order they appear in it.
pub fn actions(&self) -> impl Iterator<Item = &Action> {
self.actions.actions()
}
Expand All @@ -48,6 +50,12 @@ impl ShieldedData {
pub fn nullifiers(&self) -> impl Iterator<Item = &Nullifier> {
self.actions().map(|action| &action.nullifier)
}

/// Collect the cm_x's for this transaction, if it contains [`Action`]s with
/// outputs, in the order they appear in the transaction.
pub fn note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
dconnolly marked this conversation as resolved.
Show resolved Hide resolved
self.actions().map(|action| &action.cm_x)
}
}

impl AtLeastOne<AuthorizedAction> {
Expand Down
31 changes: 29 additions & 2 deletions zebra-chain/src/orchard/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ impl From<Root> for [u8; 32] {
}
}

impl From<&Root> for [u8; 32] {
fn from(root: &Root) -> Self {
(*root).into()
}
}

impl Hash for Root {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.to_bytes().hash(state)
Expand Down Expand Up @@ -177,15 +183,36 @@ impl From<pallas::Base> for Node {
}
}

impl serde::Serialize for Node {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.to_bytes().serialize(serializer)
}
}

impl<'de> serde::Deserialize<'de> for Node {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let bytes = <[u8; 32]>::deserialize(deserializer)?;
Option::<pallas::Base>::from(pallas::Base::from_bytes(&bytes))
.map(Node)
.ok_or_else(|| serde::de::Error::custom("invalid Pallas field element"))
}
}

#[allow(dead_code, missing_docs)]
#[derive(Error, Debug, PartialEq)]
#[derive(Error, Debug, PartialEq, Eq)]
pub enum NoteCommitmentTreeError {
#[error("The note commitment tree is full")]
FullTree,
}

/// Orchard Incremental Note Commitment Tree
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NoteCommitmentTree {
/// The tree represented as a Frontier.
///
Expand Down
13 changes: 8 additions & 5 deletions zebra-chain/src/sapling/shielded_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ where
pub value_balance: Amount,

/// A bundle of spends and outputs, containing at least one spend or
/// output.
/// output, in the order they appear in the transaction.
///
/// In V5 transactions, also contains a shared anchor, if there are any
/// spends.
Expand Down Expand Up @@ -154,7 +154,8 @@ where
/// [`Spend`]s in this `TransferData`.
spends: AtLeastOne<Spend<AnchorV>>,

/// Maybe some outputs (can be empty).
/// Maybe some outputs (can be empty), in the order they appear in the
/// transaction.
///
/// Use the [`ShieldedData::outputs`] method to get an iterator over the
/// [`Outputs`]s in this `TransferData`.
Expand All @@ -167,7 +168,7 @@ where
/// In Transaction::V5, if there are no spends, there must not be a shared
/// anchor.
JustOutputs {
/// At least one output.
/// At least one output, in the order they appear in the transaction.
///
/// Use the [`ShieldedData::outputs`] method to get an iterator over the
/// [`Outputs`]s in this `TransferData`.
Expand Down Expand Up @@ -205,7 +206,8 @@ where
self.transfers.spends()
}

/// Iterate over the [`Output`]s for this transaction.
/// Iterate over the [`Output`]s for this transaction, in the order they
/// appear in it.
pub fn outputs(&self) -> impl Iterator<Item = &Output> {
self.transfers.outputs()
}
Expand All @@ -225,7 +227,8 @@ where
self.spends().map(|spend| &spend.nullifier)
}

/// Collect the cm_u's for this transaction, if it contains [`Output`]s.
/// Collect the cm_u's for this transaction, if it contains [`Output`]s,
/// in the order they appear in the transaction.
pub fn note_commitments(&self) -> impl Iterator<Item = &jubjub::Fq> {
self.outputs().map(|output| &output.cm_u)
}
Expand Down
94 changes: 92 additions & 2 deletions zebra-chain/src/sapling/tests/tree.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
use std::sync::Arc;

use color_eyre::eyre;
use eyre::Result;
use hex::FromHex;

use crate::sapling::tests::test_vectors;
use crate::sapling::tree::*;
use crate::block::Block;
use crate::parameters::NetworkUpgrade;
use crate::sapling::{self, tree::*};
use crate::serialization::ZcashDeserializeInto;
use crate::{parameters::Network, sapling::tests::test_vectors};
use zebra_test::vectors::{
MAINNET_BLOCKS, MAINNET_FINAL_SAPLING_ROOTS, TESTNET_BLOCKS, TESTNET_FINAL_SAPLING_ROOTS,
};

#[test]
fn empty_roots() {
Expand Down Expand Up @@ -41,3 +51,83 @@ fn incremental_roots() {
);
}
}

#[test]
fn incremental_roots_with_blocks() -> Result<()> {
incremental_roots_with_blocks_for_network(Network::Mainnet)?;
incremental_roots_with_blocks_for_network(Network::Testnet)?;
Ok(())
}

fn incremental_roots_with_blocks_for_network(network: Network) -> Result<()> {
let (blocks, sapling_roots) = match network {
Network::Mainnet => (&*MAINNET_BLOCKS, &*MAINNET_FINAL_SAPLING_ROOTS),
Network::Testnet => (&*TESTNET_BLOCKS, &*TESTNET_FINAL_SAPLING_ROOTS),
};
let height = NetworkUpgrade::Sapling
.activation_height(network)
.unwrap()
.0;

// Load Block 0 (activation block of the given network upgrade)
let block0 = Arc::new(
blocks
.get(&height)
.expect("test vector exists")
.zcash_deserialize_into::<Block>()
.expect("block is structurally valid"),
);
// Build initial MMR tree with only Block 0
let sapling_root0 =
sapling::tree::Root(**sapling_roots.get(&height).expect("test vector exists"));

let mut tree = sapling::tree::NoteCommitmentTree::default();

// Add note commitments in Block 0 to the tree
for transaction in block0.transactions.iter() {
for sapling_note_commitment in transaction.sapling_note_commitments() {
tree.append(*sapling_note_commitment)
.expect("test vector is correct");
}
}

// Check if root is correct
assert_eq!(sapling_root0, tree.root());

// Load Block 1 (activation + 1)
let block1 = Arc::new(
blocks
.get(&(height + 1))
.expect("test vector exists")
.zcash_deserialize_into::<Block>()
.expect("block is structurally valid"),
);
let sapling_root1 = sapling::tree::Root(
**sapling_roots
.get(&(height + 1))
.expect("test vector exists"),
);

// Add note commitments in Block 1 to the tree
let mut appended_count = 0;
for transaction in block1.transactions.iter() {
for sapling_note_commitment in transaction.sapling_note_commitments() {
tree.append(*sapling_note_commitment)
.expect("test vector is correct");
appended_count += 1;
}
}
// We also want to make sure that sapling_note_commitments() is returning
// the commitments in the right order. But this will only be actually tested
// if there are more than one note commitment in a block.
// In the test vectors this applies only for the block 1 in mainnet,
// so we make this explicit in this assert.
if network == Network::Mainnet {
assert!(appended_count > 1);
}
teor2345 marked this conversation as resolved.
Show resolved Hide resolved

// Check if root is correct
assert_eq!(sapling_root1, tree.root());
dconnolly marked this conversation as resolved.
Show resolved Hide resolved

Ok(())
}
25 changes: 23 additions & 2 deletions zebra-chain/src/sapling/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,36 @@ impl From<jubjub::Fq> for Node {
}
}

impl serde::Serialize for Node {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.serialize(serializer)
}
}

impl<'de> serde::Deserialize<'de> for Node {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let bytes = <[u8; 32]>::deserialize(deserializer)?;
Option::<jubjub::Fq>::from(jubjub::Fq::from_bytes(&bytes))
.map(Node::from)
.ok_or_else(|| serde::de::Error::custom("invalid JubJub field element"))
}
}

#[allow(dead_code, missing_docs)]
#[derive(Error, Debug, PartialEq)]
#[derive(Error, Debug, PartialEq, Eq)]
pub enum NoteCommitmentTreeError {
#[error("The note commitment tree is full")]
FullTree,
}

/// Sapling Incremental Note Commitment Tree.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NoteCommitmentTree {
/// The tree represented as a Frontier.
///
Expand Down
Loading