Skip to content
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

Legacy chain check and tests #2366

Merged
merged 23 commits into from
Jun 29, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions zebra-chain/src/parameters/network_upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ pub(crate) const MAINNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)]
(block::Height(653_600), Blossom),
(block::Height(903_000), Heartwood),
(block::Height(1_046_400), Canopy),
// TODO: Add Nu5 mainnet activation height
// TODO: Add the correct Nu5 mainnet activation height
(block::Height(10_000_000), Nu5),
jvff marked this conversation as resolved.
Show resolved Hide resolved
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
];

/// Testnet network upgrade activation heights.
Expand All @@ -71,7 +72,8 @@ pub(crate) const TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)]
(block::Height(584_000), Blossom),
(block::Height(903_800), Heartwood),
(block::Height(1_028_500), Canopy),
// TODO: Add Nu5 testnet activation height
// TODO: Add the correct Nu5 testnet activation height
(block::Height(10_000_000), Nu5),
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
];

/// The Consensus Branch Id, used to bind transactions and blocks to a
Expand Down
3 changes: 3 additions & 0 deletions zebra-state/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,4 +316,7 @@ pub enum Request {
/// Optionally, the hash of the last header to request.
stop: Option<block::Hash>,
},

/// Returns `Some(database_path)` if Zebra has followed a legacy chain.
IsLegacyChain,
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
}
3 changes: 3 additions & 0 deletions zebra-state/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,7 @@ pub enum Response {

/// The response to a `FindBlockHeaders` request.
BlockHeaders(Vec<block::CountedHeader>),

/// The response to a `IsLegacyChain` request.
LegacyChain(Option<std::path::PathBuf>),
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
}
53 changes: 52 additions & 1 deletion zebra-state/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ use tower::{util::BoxService, Service};
use tracing::instrument;
use zebra_chain::{
block::{self, Block},
parameters::Network,
parameters::POW_AVERAGING_WINDOW,
parameters::{Network, NetworkUpgrade},
transaction,
transaction::Transaction,
transparent,
Expand Down Expand Up @@ -664,6 +664,17 @@ impl Service<Request> for StateService {
.collect();
async move { Ok(Response::BlockHeaders(res)) }.boxed()
}
Request::IsLegacyChain => {
let mut legacy_chain_path = None;
if let Some(tip) = self.best_tip() {
if legacy_chain_check(tip.0, self.any_ancestor_blocks(tip.1), self.network)
.is_err()
{
legacy_chain_path = Some(self.disk.path().to_path_buf());
}
}
async move { Ok(Response::LegacyChain(legacy_chain_path)) }.boxed()
}
}
}
}
Expand All @@ -679,3 +690,43 @@ impl Service<Request> for StateService {
pub fn init(config: Config, network: Network) -> BoxService<Request, Response, BoxError> {
BoxService::new(StateService::new(config, network))
}

/// Check if zebra is following a legacy chain and return an error if so.
pub fn legacy_chain_check<I>(
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
tip_height: block::Height,
ancestors: I,
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
network: Network,
) -> Result<(), BoxError>
where
I: Iterator<Item = Arc<Block>>,
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
{
const MAX_BLOCKS_TO_CHECK: usize = 100;

// Only do the check if we have an Nu5 activation height.
if let Some(nu5_height) = NetworkUpgrade::Nu5.activation_height(network) {
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
// And only do the check if we are above Nu5 activation.
if tip_height >= nu5_height {
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
for (count, block) in ancestors.enumerate() {
if count >= MAX_BLOCKS_TO_CHECK {
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
return Err("giving up after checking too many blocks".into());
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
}

// All transaction `network_upgrade` fields must be consistent with the block height.
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
block
.check_transaction_network_upgrade_consistency(network)
.map_err(|_| "inconsistent network upgrade found in transaction")?;
teor2345 marked this conversation as resolved.
Show resolved Hide resolved

// If we find at least one transaction with a `network_upgrade` field we are ok.
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
let has_network_upgrade = block
.transactions
.iter()
.find_map(|trans| trans.network_upgrade())
.is_some();
if has_network_upgrade {
return Ok(());
}
}
}
}
Ok(())
}
179 changes: 176 additions & 3 deletions zebra-state/src/service/tests.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::sync::Arc;
use std::{env, sync::Arc};

use futures::stream::FuturesUnordered;
use tower::{util::BoxService, Service, ServiceExt};
use zebra_chain::{
block::Block, parameters::Network, serialization::ZcashDeserializeInto, transaction,
transparent,
block::{Block, Height},
parameters::{Network, NetworkUpgrade},
serialization::ZcashDeserializeInto,
transaction, transparent,
};
use zebra_test::{prelude::*, transcript::Transcript};

Expand Down Expand Up @@ -97,6 +99,8 @@ async fn test_populated_state_responds_correctly(
}
}

transcript.push((Request::IsLegacyChain, Ok(Response::LegacyChain(None))));

let transcript = Transcript::from(transcript);
transcript.check(&mut state).await?;
}
Expand Down Expand Up @@ -183,3 +187,172 @@ fn state_behaves_when_blocks_are_committed_out_of_order() -> Result<()> {

Ok(())
}

/*
#[test]
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
fn legacy_chain() -> Result<()> {
zebra_test::init();

legacy_chain_for_network(Network::Mainnet)?;
legacy_chain_for_network(Network::Testnet)?;

Ok(())
}
jvff marked this conversation as resolved.
Show resolved Hide resolved

/// Test for legacy chain in different scenarios.
fn legacy_chain_for_network(network: Network) -> Result<()> {
zebra_test::init();

const DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES: u32 = 2;
const BLOCKS_AFTER_NU5: u32 = 100;

if let Some(nu5_height) = NetworkUpgrade::Nu5.activation_height(network) {
// Test if we can find at least one transaction with `network_upgrade` field in the chain.
let strategy1 = zebra_chain::block::LedgerState::height_strategy(
Height(nu5_height.0 + BLOCKS_AFTER_NU5),
Some(NetworkUpgrade::Nu5),
Some(5),
true,
)
.prop_flat_map(|init| Block::partial_chain_strategy(init, BLOCKS_AFTER_NU5 as usize));
jvff marked this conversation as resolved.
Show resolved Hide resolved

proptest!(ProptestConfig::with_cases(env::var("PROPTEST_CASES")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES)),
|(chain in strategy1)| {
let response = crate::service::legacy_chain_check(Height(nu5_height.0 + BLOCKS_AFTER_NU5), chain.into_iter(), network);
prop_assert_eq!(response, Ok(()));
}
);

// Test that we can't find at least one transaction with `network_upgrade` field in the chain.
let strategy2 = zebra_chain::block::LedgerState::height_strategy(
Height(nu5_height.0 + BLOCKS_AFTER_NU5),
Some(NetworkUpgrade::Nu5),
Some(4),
true,
)
.prop_flat_map(|init| Block::partial_chain_strategy(init, BLOCKS_AFTER_NU5 as usize + 1));

proptest!(ProptestConfig::with_cases(env::var("PROPTEST_CASES")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES)),
|(chain in strategy2)| {
let response = crate::service::legacy_chain_check(Height(nu5_height.0 + BLOCKS_AFTER_NU5 + 1), chain.into_iter(), network);
prop_assert_eq!(response.is_err(), true);
prop_assert_eq!(response.err().unwrap().to_string(), "giving up after checking too many blocks");
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
}
);

// Test that there is at least one transaction in the chain with an inconsistent `network_upgrade` field.
let strategy3 = zebra_chain::block::LedgerState::height_strategy(
Height(nu5_height.0 + BLOCKS_AFTER_NU5),
Some(NetworkUpgrade::Nu5),
Some(5),
false,
)
.prop_flat_map(|init| Block::partial_chain_strategy(init, BLOCKS_AFTER_NU5 as usize));

proptest!(ProptestConfig::with_cases(env::var("PROPTEST_CASES")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES)),
|(chain in strategy3)| {
let response = crate::service::legacy_chain_check(Height(nu5_height.0 + BLOCKS_AFTER_NU5 - 1), chain.into_iter(), network);
if response.is_err() {
prop_assert_eq!(response.is_err(), true);
prop_assert_eq!(response.err().unwrap().to_string(), "inconsistent network upgrade found in transaction");
}
}
);
}

Ok(())
}
*/

const DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES: u32 = 2;
const BLOCKS_AFTER_NU5: u32 = 100;

proptest! {
#![proptest_config(
proptest::test_runner::Config::with_cases(env::var("PROPTEST_CASES")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES))
)]

#[test]
fn at_least_one_transaction_with_network_upgrade(
(network, height, chain) in partial_nu5_chain_strategy(5, true, BLOCKS_AFTER_NU5)
) {
let response = crate::service::legacy_chain_check(height, chain.into_iter(), network)
.map_err(|error| error.to_string());

prop_assert_eq!(response, Ok(()));
}

#[test]
fn no_transactions_with_network_upgrade(
(network, height, chain) in partial_nu5_chain_strategy(4, true, BLOCKS_AFTER_NU5 + 1)
) {
let response = crate::service::legacy_chain_check(height, chain.into_iter(), network)
.map_err(|error| error.to_string());

prop_assert_eq!(
response,
Err("giving up after checking too many blocks".into())
);
}

#[test]
fn at_least_one_transactions_with_inconsistent_network_upgrade(
(network, height, chain) in partial_nu5_chain_strategy(5, false, BLOCKS_AFTER_NU5)
) {
let response = crate::service::legacy_chain_check(height, chain.into_iter(), network)
.map_err(|error| error.to_string());

prop_assert_eq!(
response,
Err("inconsistent network upgrade found in transaction".into())
);
}
}

// Utility functions

fn partial_nu5_chain_strategy(
transaction_version_override: u32,
transaction_has_valid_network_upgrade: bool,
blocks_after_nu5_activation: u32,
) -> impl Strategy<
Value = (
Network,
Height,
zebra_chain::fmt::SummaryDebug<Vec<Arc<Block>>>,
),
> {
any::<Network>().prop_flat_map(move |network| {
let nu5_height = NetworkUpgrade::Nu5
.activation_height(network)
.expect("NU5 activation height not set");
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
let height = Height(nu5_height.0 + blocks_after_nu5_activation);

let mut nu = NetworkUpgrade::Nu5;
if !transaction_has_valid_network_upgrade {
nu = NetworkUpgrade::Canopy;
}
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
zebra_chain::block::LedgerState::height_strategy(
height,
Some(nu),
Some(transaction_version_override),
transaction_has_valid_network_upgrade,
)
.prop_flat_map(move |init| {
Block::partial_chain_strategy(init, blocks_after_nu5_activation as usize)
})
.prop_map(move |partial_chain| (network, height, partial_chain))
})
}
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
23 changes: 22 additions & 1 deletion zebrad/src/commands/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
use abscissa_core::{config, Command, FrameworkError, Options, Runnable};
use color_eyre::eyre::{eyre, Report};
use tokio::sync::oneshot;
use tower::builder::ServiceBuilder;
use tower::{builder::ServiceBuilder, ServiceExt};

use crate::components::{tokio::RuntimeRun, Inbound};
use crate::config::ZebradConfig;
Expand Down Expand Up @@ -54,6 +54,27 @@ impl StartCmd {
config.network.network,
));

info!("starting legacy chain check");
match state
.clone()
.oneshot(zebra_state::Request::IsLegacyChain)
.await
{
Ok(zebra_state::Response::LegacyChain(response)) => {
if let Some(corrupt_db_path) = response {
// we found a legacy chain
panic!(
"Legacy chain found, database can be corrupted.
Delete your database and retry a full sync.
Database path: {:?}",
corrupt_db_path,
);
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
}
}
_ => unreachable!("the legacy chain call should always respond"),
};
info!("no legacy chain found");
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved

info!("initializing verifiers");
let verifier = zebra_consensus::chain::init(
config.consensus.clone(),
Expand Down
25 changes: 25 additions & 0 deletions zebrad/tests/acceptance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1327,3 +1327,28 @@ where

Ok(())
}

#[test]
fn legacy_chain() -> Result<()> {
zebra_test::init();

// start caches state, so run one of the start tests with persistent state
let testdir = testdir()?.with_config(&mut persistent_test_config()?)?;

let mut child = testdir.spawn_child(&["-v", "start"])?;

// Run the program and kill it after a few seconds
std::thread::sleep(LAUNCH_DELAY);
child.kill()?;

let output = child.wait_with_output()?;
let output = output.assert_failure()?;

output.stdout_line_contains("starting legacy chain check")?;
output.stdout_line_contains("no legacy chain found")?;
teor2345 marked this conversation as resolved.
Show resolved Hide resolved

// Make sure the command was killed
output.assert_was_killed()?;

Ok(())
}