Zebra/zebra-rpc/src/methods/tests/vectors.rs

934 lines
31 KiB
Rust

//! Fixed test vectors for RPC methods.
use std::{ops::RangeInclusive, sync::Arc};
use jsonrpc_core::ErrorCode;
use tower::buffer::Buffer;
use zebra_chain::{
block::Block,
chain_tip::NoChainTip,
parameters::Network::*,
serialization::{ZcashDeserializeInto, ZcashSerialize},
transaction::{UnminedTx, UnminedTxId},
transparent,
};
use zebra_network::constants::USER_AGENT;
use zebra_node_services::BoxError;
use zebra_test::mock_service::MockService;
use super::super::*;
#[tokio::test(flavor = "multi_thread")]
async fn rpc_getinfo() {
let _init_guard = zebra_test::init();
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
"RPC test",
Mainnet,
false,
Buffer::new(mempool.clone(), 1),
Buffer::new(state.clone(), 1),
NoChainTip,
);
let get_info = rpc.get_info().expect("We should have a GetInfo struct");
// make sure there is a `build` field in the response,
// and that is equal to the provided string, with an added 'v' version prefix.
assert_eq!(get_info.build, "vRPC test");
// make sure there is a `subversion` field,
// and that is equal to the Zebra user agent.
assert_eq!(get_info.subversion, USER_AGENT);
mempool.expect_no_requests().await;
state.expect_no_requests().await;
// The queue task should continue without errors or panics
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
assert!(matches!(rpc_tx_queue_task_result, None));
}
#[tokio::test(flavor = "multi_thread")]
async fn rpc_getblock() {
let _init_guard = zebra_test::init();
// Create a continuous chain of mainnet blocks from genesis
let blocks: Vec<Arc<Block>> = zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS
.iter()
.map(|(_height, block_bytes)| block_bytes.zcash_deserialize_into().unwrap())
.collect();
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
// Create a populated state service
let (_state, read_state, latest_chain_tip, _chain_tip_change) =
zebra_state::populated_state(blocks.clone(), Mainnet).await;
// Init RPC
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
"RPC test",
Mainnet,
false,
Buffer::new(mempool.clone(), 1),
read_state,
latest_chain_tip,
);
// Make calls with verbosity=0 and check response
for (i, block) in blocks.iter().enumerate() {
let get_block = rpc
.get_block(i.to_string(), 0u8)
.await
.expect("We should have a GetBlock struct");
assert_eq!(get_block, GetBlock::Raw(block.clone().into()));
}
// Make calls with verbosity=1 and check response
for (i, block) in blocks.iter().enumerate() {
let get_block = rpc
.get_block(i.to_string(), 1u8)
.await
.expect("We should have a GetBlock struct");
assert_eq!(
get_block,
GetBlock::Object {
tx: block
.transactions
.iter()
.map(|tx| tx.hash().encode_hex())
.collect()
}
);
}
mempool.expect_no_requests().await;
// The queue task should continue without errors or panics
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
assert!(matches!(rpc_tx_queue_task_result, None));
}
#[tokio::test(flavor = "multi_thread")]
async fn rpc_getblock_parse_error() {
let _init_guard = zebra_test::init();
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
// Init RPC
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
"RPC test",
Mainnet,
false,
Buffer::new(mempool.clone(), 1),
Buffer::new(state.clone(), 1),
NoChainTip,
);
// Make sure we get an error if Zebra can't parse the block height.
assert!(rpc
.get_block("not parsable as height".to_string(), 0u8)
.await
.is_err());
mempool.expect_no_requests().await;
state.expect_no_requests().await;
// The queue task should continue without errors or panics
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
assert!(matches!(rpc_tx_queue_task_result, None));
}
#[tokio::test(flavor = "multi_thread")]
async fn rpc_getblock_missing_error() {
let _init_guard = zebra_test::init();
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
// Init RPC
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
"RPC test",
Mainnet,
false,
Buffer::new(mempool.clone(), 1),
Buffer::new(state.clone(), 1),
NoChainTip,
);
// Make sure Zebra returns the correct error code `-8` for missing blocks
// https://github.com/adityapk00/lightwalletd/blob/c1bab818a683e4de69cd952317000f9bb2932274/common/common.go#L251-L254
let block_future = tokio::spawn(rpc.get_block("0".to_string(), 0u8));
// Make the mock service respond with no block
let response_handler = state
.expect_request(zebra_state::ReadRequest::Block(Height(0).into()))
.await;
response_handler.respond(zebra_state::ReadResponse::Block(None));
let block_response = block_future.await;
let block_response = block_response
.expect("unexpected panic in spawned request future")
.expect_err("unexpected success from missing block state response");
assert_eq!(block_response.code, ErrorCode::ServerError(-8),);
// Now check the error string the way `lightwalletd` checks it
assert_eq!(
serde_json::to_string(&block_response)
.expect("unexpected error serializing JSON error")
.split(':')
.nth(1)
.expect("unexpectedly low number of error fields")
.split(',')
.next(),
Some("-8")
);
mempool.expect_no_requests().await;
state.expect_no_requests().await;
// The queue task should continue without errors or panics
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
assert!(matches!(rpc_tx_queue_task_result, None));
}
#[tokio::test(flavor = "multi_thread")]
async fn rpc_getbestblockhash() {
let _init_guard = zebra_test::init();
// Create a continuous chain of mainnet blocks from genesis
let blocks: Vec<Arc<Block>> = zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS
.iter()
.map(|(_height, block_bytes)| block_bytes.zcash_deserialize_into().unwrap())
.collect();
// Get the hash of the block at the tip using hardcoded block tip bytes.
// We want to test the RPC response is equal to this hash
let tip_block = blocks.last().unwrap();
let tip_block_hash = tip_block.hash();
// Get a mempool handle
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
// Create a populated state service, the tip will be in `NUMBER_OF_BLOCKS`.
let (_state, read_state, latest_chain_tip, _chain_tip_change) =
zebra_state::populated_state(blocks.clone(), Mainnet).await;
// Init RPC
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
"RPC test",
Mainnet,
false,
Buffer::new(mempool.clone(), 1),
read_state,
latest_chain_tip,
);
// Get the tip hash using RPC method `get_best_block_hash`
let get_best_block_hash = rpc
.get_best_block_hash()
.expect("We should have a GetBlockHash struct");
let response_hash = get_best_block_hash.0;
// Check if response is equal to block 10 hash.
assert_eq!(response_hash, tip_block_hash);
mempool.expect_no_requests().await;
// The queue task should continue without errors or panics
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
assert!(matches!(rpc_tx_queue_task_result, None));
}
#[tokio::test(flavor = "multi_thread")]
async fn rpc_getrawtransaction() {
let _init_guard = zebra_test::init();
// Create a continuous chain of mainnet blocks from genesis
let blocks: Vec<Arc<Block>> = zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS
.iter()
.map(|(_height, block_bytes)| block_bytes.zcash_deserialize_into().unwrap())
.collect();
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
// Create a populated state service
let (_state, read_state, latest_chain_tip, _chain_tip_change) =
zebra_state::populated_state(blocks.clone(), Mainnet).await;
// Init RPC
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
"RPC test",
Mainnet,
false,
Buffer::new(mempool.clone(), 1),
read_state,
latest_chain_tip,
);
// Test case where transaction is in mempool.
// Skip genesis because its tx is not indexed.
for block in blocks.iter().skip(1) {
for tx in block.transactions.iter() {
let mempool_req = mempool
.expect_request_that(|request| {
if let mempool::Request::TransactionsByMinedId(ids) = request {
ids.len() == 1 && ids.contains(&tx.hash())
} else {
false
}
})
.map(|responder| {
responder.respond(mempool::Response::Transactions(vec![UnminedTx {
id: UnminedTxId::Legacy(tx.hash()),
transaction: tx.clone(),
size: 0,
}]));
});
let get_tx_req = rpc.get_raw_transaction(tx.hash().encode_hex(), 0u8);
let (response, _) = futures::join!(get_tx_req, mempool_req);
let get_tx = response.expect("We should have a GetRawTransaction struct");
if let GetRawTransaction::Raw(raw_tx) = get_tx {
assert_eq!(raw_tx.as_ref(), tx.zcash_serialize_to_vec().unwrap());
} else {
unreachable!("Should return a Raw enum")
}
}
}
// Test case where transaction is _not_ in mempool.
// Skip genesis because its tx is not indexed.
for block in blocks.iter().skip(1) {
for tx in block.transactions.iter() {
let mempool_req = mempool
.expect_request_that(|request| {
if let mempool::Request::TransactionsByMinedId(ids) = request {
ids.len() == 1 && ids.contains(&tx.hash())
} else {
false
}
})
.map(|responder| {
responder.respond(mempool::Response::Transactions(vec![]));
});
let get_tx_req = rpc.get_raw_transaction(tx.hash().encode_hex(), 0u8);
let (response, _) = futures::join!(get_tx_req, mempool_req);
let get_tx = response.expect("We should have a GetRawTransaction struct");
if let GetRawTransaction::Raw(raw_tx) = get_tx {
assert_eq!(raw_tx.as_ref(), tx.zcash_serialize_to_vec().unwrap());
} else {
unreachable!("Should return a Raw enum")
}
}
}
// The queue task should continue without errors or panics
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
assert!(matches!(rpc_tx_queue_task_result, None));
}
#[tokio::test(flavor = "multi_thread")]
async fn rpc_getaddresstxids_invalid_arguments() {
let _init_guard = zebra_test::init();
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
// Create a continuous chain of mainnet blocks from genesis
let blocks: Vec<Arc<Block>> = zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS
.iter()
.map(|(_height, block_bytes)| block_bytes.zcash_deserialize_into().unwrap())
.collect();
// Create a populated state service
let (_state, read_state, latest_chain_tip, _chain_tip_change) =
zebra_state::populated_state(blocks.clone(), Mainnet).await;
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
"RPC test",
Mainnet,
false,
Buffer::new(mempool.clone(), 1),
Buffer::new(read_state.clone(), 1),
latest_chain_tip,
);
// call the method with an invalid address string
let address = "11111111".to_string();
let addresses = vec![address.clone()];
let start: u32 = 1;
let end: u32 = 2;
let error = rpc
.get_address_tx_ids(GetAddressTxIdsRequest {
addresses: addresses.clone(),
start,
end,
})
.await
.unwrap_err();
assert_eq!(
error.message,
format!(
"invalid address \"{}\": parse error: t-addr decoding error",
address.clone()
)
);
// create a valid address
let address = "t3Vz22vK5z2LcKEdg16Yv4FFneEL1zg9ojd".to_string();
let addresses = vec![address.clone()];
// call the method with start greater than end
let start: u32 = 2;
let end: u32 = 1;
let error = rpc
.get_address_tx_ids(GetAddressTxIdsRequest {
addresses: addresses.clone(),
start,
end,
})
.await
.unwrap_err();
assert_eq!(
error.message,
"start Height(2) must be less than or equal to end Height(1)".to_string()
);
// call the method with start equal zero
let start: u32 = 0;
let end: u32 = 1;
let error = rpc
.get_address_tx_ids(GetAddressTxIdsRequest {
addresses: addresses.clone(),
start,
end,
})
.await
.unwrap_err();
assert_eq!(
error.message,
"start Height(0) and end Height(1) must both be greater than zero".to_string()
);
// call the method outside the chain tip height
let start: u32 = 1;
let end: u32 = 11;
let error = rpc
.get_address_tx_ids(GetAddressTxIdsRequest {
addresses,
start,
end,
})
.await
.unwrap_err();
assert_eq!(
error.message,
"start Height(1) and end Height(11) must both be less than or equal to the chain tip Height(10)".to_string()
);
mempool.expect_no_requests().await;
// The queue task should continue without errors or panics
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
assert!(matches!(rpc_tx_queue_task_result, None));
}
#[tokio::test(flavor = "multi_thread")]
async fn rpc_getaddresstxids_response() {
let _init_guard = zebra_test::init();
for network in [Mainnet, Testnet] {
let blocks: Vec<Arc<Block>> = match network {
Mainnet => &*zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS,
Testnet => &*zebra_test::vectors::CONTINUOUS_TESTNET_BLOCKS,
}
.iter()
.map(|(_height, block_bytes)| block_bytes.zcash_deserialize_into().unwrap())
.collect();
// The first few blocks after genesis send funds to the same founders reward address,
// in one output per coinbase transaction.
//
// Get the coinbase transaction of the first block
// (the genesis block coinbase transaction is ignored by the consensus rules).
let first_block_first_transaction = &blocks[1].transactions[0];
// Get the address.
let address = first_block_first_transaction.outputs()[1]
.address(network)
.unwrap();
if network == Mainnet {
// Exhaustively test possible block ranges for mainnet.
//
// TODO: if it takes too long on slower machines, turn this into a proptest with 10-20 cases
for start in 1..=10 {
for end in start..=10 {
rpc_getaddresstxids_response_with(network, start..=end, &blocks, &address)
.await;
}
}
} else {
// Just test the full range for testnet.
rpc_getaddresstxids_response_with(network, 1..=10, &blocks, &address).await;
}
}
}
async fn rpc_getaddresstxids_response_with(
network: Network,
range: RangeInclusive<u32>,
blocks: &[Arc<Block>],
address: &transparent::Address,
) {
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
// Create a populated state service
let (_state, read_state, latest_chain_tip, _chain_tip_change) =
zebra_state::populated_state(blocks.to_owned(), network).await;
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
"RPC test",
network,
false,
Buffer::new(mempool.clone(), 1),
Buffer::new(read_state.clone(), 1),
latest_chain_tip,
);
// call the method with valid arguments
let addresses = vec![address.to_string()];
let response = rpc
.get_address_tx_ids(GetAddressTxIdsRequest {
addresses,
start: *range.start(),
end: *range.end(),
})
.await
.expect("arguments are valid so no error can happen here");
// One founders reward output per coinbase transactions, no other transactions.
assert_eq!(response.len(), range.count());
mempool.expect_no_requests().await;
// Shut down the queue task, to close the state's file descriptors.
// (If we don't, opening ~100 simultaneous states causes process file descriptor limit errors.)
//
// TODO: abort all the join handles in all the tests, except one?
rpc_tx_queue_task_handle.abort();
// The queue task should not have panicked or exited by itself.
// It can still be running, or it can have exited due to the abort.
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
assert!(
rpc_tx_queue_task_result.is_none()
|| rpc_tx_queue_task_result
.unwrap()
.unwrap_err()
.is_cancelled()
);
}
#[tokio::test(flavor = "multi_thread")]
async fn rpc_getaddressutxos_invalid_arguments() {
let _init_guard = zebra_test::init();
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
let rpc = RpcImpl::new(
"RPC test",
Mainnet,
false,
Buffer::new(mempool.clone(), 1),
Buffer::new(state.clone(), 1),
NoChainTip,
);
// call the method with an invalid address string
let address = "11111111".to_string();
let addresses = vec![address.clone()];
let error = rpc
.0
.get_address_utxos(AddressStrings::new(addresses))
.await
.unwrap_err();
assert_eq!(
error.message,
format!(
"invalid address \"{}\": parse error: t-addr decoding error",
address
)
);
mempool.expect_no_requests().await;
state.expect_no_requests().await;
}
#[tokio::test(flavor = "multi_thread")]
async fn rpc_getaddressutxos_response() {
let _init_guard = zebra_test::init();
let blocks: Vec<Arc<Block>> = zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS
.iter()
.map(|(_height, block_bytes)| block_bytes.zcash_deserialize_into().unwrap())
.collect();
// get the first transaction of the first block
let first_block_first_transaction = &blocks[1].transactions[0];
// get the address, this is always `t3Vz22vK5z2LcKEdg16Yv4FFneEL1zg9ojd`
let address = &first_block_first_transaction.outputs()[1]
.address(Mainnet)
.unwrap();
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
// Create a populated state service
let (_state, read_state, latest_chain_tip, _chain_tip_change) =
zebra_state::populated_state(blocks.clone(), Mainnet).await;
let rpc = RpcImpl::new(
"RPC test",
Mainnet,
false,
Buffer::new(mempool.clone(), 1),
Buffer::new(read_state.clone(), 1),
latest_chain_tip,
);
// call the method with a valid address
let addresses = vec![address.to_string()];
let response = rpc
.0
.get_address_utxos(AddressStrings::new(addresses))
.await
.expect("address is valid so no error can happen here");
// there are 10 outputs for provided address
assert_eq!(response.len(), 10);
mempool.expect_no_requests().await;
}
#[tokio::test(flavor = "multi_thread")]
#[cfg(feature = "getblocktemplate-rpcs")]
async fn rpc_getblockcount() {
let _init_guard = zebra_test::init();
// Create a continuous chain of mainnet blocks from genesis
let blocks: Vec<Arc<Block>> = zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS
.iter()
.map(|(_height, block_bytes)| block_bytes.zcash_deserialize_into().unwrap())
.collect();
// Get the height of the block at the tip using hardcoded block tip bytes.
// We want to test the RPC response is equal to this hash
let tip_block = blocks.last().unwrap();
let tip_block_height = tip_block.coinbase_height().unwrap();
// Get a mempool handle
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
// Create a populated state service, the tip will be in `NUMBER_OF_BLOCKS`.
let (state, read_state, latest_chain_tip, _chain_tip_change) =
zebra_state::populated_state(blocks.clone(), Mainnet).await;
let (
chain_verifier,
_transaction_verifier,
_parameter_download_task_handle,
_max_checkpoint_height,
) = zebra_consensus::chain::init(
zebra_consensus::Config::default(),
Mainnet,
state.clone(),
true,
)
.await;
// Init RPC
let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new(
Mainnet,
Buffer::new(mempool.clone(), 1),
read_state,
latest_chain_tip.clone(),
chain_verifier,
);
// Get the tip height using RPC method `get_block_count`
let get_block_count = get_block_template_rpc
.get_block_count()
.expect("We should have a number");
// Check if response is equal to block 10 hash.
assert_eq!(get_block_count, tip_block_height.0);
mempool.expect_no_requests().await;
}
#[cfg(feature = "getblocktemplate-rpcs")]
#[tokio::test(flavor = "multi_thread")]
async fn rpc_getblockcount_empty_state() {
let _init_guard = zebra_test::init();
// Get a mempool handle
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
// Create an empty state
let (state, read_state, latest_chain_tip, _chain_tip_change) =
zebra_state::init_test_services(Mainnet);
let (
chain_verifier,
_transaction_verifier,
_parameter_download_task_handle,
_max_checkpoint_height,
) = zebra_consensus::chain::init(
zebra_consensus::Config::default(),
Mainnet,
state.clone(),
true,
)
.await;
// Init RPC
let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new(
Mainnet,
Buffer::new(mempool.clone(), 1),
read_state,
latest_chain_tip.clone(),
chain_verifier,
);
// Get the tip height using RPC method `get_block_count
let get_block_count = get_block_template_rpc.get_block_count();
// state an empty so we should get an error
assert!(get_block_count.is_err());
// Check the error we got is the correct one
assert_eq!(get_block_count.err().unwrap().message, "No blocks in state");
mempool.expect_no_requests().await;
}
#[cfg(feature = "getblocktemplate-rpcs")]
#[tokio::test(flavor = "multi_thread")]
async fn rpc_getblockhash() {
let _init_guard = zebra_test::init();
// Create a continuous chain of mainnet blocks from genesis
let blocks: Vec<Arc<Block>> = zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS
.iter()
.map(|(_height, block_bytes)| block_bytes.zcash_deserialize_into().unwrap())
.collect();
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
// Create a populated state service
let (state, read_state, latest_chain_tip, _chain_tip_change) =
zebra_state::populated_state(blocks.clone(), Mainnet).await;
let (
chain_verifier,
_transaction_verifier,
_parameter_download_task_handle,
_max_checkpoint_height,
) = zebra_consensus::chain::init(
zebra_consensus::Config::default(),
Mainnet,
state.clone(),
true,
)
.await;
// Init RPC
let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new(
Mainnet,
Buffer::new(mempool.clone(), 1),
read_state,
latest_chain_tip.clone(),
tower::ServiceBuilder::new().service(chain_verifier),
);
// Query the hashes using positive indexes
for (i, block) in blocks.iter().enumerate() {
let get_block_hash = get_block_template_rpc
.get_block_hash(i.try_into().expect("usize always fits in i32"))
.await
.expect("We should have a GetBlockHash struct");
assert_eq!(get_block_hash, GetBlockHash(block.clone().hash()));
}
// Query the hashes using negative indexes
for i in (-10..=-1).rev() {
let get_block_hash = get_block_template_rpc
.get_block_hash(i)
.await
.expect("We should have a GetBlockHash struct");
assert_eq!(
get_block_hash,
GetBlockHash(blocks[(10 + (i + 1)) as usize].hash())
);
}
mempool.expect_no_requests().await;
}
#[cfg(feature = "getblocktemplate-rpcs")]
#[tokio::test(flavor = "multi_thread")]
async fn rpc_getblocktemplate() {
use std::panic;
let _init_guard = zebra_test::init();
// Create a continuous chain of mainnet blocks from genesis
let blocks: Vec<Arc<Block>> = zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS
.iter()
.map(|(_height, block_bytes)| block_bytes.zcash_deserialize_into().unwrap())
.collect();
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
// Create a populated state service
let (state, read_state, latest_chain_tip, _chain_tip_change) =
zebra_state::populated_state(blocks.clone(), Mainnet).await;
let (
chain_verifier,
_transaction_verifier,
_parameter_download_task_handle,
_max_checkpoint_height,
) = zebra_consensus::chain::init(
zebra_consensus::Config::default(),
Mainnet,
state.clone(),
true,
)
.await;
// Init RPC
let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new(
Mainnet,
Buffer::new(mempool.clone(), 1),
read_state,
latest_chain_tip.clone(),
tower::ServiceBuilder::new().service(chain_verifier),
);
let get_block_template = tokio::spawn(get_block_template_rpc.get_block_template());
mempool
.expect_request(mempool::Request::FullTransactions)
.await
.respond(mempool::Response::FullTransactions(vec![]));
let get_block_template = get_block_template
.await
.unwrap_or_else(|error| match error.try_into_panic() {
Ok(panic_object) => panic::resume_unwind(panic_object),
Err(cancelled_error) => {
panic!("getblocktemplate task was unexpectedly cancelled: {cancelled_error:?}")
}
})
.expect("unexpected error in getblocktemplate RPC call");
assert!(get_block_template.capabilities.is_empty());
assert_eq!(get_block_template.version, 0);
assert!(get_block_template.transactions.is_empty());
assert!(get_block_template.target.is_empty());
assert_eq!(get_block_template.min_time, 0);
assert!(get_block_template.mutable.is_empty());
assert!(get_block_template.nonce_range.is_empty());
assert_eq!(get_block_template.sigop_limit, 0);
assert_eq!(get_block_template.size_limit, 0);
assert_eq!(get_block_template.cur_time, 0);
assert!(get_block_template.bits.is_empty());
assert_eq!(get_block_template.height, 0);
mempool.expect_no_requests().await;
}
#[cfg(feature = "getblocktemplate-rpcs")]
#[tokio::test(flavor = "multi_thread")]
async fn rpc_submitblock_errors() {
let _init_guard = zebra_test::init();
// Create a continuous chain of mainnet blocks from genesis
let blocks: Vec<Arc<Block>> = zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS
.iter()
.map(|(_height, block_bytes)| block_bytes.zcash_deserialize_into().unwrap())
.collect();
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
// Create a populated state service
let (state, read_state, latest_chain_tip, _chain_tip_change) =
zebra_state::populated_state(blocks, Mainnet).await;
// Init RPCs
let _rpc = RpcImpl::new(
"RPC test",
Mainnet,
false,
Buffer::new(mempool.clone(), 1),
Buffer::new(read_state.clone(), 1),
latest_chain_tip.clone(),
);
let (
chain_verifier,
_transaction_verifier,
_parameter_download_task_handle,
_max_checkpoint_height,
) = zebra_consensus::chain::init(
zebra_consensus::Config::default(),
Mainnet,
state.clone(),
true,
)
.await;
// Init RPC
let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new(
Mainnet,
Buffer::new(mempool.clone(), 1),
read_state,
latest_chain_tip.clone(),
tower::ServiceBuilder::new().service(chain_verifier),
);
// Try to submit pre-populated blocks and assert that it responds with duplicate.
for (_height, &block_bytes) in zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS.iter() {
let submit_block_response = get_block_template_rpc
.submit_block(
get_block_template_rpcs::types::hex_data::HexData(block_bytes.into()),
None,
)
.await;
assert_eq!(
submit_block_response,
Ok(get_block_template_rpcs::types::submit_block::ErrorResponse::Duplicate.into())
);
}
let submit_block_response = get_block_template_rpc
.submit_block(
get_block_template_rpcs::types::hex_data::HexData(
zebra_test::vectors::BAD_BLOCK_MAINNET_202_BYTES.to_vec(),
),
None,
)
.await;
assert_eq!(
submit_block_response,
Ok(get_block_template_rpcs::types::submit_block::ErrorResponse::Rejected.into())
);
mempool.expect_no_requests().await;
// See zebrad::tests::acceptance::submit_block for success case.
}