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

Commit

Permalink
tendermint-rs: /broadcast_tx_* RPC endpoints
Browse files Browse the repository at this point in the history
Support for all methods of broadcasting transactions presently provided
by the Tendermint RPC API.

This implementation has been unit tested against the responses
documented at https://tendermint.com/rpc/ but does not yet have live
integration tests.
  • Loading branch information
tony-iqlusion committed Apr 24, 2019
1 parent 2d01b9b commit 11fe8dc
Show file tree
Hide file tree
Showing 16 changed files with 544 additions and 22 deletions.
16 changes: 8 additions & 8 deletions tendermint-rs/src/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ use subtle::{self, ConstantTimeEq};
use subtle_encoding::hex;

/// Size of an account ID in bytes
pub const ID_LENGTH: usize = 20;
const LENGTH: usize = 20;

/// Account IDs
#[derive(Copy, Clone, Hash)]
pub struct Id([u8; ID_LENGTH]);
pub struct Id([u8; LENGTH]);

impl Id {
/// Create a new account ID from raw bytes
pub fn new(bytes: [u8; ID_LENGTH]) -> Id {
pub fn new(bytes: [u8; LENGTH]) -> Id {
Id(bytes)
}

Expand Down Expand Up @@ -62,8 +62,8 @@ impl Debug for Id {
impl From<secp256k1::PublicKey> for Id {
fn from(pk: secp256k1::PublicKey) -> Id {
let digest = Sha256::digest(pk.as_bytes());
let mut bytes = [0u8; ID_LENGTH];
bytes.copy_from_slice(&digest[..ID_LENGTH]);
let mut bytes = [0u8; LENGTH];
bytes.copy_from_slice(&digest[..LENGTH]);
Id(bytes)
}
}
Expand All @@ -78,11 +78,11 @@ impl FromStr for Id {
.or_else(|_| hex::decode(s))
.map_err(|_| Error::Parse)?;

if bytes.len() != ID_LENGTH {
if bytes.len() != LENGTH {
return Err(Error::Parse);
}

let mut result_bytes = [0u8; ID_LENGTH];
let mut result_bytes = [0u8; LENGTH];
result_bytes.copy_from_slice(&bytes);
Ok(Id(result_bytes))
}
Expand All @@ -98,7 +98,7 @@ impl<'de> Deserialize<'de> for Id {
Self::from_str(&s).map_err(|_| {
de::Error::custom(format!(
"expected {}-character hex string, got {:?}",
ID_LENGTH * 2,
LENGTH * 2,
s
))
})
Expand Down
1 change: 1 addition & 0 deletions tendermint-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ pub use crate::{
public_key::{PublicKey, TendermintKey},
signature::Signature,
time::Time,
transaction::Transaction,
version::Version,
vote::Vote,
};
18 changes: 9 additions & 9 deletions tendermint-rs/src/node/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ use std::{
use subtle::{self, ConstantTimeEq};
use subtle_encoding::hex;

/// Size of a Node ID in bytes
pub const ID_LENGTH: usize = 20;
/// Length of a Node ID in bytes
pub const LENGTH: usize = 20;

/// Node IDs
#[derive(Copy, Clone, Hash)]
pub struct Id([u8; ID_LENGTH]);
pub struct Id([u8; LENGTH]);

impl Id {
/// Create a new Node ID from raw bytes
pub fn new(bytes: [u8; ID_LENGTH]) -> Id {
pub fn new(bytes: [u8; LENGTH]) -> Id {
Id(bytes)
}

Expand Down Expand Up @@ -62,8 +62,8 @@ impl Debug for Id {
impl From<ed25519::PublicKey> for Id {
fn from(pk: ed25519::PublicKey) -> Id {
let digest = Sha256::digest(pk.as_bytes());
let mut bytes = [0u8; ID_LENGTH];
bytes.copy_from_slice(&digest[..ID_LENGTH]);
let mut bytes = [0u8; LENGTH];
bytes.copy_from_slice(&digest[..LENGTH]);
Id(bytes)
}
}
Expand All @@ -78,11 +78,11 @@ impl FromStr for Id {
.or_else(|_| hex::decode(s))
.map_err(|_| Error::Parse)?;

if bytes.len() != ID_LENGTH {
if bytes.len() != LENGTH {
return Err(Error::Parse);
}

let mut result_bytes = [0u8; ID_LENGTH];
let mut result_bytes = [0u8; LENGTH];
result_bytes.copy_from_slice(&bytes);
Ok(Id(result_bytes))
}
Expand All @@ -98,7 +98,7 @@ impl<'de> Deserialize<'de> for Id {
Self::from_str(&s).map_err(|_| {
de::Error::custom(format!(
"expected {}-character hex string, got {:?}",
ID_LENGTH * 2,
LENGTH * 2,
s
))
})
Expand Down
28 changes: 27 additions & 1 deletion tendermint-rs/src/rpc/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
block::Height,
net,
rpc::{self, endpoint::*, Error, Response},
Genesis,
Genesis, Transaction,
};
use hyper::header;
use std::io::Read;
Expand Down Expand Up @@ -58,6 +58,32 @@ impl Client {
self.perform(blockchain::Request::new(min.into(), max.into()))
}

/// `/broadcast_tx_async`: broadcast a transaction, returning immediately.
pub fn broadcast_tx_async(
&self,
tx: Transaction,
) -> Result<broadcast::tx_async::Response, Error> {
self.perform(broadcast::tx_async::Request::new(tx))
}

/// `/broadcast_tx_sync`: broadcast a transaction, returning the response
/// from `CheckTx`.
pub fn broadcast_tx_sync(
&self,
tx: Transaction,
) -> Result<broadcast::tx_sync::Response, Error> {
self.perform(broadcast::tx_sync::Request::new(tx))
}

/// `/broadcast_tx_sync`: broadcast a transaction, returning the response
/// from `CheckTx`.
pub fn broadcast_tx_commit(
&self,
tx: Transaction,
) -> Result<broadcast::tx_commit::Response, Error> {
self.perform(broadcast::tx_commit::Request::new(tx))
}

/// `/commit`: get block commit at a given height.
pub fn commit<H>(&self, height: H) -> Result<commit::Response, Error>
where
Expand Down
1 change: 1 addition & 0 deletions tendermint-rs/src/rpc/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pub mod abci_info;
pub mod block;
pub mod blockchain;
pub mod broadcast;
pub mod commit;
pub mod genesis;
pub mod health;
Expand Down
145 changes: 145 additions & 0 deletions tendermint-rs/src/rpc/endpoint/broadcast.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
//! `/broadcast_tx_*` endpoint JSONRPC wrappers

pub mod tx_async;
pub mod tx_commit;
pub mod tx_sync;

use crate::Error;
use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer};
use std::{
fmt::{self, Display},
str::FromStr,
};
use subtle_encoding::hex;

/// Transaction broadcast response codes.
///
/// These presently use 0 for success and non-zero for errors, however there
/// is ample discussion about potentially supporting non-zero success cases
/// as well.
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum Code {
/// Success
Ok,

/// Error codes
Err(u32),
}

impl Code {
/// Was the response OK?
pub fn is_ok(self) -> bool {
match self {
Code::Ok => true,
Code::Err(_) => false,
}
}

/// Was the response an error?
pub fn is_err(self) -> bool {
!self.is_ok()
}

/// Get the integer error value for this code
pub fn value(self) -> u32 {
u32::from(self)
}
}

impl From<u32> for Code {
fn from(value: u32) -> Code {
match value {
0 => Code::Ok,
err => Code::Err(err),
}
}
}

impl From<Code> for u32 {
fn from(code: Code) -> u32 {
match code {
Code::Ok => 0,
Code::Err(err) => err,
}
}
}

impl<'de> Deserialize<'de> for Code {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Ok(Code::from(
String::deserialize(deserializer)?
.parse::<u32>()
.map_err(|e| D::Error::custom(format!("{}", e)))?,
))
}
}

impl Serialize for Code {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.value().serialize(serializer)
}
}

/// Transaction data
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Data(Vec<u8>);

impl Data {
/// Borrow the data as bytes
pub fn as_bytes(&self) -> &[u8] {
self.0.as_ref()
}
}

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

impl Display for Data {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in &self.0 {
write!(f, "{:02X}", byte)?;
}
Ok(())
}
}

impl FromStr for Data {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
// Accept either upper or lower case hex
let bytes = hex::decode_upper(s)
.or_else(|_| hex::decode(s))
.map_err(|_| Error::Parse)?;

Ok(Data(bytes))
}
}

impl<'de> Deserialize<'de> for Data {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let bytes = hex::decode(String::deserialize(deserializer)?.as_bytes())
.map_err(|e| D::Error::custom(format!("{}", e)))?;

Ok(Self(bytes))
}
}

impl Serialize for Data {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.to_string().serialize(serializer)
}
}

/// Transaction log
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Log(String);

impl Display for Log {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
45 changes: 45 additions & 0 deletions tendermint-rs/src/rpc/endpoint/broadcast/tx_async.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//! `/broadcast_tx_async`: broadcast a transaction and return immediately.

use super::{Code, Data, Log};
use crate::{rpc, transaction, Transaction};
use serde::{Deserialize, Serialize};

/// `/broadcast_tx_async`: broadcast a transaction and return immediately.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Request {
/// Transaction to broadcast
pub tx: Transaction,
}

impl Request {
/// Create a new async transaction broadcast RPC request
pub fn new(tx: Transaction) -> Request {
Request { tx }
}
}

impl rpc::Request for Request {
type Response = Response;

fn method(&self) -> rpc::Method {
rpc::Method::BroadcastTxAsync
}
}

/// Response from either an async or sync transaction broadcast request.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Response {
/// Code
pub code: Code,

/// Data
pub data: Data,

/// Log
pub log: Log,

/// Transaction hash
pub hash: transaction::Hash,
}

impl rpc::Response for Response {}
Loading

0 comments on commit 11fe8dc

Please sign in to comment.