Skip to content

Commit ffe1b3a

Browse files
committed
feat(rpc): add verifychain handler walking N blocks for PoW self-consistency
Op: extend
1 parent 79d6dff commit ffe1b3a

2 files changed

Lines changed: 56 additions & 0 deletions

File tree

crates/rpc/src/handlers.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ impl Handler {
3939
"getblock" => chain::getblock(&self.ctx, params),
4040
"getblockheader" => chain::getblockheader(&self.ctx, params),
4141
"getblockstats" => chain::getblockstats(&self.ctx, params),
42+
"verifychain" => chain::verifychain(&self.ctx, params),
4243
"gettxoutsetinfo" => chain::gettxoutsetinfo(&self.ctx, params),
4344
"getblockfilter" => chain::getblockfilter(&self.ctx, params),
4445
"getindexinfo" => chain::getindexinfo(&self.ctx, params),

crates/rpc/src/handlers/chain.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,38 @@ pub(crate) fn getblockstats(ctx: &Arc<Context>, params: &Value) -> Result<Value,
213213
"utxo_size_inc": 0
214214
}))
215215
}
216+
pub(crate) fn verifychain(ctx: &Arc<Context>, params: &Value) -> Result<Value, RpcError> {
217+
let array = params_array(params)?;
218+
let _checklevel = array.first().and_then(JsonValueTrait::as_u64).unwrap_or(3);
219+
let nblocks_param = array.get(1).and_then(JsonValueTrait::as_u64).unwrap_or(6);
220+
let Ok(nblocks) = u32::try_from(nblocks_param) else {
221+
return Err(RpcError::InvalidParams("nblocks exceeds u32"));
222+
};
223+
let tree = ctx.block_tree.read();
224+
let Some(applied) = ctx.applied_tip.load_full() else {
225+
// No applied tip yet; trivially passes.
226+
return Ok(json!(true));
227+
};
228+
let mut cursor = applied.tip_id;
229+
let mut checked: u32 = 0;
230+
loop {
231+
if checked >= nblocks {
232+
break;
233+
}
234+
let Ok(node) = tree.node(cursor) else {
235+
return Ok(json!(false));
236+
};
237+
if node.header.validate_pow(node.header.target()).is_err() {
238+
return Ok(json!(false));
239+
}
240+
checked = checked.saturating_add(1);
241+
let Some(parent_id) = node.parent else {
242+
break;
243+
};
244+
cursor = parent_id;
245+
}
246+
Ok(json!(true))
247+
}
216248

217249
pub(crate) fn gettxoutsetinfo(ctx: &Arc<Context>, params: &Value) -> Result<Value, RpcError> {
218250
ensure_no_params(params)?;
@@ -766,6 +798,29 @@ mod getchaintips_tests {
766798
}
767799
}
768800

801+
#[cfg(test)]
802+
mod verifychain_tests {
803+
use alloc::sync::Arc;
804+
805+
use super::*;
806+
807+
#[test]
808+
fn verifychain_returns_true_on_empty_chain() {
809+
let ctx = Arc::new(Context::new());
810+
let result =
811+
verifychain(&ctx, &json!([])).unwrap_or_else(|err| panic!("verifychain failed: {err}"));
812+
assert_eq!(result.as_bool(), Some(true));
813+
}
814+
815+
#[test]
816+
fn verifychain_accepts_default_params() {
817+
let ctx = Arc::new(Context::new());
818+
let result = verifychain(&ctx, &json!([3, 6]))
819+
.unwrap_or_else(|err| panic!("verifychain failed: {err}"));
820+
assert_eq!(result.as_bool(), Some(true));
821+
}
822+
}
823+
769824
fn compute_branchlen(
770825
tree: &bitcoin_rs_chain::BlockTree,
771826
leaf_id: bitcoin_rs_chain::NodeId,

0 commit comments

Comments
 (0)