Skip to content

Commit 755925d

Browse files
committed
Add the /headers[/:from_hash[/:count]] endpoint
Add a new endpoint to download headers in bulk, up to 2000 with a single request. Headers are returned in binary form, with the VarInt-encoded number of items in the body preceeding them. By default `from_hash` is the current best hash, but a different starting block can be specified. The returned list goes "backwards" returning the `count - 1` blocks *before* `from_hash` plus the header of `from_hash` itself. This allows caching the response indefinitely, since it's guaranteed that the headers that come before a given block will never change. Returns an error if `from_hash` is not a valid block or it isn't found in the blockchain. If `count` is greater than the limit of `2000` it will be silently capped to said value.
1 parent abfbce7 commit 755925d

File tree

2 files changed

+61
-0
lines changed

2 files changed

+61
-0
lines changed

src/new_index/schema.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,33 @@ impl ChainQuery {
424424
}
425425
}
426426

427+
pub fn get_headers(&self, from: &BlockHash, count: usize) -> Option<Vec<BlockHeader>> {
428+
let _timer = self.start_timer("get_headers");
429+
430+
if let Some(from_header) = self
431+
.store
432+
.indexed_headers
433+
.read()
434+
.unwrap()
435+
.header_by_blockhash(from)
436+
{
437+
Some(
438+
self.store
439+
.indexed_headers
440+
.read()
441+
.unwrap()
442+
.iter()
443+
.rev()
444+
.skip(self.best_height() - from_header.height())
445+
.take(count)
446+
.map(|e| e.header().clone())
447+
.collect(),
448+
)
449+
} else {
450+
None
451+
}
452+
}
453+
427454
pub fn get_block_header(&self, hash: &BlockHash) -> Option<BlockHeader> {
428455
let _timer = self.start_timer("get_block_header");
429456
Some(self.header_by_hash(hash)?.header().clone())

src/rest.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,40 @@ fn handle_request(
627627
path.get(3),
628628
path.get(4),
629629
) {
630+
(&Method::GET, Some(&"headers"), hash, count, None, None) => {
631+
let count = count
632+
.and_then(|c| c.parse::<usize>().ok())
633+
.and_then(|c| match c {
634+
0 => Some(1),
635+
1..=2000 => Some(c),
636+
_ => None,
637+
})
638+
.unwrap_or(2000);
639+
let from_hash = hash
640+
.map(|h| BlockHash::from_hex(h))
641+
.transpose()?
642+
.unwrap_or_else(|| query.chain().best_hash());
643+
644+
let headers = match query.chain().get_headers(&from_hash, count) {
645+
Some(headers) => headers,
646+
None => return Err(HttpError::not_found("Block not found".to_string())),
647+
};
648+
649+
let mut raw = Vec::with_capacity(8 + 80 * headers.len());
650+
raw.append(&mut encode::serialize(
651+
&encode::VarInt(headers.len() as u64),
652+
));
653+
for header in headers.iter() {
654+
raw.append(&mut encode::serialize(header));
655+
}
656+
657+
Ok(Response::builder()
658+
.status(StatusCode::OK)
659+
.header("Content-Type", "application/octet-stream")
660+
.header("Cache-Control", format!("public, max-age={:}", TTL_LONG))
661+
.body(Body::from(raw))
662+
.unwrap())
663+
}
630664
(&Method::GET, Some(&"blocks"), Some(&"tip"), Some(&"hash"), None, None) => http_message(
631665
StatusCode::OK,
632666
query.chain().best_hash().to_hex(),

0 commit comments

Comments
 (0)