change(rpc): Adds `getmininginfo`, `getnetworksolps` and `getnetworkhashps` 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
This commit is contained in:
parent
accc8ccbe5
commit
77b85cf767
|
|
@ -503,6 +503,13 @@ impl std::ops::Add for Work {
|
||||||
/// Partial work used to track relative work in non-finalized chains
|
/// Partial work used to track relative work in non-finalized chains
|
||||||
pub struct PartialCumulativeWork(u128);
|
pub struct PartialCumulativeWork(u128);
|
||||||
|
|
||||||
|
impl PartialCumulativeWork {
|
||||||
|
/// Return the inner `u128` value.
|
||||||
|
pub fn as_u128(self) -> u128 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Work> for PartialCumulativeWork {
|
impl From<Work> for PartialCumulativeWork {
|
||||||
fn from(work: Work) -> Self {
|
fn from(work: Work) -> Self {
|
||||||
PartialCumulativeWork(work.0)
|
PartialCumulativeWork(work.0)
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ jsonrpc-http-server = "18.0.0"
|
||||||
num_cpus = "1.14.0"
|
num_cpus = "1.14.0"
|
||||||
|
|
||||||
# zebra-rpc needs the preserve_order feature in serde_json, which is a dependency of jsonrpc-core
|
# 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"] }
|
indexmap = { version = "1.9.2", features = ["serde"] }
|
||||||
|
|
||||||
tokio = { version = "1.23.0", features = ["time", "rt-multi-thread", "macros", "tracing"] }
|
tokio = { version = "1.23.0", features = ["time", "rt-multi-thread", "macros", "tracing"] }
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,11 @@ pub mod zip317;
|
||||||
/// > and clock time varies between nodes.
|
/// > and clock time varies between nodes.
|
||||||
const MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP: i32 = 100;
|
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.
|
/// 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:
|
/// `s-nomp` mining pool expects error code `-10` when the node is not synced:
|
||||||
|
|
@ -132,6 +137,38 @@ pub trait GetBlockTemplateRpc {
|
||||||
hex_data: HexData,
|
hex_data: HexData,
|
||||||
_options: Option<submit_block::JsonParameters>,
|
_options: Option<submit_block::JsonParameters>,
|
||||||
) -> BoxFuture<Result<submit_block::Response>>;
|
) -> 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.
|
/// RPC method implementations.
|
||||||
|
|
@ -531,6 +568,56 @@ where
|
||||||
}
|
}
|
||||||
.boxed()
|
.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
|
// get_block_template support methods
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
pub mod default_roots;
|
pub mod default_roots;
|
||||||
pub mod get_block_template;
|
pub mod get_block_template;
|
||||||
pub mod get_block_template_opts;
|
pub mod get_block_template_opts;
|
||||||
|
pub mod get_mining_info;
|
||||||
pub mod hex_data;
|
pub mod hex_data;
|
||||||
pub mod submit_block;
|
pub mod submit_block;
|
||||||
pub mod transaction;
|
pub mod transaction;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -29,7 +29,9 @@ use zebra_test::mock_service::{MockService, PanicAssertion};
|
||||||
use crate::methods::{
|
use crate::methods::{
|
||||||
get_block_template_rpcs::{
|
get_block_template_rpcs::{
|
||||||
self,
|
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,
|
tests::utils::fake_history_tree,
|
||||||
GetBlockHash, GetBlockTemplateRpc, GetBlockTemplateRpcImpl,
|
GetBlockHash, GetBlockTemplateRpc, GetBlockTemplateRpcImpl,
|
||||||
|
|
@ -127,9 +129,22 @@ pub async fn test_responses<State, ReadState>(
|
||||||
.get_block_hash(BLOCK_HEIGHT10)
|
.get_block_hash(BLOCK_HEIGHT10)
|
||||||
.await
|
.await
|
||||||
.expect("We should have a GetBlockHash struct");
|
.expect("We should have a GetBlockHash struct");
|
||||||
|
|
||||||
snapshot_rpc_getblockhash(get_block_hash, &settings);
|
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
|
// get a new empty state
|
||||||
let new_read_state = MockService::build().for_unit_tests();
|
let new_read_state = MockService::build().for_unit_tests();
|
||||||
|
|
||||||
|
|
@ -225,3 +240,16 @@ fn snapshot_rpc_submit_block_invalid(
|
||||||
insta::assert_json_snapshot!("snapshot_rpc_submit_block_invalid", submit_block_response)
|
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));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
|
||||||
|
expression: get_network_sol_ps
|
||||||
|
---
|
||||||
|
2
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
|
||||||
|
expression: get_network_sol_ps
|
||||||
|
---
|
||||||
|
0
|
||||||
|
|
@ -785,6 +785,95 @@ async fn rpc_getblockhash() {
|
||||||
mempool.expect_no_requests().await;
|
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")]
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn rpc_getblocktemplate() {
|
async fn rpc_getblocktemplate() {
|
||||||
|
|
|
||||||
|
|
@ -779,6 +779,17 @@ pub enum ReadRequest {
|
||||||
/// [`zebra-state::GetBlockTemplateChainInfo`](zebra-state::GetBlockTemplateChainInfo)` structure containing
|
/// [`zebra-state::GetBlockTemplateChainInfo`](zebra-state::GetBlockTemplateChainInfo)` structure containing
|
||||||
/// best chain state information.
|
/// best chain state information.
|
||||||
ChainInfo,
|
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 {
|
impl ReadRequest {
|
||||||
|
|
@ -806,6 +817,8 @@ impl ReadRequest {
|
||||||
ReadRequest::BestChainBlockHash(_) => "best_chain_block_hash",
|
ReadRequest::BestChainBlockHash(_) => "best_chain_block_hash",
|
||||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
ReadRequest::ChainInfo => "chain_info",
|
ReadRequest::ChainInfo => "chain_info",
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
ReadRequest::SolutionRate { .. } => "solution_rate",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,10 @@ pub enum ReadResponse {
|
||||||
/// Response to [`ReadRequest::ChainInfo`](crate::ReadRequest::ChainInfo) with the state
|
/// Response to [`ReadRequest::ChainInfo`](crate::ReadRequest::ChainInfo) with the state
|
||||||
/// information needed by the `getblocktemplate` RPC method.
|
/// information needed by the `getblocktemplate` RPC method.
|
||||||
ChainInfo(GetBlockTemplateChainInfo),
|
ChainInfo(GetBlockTemplateChainInfo),
|
||||||
|
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
/// Response to [`ReadRequest::SolutionRate`](crate::ReadRequest::SolutionRate)
|
||||||
|
SolutionRate(Option<u128>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
|
@ -204,7 +208,7 @@ impl TryFrom<ReadResponse> for Response {
|
||||||
Err("there is no corresponding Response for this ReadResponse")
|
Err("there is no corresponding Response for this ReadResponse")
|
||||||
}
|
}
|
||||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
ReadResponse::ChainInfo(_) => {
|
ReadResponse::ChainInfo(_) | ReadResponse::SolutionRate(_) => {
|
||||||
Err("there is no corresponding Response for this ReadResponse")
|
Err("there is no corresponding Response for this ReadResponse")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1622,6 +1622,62 @@ impl Service<ReadRequest> for ReadStateService {
|
||||||
.map(|join_result| join_result.expect("panic in ReadRequest::ChainInfo"))
|
.map(|join_result| join_result.expect("panic in ReadRequest::ChainInfo"))
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used by getmininginfo, getnetworksolps, and getnetworkhashps RPCs.
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
ReadRequest::SolutionRate { num_blocks, height } => {
|
||||||
|
let timer = CodeTimer::start();
|
||||||
|
|
||||||
|
let state = self.clone();
|
||||||
|
|
||||||
|
// # Performance
|
||||||
|
//
|
||||||
|
// Allow other async tasks to make progress while concurrently reading blocks from disk.
|
||||||
|
let span = Span::current();
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
span.in_scope(move || {
|
||||||
|
let latest_non_finalized_state = state.latest_non_finalized_state();
|
||||||
|
// # Correctness
|
||||||
|
//
|
||||||
|
// It is ok to do these lookups using multiple database calls. Finalized state updates
|
||||||
|
// can only add overlapping blocks, and block hashes are unique across all chain forks.
|
||||||
|
//
|
||||||
|
// The worst that can happen here is that the default `start_hash` will be below
|
||||||
|
// the chain tip.
|
||||||
|
let (tip_height, tip_hash) =
|
||||||
|
match read::tip(latest_non_finalized_state.best_chain(), &state.db) {
|
||||||
|
Some(tip_hash) => tip_hash,
|
||||||
|
None => return Ok(ReadResponse::SolutionRate(None)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let start_hash = match height {
|
||||||
|
Some(height) if height < tip_height => read::hash_by_height(
|
||||||
|
latest_non_finalized_state.best_chain(),
|
||||||
|
&state.db,
|
||||||
|
height,
|
||||||
|
),
|
||||||
|
// use the chain tip hash if height is above it or not provided.
|
||||||
|
_ => Some(tip_hash),
|
||||||
|
};
|
||||||
|
|
||||||
|
let solution_rate = start_hash.and_then(|start_hash| {
|
||||||
|
read::difficulty::solution_rate(
|
||||||
|
&latest_non_finalized_state,
|
||||||
|
&state.db,
|
||||||
|
num_blocks,
|
||||||
|
start_hash,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
// The work is done in the future.
|
||||||
|
timer.finish(module_path!(), line!(), "ReadRequest::ChainInfo");
|
||||||
|
|
||||||
|
Ok(ReadResponse::SolutionRate(solution_rate))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.map(|join_result| join_result.expect("panic in ReadRequest::ChainInfo"))
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ use std::sync::Arc;
|
||||||
use chrono::{DateTime, Duration, TimeZone, Utc};
|
use chrono::{DateTime, Duration, TimeZone, Utc};
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
block::{self, Block, Height},
|
block::{self, Block, Hash, Height},
|
||||||
history_tree::HistoryTree,
|
history_tree::HistoryTree,
|
||||||
parameters::{Network, NetworkUpgrade, POST_BLOSSOM_POW_TARGET_SPACING},
|
parameters::{Network, NetworkUpgrade, POST_BLOSSOM_POW_TARGET_SPACING},
|
||||||
work::difficulty::CompactDifficulty,
|
work::difficulty::{CompactDifficulty, PartialCumulativeWork},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -63,6 +63,58 @@ pub fn get_block_template_chain_info(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Accepts a `non_finalized_state`, [`ZebraDb`], `num_blocks`, and a block hash to start at.
|
||||||
|
///
|
||||||
|
/// Iterates over up to the last `num_blocks` blocks, summing up their total work.
|
||||||
|
/// Divides that total by the number of seconds between the timestamp of the
|
||||||
|
/// first block in the iteration and 1 block below the last block.
|
||||||
|
///
|
||||||
|
/// Returns the solution rate per second for the current best chain, or `None` if
|
||||||
|
/// the `start_hash` and at least 1 block below it are not found in the chain.
|
||||||
|
pub fn solution_rate(
|
||||||
|
non_finalized_state: &NonFinalizedState,
|
||||||
|
db: &ZebraDb,
|
||||||
|
num_blocks: usize,
|
||||||
|
start_hash: Hash,
|
||||||
|
) -> Option<u128> {
|
||||||
|
// Take 1 extra block for calculating the number of seconds between when mining on the first block likely started.
|
||||||
|
// The work for the last block in this iterator is not added to `total_work`.
|
||||||
|
let mut block_iter = any_ancestor_blocks(non_finalized_state, db, start_hash)
|
||||||
|
.take(num_blocks.checked_add(1).unwrap_or(num_blocks))
|
||||||
|
.peekable();
|
||||||
|
|
||||||
|
let get_work = |block: Arc<Block>| {
|
||||||
|
block
|
||||||
|
.header
|
||||||
|
.difficulty_threshold
|
||||||
|
.to_work()
|
||||||
|
.expect("work has already been validated")
|
||||||
|
};
|
||||||
|
|
||||||
|
let block = block_iter.next()?;
|
||||||
|
let last_block_time = block.header.time;
|
||||||
|
|
||||||
|
let mut total_work: PartialCumulativeWork = get_work(block).into();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Return `None` if the iterator doesn't yield a second item.
|
||||||
|
let block = block_iter.next()?;
|
||||||
|
|
||||||
|
if block_iter.peek().is_some() {
|
||||||
|
// Add the block's work to `total_work` if it's not the last item in the iterator.
|
||||||
|
// The last item in the iterator is only used to estimate when mining on the first block
|
||||||
|
// in the window of `num_blocks` likely started.
|
||||||
|
total_work += get_work(block);
|
||||||
|
} else {
|
||||||
|
let first_block_time = block.header.time;
|
||||||
|
let duration_between_first_and_last_block = last_block_time - first_block_time;
|
||||||
|
return Some(
|
||||||
|
total_work.as_u128() / duration_between_first_and_last_block.num_seconds() as u128,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Do a consistency check by checking the finalized tip before and after all other database queries.
|
/// Do a consistency check by checking the finalized tip before and after all other database queries.
|
||||||
/// Returns and error if the tip obtained before and after is not the same.
|
/// Returns and error if the tip obtained before and after is not the same.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue