177 lines
5.9 KiB
Rust
177 lines
5.9 KiB
Rust
//! Test submitblock RPC method.
|
|
//!
|
|
//! This test requires a cached chain state that is synchronized past the max checkpoint height,
|
|
//! and will sync to the next block without updating the cached chain state.
|
|
|
|
// TODO: Update this test and the doc to:
|
|
//
|
|
// This test requires a cached chain state that is partially synchronized close to the
|
|
// network chain tip height, and will finish the sync and update the cached chain state.
|
|
//
|
|
// After finishing the sync, it will get the first 20 blocks in the non-finalized state
|
|
// (past the MAX_BLOCK_REORG_HEIGHT) via getblock rpc calls, get the finalized tip height
|
|
// of the updated cached state, restart zebra without peers, and submit blocks above the
|
|
// finalized tip height.
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use color_eyre::eyre::{eyre, Context, Result};
|
|
|
|
use futures::TryFutureExt;
|
|
use indexmap::IndexSet;
|
|
use reqwest::Client;
|
|
use tower::{Service, ServiceExt};
|
|
use zebra_chain::{block::Height, parameters::Network, serialization::ZcashSerialize};
|
|
use zebra_state::HashOrHeight;
|
|
use zebra_test::args;
|
|
|
|
use crate::common::{
|
|
cached_state::{copy_state_directory, start_state_service_with_cache_dir},
|
|
config::{persistent_test_config, testdir},
|
|
launch::ZebradTestDirExt,
|
|
lightwalletd::random_known_rpc_port_config,
|
|
};
|
|
|
|
use super::cached_state::{load_tip_height_from_state_directory, ZEBRA_CACHED_STATE_DIR};
|
|
|
|
async fn get_future_block_hex_data(
|
|
network: Network,
|
|
zebrad_state_path: &PathBuf,
|
|
) -> Result<Option<String>> {
|
|
tracing::info!(
|
|
?zebrad_state_path,
|
|
"getting cached sync height from ZEBRA_CACHED_STATE_DIR path"
|
|
);
|
|
|
|
let cached_sync_height =
|
|
load_tip_height_from_state_directory(network, zebrad_state_path.as_ref()).await?;
|
|
|
|
let future_block_height = Height(cached_sync_height.0 + 1);
|
|
|
|
tracing::info!(
|
|
?cached_sync_height,
|
|
?future_block_height,
|
|
"got cached sync height, copying state dir to tempdir"
|
|
);
|
|
|
|
let copied_state_path = copy_state_directory(network, &zebrad_state_path).await?;
|
|
|
|
let mut config = persistent_test_config()?;
|
|
config.state.debug_stop_at_height = Some(future_block_height.0);
|
|
|
|
let mut child = copied_state_path
|
|
.with_config(&mut config)?
|
|
.spawn_child(args!["start"])?
|
|
.bypass_test_capture(true);
|
|
|
|
while child.is_running() {
|
|
tokio::task::yield_now().await;
|
|
}
|
|
|
|
let _ = child.kill(true);
|
|
let copied_state_path = child.dir.take().unwrap();
|
|
|
|
let (_read_write_state_service, mut state, _latest_chain_tip, _chain_tip_change) =
|
|
start_state_service_with_cache_dir(network, copied_state_path.as_ref()).await?;
|
|
let request = zebra_state::ReadRequest::Block(HashOrHeight::Height(future_block_height));
|
|
|
|
let response = state
|
|
.ready()
|
|
.and_then(|ready_service| ready_service.call(request))
|
|
.map_err(|error| eyre!(error))
|
|
.await?;
|
|
|
|
let block_hex_data = match response {
|
|
zebra_state::ReadResponse::Block(Some(block)) => {
|
|
hex::encode(block.zcash_serialize_to_vec()?)
|
|
}
|
|
zebra_state::ReadResponse::Block(None) => {
|
|
tracing::info!(
|
|
"Reached the end of the finalized chain, state is missing block at {future_block_height:?}",
|
|
);
|
|
return Ok(None);
|
|
}
|
|
_ => unreachable!("Incorrect response from state service: {response:?}"),
|
|
};
|
|
|
|
Ok(Some(block_hex_data))
|
|
}
|
|
|
|
#[allow(clippy::print_stderr)]
|
|
pub(crate) async fn run() -> Result<(), color_eyre::Report> {
|
|
let _init_guard = zebra_test::init();
|
|
|
|
let mut config = random_known_rpc_port_config(true)?;
|
|
let network = config.network.network;
|
|
let rpc_address = config.rpc.listen_addr.unwrap();
|
|
|
|
config.state.cache_dir = match std::env::var_os(ZEBRA_CACHED_STATE_DIR) {
|
|
Some(path) => path.into(),
|
|
None => {
|
|
eprintln!(
|
|
"skipped submitblock test, \
|
|
set the {ZEBRA_CACHED_STATE_DIR:?} environment variable to run the test",
|
|
);
|
|
|
|
return Ok(());
|
|
}
|
|
};
|
|
|
|
// TODO: As part of or as a pre-cursor to issue #5015,
|
|
// - Use only original cached state,
|
|
// - sync until the tip
|
|
// - get first 3 blocks in non-finalized state via getblock rpc calls
|
|
// - restart zebra without peers
|
|
// - submit block(s) above the finalized tip height
|
|
let block_hex_data = get_future_block_hex_data(network, &config.state.cache_dir)
|
|
.await?
|
|
.expect(
|
|
"spawned zebrad in get_future_block_hex_data should live until it gets the next block",
|
|
);
|
|
|
|
// Runs the rest of this test without an internet connection
|
|
config.network.initial_mainnet_peers = IndexSet::new();
|
|
config.network.initial_testnet_peers = IndexSet::new();
|
|
config.mempool.debug_enable_at_height = Some(0);
|
|
|
|
// We're using the cached state
|
|
config.state.ephemeral = false;
|
|
|
|
let mut child = testdir()?
|
|
.with_exact_config(&config)?
|
|
.spawn_child(args!["start"])?
|
|
.bypass_test_capture(true);
|
|
|
|
child.expect_stdout_line_matches(&format!("Opened RPC endpoint at {rpc_address}"))?;
|
|
|
|
// Create an http client
|
|
let client = Client::new();
|
|
|
|
let res = client
|
|
.post(format!("http://{}", &rpc_address))
|
|
.body(format!(
|
|
r#"{{"jsonrpc": "2.0", "method": "submitblock", "params": ["{block_hex_data}"], "id":123 }}"#
|
|
))
|
|
.header("Content-Type", "application/json")
|
|
.send()
|
|
.await?;
|
|
|
|
assert!(res.status().is_success());
|
|
let res_text = res.text().await?;
|
|
|
|
// Test rpc endpoint response
|
|
assert!(res_text.contains(r#""result":"null""#));
|
|
|
|
child.kill(false)?;
|
|
|
|
let output = child.wait_with_output()?;
|
|
let output = output.assert_failure()?;
|
|
|
|
// [Note on port conflict](#Note on port conflict)
|
|
output
|
|
.assert_was_killed()
|
|
.wrap_err("Possible port conflict. Are there other acceptance tests running?")?;
|
|
|
|
Ok(())
|
|
}
|