use crate::{
error::ErrorKind,
proto::{self, ToProto},
AccountId, Client, Duration, SecretKey, TransactionId,
};
use failure::Error;
use std::sync::Arc;
use protobuf::{Message, RepeatedField};
use grpc::ClientStub;
use crate::proto::CryptoService_grpc::CryptoService;
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(u8)]
pub enum PreCheckCode {
Ok = 0,
InvalidTransaction = 1,
InvalidAccount = 2,
InsufficientFee = 3,
InsufficientBalance = 4,
Duplicate = 5,
Busy = 6,
NotSupported = 7,
}
impl From<proto::TransactionResponse::NodeTransactionPrecheckCode> for PreCheckCode {
fn from(code: proto::TransactionResponse::NodeTransactionPrecheckCode) -> Self {
use self::proto::TransactionResponse::NodeTransactionPrecheckCode::*;
match code {
OK => PreCheckCode::Ok,
INVALID_TRANSACTION => PreCheckCode::InvalidTransaction,
INVALID_ACCOUNT => PreCheckCode::InvalidAccount,
INSUFFICIENT_FEE => PreCheckCode::InsufficientFee,
INSUFFICIENT_BALANCE => PreCheckCode::InsufficientBalance,
DUPLICATE => PreCheckCode::Duplicate,
BUSY => PreCheckCode::Busy,
NOT_SUPPORTED => PreCheckCode::NotSupported,
}
}
}
#[repr(C)]
pub struct TransactionResponse {
pub id: TransactionId,
}
pub struct Transaction<T> {
client: Arc<grpc::Client>,
operator: Option<AccountId>,
node: Option<AccountId>,
secrets: Vec<SecretKey>,
memo: Option<String>,
pub(crate) inner: Box<T>,
}
impl<T> Transaction<T> {
pub(crate) fn new(client: &Client, inner: T) -> Self {
Self {
client: client.inner.clone(),
operator: None,
node: None,
memo: None,
secrets: Vec::new(),
inner: Box::new(inner),
}
}
pub fn memo(&mut self, memo: impl Into<String>) -> &mut Self {
self.memo = Some(memo.into());
self
}
pub fn operator(&mut self, id: AccountId) -> &mut Self {
self.operator = Some(id);
self
}
pub fn node(&mut self, id: AccountId) -> &mut Self {
self.node = Some(id);
self
}
pub fn sign(&mut self, secret: SecretKey) -> &mut Self {
self.secrets.push(secret);
self
}
}
impl<T> Transaction<T>
where
Transaction<T>: ToProto<proto::Transaction::Transaction>,
T: ToProto<proto::Transaction::TransactionBody_oneof_data>,
{
pub fn execute(self) -> Result<TransactionResponse, Error> {
use self::proto::Transaction::TransactionBody_oneof_data::*;
let tx: proto::Transaction::Transaction = self.to_proto()?;
let id = tx.get_body().get_transactionID().clone().into();
let client = proto::CryptoService_grpc::CryptoServiceClient::with_client(self.client);
let o = Default::default();
let response = match tx.get_body().data {
Some(cryptoCreateAccount(_)) => client.create_account(o, tx),
Some(cryptoTransfer(_)) => client.crypto_transfer(o, tx),
_ => unimplemented!(),
};
let response = response.wait_drop_metadata()?;
match response.get_nodeTransactionPrecheckCode().into() {
PreCheckCode::Ok => Ok(TransactionResponse { id, }),
code => Err(ErrorKind::PreCheck(code))?,
}
}
}
impl<T> ToProto<proto::Transaction::Transaction> for Transaction<T>
where
Transaction<T>: ToProto<proto::Transaction::TransactionBody>,
T: ToProto<proto::Transaction::TransactionBody_oneof_data>,
{
fn to_proto(&self) -> Result<proto::Transaction::Transaction, Error> {
let body = ToProto::<proto::Transaction::TransactionBody>::to_proto(self)?;
let body_bytes = body.write_to_bytes().unwrap();
let signatures: Result<Vec<proto::BasicTypes::Signature>, Error> = self
.secrets
.iter()
.map(|secret| Ok(secret.sign(&body_bytes).to_proto()?))
.collect();
let mut signature_list = proto::BasicTypes::SignatureList::new();
signature_list.set_sigs(RepeatedField::from_vec(signatures?));
let mut tx = proto::Transaction::Transaction::new();
tx.set_body(body);
tx.set_sigs(signature_list);
Ok(tx)
}
}
impl<T> ToProto<proto::Transaction::TransactionBody> for Transaction<T>
where
T: ToProto<proto::Transaction::TransactionBody_oneof_data>,
{
fn to_proto(&self) -> Result<proto::Transaction::TransactionBody, Error> {
let account_id = self
.operator
.ok_or_else(|| ErrorKind::MissingField("account_id"))?;
let tx_id = TransactionId::new(account_id);
let mut body = proto::Transaction::TransactionBody::new();
let node = self.node.ok_or_else(|| ErrorKind::MissingField("node"))?;
body.set_nodeAccountID(node.to_proto()?);
body.set_transactionValidDuration(Duration::new(120, 0).to_proto()?);
body.set_transactionFee(10);
body.set_generateRecord(false);
body.set_transactionID(tx_id.to_proto()?);
body.data = Some(self.inner.to_proto()?);
body.set_memo(if let Some(memo) = &self.memo {
memo.to_owned()
} else {
String::new()
});
Ok(body)
}
}