Skip to content

Commit

Permalink
Brush up explorer [ECR-4084] [ECR-4092] (exonum#1685)
Browse files Browse the repository at this point in the history
  • Loading branch information
slowli authored and Oleksandr Anyshchenko committed Jan 10, 2020
1 parent dfe9f7e commit e3c9553
Show file tree
Hide file tree
Showing 12 changed files with 1,663 additions and 1,212 deletions.
1 change: 1 addition & 0 deletions components/explorer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ travis-ci = { repository = "exonum/exonum" }
exonum = { version = "0.13.0-rc.2", path = "../../exonum" }

chrono = { version = "0.4.6", features = ["serde"] }
hex = "0.4.0"
serde = "1.0"
serde_derive = "1.0"

Expand Down
127 changes: 40 additions & 87 deletions components/explorer/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,39 @@
// Copyright 2020 The Exonum Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Types used in the explorer API.
//!
//! The types are bundled together with the explorer (rather than the explorer service)
//! in order to ease dependency management for client apps.
use chrono::{DateTime, Utc};
use exonum::{
blockchain::{Block, Schema, TxLocation},
blockchain::Block,
crypto::Hash,
helpers::Height,
merkledb::{access::Access, ListProof},
merkledb::BinaryValue,
messages::{Precommit, Verified},
runtime::{CallInfo, ExecutionStatus, InstanceId},
runtime::{AnyTx, CallInfo, ExecutionStatus, InstanceId},
};
use serde_derive::*;
use serde_derive::{Deserialize, Serialize};

use std::ops::Range;

use crate::median_precommits_time;

pub mod websocket;

/// The maximum number of blocks to return per blocks request, in this way
/// the parameter limits the maximum execution time for such requests.
pub const MAX_BLOCKS_PER_REQUEST: usize = 1000;
Expand Down Expand Up @@ -108,12 +124,12 @@ pub struct BlocksQuery {
/// If true, then only non-empty blocks are returned. The default value is false.
#[serde(default)]
pub skip_empty_blocks: bool,
/// If true, then the returned `BlocksRange`'s `times` field will contain median time from the
/// corresponding blocks precommits.
/// If true, then the `time` field in each returned block will contain the median time from the
/// block precommits.
#[serde(default)]
pub add_blocks_time: bool,
/// If true, then the returned `BlocksRange.precommits` will contain precommits for the
/// corresponding returned blocks.
/// If true, then the `precommits` field in each returned block will contain precommits for the
/// block stored by the node.
#[serde(default)]
pub add_precommits: bool,
}
Expand All @@ -139,10 +155,18 @@ pub struct TransactionHex {
pub tx_body: String,
}

/// Transaction response.
impl TransactionHex {
pub fn new(transaction: &Verified<AnyTx>) -> Self {
Self {
tx_body: hex::encode(transaction.to_bytes()),
}
}
}

/// Response to a request to broadcast a transaction over the blockchain network.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub struct TransactionResponse {
/// The hex value of the transaction to be broadcasted.
/// The hash digest of the transaction.
pub tx_hash: Hash,
}

Expand All @@ -160,26 +184,8 @@ impl TransactionQuery {
}
}

impl AsRef<str> for TransactionHex {
fn as_ref(&self) -> &str {
self.tx_body.as_ref()
}
}

impl AsRef<[u8]> for TransactionHex {
fn as_ref(&self) -> &[u8] {
self.tx_body.as_ref()
}
}

/// Call status response.
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CallStatusResponse {
/// Call status
pub status: ExecutionStatus,
}

/// Call status query parameters to check `before_transactions` or `after_transactions` call.
/// Query parameters to check the execution status of a `before_transactions` or
/// `after_transactions` call.
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CallStatusQuery {
/// Height of a block.
Expand All @@ -188,62 +194,9 @@ pub struct CallStatusQuery {
pub service_id: InstanceId,
}

/// Summary about a particular transaction in the blockchain (without transaction content).
#[derive(Debug, Serialize, Deserialize)]
pub struct CommittedTransactionSummary {
/// Transaction identifier.
pub tx_hash: Hash,
/// ID of service.
pub service_id: u16,
/// ID of transaction in service.
pub message_id: u16,
/// Result of transaction execution.
/// Call status response.
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CallStatusResponse {
/// Execution status of a call.
pub status: ExecutionStatus,
/// Transaction location in the blockchain.
pub location: TxLocation,
/// Proof of existence.
pub location_proof: ListProof<Hash>,
/// Approximate finalization time.
pub time: DateTime<Utc>,
}

impl CommittedTransactionSummary {
/// Constructs a transaction summary from the core schema.
pub fn new(schema: &Schema<impl Access>, tx_hash: &Hash) -> Option<Self> {
let tx = schema.transactions().get(tx_hash)?;
let tx = tx.as_ref();
let service_id = tx.call_info.instance_id as u16;
let tx_id = tx.call_info.method_id as u16;
let location = schema.transactions_locations().get(tx_hash)?;
let tx_result = schema.transaction_result(location)?;
let location_proof = schema
.block_transactions(location.block_height())
.get_proof(location.position_in_block().into());
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: ExecutionStatus(tx_result),
location,
location_proof,
time,
})
}
}

/// Websocket notification message. This enum describes data which is sent
/// to a WebSocket listener.
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Notification {
/// Notification about new block.
Block(Block),
/// Notification about new transaction.
Transaction(CommittedTransactionSummary),
}
183 changes: 183 additions & 0 deletions components/explorer/src/api/websocket.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Copyright 2020 The Exonum Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Types used in WebSocket communication with the explorer service.
use chrono::{DateTime, Utc};
use exonum::{
blockchain::{Block, Schema, TxLocation},
crypto::Hash,
merkledb::{access::Access, ListProof},
runtime::{ExecutionStatus, InstanceId, MethodId},
};
use serde_derive::{Deserialize, Serialize};

use std::fmt;

use super::TransactionHex;
use crate::median_precommits_time;

/// Messages proactively sent by WebSocket clients to the server.
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[serde(tag = "type", content = "payload", rename_all = "snake_case")]
pub enum IncomingMessage {
/// Set subscription for websocket connection.
SetSubscriptions(Vec<SubscriptionType>),
/// Send transaction to the blockchain.
Transaction(TransactionHex),
}

/// Subscription type for new blocks or committed transactions.
#[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
#[derive(Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum SubscriptionType {
/// Subscription to nothing.
None,
/// Subscription to new blocks.
Blocks,
/// Subscription to committed transactions.
Transactions {
/// Optional filter for the subscription.
filter: Option<TransactionFilter>,
},
}

/// Filter for transactions by service instance and (optionally) method identifier
/// within the service.
#[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
#[derive(Serialize, Deserialize)]
pub struct TransactionFilter {
/// ID of the service.
pub instance_id: InstanceId,
/// Optional ID of a method within the service. If not set, transactions belonging
/// to all service methods will be sent.
pub method_id: Option<MethodId>,
}

impl TransactionFilter {
/// Creates a new transaction filter.
pub fn new(instance_id: InstanceId, method_id: Option<MethodId>) -> Self {
Self {
instance_id,
method_id,
}
}
}

/// Response to a WebSocket client. Roughly equivalent to `Result<T, String>`.
#[serde(tag = "result", rename_all = "snake_case")]
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub enum Response<T> {
/// Successful response.
Success {
/// Payload attached to the response.
response: T,
},
/// Response carrying an error.
Error {
/// Error description.
description: String,
},
}

impl<T> Response<T> {
/// Creates a response with the specified value.
pub fn success(value: T) -> Self {
Response::Success { response: value }
}

/// Creates an erroneous response.
pub fn error(description: impl fmt::Display) -> Self {
Response::Error {
description: description.to_string(),
}
}

/// Converts response into a `Result`.
pub fn into_result(self) -> Result<T, String> {
match self {
Response::Success { response } => Ok(response),
Response::Error { description } => Err(description),
}
}
}

impl<T> From<Result<T, String>> for Response<T> {
fn from(res: Result<T, String>) -> Self {
match res {
Ok(value) => Self::success(value),
Err(description) => Response::Error { description },
}
}
}

/// Summary about a particular transaction in the blockchain. Does not include transaction content.
#[derive(Debug, Serialize, Deserialize)]
pub struct CommittedTransactionSummary {
/// Transaction identifier.
pub tx_hash: Hash,
/// ID of service.
pub instance_id: InstanceId,
/// ID of the method within service.
pub method_id: MethodId,
/// Result of transaction execution.
pub status: ExecutionStatus,
/// Transaction location in the blockchain.
pub location: TxLocation,
/// Proof of existence.
pub location_proof: ListProof<Hash>,
/// Approximate finalization time.
pub time: DateTime<Utc>,
}

impl CommittedTransactionSummary {
/// Constructs a transaction summary from the core schema.
pub fn new(schema: &Schema<impl Access>, tx_hash: &Hash) -> Option<Self> {
let tx = schema.transactions().get(tx_hash)?;
let tx = tx.payload();
let instance_id = tx.call_info.instance_id;
let method_id = tx.call_info.method_id;
let location = schema.transactions_locations().get(tx_hash)?;
let tx_result = schema.transaction_result(location)?;
let location_proof = schema
.block_transactions(location.block_height())
.get_proof(location.position_in_block().into());
let time = median_precommits_time(
&schema
.block_and_precommits(location.block_height())
.unwrap()
.precommits,
);
Some(Self {
tx_hash: *tx_hash,
instance_id,
method_id,
status: ExecutionStatus(tx_result),
location,
location_proof,
time,
})
}
}

/// Notification message passed to WebSocket clients.
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Notification {
/// Notification about new block.
Block(Block),
/// Notification about new transaction.
Transaction(CommittedTransactionSummary),
}
8 changes: 2 additions & 6 deletions components/explorer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,19 +475,15 @@ impl CommittedTransaction {

/// Information about the transaction.
///
/// Values of this type are returned by the [`transaction()`] method of the `BlockchainExplorer`.
///
/// [`transaction()`]: struct.BlockchainExplorer.html#method.transaction
/// Values of this type are returned by the `transaction()` method of the `BlockchainExplorer`.
///
/// # JSON presentation
///
/// ## Committed transactions
///
/// Committed transactions are represented just like a [`CommittedTransaction`],
/// Committed transactions are represented just like a `CommittedTransaction`,
/// with the additional `type` field equal to `"committed"`.
///
/// [`CommittedTransaction`]: struct.CommittedTransaction.html#json-presentation
///
/// ## Transaction in pool
///
/// Transactions in pool are represented with a 2-field object:
Expand Down
2 changes: 2 additions & 0 deletions services/explorer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ for more details about the service API.

`exonum-explorer-service` is licensed under the Apache License (Version 2.0).
See [LICENSE](LICENSE) for details.

[explorer]: https://crates.io/crates/exonum-explorer/
Loading

0 comments on commit e3c9553

Please sign in to comment.