Skip to content

Commit

Permalink
Extend responses for endpoints [ECR-3349] (exonum#1386)
Browse files Browse the repository at this point in the history
  • Loading branch information
Oleksandr Anyshchenko authored Jul 18, 2019
1 parent ceafb15 commit c77f576
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 50 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ The project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html)

- A channel for api requests has been changed to unbounded. (#1308)

- Endpoints `explorer/v1/block` and `explorer/v1/transactions` were extended
with adding additional fields `service_id` and `time`. (#1386)

#### exonum-merkledb

- Updated `ProofMapIndex` data layout. (#1293)
Expand Down
6 changes: 5 additions & 1 deletion exonum/examples/explorer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ pub fn sample_blockchain() -> Blockchain {
let fork = blockchain.fork();
{
let mut schema = Schema::new(&fork);
schema.add_transaction_into_pool(mempool_transaction().clone());
schema.add_transaction_into_pool(mempool_transaction());
}
blockchain.merge(fork.into_patch()).unwrap();

Expand Down Expand Up @@ -160,6 +160,7 @@ fn main() {
"location_proof": tx.location_proof(),
// Execution status
"status": { "type": "success" },
"time": tx.time(),
})
);

Expand All @@ -177,6 +178,7 @@ fn main() {
"content": serde_json::to_value(erroneous_tx.content()).unwrap(),
"location": erroneous_tx.location(),
"location_proof": erroneous_tx.location_proof(),
"time": erroneous_tx.time(),
})
);

Expand All @@ -190,6 +192,7 @@ fn main() {
"content": serde_json::to_value(panicked_tx.content()).unwrap(),
"location": panicked_tx.location(),
"location_proof": panicked_tx.location_proof(),
"time": panicked_tx.time(),
})
);

Expand All @@ -212,6 +215,7 @@ fn main() {
"status": { "type": "success" },
"location": tx_ref.location(),
"location_proof": tx_ref.location_proof(),
"time": tx_ref.time(),
})
);

Expand Down
39 changes: 24 additions & 15 deletions exonum/src/api/node/public/explorer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ use futures::{Future, IntoFuture};

use std::ops::{Bound, Range};
use std::sync::{Arc, Mutex};
use std::time::UNIX_EPOCH;

use crate::{
api::{
Expand All @@ -33,7 +32,7 @@ use crate::{
},
blockchain::{Block, SharedNodeState},
crypto::Hash,
explorer::{self, BlockchainExplorer, TransactionInfo},
explorer::{self, median_precommits_time, BlockchainExplorer, TransactionInfo},
helpers::Height,
messages::{Message, Precommit, RawTransaction, Signed, SignedMessage},
};
Expand All @@ -51,6 +50,13 @@ pub struct BlocksRange {
pub blocks: Vec<BlockInfo>,
}

/// Information about a transaction included in the block.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct TxInfo {
tx_hash: Hash,
service_id: u16,
}

/// Information about a block in the blockchain.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct BlockInfo {
Expand All @@ -62,9 +68,9 @@ pub struct BlockInfo {
#[serde(skip_serializing_if = "Option::is_none")]
pub precommits: Option<Vec<Signed<Precommit>>>,

/// Hashes of transactions in the block.
/// Info of transactions in the block.
#[serde(skip_serializing_if = "Option::is_none")]
pub txs: Option<Vec<Hash>>,
pub txs: Option<Vec<TxInfo>>,

/// Median time from the block precommits.
#[serde(skip_serializing_if = "Option::is_none")]
Expand Down Expand Up @@ -352,21 +358,24 @@ impl ExplorerApi {

impl<'a> From<explorer::BlockInfo<'a>> for BlockInfo {
fn from(inner: explorer::BlockInfo<'a>) -> Self {
let txs = inner
.transaction_hashes()
.iter()
.enumerate()
.map(|(idx, hash)| {
let service_id = inner.transaction(idx).unwrap().content().service_id();
TxInfo {
tx_hash: *hash,
service_id,
}
})
.collect::<Vec<TxInfo>>();

Self {
block: inner.header().clone(),
precommits: Some(inner.precommits().to_vec()),
txs: Some(inner.transaction_hashes().to_vec()),
txs: Some(txs),
time: Some(median_precommits_time(&inner.precommits())),
}
}
}

fn median_precommits_time(precommits: &[Signed<Precommit>]) -> DateTime<Utc> {
if precommits.is_empty() {
UNIX_EPOCH.into()
} else {
let mut times: Vec<_> = precommits.iter().map(|p| p.time()).collect();
times.sort();
times[times.len() / 2]
}
}
33 changes: 19 additions & 14 deletions exonum/src/api/websocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,10 @@
use actix::*;
use actix_web::ws;

use rand::{rngs::ThreadRng, Rng};

use chrono::{DateTime, Utc};
use futures::Future;

use log::error;

use rand::{rngs::ThreadRng, Rng};
use std::{
cell::RefCell,
collections::{BTreeMap, HashMap},
Expand All @@ -36,7 +33,7 @@ use crate::api::{
use crate::blockchain::{Block, Schema, TransactionResult, TxLocation};
use crate::crypto::Hash;
use crate::events::error::into_failure;
use crate::explorer::TxStatus;
use crate::explorer::{median_precommits_time, TxStatus};
use crate::messages::{Message as ExonumMessage, ProtocolMessage, RawTransaction, SignedMessage};

use exonum_merkledb::{IndexAccess, ListProof, Snapshot};
Expand Down Expand Up @@ -90,14 +87,15 @@ impl TransactionFilter {
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct CommittedTransactionSummary {
tx_hash: Hash,
/// ID of service.
/// Service id of the transaction.
pub service_id: u16,
/// ID of transaction in service.
/// ID of the transaction.
pub message_id: u16,
#[serde(with = "TxStatus")]
status: TransactionResult,
location: TxLocation,
proof: ListProof<Hash>,
location_proof: ListProof<Hash>,
time: DateTime<Utc>,
}

impl CommittedTransactionSummary {
Expand All @@ -107,19 +105,26 @@ impl CommittedTransactionSummary {
{
let tx = schema.transactions().get(tx_hash)?;
let service_id = tx.payload().service_id();
let tx_id = tx.payload().transaction_id();
let tx_result = schema.transaction_results().get(tx_hash)?;
let message_id = tx.payload().transaction_id();
let status = schema.transaction_results().get(tx_hash)?;
let location = schema.transactions_locations().get(tx_hash)?;
let location_proof = schema
.block_transactions(location.block_height())
.get_proof(location.position_in_block());
let time = median_precommits_time(
&schema
.block_and_precommits(location.block_height())
.unwrap()
.precommits,
);
Some(Self {
tx_hash: *tx_hash,
service_id,
message_id: tx_id,
status: tx_result,
message_id,
status,
location,
proof: location_proof,
location_proof,
time,
})
}
}
Expand Down
10 changes: 9 additions & 1 deletion exonum/src/blockchain/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ pub struct TransactionResult(pub Result<(), TransactionError>);
/// and take some new transaction into pool from user input.
#[derive(Serialize, Deserialize)]
pub struct TransactionMessage {
service_id: u16,
#[serde(skip_deserializing)]
#[serde(rename = "debug")]
transaction: Option<Box<dyn Transaction>>,

#[serde(with = "HexStringRepresentation")]
message: Signed<RawTransaction>,
}
Expand All @@ -62,6 +62,7 @@ impl ::std::fmt::Debug for TransactionMessage {
.write_hex(&mut signed_message_debug)?;

let mut debug = fmt.debug_struct("TransactionMessage");
debug.field("service_id", &self.service_id);
debug.field("message", &signed_message_debug);
if let Some(ref tx) = self.transaction {
debug.field("debug", tx);
Expand All @@ -88,12 +89,19 @@ impl TransactionMessage {
use std::ops::Deref;
self.transaction.as_ref().map(Deref::deref)
}
/// Returns a service id of the transaction.
pub fn service_id(&self) -> u16 {
self.service_id
}

/// Create new `TransactionMessage` from raw message.
pub(crate) fn new(
message: Signed<RawTransaction>,
transaction: Box<dyn Transaction>,
) -> TransactionMessage {
let service_id = message.service_id();
TransactionMessage {
service_id,
transaction: Some(transaction),
message,
}
Expand Down
40 changes: 34 additions & 6 deletions exonum/src/explorer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ use crate::blockchain::{
use crate::crypto::{CryptoHash, Hash};
use crate::helpers::Height;
use crate::messages::{Precommit, RawTransaction, Signed};
use chrono::{DateTime, Utc};
use exonum_merkledb::{ListProof, Snapshot};
use std::time::UNIX_EPOCH;

/// Transaction parsing result.
type ParseResult = Result<TransactionMessage, failure::Error>;
Expand Down Expand Up @@ -384,11 +386,13 @@ impl<'a> IntoIterator for &'a BlockWithTransactions {
/// "content": {
/// "message": //...
/// # message,
/// "service_id": 0
/// },
/// "location": { "block_height": 1, "position_in_block": 0 },
/// "location_proof": // ...
/// # { "val": Hash::zero() },
/// "status": { "type": "success" }
/// "status": { "type": "success" },
/// "time": "2019-07-16T15:26:43.502696Z"
/// });
///
/// let parsed: CommittedTransaction =
Expand All @@ -403,6 +407,7 @@ pub struct CommittedTransaction {
location_proof: ListProof<Hash>,
#[serde(with = "TxStatus")]
status: TransactionResult,
time: DateTime<Utc>,
}

/// Transaction execution status. Simplified version of `TransactionResult`.
Expand Down Expand Up @@ -489,6 +494,11 @@ impl CommittedTransaction {
pub fn status(&self) -> Result<(), &TransactionError> {
self.status.0.as_ref().map(|_| ())
}

/// Returns a commit time of the block which includes this transaction.
pub fn time(&self) -> &DateTime<Utc> {
&self.time
}
}

/// Information about the transaction.
Expand Down Expand Up @@ -559,7 +569,8 @@ impl CommittedTransaction {
/// "type": "in-pool",
/// "content": {
/// "message": // ...
/// # message
/// # message,
/// "service_id": 0
/// }
/// });
///
Expand Down Expand Up @@ -651,11 +662,11 @@ impl<'a> BlockchainExplorer<'a> {
let schema = Schema::new(&self.snapshot);
let content = self.transaction_without_proof(tx_hash)?;
if schema.transactions_pool().contains(tx_hash) {
return Some(TransactionInfo::InPool { content });
Some(TransactionInfo::InPool { content })
} else {
let tx = self.committed_transaction(tx_hash, Some(content));
Some(TransactionInfo::Committed(tx))
}

let tx = self.committed_transaction(tx_hash, Some(content));
Some(TransactionInfo::Committed(tx))
}

/// Returns transaction message without proof.
Expand Down Expand Up @@ -705,6 +716,11 @@ impl<'a> BlockchainExplorer<'a> {
.block_transactions(location.block_height())
.get_proof(location.position_in_block());

let block_precommits = schema
.block_and_precommits(location.block_height())
.unwrap();
let time = median_precommits_time(&block_precommits.precommits);

// Unwrap is OK here, because we already know that transaction is committed.
let status = schema.transaction_results().get(tx_hash).unwrap();

Expand All @@ -717,6 +733,7 @@ impl<'a> BlockchainExplorer<'a> {
location,
location_proof,
status,
time,
}
}

Expand Down Expand Up @@ -834,3 +851,14 @@ impl<'a> DoubleEndedIterator for Blocks<'a> {
Some(BlockInfo::new(self.explorer, self.back))
}
}

/// Calculates a median time from precommits.
pub fn median_precommits_time(precommits: &[Signed<Precommit>]) -> DateTime<Utc> {
if precommits.is_empty() {
UNIX_EPOCH.into()
} else {
let mut times: Vec<_> = precommits.iter().map(|p| p.time()).collect();
times.sort();
times[times.len() / 2]
}
}
Loading

0 comments on commit c77f576

Please sign in to comment.