zebra/zebra-chain/src/parameters/network/testnet.rs

689 lines
25 KiB
Rust

//! Types and implementation for Testnet consensus parameters
use std::{collections::BTreeMap, fmt};
use crate::{
block::{self, Height},
parameters::{
constants::{magics, SLOW_START_INTERVAL, SLOW_START_SHIFT},
network_upgrade::TESTNET_ACTIVATION_HEIGHTS,
subsidy::{funding_stream_address_period, FUNDING_STREAM_RECEIVER_DENOMINATOR},
Network, NetworkKind, NetworkUpgrade, NETWORK_UPGRADES_IN_ORDER,
},
work::difficulty::{ExpandedDifficulty, U256},
};
use super::{
magic::Magic,
subsidy::{
FundingStreamReceiver, FundingStreamRecipient, FundingStreams, ParameterSubsidy,
FIRST_HALVING_TESTNET, POST_NU6_FUNDING_STREAMS_MAINNET, POST_NU6_FUNDING_STREAMS_TESTNET,
PRE_NU6_FUNDING_STREAMS_MAINNET, PRE_NU6_FUNDING_STREAMS_TESTNET,
},
};
/// The Regtest NU5 activation height in tests
// TODO: Serialize testnet parameters in Config then remove this and use a configured NU5 activation height.
#[cfg(any(test, feature = "proptest-impl"))]
pub const REGTEST_NU5_ACTIVATION_HEIGHT: u32 = 100;
/// Reserved network names that should not be allowed for configured Testnets.
pub const RESERVED_NETWORK_NAMES: [&str; 6] = [
"Mainnet",
"Testnet",
"Regtest",
"MainnetKind",
"TestnetKind",
"RegtestKind",
];
/// Maximum length for a configured network name.
pub const MAX_NETWORK_NAME_LENGTH: usize = 30;
/// Maximum length for a configured human-readable prefix.
pub const MAX_HRP_LENGTH: usize = 30;
/// The block hash of the Regtest genesis block, `zcash-cli -regtest getblockhash 0`
const REGTEST_GENESIS_HASH: &str =
"029f11d80ef9765602235e1bc9727e3eb6ba20839319f761fee920d63401e327";
/// The block hash of the Testnet genesis block, `zcash-cli -testnet getblockhash 0`
const TESTNET_GENESIS_HASH: &str =
"05a60a92d99d85997cce3b87616c089f6124d7342af37106edc76126334a2c38";
/// Used to validate number of funding stream recipient addresses on configured Testnets.
struct TestnetParameterSubsidyImpl;
impl ParameterSubsidy for TestnetParameterSubsidyImpl {
fn height_for_first_halving(&self) -> Height {
FIRST_HALVING_TESTNET
}
}
/// Configurable funding stream recipient for configured Testnets.
#[derive(Deserialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct ConfiguredFundingStreamRecipient {
/// Funding stream receiver, see [`FundingStreams::recipients`] for more details.
pub receiver: FundingStreamReceiver,
/// The numerator for each funding stream receiver category, see [`FundingStreamRecipient::numerator`] for more details.
pub numerator: u64,
/// Addresses for the funding stream recipient, see [`FundingStreamRecipient::addresses`] for more details.
pub addresses: Option<Vec<String>>,
}
impl ConfiguredFundingStreamRecipient {
/// Converts a [`ConfiguredFundingStreamRecipient`] to a [`FundingStreamReceiver`] and [`FundingStreamRecipient`].
pub fn into_recipient(self) -> (FundingStreamReceiver, FundingStreamRecipient) {
(
self.receiver,
FundingStreamRecipient::new(self.numerator, self.addresses.unwrap_or_default()),
)
}
}
/// Configurable funding streams for configured Testnets.
#[derive(Deserialize, Clone, Default, Debug)]
#[serde(deny_unknown_fields)]
pub struct ConfiguredFundingStreams {
/// Start and end height for funding streams see [`FundingStreams::height_range`] for more details.
pub height_range: Option<std::ops::Range<Height>>,
/// Funding stream recipients, see [`FundingStreams::recipients`] for more details.
pub recipients: Option<Vec<ConfiguredFundingStreamRecipient>>,
}
impl ConfiguredFundingStreams {
/// Converts a [`ConfiguredFundingStreams`] to a [`FundingStreams`], using the provided default values
/// if `height_range` or `recipients` are None.
fn convert_with_default(self, default_funding_streams: FundingStreams) -> FundingStreams {
let height_range = self
.height_range
.unwrap_or(default_funding_streams.height_range().clone());
let recipients = self
.recipients
.map(|recipients| {
recipients
.into_iter()
.map(ConfiguredFundingStreamRecipient::into_recipient)
.collect()
})
.unwrap_or(default_funding_streams.recipients().clone());
assert!(
height_range.start < height_range.end,
"funding stream end height must be above start height"
);
let funding_streams = FundingStreams::new(height_range.clone(), recipients);
// check that receivers have enough addresses.
let expected_min_num_addresses =
1u32.checked_add(funding_stream_address_period(
height_range
.end
.previous()
.expect("end height must be above start height and genesis height"),
&TestnetParameterSubsidyImpl,
))
.expect("no overflow should happen in this sum")
.checked_sub(funding_stream_address_period(
height_range.start,
&TestnetParameterSubsidyImpl,
))
.expect("no overflow should happen in this sub") as usize;
for (&receiver, recipient) in funding_streams.recipients() {
if receiver == FundingStreamReceiver::Deferred {
// The `Deferred` receiver doesn't need any addresses.
continue;
}
assert!(
recipient.addresses().len() >= expected_min_num_addresses,
"recipients must have a sufficient number of addresses for height range, \
minimum num addresses required: {expected_min_num_addresses}"
);
for address in recipient.addresses() {
assert_eq!(
address.network_kind(),
NetworkKind::Testnet,
"configured funding stream addresses must be for Testnet"
);
}
}
// check that sum of receiver numerators is valid.
let sum_numerators: u64 = funding_streams
.recipients()
.values()
.map(|r| r.numerator())
.sum();
assert!(
sum_numerators <= FUNDING_STREAM_RECEIVER_DENOMINATOR,
"sum of funding stream numerators must not be \
greater than denominator of {FUNDING_STREAM_RECEIVER_DENOMINATOR}"
);
funding_streams
}
}
/// Configurable activation heights for Regtest and configured Testnets.
#[derive(Deserialize, Default, Clone)]
#[serde(rename_all = "PascalCase", deny_unknown_fields)]
pub struct ConfiguredActivationHeights {
/// Activation height for `BeforeOverwinter` network upgrade.
pub before_overwinter: Option<u32>,
/// Activation height for `Overwinter` network upgrade.
pub overwinter: Option<u32>,
/// Activation height for `Sapling` network upgrade.
pub sapling: Option<u32>,
/// Activation height for `Blossom` network upgrade.
pub blossom: Option<u32>,
/// Activation height for `Heartwood` network upgrade.
pub heartwood: Option<u32>,
/// Activation height for `Canopy` network upgrade.
pub canopy: Option<u32>,
/// Activation height for `NU5` network upgrade.
#[serde(rename = "NU5")]
pub nu5: Option<u32>,
/// Activation height for `NU6` network upgrade.
#[serde(rename = "NU6")]
pub nu6: Option<u32>,
}
/// Builder for the [`Parameters`] struct.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParametersBuilder {
/// The name of this network to be used by the `Display` trait impl.
network_name: String,
/// The network magic, acts as an identifier for the network.
network_magic: Magic,
/// The genesis block hash
genesis_hash: block::Hash,
/// The network upgrade activation heights for this network, see [`Parameters::activation_heights`] for more details.
activation_heights: BTreeMap<Height, NetworkUpgrade>,
/// Slow start interval for this network
slow_start_interval: Height,
/// Pre-NU6 funding streams for this network
pre_nu6_funding_streams: FundingStreams,
/// Post-NU6 funding streams for this network
post_nu6_funding_streams: FundingStreams,
/// Target difficulty limit for this network
target_difficulty_limit: ExpandedDifficulty,
/// A flag for disabling proof-of-work checks when Zebra is validating blocks
disable_pow: bool,
}
impl Default for ParametersBuilder {
/// Creates a [`ParametersBuilder`] with all of the default Testnet parameters except `network_name`.
fn default() -> Self {
Self {
network_name: "UnknownTestnet".to_string(),
network_magic: magics::TESTNET,
// # Correctness
//
// `Genesis` network upgrade activation height must always be 0
activation_heights: TESTNET_ACTIVATION_HEIGHTS.iter().cloned().collect(),
genesis_hash: TESTNET_GENESIS_HASH
.parse()
.expect("hard-coded hash parses"),
slow_start_interval: SLOW_START_INTERVAL,
// Testnet PoWLimit is defined as `2^251 - 1` on page 73 of the protocol specification:
// <https://zips.z.cash/protocol/protocol.pdf>
//
// `zcashd` converts the PoWLimit into a compact representation before
// using it to perform difficulty filter checks.
//
// The Zcash specification converts to compact for the default difficulty
// filter, but not for testnet minimum difficulty blocks. (ZIP 205 and
// ZIP 208 don't specify this conversion either.) See #1277 for details.
target_difficulty_limit: ExpandedDifficulty::from((U256::one() << 251) - 1)
.to_compact()
.to_expanded()
.expect("difficulty limits are valid expanded values"),
disable_pow: false,
pre_nu6_funding_streams: PRE_NU6_FUNDING_STREAMS_TESTNET.clone(),
post_nu6_funding_streams: POST_NU6_FUNDING_STREAMS_TESTNET.clone(),
}
}
}
impl ParametersBuilder {
/// Sets the network name to be used in the [`Parameters`] being built.
pub fn with_network_name(mut self, network_name: impl fmt::Display) -> Self {
self.network_name = network_name.to_string();
assert!(
!RESERVED_NETWORK_NAMES.contains(&self.network_name.as_str()),
"cannot use reserved network name '{network_name}' as configured Testnet name, reserved names: {RESERVED_NETWORK_NAMES:?}"
);
assert!(
self.network_name.len() <= MAX_NETWORK_NAME_LENGTH,
"network name {network_name} is too long, must be {MAX_NETWORK_NAME_LENGTH} characters or less"
);
assert!(
self.network_name
.chars()
.all(|x| x.is_alphanumeric() || x == '_'),
"network name must include only alphanumeric characters or '_'"
);
self
}
/// Sets the network name to be used in the [`Parameters`] being built.
pub fn with_network_magic(mut self, network_magic: Magic) -> Self {
assert!(
[magics::MAINNET, magics::REGTEST]
.into_iter()
.all(|reserved_magic| network_magic != reserved_magic),
"network magic should be distinct from reserved network magics"
);
self.network_magic = network_magic;
self
}
/// Parses the hex-encoded block hash and sets it as the genesis hash in the [`Parameters`] being built.
pub fn with_genesis_hash(mut self, genesis_hash: impl fmt::Display) -> Self {
self.genesis_hash = genesis_hash
.to_string()
.parse()
.expect("configured genesis hash must parse");
self
}
/// Checks that the provided network upgrade activation heights are in the correct order, then
/// sets them as the new network upgrade activation heights.
pub fn with_activation_heights(
mut self,
ConfiguredActivationHeights {
before_overwinter,
overwinter,
sapling,
blossom,
heartwood,
canopy,
nu5,
nu6,
}: ConfiguredActivationHeights,
) -> Self {
use NetworkUpgrade::*;
// # Correctness
//
// These must be in order so that later network upgrades overwrite prior ones
// if multiple network upgrades are configured with the same activation height.
let activation_heights: BTreeMap<_, _> = before_overwinter
.into_iter()
.map(|h| (h, BeforeOverwinter))
.chain(overwinter.into_iter().map(|h| (h, Overwinter)))
.chain(sapling.into_iter().map(|h| (h, Sapling)))
.chain(blossom.into_iter().map(|h| (h, Blossom)))
.chain(heartwood.into_iter().map(|h| (h, Heartwood)))
.chain(canopy.into_iter().map(|h| (h, Canopy)))
.chain(nu5.into_iter().map(|h| (h, Nu5)))
.chain(nu6.into_iter().map(|h| (h, Nu6)))
.map(|(h, nu)| (h.try_into().expect("activation height must be valid"), nu))
.collect();
let network_upgrades: Vec<_> = activation_heights.iter().map(|(_h, &nu)| nu).collect();
// Check that the provided network upgrade activation heights are in the same order by height as the default testnet activation heights
let mut activation_heights_iter = activation_heights.iter();
for expected_network_upgrade in NETWORK_UPGRADES_IN_ORDER {
if !network_upgrades.contains(&expected_network_upgrade) {
continue;
} else if let Some((&height, &network_upgrade)) = activation_heights_iter.next() {
assert_ne!(
height,
Height(0),
"Height(0) is reserved for the `Genesis` upgrade"
);
assert!(
network_upgrade == expected_network_upgrade,
"network upgrades must be activated in order, the correct order is {NETWORK_UPGRADES_IN_ORDER:?}"
);
}
}
// # Correctness
//
// Height(0) must be reserved for the `NetworkUpgrade::Genesis`.
self.activation_heights.split_off(&Height(1));
self.activation_heights.extend(activation_heights);
self
}
/// Sets the slow start interval to be used in the [`Parameters`] being built.
pub fn with_slow_start_interval(mut self, slow_start_interval: Height) -> Self {
self.slow_start_interval = slow_start_interval;
self
}
/// Sets pre-NU6 funding streams to be used in the [`Parameters`] being built.
pub fn with_pre_nu6_funding_streams(
mut self,
funding_streams: ConfiguredFundingStreams,
) -> Self {
self.pre_nu6_funding_streams =
funding_streams.convert_with_default(PRE_NU6_FUNDING_STREAMS_TESTNET.clone());
self
}
/// Sets post-NU6 funding streams to be used in the [`Parameters`] being built.
pub fn with_post_nu6_funding_streams(
mut self,
funding_streams: ConfiguredFundingStreams,
) -> Self {
self.post_nu6_funding_streams =
funding_streams.convert_with_default(POST_NU6_FUNDING_STREAMS_TESTNET.clone());
self
}
/// Sets the target difficulty limit to be used in the [`Parameters`] being built.
// TODO: Accept a hex-encoded String instead?
pub fn with_target_difficulty_limit(
mut self,
target_difficulty_limit: impl Into<ExpandedDifficulty>,
) -> Self {
self.target_difficulty_limit = target_difficulty_limit
.into()
.to_compact()
.to_expanded()
.expect("difficulty limits are valid expanded values");
self
}
/// Sets the `disable_pow` flag to be used in the [`Parameters`] being built.
pub fn with_disable_pow(mut self, disable_pow: bool) -> Self {
self.disable_pow = disable_pow;
self
}
/// Converts the builder to a [`Parameters`] struct
pub fn finish(self) -> Parameters {
let Self {
network_name,
network_magic,
genesis_hash,
activation_heights,
slow_start_interval,
pre_nu6_funding_streams,
post_nu6_funding_streams,
target_difficulty_limit,
disable_pow,
} = self;
Parameters {
network_name,
network_magic,
genesis_hash,
activation_heights,
slow_start_interval,
slow_start_shift: Height(slow_start_interval.0 / 2),
pre_nu6_funding_streams,
post_nu6_funding_streams,
target_difficulty_limit,
disable_pow,
}
}
/// Converts the builder to a configured [`Network::Testnet`]
pub fn to_network(self) -> Network {
Network::new_configured_testnet(self.finish())
}
/// Returns true if these [`Parameters`] should be compatible with the default Testnet parameters.
pub fn is_compatible_with_default_parameters(&self) -> bool {
let Self {
network_name: _,
network_magic,
genesis_hash,
activation_heights,
slow_start_interval,
pre_nu6_funding_streams,
post_nu6_funding_streams,
target_difficulty_limit,
disable_pow,
} = Self::default();
self.activation_heights == activation_heights
&& self.network_magic == network_magic
&& self.genesis_hash == genesis_hash
&& self.slow_start_interval == slow_start_interval
&& self.pre_nu6_funding_streams == pre_nu6_funding_streams
&& self.post_nu6_funding_streams == post_nu6_funding_streams
&& self.target_difficulty_limit == target_difficulty_limit
&& self.disable_pow == disable_pow
}
}
/// Network consensus parameters for test networks such as Regtest and the default Testnet.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Parameters {
/// The name of this network to be used by the `Display` trait impl.
network_name: String,
/// The network magic, acts as an identifier for the network.
network_magic: Magic,
/// The genesis block hash
genesis_hash: block::Hash,
/// The network upgrade activation heights for this network.
///
/// Note: This value is ignored by `Network::activation_list()` when `zebra-chain` is
/// compiled with the `zebra-test` feature flag AND the `TEST_FAKE_ACTIVATION_HEIGHTS`
/// environment variable is set.
activation_heights: BTreeMap<Height, NetworkUpgrade>,
/// Slow start interval for this network
slow_start_interval: Height,
/// Slow start shift for this network, always half the slow start interval
slow_start_shift: Height,
/// Pre-NU6 funding streams for this network
pre_nu6_funding_streams: FundingStreams,
/// Post-NU6 funding streams for this network
post_nu6_funding_streams: FundingStreams,
/// Target difficulty limit for this network
target_difficulty_limit: ExpandedDifficulty,
/// A flag for disabling proof-of-work checks when Zebra is validating blocks
disable_pow: bool,
}
impl Default for Parameters {
/// Returns an instance of the default public testnet [`Parameters`].
fn default() -> Self {
Self {
network_name: "Testnet".to_string(),
..Self::build().finish()
}
}
}
impl Parameters {
/// Creates a new [`ParametersBuilder`].
pub fn build() -> ParametersBuilder {
ParametersBuilder::default()
}
/// Accepts a [`ConfiguredActivationHeights`].
///
/// Creates an instance of [`Parameters`] with `Regtest` values.
pub fn new_regtest(
nu5_activation_height: Option<u32>,
nu6_activation_height: Option<u32>,
) -> Self {
#[cfg(any(test, feature = "proptest-impl"))]
let nu5_activation_height = nu5_activation_height.or(Some(100));
Self {
network_name: "Regtest".to_string(),
network_magic: magics::REGTEST,
..Self::build()
.with_genesis_hash(REGTEST_GENESIS_HASH)
// This value is chosen to match zcashd, see: <https://github.com/zcash/zcash/blob/master/src/chainparams.cpp#L654>
.with_target_difficulty_limit(U256::from_big_endian(&[0x0f; 32]))
.with_disable_pow(true)
.with_slow_start_interval(Height::MIN)
// Removes default Testnet activation heights if not configured,
// most network upgrades are disabled by default for Regtest in zcashd
.with_activation_heights(ConfiguredActivationHeights {
canopy: Some(1),
nu5: nu5_activation_height,
nu6: nu6_activation_height,
..Default::default()
})
.finish()
}
}
/// Returns true if the instance of [`Parameters`] represents the default public Testnet.
pub fn is_default_testnet(&self) -> bool {
self == &Self::default()
}
/// Returns true if the instance of [`Parameters`] represents Regtest.
pub fn is_regtest(&self) -> bool {
if self.network_magic != magics::REGTEST {
return false;
}
let Self {
network_name,
// Already checked network magic above
network_magic: _,
genesis_hash,
// Activation heights are configurable on Regtest
activation_heights: _,
slow_start_interval,
slow_start_shift,
pre_nu6_funding_streams,
post_nu6_funding_streams,
target_difficulty_limit,
disable_pow,
} = Self::new_regtest(None, None);
self.network_name == network_name
&& self.genesis_hash == genesis_hash
&& self.slow_start_interval == slow_start_interval
&& self.slow_start_shift == slow_start_shift
&& self.pre_nu6_funding_streams == pre_nu6_funding_streams
&& self.post_nu6_funding_streams == post_nu6_funding_streams
&& self.target_difficulty_limit == target_difficulty_limit
&& self.disable_pow == disable_pow
}
/// Returns the network name
pub fn network_name(&self) -> &str {
&self.network_name
}
/// Returns the network magic
pub fn network_magic(&self) -> Magic {
self.network_magic
}
/// Returns the genesis hash
pub fn genesis_hash(&self) -> block::Hash {
self.genesis_hash
}
/// Returns the network upgrade activation heights
pub fn activation_heights(&self) -> &BTreeMap<Height, NetworkUpgrade> {
&self.activation_heights
}
/// Returns slow start interval for this network
pub fn slow_start_interval(&self) -> Height {
self.slow_start_interval
}
/// Returns slow start shift for this network
pub fn slow_start_shift(&self) -> Height {
self.slow_start_shift
}
/// Returns pre-NU6 funding streams for this network
pub fn pre_nu6_funding_streams(&self) -> &FundingStreams {
&self.pre_nu6_funding_streams
}
/// Returns post-NU6 funding streams for this network
pub fn post_nu6_funding_streams(&self) -> &FundingStreams {
&self.post_nu6_funding_streams
}
/// Returns the target difficulty limit for this network
pub fn target_difficulty_limit(&self) -> ExpandedDifficulty {
self.target_difficulty_limit
}
/// Returns true if proof-of-work validation should be disabled for this network
pub fn disable_pow(&self) -> bool {
self.disable_pow
}
}
impl Network {
/// Returns true if proof-of-work validation should be disabled for this network
pub fn disable_pow(&self) -> bool {
if let Self::Testnet(params) = self {
params.disable_pow()
} else {
false
}
}
/// Returns slow start interval for this network
pub fn slow_start_interval(&self) -> Height {
if let Self::Testnet(params) = self {
params.slow_start_interval()
} else {
SLOW_START_INTERVAL
}
}
/// Returns slow start shift for this network
pub fn slow_start_shift(&self) -> Height {
if let Self::Testnet(params) = self {
params.slow_start_shift()
} else {
SLOW_START_SHIFT
}
}
/// Returns pre-NU6 funding streams for this network
pub fn pre_nu6_funding_streams(&self) -> &FundingStreams {
if let Self::Testnet(params) = self {
params.pre_nu6_funding_streams()
} else {
&PRE_NU6_FUNDING_STREAMS_MAINNET
}
}
/// Returns post-NU6 funding streams for this network
pub fn post_nu6_funding_streams(&self) -> &FundingStreams {
if let Self::Testnet(params) = self {
params.post_nu6_funding_streams()
} else {
&POST_NU6_FUNDING_STREAMS_MAINNET
}
}
/// Returns post-Canopy funding streams for this network at the provided height
pub fn funding_streams(&self, height: Height) -> &FundingStreams {
if NetworkUpgrade::current(self, height) < NetworkUpgrade::Nu6 {
self.pre_nu6_funding_streams()
} else {
self.post_nu6_funding_streams()
}
}
}