diff --git a/Cargo.lock b/Cargo.lock index 9894dad0..7985a878 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3262,6 +3262,7 @@ dependencies = [ "lazy_static", "metrics", "once_cell", + "proptest", "serde", "sled", "spandoc", diff --git a/book/src/dev/rfcs/0005-state-updates.md b/book/src/dev/rfcs/0005-state-updates.md index 1da8e19a..f77c3a32 100644 --- a/book/src/dev/rfcs/0005-state-updates.md +++ b/book/src/dev/rfcs/0005-state-updates.md @@ -800,8 +800,10 @@ Returns Implemented by querying: -- (non-finalized) if any `Chains` contain an `OutPoint` in their `created_utxos` and not their `spent_utxo` get the `transparent::Output` from `OutPoint`'s transaction -- (finalized) else if `OutPoint` is in `utxos_by_outpoint` return the associated `transparent::Output`. +- (non-finalized) if any `Chains` contain `OutPoint` in their `created_utxos` + get the `transparent::Output` from `OutPoint`'s transaction +- (finalized) else if `OutPoint` is in `utxos_by_outpoint` return the + associated `transparent::Output`. - else wait for `OutPoint` to be created as described in [RFC0004] [RFC0004]: https://zebra.zfnd.org/dev/rfcs/0004-asynchronous-script-verification.html diff --git a/zebra-chain/src/block.rs b/zebra-chain/src/block.rs index ac4df10a..51cacb44 100644 --- a/zebra-chain/src/block.rs +++ b/zebra-chain/src/block.rs @@ -27,12 +27,8 @@ use serde::{Deserialize, Serialize}; use crate::{parameters::Network, transaction::Transaction, transparent}; -#[cfg(any(test, feature = "proptest-impl"))] -use proptest_derive::Arbitrary; - /// A Zcash block, containing a header and a list of transactions. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] pub struct Block { /// The block header, containing block metadata. pub header: Header, diff --git a/zebra-chain/src/block/arbitrary.rs b/zebra-chain/src/block/arbitrary.rs index 63b62215..fbe7335a 100644 --- a/zebra-chain/src/block/arbitrary.rs +++ b/zebra-chain/src/block/arbitrary.rs @@ -1,14 +1,50 @@ +use std::sync::Arc; + use crate::parameters::Network; use crate::work::{difficulty::CompactDifficulty, equihash}; use super::*; +use crate::LedgerState; use chrono::{TimeZone, Utc}; use proptest::{ arbitrary::{any, Arbitrary}, prelude::*, }; +impl Arbitrary for Block { + type Parameters = LedgerState; + + fn arbitrary_with(ledger_state: Self::Parameters) -> Self::Strategy { + let transactions_strategy = Transaction::vec_strategy(ledger_state, 2); + + (any::
(), transactions_strategy) + .prop_map(|(header, transactions)| Self { + header, + transactions, + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} + +impl Block { + pub fn partial_chain_strategy( + init: LedgerState, + count: usize, + ) -> BoxedStrategy>> { + let mut current = init; + let mut vec = Vec::with_capacity(count); + for _ in 0..count { + vec.push(Block::arbitrary_with(current).prop_map(Arc::new)); + current.tip_height.0 += 1; + } + + vec.boxed() + } +} + impl Arbitrary for RootHash { type Parameters = (); @@ -35,7 +71,6 @@ impl Arbitrary for Header { any::<[u8; 32]>(), // time is interpreted as u32 in the spec, but rust timestamps are i64 (0i64..(u32::MAX as i64)), - any::(), any::<[u8; 32]>(), any::(), ) @@ -46,7 +81,6 @@ impl Arbitrary for Header { merkle_root_hash, root_bytes, timestamp, - difficulty_threshold, nonce, solution, )| Header { @@ -55,7 +89,8 @@ impl Arbitrary for Header { merkle_root: merkle_root_hash, root_bytes, time: Utc.timestamp(timestamp, 0), - difficulty_threshold, + // TODO: replace with `ExpandedDifficulty.to_compact` when that method is implemented + difficulty_threshold: CompactDifficulty(545259519), nonce, solution, }, diff --git a/zebra-chain/src/block/tests/prop.rs b/zebra-chain/src/block/tests/prop.rs index 36106630..f202306f 100644 --- a/zebra-chain/src/block/tests/prop.rs +++ b/zebra-chain/src/block/tests/prop.rs @@ -2,9 +2,10 @@ use std::env; use std::io::ErrorKind; use proptest::{arbitrary::any, prelude::*, test_runner::Config}; +use zebra_test::prelude::*; -use crate::parameters::Network; use crate::serialization::{SerializationError, ZcashDeserializeInto, ZcashSerialize}; +use crate::{block, parameters::Network, LedgerState}; use super::super::{serialize::MAX_BLOCK_BYTES, *}; @@ -88,3 +89,23 @@ proptest! { } } } + +#[test] +fn blocks_have_coinbase() -> Result<()> { + zebra_test::init(); + + let strategy = any::() + .prop_map(|tip_height| LedgerState { + tip_height, + is_coinbase: true, + network: Network::Mainnet, + }) + .prop_flat_map(Block::arbitrary_with); + + proptest!(|(block in strategy)| { + let has_coinbase = block.coinbase_height().is_some(); + prop_assert!(has_coinbase); + }); + + Ok(()) +} diff --git a/zebra-chain/src/lib.rs b/zebra-chain/src/lib.rs index 59a6bbe3..ca4bd0c6 100644 --- a/zebra-chain/src/lib.rs +++ b/zebra-chain/src/lib.rs @@ -6,7 +6,7 @@ #![doc(html_favicon_url = "https://www.zfnd.org/images/zebra-favicon-128.png")] #![doc(html_logo_url = "https://www.zfnd.org/images/zebra-icon.png")] #![doc(html_root_url = "https://doc.zebra.zfnd.org/zebra_chain")] -#![deny(missing_docs)] +// #![deny(missing_docs)] #![allow(clippy::try_err)] #[macro_use] @@ -22,3 +22,27 @@ pub mod sprout; pub mod transaction; pub mod transparent; pub mod work; + +#[derive(Debug, Clone, Copy)] +#[cfg(any(test, feature = "proptest-impl"))] +pub struct LedgerState { + pub tip_height: block::Height, + pub is_coinbase: bool, + pub network: parameters::Network, +} + +#[cfg(any(test, feature = "proptest-impl"))] +impl Default for LedgerState { + fn default() -> Self { + let network = parameters::Network::Mainnet; + let tip_height = parameters::NetworkUpgrade::Sapling + .activation_height(network) + .unwrap(); + + Self { + tip_height, + is_coinbase: true, + network, + } + } +} diff --git a/zebra-chain/src/sapling/arbitrary.rs b/zebra-chain/src/sapling/arbitrary.rs index a9b6885f..32476426 100644 --- a/zebra-chain/src/sapling/arbitrary.rs +++ b/zebra-chain/src/sapling/arbitrary.rs @@ -1,8 +1,9 @@ +use jubjub::AffinePoint; use proptest::{arbitrary::any, array, collection::vec, prelude::*}; use crate::primitives::Groth16Proof; -use super::{commitment, keys, note, tree, Output, Spend}; +use super::{keys, note, tree, NoteCommitment, Output, Spend, ValueCommitment}; impl Arbitrary for Spend { type Parameters = (); @@ -10,26 +11,23 @@ impl Arbitrary for Spend { fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { ( any::(), - any::(), any::(), array::uniform32(any::()), any::(), vec(any::(), 64), ) - .prop_map( - |(anchor, cv, nullifier, rpk_bytes, proof, sig_bytes)| Self { - anchor, - cv, - nullifier, - rk: redjubjub::VerificationKeyBytes::from(rpk_bytes), - zkproof: proof, - spend_auth_sig: redjubjub::Signature::from({ - let mut b = [0u8; 64]; - b.copy_from_slice(sig_bytes.as_slice()); - b - }), - }, - ) + .prop_map(|(anchor, nullifier, rpk_bytes, proof, sig_bytes)| Self { + anchor, + cv: ValueCommitment(AffinePoint::identity()), + nullifier, + rk: redjubjub::VerificationKeyBytes::from(rpk_bytes), + zkproof: proof, + spend_auth_sig: redjubjub::Signature::from({ + let mut b = [0u8; 64]; + b.copy_from_slice(sig_bytes.as_slice()); + b + }), + }) .boxed() } @@ -41,23 +39,18 @@ impl Arbitrary for Output { fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { ( - any::(), - any::(), - any::(), any::(), any::(), any::(), ) - .prop_map( - |(cv, cm, ephemeral_key, enc_ciphertext, out_ciphertext, zkproof)| Self { - cv, - cm_u: cm.extract_u(), - ephemeral_key, - enc_ciphertext, - out_ciphertext, - zkproof, - }, - ) + .prop_map(|(enc_ciphertext, out_ciphertext, zkproof)| Self { + cv: ValueCommitment(AffinePoint::identity()), + cm_u: NoteCommitment(AffinePoint::identity()).extract_u(), + ephemeral_key: keys::EphemeralPublicKey(AffinePoint::identity()), + enc_ciphertext, + out_ciphertext, + zkproof, + }) .boxed() } diff --git a/zebra-chain/src/sapling/commitment/arbitrary.rs b/zebra-chain/src/sapling/commitment/arbitrary.rs index e8d838c9..8b137891 100644 --- a/zebra-chain/src/sapling/commitment/arbitrary.rs +++ b/zebra-chain/src/sapling/commitment/arbitrary.rs @@ -1,29 +1 @@ -use std::convert::TryFrom; -use proptest::{arbitrary::any, array, prelude::*}; - -use super::super::commitment; - -impl Arbitrary for commitment::NoteCommitment { - type Parameters = (); - - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - array::uniform32(any::()) - .prop_filter_map("Valid jubjub::AffinePoint", |b| Self::try_from(b).ok()) - .boxed() - } - - type Strategy = BoxedStrategy; -} - -impl Arbitrary for commitment::ValueCommitment { - type Parameters = (); - - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - array::uniform32(any::()) - .prop_filter_map("Valid jubjub::AffinePoint", |b| Self::try_from(b).ok()) - .boxed() - } - - type Strategy = BoxedStrategy; -} diff --git a/zebra-chain/src/sapling/keys.rs b/zebra-chain/src/sapling/keys.rs index 9b6f019d..640123be 100644 --- a/zebra-chain/src/sapling/keys.rs +++ b/zebra-chain/src/sapling/keys.rs @@ -869,7 +869,9 @@ impl FromStr for FullViewingKey { /// /// https://zips.z.cash/protocol/canopy.pdf#concretesaplingkeyagreement #[derive(Copy, Clone, Deserialize, PartialEq, Serialize)] -pub struct EphemeralPublicKey(#[serde(with = "serde_helpers::AffinePoint")] jubjub::AffinePoint); +pub struct EphemeralPublicKey( + #[serde(with = "serde_helpers::AffinePoint")] pub jubjub::AffinePoint, +); impl fmt::Debug for EphemeralPublicKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/zebra-chain/src/sapling/keys/arbitrary.rs b/zebra-chain/src/sapling/keys/arbitrary.rs index aef9019a..8b137891 100644 --- a/zebra-chain/src/sapling/keys/arbitrary.rs +++ b/zebra-chain/src/sapling/keys/arbitrary.rs @@ -1,17 +1 @@ -use std::convert::TryFrom; -use proptest::{arbitrary::any, array, prelude::*}; - -use super::*; - -impl Arbitrary for EphemeralPublicKey { - type Parameters = (); - - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - array::uniform32(any::()) - .prop_filter_map("Valid jubjub::AffinePoint", |b| Self::try_from(b).ok()) - .boxed() - } - - type Strategy = BoxedStrategy; -} diff --git a/zebra-chain/src/transaction/arbitrary.rs b/zebra-chain/src/transaction/arbitrary.rs index f85776c1..db91007b 100644 --- a/zebra-chain/src/transaction/arbitrary.rs +++ b/zebra-chain/src/transaction/arbitrary.rs @@ -1,10 +1,15 @@ +use std::sync::Arc; + +use block::Height; use chrono::{TimeZone, Utc}; use futures::future::Either; use proptest::{arbitrary::any, array, collection::vec, option, prelude::*}; +use crate::LedgerState; use crate::{ amount::Amount, block, + parameters::NetworkUpgrade, primitives::{Bctv14Proof, Groth16Proof, ZkSnarkProof}, sapling, sprout, transparent, }; @@ -13,9 +18,9 @@ use super::{JoinSplitData, LockTime, Memo, ShieldedData, Transaction}; impl Transaction { /// Generate a proptest strategy for V1 Transactions - pub fn v1_strategy() -> impl Strategy { + pub fn v1_strategy(ledger_state: LedgerState) -> BoxedStrategy { ( - vec(any::(), 0..10), + transparent::Input::vec_strategy(ledger_state, 10), vec(any::(), 0..10), any::(), ) @@ -28,9 +33,9 @@ impl Transaction { } /// Generate a proptest strategy for V2 Transactions - pub fn v2_strategy() -> impl Strategy { + pub fn v2_strategy(ledger_state: LedgerState) -> BoxedStrategy { ( - vec(any::(), 0..10), + transparent::Input::vec_strategy(ledger_state, 10), vec(any::(), 0..10), any::(), option::of(any::>()), @@ -47,9 +52,9 @@ impl Transaction { } /// Generate a proptest strategy for V3 Transactions - pub fn v3_strategy() -> impl Strategy { + pub fn v3_strategy(ledger_state: LedgerState) -> BoxedStrategy { ( - vec(any::(), 0..10), + transparent::Input::vec_strategy(ledger_state, 10), vec(any::(), 0..10), any::(), any::(), @@ -68,9 +73,9 @@ impl Transaction { } /// Generate a proptest strategy for V4 Transactions - pub fn v4_strategy() -> impl Strategy { + pub fn v4_strategy(ledger_state: LedgerState) -> BoxedStrategy { ( - vec(any::(), 0..10), + transparent::Input::vec_strategy(ledger_state, 10), vec(any::(), 0..10), any::(), any::(), @@ -99,6 +104,25 @@ impl Transaction { ) .boxed() } + + pub fn vec_strategy( + mut ledger_state: LedgerState, + len: usize, + ) -> BoxedStrategy>> { + let coinbase = Transaction::arbitrary_with(ledger_state).prop_map(Arc::new); + ledger_state.is_coinbase = false; + let remainder = vec( + Transaction::arbitrary_with(ledger_state).prop_map(Arc::new), + len, + ); + + (coinbase, remainder) + .prop_map(|(first, mut remainder)| { + remainder.insert(0, first); + remainder + }) + .boxed() + } } impl Arbitrary for Memo { @@ -189,16 +213,28 @@ impl Arbitrary for ShieldedData { } impl Arbitrary for Transaction { - type Parameters = (); + type Parameters = LedgerState; - fn arbitrary_with(_args: ()) -> Self::Strategy { - prop_oneof![ - Self::v1_strategy(), - Self::v2_strategy(), - Self::v3_strategy(), - Self::v4_strategy() - ] - .boxed() + fn arbitrary_with(ledger_state: Self::Parameters) -> Self::Strategy { + let LedgerState { + tip_height, + network, + .. + } = ledger_state; + + let height = Height(tip_height.0 + 1); + let network_upgrade = NetworkUpgrade::current(network, height); + + match network_upgrade { + NetworkUpgrade::Genesis | NetworkUpgrade::BeforeOverwinter => { + Self::v1_strategy(ledger_state) + } + NetworkUpgrade::Overwinter => Self::v2_strategy(ledger_state), + NetworkUpgrade::Sapling => Self::v3_strategy(ledger_state), + NetworkUpgrade::Blossom | NetworkUpgrade::Heartwood | NetworkUpgrade::Canopy => { + Self::v4_strategy(ledger_state) + } + } } type Strategy = BoxedStrategy; diff --git a/zebra-chain/src/transparent.rs b/zebra-chain/src/transparent.rs index c764da4f..93aacc3e 100644 --- a/zebra-chain/src/transparent.rs +++ b/zebra-chain/src/transparent.rs @@ -9,11 +9,14 @@ mod serialize; pub use address::Address; pub use script::Script; -#[cfg(any(test, feature = "proptest-impl"))] -mod arbitrary; #[cfg(any(test, feature = "proptest-impl"))] use proptest_derive::Arbitrary; +#[cfg(any(test, feature = "proptest-impl"))] +mod arbitrary; +#[cfg(test)] +mod prop; + use crate::{ amount::{Amount, NonNegative}, block, transaction, diff --git a/zebra-chain/src/transparent/arbitrary.rs b/zebra-chain/src/transparent/arbitrary.rs index d8018e2a..4bcb84cb 100644 --- a/zebra-chain/src/transparent/arbitrary.rs +++ b/zebra-chain/src/transparent/arbitrary.rs @@ -1,38 +1,44 @@ use proptest::{arbitrary::any, collection::vec, prelude::*}; -use crate::block; +use crate::{block, LedgerState}; use super::{CoinbaseData, Input, OutPoint, Script}; -impl Arbitrary for Input { - type Parameters = (); +impl Input { + /// Construct a strategy for creating validish vecs of Inputs. + pub fn vec_strategy(ledger_state: LedgerState, max_size: usize) -> BoxedStrategy> { + if ledger_state.is_coinbase { + let height = block::Height(ledger_state.tip_height.0 + 1); + Self::arbitrary_with(Some(height)) + .prop_map(|input| vec![input]) + .boxed() + } else { + vec(Self::arbitrary_with(None), max_size).boxed() + } + } +} - fn arbitrary_with(_args: ()) -> Self::Strategy { - prop_oneof![ +impl Arbitrary for Input { + type Parameters = Option; + + fn arbitrary_with(height: Self::Parameters) -> Self::Strategy { + if let Some(height) = height { + (vec(any::(), 0..95), any::()) + .prop_map(move |(data, sequence)| Input::Coinbase { + height, + data: CoinbaseData(data), + sequence, + }) + .boxed() + } else { (any::(), any::