Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

change(rpc): Adds getmininginfo, getnetworksolps and getnetworkhashps methods #5808

Merged
merged 15 commits into from
Dec 8, 2022
Merged
7 changes: 7 additions & 0 deletions zebra-chain/src/work/difficulty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,13 @@ impl std::ops::Add for Work {
/// Partial work used to track relative work in non-finalized chains
pub struct PartialCumulativeWork(u128);

impl PartialCumulativeWork {
/// Return the inner `u128` value.
pub fn as_u128(self) -> u128 {
self.0
}
}

impl From<Work> for PartialCumulativeWork {
fn from(work: Work) -> Self {
PartialCumulativeWork(work.0)
Expand Down
2 changes: 1 addition & 1 deletion zebra-rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jsonrpc-http-server = "18.0.0"
num_cpus = "1.14.0"

# zebra-rpc needs the preserve_order feature in serde_json, which is a dependency of jsonrpc-core
serde_json = { version = "1.0.89", features = ["preserve_order"] }
serde_json = { version = "1.0.89", features = ["preserve_order", "arbitrary_precision"] }
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
indexmap = { version = "1.9.2", features = ["serde"] }

tokio = { version = "1.23.0", features = ["time", "rt-multi-thread", "macros", "tracing"] }
Expand Down
87 changes: 87 additions & 0 deletions zebra-rpc/src/methods/get_block_template_rpcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ pub(crate) mod zip317;
/// > and clock time varies between nodes.
const MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP: i32 = 100;

/// The default window size specifying how many blocks to check when estimating the chain's solution rate.
///
/// Based on default value in zcashd.
const DEFAULT_SOLUTION_RATE_WINDOW_SIZE: usize = 120;

/// The RPC error code used by `zcashd` for when it's still downloading initial blocks.
///
/// `s-nomp` mining pool expects error code `-10` when the node is not synced:
Expand Down Expand Up @@ -133,6 +138,38 @@ pub trait GetBlockTemplateRpc {
hex_data: HexData,
_options: Option<submit_block::JsonParameters>,
) -> BoxFuture<Result<submit_block::Response>>;

/// Returns mining-related information.
///
/// zcashd reference: [`getmininginfo`](https://zcash.github.io/rpc/getmininginfo.html)
#[rpc(name = "getmininginfo")]
fn get_mining_info(&self) -> BoxFuture<Result<types::get_mining_info::Response>>;

/// Returns the estimated network solutions per second based on the last `num_blocks` before `height`.
/// If `num_blocks` is not supplied, uses 120 blocks.
/// If `height` is not supplied or is 0, uses the tip height.
///
/// zcashd reference: [`getnetworksolps`](https://zcash.github.io/rpc/getnetworksolps.html)
#[rpc(name = "getnetworksolps")]
fn get_network_sol_ps(
&self,
num_blocks: Option<usize>,
height: Option<i32>,
) -> BoxFuture<Result<u128>>;

/// Returns the estimated network solutions per second based on the last `num_blocks` before `height`.
/// If `num_blocks` is not supplied, uses 120 blocks.
/// If `height` is not supplied or is 0, uses the tip height.
///
/// zcashd reference: [`getnetworkhashps`](https://zcash.github.io/rpc/getnetworkhashps.html)
#[rpc(name = "getnetworkhashps")]
fn get_network_hash_ps(
&self,
num_blocks: Option<usize>,
height: Option<i32>,
) -> BoxFuture<Result<u128>> {
self.get_network_sol_ps(num_blocks, height)
}
}

/// RPC method implementations.
Expand Down Expand Up @@ -529,6 +566,56 @@ where
}
.boxed()
}

fn get_mining_info(&self) -> BoxFuture<Result<types::get_mining_info::Response>> {
let network = self.network;
let solution_rate_fut = self.get_network_sol_ps(None, None);
async move {
Ok(types::get_mining_info::Response::new(
network,
solution_rate_fut.await?,
))
}
.boxed()
}

fn get_network_sol_ps(
&self,
num_blocks: Option<usize>,
height: Option<i32>,
) -> BoxFuture<Result<u128>> {
let num_blocks = num_blocks
.map(|num_blocks| num_blocks.max(1))
.unwrap_or(DEFAULT_SOLUTION_RATE_WINDOW_SIZE);
let height = height.and_then(|height| (height > 1).then_some(Height(height as u32)));
let mut state = self.state.clone();

async move {
let request = ReadRequest::SolutionRate { num_blocks, 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,
})?;

let solution_rate = match response {
ReadResponse::SolutionRate(solution_rate) => solution_rate.ok_or(Error {
code: ErrorCode::ServerError(0),
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
message: "No blocks in state".to_string(),
data: None,
})?,
_ => unreachable!("unmatched response to a solution rate request"),
};

Ok(solution_rate)
}
.boxed()
}
}

// get_block_template support methods
Expand Down
1 change: 1 addition & 0 deletions zebra-rpc/src/methods/get_block_template_rpcs/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pub mod default_roots;
pub mod get_block_template;
pub mod get_block_template_opts;
pub mod get_mining_info;
pub mod hex_data;
pub mod submit_block;
pub mod transaction;
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//! Response type for the `getmininginfo` RPC.

use zebra_chain::parameters::Network;

/// Response to a `getmininginfo` RPC request.
#[derive(Debug, PartialEq, Eq, serde::Serialize)]
pub struct Response {
/// The estimated network solution rate in Sol/s.
networksolps: u128,

/// The estimated network solution rate in Sol/s.
networkhashps: u128,

/// Current network name as defined in BIP70 (main, test, regtest)
chain: String,

/// If using testnet or not
testnet: bool,
}

impl Response {
/// Creates a new `getmininginfo` response
pub fn new(network: Network, networksolps: u128) -> Self {
Self {
networksolps,
networkhashps: networksolps,
chain: network.bip70_network_name(),
testnet: network == Network::Testnet,
}
}
}
32 changes: 30 additions & 2 deletions zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ use zebra_test::mock_service::{MockService, PanicAssertion};
use crate::methods::{
get_block_template_rpcs::{
self,
types::{get_block_template::GetBlockTemplate, hex_data::HexData, submit_block},
types::{
get_block_template::GetBlockTemplate, get_mining_info, hex_data::HexData, submit_block,
},
},
tests::utils::fake_history_tree,
GetBlockHash, GetBlockTemplateRpc, GetBlockTemplateRpcImpl,
Expand Down Expand Up @@ -127,9 +129,22 @@ pub async fn test_responses<State, ReadState>(
.get_block_hash(BLOCK_HEIGHT10)
.await
.expect("We should have a GetBlockHash struct");

snapshot_rpc_getblockhash(get_block_hash, &settings);

// `getmininginfo`
let get_mining_info = get_block_template_rpc
.get_mining_info()
.await
.expect("We should have a success response");
snapshot_rpc_getmininginfo(get_mining_info, &settings);

// `getnetworksolps` (and `getnetworkhashps`)
let get_network_sol_ps = get_block_template_rpc
.get_network_sol_ps(None, None)
.await
.expect("We should have a success response");
snapshot_rpc_getnetworksolps(get_network_sol_ps, &settings);

// get a new empty state
let new_read_state = MockService::build().for_unit_tests();

Expand Down Expand Up @@ -225,3 +240,16 @@ fn snapshot_rpc_submit_block_invalid(
insta::assert_json_snapshot!("snapshot_rpc_submit_block_invalid", submit_block_response)
});
}

/// Snapshot `getmininginfo` response, using `cargo insta` and JSON serialization.
fn snapshot_rpc_getmininginfo(
get_mining_info: get_mining_info::Response,
settings: &insta::Settings,
) {
settings.bind(|| insta::assert_json_snapshot!("get_mining_info", get_mining_info));
}

/// Snapshot `getnetworksolps` response, using `cargo insta` and JSON serialization.
fn snapshot_rpc_getnetworksolps(get_network_sol_ps: u128, settings: &insta::Settings) {
settings.bind(|| insta::assert_json_snapshot!("get_network_sol_ps", get_network_sol_ps));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: get_mining_info
---
{
"networksolps": 2,
"networkhashps": 2,
"chain": "main",
"testnet": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: get_mining_info
---
{
"networksolps": 0,
"networkhashps": 0,
"chain": "test",
"testnet": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: get_network_sol_ps
---
2
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: get_network_sol_ps
---
0
89 changes: 89 additions & 0 deletions zebra-rpc/src/methods/tests/vectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,95 @@ async fn rpc_getblockhash() {
mempool.expect_no_requests().await;
}

#[cfg(feature = "getblocktemplate-rpcs")]
#[tokio::test(flavor = "multi_thread")]
async fn rpc_getmininginfo() {
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();

// Create a populated state service
let (_state, read_state, latest_chain_tip, _chain_tip_change) =
zebra_state::populated_state(blocks.clone(), Mainnet).await;

// Init RPC
let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new(
Mainnet,
Default::default(),
Buffer::new(MockService::build().for_unit_tests(), 1),
read_state,
latest_chain_tip.clone(),
MockService::build().for_unit_tests(),
MockSyncStatus::default(),
);

get_block_template_rpc
.get_mining_info()
.await
.expect("get_mining_info call should succeed");
}

#[cfg(feature = "getblocktemplate-rpcs")]
#[tokio::test(flavor = "multi_thread")]
async fn rpc_getnetworksolps() {
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();

// Create a populated state service
let (_state, read_state, latest_chain_tip, _chain_tip_change) =
zebra_state::populated_state(blocks.clone(), Mainnet).await;

// Init RPC
let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new(
Mainnet,
Default::default(),
Buffer::new(MockService::build().for_unit_tests(), 1),
read_state,
latest_chain_tip.clone(),
MockService::build().for_unit_tests(),
MockSyncStatus::default(),
);

let get_network_sol_ps_inputs = [
(None, None),
(Some(0), None),
(Some(0), Some(0)),
(Some(0), Some(-1)),
(Some(0), Some(10)),
(Some(0), Some(i32::MAX)),
(Some(1), None),
(Some(1), Some(0)),
(Some(1), Some(-1)),
(Some(1), Some(10)),
(Some(1), Some(i32::MAX)),
(Some(usize::MAX), None),
(Some(usize::MAX), Some(0)),
(Some(usize::MAX), Some(-1)),
(Some(usize::MAX), Some(10)),
(Some(usize::MAX), Some(i32::MAX)),
];

for (num_blocks_input, height_input) in get_network_sol_ps_inputs {
let get_network_sol_ps_result = get_block_template_rpc
.get_network_sol_ps(num_blocks_input, height_input)
.await;
assert!(
get_network_sol_ps_result
.is_ok(),
"get_network_sol_ps({num_blocks_input:?}, {height_input:?}) call with should be ok, got: {get_network_sol_ps_result:?}"
);
}
}

#[cfg(feature = "getblocktemplate-rpcs")]
#[tokio::test(flavor = "multi_thread")]
async fn rpc_getblocktemplate() {
Expand Down
13 changes: 13 additions & 0 deletions zebra-state/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,17 @@ pub enum ReadRequest {
/// [`zebra-state::GetBlockTemplateChainInfo`](zebra-state::GetBlockTemplateChainInfo)` structure containing
/// best chain state information.
ChainInfo,

#[cfg(feature = "getblocktemplate-rpcs")]
/// Get the average solution rate in the best chain.
///
/// Returns [`ReadResponse::SolutionRate`]
SolutionRate {
/// Specifies over difficulty averaging window.
num_blocks: usize,
/// Optionally estimate the network speed at the time when a certain block was found
height: Option<block::Height>,
},
}

impl ReadRequest {
Expand Down Expand Up @@ -806,6 +817,8 @@ impl ReadRequest {
ReadRequest::BestChainBlockHash(_) => "best_chain_block_hash",
#[cfg(feature = "getblocktemplate-rpcs")]
ReadRequest::ChainInfo => "chain_info",
#[cfg(feature = "getblocktemplate-rpcs")]
ReadRequest::SolutionRate { .. } => "solution_rate",
}
}

Expand Down
6 changes: 5 additions & 1 deletion zebra-state/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ pub enum ReadResponse {
/// Response to [`ReadRequest::ChainInfo`](crate::ReadRequest::ChainInfo) with the state
/// information needed by the `getblocktemplate` RPC method.
ChainInfo(GetBlockTemplateChainInfo),

#[cfg(feature = "getblocktemplate-rpcs")]
/// Response to [`ReadRequest::SolutionRate`](crate::ReadRequest::SolutionRate)
SolutionRate(Option<u128>),
}

#[cfg(feature = "getblocktemplate-rpcs")]
Expand Down Expand Up @@ -204,7 +208,7 @@ impl TryFrom<ReadResponse> for Response {
Err("there is no corresponding Response for this ReadResponse")
}
#[cfg(feature = "getblocktemplate-rpcs")]
ReadResponse::ChainInfo(_) => {
ReadResponse::ChainInfo(_) | ReadResponse::SolutionRate(_) => {
Err("there is no corresponding Response for this ReadResponse")
}
}
Expand Down
Loading