use super::utils::{get_output, get_output_v2, w};
use crate::chain;
use crate::core::core::hash::Hash;
use crate::core::core::hash::Hashed;
use crate::rest::*;
use crate::router::{Handler, ResponseFuture};
use crate::types::*;
use crate::util;
use crate::web::*;
use hyper::{Body, Request, StatusCode};
use regex::Regex;
use std::sync::Weak;
pub const BLOCK_TRANSFER_LIMIT: u64 = 1000;
pub struct HeaderHandler {
pub chain: Weak<chain::Chain>,
}
impl HeaderHandler {
fn get_header(&self, input: String) -> Result<BlockHeaderPrintable, Error> {
if let Ok(h) = self.get_header_for_output(input.clone()) {
return Ok(h);
}
if let Ok(height) = input.parse() {
match w(&self.chain)?.get_header_by_height(height) {
Ok(header) => return Ok(BlockHeaderPrintable::from_header(&header)),
Err(_) => return Err(Error::NotFound),
}
}
check_block_param(&input)?;
let vec =
util::from_hex(&input).map_err(|e| Error::Argument(format!("invalid input: {}", e)))?;
let h = Hash::from_vec(&vec);
let header = w(&self.chain)?
.get_block_header(&h)
.map_err(|_| Error::NotFound)?;
Ok(BlockHeaderPrintable::from_header(&header))
}
fn get_header_for_output(&self, commit_id: String) -> Result<BlockHeaderPrintable, Error> {
let oid = match get_output(&self.chain, &commit_id)? {
Some((_, o)) => o,
None => return Err(Error::NotFound),
};
match w(&self.chain)?.get_header_for_output(oid.commitment()) {
Ok(header) => Ok(BlockHeaderPrintable::from_header(&header)),
Err(_) => Err(Error::NotFound),
}
}
pub fn get_header_v2(&self, h: &Hash) -> Result<BlockHeaderPrintable, Error> {
let chain = w(&self.chain)?;
let header = chain.get_block_header(h).map_err(|_| Error::NotFound)?;
Ok(BlockHeaderPrintable::from_header(&header))
}
pub fn parse_inputs(
&self,
height: Option<u64>,
hash: Option<Hash>,
commit: Option<String>,
) -> Result<Hash, Error> {
if let Some(height) = height {
match w(&self.chain)?.get_header_by_height(height) {
Ok(header) => return Ok(header.hash()),
Err(_) => return Err(Error::NotFound),
}
}
if let Some(hash) = hash {
return Ok(hash);
}
if let Some(commit) = commit {
let oid = match get_output_v2(&self.chain, &commit, false, false)? {
Some((_, o)) => o,
None => return Err(Error::NotFound),
};
match w(&self.chain)?.get_header_for_output(oid.commitment()) {
Ok(header) => return Ok(header.hash()),
Err(_) => return Err(Error::NotFound),
}
}
Err(Error::Argument(
"not a valid hash, height or output commit".to_owned(),
))
}
}
impl Handler for HeaderHandler {
fn get(&self, req: Request<Body>) -> ResponseFuture {
let el = right_path_element!(req);
result_to_response(self.get_header(el.to_string()))
}
}
pub struct BlockHandler {
pub chain: Weak<chain::Chain>,
}
impl BlockHandler {
pub fn get_block(
&self,
h: &Hash,
include_proof: bool,
include_merkle_proof: bool,
) -> Result<BlockPrintable, Error> {
let chain = w(&self.chain)?;
let block = chain.get_block(h).map_err(|_| Error::NotFound)?;
BlockPrintable::from_block(&block, &chain, include_proof, include_merkle_proof)
.map_err(|_| Error::Internal("chain error".to_owned()))
}
pub fn get_blocks(
&self,
mut start_height: u64,
end_height: u64,
mut max: u64,
include_proof: Option<bool>,
) -> Result<BlockListing, Error> {
if max > BLOCK_TRANSFER_LIMIT {
max = BLOCK_TRANSFER_LIMIT;
}
let tail_height = self.get_tail_height()?;
let orig_start_height = start_height;
if start_height < tail_height {
start_height = tail_height;
}
if start_height == 1 && orig_start_height == 0 {
start_height = 0;
}
let mut result_set = BlockListing {
last_retrieved_height: 0,
blocks: vec![],
};
let mut block_count = 0;
for h in start_height..=end_height {
result_set.last_retrieved_height = h;
let hash = match self.parse_inputs(Some(h), None, None) {
Err(e) => {
if let Error::NotFound = e {
continue;
} else {
return Err(e);
}
}
Ok(h) => h,
};
let block_res = self.get_block(&hash, include_proof == Some(true), false);
match block_res {
Err(e) => {
if let Error::NotFound = e {
continue;
} else {
return Err(e);
}
}
Ok(b) => {
block_count += 1;
result_set.blocks.push(b);
}
}
if block_count == max {
break;
}
}
Ok(result_set)
}
pub fn get_tail_height(&self) -> Result<u64, Error> {
let chain = w(&self.chain)?;
Ok(chain.get_tail().map_err(|_| Error::NotFound)?.height)
}
fn get_compact_block(&self, h: &Hash) -> Result<CompactBlockPrintable, Error> {
let chain = w(&self.chain)?;
let block = chain.get_block(h).map_err(|_| Error::NotFound)?;
CompactBlockPrintable::from_compact_block(&block.into(), &chain)
.map_err(|_| Error::Internal("chain error".to_owned()))
}
fn parse_input(&self, input: String) -> Result<Hash, Error> {
if let Ok(height) = input.parse() {
match w(&self.chain)?.get_header_by_height(height) {
Ok(header) => return Ok(header.hash()),
Err(_) => return Err(Error::NotFound),
}
}
check_block_param(&input)?;
let vec =
util::from_hex(&input).map_err(|e| Error::Argument(format!("invalid input: {}", e)))?;
Ok(Hash::from_vec(&vec))
}
pub fn parse_inputs(
&self,
height: Option<u64>,
hash: Option<Hash>,
commit: Option<String>,
) -> Result<Hash, Error> {
if let Some(height) = height {
match w(&self.chain)?.get_header_by_height(height) {
Ok(header) => return Ok(header.hash()),
Err(_) => return Err(Error::NotFound),
}
}
if let Some(hash) = hash {
return Ok(hash);
}
if let Some(commit) = commit {
let oid = match get_output_v2(&self.chain, &commit, false, false)? {
Some((_, o)) => o,
None => return Err(Error::NotFound),
};
match w(&self.chain)?.get_header_for_output(oid.commitment()) {
Ok(header) => return Ok(header.hash()),
Err(_) => return Err(Error::NotFound),
}
}
Err(Error::Argument(
"not a valid hash, height or output commit".to_owned(),
))
}
}
fn check_block_param(input: &str) -> Result<(), Error> {
lazy_static! {
static ref RE: Regex = Regex::new(r"[0-9a-fA-F]{64}").unwrap();
}
if !RE.is_match(&input) {
return Err(Error::Argument("Not a valid hash or height.".to_owned()));
}
Ok(())
}
impl Handler for BlockHandler {
fn get(&self, req: Request<Body>) -> ResponseFuture {
let el = right_path_element!(req);
let h = match self.parse_input(el.to_string()) {
Err(e) => {
return response(
StatusCode::BAD_REQUEST,
format!("failed to parse input: {}", e),
);
}
Ok(h) => h,
};
let mut include_proof = false;
let mut include_merkle_proof = true;
if let Some(params) = req.uri().query() {
let query = url::form_urlencoded::parse(params.as_bytes());
let mut compact = false;
for (param, _) in query {
match param.as_ref() {
"compact" => compact = true,
"no_merkle_proof" => include_merkle_proof = false,
"include_proof" => include_proof = true,
_ => {
return response(
StatusCode::BAD_REQUEST,
format!("unsupported query parameter: {}", param),
)
}
}
}
if compact {
return result_to_response(self.get_compact_block(&h));
}
}
result_to_response(self.get_block(&h, include_proof, include_merkle_proof))
}
}