diff --git a/src/client.rs b/src/client.rs index 1e277b5..f201508 100644 --- a/src/client.rs +++ b/src/client.rs @@ -247,6 +247,30 @@ impl Requester { rx.await.map_err(|_| ClientError::RecvError) } + /// Look up a header by block hash in the locally synced header chain. Unlike + /// [`Self::get_header`], this method returns the header even when the block + /// has been demoted from the chain of most work by a reorg, so callers that + /// receive a [`BlockHash`] from an [`Event`](crate::Event) (e.g. an + /// [`IndexedFilter`](crate::IndexedFilter)) can always resolve the exact + /// header the event referred to. + /// + /// Returns `None` if the hash is not known to the local header chain. + /// + /// # Errors + /// + /// If the node has stopped running. + pub async fn get_header_by_hash( + &self, + hash: BlockHash, + ) -> Result, ClientError> { + let (tx, rx) = tokio::sync::oneshot::channel::>(); + let request = ClientRequest::new(hash, tx); + self.ntx + .send(ClientMessage::GetHeaderByHash(request)) + .map_err(|_| ClientError::SendError)?; + 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 chain of most work. /// diff --git a/src/messages.rs b/src/messages.rs index af9e291..0c3300b 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -153,6 +153,9 @@ pub(crate) enum ClientMessage { GetPeerInfo(ClientRequest<(), Vec<(AddrV2, ServiceFlags)>>), /// Look up a header at a specific height in the chain of most work. GetHeader(ClientRequest>), + /// Look up a header by its block hash. Returns headers for stale/reorganized + /// branches as well as the canonical chain. + GetHeaderByHash(ClientRequest>), /// 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. diff --git a/src/node.rs b/src/node.rs index ea35773..5359015 100644 --- a/src/node.rs +++ b/src/node.rs @@ -272,6 +272,22 @@ impl Node { self.dialog.send_warning(Warning::ChannelDropped); }; } + ClientMessage::GetHeaderByHash(request) => { + let (hash, oneshot) = request.into_values(); + let indexed = self + .chain + .header_chain + .header_at_hash(hash) + .and_then(|header| { + self.chain + .header_chain + .height_of_hash(hash) + .map(|height| IndexedHeader::new(height, header)) + }); + if oneshot.send(indexed).is_err() { + self.dialog.send_warning(Warning::ChannelDropped); + }; + } ClientMessage::NoOp => (), } }