Skip to content
This repository has been archived by the owner on Jun 3, 2020. It is now read-only.

Commit

Permalink
tendermint-rs: Initial "rpc" feature and JSONRPC types
Browse files Browse the repository at this point in the history
Adds a set of JSONRPC request and response types, designed to construct
JSON requests to the Tendermint RPC (HTTP) API, and parse the returned
JSON responses.
  • Loading branch information
tony-iqlusion committed Apr 20, 2019
1 parent 9353fa2 commit ea751ae
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 20 deletions.
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ jobs:
rustc --version
cargo --version
cargo test --all --all-features -- --test-threads 1
cd tendermint-rs && cargo test --release --features=rpc
- run:
name: audit
command: |
Expand Down
18 changes: 10 additions & 8 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@ use crate::{
};
use signatory::{ed25519, Decode, Encode, PublicKeyed};
use signatory_dalek::Ed25519Signer;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::{
panic,
path::Path,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
thread::{self, JoinHandle},
time::Duration,
};
use tendermint::{chain, node, secret_connection, Address};
use tendermint::{chain, net, node, secret_connection};

/// How long to wait after a crash before respawning (in seconds)
pub const RESPAWN_DELAY: u64 = 1;
Expand Down Expand Up @@ -67,13 +69,13 @@ fn client_loop(config: ValidatorConfig, should_term: &Arc<AtomicBool>) {
return;
}

let session_result = match &addr {
Address::Tcp {
let session_result = match addr {
net::Address::Tcp {
peer_id,
host,
ref host,
port,
} => match &secret_key {
Some(path) => tcp_session(chain_id, *peer_id, host, *port, path, should_term),
Some(path) => tcp_session(chain_id, peer_id, host, port, path, should_term),
None => {
error!(
"config error: missing field `secret_key` for validator {}",
Expand All @@ -82,7 +84,7 @@ fn client_loop(config: ValidatorConfig, should_term: &Arc<AtomicBool>) {
return;
}
},
Address::Unix { path } => unix_session(chain_id, path, should_term),
net::Address::Unix { ref path } => unix_session(chain_id, path, should_term),
};

if let Err(e) = session_result {
Expand Down
4 changes: 2 additions & 2 deletions src/config/validator.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use std::path::PathBuf;
use tendermint::{chain, Address};
use tendermint::{chain, net};

/// Validator configuration
#[derive(Clone, Deserialize, Debug)]
pub struct ValidatorConfig {
/// Address of the validator (`tcp://` or `unix://`)
pub addr: Address,
pub addr: net::Address,

/// Chain ID of the Tendermint network this validator is part of
pub chain_id: chain::Id,
Expand Down
7 changes: 5 additions & 2 deletions tendermint-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ edition = "2018"
description = """
Tendermint is a high-performance blockchain consensus engine that powers
Byzantine fault tolerant applications written in any programming language.
This crate provides types for representing information about Tendermint
blockchain networks, including chain IDs, block IDs, and block heights.
This crate provides core types for representing information about Tendermint
blockchain networks, including chain information types, secret connections,
and remote procedure calls (JSONRPC).
"""

authors = [
Expand All @@ -38,6 +39,7 @@ prost-amino-derive = { version = "0.4.0", optional = true }
rand_os = { version = "0.1", optional = true }
ring = { version = "0.14", optional = true }
serde = { version = "1", optional = true, features = ["derive"] }
serde_json = { version = "1", optional = true }
signatory = { version = "0.11.2", features = ["ed25519", "ecdsa"] }
signatory-dalek = { version = "0.11", optional = true }
sha2 = { version = "0.8", default-features = false }
Expand All @@ -53,6 +55,7 @@ serde_json = "1"
[features]
default = ["serde", "tai64"]
amino-types = ["prost-amino", "prost-amino-derive"]
rpc = ["serde", "serde_json"]
secret-connection = [
"amino-types",
"byteorder",
Expand Down
15 changes: 7 additions & 8 deletions tendermint-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ extern crate prost_amino as prost;
extern crate prost_amino_derive as prost_derive;

pub mod account;
pub mod address;
pub mod algorithm;
#[cfg(feature = "amino-types")]
pub mod amino_types;
Expand All @@ -35,22 +34,22 @@ pub mod chain;
pub mod error;
pub mod hash;
pub mod moniker;
pub mod net;
pub mod node;
pub mod public_keys;
#[cfg(feature = "rpc")]
pub mod rpc;
#[cfg(feature = "secret-connection")]
pub mod secret_connection;
pub mod timestamp;

#[cfg(feature = "secret-connection")]
pub use crate::secret_connection::SecretConnection;
pub use crate::{
address::*,
algorithm::*,
block::{ParseHeight as ParseBlockHeight, ParseId as ParseBlockId},
chain::ParseId as ParseChainId,
algorithm::{HashAlgorithm, SignatureAlgorithm},
error::Error,
hash::*,
hash::Hash,
moniker::Moniker,
public_keys::*,
timestamp::*,
public_keys::{PublicKey, TendermintKey},
timestamp::Timestamp,
};
File renamed without changes.
6 changes: 6 additions & 0 deletions tendermint-rs/src/rpc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//! Tendermint RPC: JSONRPC over HTTP support
//!
//! Wraps the RPC API described at: <https://tendermint.com/rpc/>

pub mod request;
pub mod response;
36 changes: 36 additions & 0 deletions tendermint-rs/src/rpc/request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! JSONRPC requests

use failure::Error;
use serde::{Deserialize, Serialize};
use std::{
fmt::{self, Display},
str::FromStr,
};

/// JSONRPC requests
pub trait Request {
/// Response type for this command
type Response: super::response::Response;

/// Path for this request
fn path(&self) -> Path;
}

/// JSONRPC request paths
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Path(String);

impl Display for Path {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}

impl FromStr for Path {
type Err = Error;

/// Parse a request path from a string
fn from_str(path: &str) -> Result<Self, Error> {
Ok(Path(path.to_owned()))
}
}
104 changes: 104 additions & 0 deletions tendermint-rs/src/rpc/response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//! JSONRPC response types

// TODO(tarcieri): remove this when functions below are all used
#![allow(dead_code)]

use failure::{format_err, Error};
use serde::{
de::{DeserializeOwned, Error as DeError},
Deserialize, Deserializer, Serialize, Serializer,
};
use std::{
fmt::{self, Display},
time::Duration,
};

/// JSONRPC responses
pub trait Response: Serialize + DeserializeOwned + Sized {
/// Parse a JSONRPC response from a JSON string
fn from_json(response: &str) -> Result<Self, Error> {
let wrapper: ResponseWrapper<Self> =
serde_json::from_str(response).map_err(|e| format_err!("error parsing JSON: {}", e))?;

// TODO(tarcieri): check JSONRPC version/ID?
Ok(wrapper.result)
}
}

/// JSONRPC response wrapper (i.e. message envelope)
#[derive(Debug, Deserialize, Serialize)]
pub struct ResponseWrapper<R> {
/// JSONRPC version
pub jsonrpc: Version,

/// ID
pub id: Id,

/// Result
pub result: R,
}

/// JSONRPC version
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Version(String);

impl Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}

/// JSONRPC ID
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Id(String);

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

/// Parse `u64` from a JSON string
pub(crate) fn parse_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
D: Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse::<u64>()
.map_err(|e| D::Error::custom(format!("{}", e)))
}

/// Serialize `u64` as a JSON string
#[allow(clippy::trivially_copy_pass_by_ref)]
pub(crate) fn serialize_u64<S>(value: &u64, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
format!("{}", value).serialize(serializer)
}

/// Parse `Duration` from a JSON string containing a nanosecond count
fn parse_duration<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
// TODO(tarcieri): handle 64-bit overflow?
let nanos = String::deserialize(deserializer)?
.parse::<u64>()
.map_err(|e| D::Error::custom(format!("{}", e)))?;

Ok(Duration::from_nanos(nanos))
}

/// Serialize `Duration` as a JSON string containing a nanosecond count
fn serialize_duration<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// TODO(tarcieri): use `as_nanos` when we're Rust 1.33+
format!(
"{}",
(duration.as_secs() * 1_000_000_000) + duration.subsec_nanos() as u64
)
.serialize(serializer)
}

0 comments on commit ea751ae

Please sign in to comment.