feat(rpc): introduce `getblocktemplate-rpcs` feature (#5357)

* introduce `getblocktemplate-rpcs` feature

* reorder imports

* highlight the problem

* add docs

* add additional empty state test

* add snapshot test

* remove getblocktemplate trait

* use `cfg-if`

* leave server as it was

* add a missing space to the docs

* fix typo in test

Co-authored-by: Arya <aryasolhi@gmail.com>

* suggestion for introduce `getblocktemplate-rpcs` feature (#5418)

* adds minimal new object for the get block template methods

* updated TODO comment

Co-authored-by: Arya <aryasolhi@gmail.com>
This commit is contained in:
Alfredo Garcia 2022-10-19 09:01:13 -03:00 committed by GitHub
parent c1bebead3a
commit d81a5fc576
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 203 additions and 4 deletions

View File

@ -10,6 +10,7 @@ edition = "2021"
[features]
default = []
proptest-impl = ["proptest", "proptest-derive", "zebra-chain/proptest-impl", "zebra-state/proptest-impl"]
getblocktemplate-rpcs = []
[dependencies]
chrono = { version = "0.4.22", default-features = false, features = ["clock", "std"] }

View File

@ -34,6 +34,12 @@ use zebra_state::{OutputIndex, OutputLocation, TransactionLocation};
use crate::queue::Queue;
#[cfg(feature = "getblocktemplate-rpcs")]
mod get_block_template;
#[cfg(feature = "getblocktemplate-rpcs")]
pub use get_block_template::{GetBlockTemplateRpc, GetBlockTemplateRpcImpl};
#[cfg(test)]
mod tests;

View File

@ -0,0 +1,56 @@
//! RPC methods related to mining only available with `getblocktemplate-rpcs` rust feature.
use zebra_chain::chain_tip::ChainTip;
use jsonrpc_core::{self, Error, ErrorCode, Result};
use jsonrpc_derive::rpc;
/// getblocktemplate RPC method signatures.
#[rpc(server)]
pub trait GetBlockTemplateRpc {
/// Returns the height of the most recent block in the best valid block chain (equivalently,
/// the number of blocks in this chain excluding the genesis block).
///
/// zcashd reference: [`getblockcount`](https://zcash.github.io/rpc/getblockcount.html)
///
/// # Notes
///
/// This rpc method is available only if zebra is built with `--features getblocktemplate-rpcs`.
#[rpc(name = "getblockcount")]
fn get_block_count(&self) -> Result<u32>;
}
/// RPC method implementations.
pub struct GetBlockTemplateRpcImpl<Tip>
where
Tip: ChainTip,
{
// TODO: Add the other fields from the [`Rpc`] struct as-needed
/// Allows efficient access to the best tip of the blockchain.
latest_chain_tip: Tip,
}
impl<Tip> GetBlockTemplateRpcImpl<Tip>
where
Tip: ChainTip + Clone + Send + Sync + 'static,
{
/// Create a new instance of the RPC handler.
pub fn new(latest_chain_tip: Tip) -> Self {
Self { latest_chain_tip }
}
}
impl<Tip> GetBlockTemplateRpc for GetBlockTemplateRpcImpl<Tip>
where
Tip: ChainTip + Send + Sync + 'static,
{
fn get_block_count(&self) -> Result<u32> {
self.latest_chain_tip
.best_tip_height()
.map(|height| height.0)
.ok_or(Error {
code: ErrorCode::ServerError(0),
message: "No blocks in state".to_string(),
data: None,
})
}
}

View File

@ -41,6 +41,9 @@ async fn test_rpc_response_data_for_network(network: Network) {
let (_state, read_state, latest_chain_tip, _chain_tip_change) =
zebra_state::populated_state(blocks.clone(), network).await;
#[cfg(feature = "getblocktemplate-rpcs")]
let latest_chain_tip_gbt_clone = latest_chain_tip.clone();
// Init RPC
let (rpc, _rpc_tx_queue_task_handle) = RpcImpl::new(
"RPC test",
@ -166,6 +169,17 @@ async fn test_rpc_response_data_for_network(network: Network) {
.await
.expect("We should have a vector of strings");
snapshot_rpc_getaddressutxos(get_address_utxos, &settings);
#[cfg(feature = "getblocktemplate-rpcs")]
{
let get_block_template_rpc = GetBlockTemplateRpcImpl::new(latest_chain_tip_gbt_clone);
// `getblockcount`
let get_block_count = get_block_template_rpc
.get_block_count()
.expect("We should have a number");
snapshot_rpc_getblockcount(get_block_count, &settings);
}
}
/// Snapshot `getinfo` response, using `cargo insta` and JSON serialization.
@ -254,6 +268,12 @@ fn snapshot_rpc_getaddressutxos(utxos: Vec<GetAddressUtxos>, settings: &insta::S
settings.bind(|| insta::assert_json_snapshot!("get_address_utxos", utxos));
}
#[cfg(feature = "getblocktemplate-rpcs")]
/// Snapshot `getblockcount` response, using `cargo insta` and JSON serialization.
fn snapshot_rpc_getblockcount(block_count: u32, settings: &insta::Settings) {
settings.bind(|| insta::assert_json_snapshot!("get_block_count", block_count));
}
/// Utility function to convert a `Network` to a lowercase string.
fn network_string(network: Network) -> String {
let mut net_suffix = network.to_string();

View File

@ -0,0 +1,6 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
assertion_line: 273
expression: block_count
---
10

View File

@ -0,0 +1,6 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
assertion_line: 273
expression: block_count
---
10

View File

@ -612,3 +612,94 @@ async fn rpc_getaddressutxos_response() {
mempool.expect_no_requests().await;
}
#[tokio::test(flavor = "multi_thread")]
#[cfg(feature = "getblocktemplate-rpcs")]
async fn rpc_getblockcount() {
let _init_guard = zebra_test::init();
// Create a continuous chain of mainnet blocks from genesis
let blocks: Vec<Arc<Block>> = zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS
.iter()
.map(|(_height, block_bytes)| block_bytes.zcash_deserialize_into().unwrap())
.collect();
// Get the height of the block at the tip using hardcoded block tip bytes.
// We want to test the RPC response is equal to this hash
let tip_block = blocks.last().unwrap();
let tip_block_height = tip_block.coinbase_height().unwrap();
// Get a mempool handle
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
// Create a populated state service, the tip will be in `NUMBER_OF_BLOCKS`.
let (_state, read_state, latest_chain_tip, _chain_tip_change) =
zebra_state::populated_state(blocks.clone(), Mainnet).await;
// Init RPC
let (_rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
"RPC test",
Mainnet,
false,
Buffer::new(mempool.clone(), 1),
read_state,
latest_chain_tip.clone(),
);
// Init RPC
let get_block_template_rpc =
get_block_template::GetBlockTemplateRpcImpl::new(latest_chain_tip.clone());
// Get the tip height using RPC method `get_block_count`
let get_block_count = get_block_template_rpc
.get_block_count()
.expect("We should have a number");
// Check if response is equal to block 10 hash.
assert_eq!(get_block_count, tip_block_height.0);
mempool.expect_no_requests().await;
// The queue task should continue without errors or panics
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
assert!(matches!(rpc_tx_queue_task_result, None));
}
#[cfg(feature = "getblocktemplate-rpcs")]
#[tokio::test(flavor = "multi_thread")]
async fn rpc_getblockcount_empty_state() {
let _init_guard = zebra_test::init();
// Get a mempool handle
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
// Create an empty state
let (_state, read_state, latest_chain_tip, _chain_tip_change) =
zebra_state::init_test_services(Mainnet);
// Init RPC
let (_rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
"RPC test",
Mainnet,
false,
Buffer::new(mempool.clone(), 1),
read_state,
latest_chain_tip.clone(),
);
let get_block_template_rpc =
get_block_template::GetBlockTemplateRpcImpl::new(latest_chain_tip.clone());
// Get the tip height using RPC method `get_block_count
let get_block_count = get_block_template_rpc.get_block_count();
// state an empty so we should get an error
assert!(get_block_count.is_err());
// Check the error we got is the correct one
assert_eq!(get_block_count.err().unwrap().message, "No blocks in state");
mempool.expect_no_requests().await;
// The queue task should continue without errors or panics
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
assert!(matches!(rpc_tx_queue_task_result, None));
}

View File

@ -25,6 +25,9 @@ use crate::{
server::{compatibility::FixHttpRequestMiddleware, tracing_middleware::TracingMiddleware},
};
#[cfg(feature = "getblocktemplate-rpcs")]
use crate::methods::{GetBlockTemplateRpc, GetBlockTemplateRpcImpl};
pub mod compatibility;
mod tracing_middleware;
@ -46,7 +49,7 @@ impl RpcServer {
network: Network,
) -> (JoinHandle<()>, JoinHandle<()>)
where
Version: ToString,
Version: ToString + Clone,
Mempool: tower::Service<mempool::Request, Response = mempool::Response, Error = BoxError>
+ 'static,
Mempool::Future: Send,
@ -64,6 +67,19 @@ impl RpcServer {
if let Some(listen_addr) = config.listen_addr {
info!("Trying to open RPC endpoint at {}...", listen_addr,);
// Create handler compatible with V1 and V2 RPC protocols
let mut io: MetaIoHandler<(), _> =
MetaIoHandler::new(Compatibility::Both, TracingMiddleware);
#[cfg(feature = "getblocktemplate-rpcs")]
{
// Initialize the getblocktemplate rpc methods
let get_block_template_rpc_impl =
GetBlockTemplateRpcImpl::new(latest_chain_tip.clone());
io.extend_with(get_block_template_rpc_impl.to_delegate());
}
// Initialize the rpc methods with the zebra version
let (rpc_impl, rpc_tx_queue_task_handle) = RpcImpl::new(
app_version,
@ -74,9 +90,6 @@ impl RpcServer {
latest_chain_tip,
);
// Create handler compatible with V1 and V2 RPC protocols
let mut io: MetaIoHandler<(), _> =
MetaIoHandler::new(Compatibility::Both, TracingMiddleware);
io.extend_with(rpc_impl.to_delegate());
// If zero, automatically scale threads to the number of CPU cores