Skip to content

Commit bd59444

Browse files
authored
Merge pull request #563 from 0xsiddharthks/siddharth/feat/get-header
feat: add get_header(height) to Requester API
2 parents 511e94a + 4ce805f commit bd59444

File tree

4 files changed

+45
-2
lines changed

4 files changed

+45
-2
lines changed

src/client.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use tokio::sync::mpsc::UnboundedSender;
77
use tokio::sync::oneshot;
88

99
use crate::chain::block_subsidy;
10+
use crate::chain::IndexedHeader;
1011
use crate::messages::ClientRequest;
1112
use crate::{Event, HeaderCheckpoint, Info, TrustedPeer, Warning};
1213

@@ -221,6 +222,21 @@ impl Requester {
221222
rx.await.map_err(|_| ClientError::RecvError)
222223
}
223224

225+
/// Look up a header at a specific height in the locally synced chain of most work.
226+
/// Returns `None` if the height is not in the header chain.
227+
///
228+
/// # Errors
229+
///
230+
/// If the node has stopped running.
231+
pub async fn get_header(&self, height: u32) -> Result<Option<IndexedHeader>, ClientError> {
232+
let (tx, rx) = tokio::sync::oneshot::channel::<Option<IndexedHeader>>();
233+
let request = ClientRequest::new(height, tx);
234+
self.ntx
235+
.send(ClientMessage::GetHeader(request))
236+
.map_err(|_| ClientError::SendError)?;
237+
rx.await.map_err(|_| ClientError::RecvError)
238+
}
239+
224240
/// Check if the node is running.
225241
pub fn is_running(&self) -> bool {
226242
self.ntx.send(ClientMessage::NoOp).is_ok()

src/messages.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use bitcoin::{
77
block::Header, p2p::message_network::RejectReason, BlockHash, FeeRate, Transaction, Wtxid,
88
};
99

10-
use crate::chain::BlockHeaderChanges;
10+
use crate::chain::{BlockHeaderChanges, IndexedHeader};
1111
use crate::IndexedFilter;
1212
use crate::{chain::checkpoints::HeaderCheckpoint, IndexedBlock, TrustedPeer};
1313

@@ -153,6 +153,8 @@ pub(crate) enum ClientMessage {
153153
GetBroadcastMinFeeRate(ClientRequest<(), FeeRate>),
154154
/// Get info on connections
155155
GetPeerInfo(ClientRequest<(), Vec<(AddrV2, ServiceFlags)>>),
156+
/// Look up a header at a specific height in the chain of most work.
157+
GetHeader(ClientRequest<u32, Option<IndexedHeader>>),
156158
/// Send an empty message to see if the node is running.
157159
NoOp,
158160
}

src/node.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use crate::{
2929
chain::Chain,
3030
checkpoints::HeaderCheckpoint,
3131
error::HeaderSyncError,
32-
CFHeaderChanges, ChainState, FilterCheck, HeightMonitor,
32+
CFHeaderChanges, ChainState, FilterCheck, HeightMonitor, IndexedHeader,
3333
},
3434
error::FetchBlockError,
3535
messages::ClientRequest,
@@ -268,6 +268,17 @@ impl Node {
268268
self.dialog.send_warning(Warning::ChannelDropped);
269269
};
270270
}
271+
ClientMessage::GetHeader(request) => {
272+
let (height, oneshot) = request.into_values();
273+
let header = self
274+
.chain
275+
.header_chain
276+
.header_at_height(height)
277+
.map(|h| IndexedHeader::new(height, h));
278+
if oneshot.send(header).is_err() {
279+
self.dialog.send_warning(Warning::ChannelDropped);
280+
};
281+
}
271282
ClientMessage::NoOp => (),
272283
}
273284
}

tests/core.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,20 @@ async fn various_client_methods() {
253253
let peers = requester.peer_info().await.unwrap();
254254
assert_eq!(peers.len(), 1);
255255
assert!(requester.is_running());
256+
// get_header should return a header within the synced range
257+
let header_at_1 = requester.get_header(1).await.unwrap();
258+
assert!(header_at_1.is_some());
259+
let header_at_1 = header_at_1.unwrap();
260+
assert_eq!(header_at_1.height, 1);
261+
// get_header at the tip should match the chain tip hash
262+
let tip_header = requester.get_header(cp.height).await.unwrap();
263+
assert!(tip_header.is_some());
264+
let tip_header = tip_header.unwrap();
265+
assert_eq!(tip_header.height, cp.height);
266+
assert_eq!(tip_header.block_hash(), cp.hash);
267+
// get_header beyond the chain should return None
268+
let too_high = requester.get_header(cp.height + 1).await.unwrap();
269+
assert!(too_high.is_none());
256270
requester.shutdown().unwrap();
257271
rpc.stop().unwrap();
258272
}

0 commit comments

Comments
 (0)