use std::cmp;
use std::ops::Range;
use serde_json::Value;
use storage::ListProof;
use crypto::Hash;
use blockchain::{Schema, Blockchain, Block, TxLocation, TransactionResult, TransactionErrorType};
use messages::Precommit;
use api::ApiError;
use helpers::Height;
#[derive(Debug)]
pub struct BlockchainExplorer<'a> {
blockchain: &'a Blockchain,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BlockInfo {
pub block: Block,
pub precommits: Vec<Precommit>,
pub txs: Vec<Hash>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct TxInfo {
pub content: Value,
pub location: TxLocation,
pub location_proof: ListProof<Hash>,
pub status: TxStatus,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum TxStatus {
Success,
Panic {
description: String,
},
Error {
code: u8,
description: String,
},
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BlocksRange {
pub range: Range<u64>,
pub blocks: Vec<Block>,
}
impl<'a> BlockchainExplorer<'a> {
pub fn new(blockchain: &'a Blockchain) -> Self {
BlockchainExplorer { blockchain }
}
pub fn tx_info(&self, tx_hash: &Hash) -> Result<Option<TxInfo>, ApiError> {
let schema = Schema::new(self.blockchain.snapshot());
let raw_tx = match schema.transactions().get(tx_hash) {
Some(val) => val,
None => {
return Ok(None);
}
};
let box_transaction = self.blockchain.tx_from_raw(raw_tx.clone()).ok_or_else(|| {
ApiError::InternalError(format!("Service not found for tx: {:?}", raw_tx).into())
})?;
let content = box_transaction.serialize_field().map_err(
ApiError::InternalError,
)?;
let location = schema.tx_location_by_tx_hash().get(tx_hash).expect(
&format!(
"Not found tx_hash location: {:?}",
tx_hash
),
);
let location_proof = schema.block_txs(location.block_height()).get_proof(
location.position_in_block(),
);
let status = match schema.transaction_results().get(tx_hash).unwrap() {
Ok(()) => TxStatus::Success,
Err(e) => {
let description = e.description().unwrap_or_default().to_owned();
match e.error_type() {
TransactionErrorType::Panic => TxStatus::Panic { description },
TransactionErrorType::Code(code) => TxStatus::Error { code, description },
}
}
};
Ok(Some(TxInfo {
content,
location,
location_proof,
status,
}))
}
pub fn block_info(&self, height: Height) -> Option<BlockInfo> {
let schema = Schema::new(self.blockchain.snapshot());
let txs_table = schema.block_txs(height);
let block_proof = schema.block_and_precommits(height);
match block_proof {
None => None,
Some(proof) => {
let bl = BlockInfo {
block: proof.block,
precommits: proof.precommits,
txs: txs_table.iter().collect(),
};
Some(bl)
}
}
}
pub fn blocks_range(
&self,
count: u64,
upper: Option<u64>,
skip_empty_blocks: bool,
) -> BlocksRange {
let schema = Schema::new(self.blockchain.snapshot());
let hashes = schema.block_hashes_by_height();
let blocks = schema.blocks();
let max_height = hashes.len() - 1;
let upper = upper.map(|x| cmp::min(x, max_height)).unwrap_or(max_height);
let mut height = upper + 1;
let mut genesis = false;
let mut v = Vec::new();
let mut collected: u64 = 0;
loop {
if genesis || (collected == count) {
break;
}
height -= 1;
genesis = height == 0;
let block_txs = schema.block_txs(Height(height));
if skip_empty_blocks && block_txs.is_empty() {
continue;
}
let block_hash = hashes.get(height).expect(&format!(
"Block not found, height:{:?}",
height
));
let block = blocks.get(&block_hash).expect(&format!(
"Block not found, hash:{:?}",
block_hash
));
v.push(block);
collected += 1;
}
BlocksRange {
range: height..upper + 1,
blocks: v,
}
}
pub fn transaction_result(&self, hash: &Hash) -> Option<TransactionResult> {
let schema = Schema::new(self.blockchain.snapshot());
schema.transaction_results().get(hash)
}
}