From dc514b82e2d37afd0a0d9b13375704034318e4c8 Mon Sep 17 00:00:00 2001 From: Arya Date: Mon, 10 Jun 2024 21:22:03 -0400 Subject: [PATCH] Tests that `ChainTipChange` is updated when the non-finalized best chain grows --- .../types/submit_block.rs | 4 +- zebrad/tests/acceptance.rs | 36 +++++-- zebrad/tests/common/regtest.rs | 93 +++++++++++-------- 3 files changed, 86 insertions(+), 47 deletions(-) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs index e135b0a55..bf3b22d88 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs @@ -29,7 +29,7 @@ pub struct JsonParameters { /// Response to a `submitblock` RPC request. /// /// Zebra never returns "duplicate-invalid", because it does not store invalid blocks. -#[derive(Debug, PartialEq, Eq, serde::Serialize)] +#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "kebab-case")] pub enum ErrorResponse { /// Block was already committed to the non-finalized or finalized state @@ -45,7 +45,7 @@ pub enum ErrorResponse { /// Response to a `submitblock` RPC request. /// /// Zebra never returns "duplicate-invalid", because it does not store invalid blocks. -#[derive(Debug, PartialEq, Eq, serde::Serialize)] +#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(untagged)] pub enum Response { /// Block was not successfully submitted, return error diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 594b35d16..f924dc51b 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -3173,15 +3173,18 @@ async fn regtest_submit_blocks() -> Result<()> { // TODO: // - Test that chain forks are handled correctly and that `ChainTipChange` sees a `Reset` -// - Test that `ChainTipChange` is updated when the non-finalized best chain grows // - Test that the `ChainTipChange` updated by the RPC syncer is updated when the finalized tip changes #[cfg(feature = "rpc-syncer")] #[tokio::test] async fn trusted_chain_sync_handles_forks_correctly() -> Result<()> { + use common::regtest::MiningRpcMethods; + use tokio::time::timeout; + use zebra_chain::parameters::testnet::REGTEST_NU5_ACTIVATION_HEIGHT; + let _init_guard = zebra_test::init(); - let mut config = random_known_rpc_port_config(false, &Mainnet)?; + let mut config = random_known_rpc_port_config(false, &Network::new_regtest(None))?; config.state.ephemeral = false; - config.network.network = Network::new_regtest(None); + let rpc_address = config.rpc.listen_addr.unwrap(); let testdir = testdir()?.with_config(&mut config)?; let mut child = testdir.spawn_child(args!["start"])?; @@ -3189,17 +3192,38 @@ async fn trusted_chain_sync_handles_forks_correctly() -> Result<()> { std::thread::sleep(LAUNCH_DELAY); // Spawn a read state with the RPC syncer to check that it has the same best chain as Zebra - let (_read_state, _latest_chain_tip, _chain_tip_change, _sync_task) = + let (_read_state, _latest_chain_tip, mut chain_tip_change, _sync_task) = zebra_rpc::sync::init_read_state_with_syncer( config.state, &config.network.network, - config.rpc.listen_addr.unwrap(), + rpc_address, ) .await? .map_err(|err| eyre!(err))?; + let rpc_client = RpcRequestClient::new(rpc_address); + + for _ in 0..100 { + let (block, height) = rpc_client + .block_from_template(Height(REGTEST_NU5_ACTIVATION_HEIGHT)) + .await?; + + rpc_client.submit_block(block).await?; + + let tip_action = timeout( + Duration::from_secs(1), + chain_tip_change.wait_for_tip_change(), + ) + .await??; + + assert_eq!( + tip_action.best_tip_height(), + height, + "tip action height should match block submission" + ); + } + // TODO: - // - Submit blocks while checking that `ChainTipChange` shows the chain growing before submitting the next block // - Submit several blocks before checking that `ChainTipChange` has the latest block hash and is a `TipAction::Reset` // - Check that `getblock` RPC returns the same block as the read state for every height // - Submit more blocks with an older block template (and a different nonce so the hash is different) to trigger a chain reorg diff --git a/zebrad/tests/common/regtest.rs b/zebrad/tests/common/regtest.rs index b089cb8e8..b031df68a 100644 --- a/zebrad/tests/common/regtest.rs +++ b/zebrad/tests/common/regtest.rs @@ -5,17 +5,19 @@ use std::{net::SocketAddr, sync::Arc, time::Duration}; -use color_eyre::eyre::{Context, Result}; +use color_eyre::eyre::{eyre, Context, Result}; use tracing::*; use zebra_chain::{ + block::{Block, Height}, parameters::{testnet::REGTEST_NU5_ACTIVATION_HEIGHT, Network, NetworkUpgrade}, primitives::byte_array::increment_big_endian, serialization::ZcashSerialize, }; use zebra_node_services::rpc_client::RpcRequestClient; -use zebra_rpc::methods::get_block_template_rpcs::get_block_template::{ - proposal::TimeSource, proposal_block_from_template, GetBlockTemplate, +use zebra_rpc::methods::get_block_template_rpcs::{ + get_block_template::{proposal::TimeSource, proposal_block_from_template, GetBlockTemplate}, + types::submit_block, }; use zebra_test::args; @@ -62,23 +64,10 @@ pub(crate) async fn submit_blocks_test() -> Result<()> { async fn submit_blocks(network: Network, rpc_address: SocketAddr) -> Result<()> { let client = RpcRequestClient::new(rpc_address); - for height in 1..=NUM_BLOCKS_TO_SUBMIT { - let block_template: GetBlockTemplate = client - .json_result_from_call("getblocktemplate", "[]".to_string()) - .await - .expect("response should be success output with a serialized `GetBlockTemplate`"); - - let network_upgrade = if height < REGTEST_NU5_ACTIVATION_HEIGHT.try_into().unwrap() { - NetworkUpgrade::Canopy - } else { - NetworkUpgrade::Nu5 - }; - - let mut block = - proposal_block_from_template(&block_template, TimeSource::default(), network_upgrade)?; - let height = block - .coinbase_height() - .expect("should have a coinbase height"); + for _ in 1..=NUM_BLOCKS_TO_SUBMIT { + let (mut block, height) = client + .block_from_template(Height(REGTEST_NU5_ACTIVATION_HEIGHT)) + .await?; while !network.disable_pow() && zebra_consensus::difficulty_is_valid(&block.header, &network, &height, &block.hash()) @@ -87,29 +76,55 @@ async fn submit_blocks(network: Network, rpc_address: SocketAddr) -> Result<()> 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 - .text_from_call("submitblock", format!(r#"["{block_data}"]"#)) - .await?; - - let was_submission_successful = submit_block_response.contains(r#""result":null"#); - if height.0 % 40 == 0 { - info!( - was_submission_successful, - ?block_template, - ?network_upgrade, - "submitted block" - ); + info!(?block, ?height, "submitting block"); } - // Check that the block was validated and committed. - assert!( - submit_block_response.contains(r#""result":null"#), - "unexpected response from submitblock RPC, should be null, was: {submit_block_response}" - ); + client.submit_block(block).await?; } Ok(()) } + +pub trait MiningRpcMethods { + async fn block_from_template(&self, nu5_activation_height: Height) -> Result<(Block, Height)>; + async fn submit_block(&self, block: Block) -> Result<()>; +} + +impl MiningRpcMethods for RpcRequestClient { + async fn block_from_template(&self, nu5_activation_height: Height) -> Result<(Block, Height)> { + let block_template: GetBlockTemplate = self + .json_result_from_call("getblocktemplate", "[]".to_string()) + .await + .expect("response should be success output with a serialized `GetBlockTemplate`"); + + let height = Height(block_template.height); + + let network_upgrade = if height < nu5_activation_height { + NetworkUpgrade::Canopy + } else { + NetworkUpgrade::Nu5 + }; + + Ok(( + proposal_block_from_template(&block_template, TimeSource::default(), network_upgrade)?, + height, + )) + } + + async fn submit_block(&self, block: Block) -> Result<()> { + let block_data = hex::encode(block.zcash_serialize_to_vec()?); + + let submit_block_response: submit_block::Response = self + .json_result_from_call("submitblock", format!(r#"["{block_data}"]"#)) + .await + .map_err(|err| eyre!(err))?; + + match submit_block_response { + submit_block::Response::Accepted => Ok(()), + submit_block::Response::ErrorResponse(err) => { + Err(eyre!("block submission failed: {err:?}")) + } + } + } +}