Skip to content

Commit

Permalink
Add the /headers[/:from_hash[/:count]] endpoint
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
afilini committed Dec 10, 2021
1 parent abfbce7 commit 018c094
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 0 deletions.
19 changes: 19 additions & 0 deletions src/new_index/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,25 @@ impl ChainQuery {
}
}

pub fn get_headers(&self, from: &BlockHash, count: usize) -> Option<Vec<BlockHeader>> {
let _timer = self.start_timer("get_headers");
let store = self.store.indexed_headers.read().unwrap();

if let Some(from_header) = store.header_by_blockhash(from) {
Some(
store
.iter()
.rev()
.skip(self.best_height() - from_header.height())
.take(count)
.map(|e| e.header().clone())
.collect(),
)
} else {
None
}
}

pub fn get_block_header(&self, hash: &BlockHash) -> Option<BlockHeader> {
let _timer = self.start_timer("get_block_header");
Some(self.header_by_hash(hash)?.header().clone())
Expand Down
30 changes: 30 additions & 0 deletions src/rest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,36 @@ fn handle_request(
path.get(3),
path.get(4),
) {
(&Method::GET, Some(&"headers"), hash, count, None, None) => {
let count = count
.and_then(|c| c.parse::<usize>().ok())
.map(|c| c.max(1).min(2000))
.unwrap_or(2000);
let from_hash = hash
.map(|h| BlockHash::from_hex(h))
.transpose()?
.unwrap_or_else(|| query.chain().best_hash());

let headers = match query.chain().get_headers(&from_hash, count) {
Some(headers) => headers,
None => return Err(HttpError::not_found("Block not found".to_string())),
};

let mut raw = Vec::with_capacity(8 + 80 * headers.len());
raw.append(&mut encode::serialize(
&encode::VarInt(headers.len() as u64),
));
for header in headers.into_iter() {
raw.append(&mut encode::serialize(&header));
}

Ok(Response::builder()
.status(StatusCode::OK)
.header("Content-Type", "application/octet-stream")
.header("Cache-Control", format!("public, max-age={:}", TTL_SHORT))
.body(Body::from(raw))
.unwrap())
}
(&Method::GET, Some(&"blocks"), Some(&"tip"), Some(&"hash"), None, None) => http_message(
StatusCode::OK,
query.chain().best_hash().to_hex(),
Expand Down

0 comments on commit 018c094

Please sign in to comment.