feat(rpc): implement `getrawmempool` RPC method (#3851)
* feat(rpc): implement `getrawmempool` rpc method * tests(rpc): start implementing a test * Return a `Vec<String>` from `get_raw_mempool` Ensure the returned type is serialized as an array of hexadecimal strings. * Make `getrawmempool` test a property test Test it with random sets of transaction IDs. * Fix code formatting inside `proptest!` Run `rustfmt` inside the `proptest!` block. * tests(rpc): fix lightwalletd test * fix(rpc): remove non used return type * tests(rpc): remove assert Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com>
This commit is contained in:
parent
419770409a
commit
53e2d2e298
|
@ -7,7 +7,7 @@
|
||||||
//! So this implementation follows the `lightwalletd` client implementation.
|
//! So this implementation follows the `lightwalletd` client implementation.
|
||||||
|
|
||||||
use futures::{FutureExt, TryFutureExt};
|
use futures::{FutureExt, TryFutureExt};
|
||||||
use hex::FromHex;
|
use hex::{FromHex, ToHex};
|
||||||
use jsonrpc_core::{self, BoxFuture, Error, ErrorCode, Result};
|
use jsonrpc_core::{self, BoxFuture, Error, ErrorCode, Result};
|
||||||
use jsonrpc_derive::rpc;
|
use jsonrpc_derive::rpc;
|
||||||
use tower::{buffer::Buffer, Service, ServiceExt};
|
use tower::{buffer::Buffer, Service, ServiceExt};
|
||||||
|
@ -102,6 +102,12 @@ pub trait Rpc {
|
||||||
///
|
///
|
||||||
#[rpc(name = "getbestblockhash")]
|
#[rpc(name = "getbestblockhash")]
|
||||||
fn get_best_block_hash(&self) -> BoxFuture<Result<GetBestBlockHash>>;
|
fn get_best_block_hash(&self) -> BoxFuture<Result<GetBestBlockHash>>;
|
||||||
|
|
||||||
|
/// Returns all transaction ids in the memory pool, as a JSON array.
|
||||||
|
///
|
||||||
|
/// zcashd reference: <https://zcash.github.io/rpc/getrawmempool.html>
|
||||||
|
#[rpc(name = "getrawmempool")]
|
||||||
|
fn get_raw_mempool(&self) -> BoxFuture<Result<Vec<String>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RPC method implementations.
|
/// RPC method implementations.
|
||||||
|
@ -286,6 +292,35 @@ where
|
||||||
}
|
}
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_raw_mempool(&self) -> BoxFuture<Result<Vec<String>>> {
|
||||||
|
let mut mempool = self.mempool.clone();
|
||||||
|
|
||||||
|
async move {
|
||||||
|
let request = mempool::Request::TransactionIds;
|
||||||
|
|
||||||
|
let response = mempool
|
||||||
|
.ready()
|
||||||
|
.and_then(|service| service.call(request))
|
||||||
|
.await
|
||||||
|
.map_err(|error| Error {
|
||||||
|
code: ErrorCode::ServerError(0),
|
||||||
|
message: error.to_string(),
|
||||||
|
data: None,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
match response {
|
||||||
|
mempool::Response::TransactionIds(unmined_transaction_ids) => {
|
||||||
|
Ok(unmined_transaction_ids
|
||||||
|
.iter()
|
||||||
|
.map(|id| id.mined_id().encode_hex())
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
_ => unreachable!("unmatched response to a transactionids request"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize)]
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use hex::ToHex;
|
||||||
use jsonrpc_core::{Error, ErrorCode};
|
use jsonrpc_core::{Error, ErrorCode};
|
||||||
use proptest::prelude::*;
|
use proptest::prelude::*;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
@ -5,7 +8,7 @@ use tower::buffer::Buffer;
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
serialization::{ZcashDeserialize, ZcashSerialize},
|
serialization::{ZcashDeserialize, ZcashSerialize},
|
||||||
transaction::{Transaction, UnminedTx},
|
transaction::{Transaction, UnminedTx, UnminedTxId},
|
||||||
};
|
};
|
||||||
use zebra_node_services::mempool;
|
use zebra_node_services::mempool;
|
||||||
use zebra_state::BoxError;
|
use zebra_state::BoxError;
|
||||||
|
@ -23,7 +26,11 @@ proptest! {
|
||||||
runtime.block_on(async move {
|
runtime.block_on(async move {
|
||||||
let mut mempool = MockService::build().for_prop_tests();
|
let mut mempool = MockService::build().for_prop_tests();
|
||||||
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
||||||
let rpc = RpcImpl::new("RPC test".to_owned(), Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1));
|
let rpc = RpcImpl::new(
|
||||||
|
"RPC test".to_owned(),
|
||||||
|
Buffer::new(mempool.clone(), 1),
|
||||||
|
Buffer::new(state.clone(), 1),
|
||||||
|
);
|
||||||
let hash = SentTransactionHash(transaction.hash());
|
let hash = SentTransactionHash(transaction.hash());
|
||||||
|
|
||||||
let transaction_bytes = transaction
|
let transaction_bytes = transaction
|
||||||
|
@ -65,7 +72,11 @@ proptest! {
|
||||||
let mut mempool = MockService::build().for_prop_tests();
|
let mut mempool = MockService::build().for_prop_tests();
|
||||||
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
||||||
|
|
||||||
let rpc = RpcImpl::new("RPC test".to_owned(), Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1));
|
let rpc = RpcImpl::new(
|
||||||
|
"RPC test".to_owned(),
|
||||||
|
Buffer::new(mempool.clone(), 1),
|
||||||
|
Buffer::new(state.clone(), 1),
|
||||||
|
);
|
||||||
|
|
||||||
let transaction_bytes = transaction
|
let transaction_bytes = transaction
|
||||||
.zcash_serialize_to_vec()
|
.zcash_serialize_to_vec()
|
||||||
|
@ -84,7 +95,6 @@ proptest! {
|
||||||
|
|
||||||
state.expect_no_requests().await?;
|
state.expect_no_requests().await?;
|
||||||
|
|
||||||
|
|
||||||
let result = send_task
|
let result = send_task
|
||||||
.await
|
.await
|
||||||
.expect("Sending raw transactions should not panic");
|
.expect("Sending raw transactions should not panic");
|
||||||
|
@ -113,7 +123,11 @@ proptest! {
|
||||||
let mut mempool = MockService::build().for_prop_tests();
|
let mut mempool = MockService::build().for_prop_tests();
|
||||||
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
||||||
|
|
||||||
let rpc = RpcImpl::new("RPC test".to_owned(), Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1));
|
let rpc = RpcImpl::new(
|
||||||
|
"RPC test".to_owned(),
|
||||||
|
Buffer::new(mempool.clone(), 1),
|
||||||
|
Buffer::new(state.clone(), 1),
|
||||||
|
);
|
||||||
|
|
||||||
let transaction_bytes = transaction
|
let transaction_bytes = transaction
|
||||||
.zcash_serialize_to_vec()
|
.zcash_serialize_to_vec()
|
||||||
|
@ -168,7 +182,11 @@ proptest! {
|
||||||
let mut mempool = MockService::build().for_prop_tests();
|
let mut mempool = MockService::build().for_prop_tests();
|
||||||
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
||||||
|
|
||||||
let rpc = RpcImpl::new("RPC test".to_owned(), Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1));
|
let rpc = RpcImpl::new(
|
||||||
|
"RPC test".to_owned(),
|
||||||
|
Buffer::new(mempool.clone(), 1),
|
||||||
|
Buffer::new(state.clone(), 1),
|
||||||
|
);
|
||||||
|
|
||||||
let send_task = tokio::spawn(rpc.send_raw_transaction(non_hex_string));
|
let send_task = tokio::spawn(rpc.send_raw_transaction(non_hex_string));
|
||||||
|
|
||||||
|
@ -212,7 +230,11 @@ proptest! {
|
||||||
let mut mempool = MockService::build().for_prop_tests();
|
let mut mempool = MockService::build().for_prop_tests();
|
||||||
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
||||||
|
|
||||||
let rpc = RpcImpl::new("RPC test".to_owned(), Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1));
|
let rpc = RpcImpl::new(
|
||||||
|
"RPC test".to_owned(),
|
||||||
|
Buffer::new(mempool.clone(), 1),
|
||||||
|
Buffer::new(state.clone(), 1),
|
||||||
|
);
|
||||||
|
|
||||||
let send_task = tokio::spawn(rpc.send_raw_transaction(hex::encode(random_bytes)));
|
let send_task = tokio::spawn(rpc.send_raw_transaction(hex::encode(random_bytes)));
|
||||||
|
|
||||||
|
@ -237,6 +259,53 @@ proptest! {
|
||||||
Ok::<_, TestCaseError>(())
|
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 = RpcImpl::new(
|
||||||
|
"RPC test".to_owned(),
|
||||||
|
Buffer::new(mempool.clone(), 1),
|
||||||
|
Buffer::new(state.clone(), 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
Ok::<_, TestCaseError>(())
|
||||||
|
})?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Error)]
|
#[derive(Clone, Copy, Debug, Error)]
|
||||||
|
|
|
@ -1708,20 +1708,7 @@ fn lightwalletd_integration() -> Result<()> {
|
||||||
//
|
//
|
||||||
// TODO: add extra checks when we add new Zebra RPCs
|
// TODO: add extra checks when we add new Zebra RPCs
|
||||||
|
|
||||||
// Unimplemented getrawmempool (repeated, in a separate lightwalletd thread)
|
|
||||||
//
|
|
||||||
// zcash/lightwalletd exits with a fatal error here.
|
|
||||||
// adityapk00/lightwalletd keeps trying the mempool,
|
|
||||||
// but it sometimes skips the "Method not found" log line.
|
|
||||||
//
|
|
||||||
// If a refresh is pending, we can get "Mempool refresh error" before the Ingestor log,
|
|
||||||
// and "Another refresh is in progress" after it.
|
|
||||||
let result =
|
|
||||||
lightwalletd.expect_stdout_line_matches("(Mempool refresh error: -32601: Method not found)|(Another refresh in progress, returning)");
|
|
||||||
let (_, zebrad) = zebrad.kill_on_error(result)?;
|
|
||||||
|
|
||||||
// Cleanup both processes
|
// Cleanup both processes
|
||||||
|
|
||||||
let result = lightwalletd.kill();
|
let result = lightwalletd.kill();
|
||||||
let (_, mut zebrad) = zebrad.kill_on_error(result)?;
|
let (_, mut zebrad) = zebrad.kill_on_error(result)?;
|
||||||
zebrad.kill()?;
|
zebrad.kill()?;
|
||||||
|
|
Loading…
Reference in New Issue