Add tests for `Chain` implementation (#1093)
* Begin work on RFC5 implementation * I think this is necessary * holy shit supertrait implemented via subtrait * implement most of the chain functions * change to slightly better name * implement fork * fix outpoint handling in Chain struct * update expect for work * resolve review comment * split utxo into two sets * update the Chain definition * just a little more * update comment * Apply suggestions from code review Co-authored-by: teor <teor@riseup.net> * apply changes from code review * remove allow attribute in zebra-state/lib.rs * Update zebra-state/src/memory_state.rs Co-authored-by: teor <teor@riseup.net> * merge ChainSet type into MemoryState * rename state impl types * Add error messages to asserts * checkpoint so I can split off arbitrary changes into a PR * export proptest impls for use in downstream crates * add testjob for disabled feature in zebra-chain * run rustfmt * try to fix github actions syntax * differentiate name * prove that github action tests zebra-chain build without features * revert change from last commit now that test is running * remove accidentally introduced newline * checkpoint * add module doc comment * update RFC for utxos * add missing header * working proptest for Chain * apply change from chain impl PR * setup config for proptests * Update zebra-chain/src/block/arbitrary.rs Co-authored-by: teor <teor@riseup.net> * run rustfmt Co-authored-by: teor <teor@riseup.net>
This commit is contained in:
parent
1b528404cd
commit
86ed13060f
|
@ -3262,6 +3262,7 @@ dependencies = [
|
|||
"lazy_static",
|
||||
"metrics",
|
||||
"once_cell",
|
||||
"proptest",
|
||||
"serde",
|
||||
"sled",
|
||||
"spandoc",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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::<Header>(), transactions_strategy)
|
||||
.prop_map(|(header, transactions)| Self {
|
||||
header,
|
||||
transactions,
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
type Strategy = BoxedStrategy<Self>;
|
||||
}
|
||||
|
||||
impl Block {
|
||||
pub fn partial_chain_strategy(
|
||||
init: LedgerState,
|
||||
count: usize,
|
||||
) -> BoxedStrategy<Vec<Arc<Self>>> {
|
||||
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::<CompactDifficulty>(),
|
||||
any::<[u8; 32]>(),
|
||||
any::<equihash::Solution>(),
|
||||
)
|
||||
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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::<block::Height>()
|
||||
.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(())
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::<tree::Root>(),
|
||||
any::<commitment::ValueCommitment>(),
|
||||
any::<note::Nullifier>(),
|
||||
array::uniform32(any::<u8>()),
|
||||
any::<Groth16Proof>(),
|
||||
vec(any::<u8>(), 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::<commitment::ValueCommitment>(),
|
||||
any::<commitment::NoteCommitment>(),
|
||||
any::<keys::EphemeralPublicKey>(),
|
||||
any::<note::EncryptedNote>(),
|
||||
any::<note::WrappedNoteKey>(),
|
||||
any::<Groth16Proof>(),
|
||||
)
|
||||
.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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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::<u8>())
|
||||
.prop_filter_map("Valid jubjub::AffinePoint", |b| Self::try_from(b).ok())
|
||||
.boxed()
|
||||
}
|
||||
|
||||
type Strategy = BoxedStrategy<Self>;
|
||||
}
|
||||
|
||||
impl Arbitrary for commitment::ValueCommitment {
|
||||
type Parameters = ();
|
||||
|
||||
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
||||
array::uniform32(any::<u8>())
|
||||
.prop_filter_map("Valid jubjub::AffinePoint", |b| Self::try_from(b).ok())
|
||||
.boxed()
|
||||
}
|
||||
|
||||
type Strategy = BoxedStrategy<Self>;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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::<u8>())
|
||||
.prop_filter_map("Valid jubjub::AffinePoint", |b| Self::try_from(b).ok())
|
||||
.boxed()
|
||||
}
|
||||
|
||||
type Strategy = BoxedStrategy<Self>;
|
||||
}
|
||||
|
|
|
@ -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<Value = Self> {
|
||||
pub fn v1_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
|
||||
(
|
||||
vec(any::<transparent::Input>(), 0..10),
|
||||
transparent::Input::vec_strategy(ledger_state, 10),
|
||||
vec(any::<transparent::Output>(), 0..10),
|
||||
any::<LockTime>(),
|
||||
)
|
||||
|
@ -28,9 +33,9 @@ impl Transaction {
|
|||
}
|
||||
|
||||
/// Generate a proptest strategy for V2 Transactions
|
||||
pub fn v2_strategy() -> impl Strategy<Value = Self> {
|
||||
pub fn v2_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
|
||||
(
|
||||
vec(any::<transparent::Input>(), 0..10),
|
||||
transparent::Input::vec_strategy(ledger_state, 10),
|
||||
vec(any::<transparent::Output>(), 0..10),
|
||||
any::<LockTime>(),
|
||||
option::of(any::<JoinSplitData<Bctv14Proof>>()),
|
||||
|
@ -47,9 +52,9 @@ impl Transaction {
|
|||
}
|
||||
|
||||
/// Generate a proptest strategy for V3 Transactions
|
||||
pub fn v3_strategy() -> impl Strategy<Value = Self> {
|
||||
pub fn v3_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
|
||||
(
|
||||
vec(any::<transparent::Input>(), 0..10),
|
||||
transparent::Input::vec_strategy(ledger_state, 10),
|
||||
vec(any::<transparent::Output>(), 0..10),
|
||||
any::<LockTime>(),
|
||||
any::<block::Height>(),
|
||||
|
@ -68,9 +73,9 @@ impl Transaction {
|
|||
}
|
||||
|
||||
/// Generate a proptest strategy for V4 Transactions
|
||||
pub fn v4_strategy() -> impl Strategy<Value = Self> {
|
||||
pub fn v4_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
|
||||
(
|
||||
vec(any::<transparent::Input>(), 0..10),
|
||||
transparent::Input::vec_strategy(ledger_state, 10),
|
||||
vec(any::<transparent::Output>(), 0..10),
|
||||
any::<LockTime>(),
|
||||
any::<block::Height>(),
|
||||
|
@ -99,6 +104,25 @@ impl Transaction {
|
|||
)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
pub fn vec_strategy(
|
||||
mut ledger_state: LedgerState,
|
||||
len: usize,
|
||||
) -> BoxedStrategy<Vec<Arc<Self>>> {
|
||||
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<Self>;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<Vec<Self>> {
|
||||
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<block::Height>;
|
||||
|
||||
fn arbitrary_with(height: Self::Parameters) -> Self::Strategy {
|
||||
if let Some(height) = height {
|
||||
(vec(any::<u8>(), 0..95), any::<u32>())
|
||||
.prop_map(move |(data, sequence)| Input::Coinbase {
|
||||
height,
|
||||
data: CoinbaseData(data),
|
||||
sequence,
|
||||
})
|
||||
.boxed()
|
||||
} else {
|
||||
(any::<OutPoint>(), any::<Script>(), any::<u32>())
|
||||
.prop_map(|(outpoint, unlock_script, sequence)| {
|
||||
Input::PrevOut {
|
||||
outpoint,
|
||||
unlock_script,
|
||||
sequence,
|
||||
}
|
||||
.prop_map(|(outpoint, unlock_script, sequence)| Input::PrevOut {
|
||||
outpoint,
|
||||
unlock_script,
|
||||
sequence,
|
||||
})
|
||||
.boxed(),
|
||||
(
|
||||
any::<block::Height>(),
|
||||
vec(any::<u8>(), 0..95),
|
||||
any::<u32>()
|
||||
)
|
||||
.prop_map(|(height, data, sequence)| {
|
||||
Input::Coinbase {
|
||||
height,
|
||||
data: CoinbaseData(data),
|
||||
sequence,
|
||||
}
|
||||
})
|
||||
.boxed(),
|
||||
]
|
||||
.boxed()
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
type Strategy = BoxedStrategy<Self>;
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
use zebra_test::prelude::*;
|
||||
|
||||
use crate::{block, parameters::Network, LedgerState};
|
||||
|
||||
use super::Input;
|
||||
|
||||
#[test]
|
||||
fn coinbase_has_height() -> Result<()> {
|
||||
zebra_test::init();
|
||||
|
||||
let strategy =
|
||||
any::<block::Height>().prop_flat_map(|height| Input::arbitrary_with(Some(height)));
|
||||
|
||||
proptest!(|(input in strategy)| {
|
||||
let is_coinbase = matches!(input, Input::Coinbase { .. });
|
||||
prop_assert!(is_coinbase);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_coinbase_vecs_only_have_coinbase_input() -> Result<()> {
|
||||
zebra_test::init();
|
||||
|
||||
let max_size = 100;
|
||||
let strategy = any::<block::Height>()
|
||||
.prop_map(|tip_height| LedgerState {
|
||||
tip_height,
|
||||
is_coinbase: true,
|
||||
network: Network::Mainnet,
|
||||
})
|
||||
.prop_flat_map(|ledger_state| Input::vec_strategy(ledger_state, max_size));
|
||||
|
||||
proptest!(|(inputs in strategy)| {
|
||||
let len = inputs.len();
|
||||
for (ind, input) in inputs.into_iter().enumerate() {
|
||||
let is_coinbase = matches!(input, Input::Coinbase { .. });
|
||||
if ind == 0 {
|
||||
prop_assert!(is_coinbase);
|
||||
prop_assert_eq!(1, len);
|
||||
} else {
|
||||
prop_assert!(!is_coinbase);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -20,7 +20,8 @@ use primitive_types::U256;
|
|||
|
||||
#[cfg(any(test, feature = "proptest-impl"))]
|
||||
use proptest_derive::Arbitrary;
|
||||
#[cfg(tests)]
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// A 32-bit "compact bits" value, which represents the difficulty threshold for
|
||||
|
|
|
@ -33,3 +33,4 @@ once_cell = "1.4"
|
|||
spandoc = "0.2"
|
||||
tempdir = "0.3.7"
|
||||
tokio = { version = "0.2.22", features = ["full"] }
|
||||
proptest = "0.10.1"
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
#![allow(dead_code)]
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::BTreeSet,
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
|
||||
fmt,
|
||||
ops::Deref,
|
||||
sync::Arc,
|
||||
};
|
||||
|
@ -20,7 +20,7 @@ use zebra_chain::{
|
|||
use crate::service::QueuedBlock;
|
||||
|
||||
/// The state of the chains in memory, incuding queued blocks.
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Default)]
|
||||
pub struct NonFinalizedState {
|
||||
/// Verified, non-finalized chains.
|
||||
chain_set: BTreeSet<Chain>,
|
||||
|
@ -29,7 +29,7 @@ pub struct NonFinalizedState {
|
|||
}
|
||||
|
||||
/// A queue of blocks, awaiting the arrival of parent blocks.
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Default)]
|
||||
struct QueuedBlocks {
|
||||
/// Blocks awaiting their parent blocks for contextual verification.
|
||||
blocks: HashMap<block::Hash, QueuedBlock>,
|
||||
|
@ -57,7 +57,7 @@ impl NonFinalizedState {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Default, Clone)]
|
||||
struct Chain {
|
||||
blocks: BTreeMap<block::Height, Arc<Block>>,
|
||||
height_by_hash: HashMap<block::Hash, block::Height>,
|
||||
|
@ -459,3 +459,187 @@ impl Ord for Chain {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use transaction::Transaction;
|
||||
|
||||
use std::{env, mem};
|
||||
|
||||
use zebra_chain::serialization::ZcashDeserializeInto;
|
||||
use zebra_chain::{
|
||||
parameters::{Network, NetworkUpgrade},
|
||||
LedgerState,
|
||||
};
|
||||
use zebra_test::prelude::*;
|
||||
|
||||
use self::assert_eq;
|
||||
use super::*;
|
||||
|
||||
/// Helper trait for constructing "valid" looking chains of blocks
|
||||
trait FakeChainHelper {
|
||||
fn make_fake_child(&self) -> Arc<Block>;
|
||||
}
|
||||
|
||||
impl FakeChainHelper for Block {
|
||||
fn make_fake_child(&self) -> Arc<Block> {
|
||||
let parent_hash = self.hash();
|
||||
let mut child = Block::clone(self);
|
||||
let mut transactions = mem::take(&mut child.transactions);
|
||||
let mut tx = transactions.remove(0);
|
||||
|
||||
let input = match Arc::make_mut(&mut tx) {
|
||||
Transaction::V1 { inputs, .. } => &mut inputs[0],
|
||||
Transaction::V2 { inputs, .. } => &mut inputs[0],
|
||||
Transaction::V3 { inputs, .. } => &mut inputs[0],
|
||||
Transaction::V4 { inputs, .. } => &mut inputs[0],
|
||||
};
|
||||
|
||||
match input {
|
||||
transparent::Input::Coinbase { height, .. } => height.0 += 1,
|
||||
_ => panic!("block must have a coinbase height to create a child"),
|
||||
}
|
||||
|
||||
child.transactions.push(tx);
|
||||
child.header.previous_block_hash = parent_hash;
|
||||
|
||||
Arc::new(child)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construct_empty() {
|
||||
zebra_test::init();
|
||||
let _chain = Chain::default();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construct_single() -> Result<()> {
|
||||
zebra_test::init();
|
||||
let block = zebra_test::vectors::BLOCK_MAINNET_434873_BYTES.zcash_deserialize_into()?;
|
||||
|
||||
let mut chain = Chain::default();
|
||||
chain.push(block);
|
||||
|
||||
assert_eq!(1, chain.blocks.len());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construct_many() -> Result<()> {
|
||||
zebra_test::init();
|
||||
|
||||
let mut block: Arc<Block> =
|
||||
zebra_test::vectors::BLOCK_MAINNET_434873_BYTES.zcash_deserialize_into()?;
|
||||
let mut blocks = vec![];
|
||||
|
||||
while blocks.len() < 100 {
|
||||
let next_block = block.make_fake_child();
|
||||
blocks.push(block);
|
||||
block = next_block;
|
||||
}
|
||||
|
||||
let mut chain = Chain::default();
|
||||
|
||||
for block in blocks {
|
||||
chain.push(block);
|
||||
}
|
||||
|
||||
assert_eq!(100, chain.blocks.len());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn arbitrary_chain(height: block::Height) -> BoxedStrategy<Vec<Arc<Block>>> {
|
||||
Block::partial_chain_strategy(
|
||||
LedgerState {
|
||||
tip_height: height,
|
||||
is_coinbase: true,
|
||||
network: Network::Mainnet,
|
||||
},
|
||||
100,
|
||||
)
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
fn arbitrary_chain_and_count()
|
||||
(chain in arbitrary_chain(NetworkUpgrade::Blossom.activation_height(Network::Mainnet).unwrap()))
|
||||
(count in 1..chain.len(), chain in Just(chain)) -> (NoDebug<Vec<Arc<Block>>>, usize)
|
||||
{
|
||||
(NoDebug(chain), count)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forked_equals_pushed() -> Result<()> {
|
||||
zebra_test::init();
|
||||
|
||||
proptest!(ProptestConfig::with_cases(env::var("PROPTEST_CASES")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(1)),
|
||||
|((chain, count) in arbitrary_chain_and_count())| {
|
||||
let chain = chain.0;
|
||||
let fork_tip_hash = chain[count - 1].hash();
|
||||
let mut full_chain = Chain::default();
|
||||
let mut partial_chain = Chain::default();
|
||||
|
||||
for block in chain.iter().take(count) {
|
||||
partial_chain.push(block.clone());
|
||||
}
|
||||
|
||||
for block in chain {
|
||||
full_chain.push(block);
|
||||
}
|
||||
|
||||
let forked = full_chain.fork(fork_tip_hash).expect("hash is present");
|
||||
|
||||
prop_assert_eq!(forked.blocks.len(), partial_chain.blocks.len());
|
||||
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finalized_equals_pushed() -> Result<()> {
|
||||
zebra_test::init();
|
||||
|
||||
proptest!(ProptestConfig::with_cases(env::var("PROPTEST_CASES")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(1)),
|
||||
|((chain, end_count) in arbitrary_chain_and_count())| {
|
||||
let chain = chain.0;
|
||||
let finalized_count = chain.len() - end_count;
|
||||
let mut full_chain = Chain::default();
|
||||
let mut partial_chain = Chain::default();
|
||||
|
||||
for block in chain.iter().skip(finalized_count) {
|
||||
partial_chain.push(block.clone());
|
||||
}
|
||||
|
||||
for block in chain {
|
||||
full_chain.push(block);
|
||||
}
|
||||
|
||||
for _ in 0..finalized_count {
|
||||
let _finalized = full_chain.pop_root();
|
||||
}
|
||||
|
||||
prop_assert_eq!(full_chain.blocks.len(), partial_chain.blocks.len());
|
||||
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct NoDebug<T>(T);
|
||||
|
||||
impl<T> fmt::Debug for NoDebug<Vec<T>> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}, len={}", std::any::type_name::<T>(), self.0.len())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,7 +72,6 @@ pub fn init() {
|
|||
})
|
||||
});
|
||||
}))
|
||||
.display_env_section(false)
|
||||
.panic_message(SkipTestReturnedErrPanicMessages)
|
||||
.install()
|
||||
.unwrap();
|
||||
|
|
Loading…
Reference in New Issue