feature(rpc): implement getblock api call (#3707)
* feature(rpc): start adding a `getblock` method * fix(rpc): replace oneshot * fix(rpc): replace a panic with error * fix(rpc): fix test * feature(rpc): add hex to response * refactor(rpc): use generic instead of alias * docs(rpc): improve docs for getblock method * test(rpc): add a test for getblock method * deps(rpc): remove non needed tower features Co-authored-by: teor <teor@riseup.net> * docs(rpc): add a note to getblock doc * refactor(rpc): replace alias * fix(rpc): use `zcash_serialize_to_vec()` instead of logging format * tests(rpc): add network argument to `populated_state()` * refactor(rpc): use an error for state service readiness * fix(rpc): add parameter * fix(rpc): clippy * nit(rpc): remove new line from imports * fix(rpc): remove commented code * fix(rpc): simplify error Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> * Use a `SerializedBlock` type to help serializing blocks (#3725) * Create a `SerializedBlock` helper type Create a type that can be used as a byte slice, but is guaranteed to represent a valid block. * Use `into_iter` instead of `iter` There's no need to borrow the elements, they can be moved out directly. This will be necessary because `&Arc<T>` doesn't implement `Borrow<T>`, so a `SerializedBlock` can't be built directly from an `&Arc<Block>`. * Use `SerializedBlock` in `GetBlock` Make the type stricter to avoid storing possibly invalid values. The bytes are still serialized as a hexadecimal string, through the usage of `hex`. The `serde::Deserialize` can't be derived because `hex` requires the type to also implement `FromHex`. * feature(rpc): add suggestions from code review Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> * tests(rpc): make sure mempool has no requests in get_block test * fix(rpc): change height argument type in getblock method * fix(rpc): rustfmt * fix(rpc): replace panic * fix(rpc): change getblock response * fix(rpc): fix lightwalletd test * tests(rpc): add a getblock error test * fix(rpc): try another regex Co-authored-by: teor <teor@riseup.net> Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com>
This commit is contained in:
parent
d8d3f6407c
commit
833560411f
|
@ -554,9 +554,9 @@ checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
|
|||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.7.3"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f"
|
||||
checksum = "0e851ca7c24871e7336801608a4797d7376545b6928a10d32d75685687141ead"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
|
@ -747,9 +747,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "color-eyre"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d6ec7641ff3474b7593009c809db602c414cd97c7d47a78ed004162b74ff96c"
|
||||
checksum = "8ebf286c900a6d5867aeff75cfee3192857bb7f24b547d4f0df2ed6baa812c90"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"color-spantrace 0.2.0",
|
||||
|
@ -1233,9 +1233,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ed25519"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74e1069e39f1454367eb2de793ed062fac4c35c2934b76a81d90dd9abcd28816"
|
||||
checksum = "eed12bbf7b5312f8da1c2722bc06d8c6b12c2d86a7fb35a194c7f3e6fc2bbe39"
|
||||
dependencies = [
|
||||
"signature",
|
||||
]
|
||||
|
@ -1351,9 +1351,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "eyre"
|
||||
version = "0.6.6"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc225d8f637923fe585089fcf03e705c222131232d2c1fb622e84ecf725d0eb8"
|
||||
checksum = "9289ed2c0440a6536e65119725cf91fc2c6b5e513bfd2e36e1134d7cca6ca12f"
|
||||
dependencies = [
|
||||
"indenter",
|
||||
"once_cell",
|
||||
|
@ -1616,9 +1616,9 @@ checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
|
|||
|
||||
[[package]]
|
||||
name = "git2"
|
||||
version = "0.13.25"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6"
|
||||
checksum = "6e7d3b96ec1fcaa8431cf04a4f1ef5caafe58d5cf7bcc31f09c1626adddb0ffe"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
|
@ -1963,9 +1963,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.12.0"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c0c443f6dceb3a1cb7607c87501aa91e4b9c976044f725c2a74ca2152c91a4"
|
||||
checksum = "30a7e1911532a662f6b08b68f884080850f2fd9544963c3ab23a5af42bda1eac"
|
||||
dependencies = [
|
||||
"console",
|
||||
"once_cell",
|
||||
|
@ -1987,9 +1987,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.3.1"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9"
|
||||
checksum = "35e70ee094dc02fd9c13fdad4940090f22dbd6ac7c9e7094a46cf0232a50bc7c"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
|
@ -2134,9 +2134,9 @@ checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
|
|||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
version = "0.12.26+1.3.0"
|
||||
version = "0.13.1+1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494"
|
||||
checksum = "43e598aa7a4faedf1ea1b4608f582b06f0f40211eec551b7ef36019ae3f62def"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
@ -2187,9 +2187,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66"
|
||||
checksum = "df2bf61678a0a521c3f7daf815d2e6717d85a272a7dcd02c9768272b32bd1e2a"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
@ -2399,9 +2399,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "minreq"
|
||||
version = "2.5.1"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "086dde2aacc3ce84b680e76ef4a60d77e75cb8109e5a508cc7ea81a86d65fd0d"
|
||||
checksum = "4c785bc6027fd359756e538541c8624012ba3776d3d3fe123885643092ed4132"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
|
@ -3355,9 +3355,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
|
||||
checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
@ -3447,9 +3447,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "retain_mut"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51dd4445360338dab5116712bee1388dc727991d51969558a8882ab552e6db30"
|
||||
checksum = "8c31b5c4033f8fdde8700e4657be2c497e7288f01515be52168c631e2e4d4086"
|
||||
|
||||
[[package]]
|
||||
name = "retry-error"
|
||||
|
@ -3459,9 +3459,9 @@ checksum = "5f0cb6e2859e29280664e192b37e0a698cef381fec81783f9efa3e5b0ffbaf8f"
|
|||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.31"
|
||||
version = "0.8.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a374af9a0e5fdcdd98c1c7b64f05004f9ea2555b6c75f211daa81268a3c50f1"
|
||||
checksum = "e74fdc210d8f24a7dbfedc13b04ba5764f5232754ccebfdf5fff1bad791ccbc6"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
@ -4006,9 +4006,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.9"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e"
|
||||
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
|
||||
|
||||
[[package]]
|
||||
name = "sketches-ddsketch"
|
||||
|
@ -4206,9 +4206,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
@ -4841,7 +4841,7 @@ dependencies = [
|
|||
name = "tower-batch"
|
||||
version = "0.2.21"
|
||||
dependencies = [
|
||||
"color-eyre 0.6.0",
|
||||
"color-eyre 0.6.1",
|
||||
"ed25519-zebra",
|
||||
"futures",
|
||||
"futures-core",
|
||||
|
@ -5614,7 +5614,7 @@ dependencies = [
|
|||
"bs58",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
"color-eyre 0.6.0",
|
||||
"color-eyre 0.6.1",
|
||||
"criterion",
|
||||
"displaydoc",
|
||||
"ed25519-zebra",
|
||||
|
@ -5666,7 +5666,7 @@ dependencies = [
|
|||
"blake2b_simd 1.0.0",
|
||||
"bls12_381",
|
||||
"chrono",
|
||||
"color-eyre 0.6.0",
|
||||
"color-eyre 0.6.1",
|
||||
"dirs",
|
||||
"displaydoc",
|
||||
"futures",
|
||||
|
@ -5762,6 +5762,7 @@ dependencies = [
|
|||
"zebra-chain",
|
||||
"zebra-network",
|
||||
"zebra-node-services",
|
||||
"zebra-state",
|
||||
"zebra-test",
|
||||
]
|
||||
|
||||
|
@ -5784,7 +5785,7 @@ version = "1.0.0-beta.5"
|
|||
dependencies = [
|
||||
"bincode",
|
||||
"chrono",
|
||||
"color-eyre 0.6.0",
|
||||
"color-eyre 0.6.1",
|
||||
"dirs",
|
||||
"displaydoc",
|
||||
"futures",
|
||||
|
@ -5841,7 +5842,7 @@ dependencies = [
|
|||
name = "zebra-utils"
|
||||
version = "1.0.0-beta.5"
|
||||
dependencies = [
|
||||
"color-eyre 0.6.0",
|
||||
"color-eyre 0.6.1",
|
||||
"hex",
|
||||
"serde_json",
|
||||
"structopt",
|
||||
|
|
|
@ -22,7 +22,7 @@ pub use commitment::{
|
|||
pub use hash::Hash;
|
||||
pub use header::{BlockTimeError, CountedHeader, Header};
|
||||
pub use height::Height;
|
||||
pub use serialize::MAX_BLOCK_BYTES;
|
||||
pub use serialize::{SerializedBlock, MAX_BLOCK_BYTES};
|
||||
|
||||
#[cfg(any(test, feature = "proptest-impl"))]
|
||||
pub use arbitrary::LedgerState;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{convert::TryInto, io};
|
||||
use std::{borrow::Borrow, convert::TryInto, io};
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use chrono::{TimeZone, Utc};
|
||||
|
@ -135,3 +135,30 @@ impl ZcashDeserialize for Block {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A serialized block.
|
||||
///
|
||||
/// Stores bytes that are guaranteed to be deserializable into a [`Block`].
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct SerializedBlock {
|
||||
bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Build a [`SerializedBlock`] by serializing a block.
|
||||
impl<B: Borrow<Block>> From<B> for SerializedBlock {
|
||||
fn from(block: B) -> Self {
|
||||
SerializedBlock {
|
||||
bytes: block
|
||||
.borrow()
|
||||
.zcash_serialize_to_vec()
|
||||
.expect("Writing to a `Vec` should never fail"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Access the serialized bytes of a [`SerializedBlock`].
|
||||
impl AsRef<[u8]> for SerializedBlock {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.bytes.as_ref()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
zebra-chain = { path = "../zebra-chain" }
|
||||
zebra-node-services = { path = "../zebra-node-services" }
|
||||
|
||||
zebra-network = { path = "../zebra-network" }
|
||||
zebra-node-services = { path = "../zebra-node-services" }
|
||||
zebra-state = { path = "../zebra-state" }
|
||||
|
||||
futures = "0.3.21"
|
||||
|
||||
|
|
|
@ -6,14 +6,15 @@
|
|||
//! Some parts of the `zcashd` RPC documentation are outdated.
|
||||
//! So this implementation follows the `lightwalletd` client implementation.
|
||||
|
||||
use futures::FutureExt;
|
||||
use futures::{FutureExt, TryFutureExt};
|
||||
use hex::FromHex;
|
||||
use jsonrpc_core::{self, BoxFuture, Error, ErrorCode, Result};
|
||||
use jsonrpc_derive::rpc;
|
||||
use tower::{buffer::Buffer, ServiceExt};
|
||||
use tower::{buffer::Buffer, Service, ServiceExt};
|
||||
|
||||
use zebra_chain::{
|
||||
serialization::ZcashDeserialize,
|
||||
block::SerializedBlock,
|
||||
serialization::{SerializationError, ZcashDeserialize},
|
||||
transaction::{self, Transaction},
|
||||
};
|
||||
use zebra_network::constants::USER_AGENT;
|
||||
|
@ -72,38 +73,82 @@ pub trait Rpc {
|
|||
&self,
|
||||
raw_transaction_hex: String,
|
||||
) -> BoxFuture<Result<SentTransactionHash>>;
|
||||
|
||||
/// getblock
|
||||
///
|
||||
/// Returns requested block by height, encoded as hex.
|
||||
///
|
||||
/// zcashd reference: <https://zcash.github.io/rpc/getblock.html>
|
||||
///
|
||||
/// Result:
|
||||
/// {
|
||||
/// "data": String, // The block encoded as hex
|
||||
/// }
|
||||
///
|
||||
/// Note 1: We only expose the `data` field as lightwalletd uses the non-verbose
|
||||
/// mode for all getblock calls: <https://github.com/zcash/lightwalletd/blob/v0.4.9/common/common.go#L232>
|
||||
///
|
||||
/// Note 2: `lightwalletd` only requests blocks by height, so we don't support
|
||||
/// getting blocks by hash.
|
||||
///
|
||||
/// Note 3: The `verbosity` parameter is ignored but required in the call.
|
||||
#[rpc(name = "getblock")]
|
||||
fn get_block(&self, height: String, verbosity: u8) -> BoxFuture<Result<GetBlock>>;
|
||||
}
|
||||
|
||||
/// RPC method implementations.
|
||||
pub struct RpcImpl<Mempool>
|
||||
pub struct RpcImpl<Mempool, State>
|
||||
where
|
||||
Mempool: tower::Service<mempool::Request, Response = mempool::Response, Error = BoxError>,
|
||||
Mempool: Service<mempool::Request, Response = mempool::Response, Error = BoxError>,
|
||||
State: Service<
|
||||
zebra_state::Request,
|
||||
Response = zebra_state::Response,
|
||||
Error = zebra_state::BoxError,
|
||||
>,
|
||||
{
|
||||
/// Zebra's application version.
|
||||
app_version: String,
|
||||
|
||||
/// A handle to the mempool service.
|
||||
mempool: Buffer<Mempool, mempool::Request>,
|
||||
/// A handle to the state service.
|
||||
state: Buffer<State, zebra_state::Request>,
|
||||
}
|
||||
|
||||
impl<Mempool> RpcImpl<Mempool>
|
||||
impl<Mempool, State> RpcImpl<Mempool, State>
|
||||
where
|
||||
Mempool: tower::Service<mempool::Request, Response = mempool::Response, Error = BoxError>,
|
||||
Mempool: Service<mempool::Request, Response = mempool::Response, Error = BoxError>,
|
||||
State: Service<
|
||||
zebra_state::Request,
|
||||
Response = zebra_state::Response,
|
||||
Error = zebra_state::BoxError,
|
||||
> + 'static,
|
||||
State::Future: Send,
|
||||
{
|
||||
/// Create a new instance of the RPC handler.
|
||||
pub fn new(app_version: String, mempool: Buffer<Mempool, mempool::Request>) -> Self {
|
||||
pub fn new(
|
||||
app_version: String,
|
||||
mempool: Buffer<Mempool, mempool::Request>,
|
||||
state: Buffer<State, zebra_state::Request>,
|
||||
) -> Self {
|
||||
RpcImpl {
|
||||
app_version,
|
||||
mempool,
|
||||
state,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Mempool> Rpc for RpcImpl<Mempool>
|
||||
impl<Mempool, State> Rpc for RpcImpl<Mempool, State>
|
||||
where
|
||||
Mempool:
|
||||
tower::Service<mempool::Request, Response = mempool::Response, Error = BoxError> + 'static,
|
||||
Mempool::Future: Send,
|
||||
State: Service<
|
||||
zebra_state::Request,
|
||||
Response = zebra_state::Response,
|
||||
Error = zebra_state::BoxError,
|
||||
> + 'static,
|
||||
State::Future: Send,
|
||||
{
|
||||
fn get_info(&self) -> Result<GetInfo> {
|
||||
let response = GetInfo {
|
||||
|
@ -169,6 +214,40 @@ where
|
|||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn get_block(&self, height: String, _verbosity: u8) -> BoxFuture<Result<GetBlock>> {
|
||||
let mut state = self.state.clone();
|
||||
|
||||
async move {
|
||||
let height = height.parse().map_err(|error: SerializationError| Error {
|
||||
code: ErrorCode::ServerError(0),
|
||||
message: error.to_string(),
|
||||
data: None,
|
||||
})?;
|
||||
|
||||
let request = zebra_state::Request::Block(zebra_state::HashOrHeight::Height(height));
|
||||
let response = state
|
||||
.ready()
|
||||
.and_then(|service| service.call(request))
|
||||
.await
|
||||
.map_err(|error| Error {
|
||||
code: ErrorCode::ServerError(0),
|
||||
message: error.to_string(),
|
||||
data: None,
|
||||
})?;
|
||||
|
||||
match response {
|
||||
zebra_state::Response::Block(Some(block)) => Ok(GetBlock(block.into())),
|
||||
zebra_state::Response::Block(None) => Err(Error {
|
||||
code: ErrorCode::ServerError(0),
|
||||
message: "Block not found".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
_ => unreachable!("unmatched response to a block request"),
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
|
@ -190,3 +269,7 @@ pub struct GetBlockChainInfo {
|
|||
///
|
||||
/// A JSON string with the transaction hash in hexadecimal.
|
||||
pub struct SentTransactionHash(#[serde(with = "hex")] transaction::Hash);
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
/// Response to a `getblock` RPC request.
|
||||
pub struct GetBlock(#[serde(with = "hex")] SerializedBlock);
|
||||
|
|
|
@ -20,7 +20,8 @@ proptest! {
|
|||
|
||||
runtime.block_on(async move {
|
||||
let mut mempool = MockService::build().for_prop_tests();
|
||||
let rpc = RpcImpl::new("RPC test".to_owned(), Buffer::new(mempool.clone(), 1));
|
||||
let mut state = MockService::build().for_prop_tests();
|
||||
let rpc = RpcImpl::new("RPC test".to_owned(), Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1));
|
||||
let hash = SentTransactionHash(transaction.hash());
|
||||
|
||||
let transaction_bytes = transaction
|
||||
|
@ -39,6 +40,8 @@ proptest! {
|
|||
.await?
|
||||
.respond(response);
|
||||
|
||||
state.expect_no_requests().await?;
|
||||
|
||||
let result = send_task
|
||||
.await
|
||||
.expect("Sending raw transactions should not panic");
|
||||
|
@ -58,7 +61,9 @@ proptest! {
|
|||
|
||||
runtime.block_on(async move {
|
||||
let mut mempool = MockService::build().for_prop_tests();
|
||||
let rpc = RpcImpl::new("RPC test".to_owned(), Buffer::new(mempool.clone(), 1));
|
||||
let mut state = MockService::build().for_prop_tests();
|
||||
|
||||
let rpc = RpcImpl::new("RPC test".to_owned(), Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1));
|
||||
|
||||
let transaction_bytes = transaction
|
||||
.zcash_serialize_to_vec()
|
||||
|
@ -75,6 +80,9 @@ proptest! {
|
|||
.await?
|
||||
.respond(Err(DummyError));
|
||||
|
||||
state.expect_no_requests().await?;
|
||||
|
||||
|
||||
let result = send_task
|
||||
.await
|
||||
.expect("Sending raw transactions should not panic");
|
||||
|
@ -101,7 +109,9 @@ proptest! {
|
|||
|
||||
runtime.block_on(async move {
|
||||
let mut mempool = MockService::build().for_prop_tests();
|
||||
let rpc = RpcImpl::new("RPC test".to_owned(), Buffer::new(mempool.clone(), 1));
|
||||
let mut state = MockService::build().for_prop_tests();
|
||||
|
||||
let rpc = RpcImpl::new("RPC test".to_owned(), Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1));
|
||||
|
||||
let transaction_bytes = transaction
|
||||
.zcash_serialize_to_vec()
|
||||
|
@ -119,6 +129,8 @@ proptest! {
|
|||
.await?
|
||||
.respond(response);
|
||||
|
||||
state.expect_no_requests().await?;
|
||||
|
||||
let result = send_task
|
||||
.await
|
||||
.expect("Sending raw transactions should not panic");
|
||||
|
@ -152,11 +164,14 @@ proptest! {
|
|||
|
||||
runtime.block_on(async move {
|
||||
let mut mempool = MockService::build().for_prop_tests();
|
||||
let rpc = RpcImpl::new("RPC test".to_owned(), Buffer::new(mempool.clone(), 1));
|
||||
let mut state = MockService::build().for_prop_tests();
|
||||
|
||||
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));
|
||||
|
||||
mempool.expect_no_requests().await?;
|
||||
state.expect_no_requests().await?;
|
||||
|
||||
let result = send_task
|
||||
.await
|
||||
|
@ -193,11 +208,14 @@ proptest! {
|
|||
|
||||
runtime.block_on(async move {
|
||||
let mut mempool = MockService::build().for_prop_tests();
|
||||
let rpc = RpcImpl::new("RPC test".to_owned(), Buffer::new(mempool.clone(), 1));
|
||||
let mut state = MockService::build().for_prop_tests();
|
||||
|
||||
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)));
|
||||
|
||||
mempool.expect_no_requests().await?;
|
||||
state.expect_no_requests().await?;
|
||||
|
||||
let result = send_task
|
||||
.await
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
//! Fixed test vectors for RPC methods.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use tower::buffer::Buffer;
|
||||
|
||||
use zebra_chain::{block::Block, parameters::Network, serialization::ZcashDeserializeInto};
|
||||
use zebra_network::constants::USER_AGENT;
|
||||
use zebra_node_services::BoxError;
|
||||
use zebra_test::mock_service::MockService;
|
||||
|
@ -13,10 +16,12 @@ async fn rpc_getinfo() {
|
|||
zebra_test::init();
|
||||
|
||||
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
|
||||
let mut state = MockService::build().for_unit_tests();
|
||||
|
||||
let rpc = RpcImpl::new(
|
||||
"Zebra version test".to_string(),
|
||||
Buffer::new(mempool.clone(), 1),
|
||||
Buffer::new(state.clone(), 1),
|
||||
);
|
||||
|
||||
let get_info = rpc.get_info().expect("We should have a GetInfo struct");
|
||||
|
@ -30,4 +35,66 @@ async fn rpc_getinfo() {
|
|||
assert_eq!(get_info.subversion, USER_AGENT);
|
||||
|
||||
mempool.expect_no_requests().await;
|
||||
state.expect_no_requests().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rpc_getblock() {
|
||||
zebra_test::init();
|
||||
|
||||
// Number of blocks to populate state with
|
||||
const NUMBER_OF_BLOCKS: u32 = 10;
|
||||
|
||||
// Put the first `NUMBER_OF_BLOCKS` blocks in a vector
|
||||
let blocks: Vec<Arc<Block>> = zebra_test::vectors::MAINNET_BLOCKS
|
||||
.range(0..=NUMBER_OF_BLOCKS)
|
||||
.map(|(_, block_bytes)| block_bytes.zcash_deserialize_into::<Arc<Block>>().unwrap())
|
||||
.collect();
|
||||
|
||||
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
|
||||
// Create a populated state service
|
||||
let state = zebra_state::populated_state(blocks.clone(), Network::Mainnet).await;
|
||||
|
||||
// Init RPC
|
||||
let rpc = RpcImpl {
|
||||
app_version: "Zebra version test".to_string(),
|
||||
mempool: Buffer::new(mempool.clone(), 1),
|
||||
state,
|
||||
};
|
||||
|
||||
// Make calls and check response
|
||||
for (i, block) in blocks.into_iter().enumerate() {
|
||||
let get_block = rpc
|
||||
.get_block(i.to_string(), 0u8)
|
||||
.await
|
||||
.expect("We should have a GetBlock struct");
|
||||
|
||||
assert_eq!(get_block.0, block.into());
|
||||
}
|
||||
|
||||
mempool.expect_no_requests().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rpc_getblock_error() {
|
||||
zebra_test::init();
|
||||
|
||||
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
|
||||
let mut state = MockService::build().for_unit_tests();
|
||||
|
||||
// Init RPC
|
||||
let rpc = RpcImpl {
|
||||
app_version: "Zebra version test".to_string(),
|
||||
mempool: Buffer::new(mempool.clone(), 1),
|
||||
state: Buffer::new(state.clone(), 1),
|
||||
};
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
use jsonrpc_core;
|
||||
use jsonrpc_http_server::ServerBuilder;
|
||||
use tower::buffer::Buffer;
|
||||
use tower::{buffer::Buffer, Service};
|
||||
use tracing::*;
|
||||
use tracing_futures::Instrument;
|
||||
|
||||
|
@ -26,21 +26,28 @@ pub struct RpcServer;
|
|||
|
||||
impl RpcServer {
|
||||
/// Start a new RPC server endpoint
|
||||
pub fn spawn<Mempool>(
|
||||
pub fn spawn<Mempool, State>(
|
||||
config: Config,
|
||||
app_version: String,
|
||||
mempool: Buffer<Mempool, mempool::Request>,
|
||||
state: Buffer<State, zebra_state::Request>,
|
||||
) -> tokio::task::JoinHandle<()>
|
||||
where
|
||||
Mempool: tower::Service<mempool::Request, Response = mempool::Response, Error = BoxError>
|
||||
+ 'static,
|
||||
Mempool::Future: Send,
|
||||
State: Service<
|
||||
zebra_state::Request,
|
||||
Response = zebra_state::Response,
|
||||
Error = zebra_state::BoxError,
|
||||
> + 'static,
|
||||
State::Future: Send,
|
||||
{
|
||||
if let Some(listen_addr) = config.listen_addr {
|
||||
info!("Trying to open RPC endpoint at {}...", listen_addr,);
|
||||
|
||||
// Initialize the rpc methods with the zebra version
|
||||
let rpc_impl = RpcImpl::new(app_version, mempool);
|
||||
let rpc_impl = RpcImpl::new(app_version, mempool, state);
|
||||
|
||||
// Create handler compatible with V1 and V2 RPC protocols
|
||||
let mut io =
|
||||
|
|
|
@ -41,7 +41,7 @@ pub use service::{
|
|||
#[cfg(any(test, feature = "proptest-impl"))]
|
||||
pub use service::{
|
||||
chain_tip::{ChainTipBlock, ChainTipSender},
|
||||
init_test,
|
||||
init_test, populated_state,
|
||||
};
|
||||
|
||||
pub(crate) use request::ContextuallyValidBlock;
|
||||
|
|
|
@ -7,9 +7,9 @@ use std::{
|
|||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use futures::future::FutureExt;
|
||||
use futures::{future::FutureExt, stream::FuturesUnordered};
|
||||
use tokio::sync::oneshot;
|
||||
use tower::{util::BoxService, Service};
|
||||
use tower::{util::BoxService, Service, ServiceExt};
|
||||
use tracing::instrument;
|
||||
|
||||
#[cfg(any(test, feature = "proptest-impl"))]
|
||||
|
@ -843,6 +843,33 @@ pub fn init_test(network: Network) -> Buffer<BoxService<Request, Response, BoxEr
|
|||
Buffer::new(BoxService::new(state_service), 1)
|
||||
}
|
||||
|
||||
/// Initialize a state service with blocks.
|
||||
#[cfg(any(test, feature = "proptest-impl"))]
|
||||
pub async fn populated_state(
|
||||
blocks: impl IntoIterator<Item = Arc<Block>>,
|
||||
network: Network,
|
||||
) -> Buffer<BoxService<Request, Response, BoxError>, Request> {
|
||||
let requests = blocks
|
||||
.into_iter()
|
||||
.map(|block| Request::CommitFinalizedBlock(block.into()));
|
||||
|
||||
let mut state = init_test(network);
|
||||
|
||||
let mut responses = FuturesUnordered::new();
|
||||
|
||||
for request in requests {
|
||||
let rsp = state.ready().await.unwrap().call(request);
|
||||
responses.push(rsp);
|
||||
}
|
||||
|
||||
use futures::StreamExt;
|
||||
while let Some(rsp) = responses.next().await {
|
||||
rsp.expect("blocks should commit just fine");
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
/// Check if zebra is following a legacy chain and return an error if so.
|
||||
fn legacy_chain_check<I>(
|
||||
nu5_activation_height: block::Height,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use std::{convert::TryInto, env, sync::Arc};
|
||||
|
||||
use futures::stream::FuturesUnordered;
|
||||
use tower::{buffer::Buffer, util::BoxService, Service, ServiceExt};
|
||||
use tower::{buffer::Buffer, util::BoxService};
|
||||
|
||||
use zebra_chain::{
|
||||
block::{self, Block, CountedHeader},
|
||||
|
@ -17,38 +16,13 @@ use zebra_test::{prelude::*, transcript::Transcript};
|
|||
use crate::{
|
||||
arbitrary::Prepare,
|
||||
constants, init_test,
|
||||
service::{chain_tip::TipAction, StateService},
|
||||
service::{chain_tip::TipAction, populated_state, StateService},
|
||||
tests::setup::{partial_nu5_chain_strategy, transaction_v4_from_coinbase},
|
||||
BoxError, Config, FinalizedBlock, PreparedBlock, Request, Response,
|
||||
};
|
||||
|
||||
const LAST_BLOCK_HEIGHT: u32 = 10;
|
||||
|
||||
async fn populated_state(
|
||||
blocks: impl IntoIterator<Item = Arc<Block>>,
|
||||
) -> Buffer<BoxService<Request, Response, BoxError>, Request> {
|
||||
let requests = blocks
|
||||
.into_iter()
|
||||
.map(|block| Request::CommitFinalizedBlock(block.into()));
|
||||
|
||||
let network = Network::Mainnet;
|
||||
let mut state = init_test(network);
|
||||
|
||||
let mut responses = FuturesUnordered::new();
|
||||
|
||||
for request in requests {
|
||||
let rsp = state.ready().await.unwrap().call(request);
|
||||
responses.push(rsp);
|
||||
}
|
||||
|
||||
use futures::StreamExt;
|
||||
while let Some(rsp) = responses.next().await {
|
||||
rsp.expect("blocks should commit just fine");
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
async fn test_populated_state_responds_correctly(
|
||||
mut state: Buffer<BoxService<Request, Response, BoxError>, Request>,
|
||||
) -> Result<()> {
|
||||
|
@ -222,7 +196,7 @@ async fn test_populated_state_responds_correctly(
|
|||
|
||||
#[tokio::main]
|
||||
async fn populate_and_check(blocks: Vec<Arc<Block>>) -> Result<()> {
|
||||
let state = populated_state(blocks).await;
|
||||
let state = populated_state(blocks, Network::Mainnet).await;
|
||||
test_populated_state_responds_correctly(state).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -155,8 +155,12 @@ impl StartCmd {
|
|||
.service(mempool);
|
||||
|
||||
// Launch RPC server
|
||||
let rpc_task_handle =
|
||||
RpcServer::spawn(config.rpc, app_version().to_string(), mempool.clone());
|
||||
let rpc_task_handle = RpcServer::spawn(
|
||||
config.rpc,
|
||||
app_version().to_string(),
|
||||
mempool.clone(),
|
||||
state.clone(),
|
||||
);
|
||||
|
||||
let setup_data = InboundSetupData {
|
||||
address_book,
|
||||
|
|
|
@ -1693,7 +1693,7 @@ fn lightwalletd_integration() -> Result<()> {
|
|||
// zcash/lightwalletd calls getbestblockhash here, but
|
||||
// adityapk00/lightwalletd calls getblock
|
||||
let result =
|
||||
lightwalletd.expect_stdout_line_matches("Method not found.*error zcashd getblock rpc");
|
||||
lightwalletd.expect_stdout_line_matches("Block hash changed, clearing mempool clients");
|
||||
let (_, zebrad) = zebrad.kill_on_error(result)?;
|
||||
|
||||
// zcash/lightwalletd exits with a fatal error here.
|
||||
|
|
Loading…
Reference in New Issue