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

Light Client Root Hash for Block #856

Merged
merged 4 commits into from
Aug 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 20 additions & 0 deletions zebra-chain/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
mod difficulty;
mod hash;
mod header;
mod light_client;
mod serialize;

#[cfg(test)]
Expand All @@ -17,9 +18,11 @@ use proptest_derive::Arbitrary;

use crate::transaction::Transaction;
use crate::types::BlockHeight;
use crate::Network;

pub use hash::BlockHeaderHash;
pub use header::BlockHeader;
pub use light_client::LightClientRootHash;

/// A block in your blockchain.
///
Expand Down Expand Up @@ -90,6 +93,23 @@ impl Block {
pub fn hash(&self) -> BlockHeaderHash {
BlockHeaderHash::from(self)
}

/// Get the parsed light client root hash for this block.
///
/// The interpretation of the light client root hash depends on the
/// configured `network`, and this block's height.
///
/// Returns None if this block does not have a block height.
pub fn light_client_root_hash(&self, network: Network) -> Option<LightClientRootHash> {
match self.coinbase_height() {
Some(height) => Some(LightClientRootHash::from_bytes(
self.header.light_client_root_hash,
network,
height,
)),
None => None,
}
}
}

impl<'a> From<&'a Block> for BlockHeaderHash {
Expand Down
19 changes: 6 additions & 13 deletions zebra-chain/src/block/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,12 @@ pub struct BlockHeader {
/// header.
pub merkle_root_hash: MerkleTreeRootHash,

/// [Pre-Sapling] Reserved. All zeroes.
/// [Sapling and Blossom] The root LEBS2OSP256(rt) of the Sapling note
/// commitment tree corresponding to the final Sapling treestate of
/// this block.
/// [Heartwood onward] The root of a Merkle Mountain Range tree, which
/// commits to various features of the chain's history, including the
/// Sapling commitment tree. This commitment supports the FlyClient
/// protocol. See ZIP-221 for details.
// TODO:
// - replace with an unspecified HistoryRootHash type?
// Note that the NetworkUpgrade list is in zebra-consensus, so we can't
// parse this field into a HistoryRootHash enum in zebra-chain.
pub history_root_hash: [u8; 32],
/// The light client root hash.
///
/// This field is interpreted differently, based on the current
/// block height. Use `block.light_client_root_hash(network)` to get the
/// parsed `LightClientRootHash` for this block.
pub(super) light_client_root_hash: [u8; 32],

/// The block timestamp is a Unix epoch time (UTC) when the miner
/// started hashing the header (according to the miner).
Expand Down
85 changes: 85 additions & 0 deletions zebra-chain/src/block/light_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//! The LightClientRootHash enum, used for the corresponding block header field.

use crate::note_commitment_tree::SaplingNoteTreeRootHash;
use crate::types::BlockHeight;
use crate::{Network, NetworkUpgrade, NetworkUpgrade::*};

/// Light client root hashes.
///
/// The `BlockHeader.light_client_root_hash` field is interpreted differently,
/// based on the current block height. The interpretation changes at or after
/// network upgrades.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum LightClientRootHash {
/// [Pre-Sapling] Reserved field.
///
/// All zeroes.
PreSaplingReserved([u8; 32]),

/// [Sapling and Blossom] The final Sapling treestate of this block.
///
/// The root LEBS2OSP256(rt) of the Sapling note commitment tree
/// corresponding to the final Sapling treestate of this block.
FinalSaplingRoot(SaplingNoteTreeRootHash),

/// [Heartwood activation block] Reserved field.
///
/// All zeroes. This MUST NOT be interpreted as a root hash.
/// See ZIP-221 for details.
ChainHistoryActivationReserved([u8; 32]),

/// [After Heartwood activation block] The root of a Merkle Mountain
/// Range chain history tree.
///
/// This root hash commits to various features of the chain's history,
/// including the Sapling commitment tree. This commitment supports the
/// FlyClient protocol. See ZIP-221 for details.
///
/// The commitment in each block covers the chain history from the most
/// recent network upgrade, through to the previous block. In particular,
/// an activation block commits to the entire previous network upgrade, and
/// the block after activation commits only to the activation block.
ChainHistoryRoot(ChainHistoryMmrRootHash),
}

impl LightClientRootHash {
/// Returns `bytes` as the LightClientRootHash variant for `network` and
/// `height`.
pub fn from_bytes(
bytes: [u8; 32],
network: Network,
height: BlockHeight,
) -> LightClientRootHash {
use LightClientRootHash::*;

match NetworkUpgrade::current(network, height) {
Genesis | BeforeOverwinter | Overwinter => PreSaplingReserved(bytes),
Sapling | Blossom => FinalSaplingRoot(SaplingNoteTreeRootHash(bytes)),
Heartwood if Some(height) == Heartwood.activation_height(network) => {
ChainHistoryActivationReserved(bytes)
}
Heartwood | Canopy => ChainHistoryRoot(ChainHistoryMmrRootHash(bytes)),
}
}

/// Returns the serialized bytes for this LightClientRootHash.
#[allow(dead_code)]
pub fn to_bytes(self) -> [u8; 32] {
use LightClientRootHash::*;

match self {
PreSaplingReserved(b) => b,
FinalSaplingRoot(v) => v.0,
ChainHistoryActivationReserved(b) => b,
ChainHistoryRoot(v) => v.0,
}
}
}

/// The root hash of a Merkle Mountain Range chain history tree.
// TODO:
// - add methods for maintaining the MMR peaks, and calculating the root
// hash from the current set of peaks.
// - move to a separate file.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ChainHistoryMmrRootHash([u8; 32]);
4 changes: 2 additions & 2 deletions zebra-chain/src/block/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ impl ZcashSerialize for BlockHeader {
writer.write_u32::<LittleEndian>(self.version)?;
self.previous_block_hash.zcash_serialize(&mut writer)?;
writer.write_all(&self.merkle_root_hash.0[..])?;
writer.write_all(&self.history_root_hash[..])?;
writer.write_all(&self.light_client_root_hash[..])?;
// this is a truncating cast, rather than a saturating cast
// but u32 times are valid until 2106, and our block verification time
// checks should detect any truncation.
Expand Down Expand Up @@ -66,7 +66,7 @@ impl ZcashDeserialize for BlockHeader {
version,
previous_block_hash: BlockHeaderHash::zcash_deserialize(&mut reader)?,
merkle_root_hash: MerkleTreeRootHash(reader.read_32_bytes()?),
history_root_hash: reader.read_32_bytes()?,
light_client_root_hash: reader.read_32_bytes()?,
// This can't panic, because all u32 values are valid `Utc.timestamp`s
time: Utc.timestamp(reader.read_u32::<LittleEndian>()? as i64, 0),
difficulty_threshold: CompactDifficulty(reader.read_u32::<LittleEndian>()?),
Expand Down
42 changes: 38 additions & 4 deletions zebra-chain/src/block/tests.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use super::*;

use crate::block::difficulty::CompactDifficulty;
use crate::block::{difficulty::CompactDifficulty, light_client::LightClientRootHash};
use crate::equihash_solution::EquihashSolution;
use crate::merkle_tree::MerkleTreeRootHash;
use crate::serialization::{
SerializationError, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize,
};
use crate::types::BlockHeight;
use crate::types::LockTime;
use crate::Network;
use crate::{sha256d_writer::Sha256dWriter, test::generate};

use chrono::{DateTime, Duration, LocalResult, TimeZone, Utc};
Expand All @@ -18,6 +20,20 @@ use proptest::{
use std::env;
use std::io::{Cursor, ErrorKind, Write};

impl Arbitrary for LightClientRootHash {
type Parameters = ();

fn arbitrary_with(_args: ()) -> Self::Strategy {
(any::<[u8; 32]>(), any::<Network>(), any::<BlockHeight>())
.prop_map(|(light_client_root_hash, network, block_height)| {
LightClientRootHash::from_bytes(light_client_root_hash, network, block_height)
})
.boxed()
}

type Strategy = BoxedStrategy<Self>;
}

impl Arbitrary for BlockHeader {
type Parameters = ();

Expand All @@ -39,7 +55,7 @@ impl Arbitrary for BlockHeader {
version,
previous_block_hash,
merkle_root_hash,
history_root_hash,
light_client_root_hash,
timestamp,
difficulty_threshold,
nonce,
Expand All @@ -48,7 +64,7 @@ impl Arbitrary for BlockHeader {
version,
previous_block_hash,
merkle_root_hash,
history_root_hash,
light_client_root_hash,
time: Utc.timestamp(timestamp, 0),
difficulty_threshold,
nonce,
Expand Down Expand Up @@ -212,6 +228,14 @@ proptest! {

prop_assert_eq![header, other_header];
}

#[test]
fn light_client_roundtrip(bytes in any::<[u8; 32]>(), network in any::<Network>(), block_height in any::<BlockHeight>()) {
let light_hash = LightClientRootHash::from_bytes(bytes, network, block_height);
let other_bytes = light_hash.to_bytes();

prop_assert_eq![bytes, other_bytes];
}
}

proptest! {
Expand All @@ -223,12 +247,22 @@ proptest! {
.unwrap_or(16)))]

#[test]
fn block_roundtrip(block in any::<Block>()) {
fn block_roundtrip(block in any::<Block>(), network in any::<Network>()) {
let bytes = block.zcash_serialize_to_vec()?;
let bytes = &mut bytes.as_slice();

// Check the light client root hash
let light_hash = block.light_client_root_hash(network);
if let Some(light_hash) = light_hash {
let light_hash_bytes = light_hash.to_bytes();
prop_assert_eq![block.header.light_client_root_hash, light_hash_bytes];
} else {
prop_assert_eq![block.coinbase_height(), None];
}

// Check the block size limit
if bytes.len() <= MAX_BLOCK_BYTES as _ {
// Check deserialization
let other_block = bytes.zcash_deserialize_into()?;

prop_assert_eq![block, other_block];
Expand Down
3 changes: 3 additions & 0 deletions zebra-chain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub mod keys;
pub mod note_commitment_tree;
pub mod notes;
pub mod nullifier;
pub mod parameters;
pub mod proofs;
pub mod serialization;
pub mod transaction;
Expand All @@ -31,6 +32,8 @@ pub mod utils;
pub use ed25519_zebra;
pub use redjubjub;

pub use parameters::NetworkUpgrade;

#[cfg(test)]
use proptest_derive::Arbitrary;

Expand Down
20 changes: 20 additions & 0 deletions zebra-chain/src/parameters.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//! The consensus parameters for each Zcash network.
//!
//! This module contains the consensus parameters which are required for
//! parsing.
//!
//! Some consensus parameters change based on network upgrades. Each network
//! upgrade happens at a particular block height. Some parameters have a value
//! (or function) before the upgrade height, at the upgrade height, and after
//! the upgrade height. (For example, the value of the reserved field in the
//! block header during the Heartwood upgrade.)
//!
//! Typically, consensus parameters are accessed via a function that takes a
//! `Network` and `BlockHeight`.

pub mod network_upgrade;

pub use network_upgrade::*;

#[cfg(test)]
mod tests;
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

use NetworkUpgrade::*;

use crate::types::BlockHeight;
use crate::{Network, Network::*};

use std::collections::{BTreeMap, HashMap};
use std::ops::Bound::*;

use zebra_chain::types::BlockHeight;
use zebra_chain::{Network, Network::*};

/// A Zcash network upgrade.
///
/// Network upgrades can change the Zcash network protocol or consensus rules in
Expand Down
Loading