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:
Alfredo Garcia 2022-03-09 22:12:41 -03:00 committed by GitHub
parent d8d3f6407c
commit 833560411f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 300 additions and 92 deletions

71
Cargo.lock generated
View File

@ -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",

View File

@ -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;

View File

@ -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()
}
}

View File

@ -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"

View File

@ -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);

View File

@ -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

View File

@ -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;
}

View File

@ -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 =

View File

@ -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;

View File

@ -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,

View File

@ -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(())
}

View File

@ -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,

View File

@ -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.