From 72fdceb0d62909686f84c88e5ded3160911ad5ca Mon Sep 17 00:00:00 2001 From: Johnny Gannon Date: Tue, 5 Dec 2017 10:55:32 -0800 Subject: [PATCH] API endpoints to browse blocks (#416) * Implement /block api endpoint displaying basic information * Add block inputs and kernels to the api output. * Add fields to BlockHeaderInfo and TxKernelPrintable * Add features debug string to TxKernelPrintable. * Return 400 and 404 statuses from the blocks api endpoint. * For the blocks api, return a 404 if a block is not found at the requested height * Add back hash to BlockHeader api output. --- api/Cargo.toml | 2 + api/src/handlers.rs | 54 ++++++++++++++++++++ api/src/lib.rs | 3 ++ api/src/types.rs | 117 ++++++++++++++++++++++++++++++++++++++------ 4 files changed, 160 insertions(+), 16 deletions(-) diff --git a/api/Cargo.toml b/api/Cargo.toml index 6a576bd9a..22aa7b0a8 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -13,8 +13,10 @@ grin_util = { path = "../util" } grin_p2p = { path = "../p2p" } hyper = "~0.10.6" slog = { version = "^2.0.12", features = ["max_level_trace", "release_max_level_trace"] } +lazy_static = "1.0" iron = "~0.5.1" router = "~0.5.1" +regex = "0.2" mount = "~0.3.0" urlencoded = "~0.5.0" serde = "~1.0.8" diff --git a/api/src/handlers.rs b/api/src/handlers.rs index 870bee3db..4865ed0e4 100644 --- a/api/src/handlers.rs +++ b/api/src/handlers.rs @@ -25,10 +25,12 @@ use serde_json; use chain; use core::core::Transaction; +use core::core::hash::Hash; use core::core::hash::Hashed; use core::ser; use pool; use p2p; +use regex::Regex; use rest::*; use util::secp::pedersen::Commitment; use types::*; @@ -273,6 +275,53 @@ impl Handler for ChainHandler { } } +// Gets block details given either a hex address or height. +// GET /v1/block/
+// GET /v1/block/ +pub struct BlockHandler { + pub chain: Arc, +} + +impl BlockHandler { + fn get_block(&self, h: &Hash) -> Result { + let block = self.chain.clone().get_block(h).map_err(|_| Error::NotFound)?; + Ok(BlockPrintable::from_block(&block)) + } + + // Try to decode the string as a height or a hash address. + fn parse_input(&self, input: String) -> Result { + if let Ok(height) = input.parse() { + match self.chain.clone().get_header_by_height(height) { + Ok(header) => return Ok(header.hash()), + Err(_) => return Err(Error::NotFound), + } + } + lazy_static! { + static ref RE: Regex = Regex::new(r"[0-9a-fA-F]{64}").unwrap(); + } + if !RE.is_match(&input) { + return Err(Error::Argument( + String::from("Not a valid hex address or height."))) + } + let vec = util::from_hex(input).unwrap(); + Ok(Hash::from_vec(vec)) + } +} + +impl Handler for BlockHandler { + fn handle(&self, req: &mut Request) -> IronResult { + let url = req.url.clone(); + let mut path_elems = url.path(); + if *path_elems.last().unwrap() == "" { + path_elems.pop(); + } + let el = *path_elems.last().unwrap(); + let h = try!(self.parse_input(el.to_string())); + let b = try!(self.get_block(&h)); + json_response(&b) + } +} + // Get basic information about the transaction pool. struct PoolInfoHandler { tx_pool: Arc>>, @@ -382,6 +431,9 @@ pub fn start_rest_apis( let utxo_handler = UtxoHandler { chain: chain.clone(), }; + let block_handler = BlockHandler { + chain: chain.clone(), + }; let chain_tip_handler = ChainHandler { chain: chain.clone(), }; @@ -403,6 +455,7 @@ pub fn start_rest_apis( let route_list = vec!( "get /".to_string(), + "get /blocks".to_string(), "get /chain".to_string(), "get /chain/utxos".to_string(), "get /sumtrees/roots".to_string(), @@ -417,6 +470,7 @@ pub fn start_rest_apis( let index_handler = IndexHandler { list: route_list }; let router = router!( index: get "/" => index_handler, + blocks: get "/blocks/*" => block_handler, chain_tip: get "/chain" => chain_tip_handler, chain_utxos: get "/chain/utxos/*" => utxo_handler, sumtree_roots: get "/sumtrees/*" => sumtree_handler, diff --git a/api/src/lib.rs b/api/src/lib.rs index fbf0c13f7..3aec1a205 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -20,8 +20,11 @@ extern crate grin_store as store; extern crate grin_util as util; extern crate hyper; +#[macro_use] +extern crate lazy_static; extern crate iron; extern crate mount; +extern crate regex; #[macro_use] extern crate router; extern crate serde; diff --git a/api/src/types.rs b/api/src/types.rs index bce547d8d..8cdc59d7c 100644 --- a/api/src/types.rs +++ b/api/src/types.rs @@ -15,6 +15,7 @@ use std::sync::Arc; use core::{core, global}; use core::core::hash::Hashed; +use core::core::target::Difficulty; use chain; use util::secp::pedersen; use rest::*; @@ -148,7 +149,7 @@ pub struct Output { } impl Output { - pub fn from_output(output: &core::Output, block_header: &core::BlockHeader, + pub fn from_output(output: &core::Output, block_header: &core::BlockHeader, include_proof:bool, include_switch: bool) -> Output { let (output_type, lock_height) = match output.features { x if x.contains(core::transaction::COINBASE_OUTPUT) => ( @@ -222,7 +223,7 @@ impl OutputPrintable { // As above, except just the info needed for wallet reconstruction #[derive(Debug, Serialize, Deserialize, Clone)] pub struct OutputSwitch { - /// the commit + /// the commit pub commit: String, /// switch commit hash pub switch_commit_hash: [u8; core::SWITCH_COMMIT_HASH_SIZE], @@ -239,26 +240,110 @@ impl OutputSwitch { } } } -// Just the information required for wallet reconstruction + +// Printable representation of a block #[derive(Debug, Serialize, Deserialize, Clone)] -pub struct BlockHeaderInfo { - /// Hash - pub hash: String, - /// Previous block hash - pub previous: String, - /// Height - pub height: u64 +pub struct TxKernelPrintable { + pub features: String, + pub fee: u64, + pub lock_height: u64, + pub excess: String, + pub excess_sig: String, } -impl BlockHeaderInfo { - pub fn from_header(block_header: &core::BlockHeader) -> BlockHeaderInfo{ - BlockHeaderInfo { - hash: util::to_hex(block_header.hash().to_vec()), - previous: util::to_hex(block_header.previous.to_vec()), - height: block_header.height, +impl TxKernelPrintable { + pub fn from_txkernel(k: &core::TxKernel) -> TxKernelPrintable { + TxKernelPrintable { + features: format!("{:?}", k.features), + fee: k.fee, + lock_height: k.lock_height, + excess: util::to_hex(k.excess.0.to_vec()), + excess_sig: util::to_hex(k.excess_sig.to_vec()) } } } + +// Just the information required for wallet reconstruction +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct BlockHeaderInfo { + // Hash + pub hash: String, + /// Version of the block + pub version: u16, + /// Height of this block since the genesis block (height 0) + pub height: u64, + /// Hash of the block previous to this in the chain. + pub previous: String, + /// rfc3339 timestamp at which the block was built. + pub timestamp: String, + /// Merklish root of all the commitments in the UTXO set + pub utxo_root: String, + /// Merklish root of all range proofs in the UTXO set + pub range_proof_root: String, + /// Merklish root of all transaction kernels in the UTXO set + pub kernel_root: String, + /// Nonce increment used to mine this block. + pub nonce: u64, + /// Difficulty used to mine the block. + pub difficulty: Difficulty, + /// Total accumulated difficulty since genesis block + pub total_difficulty: Difficulty, +} + +impl BlockHeaderInfo { + pub fn from_header(h: &core::BlockHeader) -> BlockHeaderInfo { + BlockHeaderInfo { + hash: util::to_hex(h.hash().to_vec()), + version: h.version, + height: h.height, + previous: util::to_hex(h.previous.to_vec()), + timestamp: h.timestamp.rfc3339().to_string(), + utxo_root: util::to_hex(h.utxo_root.to_vec()), + range_proof_root: util::to_hex(h.range_proof_root.to_vec()), + kernel_root: util::to_hex(h.kernel_root.to_vec()), + nonce: h.nonce, + difficulty: h.difficulty.clone(), + total_difficulty: h.total_difficulty.clone(), + } + } +} + +// Printable representation of a block +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct BlockPrintable { + /// The block header + pub header: BlockHeaderInfo, + // Input transactions + pub inputs: Vec, + /// A printable version of the outputs + pub outputs: Vec, + /// A printable version of the transaction kernels + pub kernels: Vec, +} + +impl BlockPrintable { + pub fn from_block(block: &core::Block) -> BlockPrintable { + let inputs = block.inputs + .iter() + .map(|input| util::to_hex((input.0).0.to_vec())) + .collect(); + let outputs = block.outputs + .iter() + .map(|output| OutputPrintable::from_output(output, &block.header, true)) + .collect(); + let kernels = block.kernels + .iter() + .map(|kernel| TxKernelPrintable::from_txkernel(kernel)) + .collect(); + BlockPrintable { + header: BlockHeaderInfo::from_header(&block.header), + inputs: inputs, + outputs: outputs, + kernels: kernels, + } + } +} + // For wallet reconstruction, include the header info along with the // transactions in the block #[derive(Debug, Serialize, Deserialize, Clone)]