From 7d6c4cb5fb68f52fa86052dfa6951ef478ff8f2b Mon Sep 17 00:00:00 2001 From: Coa Date: Wed, 4 Dec 2024 17:09:38 +0100 Subject: [PATCH] Implement send_transaction2 --- crates/antelope/src/api/v1/chain.rs | 68 +++++++++++++++++++--- crates/antelope/src/api/v1/structs.rs | 74 +++++++++++++++++++++++- crates/antelope/src/chain/transaction.rs | 4 +- 3 files changed, 136 insertions(+), 10 deletions(-) diff --git a/crates/antelope/src/api/v1/chain.rs b/crates/antelope/src/api/v1/chain.rs index 457bd1e..3c526ba 100644 --- a/crates/antelope/src/api/v1/chain.rs +++ b/crates/antelope/src/api/v1/chain.rs @@ -3,16 +3,18 @@ use std::fmt::Debug; use serde_json::{self, Value}; use crate::api::v1::structs::{ - ABIResponse, EncodingError, GetBlockResponse, GetTransactionStatusResponse, ServerError, + ABIResponse, EncodingError, GetBlockResponse, GetTransactionStatusResponse, + SendTransaction2Request, ServerError, }; use crate::chain::checksum::{Checksum160, Checksum256}; use crate::{ api::{ client::Provider, v1::structs::{ - AccountObject, ClientError, ErrorResponse, GetInfoResponse, GetTableRowsParams, - GetTableRowsResponse, SendTransactionResponse, SendTransactionResponseError, - TableIndexType, + AccountObject, ClientError, ErrorResponse, ErrorResponse2, GetInfoResponse, + GetTableRowsParams, GetTableRowsResponse, SendTransaction2Options, + SendTransaction2Response, SendTransactionResponse, SendTransactionResponse2Error, + SendTransactionResponseError, TableIndexType, }, }, chain::{ @@ -35,8 +37,6 @@ impl ChainAPI { ChainAPI { provider } } - // pub async fn get_abi(&self) -> Result< - pub async fn get_account( &self, account_name: String, @@ -155,6 +155,8 @@ impl ChainAPI { } } + /// send_transaction sends transaction to telos using /v1/chain/send_transaction + /// and using ZLIB compression type. pub async fn send_transaction( &self, trx: SignedTransaction, @@ -165,7 +167,10 @@ impl ChainAPI { let trx_json = packed.to_json(); let result = self .provider - .post(String::from("/v1/chain/send_transaction"), Some(trx_json)) + .post( + String::from("/v1/chain/send_transaction"), + Some(trx_json.to_string()), + ) .await .map_err(|_| ClientError::NETWORK("Failed to send transaction".into()))?; @@ -195,6 +200,55 @@ impl ChainAPI { } } + /// send_transaction2 sends transaction to telos using /v1/chain/send_transaction2 + /// which enables retry in case of transaction failure using ZLIB compression type. + pub async fn send_transaction2( + &self, + trx: SignedTransaction, + options: Option, + ) -> Result> { + let packed_transaction = PackedTransaction::from_signed(trx, CompressionType::ZLIB) + .map_err(|_| ClientError::encoding("Failed to pack transaction".into()))?; + + let request_body = SendTransaction2Request::build(packed_transaction, options); + + let request_body_str = serde_json::to_string(&request_body) + .map_err(|_| ClientError::encoding("Failed to serialize request body".into()))?; + + // Send the request to the endpoint + let result = self + .provider + .post( + String::from("/v1/chain/send_transaction2"), + Some(request_body_str), + ) + .await + .map_err(|_| ClientError::NETWORK("Failed to send transaction".into()))?; + + // tracing::warn!("Result of the send_transaction2: {result}"); + + // Deserialize the response + match serde_json::from_str::(&result) { + Ok(response) => match response.processed.except { + Some(error) => Err(ClientError::SERVER(ServerError { error })), + None => Ok(response), + }, + Err(error) => { + tracing::error!("Failed to deserialize send_transactions2 response: {error}"); + + // Try to parse an error response + match serde_json::from_str::(&result) { + Ok(error_response) => Err(ClientError::SERVER(ServerError { + error: error_response.error, + })), + Err(e) => Err(ClientError::ENCODING(EncodingError { + message: format!("Failed to parse response: {}", e), + })), + } + } + } + } + pub async fn get_transaction_status( &self, trx_id: Checksum256, diff --git a/crates/antelope/src/api/v1/structs.rs b/crates/antelope/src/api/v1/structs.rs index 2354da6..ad0627a 100644 --- a/crates/antelope/src/api/v1/structs.rs +++ b/crates/antelope/src/api/v1/structs.rs @@ -9,6 +9,7 @@ use std::mem::discriminant; use crate::chain::abi::ABI; use crate::chain::public_key::PublicKey; use crate::chain::signature::Signature; +use crate::chain::transaction::PackedTransaction; use crate::chain::{ action::{Action, PermissionLevel}, asset::{deserialize_asset, deserialize_optional_asset, Asset}, @@ -172,6 +173,22 @@ pub struct ProcessedTransaction { pub account_ram_delta: Option, } +#[derive(Debug, Serialize, Deserialize)] +pub struct ProcessedTransaction2 { + pub id: String, + pub block_num: u64, + pub block_time: String, + pub producer_block_id: Option, + pub receipt: Option, + pub elapsed: u64, + pub net_usage: u32, + pub scheduled: bool, + pub action_traces: Vec, + pub account_ram_delta: Option, + pub except: Option, + pub error_code: Option, +} + #[derive(Debug, Serialize, Deserialize)] pub struct SendTransactionResponseExceptionStackContext { pub level: String, @@ -199,6 +216,14 @@ pub struct SendTransactionResponseError { pub details: Vec, } +#[derive(Debug, Serialize, Deserialize)] +pub struct SendTransactionResponse2Error { + pub code: Option, + pub name: String, + pub message: String, + pub stack: Option>, +} + impl SendTransactionResponseError { pub fn print_error(&self) { self.details.iter().for_each(|d| info!("{:?}", d)); @@ -225,6 +250,40 @@ pub struct SendTransactionResponseErrorDetails { pub method: String, } +pub struct SendTransaction2Options { + pub return_failure_trace: bool, + pub retry_trx: bool, + pub retry_trx_num_blocks: u32, +} + +#[derive(Serialize)] +pub struct SendTransaction2Request { + pub return_failure_trace: bool, + pub retry_trx: bool, + pub retry_trx_num_blocks: u32, + pub transaction: Value, +} + +impl SendTransaction2Request { + pub fn build( + trx: PackedTransaction, + options: Option, + ) -> SendTransaction2Request { + let opts = options.unwrap_or(SendTransaction2Options { + return_failure_trace: true, + retry_trx: false, + retry_trx_num_blocks: 0, + }); + + SendTransaction2Request { + return_failure_trace: opts.return_failure_trace, + retry_trx: opts.retry_trx, + retry_trx_num_blocks: opts.retry_trx_num_blocks, + transaction: trx.to_json(), + } + } +} + #[derive(Debug, Serialize, Deserialize)] pub struct ErrorResponse { pub code: u16, @@ -232,13 +291,26 @@ pub struct ErrorResponse { pub error: SendTransactionResponseError, } +#[derive(Debug, Serialize, Deserialize)] +pub struct ErrorResponse2 { + pub code: u16, + pub message: String, + pub error: SendTransactionResponse2Error, +} + #[derive(Debug, Serialize, Deserialize)] pub struct SendTransactionResponse { pub transaction_id: String, pub processed: ProcessedTransaction, } -#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Serialize, Deserialize)] +pub struct SendTransaction2Response { + pub transaction_id: String, + pub processed: ProcessedTransaction2, +} + +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum TransactionState { LocallyApplied, diff --git a/crates/antelope/src/chain/transaction.rs b/crates/antelope/src/chain/transaction.rs index 1f6b8ae..241053a 100644 --- a/crates/antelope/src/chain/transaction.rs +++ b/crates/antelope/src/chain/transaction.rs @@ -104,7 +104,7 @@ impl PackedTransaction { }) } - pub fn to_json(&self) -> String { + pub fn to_json(&self) -> Value { let mut trx: HashMap<&str, Value> = HashMap::new(); let signatures: Vec = self.signatures.iter().map(|sig| sig.to_string()).collect(); trx.insert("signatures", json!(signatures)); @@ -122,6 +122,6 @@ impl PackedTransaction { "packed_trx", Value::String(bytes_to_hex(&self.packed_transaction)), ); - json!(trx).to_string() + json!(trx) } }