Skip to content

Commit

Permalink
add(consensus): Adds disable_pow and genesis_hash fields, convert…
Browse files Browse the repository at this point in the history
…s `equihash::Solution` to an enum (#8421)

* minor cleanup and rename

* Adds an empty NetworkParameters struct to Network::Testnet variant, updates production code.

* Updates tests

* Adds `NetworkKind` and uses it instead of `Network` in `HistoryTreeParts` and `transparent::Address`

* Adds a [network.testnet_parameters] section to the config, uses `NetworkKind` as zebra_network::Config::network field type, and converts 'Network' to `NetworkKind` before serializing

* Applies some suggestions from code review

* Applies suggestions from code review

* returns b58 prefix constants directly to remove From<NetworkKind> impl for zcash_primitives::consensus::Network

* Applies more suggestions from code review.

* moves conversions to zcash_primitives::consensus::Network to where they're used.

* Apply suggestions from code review

Co-authored-by: Marek <[email protected]>

* rename `network` variables and method names typed as NetworkKind to `network_kind`

* use only test block heights for the network associated with them

* Applies more suggestions from code review.

* Rename `NetworkParameters` to `Parameters` and move it a new `testnet` module

* adds activation heights field

* updates stored test config, adds a quicker test for checking that stored configs can be parsed, adds an intermediate representation of activation heights

* implement Parameters for Network

* Passes &Network directly instead of converting to zp_consensus::Network where there were conversions

* fixes a bad merge (removes a network conversion in zcash_note_encryption)

* Adds a test for the Parameters impl for zebra_chain::Network

* fixes doc links

* - Makes the `activation_heights` config field optional by adding a #[serde(default)]
- Panics if a non-zero activation height is provided for the `Genesis` network upgrade
- Always sets the `Genesis` and `BeforeOverwinter` network upgrade activation heights to 0 and 1, `BeforeOverwinter` could be overwritten by a later network upgrade
- Makes the `activation_heights` field on `Parameters` private, adds/uses an accessor method instead, and adds a builder struct and `build()` method

* small refactor of activation_heights() method

* check that activation heights are in the right order

* Updates `NetworkUpgrade::activation_height()` to return the height of the next NetworkUpgrade if it doesn't find the activation height of `&self`

* checks that the miner address is of TestnetKind on Regtest and update assertion message

* Adds a DNetworkUpgradeActivationHeights struct for better control over how activation heights can be configured

* moves all ordered network upgrades to a constant, adds a no_duplicates test, moves struct with activation heights outside deserialization impl and accepts it in `ParametersBuilder::activation_heights()` instead of a Vec

* panics if any network upgrades are configured to activate at Height(0) because it's reserved for Genesis

* Simplifies the `ParametersBuilder::activation_heights()` method and removes an unnecessary test.

* Adds Sapling HRPs as fields on testnet params. (#8398)

* Applies suggestions from code review.

* Update zebra-chain/src/parameters/network_upgrade.rs

Co-authored-by: Marek <[email protected]>

* Adds `network_name` field and accessor method on `Parameters`, uses it in the `Display` impl for `Network`

* Removes unnecessary `.filter()`

* adds constraints on valid network names and a test

* adds config field for setting network name, adds "with_" prefix to ParameterBuilder setter methods

* Adds `MainnetKind`, `TestnetKind`, and `RegtestKind` to reserved network names

* updates stored test configs and fixes `network_name()` docs

* Adds a `new_regtest()` method on `Network` and `testnet::Parameters`, updates config deserialization to return an error if the initial_testnet_peers include any default initial peers AND contain configured activation heights

* Updates `activates_network_upgrades_correctly` test to check Regtest activation heights (and default Mainnet/Testnet)

* Refactors if-let & match statement into general match statement, removes constraint against including `testnet_parameters` field/section in the config when using `Mainnet` or `Regtest`

* Removes outdated TODO

* Restores `testnet_parameters` section of the latest stored config.

* Adds `with_sapling_hrps()` method and uses it to set the Regtest HRPs in `new_regtest()`.

Adds a test for Sapling HRP validation

* Checks that default Mainnet/Testnet/Regtest Sapling HRPs pass validation in `with_sapling_hrps()`

* Uses the correct constant in test

* Adds `is_regtest()` methods

* Adds genesis hash methods and fields to `testnet::Parameters` and its builder

* Copies Regtest genesis block from zcashd

* moves genesis hash check to Network.checkpoint_list()

* Checks the first line in the checkpoint list instead of the first item in the BTreeMap

* Checks that there is _some_ genesis block in the checkpoints list in `from_list()`, adds Regtest checkpoints (which only includes the genesis block)

* Adds a doc comment to `ParametersBuilder::default()`

* Adds a `disable_pow` field and skips checking proof of work if it's true

* Makes `equihash::Solution` an enum to support Regtest solutions, adds a test for validating and committing the Regtest genesis block

* use genesis_hash as first checkpoint when checkpoints are missing for configured testnets too

* Applies suggestions from code review.

* Reverts changes to `activation_height()` method

* Avoids panic in test

* Updates test docs

* drop custom panic hooks after expected panics

---------

Co-authored-by: Marek <[email protected]>
  • Loading branch information
arya2 and upbqdn authored Apr 26, 2024
1 parent c28e37b commit d84d735
Show file tree
Hide file tree
Showing 13 changed files with 239 additions and 83 deletions.
1 change: 1 addition & 0 deletions zebra-chain/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ mod header;
mod height;
mod serialize;

pub mod genesis;
pub mod merkle;

#[cfg(any(test, feature = "proptest-impl"))]
Expand Down
19 changes: 19 additions & 0 deletions zebra-chain/src/block/genesis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//! Regtest genesis block

use std::sync::Arc;

use hex::FromHex;

use crate::{block::Block, serialization::ZcashDeserializeInto};

/// Genesis block for Regtest, copied from zcashd via `getblock 0 0` RPC method
pub fn regtest_genesis_block() -> Arc<Block> {
let regtest_genesis_block_bytes =
<Vec<u8>>::from_hex(include_str!("genesis/block-regtest-0-000-000.txt").trim())
.expect("Block bytes are in valid hex representation");

regtest_genesis_block_bytes
.zcash_deserialize_into()
.map(Arc::new)
.expect("hard-coded Regtest genesis block data must deserialize successfully")
}
1 change: 1 addition & 0 deletions zebra-chain/src/block/genesis/block-regtest-0-000-000.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
040000000000000000000000000000000000000000000000000000000000000000000000db4d7a85b768123f1dff1d4c4cece70083b2d27e117b4ac2e31d087988a5eac40000000000000000000000000000000000000000000000000000000000000000dae5494d0f0f0f2009000000000000000000000000000000000000000000000000000000000000002401936b7db1eb4ac39f151b8704642d0a8bda13ec547d54cd5e43ba142fc6d8877cab07b30101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff071f0104455a6361736830623963346565663862376363343137656535303031653335303039383462366665613335363833613763616331343161303433633432303634383335643334ffffffff010000000000000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000
12 changes: 9 additions & 3 deletions zebra-chain/src/history_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,9 +455,15 @@ impl HistoryTree {
sapling_root: &sapling::tree::Root,
orchard_root: &orchard::tree::Root,
) -> Result<(), HistoryTreeError> {
let heartwood_height = NetworkUpgrade::Heartwood
.activation_height(network)
.expect("Heartwood height is known");
let Some(heartwood_height) = NetworkUpgrade::Heartwood.activation_height(network) else {
assert!(
self.0.is_none(),
"history tree must not exist pre-Heartwood"
);

return Ok(());
};

match block
.coinbase_height()
.expect("must have height")
Expand Down
8 changes: 8 additions & 0 deletions zebra-chain/src/parameters/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,14 @@ 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 the [`NetworkKind`] for this network.
pub fn kind(&self) -> NetworkKind {
match self {
Expand Down
61 changes: 59 additions & 2 deletions zebra-chain/src/parameters/network/testnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::{collections::BTreeMap, fmt};
use zcash_primitives::constants as zp_constants;

use crate::{
block::Height,
block::{self, Height},
parameters::{
network_upgrade::TESTNET_ACTIVATION_HEIGHTS, Network, NetworkUpgrade,
NETWORK_UPGRADES_IN_ORDER,
Expand All @@ -27,6 +27,14 @@ 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";

/// Configurable activation heights for Regtest and configured Testnets.
#[derive(Deserialize, Default)]
#[serde(rename_all = "PascalCase")]
Expand All @@ -53,6 +61,8 @@ pub struct ConfiguredActivationHeights {
pub struct ParametersBuilder {
/// The name of this network to be used by the `Display` trait impl.
network_name: String,
/// 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>,
/// Sapling extended spending key human-readable prefix for this network
Expand All @@ -61,9 +71,12 @@ pub struct ParametersBuilder {
hrp_sapling_extended_full_viewing_key: String,
/// Sapling payment address human-readable prefix for this network
hrp_sapling_payment_address: String,
/// 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(),
Expand All @@ -77,6 +90,10 @@ impl Default for ParametersBuilder {
zp_constants::testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY.to_string(),
hrp_sapling_payment_address: zp_constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS
.to_string(),
genesis_hash: TESTNET_GENESIS_HASH
.parse()
.expect("hard-coded hash parses"),
disable_pow: false,
}
}
}
Expand Down Expand Up @@ -144,6 +161,15 @@ impl ParametersBuilder {
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(
Expand Down Expand Up @@ -208,21 +234,31 @@ impl ParametersBuilder {
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,
genesis_hash,
activation_heights,
hrp_sapling_extended_spending_key,
hrp_sapling_extended_full_viewing_key,
hrp_sapling_payment_address,
disable_pow,
} = self;
Parameters {
network_name,
genesis_hash,
activation_heights,
hrp_sapling_extended_spending_key,
hrp_sapling_extended_full_viewing_key,
hrp_sapling_payment_address,
disable_pow,
}
}

Expand All @@ -237,6 +273,8 @@ impl ParametersBuilder {
pub struct Parameters {
/// The name of this network to be used by the `Display` trait impl.
network_name: String,
/// 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
Expand All @@ -249,6 +287,8 @@ pub struct Parameters {
hrp_sapling_extended_full_viewing_key: String,
/// Sapling payment address human-readable prefix for this network
hrp_sapling_payment_address: String,
/// A flag for disabling proof-of-work checks when Zebra is validating blocks
disable_pow: bool,
}

impl Default for Parameters {
Expand All @@ -274,6 +314,8 @@ impl Parameters {
Self {
network_name: "Regtest".to_string(),
..Self::build()
.with_genesis_hash(REGTEST_GENESIS_HASH)
.with_disable_pow(true)
.with_sapling_hrps(
zp_constants::regtest::HRP_SAPLING_EXTENDED_SPENDING_KEY,
zp_constants::regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
Expand All @@ -295,23 +337,33 @@ impl Parameters {
pub fn is_regtest(&self) -> bool {
let Self {
network_name,
genesis_hash,
// Activation heights are configurable on Regtest
activation_heights: _,
hrp_sapling_extended_spending_key,
hrp_sapling_extended_full_viewing_key,
hrp_sapling_payment_address,
..
disable_pow,
} = Self::new_regtest(ConfiguredActivationHeights::default());

self.network_name == network_name
&& self.genesis_hash == genesis_hash
&& self.hrp_sapling_extended_spending_key == hrp_sapling_extended_spending_key
&& self.hrp_sapling_extended_full_viewing_key == hrp_sapling_extended_full_viewing_key
&& self.hrp_sapling_payment_address == hrp_sapling_payment_address
&& self.disable_pow == disable_pow
}

/// Returns the network name
pub fn network_name(&self) -> &str {
&self.network_name
}

/// 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
Expand All @@ -331,4 +383,9 @@ impl Parameters {
pub fn hrp_sapling_payment_address(&self) -> &str {
&self.hrp_sapling_payment_address
}

/// Returns true if proof-of-work validation should be disabled for this network
pub fn disable_pow(&self) -> bool {
self.disable_pow
}
}
2 changes: 1 addition & 1 deletion zebra-chain/src/work/arbitrary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ impl Arbitrary for equihash::Solution {
.prop_map(|v| {
let mut bytes = [0; equihash::SOLUTION_SIZE];
bytes.copy_from_slice(v.as_slice());
Self(bytes)
Self::Common(bytes)
})
.boxed()
}
Expand Down
73 changes: 49 additions & 24 deletions zebra-chain/src/work/equihash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,26 @@ pub struct SolverCancelled;
/// The size of an Equihash solution in bytes (always 1344).
pub(crate) const SOLUTION_SIZE: usize = 1344;

/// The size of an Equihash solution in bytes on Regtest (always 36).
pub(crate) const REGTEST_SOLUTION_SIZE: usize = 36;

/// Equihash Solution in compressed format.
///
/// A wrapper around [u8; 1344] because Rust doesn't implement common
/// traits like `Debug`, `Clone`, etc for collections like array
/// beyond lengths 0 to 32.
/// A wrapper around `[u8; n]` where `n` is the solution size because
/// Rust doesn't implement common traits like `Debug`, `Clone`, etc.
/// for collections like arrays beyond lengths 0 to 32.
///
/// The size of an Equihash solution in bytes is always 1344 so the
/// length of this type is fixed.
/// The size of an Equihash solution in bytes is always 1344 on Mainnet and Testnet, and
/// is always 36 on Regtest so the length of this type is fixed.
#[derive(Deserialize, Serialize)]
pub struct Solution(#[serde(with = "BigArray")] pub [u8; SOLUTION_SIZE]);
// It's okay to use the extra space on Regtest
#[allow(clippy::large_enum_variant)]
pub enum Solution {
/// Equihash solution on Mainnet or Testnet
Common(#[serde(with = "BigArray")] [u8; SOLUTION_SIZE]),
/// Equihash solution on Regtest
Regtest(#[serde(with = "BigArray")] [u8; REGTEST_SOLUTION_SIZE]),
}

impl Solution {
/// The length of the portion of the header used as input when verifying
Expand All @@ -48,15 +58,24 @@ impl Solution {
/// to the verification function.
pub const INPUT_LENGTH: usize = 4 + 32 * 3 + 4 * 2;

/// Returns the inner value of the [`Solution`] as a byte slice.
fn value(&self) -> &[u8] {
match self {
Solution::Common(solution) => solution.as_slice(),
Solution::Regtest(solution) => solution.as_slice(),
}
}

/// Returns `Ok(())` if `EquihashSolution` is valid for `header`
#[allow(clippy::unwrap_in_result)]
pub fn check(&self, header: &Header) -> Result<(), Error> {
let n = 200;
let k = 9;
let nonce = &header.nonce;
let solution = &self.0;
let mut input = Vec::new();
let (solution, n, k) = match self {
Solution::Common(solution) => (solution.as_slice(), 200, 9),
Solution::Regtest(solution) => (solution.as_slice(), 48, 5),
};

let mut input = Vec::new();
header
.zcash_serialize(&mut input)
.expect("serialization into a vec can't fail");
Expand All @@ -73,23 +92,29 @@ impl Solution {
/// Returns a [`Solution`] containing the bytes from `solution`.
/// Returns an error if `solution` is the wrong length.
pub fn from_bytes(solution: &[u8]) -> Result<Self, SerializationError> {
if solution.len() != SOLUTION_SIZE {
return Err(SerializationError::Parse(
match solution.len() {
// Won't panic, because we just checked the length.
SOLUTION_SIZE => {
let mut bytes = [0; SOLUTION_SIZE];
bytes.copy_from_slice(solution);
Ok(Self::Common(bytes))
}
REGTEST_SOLUTION_SIZE => {
let mut bytes = [0; REGTEST_SOLUTION_SIZE];
bytes.copy_from_slice(solution);
Ok(Self::Regtest(bytes))
}
_unexpected_len => Err(SerializationError::Parse(
"incorrect equihash solution size",
));
)),
}

let mut bytes = [0; SOLUTION_SIZE];
// Won't panic, because we just checked the length.
bytes.copy_from_slice(solution);

Ok(Self(bytes))
}

/// Returns a [`Solution`] of `[0; SOLUTION_SIZE]` to be used in block proposals.
#[cfg(feature = "getblocktemplate-rpcs")]
pub fn for_proposal() -> Self {
Self([0; SOLUTION_SIZE])
// TODO: Accept network as an argument, and if it's Regtest, return the shorter null solution.
Self::Common([0; SOLUTION_SIZE])
}

/// Mines and returns one or more [`Solution`]s based on a template `header`.
Expand Down Expand Up @@ -126,14 +151,14 @@ impl Solution {

impl PartialEq<Solution> for Solution {
fn eq(&self, other: &Solution) -> bool {
self.0.as_ref() == other.0.as_ref()
self.value() == other.value()
}
}

impl fmt::Debug for Solution {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("EquihashSolution")
.field(&hex::encode(&self.0[..]))
.field(&hex::encode(self.value()))
.finish()
}
}
Expand All @@ -153,13 +178,13 @@ impl Eq for Solution {}
#[cfg(any(test, feature = "proptest-impl"))]
impl Default for Solution {
fn default() -> Self {
Self([0; SOLUTION_SIZE])
Self::Common([0; SOLUTION_SIZE])
}
}

impl ZcashSerialize for Solution {
fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
zcash_serialize_bytes(&self.0.to_vec(), writer)
zcash_serialize_bytes(&self.value().to_vec(), writer)
}
}

Expand Down
Loading

0 comments on commit d84d735

Please sign in to comment.