add(consensus): Add a `target_difficulty_limit` field on `testnet::Parameters` (#8518)

* Adds a `target_difficulty_limit` field on `testnet::Parameters`

* updates test to increment block nonce until finding a block that's below the difficulty threshold

* increment the nonce while the difficulty is invalid instead of while the difficulty threshold is invalid

* Adds comments
This commit is contained in:
Arya 2024-05-17 19:06:08 -04:00 committed by GitHub
parent 827599e1aa
commit 99b017e2a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 86 additions and 28 deletions

View File

@ -10,6 +10,7 @@ use crate::{
network_upgrade::TESTNET_ACTIVATION_HEIGHTS, network_upgrade::TESTNET_ACTIVATION_HEIGHTS,
Network, NetworkUpgrade, NETWORK_UPGRADES_IN_ORDER, Network, NetworkUpgrade, NETWORK_UPGRADES_IN_ORDER,
}, },
work::difficulty::{ExpandedDifficulty, U256},
}; };
/// The Regtest NU5 activation height in tests /// The Regtest NU5 activation height in tests
@ -63,7 +64,7 @@ pub struct ConfiguredActivationHeights {
} }
/// Builder for the [`Parameters`] struct. /// Builder for the [`Parameters`] struct.
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct ParametersBuilder { pub struct ParametersBuilder {
/// The name of this network to be used by the `Display` trait impl. /// The name of this network to be used by the `Display` trait impl.
network_name: String, network_name: String,
@ -79,6 +80,8 @@ pub struct ParametersBuilder {
hrp_sapling_payment_address: String, hrp_sapling_payment_address: String,
/// Slow start interval for this network /// Slow start interval for this network
slow_start_interval: Height, slow_start_interval: Height,
/// Target difficulty limit for this network
target_difficulty_limit: ExpandedDifficulty,
/// A flag for disabling proof-of-work checks when Zebra is validating blocks /// A flag for disabling proof-of-work checks when Zebra is validating blocks
disable_pow: bool, disable_pow: bool,
} }
@ -102,6 +105,19 @@ impl Default for ParametersBuilder {
.parse() .parse()
.expect("hard-coded hash parses"), .expect("hard-coded hash parses"),
slow_start_interval: SLOW_START_INTERVAL, 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, disable_pow: false,
} }
} }
@ -247,6 +263,16 @@ impl ParametersBuilder {
self 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: U256) -> Self {
self.target_difficulty_limit = ExpandedDifficulty::from(target_difficulty_limit)
.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. /// Sets the `disable_pow` flag to be used in the [`Parameters`] being built.
pub fn with_disable_pow(mut self, disable_pow: bool) -> Self { pub fn with_disable_pow(mut self, disable_pow: bool) -> Self {
self.disable_pow = disable_pow; self.disable_pow = disable_pow;
@ -263,6 +289,7 @@ impl ParametersBuilder {
hrp_sapling_extended_full_viewing_key, hrp_sapling_extended_full_viewing_key,
hrp_sapling_payment_address, hrp_sapling_payment_address,
slow_start_interval, slow_start_interval,
target_difficulty_limit,
disable_pow, disable_pow,
} = self; } = self;
Parameters { Parameters {
@ -274,6 +301,7 @@ impl ParametersBuilder {
hrp_sapling_payment_address, hrp_sapling_payment_address,
slow_start_interval, slow_start_interval,
slow_start_shift: Height(slow_start_interval.0 / 2), slow_start_shift: Height(slow_start_interval.0 / 2),
target_difficulty_limit,
disable_pow, disable_pow,
} }
} }
@ -285,7 +313,7 @@ impl ParametersBuilder {
} }
/// Network consensus parameters for test networks such as Regtest and the default Testnet. /// Network consensus parameters for test networks such as Regtest and the default Testnet.
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Parameters { pub struct Parameters {
/// The name of this network to be used by the `Display` trait impl. /// The name of this network to be used by the `Display` trait impl.
network_name: String, network_name: String,
@ -307,6 +335,8 @@ pub struct Parameters {
slow_start_interval: Height, slow_start_interval: Height,
/// Slow start shift for this network, always half the slow start interval /// Slow start shift for this network, always half the slow start interval
slow_start_shift: Height, slow_start_shift: Height,
/// Target difficulty limit for this network
target_difficulty_limit: ExpandedDifficulty,
/// A flag for disabling proof-of-work checks when Zebra is validating blocks /// A flag for disabling proof-of-work checks when Zebra is validating blocks
disable_pow: bool, disable_pow: bool,
} }
@ -338,6 +368,8 @@ impl Parameters {
network_name: "Regtest".to_string(), network_name: "Regtest".to_string(),
..Self::build() ..Self::build()
.with_genesis_hash(REGTEST_GENESIS_HASH) .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_disable_pow(true)
.with_slow_start_interval(Height::MIN) .with_slow_start_interval(Height::MIN)
.with_sapling_hrps( .with_sapling_hrps(
@ -373,6 +405,7 @@ impl Parameters {
hrp_sapling_payment_address, hrp_sapling_payment_address,
slow_start_interval, slow_start_interval,
slow_start_shift, slow_start_shift,
target_difficulty_limit,
disable_pow, disable_pow,
} = Self::new_regtest(None); } = Self::new_regtest(None);
@ -383,6 +416,7 @@ impl Parameters {
&& self.hrp_sapling_payment_address == hrp_sapling_payment_address && self.hrp_sapling_payment_address == hrp_sapling_payment_address
&& self.slow_start_interval == slow_start_interval && self.slow_start_interval == slow_start_interval
&& self.slow_start_shift == slow_start_shift && self.slow_start_shift == slow_start_shift
&& self.target_difficulty_limit == target_difficulty_limit
&& self.disable_pow == disable_pow && self.disable_pow == disable_pow
} }
@ -426,6 +460,11 @@ impl Parameters {
self.slow_start_shift self.slow_start_shift
} }
/// 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 /// Returns true if proof-of-work validation should be disabled for this network
pub fn disable_pow(&self) -> bool { pub fn disable_pow(&self) -> bool {
self.disable_pow self.disable_pow

View File

@ -100,7 +100,7 @@ pub const INVALID_COMPACT_DIFFICULTY: CompactDifficulty = CompactDifficulty(u32:
/// [section 7.7.2]: https://zips.z.cash/protocol/protocol.pdf#difficulty /// [section 7.7.2]: https://zips.z.cash/protocol/protocol.pdf#difficulty
// //
// TODO: Use NonZeroU256, when available // TODO: Use NonZeroU256, when available
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] #[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct ExpandedDifficulty(U256); pub struct ExpandedDifficulty(U256);
/// A 128-bit unsigned "Work" value. /// A 128-bit unsigned "Work" value.
@ -696,11 +696,11 @@ impl ParameterDifficulty for Network {
/// See [`ParameterDifficulty::target_difficulty_limit`] /// See [`ParameterDifficulty::target_difficulty_limit`]
fn target_difficulty_limit(&self) -> ExpandedDifficulty { fn target_difficulty_limit(&self) -> ExpandedDifficulty {
let limit: U256 = match self { let limit: U256 = match self {
/* 2^243 - 1 */ // Mainnet PoWLimit is defined as `2^243 - 1` on page 73 of the protocol specification:
// <https://zips.z.cash/protocol/protocol.pdf>
Network::Mainnet => (U256::one() << 243) - 1, Network::Mainnet => (U256::one() << 243) - 1,
/* 2^251 - 1 */ // 2^251 - 1 for the default testnet, see `testnet::ParametersBuilder::default`()
// TODO: Add a `target_difficulty_limit` field to `testnet::Parameters` to return here. (`U256::from_big_endian(&[0x0f].repeat(8))` for Regtest) Network::Testnet(params) => return params.target_difficulty_limit(),
Network::Testnet(_params) => (U256::one() << 251) - 1,
}; };
// `zcashd` converts the PoWLimit into a compact representation before // `zcashd` converts the PoWLimit into a compact representation before

View File

@ -176,18 +176,16 @@ where
Err(BlockError::MaxHeight(height, hash, block::Height::MAX))?; Err(BlockError::MaxHeight(height, hash, block::Height::MAX))?;
} }
if !network.disable_pow() { // > The block data MUST be validated and checked against the server's usual
// > The block data MUST be validated and checked against the server's usual // > acceptance rules (excluding the check for a valid proof-of-work).
// > acceptance rules (excluding the check for a valid proof-of-work). // <https://en.bitcoin.it/wiki/BIP_0023#Block_Proposal>
// <https://en.bitcoin.it/wiki/BIP_0023#Block_Proposal> if request.is_proposal() || network.disable_pow() {
if request.is_proposal() { check::difficulty_threshold_is_valid(&block.header, &network, &height, &hash)?;
check::difficulty_threshold_is_valid(&block.header, &network, &height, &hash)?; } else {
} else { // Do the difficulty checks first, to raise the threshold for
// Do the difficulty checks first, to raise the threshold for // attacks that use any other fields.
// attacks that use any other fields. check::difficulty_is_valid(&block.header, &network, &height, &hash)?;
check::difficulty_is_valid(&block.header, &network, &height, &hash)?; check::equihash_solution_is_valid(&block.header)?;
check::equihash_solution_is_valid(&block.header)?;
}
} }
// Next, check the Merkle root validity, to ensure that // Next, check the Merkle root validity, to ensure that

View File

@ -595,7 +595,14 @@ where
.ok_or(VerifyCheckpointError::CoinbaseHeight { hash })?; .ok_or(VerifyCheckpointError::CoinbaseHeight { hash })?;
self.check_height(height)?; self.check_height(height)?;
if !self.network.disable_pow() { if self.network.disable_pow() {
crate::block::check::difficulty_threshold_is_valid(
&block.header,
&self.network,
&height,
&hash,
)?;
} else {
crate::block::check::difficulty_is_valid(&block.header, &self.network, &height, &hash)?; crate::block::check::difficulty_is_valid(&block.header, &self.network, &height, &hash)?;
crate::block::check::equihash_solution_is_valid(&block.header)?; crate::block::check::equihash_solution_is_valid(&block.header)?;
} }

View File

@ -45,6 +45,9 @@ pub mod error;
pub mod router; pub mod router;
pub mod transaction; pub mod transaction;
#[cfg(any(test, feature = "proptest-impl"))]
pub use block::check::difficulty_is_valid;
pub use block::{ pub use block::{
subsidy::{ subsidy::{
funding_streams::{ funding_streams::{

View File

@ -3,13 +3,14 @@
//! This test will get block templates via the `getblocktemplate` RPC method and submit them as new blocks //! This test will get block templates via the `getblocktemplate` RPC method and submit them as new blocks
//! via the `submitblock` RPC method on Regtest. //! via the `submitblock` RPC method on Regtest.
use std::{net::SocketAddr, time::Duration}; use std::{net::SocketAddr, sync::Arc, time::Duration};
use color_eyre::eyre::{Context, Result}; use color_eyre::eyre::{Context, Result};
use tracing::*; use tracing::*;
use zebra_chain::{ use zebra_chain::{
parameters::{testnet::REGTEST_NU5_ACTIVATION_HEIGHT, Network, NetworkUpgrade}, parameters::{testnet::REGTEST_NU5_ACTIVATION_HEIGHT, Network, NetworkUpgrade},
primitives::byte_array::increment_big_endian,
serialization::ZcashSerialize, serialization::ZcashSerialize,
}; };
use zebra_node_services::rpc_client::RpcRequestClient; use zebra_node_services::rpc_client::RpcRequestClient;
@ -44,7 +45,7 @@ pub(crate) async fn submit_blocks_test() -> Result<()> {
tokio::time::sleep(Duration::from_secs(30)).await; tokio::time::sleep(Duration::from_secs(30)).await;
info!("attempting to submit blocks"); info!("attempting to submit blocks");
submit_blocks(rpc_address).await?; submit_blocks(network, rpc_address).await?;
zebrad.kill(false)?; zebrad.kill(false)?;
@ -58,7 +59,7 @@ pub(crate) async fn submit_blocks_test() -> Result<()> {
} }
/// Get block templates and submit blocks /// Get block templates and submit blocks
async fn submit_blocks(rpc_address: SocketAddr) -> Result<()> { async fn submit_blocks(network: Network, rpc_address: SocketAddr) -> Result<()> {
let client = RpcRequestClient::new(rpc_address); let client = RpcRequestClient::new(rpc_address);
for height in 1..=NUM_BLOCKS_TO_SUBMIT { for height in 1..=NUM_BLOCKS_TO_SUBMIT {
@ -73,10 +74,20 @@ async fn submit_blocks(rpc_address: SocketAddr) -> Result<()> {
NetworkUpgrade::Nu5 NetworkUpgrade::Nu5
}; };
let block_data = hex::encode( let mut block =
proposal_block_from_template(&block_template, TimeSource::default(), network_upgrade)? proposal_block_from_template(&block_template, TimeSource::default(), network_upgrade)?;
.zcash_serialize_to_vec()?, let height = block
); .coinbase_height()
.expect("should have a coinbase height");
while !network.disable_pow()
&& zebra_consensus::difficulty_is_valid(&block.header, &network, &height, &block.hash())
.is_err()
{
increment_big_endian(Arc::make_mut(&mut block.header).nonce.as_mut());
}
let block_data = hex::encode(block.zcash_serialize_to_vec()?);
let submit_block_response = client let submit_block_response = client
.text_from_call("submitblock", format!(r#"["{block_data}"]"#)) .text_from_call("submitblock", format!(r#"["{block_data}"]"#))
@ -84,7 +95,7 @@ async fn submit_blocks(rpc_address: SocketAddr) -> Result<()> {
let was_submission_successful = submit_block_response.contains(r#""result":null"#); let was_submission_successful = submit_block_response.contains(r#""result":null"#);
if height % 40 == 0 { if height.0 % 40 == 0 {
info!( info!(
was_submission_successful, was_submission_successful,
?block_template, ?block_template,