From b296d1e2a30a3fe7415ad451b4755b0988d36f2c Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Sat, 15 Aug 2020 15:56:33 -0700 Subject: [PATCH] chain: move Block into a leaf module. This might make things a little easier to rearrange. In the future it would probably be good to change to block::Hash, block::Header, etc. --- zebra-chain/src/block.rs | 107 ++---------------- zebra-chain/src/block/block.rs | 91 +++++++++++++++ zebra-chain/src/block/serialize.rs | 9 +- zebra-chain/src/block/tests/generate.rs | 3 +- zebra-chain/src/block/tests/prop.rs | 2 +- zebra-chain/src/block/tests/vectors.rs | 7 +- zebra-utils/src/bin/zebra-checkpoints/main.rs | 1 - 7 files changed, 114 insertions(+), 106 deletions(-) create mode 100644 zebra-chain/src/block/block.rs diff --git a/zebra-chain/src/block.rs b/zebra-chain/src/block.rs index 2461f6f5..543d43de 100644 --- a/zebra-chain/src/block.rs +++ b/zebra-chain/src/block.rs @@ -1,6 +1,9 @@ //! Definitions of block datastructures. -#![allow(clippy::unit_arg)] +// block::block is done on purpose and is the most representative name +#![allow(clippy::module_inception)] + +mod block; mod hash; mod header; mod height; @@ -10,108 +13,12 @@ mod serialize; #[cfg(test)] mod tests; -use serde::{Deserialize, Serialize}; -use std::{error, sync::Arc}; - -#[cfg(test)] -use proptest_derive::Arbitrary; - -use crate::parameters::Network; -use crate::transaction::Transaction; - +pub use block::Block; pub use hash::BlockHeaderHash; pub use header::BlockHeader; pub use height::BlockHeight; pub use light_client::LightClientRootHash; -/// A block in your blockchain. -/// -/// A block is a data structure with two fields: -/// -/// Block header: a data structure containing the block's metadata -/// Transactions: an array (vector in Rust) of transactions -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -#[cfg_attr(test, derive(Arbitrary))] -pub struct Block { - /// The block header, containing block metadata. - pub header: BlockHeader, - /// The block transactions. - pub transactions: Vec>, -} - -/// The maximum size of a Zcash block, in bytes. -/// -/// Post-Sapling, this is also the maximum size of a transaction -/// in the Zcash specification. (But since blocks also contain a -/// block header and transaction count, the maximum size of a -/// transaction in the chain is approximately 1.5 kB smaller.) -pub const MAX_BLOCK_BYTES: u64 = 2_000_000; - /// The error type for Block checks. -// TODO(jlusby): Error = Report ? -type Error = Box; - -impl Block { - /// Return the block height reported in the coinbase transaction, if any. - pub fn coinbase_height(&self) -> Option { - use crate::transaction::TransparentInput; - self.transactions - .get(0) - .and_then(|tx| tx.inputs().get(0)) - .and_then(|input| match input { - TransparentInput::Coinbase { ref height, .. } => Some(*height), - _ => None, - }) - } - - /// Check that there is exactly one coinbase transaction in `Block`, and that - /// the coinbase transaction is the first transaction in the block. - /// - /// "The first (and only the first) transaction in a block is a coinbase - /// transaction, which collects and spends any miner subsidy and transaction - /// fees paid by transactions included in this block." [§3.10][3.10] - /// - /// [3.10]: https://zips.z.cash/protocol/protocol.pdf#coinbasetransactions - pub fn is_coinbase_first(&self) -> Result<(), Error> { - let first = self - .transactions - .get(0) - .ok_or_else(|| "block has no transactions")?; - let mut rest = self.transactions.iter().skip(1); - if !first.is_coinbase() { - Err("first transaction must be coinbase")?; - } - if rest.any(|tx| tx.contains_coinbase_input()) { - Err("coinbase input found in non-coinbase transaction")?; - } - Ok(()) - } - - /// Get the hash for the current 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 { - match self.coinbase_height() { - Some(height) => Some(LightClientRootHash::from_bytes( - self.header.light_client_root_bytes, - network, - height, - )), - None => None, - } - } -} - -impl<'a> From<&'a Block> for BlockHeaderHash { - fn from(block: &'a Block) -> BlockHeaderHash { - (&block.header).into() - } -} +// XXX try to remove this -- block checks should be done in zebra-consensus +type Error = Box; diff --git a/zebra-chain/src/block/block.rs b/zebra-chain/src/block/block.rs new file mode 100644 index 00000000..4b672f4e --- /dev/null +++ b/zebra-chain/src/block/block.rs @@ -0,0 +1,91 @@ +use std::sync::Arc; + +use serde::{Deserialize, Serialize}; + +use crate::parameters::Network; +use crate::transaction::Transaction; + +use super::{BlockHeader, BlockHeaderHash, BlockHeight, Error, LightClientRootHash}; + +#[cfg(test)] +use proptest_derive::Arbitrary; + +/// A block in your blockchain. +/// +/// A block is a data structure with two fields: +/// +/// Block header: a data structure containing the block's metadata +/// Transactions: an array (vector in Rust) of transactions +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct Block { + /// The block header, containing block metadata. + pub header: BlockHeader, + /// The block transactions. + pub transactions: Vec>, +} + +impl Block { + /// Return the block height reported in the coinbase transaction, if any. + pub fn coinbase_height(&self) -> Option { + use crate::transaction::TransparentInput; + self.transactions + .get(0) + .and_then(|tx| tx.inputs().get(0)) + .and_then(|input| match input { + TransparentInput::Coinbase { ref height, .. } => Some(*height), + _ => None, + }) + } + + /// Check that there is exactly one coinbase transaction in `Block`, and that + /// the coinbase transaction is the first transaction in the block. + /// + /// "The first (and only the first) transaction in a block is a coinbase + /// transaction, which collects and spends any miner subsidy and transaction + /// fees paid by transactions included in this block." [§3.10][3.10] + /// + /// [3.10]: https://zips.z.cash/protocol/protocol.pdf#coinbasetransactions + pub fn is_coinbase_first(&self) -> Result<(), Error> { + let first = self + .transactions + .get(0) + .ok_or_else(|| "block has no transactions")?; + let mut rest = self.transactions.iter().skip(1); + if !first.is_coinbase() { + Err("first transaction must be coinbase")?; + } + if rest.any(|tx| tx.contains_coinbase_input()) { + Err("coinbase input found in non-coinbase transaction")?; + } + Ok(()) + } + + /// Get the hash for the current 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 { + match self.coinbase_height() { + Some(height) => Some(LightClientRootHash::from_bytes( + self.header.light_client_root_bytes, + network, + height, + )), + None => None, + } + } +} + +impl<'a> From<&'a Block> for BlockHeaderHash { + fn from(block: &'a Block) -> BlockHeaderHash { + (&block.header).into() + } +} diff --git a/zebra-chain/src/block/serialize.rs b/zebra-chain/src/block/serialize.rs index 771b4310..471c9fab 100644 --- a/zebra-chain/src/block/serialize.rs +++ b/zebra-chain/src/block/serialize.rs @@ -10,7 +10,14 @@ use crate::work::{difficulty::CompactDifficulty, equihash}; use super::Block; use super::BlockHeader; use super::BlockHeaderHash; -use super::MAX_BLOCK_BYTES; + +/// The maximum size of a Zcash block, in bytes. +/// +/// Post-Sapling, this is also the maximum size of a transaction +/// in the Zcash specification. (But since blocks also contain a +/// block header and transaction count, the maximum size of a +/// transaction in the chain is approximately 1.5 kB smaller.) +pub const MAX_BLOCK_BYTES: u64 = 2_000_000; impl ZcashSerialize for BlockHeader { fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { diff --git a/zebra-chain/src/block/tests/generate.rs b/zebra-chain/src/block/tests/generate.rs index 1dbfd19a..a6b569ef 100644 --- a/zebra-chain/src/block/tests/generate.rs +++ b/zebra-chain/src/block/tests/generate.rs @@ -3,11 +3,12 @@ use chrono::{DateTime, NaiveDateTime, Utc}; use std::sync::Arc; use crate::{ - block::{Block, BlockHeader, MAX_BLOCK_BYTES}, serialization::{ZcashDeserialize, ZcashSerialize}, transaction::{LockTime, Transaction, TransparentInput, TransparentOutput}, }; +use super::super::{serialize::MAX_BLOCK_BYTES, Block, BlockHeader}; + /// Generate a block header pub fn block_header() -> BlockHeader { BlockHeader::zcash_deserialize(&zebra_test::vectors::DUMMY_HEADER[..]).unwrap() diff --git a/zebra-chain/src/block/tests/prop.rs b/zebra-chain/src/block/tests/prop.rs index 016af73e..aba04068 100644 --- a/zebra-chain/src/block/tests/prop.rs +++ b/zebra-chain/src/block/tests/prop.rs @@ -6,7 +6,7 @@ use proptest::{arbitrary::any, prelude::*, test_runner::Config}; use crate::parameters::Network; use crate::serialization::{SerializationError, ZcashDeserializeInto, ZcashSerialize}; -use super::super::*; +use super::super::{serialize::MAX_BLOCK_BYTES, *}; proptest! { #[test] diff --git a/zebra-chain/src/block/tests/vectors.rs b/zebra-chain/src/block/tests/vectors.rs index 66da7e08..b1f3c054 100644 --- a/zebra-chain/src/block/tests/vectors.rs +++ b/zebra-chain/src/block/tests/vectors.rs @@ -1,11 +1,14 @@ -use std::io::{Cursor, Write}; +use std::{ + io::{Cursor, Write}, + sync::Arc, +}; use chrono::{DateTime, Duration, LocalResult, TimeZone, Utc}; use crate::serialization::{sha256d, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize}; use crate::transaction::LockTime; -use super::super::*; +use super::super::{serialize::MAX_BLOCK_BYTES, *}; use super::generate; // XXX this should be rewritten as strategies #[test] diff --git a/zebra-utils/src/bin/zebra-checkpoints/main.rs b/zebra-utils/src/bin/zebra-checkpoints/main.rs index 01126a39..2a23e6a2 100644 --- a/zebra-utils/src/bin/zebra-checkpoints/main.rs +++ b/zebra-utils/src/bin/zebra-checkpoints/main.rs @@ -145,7 +145,6 @@ fn main() -> Result<()> { assert!(height <= BlockHeight::MAX); assert_eq!(x, height.0); let size = v["size"].as_u64().unwrap(); - assert!(size <= zebra_chain::block::MAX_BLOCK_BYTES); // compute cumulative_bytes += size;