Skip to content

Commit

Permalink
change(rpc): Adds getmininginfo, getnetworksolps and `getnetworkh…
Browse files Browse the repository at this point in the history
…ashps` methods (#5808)

* adds type and stub

* adds:
- SolutionRate state request

- getnetworksolps, getnetworkhashps, & getmininginfo RPCs

- vectors tests

* adds snapshot tests

updates ReadRequest::SolutionRate doc link

* removes random slash in doc comment

moves snapshot tests up where it can use the populated state service

* adds snapshots

* updates doc comments

* applies `num_blocks` default in RPC instead of `solution_rate`

* adds # Correctness comment

* Add testnet field to getmininginfo response

* use PartialCumulativeWork instead of u128

* document why `solution_rate` takes an extra block

* add comment explaining why the work for the last block in the iterator is not added to `total_work`

* use `as_u128` method instead of deref for PartialCumulativeWork

* Updates `chain` field of getmininginfo response

* Updates snapshots

Adds "arbitrary_precision" feature to serde_json in zebra-rpc
  • Loading branch information
arya2 authored Dec 8, 2022
1 parent accc8cc commit 77b85cf
Show file tree
Hide file tree
Showing 15 changed files with 404 additions and 6 deletions.
7 changes: 7 additions & 0 deletions zebra-chain/src/work/difficulty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,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"] }
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 @@ -52,6 +52,11 @@ pub 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 @@ -132,6 +137,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 @@ -531,6 +568,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),
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 @@ -785,6 +785,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

0 comments on commit 77b85cf

Please sign in to comment.