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]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
proptest-impl = ["proptest", "proptest-derive", "zebra-chain/proptest-impl", "zebra-state/proptest-impl"]
|
proptest-impl = ["proptest", "proptest-derive", "zebra-chain/proptest-impl", "zebra-state/proptest-impl"]
|
||||||
|
getblocktemplate-rpcs = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = { version = "0.4.22", default-features = false, features = ["clock", "std"] }
|
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;
|
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)]
|
#[cfg(test)]
|
||||||
mod tests;
|
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) =
|
let (_state, read_state, latest_chain_tip, _chain_tip_change) =
|
||||||
zebra_state::populated_state(blocks.clone(), network).await;
|
zebra_state::populated_state(blocks.clone(), network).await;
|
||||||
|
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
let latest_chain_tip_gbt_clone = latest_chain_tip.clone();
|
||||||
|
|
||||||
// Init RPC
|
// Init RPC
|
||||||
let (rpc, _rpc_tx_queue_task_handle) = RpcImpl::new(
|
let (rpc, _rpc_tx_queue_task_handle) = RpcImpl::new(
|
||||||
"RPC test",
|
"RPC test",
|
||||||
|
@ -166,6 +169,17 @@ async fn test_rpc_response_data_for_network(network: Network) {
|
||||||
.await
|
.await
|
||||||
.expect("We should have a vector of strings");
|
.expect("We should have a vector of strings");
|
||||||
snapshot_rpc_getaddressutxos(get_address_utxos, &settings);
|
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.
|
/// 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));
|
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.
|
/// Utility function to convert a `Network` to a lowercase string.
|
||||||
fn network_string(network: Network) -> String {
|
fn network_string(network: Network) -> String {
|
||||||
let mut net_suffix = network.to_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;
|
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},
|
server::{compatibility::FixHttpRequestMiddleware, tracing_middleware::TracingMiddleware},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
use crate::methods::{GetBlockTemplateRpc, GetBlockTemplateRpcImpl};
|
||||||
|
|
||||||
pub mod compatibility;
|
pub mod compatibility;
|
||||||
mod tracing_middleware;
|
mod tracing_middleware;
|
||||||
|
|
||||||
|
@ -46,7 +49,7 @@ impl RpcServer {
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> (JoinHandle<()>, JoinHandle<()>)
|
) -> (JoinHandle<()>, JoinHandle<()>)
|
||||||
where
|
where
|
||||||
Version: ToString,
|
Version: ToString + Clone,
|
||||||
Mempool: tower::Service<mempool::Request, Response = mempool::Response, Error = BoxError>
|
Mempool: tower::Service<mempool::Request, Response = mempool::Response, Error = BoxError>
|
||||||
+ 'static,
|
+ 'static,
|
||||||
Mempool::Future: Send,
|
Mempool::Future: Send,
|
||||||
|
@ -64,6 +67,19 @@ impl RpcServer {
|
||||||
if let Some(listen_addr) = config.listen_addr {
|
if let Some(listen_addr) = config.listen_addr {
|
||||||
info!("Trying to open RPC endpoint at {}...", 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
|
// Initialize the rpc methods with the zebra version
|
||||||
let (rpc_impl, rpc_tx_queue_task_handle) = RpcImpl::new(
|
let (rpc_impl, rpc_tx_queue_task_handle) = RpcImpl::new(
|
||||||
app_version,
|
app_version,
|
||||||
|
@ -74,9 +90,6 @@ impl RpcServer {
|
||||||
latest_chain_tip,
|
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());
|
io.extend_with(rpc_impl.to_delegate());
|
||||||
|
|
||||||
// If zero, automatically scale threads to the number of CPU cores
|
// If zero, automatically scale threads to the number of CPU cores
|
||||||
|
|
Loading…
Reference in New Issue