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)]