-
Notifications
You must be signed in to change notification settings - Fork 107
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
change(network): Add configurable network parameters to Network::Testnet
#7924
Conversation
…enesis_hash from params
This comment was marked as resolved.
This comment was marked as resolved.
&*vectors::MAINNET_FINAL_SPROUT_ROOTS, | ||
MAINNET_FIRST_JOINSPLIT_HEIGHT, | ||
), | ||
Network::Testnet => ( | ||
Network::Testnet(_) => ( | ||
&*vectors::TESTNET_BLOCKS, | ||
&*vectors::TESTNET_FINAL_SPROUT_ROOTS, | ||
TESTNET_FIRST_JOINSPLIT_HEIGHT, |
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.
It seems like this code has been copied around a lot, maybe we should replace it with methods in test::vectors
that take is_mainnet: bool
?
This can happen in an earlier PR, and then all this test code doesn't need to change at all in this PR.
@@ -95,7 +95,7 @@ fn funding_stream_address_period(height: Height, network: Network) -> u32 { | |||
fn funding_stream_address_index(height: Height, network: Network) -> usize { | |||
let num_addresses = match network { | |||
Network::Mainnet => FUNDING_STREAMS_NUM_ADDRESSES_MAINNET, | |||
Network::Testnet => FUNDING_STREAMS_NUM_ADDRESSES_TESTNET, | |||
Network::Testnet(_) => FUNDING_STREAMS_NUM_ADDRESSES_TESTNET, |
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.
Let's check what zcashd
does here.
@@ -60,7 +60,7 @@ pub fn height_for_first_halving(network: Network) -> Height { | |||
Network::Mainnet => Canopy | |||
.activation_height(network) | |||
.expect("canopy activation height should be available"), | |||
Network::Testnet => FIRST_HALVING_TESTNET, | |||
Network::Testnet(_) => FIRST_HALVING_TESTNET, |
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.
Let's check what zcashd
does here.
Network::Testnet(_) => Self::from_str(TESTNET_CHECKPOINTS, network) | ||
.expect("Hard-coded Testnet checkpoint list parses and validates"), | ||
}; | ||
|
||
match checkpoint_list.hash(block::Height(0)) { | ||
Some(hash) if hash == genesis_hash(network) => checkpoint_list, | ||
Some(_) => { | ||
panic!("The hard-coded genesis checkpoint does not match the network genesis hash") | ||
} | ||
None => unreachable!("Parser should have checked for a missing genesis checkpoint"), | ||
} | ||
} |
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 is only correct if network == Network::new_testnet()
. If it's not, then the list should consist of only the checkpoints supplied by the custom network. Currently that's a single item list containing the genesis block hash.
If we implement it that way, then there's no need to check the genesis checkpoint in production code, and that check can be moved to a unit test.
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.
If changing genesis is out of scope, then this should be the hard-coded testnet genesis block hash.
// Check that the list starts with the correct genesis block | ||
match checkpoints.iter().next() { | ||
Some((block::Height(0), hash)) | ||
if (hash == &genesis_hash(Network::Mainnet) | ||
|| hash == &genesis_hash(Network::Testnet)) => {} | ||
Some((block::Height(0), hash)) if hash == &genesis_hash(network) => {} | ||
Some((block::Height(0), _)) => { | ||
Err("the genesis checkpoint does not match the Mainnet or Testnet genesis hash")? | ||
Err("the genesis checkpoint does not match the network genesis hash")? | ||
} | ||
Some(_) => Err("checkpoints must start at the genesis block height 0")?, | ||
None => Err("there must be at least one checkpoint, for the genesis block")?, |
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.
Rather than complicating this code by adding a network parameter, let's move the checks that depend on network
into new()
. (And replace the similar check already in new()
.)
@@ -225,7 +225,7 @@ impl Config { | |||
pub fn initial_peer_hostnames(&self) -> &IndexSet<String> { | |||
match self.network { | |||
Network::Mainnet => &self.initial_mainnet_peers, | |||
Network::Testnet => &self.initial_testnet_peers, | |||
Network::Testnet(_) => &self.initial_testnet_peers, |
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 is incorrect and could cause issues with the reliability of the public testnet. A custom testnet needs to supply its own peers if it changes certain parameters.
Currently those are the genesis hash, the activation heights, and the network magic. Let's write code that checks if these parameters match the default testnet parameters?
There's a specific way to do that so that each newly added parameter field causes a compile error, and we have to add it to the check.
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.
I think the specific check that we want here is:
If the network is an incompatible custom testnet (that is, it changes any incompatible parameters from the default testnet values), and the configured initial testnet peers contains any of the default testnet peers, then panic.
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.
A simpler check would be to just check against the default testnet for now.
let mut blocks_size_to_dump = match self.network { | ||
Network::Mainnet => MAINNET_AWAY_FROM_TIP_BULK_SIZE, | ||
Network::Testnet => TESTNET_AWAY_FROM_TIP_BULK_SIZE, | ||
Network::Testnet(_) => TESTNET_AWAY_FROM_TIP_BULK_SIZE, | ||
}; |
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 code is incorrect, and will fail if the public or custom testnet has a significant number of 2MB blocks. Instead, the value of MAINNET_AWAY_FROM_TIP_BULK_SIZE
should be used for all networks.
(Testnet(_), PayToPublicKeyHash { .. }) => 2, | ||
(Testnet(_), PayToScriptHash { .. }) => 3, |
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 might be incorrect for custom testnets if they change their address formats. What does zcashd
do here?
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.
What does zcashd do here?
I'm not sure, I imagine it may be storing the entire prefix, I'll ask.
I think this could be fine as long as we use always different cache directories, but it would be safer to use different values for Regtest
or custom testnets (without differentiating between different kinds of custom testnets).
@@ -107,7 +107,7 @@ lazy_static! { | |||
pub static ref FUNDING_STREAM_HEIGHT_RANGES: HashMap<Network, std::ops::Range<Height>> = { | |||
let mut hash_map = HashMap::new(); | |||
hash_map.insert(Network::Mainnet, Height(1_046_400)..Height(2_726_400)); | |||
hash_map.insert(Network::Testnet, Height(1_028_500)..Height(2_796_000)); | |||
hash_map.insert(Network::new_testnet(), Height(1_028_500)..Height(2_796_000)); |
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 will cause a panic with a cryptic error message for custom testnets:
let range = FUNDING_STREAM_HEIGHT_RANGES.get(&network).unwrap(); |
@@ -127,7 +127,7 @@ lazy_static! { | |||
testnet_addresses.insert(FundingStreamReceiver::Ecc, FUNDING_STREAM_ECC_ADDRESSES_TESTNET.iter().map(|a| a.to_string()).collect()); | |||
testnet_addresses.insert(FundingStreamReceiver::ZcashFoundation, FUNDING_STREAM_ZF_ADDRESSES_TESTNET.iter().map(|a| a.to_string()).collect()); | |||
testnet_addresses.insert(FundingStreamReceiver::MajorGrants, FUNDING_STREAM_MG_ADDRESSES_TESTNET.iter().map(|a| a.to_string()).collect()); | |||
addresses_by_network.insert(Network::Testnet, testnet_addresses); | |||
addresses_by_network.insert(Network::new_testnet(), testnet_addresses); |
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 will cause a panic here for custom testnets:
zebra/zebra-consensus/src/block/subsidy/funding_streams.rs
Lines 126 to 128 in 2efc6bf
let address = &FUNDING_STREAM_ADDRESSES | |
.get(&network) | |
.expect("there is always another hash map as value for a given valid network") |
@@ -85,11 +170,26 @@ impl fmt::Display for Network { | |||
} | |||
|
|||
impl Network { | |||
/// Creates a new `Testnet` with no parameters. | |||
pub fn new_testnet() -> Self { |
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.
It looks like we're using this method for both:
- the canonical public testnet used in tests
- the custom testnet parameters used by
zebrad
when configured with Testnet
They can be the same, but we also want to make it easy for them to be different. And make the code easy to maintain.
So here's what I suggest to make the code clearer and easier to maintain:
- use a PUBLIC_TESTNET constant in tests, and make it test-only/proptest-impl using #cfg
- use a new_configured_testnet() method in production code, it should only be called once when the network config is loaded, so it can be pub(crate)
- make all the other methods that can create a testnet into test-only/proptest-impl using #cfg
After that change, anything that doesn't compile is an incorrect use of the public testnet in production code, or an incorrect use of the configured testnet in test code.
We'll also need tests for custom parameter changes, so we can't make those methods private, they need to be called from tests.
pub fn new_testnet() -> Self { | |
#[cfg(any(test, feature = "proptest-impl"))] | |
pub(crate) fn new_public_testnet() -> Self { |
} | ||
|
||
/// Creates a new `Testnet` with the provided parameters. | ||
pub fn new_testnet_with_parameters(parameters: impl Into<TestnetParameters>) -> Self { |
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.
Except I think this one needs to be private so it can be called from both new_public_testnet() and new_configured_testnet(). (But not anything else.)
pub fn new_testnet_with_parameters(parameters: impl Into<TestnetParameters>) -> Self { | |
fn new_testnet_with_parameters(parameters: impl Into<TestnetParameters>) -> Self { |
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.
Draft review, I'll resolve most of it.
|
||
impl TestnetParameters { | ||
/// Returns `network_id` in network parameters, if any. | ||
pub fn network_id(self) -> Option<[u8; 4]> { |
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.
Taking self by value only works while this is a Copy type. Instead, take &self (by reference) to make a future transition to a non-Copy type easier.
Network::Testnet(_) => Self::from_str(TESTNET_CHECKPOINTS, network) | ||
.expect("Hard-coded Testnet checkpoint list parses and validates"), | ||
}; | ||
|
||
match checkpoint_list.hash(block::Height(0)) { | ||
Some(hash) if hash == genesis_hash(network) => checkpoint_list, | ||
Some(_) => { | ||
panic!("The hard-coded genesis checkpoint does not match the network genesis hash") | ||
} | ||
None => unreachable!("Parser should have checked for a missing genesis checkpoint"), | ||
} | ||
} |
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.
If changing genesis is out of scope, then this should be the hard-coded testnet genesis block hash.
@@ -225,7 +225,7 @@ impl Config { | |||
pub fn initial_peer_hostnames(&self) -> &IndexSet<String> { | |||
match self.network { | |||
Network::Mainnet => &self.initial_mainnet_peers, | |||
Network::Testnet => &self.initial_testnet_peers, | |||
Network::Testnet(_) => &self.initial_testnet_peers, |
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.
A simpler check would be to just check against the default testnet for now.
|
||
/// Configures network parameters to use instead of default values. | ||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, Serialize)] | ||
pub struct NetworkParameters { |
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.
I think the first useful parameter to add is activation_heights
, let's open a ticket?
In another ticket it would be useful to add an NU6 variant and a random consensus branch ID.
The changes in this PR and the requirements of the issue it was intended to close have diverged. |
Motivation
This PR is a step towards adding the
Regtest
network to Zebra, adding optionalcache_name
,genesis_hash
,network_id
,default_port
, andactivation_heights
fields toNetwork::Testnet
.For testing ZSAs, it's not yet clear what other fields will be needed or whether the scope of this approach will be viable.
Part of #7845.
PR Author Checklist
Check before marking the PR as ready for review:
For significant changes:
Solution
TestnetParameters
inTestnet
TestnetParameters
, if any, instead of hard-coded defaultsReview
Anyone can review.
Reviewer Checklist
Check before approving the PR:
PR blockers can be dealt with in new tickets or PRs.
And check the PR Author checklist is complete.
Follow Up Work