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:
parent
c1bebead3a
commit
d81a5fc576
|
@ -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"] }
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
source: zebra-rpc/src/methods/tests/snapshot.rs
|
||||
assertion_line: 273
|
||||
expression: block_count
|
||||
---
|
||||
10
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
source: zebra-rpc/src/methods/tests/snapshot.rs
|
||||
assertion_line: 273
|
||||
expression: block_count
|
||||
---
|
||||
10
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue