Skip to content

Commit fdd1a2a

Browse files
authored
Merge pull request #564 from 0xsiddharthks/siddharth/feat/height-of-hash
feat: add height_of_hash to Requester API
2 parents fb9834e + adaba07 commit fdd1a2a

5 files changed

Lines changed: 55 additions & 0 deletions

File tree

src/chain/graph.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,14 @@ impl BlockTree {
341341
self.headers.get(hash).map(|node| node.header)
342342
}
343343

344+
// Returns the height of `hash` only when it sits on the canonical chain of most work.
345+
// Returns `None` for unknown hashes and for hashes on stale/reorganized branches.
346+
pub(crate) fn height_of_hash_canonical_only(&self, hash: BlockHash) -> Option<Height> {
347+
let height = self.headers.get(&hash).map(|node| node.height)?;
348+
let canonical = self.block_hash_at_height(height)?;
349+
(canonical == hash).then_some(height)
350+
}
351+
344352
pub(crate) fn height_of_hash(&self, hash: BlockHash) -> Option<Height> {
345353
self.headers.get(&hash).map(|node| node.height)
346354
}
@@ -614,6 +622,20 @@ mod tests {
614622
assert_eq!(chain.header_at_height(10), Some(new_block_10));
615623
assert_eq!(chain.header_at_height(9), Some(base[1].0));
616624
assert_eq!(chain.header_at_height(8), Some(base[0].0));
625+
// height_of_hash returns the height for hashes on the canonical chain
626+
assert_eq!(chain.height_of_hash(new_block_10.block_hash()), Some(10));
627+
assert_eq!(chain.height_of_hash(block_11.block_hash()), Some(11));
628+
// ...and None for hashes that were reorganized away, even though the header
629+
// is still stored in the graph.
630+
assert_eq!(
631+
chain.height_of_hash_canonical_only(old_block_10.block_hash()),
632+
None
633+
);
634+
// Unknown hashes also return None.
635+
let unknown =
636+
BlockHash::from_str("0000000000000000000000000000000000000000000000000000000000000000")
637+
.unwrap();
638+
assert_eq!(chain.height_of_hash(unknown), None);
617639
}
618640

619641
#[test]

src/client.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,21 @@ impl Requester {
237237
rx.await.map_err(|_| ClientError::RecvError)
238238
}
239239

240+
/// Look up the height of a block hash in the locally synced chain of most work.
241+
/// Returns `None` if the hash is not in the chain of most work.
242+
///
243+
/// # Errors
244+
///
245+
/// If the node has stopped running.
246+
pub async fn height_of_hash(&self, hash: BlockHash) -> Result<Option<u32>, ClientError> {
247+
let (tx, rx) = tokio::sync::oneshot::channel::<Option<u32>>();
248+
let request = ClientRequest::new(hash, tx);
249+
self.ntx
250+
.send(ClientMessage::HeightOfHash(request))
251+
.map_err(|_| ClientError::SendError)?;
252+
rx.await.map_err(|_| ClientError::RecvError)
253+
}
254+
240255
/// Check if the node is running.
241256
pub fn is_running(&self) -> bool {
242257
self.ntx.send(ClientMessage::NoOp).is_ok()

src/messages.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ pub(crate) enum ClientMessage {
155155
GetPeerInfo(ClientRequest<(), Vec<(AddrV2, ServiceFlags)>>),
156156
/// Look up a header at a specific height in the chain of most work.
157157
GetHeader(ClientRequest<u32, Option<IndexedHeader>>),
158+
/// Look up the height of a block hash in the chain of most work.
159+
HeightOfHash(ClientRequest<BlockHash, Option<u32>>),
158160
/// Send an empty message to see if the node is running.
159161
NoOp,
160162
}

src/node.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,14 @@ impl Node {
264264
self.dialog.send_warning(Warning::ChannelDropped);
265265
};
266266
}
267+
ClientMessage::HeightOfHash(request) => {
268+
let (hash, oneshot) = request.into_values();
269+
let height =
270+
self.chain.header_chain.height_of_hash_canonical_only(hash);
271+
if oneshot.send(height).is_err() {
272+
self.dialog.send_warning(Warning::ChannelDropped);
273+
};
274+
}
267275
ClientMessage::NoOp => (),
268276
}
269277
}

tests/core.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,14 @@ async fn various_client_methods() {
267267
// get_header beyond the chain should return None
268268
let too_high = requester.get_header(cp.height + 1).await.unwrap();
269269
assert!(too_high.is_none());
270+
// height_of_hash for the chain tip should return the tip height
271+
let tip_height = requester.height_of_hash(cp.hash).await.unwrap();
272+
assert!(tip_height.is_some());
273+
assert_eq!(tip_height.unwrap(), cp.height);
274+
// height_of_hash for an unknown hash should return None
275+
let fake_hash: BlockHash = bitcoin::hashes::Hash::all_zeros();
276+
let unknown = requester.height_of_hash(fake_hash).await.unwrap();
277+
assert!(unknown.is_none());
270278
requester.shutdown().unwrap();
271279
rpc.stop().unwrap();
272280
}

0 commit comments

Comments
 (0)