From 14c266a2b54c9181a6e56c1b5bd1fbcf47af0b9d Mon Sep 17 00:00:00 2001 From: Siddharth Sharma Date: Thu, 9 Apr 2026 14:51:54 -0700 Subject: [PATCH 1/2] feat: add height_of_hash to Requester API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expose BlockTree::height_of_hash through the Requester channel. Given a block hash, returns its height in the chain of most work, or None if the hash is not in the chain. Zero-cost local HashMap lookup — no network fetch needed. --- src/client.rs | 15 +++++++++++++++ src/messages.rs | 2 ++ src/node.rs | 8 ++++++++ 3 files changed, 25 insertions(+) diff --git a/src/client.rs b/src/client.rs index 4bf37165..d1fcd4c3 100644 --- a/src/client.rs +++ b/src/client.rs @@ -221,6 +221,21 @@ impl Requester { rx.await.map_err(|_| ClientError::RecvError) } + /// Look up the height of a block hash in the locally synced chain of most work. + /// Returns `None` if the hash is not in the header chain. + /// + /// # Errors + /// + /// If the node has stopped running. + pub async fn height_of_hash(&self, hash: BlockHash) -> Result, ClientError> { + let (tx, rx) = tokio::sync::oneshot::channel::>(); + let request = ClientRequest::new(hash, tx); + self.ntx + .send(ClientMessage::HeightOfHash(request)) + .map_err(|_| ClientError::SendError)?; + rx.await.map_err(|_| ClientError::RecvError) + } + /// Check if the node is running. pub fn is_running(&self) -> bool { self.ntx.send(ClientMessage::NoOp).is_ok() diff --git a/src/messages.rs b/src/messages.rs index 7021c859..3f5caa31 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -153,6 +153,8 @@ pub(crate) enum ClientMessage { GetBroadcastMinFeeRate(ClientRequest<(), FeeRate>), /// Get info on connections GetPeerInfo(ClientRequest<(), Vec<(AddrV2, ServiceFlags)>>), + /// Look up the height of a block hash in the chain of most work. + HeightOfHash(ClientRequest>), /// Send an empty message to see if the node is running. NoOp, } diff --git a/src/node.rs b/src/node.rs index 94bd4293..493a2995 100644 --- a/src/node.rs +++ b/src/node.rs @@ -268,6 +268,14 @@ impl Node { self.dialog.send_warning(Warning::ChannelDropped); }; } + ClientMessage::HeightOfHash(request) => { + let (hash, oneshot) = request.into_values(); + let height = + self.chain.header_chain.height_of_hash(hash); + if oneshot.send(height).is_err() { + self.dialog.send_warning(Warning::ChannelDropped); + }; + } ClientMessage::NoOp => (), } } From 1c12d00febf8462077e7c6b072830d7da230aae3 Mon Sep 17 00:00:00 2001 From: Siddharth Sharma Date: Thu, 9 Apr 2026 15:16:20 -0700 Subject: [PATCH 2/2] test: add integration tests for height_of_hash Extend various_client_methods to verify height_of_hash returns the correct height for the chain tip hash and returns None for an unknown block hash. --- tests/core.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/core.rs b/tests/core.rs index 5d62e984..5f5ba30b 100644 --- a/tests/core.rs +++ b/tests/core.rs @@ -253,6 +253,14 @@ async fn various_client_methods() { let peers = requester.peer_info().await.unwrap(); assert_eq!(peers.len(), 1); assert!(requester.is_running()); + // height_of_hash for the chain tip should return the tip height + let tip_height = requester.height_of_hash(cp.hash).await.unwrap(); + assert!(tip_height.is_some()); + assert_eq!(tip_height.unwrap(), cp.height); + // height_of_hash for an unknown hash should return None + let fake_hash: BlockHash = bitcoin::hashes::Hash::all_zeros(); + let unknown = requester.height_of_hash(fake_hash).await.unwrap(); + assert!(unknown.is_none()); requester.shutdown().unwrap(); rpc.stop().unwrap(); }