exonum_explorer/api/
websocket.rs

1// Copyright 2020 The Exonum Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//   http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Types used in WebSocket communication with the explorer service.
16
17use chrono::{DateTime, Utc};
18use exonum::{
19    blockchain::{Block, Schema, TxLocation},
20    crypto::Hash,
21    merkledb::{access::Access, ListProof},
22    runtime::{ExecutionStatus, InstanceId, MethodId},
23};
24use serde_derive::{Deserialize, Serialize};
25
26use std::fmt;
27
28use super::TransactionHex;
29use crate::median_precommits_time;
30
31/// Messages proactively sent by WebSocket clients to the server.
32#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
33#[serde(tag = "type", content = "payload", rename_all = "snake_case")]
34pub enum IncomingMessage {
35    /// Set subscription for websocket connection.
36    SetSubscriptions(Vec<SubscriptionType>),
37    /// Send transaction to the blockchain.
38    Transaction(TransactionHex),
39}
40
41/// Subscription type for new blocks or committed transactions.
42#[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
43#[derive(Serialize, Deserialize)]
44#[serde(tag = "type", rename_all = "snake_case")]
45pub enum SubscriptionType {
46    /// Subscription to nothing.
47    None,
48    /// Subscription to new blocks.
49    Blocks,
50    /// Subscription to committed transactions.
51    Transactions {
52        /// Optional filter for the subscription.
53        filter: Option<TransactionFilter>,
54    },
55}
56
57/// Filter for transactions by service instance and (optionally) method identifier
58/// within the service.
59#[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
60#[derive(Serialize, Deserialize)]
61pub struct TransactionFilter {
62    /// ID of the service.
63    pub instance_id: InstanceId,
64    /// Optional ID of a method within the service. If not set, transactions belonging
65    /// to all service methods will be sent.
66    pub method_id: Option<MethodId>,
67}
68
69impl TransactionFilter {
70    /// Creates a new transaction filter.
71    pub fn new(instance_id: InstanceId, method_id: Option<MethodId>) -> Self {
72        Self {
73            instance_id,
74            method_id,
75        }
76    }
77}
78
79/// Response to a WebSocket client. Roughly equivalent to `Result<T, String>`.
80#[serde(tag = "result", rename_all = "snake_case")]
81#[derive(Debug, PartialEq, Serialize, Deserialize)]
82pub enum Response<T> {
83    /// Successful response.
84    Success {
85        /// Payload attached to the response.
86        response: T,
87    },
88    /// Response carrying an error.
89    Error {
90        /// Error description.
91        description: String,
92    },
93}
94
95impl<T> Response<T> {
96    /// Creates a response with the specified value.
97    pub fn success(value: T) -> Self {
98        Response::Success { response: value }
99    }
100
101    /// Creates an erroneous response.
102    pub fn error(description: impl fmt::Display) -> Self {
103        Response::Error {
104            description: description.to_string(),
105        }
106    }
107
108    /// Converts response into a `Result`.
109    pub fn into_result(self) -> Result<T, String> {
110        match self {
111            Response::Success { response } => Ok(response),
112            Response::Error { description } => Err(description),
113        }
114    }
115}
116
117impl<T> From<Result<T, String>> for Response<T> {
118    fn from(res: Result<T, String>) -> Self {
119        match res {
120            Ok(value) => Self::success(value),
121            Err(description) => Response::Error { description },
122        }
123    }
124}
125
126/// Summary about a particular transaction in the blockchain. Does not include transaction content.
127#[derive(Debug, Serialize, Deserialize)]
128pub struct CommittedTransactionSummary {
129    /// Transaction identifier.
130    pub tx_hash: Hash,
131    /// ID of service.
132    pub instance_id: InstanceId,
133    /// ID of the method within service.
134    pub method_id: MethodId,
135    /// Result of transaction execution.
136    pub status: ExecutionStatus,
137    /// Transaction location in the blockchain.
138    pub location: TxLocation,
139    /// Proof of existence.
140    pub location_proof: ListProof<Hash>,
141    /// Approximate finalization time.
142    pub time: DateTime<Utc>,
143}
144
145impl CommittedTransactionSummary {
146    /// Constructs a transaction summary from the core schema.
147    pub fn new(schema: &Schema<impl Access>, tx_hash: &Hash) -> Option<Self> {
148        let tx = schema.transactions().get(tx_hash)?;
149        let tx = tx.payload();
150        let instance_id = tx.call_info.instance_id;
151        let method_id = tx.call_info.method_id;
152        let location = schema.transactions_locations().get(tx_hash)?;
153        let tx_result = schema.transaction_result(location)?;
154        let location_proof = schema
155            .block_transactions(location.block_height())
156            .get_proof(location.position_in_block().into());
157        let time = median_precommits_time(
158            &schema
159                .block_and_precommits(location.block_height())
160                .unwrap()
161                .precommits,
162        );
163        Some(Self {
164            tx_hash: *tx_hash,
165            instance_id,
166            method_id,
167            status: ExecutionStatus(tx_result),
168            location,
169            location_proof,
170            time,
171        })
172    }
173}
174
175/// Notification message passed to WebSocket clients.
176#[derive(Debug, Serialize, Deserialize)]
177#[serde(tag = "type", rename_all = "snake_case")]
178pub enum Notification {
179    /// Notification about new block.
180    Block(Block),
181    /// Notification about new transaction.
182    Transaction(CommittedTransactionSummary),
183}