-
Notifications
You must be signed in to change notification settings - Fork 106
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
Generate test chains that pass basic chain consistency tests #2221
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ use proptest::{ | |
use std::sync::Arc; | ||
|
||
use crate::{ | ||
block, | ||
parameters::{Network, NetworkUpgrade, GENESIS_PREVIOUS_BLOCK_HASH}, | ||
serialization, | ||
work::{difficulty::CompactDifficulty, equihash}, | ||
|
@@ -17,21 +18,21 @@ use super::*; | |
#[non_exhaustive] | ||
/// The configuration data for proptest when generating arbitrary chains | ||
pub struct LedgerState { | ||
/// The tip height of the block or start of the chain. | ||
/// The height of the generated block, or the start height of the generated chain. | ||
/// | ||
/// To get the network upgrade, use the `network_upgrade` method. | ||
/// | ||
/// If `network_upgrade_override` is not set, the network upgrade is derived | ||
/// from the height and network. | ||
pub tip_height: Height, | ||
/// from the `height` and `network`. | ||
pub height: Height, | ||
|
||
/// The network to generate fake blocks for. | ||
pub network: Network, | ||
|
||
/// Overrides the network upgrade calculated from `tip_height` and `network`. | ||
/// Overrides the network upgrade calculated from `height` and `network`. | ||
/// | ||
/// To get the network upgrade, use the `network_upgrade` method. | ||
pub network_upgrade_override: Option<NetworkUpgrade>, | ||
network_upgrade_override: Option<NetworkUpgrade>, | ||
|
||
/// Generate coinbase transactions. | ||
/// | ||
|
@@ -42,94 +43,181 @@ pub struct LedgerState { | |
/// transaction. | ||
pub(crate) has_coinbase: bool, | ||
|
||
/// Should this block have a genesis (all-zeroes) previous block hash? | ||
/// | ||
/// In Zebra's proptests, the previous block hash can be overriden with | ||
/// genesis at any block height. | ||
genesis_previous_block_hash_override: bool, | ||
/// Overrides the previous block hashes in blocks generated by this ledger. | ||
previous_block_hash_override: Option<block::Hash>, | ||
} | ||
|
||
/// Overrides for arbitrary [`LedgerState`]s. | ||
#[derive(Debug, Clone, Copy)] | ||
pub struct LedgerStateOverride { | ||
/// Regardless of tip height and network, every block has features from this | ||
/// network upgrade. | ||
pub network_upgrade_override: Option<NetworkUpgrade>, | ||
|
||
/// Every block has exactly one coinbase transaction. | ||
/// Transactions are always coinbase transactions. | ||
pub always_has_coinbase: bool, | ||
|
||
/// Every chain starts at this block. Single blocks have this height. | ||
pub height_override: Option<Height>, | ||
|
||
/// Every chain starts with a block with this previous block hash. | ||
/// Single blocks have this previous block hash. | ||
pub previous_block_hash_override: Option<block::Hash>, | ||
} | ||
|
||
impl LedgerState { | ||
/// Returns the default strategy for creating arbitrary `LedgerState`s. | ||
pub fn default_strategy() -> BoxedStrategy<Self> { | ||
Self::arbitrary_with(LedgerStateOverride::default()) | ||
} | ||
|
||
/// Returns a strategy for creating arbitrary `LedgerState`s, without any | ||
/// overrides. | ||
pub fn no_override_strategy() -> BoxedStrategy<Self> { | ||
Self::arbitrary_with(LedgerStateOverride { | ||
network_upgrade_override: None, | ||
always_has_coinbase: false, | ||
height_override: None, | ||
previous_block_hash_override: None, | ||
}) | ||
} | ||
|
||
/// Returns a strategy for creating `LedgerState`s with features from | ||
/// `network_upgrade_override`. | ||
/// | ||
/// These featues ignore the actual tip height and network). | ||
pub fn network_upgrade_strategy( | ||
network_upgrade_override: NetworkUpgrade, | ||
) -> BoxedStrategy<Self> { | ||
Self::arbitrary_with(LedgerStateOverride { | ||
network_upgrade_override: Some(network_upgrade_override), | ||
always_has_coinbase: false, | ||
height_override: None, | ||
previous_block_hash_override: None, | ||
}) | ||
} | ||
|
||
/// Returns a strategy for creating `LedgerState`s that always have coinbase | ||
/// transactions. | ||
/// | ||
/// Also applies `network_upgrade_override`, if present. | ||
pub fn coinbase_strategy( | ||
network_upgrade_override: impl Into<Option<NetworkUpgrade>>, | ||
) -> BoxedStrategy<Self> { | ||
Self::arbitrary_with(LedgerStateOverride { | ||
network_upgrade_override: network_upgrade_override.into(), | ||
always_has_coinbase: true, | ||
height_override: None, | ||
previous_block_hash_override: None, | ||
}) | ||
} | ||
|
||
/// Returns a strategy for creating `LedgerState`s that start with a genesis | ||
/// block. | ||
/// | ||
/// These strategies also have coinbase transactions, and an optional network | ||
/// upgrade override. | ||
/// | ||
/// Use the `Genesis` network upgrade to get a random genesis block, with | ||
/// Zcash genesis features. | ||
pub fn genesis_strategy( | ||
network_upgrade_override: impl Into<Option<NetworkUpgrade>>, | ||
) -> BoxedStrategy<Self> { | ||
Self::arbitrary_with(LedgerStateOverride { | ||
network_upgrade_override: network_upgrade_override.into(), | ||
always_has_coinbase: true, | ||
height_override: Some(Height(0)), | ||
previous_block_hash_override: Some(GENESIS_PREVIOUS_BLOCK_HASH), | ||
}) | ||
} | ||
|
||
/// Returns the network upgrade for this ledger state. | ||
/// | ||
/// If `network_upgrade_override` is set, it replaces the upgrade calculated | ||
/// using `tip_height` and `network`. | ||
/// using `height` and `network`. | ||
pub fn network_upgrade(&self) -> NetworkUpgrade { | ||
if let Some(network_upgrade_override) = self.network_upgrade_override { | ||
network_upgrade_override | ||
} else { | ||
NetworkUpgrade::current(self.network, self.tip_height) | ||
NetworkUpgrade::current(self.network, self.height) | ||
} | ||
} | ||
} | ||
|
||
/// Should this block have a genesis (all-zeroes) previous block hash? | ||
/// | ||
/// In Zebra's proptests, the previous block hash can be overriden with | ||
/// genesis at any block height. | ||
pub fn use_genesis_previous_block_hash(&self) -> bool { | ||
self.tip_height == Height(0) || self.genesis_previous_block_hash_override | ||
} | ||
impl Default for LedgerState { | ||
fn default() -> Self { | ||
// TODO: stop having a default network | ||
let default_network = Network::default(); | ||
let default_override = LedgerStateOverride::default(); | ||
|
||
/// Returns a strategy for creating `LedgerState`s that always have coinbase | ||
/// transactions. | ||
pub fn coinbase_strategy() -> BoxedStrategy<Self> { | ||
Self::arbitrary_with(true) | ||
let most_recent_nu = NetworkUpgrade::current(default_network, Height::MAX); | ||
let most_recent_activation_height = | ||
most_recent_nu.activation_height(default_network).unwrap(); | ||
|
||
Self { | ||
height: most_recent_activation_height, | ||
network: default_network, | ||
network_upgrade_override: default_override.network_upgrade_override, | ||
has_coinbase: default_override.always_has_coinbase, | ||
previous_block_hash_override: default_override.previous_block_hash_override, | ||
} | ||
} | ||
} | ||
|
||
impl Default for LedgerState { | ||
impl Default for LedgerStateOverride { | ||
fn default() -> Self { | ||
let network = Network::Mainnet; | ||
let most_recent_nu = NetworkUpgrade::current(network, Height::MAX); | ||
let most_recent_activation_height = most_recent_nu.activation_height(network).unwrap(); | ||
let default_network = Network::default(); | ||
|
||
// TODO: dynamically select any future network upgrade (#1974) | ||
let nu5_activation_height = NetworkUpgrade::Nu5.activation_height(network); | ||
let nu5_activation_height = NetworkUpgrade::Nu5.activation_height(default_network); | ||
let nu5_override = if nu5_activation_height.is_some() { | ||
None | ||
} else { | ||
Some(NetworkUpgrade::Nu5) | ||
}; | ||
|
||
Self { | ||
tip_height: most_recent_activation_height, | ||
network, | ||
LedgerStateOverride { | ||
network_upgrade_override: nu5_override, | ||
has_coinbase: true, | ||
// start each chain with a genesis previous block hash, regardless of height | ||
genesis_previous_block_hash_override: true, | ||
always_has_coinbase: true, | ||
height_override: None, | ||
previous_block_hash_override: None, | ||
} | ||
} | ||
} | ||
|
||
impl Arbitrary for LedgerState { | ||
type Parameters = bool; | ||
type Parameters = LedgerStateOverride; | ||
|
||
/// Generate an arbitrary `LedgerState`. | ||
/// | ||
/// The default strategy arbitrarily skips some coinbase transactions. To | ||
/// override, use `LedgerState::coinbase_strategy`. | ||
fn arbitrary_with(require_coinbase: Self::Parameters) -> Self::Strategy { | ||
/// The default strategy arbitrarily skips some coinbase transactions, and | ||
/// has an arbitrary start height. To override, use: | ||
/// - [`LedgerState::coinbase_strategy`], or | ||
/// - [`LedgerState::genesis_strategy`]. | ||
fn arbitrary_with(ledger_override: Self::Parameters) -> Self::Strategy { | ||
( | ||
any::<Height>(), | ||
any::<Network>(), | ||
any::<bool>(), | ||
any::<bool>(), | ||
) | ||
.prop_map(move |(tip_height, network, nu5_override, has_coinbase)| { | ||
.prop_map(move |(height, network, nu5_override, has_coinbase)| { | ||
// TODO: dynamically select any future network upgrade (#1974) | ||
let network_upgrade_override = if nu5_override { | ||
let nu5_override = if nu5_override { | ||
Some(NetworkUpgrade::Nu5) | ||
} else { | ||
None | ||
}; | ||
|
||
LedgerState { | ||
tip_height, | ||
height: ledger_override.height_override.unwrap_or(height), | ||
network, | ||
network_upgrade_override, | ||
has_coinbase: require_coinbase || has_coinbase, | ||
genesis_previous_block_hash_override: true, | ||
network_upgrade_override: ledger_override | ||
.network_upgrade_override | ||
.or(nu5_override), | ||
has_coinbase: ledger_override.always_has_coinbase || has_coinbase, | ||
previous_block_hash_override: ledger_override.previous_block_hash_override, | ||
} | ||
}) | ||
.boxed() | ||
|
@@ -144,15 +232,10 @@ impl Arbitrary for Block { | |
fn arbitrary_with(ledger_state: Self::Parameters) -> Self::Strategy { | ||
let transactions_strategy = Transaction::vec_strategy(ledger_state, 2); | ||
|
||
(any::<Header>(), transactions_strategy) | ||
.prop_map(move |(mut header, transactions)| { | ||
if ledger_state.genesis_previous_block_hash_override { | ||
header.previous_block_hash = GENESIS_PREVIOUS_BLOCK_HASH; | ||
} | ||
Self { | ||
header, | ||
transactions, | ||
} | ||
(Header::arbitrary_with(ledger_state), transactions_strategy) | ||
.prop_map(move |(header, transactions)| Self { | ||
header, | ||
transactions, | ||
}) | ||
.boxed() | ||
} | ||
|
@@ -164,18 +247,29 @@ impl Block { | |
/// Returns a strategy for creating Vecs of blocks with increasing height of | ||
/// the given length. | ||
pub fn partial_chain_strategy( | ||
init: LedgerState, | ||
mut current: LedgerState, | ||
count: usize, | ||
) -> BoxedStrategy<Vec<Arc<Self>>> { | ||
let mut current = init; | ||
let mut vec = Vec::with_capacity(count); | ||
|
||
// generate block strategies with the correct heights | ||
for _ in 0..count { | ||
vec.push(Block::arbitrary_with(current).prop_map(Arc::new)); | ||
current.tip_height.0 += 1; | ||
current.genesis_previous_block_hash_override = false; | ||
vec.push(Block::arbitrary_with(current)); | ||
current.height.0 += 1; | ||
} | ||
|
||
vec.boxed() | ||
// after the vec strategy generates blocks, update the previous block hashes | ||
vec.prop_map(|mut vec| { | ||
let mut previous_block_hash = None; | ||
for block in vec.iter_mut() { | ||
if let Some(previous_block_hash) = previous_block_hash { | ||
block.header.previous_block_hash = previous_block_hash; | ||
} | ||
previous_block_hash = Some(block.hash()); | ||
} | ||
vec.into_iter().map(Arc::new).collect() | ||
}) | ||
.boxed() | ||
Comment on lines
+261
to
+272
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change fixes up the previous block hashes |
||
} | ||
} | ||
|
||
|
@@ -203,9 +297,9 @@ impl Arbitrary for Commitment { | |
} | ||
|
||
impl Arbitrary for Header { | ||
type Parameters = (); | ||
type Parameters = LedgerState; | ||
|
||
fn arbitrary_with(_args: ()) -> Self::Strategy { | ||
fn arbitrary_with(ledger_state: Self::Parameters) -> Self::Strategy { | ||
( | ||
// version is interpreted as i32 in the spec, so we are limited to i32::MAX here | ||
(4u32..(i32::MAX as u32)), | ||
|
@@ -218,24 +312,34 @@ impl Arbitrary for Header { | |
any::<equihash::Solution>(), | ||
) | ||
.prop_map( | ||
|( | ||
move |( | ||
version, | ||
previous_block_hash, | ||
merkle_root, | ||
commitment_bytes, | ||
time, | ||
difficulty_threshold, | ||
nonce, | ||
solution, | ||
)| Header { | ||
version, | ||
previous_block_hash, | ||
mut previous_block_hash, | ||
merkle_root, | ||
commitment_bytes, | ||
time, | ||
difficulty_threshold, | ||
nonce, | ||
solution, | ||
)| { | ||
if let Some(previous_block_hash_override) = | ||
ledger_state.previous_block_hash_override | ||
{ | ||
previous_block_hash = previous_block_hash_override; | ||
} else if ledger_state.height == Height(0) { | ||
previous_block_hash = GENESIS_PREVIOUS_BLOCK_HASH; | ||
} | ||
Comment on lines
+325
to
+331
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code move makes header generation consistent with block generation |
||
|
||
Header { | ||
version, | ||
previous_block_hash, | ||
merkle_root, | ||
commitment_bytes, | ||
time, | ||
difficulty_threshold, | ||
nonce, | ||
solution, | ||
} | ||
}, | ||
) | ||
.boxed() | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change makes it easier to generate different kinds of chains.