- When proof of work is disabled, skips checking if Zebra is synced to the network tip in the getblocktemplate method

- When network is regtest, skips starting sync task and commits the genesis block if it's missing in the state
- Adds `regtest_submits_blocks` test
This commit is contained in:
Arya 2024-04-24 20:16:30 -04:00
parent 2fcf6fe309
commit 51f66f4ab4
8 changed files with 147 additions and 15 deletions

View File

@ -638,7 +638,10 @@ where
//
// Optional TODO:
// - add `async changed()` method to ChainSyncStatus (like `ChainTip`)
check_synced_to_tip(&network, latest_chain_tip.clone(), sync_status.clone())?;
// TODO: Add a `disable_peers` field to `Network` to check instead of `disable_pow()` (#8361)
if !network.disable_pow() {
check_synced_to_tip(&network, latest_chain_tip.clone(), sync_status.clone())?;
}
// TODO: return an error if we have no peers, like `zcashd` does,
// and add a developer config that mines regardless of how many peers we have.

View File

@ -395,7 +395,7 @@ impl StateService {
- HeightDiff::try_from(checkpoint_verify_concurrency_limit)
.expect("fits in HeightDiff");
let full_verifier_utxo_lookahead =
full_verifier_utxo_lookahead.expect("unexpected negative height");
full_verifier_utxo_lookahead.unwrap_or(block::Height::MIN);
let non_finalized_state_queued_blocks = QueuedBlocks::default();
let pending_utxos = PendingUtxos::default();

View File

@ -78,14 +78,17 @@
//!
//! Some of the diagnostic features are optional, and need to be enabled at compile-time.
use std::sync::Arc;
use abscissa_core::{config, Command, FrameworkError};
use color_eyre::eyre::{eyre, Report};
use futures::FutureExt;
use tokio::{pin, select, sync::oneshot};
use tower::{builder::ServiceBuilder, util::BoxService};
use tower::{builder::ServiceBuilder, util::BoxService, ServiceExt};
use tracing_futures::Instrument;
use zebra_consensus::router::BackgroundTaskHandles;
use zebra_chain::block::genesis::regtest_genesis_block;
use zebra_consensus::{router::BackgroundTaskHandles, ParameterCheckpoint};
use zebra_rpc::server::RpcServer;
use crate::{
@ -110,9 +113,8 @@ pub struct StartCmd {
}
impl StartCmd {
async fn start(&self) -> Result<(), Report> {
let config = APPLICATION.config();
/// Starts `zebrad` with the provided config.
pub async fn start(&self, config: Arc<ZebradConfig>) -> Result<(), Report> {
info!("initializing node state");
let (_, max_checkpoint_height) = zebra_consensus::router::init_checkpoint_list(
config.consensus.clone(),
@ -135,7 +137,7 @@ impl StartCmd {
read_only_state_service.log_db_metrics();
let state = ServiceBuilder::new()
.buffer(Self::state_buffer_bound())
.buffer(Self::state_buffer_bound(config.clone()))
.service(state_service);
info!("initializing network");
@ -177,7 +179,7 @@ impl StartCmd {
.await;
info!("initializing syncer");
let (syncer, sync_status) = ChainSync::new(
let (mut syncer, sync_status) = ChainSync::new(
&config,
max_checkpoint_height,
peer_set.clone(),
@ -300,7 +302,28 @@ impl StartCmd {
);
info!("spawning syncer task");
let syncer_task_handle = tokio::spawn(syncer.sync().in_current_span());
let syncer_task_handle = if config.network.network.is_regtest() {
if !syncer
.state_contains(config.network.network.genesis_hash())
.await?
{
let genesis_hash = block_verifier_router
.clone()
.oneshot(zebra_consensus::Request::Commit(regtest_genesis_block()))
.await
.expect("should validate Regtest genesis block");
assert_eq!(
genesis_hash,
config.network.network.genesis_hash(),
"validated block hash should match network genesis hash"
)
}
tokio::spawn(std::future::pending().in_current_span())
} else {
tokio::spawn(syncer.sync().in_current_span())
};
#[cfg(feature = "shielded-scan")]
// Spawn never ending scan task only if we have keys to scan for.
@ -505,9 +528,7 @@ impl StartCmd {
/// Returns the bound for the state service buffer,
/// based on the configurations of the services that use the state concurrently.
fn state_buffer_bound() -> usize {
let config = APPLICATION.config();
fn state_buffer_bound(config: Arc<ZebradConfig>) -> usize {
// Ignore the checkpoint verify limit, because it is very large.
//
// TODO: do we also need to account for concurrent use across services?
@ -536,8 +557,10 @@ impl Runnable for StartCmd {
.rt
.take();
let config = APPLICATION.config();
rt.expect("runtime should not already be taken")
.run(self.start());
.run(self.start(config));
info!("stopping zebrad");
}

View File

@ -1136,7 +1136,7 @@ where
/// Returns `true` if the hash is present in the state, and `false`
/// if the hash is not present in the state.
async fn state_contains(&mut self, hash: block::Hash) -> Result<bool, Report> {
pub(crate) async fn state_contains(&mut self, hash: block::Hash) -> Result<bool, Report> {
match self
.state
.ready()

View File

@ -3126,3 +3126,15 @@ async fn validate_regtest_genesis_block() {
"validated block hash should match network genesis hash"
)
}
/// Test successful `getblocktemplate` and `submitblock` RPC calls on Regtest on Canopy and NU5.
///
/// See [`common::regtest::submit_blocks`] for more information.
#[tokio::test]
#[cfg(feature = "getblocktemplate-rpcs")]
async fn regtest_submit_blocks() -> Result<()> {
for nu5_activation_height in [None, Some(1), Some(1_000)] {
common::regtest::submit_blocks::run(nu5_activation_height).await?;
}
Ok(())
}

View File

@ -24,5 +24,8 @@ pub mod checkpoints;
#[cfg(feature = "getblocktemplate-rpcs")]
pub mod get_block_template_rpcs;
#[cfg(feature = "getblocktemplate-rpcs")]
pub mod regtest;
#[cfg(feature = "shielded-scan")]
pub mod shielded_scan;

View File

@ -0,0 +1,3 @@
//! Acceptance tests for Regtest in Zebra.
pub(crate) mod submit_blocks;

View File

@ -0,0 +1,88 @@
//! Test submitblock RPC method on Regtest.
//!
//! This test will get block templates via the `getblocktemplate` RPC method and submit them as new blocks
//! via the `submitblock` RPC method on Regtest.
use std::{net::SocketAddr, sync::Arc, time::Duration};
use color_eyre::eyre::Result;
use tokio::task::JoinHandle;
use tracing::*;
use zebra_chain::{parameters::Network, 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 crate::common::config::random_known_rpc_port_config;
use zebrad::commands::StartCmd;
/// Number of blocks that should be submitted before the test is considered successful.
const NUM_BLOCKS_TO_SUBMIT: usize = 2_000;
/// - Starts Zebrad
/// - Calls `getblocktemplate` RPC method and submits blocks with empty equihash solutions
/// - Checks that blocks were validated and committed successfully
// TODO: Implement custom serialization for `zebra_network::Config` and spawn a zebrad child process instead?
pub(crate) async fn run(nu5_activation_height: Option<u32>) -> Result<()> {
let _init_guard = zebra_test::init();
info!("starting regtest submit_blocks test");
let network = Network::new_regtest(nu5_activation_height);
let config = random_known_rpc_port_config(false, &network)?;
let rpc_address = config.rpc.listen_addr.unwrap();
info!("starting zebrad");
let _zebrad_start_task: JoinHandle<_> = tokio::task::spawn_blocking(|| {
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("runtime building should not fail");
let start_cmd = StartCmd::default();
let _ = rt.block_on(start_cmd.start(Arc::new(config)));
});
info!("waiting for zebrad to start");
tokio::time::sleep(Duration::from_secs(30)).await;
info!("attempting to submit blocks");
submit_blocks(rpc_address).await?;
Ok(())
}
/// Get block templates and submit blocks
async fn submit_blocks(rpc_address: SocketAddr) -> Result<()> {
let client = RpcRequestClient::new(rpc_address);
for _ in 0..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 block_data = hex::encode(
proposal_block_from_template(&block_template, TimeSource::default())?
.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"#);
info!(was_submission_successful, "submitted 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}"
);
}
Ok(())
}