750 lines
28 KiB
Rust
750 lines
28 KiB
Rust
//! Randomised property tests for RPC methods.
|
|
|
|
use std::collections::HashSet;
|
|
|
|
use futures::FutureExt;
|
|
use hex::ToHex;
|
|
use jsonrpc_core::{Error, ErrorCode};
|
|
use proptest::prelude::*;
|
|
use thiserror::Error;
|
|
use tower::buffer::Buffer;
|
|
|
|
use zebra_chain::{
|
|
block::{Block, Height},
|
|
chain_tip::{mock::MockChainTip, NoChainTip},
|
|
parameters::{
|
|
Network::{self, *},
|
|
NetworkUpgrade,
|
|
},
|
|
serialization::{ZcashDeserialize, ZcashSerialize},
|
|
transaction::{self, Transaction, UnminedTx, UnminedTxId},
|
|
};
|
|
use zebra_node_services::mempool;
|
|
use zebra_state::BoxError;
|
|
|
|
use zebra_test::mock_service::MockService;
|
|
|
|
use super::super::{NetworkUpgradeStatus, Rpc, RpcImpl, SentTransactionHash};
|
|
|
|
proptest! {
|
|
/// Test that when sending a raw transaction, it is received by the mempool service.
|
|
#[test]
|
|
fn mempool_receives_raw_transaction(transaction in any::<Transaction>()) {
|
|
let runtime = zebra_test::init_async();
|
|
|
|
runtime.block_on(async move {
|
|
let mut mempool = MockService::build().for_prop_tests();
|
|
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
|
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
|
"RPC test",
|
|
Buffer::new(mempool.clone(), 1),
|
|
Buffer::new(state.clone(), 1),
|
|
NoChainTip,
|
|
Mainnet,
|
|
);
|
|
let hash = SentTransactionHash(transaction.hash());
|
|
|
|
let transaction_bytes = transaction
|
|
.zcash_serialize_to_vec()
|
|
.expect("Transaction serializes successfully");
|
|
let transaction_hex = hex::encode(&transaction_bytes);
|
|
|
|
let send_task = tokio::spawn(rpc.send_raw_transaction(transaction_hex));
|
|
|
|
let unmined_transaction = UnminedTx::from(transaction);
|
|
let expected_request = mempool::Request::Queue(vec![unmined_transaction.into()]);
|
|
let response = mempool::Response::Queued(vec![Ok(())]);
|
|
|
|
mempool
|
|
.expect_request(expected_request)
|
|
.await?
|
|
.respond(response);
|
|
|
|
state.expect_no_requests().await?;
|
|
|
|
let result = send_task
|
|
.await
|
|
.expect("Sending raw transactions should not panic");
|
|
|
|
prop_assert_eq!(result, Ok(hash));
|
|
|
|
// The queue task should continue without errors or panics
|
|
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
|
|
prop_assert!(matches!(rpc_tx_queue_task_result, None));
|
|
|
|
Ok::<_, TestCaseError>(())
|
|
})?;
|
|
}
|
|
|
|
/// Test that mempool errors are forwarded to the caller.
|
|
///
|
|
/// Mempool service errors should become server errors.
|
|
#[test]
|
|
fn mempool_errors_are_forwarded(transaction in any::<Transaction>()) {
|
|
let runtime = zebra_test::init_async();
|
|
|
|
runtime.block_on(async move {
|
|
let mut mempool = MockService::build().for_prop_tests();
|
|
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
|
|
|
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
|
"RPC test",
|
|
Buffer::new(mempool.clone(), 1),
|
|
Buffer::new(state.clone(), 1),
|
|
NoChainTip,
|
|
Mainnet,
|
|
);
|
|
|
|
let transaction_bytes = transaction
|
|
.zcash_serialize_to_vec()
|
|
.expect("Transaction serializes successfully");
|
|
let transaction_hex = hex::encode(&transaction_bytes);
|
|
|
|
let send_task = tokio::spawn(rpc.send_raw_transaction(transaction_hex));
|
|
|
|
let unmined_transaction = UnminedTx::from(transaction);
|
|
let expected_request = mempool::Request::Queue(vec![unmined_transaction.into()]);
|
|
|
|
mempool
|
|
.expect_request(expected_request)
|
|
.await?
|
|
.respond(Err(DummyError));
|
|
|
|
state.expect_no_requests().await?;
|
|
|
|
let result = send_task
|
|
.await
|
|
.expect("Sending raw transactions should not panic");
|
|
|
|
prop_assert!(
|
|
matches!(
|
|
result,
|
|
Err(Error {
|
|
code: ErrorCode::ServerError(_),
|
|
..
|
|
})
|
|
),
|
|
"Result is not a server error: {result:?}"
|
|
);
|
|
|
|
// The queue task should continue without errors or panics
|
|
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
|
|
prop_assert!(matches!(rpc_tx_queue_task_result, None));
|
|
|
|
Ok::<_, TestCaseError>(())
|
|
})?;
|
|
}
|
|
|
|
/// Test that when the mempool rejects a transaction the caller receives an error.
|
|
#[test]
|
|
fn rejected_transactions_are_reported(transaction in any::<Transaction>()) {
|
|
let runtime = zebra_test::init_async();
|
|
|
|
runtime.block_on(async move {
|
|
let mut mempool = MockService::build().for_prop_tests();
|
|
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
|
|
|
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
|
"RPC test",
|
|
Buffer::new(mempool.clone(), 1),
|
|
Buffer::new(state.clone(), 1),
|
|
NoChainTip,
|
|
Mainnet,
|
|
);
|
|
|
|
let transaction_bytes = transaction
|
|
.zcash_serialize_to_vec()
|
|
.expect("Transaction serializes successfully");
|
|
let transaction_hex = hex::encode(&transaction_bytes);
|
|
|
|
let send_task = tokio::spawn(rpc.send_raw_transaction(transaction_hex));
|
|
|
|
let unmined_transaction = UnminedTx::from(transaction);
|
|
let expected_request = mempool::Request::Queue(vec![unmined_transaction.into()]);
|
|
let response = mempool::Response::Queued(vec![Err(DummyError.into())]);
|
|
|
|
mempool
|
|
.expect_request(expected_request)
|
|
.await?
|
|
.respond(response);
|
|
|
|
state.expect_no_requests().await?;
|
|
|
|
let result = send_task
|
|
.await
|
|
.expect("Sending raw transactions should not panic");
|
|
|
|
prop_assert!(
|
|
matches!(
|
|
result,
|
|
Err(Error {
|
|
code: ErrorCode::ServerError(_),
|
|
..
|
|
})
|
|
),
|
|
"Result is not a server error: {result:?}"
|
|
);
|
|
|
|
// The queue task should continue without errors or panics
|
|
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
|
|
prop_assert!(matches!(rpc_tx_queue_task_result, None));
|
|
|
|
Ok::<_, TestCaseError>(())
|
|
})?;
|
|
}
|
|
|
|
/// Test that the method rejects non-hexadecimal characters.
|
|
///
|
|
/// Try to call `send_raw_transaction` using a string parameter that has at least one
|
|
/// non-hexadecimal character, and check that it fails with an expected error.
|
|
#[test]
|
|
fn non_hexadecimal_string_results_in_an_error(non_hex_string in ".*[^0-9A-Fa-f].*") {
|
|
let runtime = zebra_test::init_async();
|
|
let _guard = runtime.enter();
|
|
|
|
// CORRECTNESS: Nothing in this test depends on real time, so we can speed it up.
|
|
tokio::time::pause();
|
|
|
|
runtime.block_on(async move {
|
|
let mut mempool = MockService::build().for_prop_tests();
|
|
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
|
|
|
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
|
"RPC test",
|
|
Buffer::new(mempool.clone(), 1),
|
|
Buffer::new(state.clone(), 1),
|
|
NoChainTip,
|
|
Mainnet,
|
|
);
|
|
|
|
let send_task = tokio::spawn(rpc.send_raw_transaction(non_hex_string));
|
|
|
|
mempool.expect_no_requests().await?;
|
|
state.expect_no_requests().await?;
|
|
|
|
let result = send_task
|
|
.await
|
|
.expect("Sending raw transactions should not panic");
|
|
|
|
prop_assert!(
|
|
matches!(
|
|
result,
|
|
Err(Error {
|
|
code: ErrorCode::InvalidParams,
|
|
..
|
|
})
|
|
),
|
|
"Result is not an invalid parameters error: {result:?}"
|
|
);
|
|
|
|
// The queue task should continue without errors or panics
|
|
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
|
|
prop_assert!(matches!(rpc_tx_queue_task_result, None));
|
|
|
|
Ok::<_, TestCaseError>(())
|
|
})?;
|
|
}
|
|
|
|
/// Test that the method rejects an input that's not a transaction.
|
|
///
|
|
/// Try to call `send_raw_transaction` using random bytes that fail to deserialize as a
|
|
/// transaction, and check that it fails with an expected error.
|
|
#[test]
|
|
fn invalid_transaction_results_in_an_error(random_bytes in any::<Vec<u8>>()) {
|
|
let runtime = zebra_test::init_async();
|
|
let _guard = runtime.enter();
|
|
|
|
// CORRECTNESS: Nothing in this test depends on real time, so we can speed it up.
|
|
tokio::time::pause();
|
|
|
|
prop_assume!(Transaction::zcash_deserialize(&*random_bytes).is_err());
|
|
|
|
runtime.block_on(async move {
|
|
let mut mempool = MockService::build().for_prop_tests();
|
|
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
|
|
|
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
|
"RPC test",
|
|
Buffer::new(mempool.clone(), 1),
|
|
Buffer::new(state.clone(), 1),
|
|
NoChainTip,
|
|
Mainnet,
|
|
);
|
|
|
|
let send_task = tokio::spawn(rpc.send_raw_transaction(hex::encode(random_bytes)));
|
|
|
|
mempool.expect_no_requests().await?;
|
|
state.expect_no_requests().await?;
|
|
|
|
let result = send_task
|
|
.await
|
|
.expect("Sending raw transactions should not panic");
|
|
|
|
prop_assert!(
|
|
matches!(
|
|
result,
|
|
Err(Error {
|
|
code: ErrorCode::InvalidParams,
|
|
..
|
|
})
|
|
),
|
|
"Result is not an invalid parameters error: {result:?}"
|
|
);
|
|
|
|
// The queue task should continue without errors or panics
|
|
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
|
|
prop_assert!(matches!(rpc_tx_queue_task_result, None));
|
|
|
|
Ok::<_, TestCaseError>(())
|
|
})?;
|
|
}
|
|
|
|
/// Test that the `getrawmempool` method forwards the transactions in the mempool.
|
|
///
|
|
/// Make the mock mempool service return a list of transaction IDs, and check that the RPC call
|
|
/// returns those IDs as hexadecimal strings.
|
|
#[test]
|
|
fn mempool_transactions_are_sent_to_caller(transaction_ids in any::<HashSet<UnminedTxId>>())
|
|
{
|
|
let runtime = zebra_test::init_async();
|
|
let _guard = runtime.enter();
|
|
|
|
// CORRECTNESS: Nothing in this test depends on real time, so we can speed it up.
|
|
tokio::time::pause();
|
|
|
|
runtime.block_on(async move {
|
|
let mut mempool = MockService::build().for_prop_tests();
|
|
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
|
|
|
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
|
"RPC test",
|
|
Buffer::new(mempool.clone(), 1),
|
|
Buffer::new(state.clone(), 1),
|
|
NoChainTip,
|
|
Mainnet,
|
|
);
|
|
|
|
let call_task = tokio::spawn(rpc.get_raw_mempool());
|
|
let expected_response: Vec<String> = transaction_ids
|
|
.iter()
|
|
.map(|id| id.mined_id().encode_hex())
|
|
.collect();
|
|
|
|
mempool
|
|
.expect_request(mempool::Request::TransactionIds)
|
|
.await?
|
|
.respond(mempool::Response::TransactionIds(transaction_ids));
|
|
|
|
mempool.expect_no_requests().await?;
|
|
state.expect_no_requests().await?;
|
|
|
|
let result = call_task
|
|
.await
|
|
.expect("Sending raw transactions should not panic");
|
|
|
|
prop_assert_eq!(result, Ok(expected_response));
|
|
|
|
// The queue task should continue without errors or panics
|
|
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
|
|
prop_assert!(matches!(rpc_tx_queue_task_result, None));
|
|
|
|
Ok::<_, TestCaseError>(())
|
|
})?;
|
|
}
|
|
|
|
/// Test that the method rejects non-hexadecimal characters.
|
|
///
|
|
/// Try to call `get_raw_transaction` using a string parameter that has at least one
|
|
/// non-hexadecimal character, and check that it fails with an expected error.
|
|
#[test]
|
|
fn get_raw_transaction_non_hexadecimal_string_results_in_an_error(non_hex_string in ".*[^0-9A-Fa-f].*") {
|
|
let runtime = zebra_test::init_async();
|
|
let _guard = runtime.enter();
|
|
|
|
// CORRECTNESS: Nothing in this test depends on real time, so we can speed it up.
|
|
tokio::time::pause();
|
|
|
|
runtime.block_on(async move {
|
|
let mut mempool = MockService::build().for_prop_tests();
|
|
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
|
|
|
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
|
"RPC test",
|
|
Buffer::new(mempool.clone(), 1),
|
|
Buffer::new(state.clone(), 1),
|
|
NoChainTip,
|
|
Mainnet,
|
|
);
|
|
|
|
let send_task = tokio::spawn(rpc.get_raw_transaction(non_hex_string, 0));
|
|
|
|
mempool.expect_no_requests().await?;
|
|
state.expect_no_requests().await?;
|
|
|
|
let result = send_task
|
|
.await
|
|
.expect("Sending raw transactions should not panic");
|
|
|
|
prop_assert!(
|
|
matches!(
|
|
result,
|
|
Err(Error {
|
|
code: ErrorCode::InvalidParams,
|
|
..
|
|
})
|
|
),
|
|
"Result is not an invalid parameters error: {result:?}"
|
|
);
|
|
|
|
// The queue task should continue without errors or panics
|
|
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
|
|
prop_assert!(matches!(rpc_tx_queue_task_result, None));
|
|
|
|
Ok::<_, TestCaseError>(())
|
|
})?;
|
|
}
|
|
|
|
/// Test that the method rejects an input that's not a transaction.
|
|
///
|
|
/// Try to call `get_raw_transaction` using random bytes that fail to deserialize as a
|
|
/// transaction, and check that it fails with an expected error.
|
|
#[test]
|
|
fn get_raw_transaction_invalid_transaction_results_in_an_error(random_bytes in any::<Vec<u8>>()) {
|
|
let runtime = zebra_test::init_async();
|
|
let _guard = runtime.enter();
|
|
|
|
// CORRECTNESS: Nothing in this test depends on real time, so we can speed it up.
|
|
tokio::time::pause();
|
|
|
|
prop_assume!(transaction::Hash::zcash_deserialize(&*random_bytes).is_err());
|
|
|
|
runtime.block_on(async move {
|
|
let mut mempool = MockService::build().for_prop_tests();
|
|
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
|
|
|
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
|
"RPC test",
|
|
Buffer::new(mempool.clone(), 1),
|
|
Buffer::new(state.clone(), 1),
|
|
NoChainTip,
|
|
Mainnet,
|
|
);
|
|
|
|
let send_task = tokio::spawn(rpc.get_raw_transaction(hex::encode(random_bytes), 0));
|
|
|
|
mempool.expect_no_requests().await?;
|
|
state.expect_no_requests().await?;
|
|
|
|
let result = send_task
|
|
.await
|
|
.expect("Sending raw transactions should not panic");
|
|
|
|
prop_assert!(
|
|
matches!(
|
|
result,
|
|
Err(Error {
|
|
code: ErrorCode::InvalidParams,
|
|
..
|
|
})
|
|
),
|
|
"Result is not an invalid parameters error: {result:?}"
|
|
);
|
|
|
|
// The queue task should continue without errors or panics
|
|
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
|
|
prop_assert!(matches!(rpc_tx_queue_task_result, None));
|
|
|
|
Ok::<_, TestCaseError>(())
|
|
})?;
|
|
}
|
|
|
|
/// Test the `get_blockchain_info` response when Zebra's state is empty.
|
|
#[test]
|
|
fn get_blockchain_info_response_without_a_chain_tip(network in any::<Network>()) {
|
|
let runtime = zebra_test::init_async();
|
|
let _guard = runtime.enter();
|
|
let mut mempool = MockService::build().for_prop_tests();
|
|
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
|
|
|
// look for an error with a `NoChainTip`
|
|
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
|
"RPC test",
|
|
Buffer::new(mempool.clone(), 1),
|
|
Buffer::new(state.clone(), 1),
|
|
NoChainTip,
|
|
network,
|
|
);
|
|
|
|
let response = rpc.get_blockchain_info();
|
|
prop_assert_eq!(&response.err().unwrap().message, "No Chain tip available yet");
|
|
|
|
// The queue task should continue without errors or panics
|
|
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
|
|
prop_assert!(matches!(rpc_tx_queue_task_result, None));
|
|
|
|
runtime.block_on(async move {
|
|
mempool.expect_no_requests().await?;
|
|
state.expect_no_requests().await?;
|
|
Ok::<_, TestCaseError>(())
|
|
})?;
|
|
}
|
|
|
|
/// Test the `get_blockchain_info` response using an arbitrary block as the `ChainTip`.
|
|
#[test]
|
|
fn get_blockchain_info_response_with_an_arbitrary_chain_tip(
|
|
network in any::<Network>(),
|
|
block in any::<Block>(),
|
|
) {
|
|
let runtime = zebra_test::init_async();
|
|
let _guard = runtime.enter();
|
|
let mut mempool = MockService::build().for_prop_tests();
|
|
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
|
|
|
// get block data
|
|
let block_height = block.coinbase_height().unwrap();
|
|
let block_hash = block.hash();
|
|
let block_time = block.header.time;
|
|
|
|
// create a mocked `ChainTip`
|
|
let (chain_tip, mock_chain_tip_sender) = MockChainTip::new();
|
|
mock_chain_tip_sender.send_best_tip_height(block_height);
|
|
mock_chain_tip_sender.send_best_tip_hash(block_hash);
|
|
mock_chain_tip_sender.send_best_tip_block_time(block_time);
|
|
|
|
// Start RPC with the mocked `ChainTip`
|
|
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
|
"RPC test",
|
|
Buffer::new(mempool.clone(), 1),
|
|
Buffer::new(state.clone(), 1),
|
|
chain_tip,
|
|
network,
|
|
);
|
|
let response = rpc.get_blockchain_info();
|
|
|
|
// Check response
|
|
match response {
|
|
Ok(info) => {
|
|
prop_assert_eq!(info.chain, network.bip70_network_name());
|
|
prop_assert_eq!(info.blocks, block_height.0);
|
|
prop_assert_eq!(info.best_block_hash.0, block_hash);
|
|
prop_assert!(info.estimated_height < Height::MAX.0);
|
|
|
|
prop_assert_eq!(info.consensus.chain_tip.0, NetworkUpgrade::current(network, block_height).branch_id().unwrap());
|
|
prop_assert_eq!(info.consensus.next_block.0, NetworkUpgrade::current(network, (block_height + 1).unwrap()).branch_id().unwrap());
|
|
|
|
for u in info.upgrades {
|
|
let mut status = NetworkUpgradeStatus::Active;
|
|
if block_height < u.1.activation_height {
|
|
status = NetworkUpgradeStatus::Pending;
|
|
}
|
|
prop_assert_eq!(u.1.status, status);
|
|
}
|
|
},
|
|
Err(_) => {
|
|
unreachable!("Test should never error with the data we are feeding it")
|
|
},
|
|
};
|
|
|
|
// The queue task should continue without errors or panics
|
|
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
|
|
prop_assert!(matches!(rpc_tx_queue_task_result, None));
|
|
|
|
// check no requests were made during this test
|
|
runtime.block_on(async move {
|
|
mempool.expect_no_requests().await?;
|
|
state.expect_no_requests().await?;
|
|
|
|
Ok::<_, TestCaseError>(())
|
|
})?;
|
|
}
|
|
|
|
/// Test the queue functionality using `send_raw_transaction`
|
|
#[test]
|
|
fn rpc_queue_main_loop(tx in any::<Transaction>())
|
|
{
|
|
let runtime = zebra_test::init_async();
|
|
let _guard = runtime.enter();
|
|
|
|
let transaction_hash = tx.hash();
|
|
|
|
runtime.block_on(async move {
|
|
tokio::time::pause();
|
|
|
|
let mut mempool = MockService::build().for_prop_tests();
|
|
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
|
|
|
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
|
"RPC test",
|
|
Buffer::new(mempool.clone(), 1),
|
|
Buffer::new(state.clone(), 1),
|
|
NoChainTip,
|
|
Mainnet,
|
|
);
|
|
|
|
// send a transaction
|
|
let tx_bytes = tx
|
|
.zcash_serialize_to_vec()
|
|
.expect("Transaction serializes successfully");
|
|
let tx_hex = hex::encode(&tx_bytes);
|
|
let send_task = tokio::spawn(rpc.send_raw_transaction(tx_hex));
|
|
|
|
let tx_unmined = UnminedTx::from(tx);
|
|
let expected_request = mempool::Request::Queue(vec![tx_unmined.clone().into()]);
|
|
|
|
// fail the mempool insertion
|
|
mempool
|
|
.expect_request(expected_request)
|
|
.await
|
|
.unwrap()
|
|
.respond(Err(DummyError));
|
|
|
|
let _ = send_task
|
|
.await
|
|
.expect("Sending raw transactions should not panic");
|
|
|
|
// advance enough time to have a new runner iteration
|
|
let spacing = chrono::Duration::seconds(150);
|
|
tokio::time::advance(spacing.to_std().unwrap()).await;
|
|
|
|
// the runner will made a new call to TransactionsById
|
|
let mut transactions_hash_set = HashSet::new();
|
|
transactions_hash_set.insert(tx_unmined.id);
|
|
let expected_request = mempool::Request::TransactionsById(transactions_hash_set);
|
|
let response = mempool::Response::Transactions(vec![]);
|
|
|
|
mempool
|
|
.expect_request(expected_request)
|
|
.await?
|
|
.respond(response);
|
|
|
|
// the runner will also query the state again for the transaction
|
|
let expected_request = zebra_state::ReadRequest::Transaction(transaction_hash);
|
|
let response = zebra_state::ReadResponse::Transaction(None);
|
|
|
|
state
|
|
.expect_request(expected_request)
|
|
.await?
|
|
.respond(response);
|
|
|
|
// now a retry will be sent to the mempool
|
|
let expected_request = mempool::Request::Queue(vec![mempool::Gossip::Tx(tx_unmined.clone())]);
|
|
let response = mempool::Response::Queued(vec![Ok(())]);
|
|
|
|
mempool
|
|
.expect_request(expected_request)
|
|
.await?
|
|
.respond(response);
|
|
|
|
// no more requests are done
|
|
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();
|
|
prop_assert!(matches!(rpc_tx_queue_task_result, None));
|
|
|
|
Ok::<_, TestCaseError>(())
|
|
})?;
|
|
}
|
|
|
|
/// Test we receive all transactions that are sent in a channel
|
|
#[test]
|
|
fn rpc_queue_receives_all_transactions_from_channel(txs in any::<[Transaction; 2]>())
|
|
{
|
|
let runtime = zebra_test::init_async();
|
|
let _guard = runtime.enter();
|
|
|
|
runtime.block_on(async move {
|
|
tokio::time::pause();
|
|
|
|
let mut mempool = MockService::build().for_prop_tests();
|
|
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
|
|
|
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
|
"RPC test",
|
|
Buffer::new(mempool.clone(), 1),
|
|
Buffer::new(state.clone(), 1),
|
|
NoChainTip,
|
|
Mainnet,
|
|
);
|
|
|
|
let mut transactions_hash_set = HashSet::new();
|
|
for tx in txs.clone() {
|
|
// send a transaction
|
|
let tx_bytes = tx
|
|
.zcash_serialize_to_vec()
|
|
.expect("Transaction serializes successfully");
|
|
let tx_hex = hex::encode(&tx_bytes);
|
|
let send_task = tokio::spawn(rpc.send_raw_transaction(tx_hex));
|
|
|
|
let tx_unmined = UnminedTx::from(tx.clone());
|
|
let expected_request = mempool::Request::Queue(vec![tx_unmined.clone().into()]);
|
|
|
|
// insert to hs we will use later
|
|
transactions_hash_set.insert(tx_unmined.id);
|
|
|
|
// fail the mempool insertion
|
|
mempool
|
|
.clone()
|
|
.expect_request(expected_request)
|
|
.await
|
|
.unwrap()
|
|
.respond(Err(DummyError));
|
|
|
|
let _ = send_task
|
|
.await
|
|
.expect("Sending raw transactions should not panic");
|
|
}
|
|
|
|
// advance enough time to have a new runner iteration
|
|
let spacing = chrono::Duration::seconds(150);
|
|
tokio::time::advance(spacing.to_std().unwrap()).await;
|
|
|
|
// the runner will made a new call to TransactionsById querying with both transactions
|
|
let expected_request = mempool::Request::TransactionsById(transactions_hash_set);
|
|
let response = mempool::Response::Transactions(vec![]);
|
|
|
|
mempool
|
|
.expect_request(expected_request)
|
|
.await?
|
|
.respond(response);
|
|
|
|
// the runner will also query the state again for each transaction
|
|
for _tx in txs.clone() {
|
|
let response = zebra_state::ReadResponse::Transaction(None);
|
|
|
|
// we use `expect_request_that` because we can't guarantee the state request order
|
|
state
|
|
.expect_request_that(|request| matches!(request, zebra_state::ReadRequest::Transaction(_)))
|
|
.await?
|
|
.respond(response);
|
|
}
|
|
|
|
// each transaction will be retried
|
|
for tx in txs.clone() {
|
|
let expected_request = mempool::Request::Queue(vec![mempool::Gossip::Tx(UnminedTx::from(tx))]);
|
|
let response = mempool::Response::Queued(vec![Ok(())]);
|
|
|
|
mempool
|
|
.expect_request(expected_request)
|
|
.await?
|
|
.respond(response);
|
|
}
|
|
|
|
// no more requests are done
|
|
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();
|
|
prop_assert!(matches!(rpc_tx_queue_task_result, None));
|
|
|
|
Ok::<_, TestCaseError>(())
|
|
})?;
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Error)]
|
|
#[error("a dummy error type")]
|
|
pub struct DummyError;
|