Skip to content

Commit

Permalink
feat(rust): expose transaction receipt type and query
Browse files Browse the repository at this point in the history
  • Loading branch information
mehcode committed May 23, 2022
1 parent 21caa46 commit dc5f358
Show file tree
Hide file tree
Showing 15 changed files with 321 additions and 23 deletions.
31 changes: 29 additions & 2 deletions protobufs/rust/build.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::env;
use std::fs::{create_dir_all, read_dir};
use std::fs::{self, create_dir_all, read_dir};
use std::path::Path;

const DERIVE_EQ_HASH: &str = "#[derive(Eq, Hash)]";
Expand Down Expand Up @@ -48,12 +48,28 @@ fn main() -> anyhow::Result<()> {
.type_attribute("proto.GrantedTokenAllowance", DERIVE_EQ_HASH)
.type_attribute("proto.Duration", DERIVE_EQ_HASH_COPY);

// the ResponseCodeEnum should be marked as #[non_exhaustive] so
// adding variants does not trigger a breaking change
cfg = cfg.type_attribute("proto.ResponseCodeEnum", "#[non_exhaustive]");

// the ResponseCodeEnum is not documented in the proto source
cfg = cfg.type_attribute("proto.ResponseCodeEnum", r#"#[doc = "
Returned in `TransactionReceipt`, `Error::PreCheckStatus`, and `Error::ReceiptStatus`.
The success variant is `Success` which is what a `TransactionReceipt` will contain for a
successful transaction.
"]"#);

if cfg!(feature = "serde") {
cfg = cfg.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]");
cfg = cfg.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]")
.type_attribute("proto.ResponseCodeEnum", "#[serde(rename_all = \"SCREAMING_SNAKE_CASE\")]");
}

cfg.compile(&services, &["../src/services/"])?;

// NOTE: prost generates rust doc comments and fails to remove the leading * line
remove_useless_comments(&Path::new(&env::var("OUT_DIR")?).join("proto.rs"))?;

// mirror
// NOTE: must be compiled in a separate folder otherwise it will overwrite the previous build

Expand Down Expand Up @@ -155,3 +171,14 @@ fn main() -> anyhow::Result<()> {

Ok(())
}

fn remove_useless_comments(path: &Path) -> anyhow::Result<()> {
let mut contents = fs::read_to_string(path)?;

contents = contents.replace("///*", "");
contents = contents.replace("/// UNDOCUMENTED", "");

fs::write(path, contents)?;

Ok(())
}
2 changes: 1 addition & 1 deletion sdk/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ serde = { version = "1.0.137", features = ["derive"] }
thiserror = "1.0.31"
time = { version = "0.3.9", features = ["serde"] }
tokio = { version = "1.18.1" }
hedera-proto = { path = "../../protobufs/rust", features = ["time_0_3"] }
hedera-proto = { path = "../../protobufs/rust", features = ["serde", "time_0_3"] }
serde_json = "1.0.79"
serde_with = "1.12.1"
tonic = { version = "0.7.1", features = ["compression"] }
Expand Down
6 changes: 3 additions & 3 deletions sdk/rust/examples/transfer_hbar.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::str::FromStr;

use hedera::{AccountId, Client, PrivateKey, TransferTransaction};
use hedera::{AccountId, Client, PrivateKey, TransferTransaction, TransactionReceiptQuery};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
Expand All @@ -16,9 +16,9 @@ async fn main() -> anyhow::Result<()> {

let amount = 10_000;

TransferTransaction::new()
let response = TransferTransaction::new()
.hbar_transfer(sender_id, -amount)
.hbar_transfer(receiver_id, 2)
.hbar_transfer(receiver_id, amount)
.execute(&client)
.await?;

Expand Down
13 changes: 12 additions & 1 deletion sdk/rust/src/contract/contract_id.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use hedera_proto::services;

use crate::ToProtobuf;
use crate::{FromProtobuf, ToProtobuf};

/// The unique identifier for a smart contract on Hedera.
#[derive(Debug, serde::Serialize, serde::Deserialize, Hash, PartialEq, Eq, Clone, Copy)]
Expand All @@ -11,6 +11,17 @@ pub struct ContractId {
pub num: u64,
}

impl FromProtobuf for ContractId {
type Protobuf = services::ContractId;

fn from_protobuf(pb: Self::Protobuf) -> crate::Result<Self> {
let account = pb_getf!(pb, contract)?;
let num = pb_getv!(account, ContractNum, services::contract_id::Contract);

Ok(Self { num: num as u64, shard: pb.shard_num as u64, realm: pb.realm_num as u64 })
}
}

/// The identifier for a smart contract represented with an EVM address instead of a
/// contract number.
#[derive(Debug, serde::Serialize, serde::Deserialize, Hash, PartialEq, Eq, Clone)]
Expand Down
11 changes: 3 additions & 8 deletions sdk/rust/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use std::error::Error as StdError;
use std::result::Result as StdResult;

use hedera_proto::services::ResponseCodeEnum;

use crate::{AccountId, TransactionId};
use crate::{AccountId, Status, TransactionId};

pub type Result<T> = StdResult<T, Error>;

Expand All @@ -25,7 +23,7 @@ pub enum Error {
// FIXME: Use hedera::Status (once available)
// TODO: Add transaction_id: Option<TransactionId>
#[error("transaction `{}` failed pre-check with status `{status:?}`", .transaction_id.as_ref().map(|id| id.to_string()).as_deref().unwrap_or("_"))]
PreCheckStatus { status: ResponseCodeEnum, transaction_id: Option<TransactionId> },
PreCheckStatus { status: Status, transaction_id: Option<TransactionId> },

/// Failed to parse a basic type from string (ex. AccountId, ContractId, TransactionId, etc.).
#[error("failed to parse: {0}")]
Expand Down Expand Up @@ -79,10 +77,7 @@ impl Error {
Self::Signature(error.into())
}

pub(crate) fn pre_check(
status: ResponseCodeEnum,
transaction_id: Option<TransactionId>,
) -> Self {
pub(crate) fn pre_check(status: Status, transaction_id: Option<TransactionId>) -> Self {
Self::PreCheckStatus { status, transaction_id }
}
}
11 changes: 5 additions & 6 deletions sdk/rust/src/execute.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use async_trait::async_trait;
use backoff::backoff::Backoff;
use backoff::ExponentialBackoff;
use hedera_proto::services::ResponseCodeEnum;
use prost::Message;
use rand::thread_rng;
use tokio::time::sleep;
use tonic::transport::Channel;

use crate::{AccountId, Client, Error, TransactionId};
use crate::{AccountId, Client, Error, Status, TransactionId};

#[async_trait]
pub(crate) trait Execute {
Expand Down Expand Up @@ -145,9 +144,9 @@ where

let pre_check_status = E::response_pre_check_status(&response)?;

match ResponseCodeEnum::from_i32(pre_check_status) {
match Status::from_i32(pre_check_status) {
Some(status) => match status {
ResponseCodeEnum::Ok => {
Status::Ok => {
// TODO: another function in the Execute trait to see if we need to
// retry yet again

Expand All @@ -159,14 +158,14 @@ where
);
}

ResponseCodeEnum::Busy | ResponseCodeEnum::PlatformNotActive => {
Status::Busy | Status::PlatformNotActive => {
// NOTE: this is a "busy" node
// try the next node in our allowed list, immediately
last_error = Some(Error::pre_check(status, transaction_id));
continue;
}

ResponseCodeEnum::TransactionExpired if explicit_transaction_id.is_none() => {
Status::TransactionExpired if explicit_transaction_id.is_none() => {
// the transaction that was generated has since expired
// re-generate the transaction ID and try again, immediately
last_error = Some(Error::pre_check(status, transaction_id));
Expand Down
12 changes: 12 additions & 0 deletions sdk/rust/src/file/file_id.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use hedera_proto::services;

use crate::FromProtobuf;

/// The unique identifier for a file on Hedera.
#[derive(Debug, serde::Serialize, serde::Deserialize, Hash, PartialEq, Eq, Clone, Copy)]
#[repr(C)]
Expand All @@ -6,3 +10,11 @@ pub struct FileId {
pub realm: u64,
pub num: u64,
}

impl FromProtobuf for FileId {
type Protobuf = services::FileId;

fn from_protobuf(pb: Self::Protobuf) -> crate::Result<Self> {
Ok(Self { num: pb.file_num as u64, shard: pb.shard_num as u64, realm: pb.realm_num as u64 })
}
}
5 changes: 5 additions & 0 deletions sdk/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ mod topic;
mod transaction;
mod transaction_hash;
mod transaction_id;
mod transaction_receipt;
mod transaction_receipt_query;
mod transaction_response;
mod transfer_transaction;

Expand All @@ -44,6 +46,7 @@ pub use client::Client;
pub use contract::{ContractEvmAddress, ContractId, ContractIdOrEvmAddress};
pub use error::{Error, Result};
pub use file::FileId;
pub use hedera_proto::services::ResponseCodeEnum as Status;
pub use key::{Key, PrivateKey, PublicKey};
pub use protobuf::{FromProtobuf, ToProtobuf};
pub use query::{AnyQuery, Query};
Expand All @@ -55,5 +58,7 @@ pub use topic::TopicId;
pub use transaction::Transaction;
pub use transaction_hash::TransactionHash;
pub use transaction_id::TransactionId;
pub use transaction_receipt::TransactionReceipt;
pub use transaction_receipt_query::TransactionReceiptQuery;
pub use transaction_response::TransactionResponse;
pub use transfer_transaction::TransferTransaction;
11 changes: 10 additions & 1 deletion sdk/rust/src/query/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use crate::account::{AccountBalanceQueryData, AccountInfoQueryData};
use crate::query::payment_transaction::PaymentTransactionData;
use crate::query::QueryExecute;
use crate::transaction::AnyTransactionBody;
use crate::{AccountBalance, AccountInfo, FromProtobuf, Query, Transaction};
use crate::transaction_receipt_query::TransactionReceiptQueryData;
use crate::{AccountBalance, AccountInfo, FromProtobuf, Query, Transaction, TransactionReceipt};

/// Any possible query that may be executed on the Hedera network.
pub type AnyQuery = Query<AnyQueryData>;
Expand All @@ -18,20 +19,23 @@ pub type AnyQuery = Query<AnyQueryData>;
pub enum AnyQueryData {
AccountBalance(AccountBalanceQueryData),
AccountInfo(AccountInfoQueryData),
TransactionReceipt(TransactionReceiptQueryData),
}

#[derive(Debug, serde::Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub enum AnyQueryResponse {
AccountBalance(AccountBalance),
AccountInfo(AccountInfo),
TransactionReceipt(TransactionReceipt),
}

impl ToQueryProtobuf for AnyQueryData {
fn to_query_protobuf(&self, header: services::QueryHeader) -> services::Query {
match self {
Self::AccountBalance(data) => data.to_query_protobuf(header),
Self::AccountInfo(data) => data.to_query_protobuf(header),
Self::TransactionReceipt(data) => data.to_query_protobuf(header),
}
}
}
Expand All @@ -44,6 +48,7 @@ impl QueryExecute for AnyQueryData {
match self {
Self::AccountInfo(query) => query.is_payment_required(),
Self::AccountBalance(query) => query.is_payment_required(),
Self::TransactionReceipt(query) => query.is_payment_required(),
}
}

Expand All @@ -55,6 +60,7 @@ impl QueryExecute for AnyQueryData {
match self {
Self::AccountInfo(query) => query.execute(channel, request).await,
Self::AccountBalance(query) => query.execute(channel, request).await,
Self::TransactionReceipt(query) => query.execute(channel, request).await,
}
}
}
Expand All @@ -69,6 +75,9 @@ impl FromProtobuf for AnyQueryResponse {
use services::response::Response::*;

Ok(match response {
TransactionGetReceipt(_) => {
Self::TransactionReceipt(TransactionReceipt::from_protobuf(response)?)
}
CryptoGetInfo(_) => Self::AccountInfo(AccountInfo::from_protobuf(response)?),
CryptogetAccountBalance(_) => {
Self::AccountBalance(AccountBalance::from_protobuf(response)?)
Expand Down
12 changes: 12 additions & 0 deletions sdk/rust/src/schedule/schedule_id.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use hedera_proto::services;

use crate::FromProtobuf;

/// The unique identifier for a schedule on Hedera.
#[derive(Debug, serde::Serialize, serde::Deserialize, Hash, PartialEq, Eq, Clone, Copy)]
#[repr(C)]
Expand All @@ -6,3 +10,11 @@ pub struct ScheduleId {
pub realm: u64,
pub num: u64,
}

impl FromProtobuf for ScheduleId {
type Protobuf = services::ScheduleId;

fn from_protobuf(pb: Self::Protobuf) -> crate::Result<Self> {
Ok(Self { num: pb.schedule_num as u64, shard: pb.shard_num as u64, realm: pb.realm_num as u64 })
}
}
12 changes: 12 additions & 0 deletions sdk/rust/src/token/token_id.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use hedera_proto::services;

use crate::FromProtobuf;

/// The unique identifier for a token on Hedera.
#[derive(Debug, serde::Serialize, serde::Deserialize, Hash, PartialEq, Eq, Clone, Copy)]
#[repr(C)]
Expand All @@ -6,3 +10,11 @@ pub struct TokenId {
pub realm: u64,
pub num: u64,
}

impl FromProtobuf for TokenId {
type Protobuf = services::TokenId;

fn from_protobuf(pb: Self::Protobuf) -> crate::Result<Self> {
Ok(Self { num: pb.token_num as u64, shard: pb.shard_num as u64, realm: pb.realm_num as u64 })
}
}
12 changes: 12 additions & 0 deletions sdk/rust/src/topic/topic_id.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use hedera_proto::services;

use crate::FromProtobuf;

/// The unique identifier for a topic on Hedera.
#[derive(Debug, serde::Serialize, serde::Deserialize, Hash, PartialEq, Eq, Clone, Copy)]
#[repr(C)]
Expand All @@ -6,3 +10,11 @@ pub struct TopicId {
pub realm: u64,
pub num: u64,
}

impl FromProtobuf for TopicId {
type Protobuf = services::TopicId;

fn from_protobuf(pb: Self::Protobuf) -> crate::Result<Self> {
Ok(Self { num: pb.topic_num as u64, shard: pb.shard_num as u64, realm: pb.realm_num as u64 })
}
}
20 changes: 19 additions & 1 deletion sdk/rust/src/transaction_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use rand::{thread_rng, Rng};
use serde_with::{DeserializeFromStr, SerializeDisplay};
use time::{Duration, OffsetDateTime};

use crate::{AccountId, Error, ToProtobuf};
use crate::{AccountId, Error, FromProtobuf, ToProtobuf};

/// The client-generated ID for a transaction.
///
Expand Down Expand Up @@ -62,6 +62,24 @@ impl Display for TransactionId {
}
}

impl FromProtobuf for TransactionId {
type Protobuf = services::TransactionId;

fn from_protobuf(pb: Self::Protobuf) -> crate::Result<Self> {
let account_id = pb_getf!(pb, account_id)?;
let account_id = AccountId::from_protobuf(account_id)?;

let valid_start = pb_getf!(pb, transaction_valid_start)?;

Ok(Self {
account_id,
valid_start: valid_start.into(),
nonce: (pb.nonce != 0).then(|| pb.nonce),
scheduled: pb.scheduled,
})
}
}

// TODO: add unit tests to prove parsing
// TODO: potentially improve parsing with `nom` or `combine`
impl FromStr for TransactionId {
Expand Down
Loading

0 comments on commit dc5f358

Please sign in to comment.