Skip to content

Commit 801c309

Browse files
committed
feat(rpc): getmininginfo populates networkhashps from tip difficulty
Op: extend
1 parent da0a334 commit 801c309

1 file changed

Lines changed: 51 additions & 1 deletion

File tree

crates/rpc/src/handlers/mining.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use crate::context::Context;
77
use crate::error::RpcError;
88
use crate::handlers::{ensure_no_params, params_array, required_str};
99

10+
const NETWORK_HASHPS_WINDOW: u32 = 120;
11+
1012
pub(crate) fn getblocktemplate(ctx: &Arc<Context>, params: &Value) -> Result<Value, RpcError> {
1113
if !params.is_null() {
1214
let request = params_array(params)?.first();
@@ -72,13 +74,50 @@ pub(crate) fn getmininginfo(ctx: &Arc<Context>, params: &Value) -> Result<Value,
7274
"currentblockweight": 0_u64,
7375
"currentblocktx": 0_u64,
7476
"difficulty": difficulty,
75-
"networkhashps": 0.0_f64,
77+
"networkhashps": estimate_network_hashps(ctx),
7678
"pooledtx": pooledtx,
7779
"chain": chain,
7880
"warnings": ""
7981
}))
8082
}
8183

84+
fn estimate_network_hashps(ctx: &Context) -> f64 {
85+
let tree = ctx.block_tree.read();
86+
let Some(tip_snapshot) = ctx.applied_tip.load_full() else {
87+
return 0.0;
88+
};
89+
let tip_id = tip_snapshot.tip_id;
90+
let Ok(tip_node) = tree.node(tip_id) else {
91+
return 0.0;
92+
};
93+
let target_height = tip_node.height.saturating_sub(NETWORK_HASHPS_WINDOW);
94+
let Some(earliest_id) = tree.node_at_height_from(tip_id, target_height) else {
95+
return 0.0;
96+
};
97+
let Ok(earliest_node) = tree.node(earliest_id) else {
98+
return 0.0;
99+
};
100+
if earliest_node.height == tip_node.height {
101+
return 0.0;
102+
}
103+
104+
let work_delta = tip_node.chainwork.saturating_sub(earliest_node.chainwork);
105+
let time_delta_secs =
106+
i64::from(tip_node.header.time).saturating_sub(i64::from(earliest_node.header.time));
107+
if time_delta_secs <= 0 {
108+
return 0.0;
109+
}
110+
111+
chainwork_to_f64(work_delta) / f64::from(u32::try_from(time_delta_secs).unwrap_or(u32::MAX))
112+
}
113+
114+
fn chainwork_to_f64(work: bitcoin_rs_chain::ChainWork) -> f64 {
115+
let bytes: [u8; 32] = work.to_be_bytes();
116+
bytes
117+
.iter()
118+
.fold(0.0_f64, |acc, &byte| acc.mul_add(256.0, f64::from(byte)))
119+
}
120+
82121
pub(crate) fn submitblock(_ctx: &Arc<Context>, params: &Value) -> Result<Value, RpcError> {
83122
use bitcoin::consensus::encode::deserialize;
84123
use bitcoin::hex::FromHex as _;
@@ -175,4 +214,15 @@ mod getmininginfo_tests {
175214
};
176215
assert_eq!(pooledtx, 0);
177216
}
217+
218+
#[test]
219+
fn getmininginfo_networkhashps_zero_when_no_applied_tip() {
220+
let ctx = Arc::new(Context::new());
221+
let result = getmininginfo(&ctx, &json!([]))
222+
.unwrap_or_else(|err| panic!("getmininginfo failed: {err}"));
223+
let Some(rate) = result.get("networkhashps").and_then(JsonValueTrait::as_f64) else {
224+
panic!("networkhashps missing: {result:?}");
225+
};
226+
assert!(rate.abs() < f64::EPSILON, "expected zero, got {rate}");
227+
}
178228
}

0 commit comments

Comments
 (0)