From 8f51821283f9ae13ad78dc243359df06c7ec9721 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Thu, 15 Feb 2024 18:49:27 +0000 Subject: [PATCH 01/19] current state --- Cargo.toml | 13 ++++++-- src/asynch/clients/embedded_ws.rs | 7 ++-- src/asynch/clients/exceptions.rs | 4 +++ src/asynch/clients/mod.rs | 26 +++++++++++++++ src/asynch/clients/tungstenite.rs | 37 ++++++++++++++++----- src/asynch/ledger/mod.rs | 55 +++++++++++++++++++++++++++++++ src/asynch/mod.rs | 2 ++ src/asynch/transaction/mod.rs | 53 +++++++++++++++++++++++++++++ src/models/amount/exceptions.rs | 4 +++ src/models/amount/xrp_amount.rs | 29 +++++++++++++++- src/models/transactions/mod.rs | 14 +++++--- 11 files changed, 223 insertions(+), 21 deletions(-) create mode 100644 src/asynch/ledger/mod.rs create mode 100644 src/asynch/transaction/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 6e0d190a..449b1a98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,10 @@ url = { version = "2.2.2", default-features = false, optional = true } futures = { version = "0.3.28", default-features = false, optional = true } rand_core = { version = "0.6.4", default-features = false } tokio-tungstenite = { version = "0.20.0", optional = true } +futures-util = { version = "0.3.30", default-features = false, optional = true, features = [ + "async-await", + "async-await-macro", +] } [dependencies.embedded-websocket] # git version needed to use `framer_async` @@ -82,7 +86,6 @@ cargo-husky = { version = "1.5.0", default-features = false, features = [ ] } tokio = { version = "1.28.0", features = ["full"] } tokio-util = { version = "0.7.7", features = ["codec"] } -futures-util = "0.3.30" bytes = { version = "1.4.0", default-features = false } rand = { version = "0.8.4", default-features = false, features = [ "getrandom", @@ -102,7 +105,13 @@ requests = ["core", "amounts", "currencies"] ledger = ["core", "amounts", "currencies"] amounts = ["core"] currencies = ["core"] -tungstenite = ["url", "futures", "tokio/full", "tokio-tungstenite/native-tls"] +tungstenite = [ + "url", + "futures", + "tokio/full", + "tokio-tungstenite/native-tls", + "futures-util/std", +] embedded-ws = ["url", "futures", "embedded-websocket"] core = ["utils"] utils = [] diff --git a/src/asynch/clients/embedded_ws.rs b/src/asynch/clients/embedded_ws.rs index 76106dee..33d46924 100644 --- a/src/asynch/clients/embedded_ws.rs +++ b/src/asynch/clients/embedded_ws.rs @@ -1,3 +1,4 @@ +use super::WebsocketClient; use super::{ exceptions::XRPLWebsocketException, {WebsocketClosed, WebsocketOpen}, @@ -25,11 +26,7 @@ pub struct AsyncWebsocketClient { status: PhantomData, } -impl AsyncWebsocketClient { - pub fn is_open(&self) -> bool { - core::any::type_name::() == core::any::type_name::() - } -} +impl WebsocketClient for AsyncWebsocketClient {} impl AsyncWebsocketClient { /// Open a websocket connection. diff --git a/src/asynch/clients/exceptions.rs b/src/asynch/clients/exceptions.rs index 220084cb..16eb4375 100644 --- a/src/asynch/clients/exceptions.rs +++ b/src/asynch/clients/exceptions.rs @@ -25,6 +25,10 @@ pub enum XRPLWebsocketException { Disconnected, #[error("Read buffer is too small (size: {0:?})")] RxBufferTooSmall(usize), + #[error("No response")] + NoResponse, + #[error("Unexpected message type")] + UnexpectedMessageType, } #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index 54eb0495..9f8f4287 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -1,3 +1,7 @@ +use crate::models::requests::Request; +use alloc::string::String; +use anyhow::Result; + pub mod exceptions; pub struct WebsocketOpen; @@ -10,5 +14,27 @@ mod tungstenite; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] pub use embedded_websocket::*; +use serde::{Deserialize, Serialize}; +use serde_json::Value; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] pub use tungstenite::*; + +pub trait WebsocketClient { + fn is_open(&self) -> bool { + core::any::type_name::() == core::any::type_name::() + } +} + +#[derive(Serialize, Deserialize)] +pub struct XRPLResponse { + pub id: Option, + pub result: Option, + pub status: Option, + pub r#type: Option, + pub forwarded: Option, + pub warnings: Option, +} + +pub trait Client { + async fn request(&mut self, req: impl Serialize) -> Result; +} diff --git a/src/asynch/clients/tungstenite.rs b/src/asynch/clients/tungstenite.rs index c03bf281..4a66f3a9 100644 --- a/src/asynch/clients/tungstenite.rs +++ b/src/asynch/clients/tungstenite.rs @@ -1,13 +1,16 @@ use super::{ exceptions::XRPLWebsocketException, - {WebsocketClosed, WebsocketOpen}, + Client, WebsocketClient, XRPLResponse, {WebsocketClosed, WebsocketOpen}, }; use crate::Err; +use alloc::string::String; use anyhow::Result; use core::marker::PhantomData; use core::{pin::Pin, task::Poll}; use futures::{Sink, Stream}; +use futures_util::{SinkExt, TryStreamExt}; +use serde::Serialize; use tokio::net::TcpStream; use tokio_tungstenite::{ connect_async as tungstenite_connect_async, MaybeTlsStream as TungsteniteMaybeTlsStream, @@ -22,11 +25,7 @@ pub struct AsyncWebsocketClient { status: PhantomData, } -impl AsyncWebsocketClient { - pub fn is_open(&self) -> bool { - core::any::type_name::() == core::any::type_name::() - } -} +impl WebsocketClient for AsyncWebsocketClient {} impl Sink for AsyncWebsocketClient where @@ -81,14 +80,25 @@ where } impl Stream for AsyncWebsocketClient { - type Item = > as Stream>::Item; + type Item = Result; fn poll_next( mut self: Pin<&mut Self>, cx: &mut core::task::Context<'_>, ) -> Poll> { match Pin::new(&mut self.inner).poll_next(cx) { - Poll::Ready(Some(item)) => Poll::Ready(Some(item)), + Poll::Ready(Some(item)) => match item { + Ok(message) => match message { + TungsteniteMessage::Text(response) => match serde_json::from_str(&response) { + Ok(response) => Poll::Ready(Some(Ok(response))), + Err(error) => Poll::Ready(Some(Err!(error))), + }, + _ => Poll::Ready(Some(Err!( + XRPLWebsocketException::::UnexpectedMessageType + ))), + }, + Err(error) => Poll::Ready(Some(Err!(error))), + }, Poll::Ready(None) => Poll::Ready(None), Poll::Pending => Poll::Pending, } @@ -110,3 +120,14 @@ impl AsyncWebsocketClient { } } } + +impl Client for AsyncWebsocketClient { + async fn request(&mut self, req: impl Serialize) -> Result { + self.send(req).await?; + while let Ok(Some(response)) = self.try_next().await { + return Ok(response); + } + + Err!(XRPLWebsocketException::::NoResponse) + } +} diff --git a/src/asynch/ledger/mod.rs b/src/asynch/ledger/mod.rs new file mode 100644 index 00000000..aa8766f3 --- /dev/null +++ b/src/asynch/ledger/mod.rs @@ -0,0 +1,55 @@ +use core::cmp::min; + +use alloc::string::ToString; +use anyhow::Result; +use serde_json::Value; + +use crate::models::{amount::XRPAmount, requests::Fee}; + +use super::clients::Client; + +pub enum FeeType { + Open, + Minimum, + Dynamic, +} + +pub async fn get_fee<'a>( + client: &'a mut impl Client, + max_fee: Option, + fee_type: Option, +) -> Result> { + let fee_request = Fee::new(None); + match client.request(fee_request).await { + Ok(response) => { + let response_value = serde_json::to_value(&response).unwrap(); + let drops = response_value.get("result").unwrap().get("drops").unwrap(); + let fee = match_fee_type(fee_type, drops); + + if let Some(max_fee) = max_fee { + Ok(XRPAmount::from(min(max_fee, fee).to_string())) + } else { + Ok(XRPAmount::from(fee.to_string())) + } + } + Err(err) => Err(err), + } +} + +fn match_fee_type(fee_type: Option, drops: &Value) -> u16 { + match fee_type { + None | Some(FeeType::Open) => drops + .get("open_ledger_fee") + .unwrap() + .to_string() + .parse() + .unwrap(), + Some(FeeType::Minimum) => drops + .get("minimum_fee") + .unwrap() + .to_string() + .parse() + .unwrap(), + Some(FeeType::Dynamic) => unimplemented!("Dynamic fee calculation not yet implemented"), + } +} diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index 70076baf..8c1b3084 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -1,2 +1,4 @@ #[cfg(any(feature = "tungstenite", feature = "embedded-ws"))] pub mod clients; +pub mod ledger; +pub mod transaction; diff --git a/src/asynch/transaction/mod.rs b/src/asynch/transaction/mod.rs new file mode 100644 index 00000000..a188c1b0 --- /dev/null +++ b/src/asynch/transaction/mod.rs @@ -0,0 +1,53 @@ +use core::cmp::max; + +use anyhow::Result; +use serde::Deserialize; +use serde::Serialize; +use strum::IntoEnumIterator; + +use crate::models::amount::XRPAmount; +use crate::models::requests::Fee; +use crate::models::transactions::CommonFields; +use crate::models::transactions::EscrowFinish; +use crate::models::transactions::Transaction; +use crate::models::transactions::TransactionType; + +use super::clients::Client; +use super::ledger::get_fee; + +async fn check_fee<'a, 'de, F, R: Serialize, T: Deserialize<'de>>( + transaction: &'a mut impl Transaction, + client: Option<&'a mut impl Client>, + signers_count: Option, +) where + F: IntoEnumIterator + Serialize + core::fmt::Debug + PartialEq, +{ + let expected_fee = max( + 0.1, + calculate_fee_per_transaction_type(transaction, client, signers_count).await, + ); +} + +async fn calculate_fee_per_transaction_type<'a, F>( + transaction: &'a impl Transaction, + client: Option<&'a mut impl Client>, + signers_count: Option, +) -> Result +where + F: IntoEnumIterator + Serialize + core::fmt::Debug + PartialEq, +{ + let net_fee = if let Some(client) = client { + get_fee(client, None, None).await? + } else { + XRPAmount::from("10") + }; + + let base_fee = net_fee.clone(); + + if transaction.get_transaction_type() == TransactionType::EscrowFinish { + // cast type to transaction `EscrowFinish` + let escrow_finish = transaction.as_any().downcast_ref::().unwrap(); + } + + todo!() +} diff --git a/src/models/amount/exceptions.rs b/src/models/amount/exceptions.rs index 07746a28..2f8fa1c7 100644 --- a/src/models/amount/exceptions.rs +++ b/src/models/amount/exceptions.rs @@ -1,9 +1,13 @@ +use core::num::ParseFloatError; + use thiserror_no_std::Error; #[derive(Debug, Clone, PartialEq, Error)] pub enum XRPLAmountException { #[error("Unable to convert amount `value` into `Decimal`.")] ToDecimalError(#[from] rust_decimal::Error), + #[error("Unable to convert amount `value` into `f64`.")] + ToFloatError(#[from] ParseFloatError), } #[cfg(feature = "std")] diff --git a/src/models/amount/xrp_amount.rs b/src/models/amount/xrp_amount.rs index 4bb01b54..b9c00a88 100644 --- a/src/models/amount/xrp_amount.rs +++ b/src/models/amount/xrp_amount.rs @@ -1,11 +1,15 @@ use crate::models::amount::exceptions::XRPLAmountException; use crate::models::Model; -use alloc::borrow::Cow; +use alloc::{ + borrow::Cow, + string::{String, ToString}, +}; use core::convert::TryInto; use core::str::FromStr; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; +/// Represents an amount of XRP in Drops. #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)] pub struct XRPAmount<'a>(pub Cow<'a, str>); @@ -23,6 +27,29 @@ impl<'a> From<&'a str> for XRPAmount<'a> { } } +impl<'a> From for XRPAmount<'a> { + fn from(value: String) -> Self { + Self(value.into()) + } +} + +impl<'a> From for XRPAmount<'a> { + fn from(value: Decimal) -> Self { + Self(value.to_string().into()) + } +} + +impl<'a> TryInto for XRPAmount<'a> { + type Error = XRPLAmountException; + + fn try_into(self) -> Result { + match self.0.parse::() { + Ok(f64_value) => Ok(f64_value), + Err(parse_error) => Err(XRPLAmountException::ToFloatError(parse_error)), + } + } +} + impl<'a> TryInto for XRPAmount<'a> { type Error = XRPLAmountException; diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 57ad9163..18b6d9e3 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -54,10 +54,7 @@ pub use ticket_create::*; pub use trust_set::*; use crate::models::amount::XRPAmount; -use crate::{ - _serde::txn_flags, - serde_with_tag, -}; +use crate::{_serde::txn_flags, serde_with_tag}; use alloc::borrow::Cow; use alloc::string::String; use alloc::vec::Vec; @@ -135,7 +132,7 @@ pub struct SignedTransaction<'a, T> { #[serde(rename_all = "PascalCase")] pub struct CommonFields<'a, F> where - F: IntoEnumIterator + Serialize + core::fmt::Debug, + F: IntoEnumIterator + Serialize + core::fmt::Debug + PartialEq, { /// The unique address of the account that initiated the transaction. pub account: Cow<'a, str>, @@ -278,6 +275,13 @@ where } fn get_transaction_type(&self) -> TransactionType; + + // fn cast_transaction(&self) -> Self + // where + // C: Transaction, + // { + // C { ...Self } + // } } #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Display, AsRefStr)] From f426882eac9b3faf413c497cd09c07593482fcb1 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 17 Feb 2024 10:39:47 +0000 Subject: [PATCH 02/19] check_fee --- .devcontainer/Dockerfile | 2 +- .devcontainer/devcontainer.json | 7 +- src/asynch/clients/mod.rs | 1 - src/asynch/clients/tungstenite.rs | 1 - src/asynch/ledger/mod.rs | 6 +- src/asynch/transaction/exceptions.rs | 9 ++ src/asynch/transaction/mod.rs | 142 +++++++++++++++--- src/models/amount/exceptions.rs | 4 +- src/models/amount/issued_currency_amount.rs | 104 ++++++++++++- src/models/amount/xrp_amount.rs | 141 ++++++++++++++++- src/models/mod.rs | 15 -- src/models/transactions/account_delete.rs | 10 +- src/models/transactions/account_set.rs | 10 +- src/models/transactions/check_cancel.rs | 10 +- src/models/transactions/check_cash.rs | 10 +- src/models/transactions/check_create.rs | 10 +- src/models/transactions/deposit_preauth.rs | 10 +- src/models/transactions/escrow_cancel.rs | 10 +- src/models/transactions/escrow_create.rs | 10 +- src/models/transactions/escrow_finish.rs | 10 +- src/models/transactions/mod.rs | 27 ++-- .../transactions/nftoken_accept_offer.rs | 10 +- src/models/transactions/nftoken_burn.rs | 10 +- .../transactions/nftoken_cancel_offer.rs | 10 +- .../transactions/nftoken_create_offer.rs | 10 +- src/models/transactions/nftoken_mint.rs | 10 +- src/models/transactions/offer_cancel.rs | 10 +- src/models/transactions/offer_create.rs | 10 +- src/models/transactions/payment.rs | 10 +- .../transactions/payment_channel_claim.rs | 10 +- .../transactions/payment_channel_create.rs | 10 +- .../transactions/payment_channel_fund.rs | 10 +- .../pseudo_transactions/enable_amendment.rs | 10 +- .../pseudo_transactions/set_fee.rs | 10 +- .../pseudo_transactions/unl_modify.rs | 10 +- src/models/transactions/set_regular_key.rs | 10 +- src/models/transactions/signer_list_set.rs | 10 +- src/models/transactions/ticket_create.rs | 10 +- src/models/transactions/trust_set.rs | 10 +- 39 files changed, 641 insertions(+), 88 deletions(-) create mode 100644 src/asynch/transaction/exceptions.rs diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index f56adafc..95eea2ad 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/devcontainers/rust:1.0-1-bullseye +FROM mcr.microsoft.com/devcontainers/rust:1-1-bookworm # [Optional] Uncomment this section to install additional OS packages. # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index fd1443fd..325242a2 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,7 +14,7 @@ // } // ] // Use 'postCreateCommand' to run commands after the container is created. - "postStartCommand": "rustup --version && rustup component add rustfmt && rustup component add clippy", + "postCreateCommand": "rustup install nightly && rustup --version && rustup component add cargo && rustup component add rustfmt && rustup component add clippy", // Configure tool-specific properties. "customizations": { "vscode": { @@ -22,7 +22,6 @@ "Gydunhn.vsc-essentials", "GitHub.copilot", "swellaby.rust-pack", - "panicbit.cargo", "vadimcn.vscode-lldb", "tamasfe.even-better-toml", "Swellaby.vscode-rust-test-adapter" @@ -36,7 +35,7 @@ } } } - } + }, // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" + "remoteUser": "root" } \ No newline at end of file diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index 9f8f4287..0a5052df 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -1,4 +1,3 @@ -use crate::models::requests::Request; use alloc::string::String; use anyhow::Result; diff --git a/src/asynch/clients/tungstenite.rs b/src/asynch/clients/tungstenite.rs index 4a66f3a9..eb0feabb 100644 --- a/src/asynch/clients/tungstenite.rs +++ b/src/asynch/clients/tungstenite.rs @@ -4,7 +4,6 @@ use super::{ }; use crate::Err; -use alloc::string::String; use anyhow::Result; use core::marker::PhantomData; use core::{pin::Pin, task::Poll}; diff --git a/src/asynch/ledger/mod.rs b/src/asynch/ledger/mod.rs index aa8766f3..4e381d26 100644 --- a/src/asynch/ledger/mod.rs +++ b/src/asynch/ledger/mod.rs @@ -18,7 +18,7 @@ pub async fn get_fee<'a>( client: &'a mut impl Client, max_fee: Option, fee_type: Option, -) -> Result> { +) -> Result<(XRPAmount<'a>, &'a mut impl Client)> { let fee_request = Fee::new(None); match client.request(fee_request).await { Ok(response) => { @@ -27,9 +27,9 @@ pub async fn get_fee<'a>( let fee = match_fee_type(fee_type, drops); if let Some(max_fee) = max_fee { - Ok(XRPAmount::from(min(max_fee, fee).to_string())) + Ok((XRPAmount::from(min(max_fee, fee).to_string()), client)) } else { - Ok(XRPAmount::from(fee.to_string())) + Ok((XRPAmount::from(fee.to_string()), client)) } } Err(err) => Err(err), diff --git a/src/asynch/transaction/exceptions.rs b/src/asynch/transaction/exceptions.rs new file mode 100644 index 00000000..4afc1d47 --- /dev/null +++ b/src/asynch/transaction/exceptions.rs @@ -0,0 +1,9 @@ +use thiserror_no_std::Error; + +use crate::models::amount::XRPAmount; + +#[derive(Error, Debug, PartialEq)] +pub enum XRPLTransactionException<'a> { + #[error("Fee of {0:?} Drops is much higher than a typical XRP transaction fee. This may be a mistake. If intentional, please use `check_fee = false`")] + FeeUnusuallyHigh(XRPAmount<'a>), +} diff --git a/src/asynch/transaction/mod.rs b/src/asynch/transaction/mod.rs index a188c1b0..fcaab3a6 100644 --- a/src/asynch/transaction/mod.rs +++ b/src/asynch/transaction/mod.rs @@ -1,53 +1,151 @@ -use core::cmp::max; +use core::any::Any; +use core::convert::TryFrom; +use core::convert::TryInto; +use alloc::borrow::Cow; +use alloc::string::ToString; +use alloc::vec::Vec; +use anyhow::Ok; use anyhow::Result; -use serde::Deserialize; +use rust_decimal::Decimal; use serde::Serialize; use strum::IntoEnumIterator; use crate::models::amount::XRPAmount; -use crate::models::requests::Fee; -use crate::models::transactions::CommonFields; +use crate::models::requests::ServerState; use crate::models::transactions::EscrowFinish; use crate::models::transactions::Transaction; use crate::models::transactions::TransactionType; +use crate::models::Model; +use crate::Err; + +use self::exceptions::XRPLTransactionException; use super::clients::Client; +use super::clients::XRPLResponse; use super::ledger::get_fee; -async fn check_fee<'a, 'de, F, R: Serialize, T: Deserialize<'de>>( - transaction: &'a mut impl Transaction, +pub mod exceptions; + +const OWNER_RESERVE: &'static str = "2000000"; // 2 XRP + +async fn check_fee<'a, F, T>( + transaction: &'a T, client: Option<&'a mut impl Client>, signers_count: Option, -) where - F: IntoEnumIterator + Serialize + core::fmt::Debug + PartialEq, +) -> Result> +where + T: Transaction<'a, F> + Model + 'static, + F: IntoEnumIterator + Serialize + core::fmt::Debug + PartialEq + 'a, { - let expected_fee = max( - 0.1, - calculate_fee_per_transaction_type(transaction, client, signers_count).await, - ); + let fee_to_high = XRPAmount::from("1000"); + let (calculated_fee, client_ref) = + calculate_fee_per_transaction_type(transaction, client, signers_count) + .await + .unwrap(); + let expected_fee = fee_to_high.max(calculated_fee); + let common_fields = transaction.as_common_fields(); + if let Some(fee) = &common_fields.fee { + if fee < &expected_fee { + return Err!(XRPLTransactionException::FeeUnusuallyHigh(fee.clone())); + } + } + + Ok(client_ref) } -async fn calculate_fee_per_transaction_type<'a, F>( - transaction: &'a impl Transaction, +async fn calculate_fee_per_transaction_type<'a, T, F>( + transaction: &'a T, client: Option<&'a mut impl Client>, signers_count: Option, -) -> Result +) -> Result<(XRPAmount<'a>, Option<&'a mut impl Client>)> where + T: Transaction<'a, F> + 'static, F: IntoEnumIterator + Serialize + core::fmt::Debug + PartialEq, { - let net_fee = if let Some(client) = client { - get_fee(client, None, None).await? + if let Some(client) = client { + let (net_fee, client_ref) = get_fee(client, None, None).await?; + let (mut base_fee, client_) = match transaction.get_transaction_type() { + TransactionType::EscrowFinish => { + let base_fee = calculate_base_fee_for_escrow_finish(transaction, net_fee.clone()); + (base_fee, client_ref) + } + // TODO: same for TransactionType::AMMCreate + TransactionType::AccountDelete => { + let owner_reserve_response = client_ref.request(ServerState::new(None)).await?; + let owner_reserve = get_owner_reserve_from_response(owner_reserve_response); + + (owner_reserve, client_ref) + } + _ => (net_fee.clone(), client_ref), + }; + if let Some(signers_count) = signers_count { + base_fee += net_fee.clone() * (1 + signers_count); + } + + return Ok((base_fee.ceil(), Some(client_))); } else { - XRPAmount::from("10") - }; + let net_fee = XRPAmount::from("10"); + let mut base_fee = match transaction.get_transaction_type() { + TransactionType::EscrowFinish => { + calculate_base_fee_for_escrow_finish(transaction, net_fee.clone()) + } + // TODO: same for TransactionType::AMMCreate + TransactionType::AccountDelete => XRPAmount::from(OWNER_RESERVE), + _ => net_fee.clone(), + }; + if let Some(signers_count) = signers_count { + base_fee += net_fee.clone() * (1 + signers_count); + } - let base_fee = net_fee.clone(); + return Ok((base_fee.ceil(), None)); + } +} +fn get_owner_reserve_from_response<'a>(response: XRPLResponse) -> XRPAmount<'a> { + let owner_reserve = response + .result + .unwrap() + .get("state") + .unwrap() + .get("validated_ledger") + .unwrap() + .get("reserve_inc") + .unwrap() + .clone(); + XRPAmount::try_from(owner_reserve).unwrap() +} + +fn calculate_base_fee_for_escrow_finish<'a, T, F>( + transaction: &'a T, + net_fee: XRPAmount<'a>, +) -> XRPAmount<'a> +where + T: Transaction<'a, F> + 'static, + F: IntoEnumIterator + Serialize + core::fmt::Debug + PartialEq, +{ if transaction.get_transaction_type() == TransactionType::EscrowFinish { // cast type to transaction `EscrowFinish` - let escrow_finish = transaction.as_any().downcast_ref::().unwrap(); + let escrow_finish: Option<&EscrowFinish<'a>> = + (transaction as &dyn Any).downcast_ref::(); + if let Some(escrow_finish) = escrow_finish { + if let Some(fulfillment) = escrow_finish.fulfillment.clone() { + return calculate_based_on_fulfillment(fulfillment, net_fee); + } + } } - todo!() + net_fee +} + +fn calculate_based_on_fulfillment<'a>( + fulfillment: Cow, + net_fee: XRPAmount<'a>, +) -> XRPAmount<'a> { + let fulfillment_bytes: Vec = fulfillment.chars().map(|c| c as u8).collect(); + let net_fee_f64: f64 = net_fee.try_into().unwrap(); + let base_fee_string = + (net_fee_f64 * (33.0 + (fulfillment_bytes.len() as f64 / 16.0))).to_string(); + let base_fee_decimal: Decimal = base_fee_string.parse().unwrap(); + XRPAmount::from(base_fee_decimal.ceil()) } diff --git a/src/models/amount/exceptions.rs b/src/models/amount/exceptions.rs index 2f8fa1c7..f20b9e1d 100644 --- a/src/models/amount/exceptions.rs +++ b/src/models/amount/exceptions.rs @@ -2,12 +2,14 @@ use core::num::ParseFloatError; use thiserror_no_std::Error; -#[derive(Debug, Clone, PartialEq, Error)] +#[derive(Debug, Error)] pub enum XRPLAmountException { #[error("Unable to convert amount `value` into `Decimal`.")] ToDecimalError(#[from] rust_decimal::Error), #[error("Unable to convert amount `value` into `f64`.")] ToFloatError(#[from] ParseFloatError), + #[error("{0:?}")] + FromSerdeError(#[from] serde_json::Error), } #[cfg(feature = "std")] diff --git a/src/models/amount/issued_currency_amount.rs b/src/models/amount/issued_currency_amount.rs index 8a63c07a..5a33549a 100644 --- a/src/models/amount/issued_currency_amount.rs +++ b/src/models/amount/issued_currency_amount.rs @@ -1,8 +1,10 @@ use crate::models::amount::exceptions::XRPLAmountException; use crate::models::Model; use alloc::borrow::Cow; -use core::convert::TryInto; +use alloc::string::ToString; +use core::ops::{AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; use core::str::FromStr; +use core::{convert::TryInto, ops::Add}; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; @@ -35,3 +37,103 @@ impl<'a> TryInto for IssuedCurrencyAmount<'a> { } } } + +impl<'a> PartialOrd for IssuedCurrencyAmount<'a> { + fn partial_cmp(&self, other: &Self) -> Option { + self.value.partial_cmp(&other.value) + } +} + +impl<'a> Ord for IssuedCurrencyAmount<'a> { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.value.cmp(&other.value) + } +} + +impl<'a> Add for IssuedCurrencyAmount<'a> { + type Output = Self; + + fn add(self, other: Self) -> Self { + let value = + Decimal::from_str(&self.value).unwrap() + Decimal::from_str(&other.value).unwrap(); + Self { + currency: self.currency, + issuer: self.issuer, + value: value.to_string().into(), + } + } +} + +impl<'a> AddAssign for IssuedCurrencyAmount<'a> { + fn add_assign(&mut self, other: Self) { + let value = + Decimal::from_str(&self.value).unwrap() + Decimal::from_str(&other.value).unwrap(); + self.value = value.to_string().into(); + } +} + +impl<'a> Sub for IssuedCurrencyAmount<'a> { + type Output = Self; + + fn sub(self, other: Self) -> Self { + let value = + Decimal::from_str(&self.value).unwrap() - Decimal::from_str(&other.value).unwrap(); + Self { + currency: self.currency, + issuer: self.issuer, + value: value.to_string().into(), + } + } +} + +impl<'a> SubAssign for IssuedCurrencyAmount<'a> { + fn sub_assign(&mut self, other: Self) { + let value = + Decimal::from_str(&self.value).unwrap() - Decimal::from_str(&other.value).unwrap(); + self.value = value.to_string().into(); + } +} + +impl<'a> Mul for IssuedCurrencyAmount<'a> { + type Output = Self; + + fn mul(self, other: Self) -> Self { + let value = + Decimal::from_str(&self.value).unwrap() * Decimal::from_str(&other.value).unwrap(); + Self { + currency: self.currency, + issuer: self.issuer, + value: value.to_string().into(), + } + } +} + +impl<'a> MulAssign for IssuedCurrencyAmount<'a> { + fn mul_assign(&mut self, other: Self) { + let value = + Decimal::from_str(&self.value).unwrap() * Decimal::from_str(&other.value).unwrap(); + self.value = value.to_string().into(); + } +} + +impl<'a> Div for IssuedCurrencyAmount<'a> { + type Output = Self; + + fn div(self, other: Self) -> Self { + let value = + Decimal::from_str(&self.value).unwrap() / Decimal::from_str(&other.value).unwrap(); + Self { + currency: self.currency, + issuer: self.issuer, + value: value.to_string().into(), + } + } +} + +impl<'a> DivAssign for IssuedCurrencyAmount<'a> { + fn div_assign(&mut self, other: Self) { + let value = + Decimal::from_str(&self.value).unwrap() / Decimal::from_str(&other.value).unwrap(); + self.value = value.to_string().into(); + } +} diff --git a/src/models/amount/xrp_amount.rs b/src/models/amount/xrp_amount.rs index b9c00a88..62185d41 100644 --- a/src/models/amount/xrp_amount.rs +++ b/src/models/amount/xrp_amount.rs @@ -4,10 +4,14 @@ use alloc::{ borrow::Cow, string::{String, ToString}, }; -use core::convert::TryInto; -use core::str::FromStr; +use core::{ + convert::{TryFrom, TryInto}, + ops::{Add, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}, +}; +use core::{ops::AddAssign, str::FromStr}; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; +use serde_json::Value; /// Represents an amount of XRP in Drops. #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)] @@ -39,6 +43,22 @@ impl<'a> From for XRPAmount<'a> { } } +impl<'a> From for XRPAmount<'a> { + fn from(value: f64) -> Self { + Self(value.to_string().into()) + } +} + +impl<'a> TryFrom for XRPAmount<'a> { + type Error = XRPLAmountException; + + fn try_from(value: Value) -> Result { + let amount_string = + serde_json::to_string(&value).map_err(XRPLAmountException::FromSerdeError)?; + Ok(Self(amount_string.into())) + } +} + impl<'a> TryInto for XRPAmount<'a> { type Error = XRPLAmountException; @@ -60,3 +80,120 @@ impl<'a> TryInto for XRPAmount<'a> { } } } + +impl<'a> PartialOrd for XRPAmount<'a> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.0.cmp(&other.0)) + } +} + +impl<'a> Ord for XRPAmount<'a> { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.0.cmp(&other.0) + } +} + +impl<'a> Add for XRPAmount<'a> { + type Output = Self; + + fn add(self, other: Self) -> Self { + let self_decimal: Decimal = self.try_into().unwrap(); + let other_decimal: Decimal = other.try_into().unwrap(); + let result_decimal = self_decimal + other_decimal; + result_decimal.into() + } +} + +impl<'a> AddAssign for XRPAmount<'a> { + fn add_assign(&mut self, other: Self) { + let self_decimal: Decimal = self.clone().try_into().unwrap(); + let other_decimal: Decimal = other.try_into().unwrap(); + let result_decimal = self_decimal + other_decimal; + *self = result_decimal.into(); + } +} + +impl<'a> Sub for XRPAmount<'a> { + type Output = Self; + + fn sub(self, other: Self) -> Self { + let self_decimal: Decimal = self.try_into().unwrap(); + let other_decimal: Decimal = other.try_into().unwrap(); + let result_decimal = self_decimal - other_decimal; + result_decimal.into() + } +} + +impl<'a> SubAssign for XRPAmount<'a> { + fn sub_assign(&mut self, other: Self) { + let self_decimal: Decimal = self.clone().try_into().unwrap(); + let other_decimal: Decimal = other.try_into().unwrap(); + let result_decimal = self_decimal - other_decimal; + *self = result_decimal.into(); + } +} + +impl<'a> Mul for XRPAmount<'a> { + type Output = Self; + + fn mul(self, other: Self) -> Self { + let self_decimal: Decimal = self.try_into().unwrap(); + let other_decimal: Decimal = other.try_into().unwrap(); + let result_decimal = self_decimal * other_decimal; + result_decimal.into() + } +} + +impl<'a> Mul for XRPAmount<'a> { + type Output = Self; + + fn mul(self, other: u8) -> Self { + let self_decimal: Decimal = self.try_into().unwrap(); + let other_decimal: Decimal = other.try_into().unwrap(); + let result_decimal = self_decimal * other_decimal; + result_decimal.into() + } +} + +impl<'a> MulAssign for XRPAmount<'a> { + fn mul_assign(&mut self, other: Self) { + let self_decimal: Decimal = self.clone().try_into().unwrap(); + let other_decimal: Decimal = other.try_into().unwrap(); + let result_decimal = self_decimal * other_decimal; + *self = result_decimal.into(); + } +} + +impl<'a> Div for XRPAmount<'a> { + type Output = Self; + + fn div(self, other: Self) -> Self { + let self_decimal: Decimal = self.try_into().unwrap(); + let other_decimal: Decimal = other.try_into().unwrap(); + let result_decimal = self_decimal / other_decimal; + result_decimal.into() + } +} + +impl<'a> DivAssign for XRPAmount<'a> { + fn div_assign(&mut self, other: Self) { + let self_decimal: Decimal = self.clone().try_into().unwrap(); + let other_decimal: Decimal = other.try_into().unwrap(); + let result_decimal = self_decimal / other_decimal; + *self = result_decimal.into(); + } +} + +impl XRPAmount<'_> { + pub fn ceil(&self) -> Self { + let decimal: Decimal = self.clone().try_into().unwrap(); + let result_decimal = decimal.ceil(); + result_decimal.into() + } + + pub fn floor(&self) -> Self { + let decimal: Decimal = self.clone().try_into().unwrap(); + let result_decimal = decimal.floor(); + result_decimal.into() + } +} diff --git a/src/models/mod.rs b/src/models/mod.rs index 7cd5e1dc..7c4a3165 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -187,18 +187,3 @@ where Err(_error) => Err!(XRPLFlagsException::CannotConvertFlagToU32), } } - -// pub trait SignAndSubmitError { -// fn _get_field_error(&self) -> Result<(), XRPLSignAndSubmitException>; -// fn _get_key_type_error(&self) -> Result<(), XRPLSignAndSubmitException>; -// } -// -// pub trait SignForError { -// fn _get_field_error(&self) -> Result<(), XRPLSignForException>; -// fn _get_key_type_error(&self) -> Result<(), XRPLSignForException>; -// } -// -// pub trait SignError { -// fn _get_field_error(&self) -> Result<(), XRPLSignException>; -// fn _get_key_type_error(&self) -> Result<(), XRPLSignException>; -// } diff --git a/src/models/transactions/account_delete.rs b/src/models/transactions/account_delete.rs index 26a95147..2e89e9f1 100644 --- a/src/models/transactions/account_delete.rs +++ b/src/models/transactions/account_delete.rs @@ -46,10 +46,18 @@ pub struct AccountDelete<'a> { impl<'a> Model for AccountDelete<'a> {} -impl<'a> Transaction for AccountDelete<'a> { +impl<'a> Transaction<'a, NoFlags> for AccountDelete<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.get_transaction_type() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + &mut self.common_fields + } } impl<'a> AccountDelete<'a> { diff --git a/src/models/transactions/account_set.rs b/src/models/transactions/account_set.rs index 0377406f..edd15652 100644 --- a/src/models/transactions/account_set.rs +++ b/src/models/transactions/account_set.rs @@ -141,7 +141,7 @@ impl<'a: 'static> Model for AccountSet<'a> { } } -impl<'a> Transaction for AccountSet<'a> { +impl<'a> Transaction<'a, AccountSetFlag> for AccountSet<'a> { fn has_flag(&self, flag: &AccountSetFlag) -> bool { self.common_fields.has_flag(flag) } @@ -149,6 +149,14 @@ impl<'a> Transaction for AccountSet<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.get_transaction_type() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, AccountSetFlag> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, AccountSetFlag> { + &mut self.common_fields + } } impl<'a> AccountSetError for AccountSet<'a> { diff --git a/src/models/transactions/check_cancel.rs b/src/models/transactions/check_cancel.rs index 1f9aa015..05406f0a 100644 --- a/src/models/transactions/check_cancel.rs +++ b/src/models/transactions/check_cancel.rs @@ -41,10 +41,18 @@ pub struct CheckCancel<'a> { impl<'a> Model for CheckCancel<'a> {} -impl<'a> Transaction for CheckCancel<'a> { +impl<'a> Transaction<'a, NoFlags> for CheckCancel<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.get_transaction_type() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + &mut self.common_fields + } } impl<'a> CheckCancel<'a> { diff --git a/src/models/transactions/check_cash.rs b/src/models/transactions/check_cash.rs index 83a7485f..801bf2d0 100644 --- a/src/models/transactions/check_cash.rs +++ b/src/models/transactions/check_cash.rs @@ -56,10 +56,18 @@ impl<'a: 'static> Model for CheckCash<'a> { } } -impl<'a> Transaction for CheckCash<'a> { +impl<'a> Transaction<'a, NoFlags> for CheckCash<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.get_transaction_type() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + &mut self.common_fields + } } impl<'a> CheckCashError for CheckCash<'a> { diff --git a/src/models/transactions/check_create.rs b/src/models/transactions/check_create.rs index 8f2b23f6..e7a053ed 100644 --- a/src/models/transactions/check_create.rs +++ b/src/models/transactions/check_create.rs @@ -52,10 +52,18 @@ pub struct CheckCreate<'a> { impl<'a> Model for CheckCreate<'a> {} -impl<'a> Transaction for CheckCreate<'a> { +impl<'a> Transaction<'a, NoFlags> for CheckCreate<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.get_transaction_type() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + &mut self.common_fields + } } impl<'a> CheckCreate<'a> { diff --git a/src/models/transactions/deposit_preauth.rs b/src/models/transactions/deposit_preauth.rs index 76857280..c9477573 100644 --- a/src/models/transactions/deposit_preauth.rs +++ b/src/models/transactions/deposit_preauth.rs @@ -47,10 +47,18 @@ impl<'a: 'static> Model for DepositPreauth<'a> { } } -impl<'a> Transaction for DepositPreauth<'a> { +impl<'a> Transaction<'a, NoFlags> for DepositPreauth<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.get_transaction_type() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + &mut self.common_fields + } } impl<'a> DepositPreauthError for DepositPreauth<'a> { diff --git a/src/models/transactions/escrow_cancel.rs b/src/models/transactions/escrow_cancel.rs index 69ddbd04..fb91c605 100644 --- a/src/models/transactions/escrow_cancel.rs +++ b/src/models/transactions/escrow_cancel.rs @@ -40,10 +40,18 @@ pub struct EscrowCancel<'a> { impl<'a> Model for EscrowCancel<'a> {} -impl<'a> Transaction for EscrowCancel<'a> { +impl<'a> Transaction<'a, NoFlags> for EscrowCancel<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.get_transaction_type() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + &mut self.common_fields + } } impl<'a> EscrowCancel<'a> { diff --git a/src/models/transactions/escrow_create.rs b/src/models/transactions/escrow_create.rs index 379873e1..ccf02183 100644 --- a/src/models/transactions/escrow_create.rs +++ b/src/models/transactions/escrow_create.rs @@ -65,10 +65,18 @@ impl<'a: 'static> Model for EscrowCreate<'a> { } } -impl<'a> Transaction for EscrowCreate<'a> { +impl<'a> Transaction<'a, NoFlags> for EscrowCreate<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.get_transaction_type() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + &mut self.common_fields + } } impl<'a> EscrowCreateError for EscrowCreate<'a> { diff --git a/src/models/transactions/escrow_finish.rs b/src/models/transactions/escrow_finish.rs index 36d4b9a1..cc7e3cf6 100644 --- a/src/models/transactions/escrow_finish.rs +++ b/src/models/transactions/escrow_finish.rs @@ -56,10 +56,18 @@ impl<'a: 'static> Model for EscrowFinish<'a> { } } -impl<'a> Transaction for EscrowFinish<'a> { +impl<'a> Transaction<'a, NoFlags> for EscrowFinish<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.transaction_type.clone() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + &mut self.common_fields + } } impl<'a> EscrowFinishError for EscrowFinish<'a> { diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 18b6d9e3..05aaaa10 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -25,6 +25,8 @@ pub mod signer_list_set; pub mod ticket_create; pub mod trust_set; +use core::fmt::Debug; + pub use account_delete::*; pub use account_set::*; pub use check_cancel::*; @@ -184,7 +186,7 @@ where impl<'a, T> CommonFields<'a, T> where - T: IntoEnumIterator + Serialize + core::fmt::Debug, + T: IntoEnumIterator + Serialize + core::fmt::Debug + PartialEq, { pub fn new( account: Cow<'a, str>, @@ -215,7 +217,7 @@ where } } -impl<'a, T> Transaction for CommonFields<'a, T> +impl<'a, T> Transaction<'a, T> for CommonFields<'a, T> where T: IntoEnumIterator + Serialize + PartialEq + core::fmt::Debug, { @@ -229,6 +231,14 @@ where fn get_transaction_type(&self) -> TransactionType { self.transaction_type.clone() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, T> { + self + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, T> { + self + } } serde_with_tag! { @@ -265,9 +275,9 @@ pub struct Signer<'a> { } /// Standard functions for transactions. -pub trait Transaction +pub trait Transaction<'a, T> where - T: IntoEnumIterator + Serialize, + T: IntoEnumIterator + Serialize + Debug + PartialEq, { fn has_flag(&self, flag: &T) -> bool { let _txn_flag = flag; @@ -276,12 +286,9 @@ where fn get_transaction_type(&self) -> TransactionType; - // fn cast_transaction(&self) -> Self - // where - // C: Transaction, - // { - // C { ...Self } - // } + fn as_common_fields(&'a self) -> &'a CommonFields<'a, T>; + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, T>; } #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Display, AsRefStr)] diff --git a/src/models/transactions/nftoken_accept_offer.rs b/src/models/transactions/nftoken_accept_offer.rs index 2346f031..00e0b77f 100644 --- a/src/models/transactions/nftoken_accept_offer.rs +++ b/src/models/transactions/nftoken_accept_offer.rs @@ -70,10 +70,18 @@ impl<'a: 'static> Model for NFTokenAcceptOffer<'a> { } } -impl<'a> Transaction for NFTokenAcceptOffer<'a> { +impl<'a> Transaction<'a, NoFlags> for NFTokenAcceptOffer<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.transaction_type.clone() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + &mut self.common_fields + } } impl<'a> NFTokenAcceptOfferError for NFTokenAcceptOffer<'a> { diff --git a/src/models/transactions/nftoken_burn.rs b/src/models/transactions/nftoken_burn.rs index c6e32e5e..d2ef0475 100644 --- a/src/models/transactions/nftoken_burn.rs +++ b/src/models/transactions/nftoken_burn.rs @@ -48,10 +48,18 @@ pub struct NFTokenBurn<'a> { impl<'a> Model for NFTokenBurn<'a> {} -impl<'a> Transaction for NFTokenBurn<'a> { +impl<'a> Transaction<'a, NoFlags> for NFTokenBurn<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.transaction_type.clone() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + &mut self.common_fields + } } impl<'a> NFTokenBurn<'a> { diff --git a/src/models/transactions/nftoken_cancel_offer.rs b/src/models/transactions/nftoken_cancel_offer.rs index 8078e572..7ef852e2 100644 --- a/src/models/transactions/nftoken_cancel_offer.rs +++ b/src/models/transactions/nftoken_cancel_offer.rs @@ -56,10 +56,18 @@ impl<'a: 'static> Model for NFTokenCancelOffer<'a> { } } -impl<'a> Transaction for NFTokenCancelOffer<'a> { +impl<'a> Transaction<'a, NoFlags> for NFTokenCancelOffer<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.transaction_type.clone() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + &mut self.common_fields + } } impl<'a> NFTokenCancelOfferError for NFTokenCancelOffer<'a> { diff --git a/src/models/transactions/nftoken_create_offer.rs b/src/models/transactions/nftoken_create_offer.rs index 09ad25c2..95041e4e 100644 --- a/src/models/transactions/nftoken_create_offer.rs +++ b/src/models/transactions/nftoken_create_offer.rs @@ -96,7 +96,7 @@ impl<'a: 'static> Model for NFTokenCreateOffer<'a> { } } -impl<'a> Transaction for NFTokenCreateOffer<'a> { +impl<'a> Transaction<'a, NFTokenCreateOfferFlag> for NFTokenCreateOffer<'a> { fn has_flag(&self, flag: &NFTokenCreateOfferFlag) -> bool { self.common_fields.has_flag(flag) } @@ -104,6 +104,14 @@ impl<'a> Transaction for NFTokenCreateOffer<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.transaction_type.clone() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NFTokenCreateOfferFlag> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NFTokenCreateOfferFlag> { + &mut self.common_fields + } } impl<'a> NFTokenCreateOfferError for NFTokenCreateOffer<'a> { diff --git a/src/models/transactions/nftoken_mint.rs b/src/models/transactions/nftoken_mint.rs index ab097b55..f7db2c95 100644 --- a/src/models/transactions/nftoken_mint.rs +++ b/src/models/transactions/nftoken_mint.rs @@ -105,7 +105,7 @@ impl<'a: 'static> Model for NFTokenMint<'a> { } } -impl<'a> Transaction for NFTokenMint<'a> { +impl<'a> Transaction<'a, NFTokenMintFlag> for NFTokenMint<'a> { fn has_flag(&self, flag: &NFTokenMintFlag) -> bool { self.common_fields.has_flag(flag) } @@ -113,6 +113,14 @@ impl<'a> Transaction for NFTokenMint<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.transaction_type.clone() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NFTokenMintFlag> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NFTokenMintFlag> { + &mut self.common_fields + } } impl<'a> NFTokenMintError for NFTokenMint<'a> { diff --git a/src/models/transactions/offer_cancel.rs b/src/models/transactions/offer_cancel.rs index c0471cb3..0b7f3808 100644 --- a/src/models/transactions/offer_cancel.rs +++ b/src/models/transactions/offer_cancel.rs @@ -43,10 +43,18 @@ pub struct OfferCancel<'a> { impl<'a> Model for OfferCancel<'a> {} -impl<'a> Transaction for OfferCancel<'a> { +impl<'a> Transaction<'a, NoFlags> for OfferCancel<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.transaction_type.clone() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + &mut self.common_fields + } } impl<'a> OfferCancel<'a> { diff --git a/src/models/transactions/offer_create.rs b/src/models/transactions/offer_create.rs index 2a356ba5..cd1261cb 100644 --- a/src/models/transactions/offer_create.rs +++ b/src/models/transactions/offer_create.rs @@ -81,7 +81,7 @@ pub struct OfferCreate<'a> { impl<'a> Model for OfferCreate<'a> {} -impl<'a> Transaction for OfferCreate<'a> { +impl<'a> Transaction<'a, OfferCreateFlag> for OfferCreate<'a> { fn has_flag(&self, flag: &OfferCreateFlag) -> bool { self.common_fields.has_flag(flag) } @@ -89,6 +89,14 @@ impl<'a> Transaction for OfferCreate<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.transaction_type.clone() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, OfferCreateFlag> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, OfferCreateFlag> { + &mut self.common_fields + } } impl<'a> OfferCreate<'a> { diff --git a/src/models/transactions/payment.rs b/src/models/transactions/payment.rs index 15e73b28..01e6e761 100644 --- a/src/models/transactions/payment.rs +++ b/src/models/transactions/payment.rs @@ -105,7 +105,7 @@ impl<'a: 'static> Model for Payment<'a> { } } -impl<'a> Transaction for Payment<'a> { +impl<'a> Transaction<'a, PaymentFlag> for Payment<'a> { fn has_flag(&self, flag: &PaymentFlag) -> bool { self.common_fields.has_flag(flag) } @@ -113,6 +113,14 @@ impl<'a> Transaction for Payment<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.transaction_type.clone() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, PaymentFlag> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, PaymentFlag> { + &mut self.common_fields + } } impl<'a> PaymentError for Payment<'a> { diff --git a/src/models/transactions/payment_channel_claim.rs b/src/models/transactions/payment_channel_claim.rs index c961c086..db1812c2 100644 --- a/src/models/transactions/payment_channel_claim.rs +++ b/src/models/transactions/payment_channel_claim.rs @@ -90,7 +90,7 @@ pub struct PaymentChannelClaim<'a> { impl<'a> Model for PaymentChannelClaim<'a> {} -impl<'a> Transaction for PaymentChannelClaim<'a> { +impl<'a> Transaction<'a, PaymentChannelClaimFlag> for PaymentChannelClaim<'a> { fn has_flag(&self, flag: &PaymentChannelClaimFlag) -> bool { self.common_fields.has_flag(flag) } @@ -98,6 +98,14 @@ impl<'a> Transaction for PaymentChannelClaim<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.transaction_type.clone() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, PaymentChannelClaimFlag> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, PaymentChannelClaimFlag> { + &mut self.common_fields + } } impl<'a> PaymentChannelClaim<'a> { diff --git a/src/models/transactions/payment_channel_create.rs b/src/models/transactions/payment_channel_create.rs index 6a68935f..677d2435 100644 --- a/src/models/transactions/payment_channel_create.rs +++ b/src/models/transactions/payment_channel_create.rs @@ -60,10 +60,18 @@ pub struct PaymentChannelCreate<'a> { impl<'a> Model for PaymentChannelCreate<'a> {} -impl<'a> Transaction for PaymentChannelCreate<'a> { +impl<'a> Transaction<'a, NoFlags> for PaymentChannelCreate<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.transaction_type.clone() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + &mut self.common_fields + } } impl<'a> PaymentChannelCreate<'a> { diff --git a/src/models/transactions/payment_channel_fund.rs b/src/models/transactions/payment_channel_fund.rs index 5a33dd8c..efcb38db 100644 --- a/src/models/transactions/payment_channel_fund.rs +++ b/src/models/transactions/payment_channel_fund.rs @@ -52,10 +52,18 @@ pub struct PaymentChannelFund<'a> { impl<'a> Model for PaymentChannelFund<'a> {} -impl<'a> Transaction for PaymentChannelFund<'a> { +impl<'a> Transaction<'a, NoFlags> for PaymentChannelFund<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.transaction_type.clone() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + &mut self.common_fields + } } impl<'a> PaymentChannelFund<'a> { diff --git a/src/models/transactions/pseudo_transactions/enable_amendment.rs b/src/models/transactions/pseudo_transactions/enable_amendment.rs index 3a949eab..7f3bdda0 100644 --- a/src/models/transactions/pseudo_transactions/enable_amendment.rs +++ b/src/models/transactions/pseudo_transactions/enable_amendment.rs @@ -52,7 +52,7 @@ pub struct EnableAmendment<'a> { impl<'a> Model for EnableAmendment<'a> {} -impl<'a> Transaction for EnableAmendment<'a> { +impl<'a> Transaction<'a, EnableAmendmentFlag> for EnableAmendment<'a> { fn has_flag(&self, flag: &EnableAmendmentFlag) -> bool { self.common_fields.has_flag(flag) } @@ -60,6 +60,14 @@ impl<'a> Transaction for EnableAmendment<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.transaction_type.clone() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, EnableAmendmentFlag> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, EnableAmendmentFlag> { + &mut self.common_fields + } } impl<'a> EnableAmendment<'a> { diff --git a/src/models/transactions/pseudo_transactions/set_fee.rs b/src/models/transactions/pseudo_transactions/set_fee.rs index 2ec56e47..273ad2cb 100644 --- a/src/models/transactions/pseudo_transactions/set_fee.rs +++ b/src/models/transactions/pseudo_transactions/set_fee.rs @@ -41,10 +41,18 @@ pub struct SetFee<'a> { impl<'a> Model for SetFee<'a> {} -impl<'a> Transaction for SetFee<'a> { +impl<'a> Transaction<'a, NoFlags> for SetFee<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.transaction_type.clone() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + &mut self.common_fields + } } impl<'a> SetFee<'a> { diff --git a/src/models/transactions/pseudo_transactions/unl_modify.rs b/src/models/transactions/pseudo_transactions/unl_modify.rs index 3bceedc6..98e40f74 100644 --- a/src/models/transactions/pseudo_transactions/unl_modify.rs +++ b/src/models/transactions/pseudo_transactions/unl_modify.rs @@ -50,10 +50,18 @@ pub struct UNLModify<'a> { impl<'a> Model for UNLModify<'a> {} -impl<'a> Transaction for UNLModify<'a> { +impl<'a> Transaction<'a, NoFlags> for UNLModify<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.transaction_type.clone() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + &mut self.common_fields + } } impl<'a> UNLModify<'a> { diff --git a/src/models/transactions/set_regular_key.rs b/src/models/transactions/set_regular_key.rs index 0fdae169..80c64ad0 100644 --- a/src/models/transactions/set_regular_key.rs +++ b/src/models/transactions/set_regular_key.rs @@ -47,10 +47,18 @@ pub struct SetRegularKey<'a> { impl<'a> Model for SetRegularKey<'a> {} -impl<'a> Transaction for SetRegularKey<'a> { +impl<'a> Transaction<'a, NoFlags> for SetRegularKey<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.transaction_type.clone() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + &mut self.common_fields + } } impl<'a> SetRegularKey<'a> { diff --git a/src/models/transactions/signer_list_set.rs b/src/models/transactions/signer_list_set.rs index 837963ea..9f4f3176 100644 --- a/src/models/transactions/signer_list_set.rs +++ b/src/models/transactions/signer_list_set.rs @@ -74,10 +74,18 @@ impl<'a> Model for SignerListSet<'a> { } } -impl<'a> Transaction for SignerListSet<'a> { +impl<'a> Transaction<'a, NoFlags> for SignerListSet<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.transaction_type.clone() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + &mut self.common_fields + } } impl<'a> SignerListSetError for SignerListSet<'a> { diff --git a/src/models/transactions/ticket_create.rs b/src/models/transactions/ticket_create.rs index 77ececd3..66b4fdc6 100644 --- a/src/models/transactions/ticket_create.rs +++ b/src/models/transactions/ticket_create.rs @@ -41,10 +41,18 @@ pub struct TicketCreate<'a> { impl<'a> Model for TicketCreate<'a> {} -impl<'a> Transaction for TicketCreate<'a> { +impl<'a> Transaction<'a, NoFlags> for TicketCreate<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.get_transaction_type() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + &mut self.common_fields + } } impl<'a> TicketCreate<'a> { diff --git a/src/models/transactions/trust_set.rs b/src/models/transactions/trust_set.rs index 86a2f2c9..911219cf 100644 --- a/src/models/transactions/trust_set.rs +++ b/src/models/transactions/trust_set.rs @@ -72,7 +72,7 @@ pub struct TrustSet<'a> { impl<'a> Model for TrustSet<'a> {} -impl<'a> Transaction for TrustSet<'a> { +impl<'a> Transaction<'a, TrustSetFlag> for TrustSet<'a> { fn has_flag(&self, flag: &TrustSetFlag) -> bool { self.common_fields.has_flag(flag) } @@ -80,6 +80,14 @@ impl<'a> Transaction for TrustSet<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.transaction_type.clone() } + + fn as_common_fields(&'a self) -> &'a CommonFields<'a, TrustSetFlag> { + &self.common_fields + } + + fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, TrustSetFlag> { + &mut self.common_fields + } } impl<'a> TrustSet<'a> { From f3802dd86c28eb911e861fbdcc0530cc97999a65 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Wed, 21 Feb 2024 17:20:31 +0000 Subject: [PATCH 03/19] current state --- src/asynch/clients/exceptions.rs | 6 ++++ src/asynch/clients/mod.rs | 15 ++++++-- src/asynch/clients/tungstenite.rs | 58 ++++++++++++++++++++++++++----- src/asynch/transaction/mod.rs | 12 +++++++ src/models/transactions/mod.rs | 11 ++++++ 5 files changed, 91 insertions(+), 11 deletions(-) diff --git a/src/asynch/clients/exceptions.rs b/src/asynch/clients/exceptions.rs index 16eb4375..9e385501 100644 --- a/src/asynch/clients/exceptions.rs +++ b/src/asynch/clients/exceptions.rs @@ -4,6 +4,12 @@ use core::str::Utf8Error; use embedded_websocket::framer_async::FramerError; use thiserror_no_std::Error; +#[derive(Debug, Error)] +pub enum XRPLClientException { + #[error("{0:?}")] + Serde(#[from] serde_json::Error), +} + #[derive(Debug, Error)] pub enum XRPLWebsocketException { #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index 0a5052df..0c181dde 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -1,4 +1,4 @@ -use alloc::string::String; +use alloc::{borrow::Cow, string::String}; use anyhow::Result; pub mod exceptions; @@ -18,12 +18,19 @@ use serde_json::Value; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] pub use tungstenite::*; +pub struct CommonFields<'a> { + pub network_id: Option>, + pub build_version: Option>, +} + pub trait WebsocketClient { fn is_open(&self) -> bool { core::any::type_name::() == core::any::type_name::() } } +/// A response from a XRPL node. +// TODO: Once Responses are implemented, replace `result: Option` with the appropriate type. #[derive(Serialize, Deserialize)] pub struct XRPLResponse { pub id: Option, @@ -34,6 +41,10 @@ pub struct XRPLResponse { pub warnings: Option, } -pub trait Client { +pub trait Client<'a> { async fn request(&mut self, req: impl Serialize) -> Result; + + async fn get_common_fields(self) -> Result>; + + async fn set_common_fields(&mut self, common_fields_response: &XRPLResponse) -> Result<()>; } diff --git a/src/asynch/clients/tungstenite.rs b/src/asynch/clients/tungstenite.rs index eb0feabb..417af837 100644 --- a/src/asynch/clients/tungstenite.rs +++ b/src/asynch/clients/tungstenite.rs @@ -1,9 +1,10 @@ use super::{ - exceptions::XRPLWebsocketException, - Client, WebsocketClient, XRPLResponse, {WebsocketClosed, WebsocketOpen}, + exceptions::XRPLWebsocketException, Client, CommonFields, WebsocketClient, WebsocketClosed, + WebsocketOpen, XRPLResponse, }; use crate::Err; +use alloc::sync::Arc; use anyhow::Result; use core::marker::PhantomData; use core::{pin::Pin, task::Poll}; @@ -19,14 +20,15 @@ use url::Url; pub use tokio_tungstenite::tungstenite::Message as TungsteniteMessage; -pub struct AsyncWebsocketClient { +pub struct AsyncWebsocketClient<'a, Status = WebsocketClosed> { + common_fields: Option>, inner: TungsteniteWebsocketStream>, status: PhantomData, } -impl WebsocketClient for AsyncWebsocketClient {} +impl<'a, Status> WebsocketClient for AsyncWebsocketClient<'a, Status> {} -impl Sink for AsyncWebsocketClient +impl<'a, I> Sink for AsyncWebsocketClient<'a, WebsocketOpen> where I: serde::Serialize, { @@ -78,7 +80,7 @@ where } } -impl Stream for AsyncWebsocketClient { +impl<'a> Stream for AsyncWebsocketClient<'a, WebsocketOpen> { type Item = Result; fn poll_next( @@ -104,10 +106,11 @@ impl Stream for AsyncWebsocketClient { } } -impl AsyncWebsocketClient { - pub async fn open(uri: Url) -> Result> { +impl<'a> AsyncWebsocketClient<'a, WebsocketClosed> { + pub async fn open(uri: Url) -> Result> { match tungstenite_connect_async(uri).await { Ok((websocket_stream, _)) => Ok(AsyncWebsocketClient { + common_fields: None, inner: websocket_stream, status: PhantomData::, }), @@ -120,7 +123,7 @@ impl AsyncWebsocketClient { } } -impl Client for AsyncWebsocketClient { +impl<'a> Client<'a> for AsyncWebsocketClient<'a, WebsocketOpen> { async fn request(&mut self, req: impl Serialize) -> Result { self.send(req).await?; while let Ok(Some(response)) = self.try_next().await { @@ -129,4 +132,41 @@ impl Client for AsyncWebsocketClient { Err!(XRPLWebsocketException::::NoResponse) } + + async fn get_common_fields(self) -> Result> { + todo!() + } + + async fn set_common_fields(&mut self, common_fields_response: &XRPLResponse) -> Result<()> { + let result = common_fields_response.result.clone(); + + let network_id = result + .as_ref() + .and_then(|result| { + result + .get("info") + .and_then(|info| info.get("network_id")) + .and_then(|network_id| network_id.as_str()) + .map(|network_id| network_id.into()) + }) + .clone(); + + let build_version = result + .as_ref() + .and_then(|result| { + result + .get("info") + .and_then(|info| info.get("build_version")) + .and_then(|build_version| build_version.as_str()) + .map(|build_version| build_version.into()) + }) + .clone(); + + self.common_fields = Some(CommonFields { + network_id, + build_version, + }); + + Ok(()) + } } diff --git a/src/asynch/transaction/mod.rs b/src/asynch/transaction/mod.rs index fcaab3a6..b294ed03 100644 --- a/src/asynch/transaction/mod.rs +++ b/src/asynch/transaction/mod.rs @@ -13,6 +13,7 @@ use strum::IntoEnumIterator; use crate::models::amount::XRPAmount; use crate::models::requests::ServerState; +use crate::models::transactions::AutofilledTransaction; use crate::models::transactions::EscrowFinish; use crate::models::transactions::Transaction; use crate::models::transactions::TransactionType; @@ -29,6 +30,17 @@ pub mod exceptions; const OWNER_RESERVE: &'static str = "2000000"; // 2 XRP +pub async fn autofill<'a, F, T>( + transaction: &'a T, + client: Option<&'a mut impl Client>, + signers_count: Option, +) -> Result<(AutofilledTransaction<'a, T>, Option<&'a mut impl Client>)> +where + T: Transaction<'a, F> + Model + 'static, + F: IntoEnumIterator + Serialize + core::fmt::Debug + PartialEq + 'a, +{ +} + async fn check_fee<'a, F, T>( transaction: &'a T, client: Option<&'a mut impl Client>, diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 05aaaa10..235b7f16 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -104,6 +104,17 @@ pub enum TransactionType { UNLModify, } +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, new)] +#[serde(rename_all = "PascalCase")] +pub struct AutofilledTransaction { + #[serde(flatten)] + pub transaction: T, + /// The network ID of the chain this transaction is intended for. + /// MUST BE OMITTED for Mainnet and some test networks. + /// REQUIRED on chains whose network ID is 1025 or higher. + pub netork_id: Option, +} + #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, new)] #[serde(rename_all = "PascalCase")] pub struct PreparedTransaction<'a, T> { From 1f90dbba01b7270cfcb843da7b153d28f7d451fc Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 15 Apr 2024 14:55:59 +0000 Subject: [PATCH 04/19] rename as_common_fields to get_common_fields --- src/models/transactions/account_delete.rs | 4 ++-- src/models/transactions/account_set.rs | 4 ++-- src/models/transactions/check_cancel.rs | 4 ++-- src/models/transactions/check_cash.rs | 4 ++-- src/models/transactions/check_create.rs | 4 ++-- src/models/transactions/deposit_preauth.rs | 4 ++-- src/models/transactions/escrow_cancel.rs | 4 ++-- src/models/transactions/escrow_create.rs | 4 ++-- src/models/transactions/escrow_finish.rs | 4 ++-- src/models/transactions/mod.rs | 8 ++++---- src/models/transactions/nftoken_accept_offer.rs | 4 ++-- src/models/transactions/nftoken_burn.rs | 4 ++-- src/models/transactions/nftoken_cancel_offer.rs | 4 ++-- src/models/transactions/nftoken_create_offer.rs | 4 ++-- src/models/transactions/nftoken_mint.rs | 4 ++-- src/models/transactions/offer_cancel.rs | 4 ++-- src/models/transactions/offer_create.rs | 4 ++-- src/models/transactions/payment.rs | 4 ++-- src/models/transactions/payment_channel_claim.rs | 4 ++-- src/models/transactions/payment_channel_create.rs | 4 ++-- src/models/transactions/payment_channel_fund.rs | 4 ++-- .../transactions/pseudo_transactions/enable_amendment.rs | 4 ++-- src/models/transactions/pseudo_transactions/set_fee.rs | 4 ++-- src/models/transactions/pseudo_transactions/unl_modify.rs | 4 ++-- src/models/transactions/set_regular_key.rs | 4 ++-- src/models/transactions/signer_list_set.rs | 4 ++-- src/models/transactions/ticket_create.rs | 4 ++-- src/models/transactions/trust_set.rs | 4 ++-- 28 files changed, 58 insertions(+), 58 deletions(-) diff --git a/src/models/transactions/account_delete.rs b/src/models/transactions/account_delete.rs index 2e89e9f1..01939d68 100644 --- a/src/models/transactions/account_delete.rs +++ b/src/models/transactions/account_delete.rs @@ -51,11 +51,11 @@ impl<'a> Transaction<'a, NoFlags> for AccountDelete<'a> { self.common_fields.get_transaction_type() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/account_set.rs b/src/models/transactions/account_set.rs index edd15652..702dfe03 100644 --- a/src/models/transactions/account_set.rs +++ b/src/models/transactions/account_set.rs @@ -150,11 +150,11 @@ impl<'a> Transaction<'a, AccountSetFlag> for AccountSet<'a> { self.common_fields.get_transaction_type() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, AccountSetFlag> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, AccountSetFlag> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, AccountSetFlag> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, AccountSetFlag> { &mut self.common_fields } } diff --git a/src/models/transactions/check_cancel.rs b/src/models/transactions/check_cancel.rs index 05406f0a..77608ee9 100644 --- a/src/models/transactions/check_cancel.rs +++ b/src/models/transactions/check_cancel.rs @@ -46,11 +46,11 @@ impl<'a> Transaction<'a, NoFlags> for CheckCancel<'a> { self.common_fields.get_transaction_type() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/check_cash.rs b/src/models/transactions/check_cash.rs index 801bf2d0..7acd9b3c 100644 --- a/src/models/transactions/check_cash.rs +++ b/src/models/transactions/check_cash.rs @@ -61,11 +61,11 @@ impl<'a> Transaction<'a, NoFlags> for CheckCash<'a> { self.common_fields.get_transaction_type() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/check_create.rs b/src/models/transactions/check_create.rs index e7a053ed..61d9b631 100644 --- a/src/models/transactions/check_create.rs +++ b/src/models/transactions/check_create.rs @@ -57,11 +57,11 @@ impl<'a> Transaction<'a, NoFlags> for CheckCreate<'a> { self.common_fields.get_transaction_type() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/deposit_preauth.rs b/src/models/transactions/deposit_preauth.rs index c9477573..75d877b8 100644 --- a/src/models/transactions/deposit_preauth.rs +++ b/src/models/transactions/deposit_preauth.rs @@ -52,11 +52,11 @@ impl<'a> Transaction<'a, NoFlags> for DepositPreauth<'a> { self.common_fields.get_transaction_type() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/escrow_cancel.rs b/src/models/transactions/escrow_cancel.rs index fb91c605..789860cc 100644 --- a/src/models/transactions/escrow_cancel.rs +++ b/src/models/transactions/escrow_cancel.rs @@ -45,11 +45,11 @@ impl<'a> Transaction<'a, NoFlags> for EscrowCancel<'a> { self.common_fields.get_transaction_type() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/escrow_create.rs b/src/models/transactions/escrow_create.rs index ccf02183..01b248a9 100644 --- a/src/models/transactions/escrow_create.rs +++ b/src/models/transactions/escrow_create.rs @@ -70,11 +70,11 @@ impl<'a> Transaction<'a, NoFlags> for EscrowCreate<'a> { self.common_fields.get_transaction_type() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/escrow_finish.rs b/src/models/transactions/escrow_finish.rs index cc7e3cf6..7ddb5b38 100644 --- a/src/models/transactions/escrow_finish.rs +++ b/src/models/transactions/escrow_finish.rs @@ -61,11 +61,11 @@ impl<'a> Transaction<'a, NoFlags> for EscrowFinish<'a> { self.common_fields.transaction_type.clone() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 235b7f16..17262670 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -243,11 +243,11 @@ where self.transaction_type.clone() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, T> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, T> { self } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, T> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, T> { self } } @@ -297,9 +297,9 @@ where fn get_transaction_type(&self) -> TransactionType; - fn as_common_fields(&'a self) -> &'a CommonFields<'a, T>; + fn get_common_fields(&'a self) -> &'a CommonFields<'a, T>; - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, T>; + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, T>; } #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Display, AsRefStr)] diff --git a/src/models/transactions/nftoken_accept_offer.rs b/src/models/transactions/nftoken_accept_offer.rs index 00e0b77f..c8e46f7e 100644 --- a/src/models/transactions/nftoken_accept_offer.rs +++ b/src/models/transactions/nftoken_accept_offer.rs @@ -75,11 +75,11 @@ impl<'a> Transaction<'a, NoFlags> for NFTokenAcceptOffer<'a> { self.common_fields.transaction_type.clone() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/nftoken_burn.rs b/src/models/transactions/nftoken_burn.rs index d2ef0475..57bd85f7 100644 --- a/src/models/transactions/nftoken_burn.rs +++ b/src/models/transactions/nftoken_burn.rs @@ -53,11 +53,11 @@ impl<'a> Transaction<'a, NoFlags> for NFTokenBurn<'a> { self.common_fields.transaction_type.clone() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/nftoken_cancel_offer.rs b/src/models/transactions/nftoken_cancel_offer.rs index 7ef852e2..159778bc 100644 --- a/src/models/transactions/nftoken_cancel_offer.rs +++ b/src/models/transactions/nftoken_cancel_offer.rs @@ -61,11 +61,11 @@ impl<'a> Transaction<'a, NoFlags> for NFTokenCancelOffer<'a> { self.common_fields.transaction_type.clone() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/nftoken_create_offer.rs b/src/models/transactions/nftoken_create_offer.rs index 95041e4e..4c2a8b12 100644 --- a/src/models/transactions/nftoken_create_offer.rs +++ b/src/models/transactions/nftoken_create_offer.rs @@ -105,11 +105,11 @@ impl<'a> Transaction<'a, NFTokenCreateOfferFlag> for NFTokenCreateOffer<'a> { self.common_fields.transaction_type.clone() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NFTokenCreateOfferFlag> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NFTokenCreateOfferFlag> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NFTokenCreateOfferFlag> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NFTokenCreateOfferFlag> { &mut self.common_fields } } diff --git a/src/models/transactions/nftoken_mint.rs b/src/models/transactions/nftoken_mint.rs index f7db2c95..caafdd2c 100644 --- a/src/models/transactions/nftoken_mint.rs +++ b/src/models/transactions/nftoken_mint.rs @@ -114,11 +114,11 @@ impl<'a> Transaction<'a, NFTokenMintFlag> for NFTokenMint<'a> { self.common_fields.transaction_type.clone() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NFTokenMintFlag> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NFTokenMintFlag> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NFTokenMintFlag> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NFTokenMintFlag> { &mut self.common_fields } } diff --git a/src/models/transactions/offer_cancel.rs b/src/models/transactions/offer_cancel.rs index 0b7f3808..cc57e262 100644 --- a/src/models/transactions/offer_cancel.rs +++ b/src/models/transactions/offer_cancel.rs @@ -48,11 +48,11 @@ impl<'a> Transaction<'a, NoFlags> for OfferCancel<'a> { self.common_fields.transaction_type.clone() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/offer_create.rs b/src/models/transactions/offer_create.rs index cd1261cb..29c4ab79 100644 --- a/src/models/transactions/offer_create.rs +++ b/src/models/transactions/offer_create.rs @@ -90,11 +90,11 @@ impl<'a> Transaction<'a, OfferCreateFlag> for OfferCreate<'a> { self.common_fields.transaction_type.clone() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, OfferCreateFlag> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, OfferCreateFlag> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, OfferCreateFlag> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, OfferCreateFlag> { &mut self.common_fields } } diff --git a/src/models/transactions/payment.rs b/src/models/transactions/payment.rs index 01e6e761..f4ed94cf 100644 --- a/src/models/transactions/payment.rs +++ b/src/models/transactions/payment.rs @@ -114,11 +114,11 @@ impl<'a> Transaction<'a, PaymentFlag> for Payment<'a> { self.common_fields.transaction_type.clone() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, PaymentFlag> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, PaymentFlag> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, PaymentFlag> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, PaymentFlag> { &mut self.common_fields } } diff --git a/src/models/transactions/payment_channel_claim.rs b/src/models/transactions/payment_channel_claim.rs index db1812c2..f9e6f7b6 100644 --- a/src/models/transactions/payment_channel_claim.rs +++ b/src/models/transactions/payment_channel_claim.rs @@ -99,11 +99,11 @@ impl<'a> Transaction<'a, PaymentChannelClaimFlag> for PaymentChannelClaim<'a> { self.common_fields.transaction_type.clone() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, PaymentChannelClaimFlag> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, PaymentChannelClaimFlag> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, PaymentChannelClaimFlag> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, PaymentChannelClaimFlag> { &mut self.common_fields } } diff --git a/src/models/transactions/payment_channel_create.rs b/src/models/transactions/payment_channel_create.rs index 677d2435..31d29356 100644 --- a/src/models/transactions/payment_channel_create.rs +++ b/src/models/transactions/payment_channel_create.rs @@ -65,11 +65,11 @@ impl<'a> Transaction<'a, NoFlags> for PaymentChannelCreate<'a> { self.common_fields.transaction_type.clone() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/payment_channel_fund.rs b/src/models/transactions/payment_channel_fund.rs index efcb38db..2b67e881 100644 --- a/src/models/transactions/payment_channel_fund.rs +++ b/src/models/transactions/payment_channel_fund.rs @@ -57,11 +57,11 @@ impl<'a> Transaction<'a, NoFlags> for PaymentChannelFund<'a> { self.common_fields.transaction_type.clone() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/pseudo_transactions/enable_amendment.rs b/src/models/transactions/pseudo_transactions/enable_amendment.rs index 7f3bdda0..b493f9d2 100644 --- a/src/models/transactions/pseudo_transactions/enable_amendment.rs +++ b/src/models/transactions/pseudo_transactions/enable_amendment.rs @@ -61,11 +61,11 @@ impl<'a> Transaction<'a, EnableAmendmentFlag> for EnableAmendment<'a> { self.common_fields.transaction_type.clone() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, EnableAmendmentFlag> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, EnableAmendmentFlag> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, EnableAmendmentFlag> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, EnableAmendmentFlag> { &mut self.common_fields } } diff --git a/src/models/transactions/pseudo_transactions/set_fee.rs b/src/models/transactions/pseudo_transactions/set_fee.rs index 273ad2cb..89ed9d3c 100644 --- a/src/models/transactions/pseudo_transactions/set_fee.rs +++ b/src/models/transactions/pseudo_transactions/set_fee.rs @@ -46,11 +46,11 @@ impl<'a> Transaction<'a, NoFlags> for SetFee<'a> { self.common_fields.transaction_type.clone() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/pseudo_transactions/unl_modify.rs b/src/models/transactions/pseudo_transactions/unl_modify.rs index 98e40f74..3a50da19 100644 --- a/src/models/transactions/pseudo_transactions/unl_modify.rs +++ b/src/models/transactions/pseudo_transactions/unl_modify.rs @@ -55,11 +55,11 @@ impl<'a> Transaction<'a, NoFlags> for UNLModify<'a> { self.common_fields.transaction_type.clone() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/set_regular_key.rs b/src/models/transactions/set_regular_key.rs index 80c64ad0..683b7337 100644 --- a/src/models/transactions/set_regular_key.rs +++ b/src/models/transactions/set_regular_key.rs @@ -52,11 +52,11 @@ impl<'a> Transaction<'a, NoFlags> for SetRegularKey<'a> { self.common_fields.transaction_type.clone() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/signer_list_set.rs b/src/models/transactions/signer_list_set.rs index 9f4f3176..698ea089 100644 --- a/src/models/transactions/signer_list_set.rs +++ b/src/models/transactions/signer_list_set.rs @@ -79,11 +79,11 @@ impl<'a> Transaction<'a, NoFlags> for SignerListSet<'a> { self.common_fields.transaction_type.clone() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/ticket_create.rs b/src/models/transactions/ticket_create.rs index 66b4fdc6..448d67cd 100644 --- a/src/models/transactions/ticket_create.rs +++ b/src/models/transactions/ticket_create.rs @@ -46,11 +46,11 @@ impl<'a> Transaction<'a, NoFlags> for TicketCreate<'a> { self.common_fields.get_transaction_type() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/trust_set.rs b/src/models/transactions/trust_set.rs index 911219cf..b179ef5f 100644 --- a/src/models/transactions/trust_set.rs +++ b/src/models/transactions/trust_set.rs @@ -81,11 +81,11 @@ impl<'a> Transaction<'a, TrustSetFlag> for TrustSet<'a> { self.common_fields.transaction_type.clone() } - fn as_common_fields(&'a self) -> &'a CommonFields<'a, TrustSetFlag> { + fn get_common_fields(&'a self) -> &'a CommonFields<'a, TrustSetFlag> { &self.common_fields } - fn as_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, TrustSetFlag> { + fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, TrustSetFlag> { &mut self.common_fields } } From 97c2f1161d260e53c202f5c8179224bf874064e7 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 15 Apr 2024 14:56:49 +0000 Subject: [PATCH 05/19] feature relevant results --- src/models/results/account_info.rs | 8 ++ src/models/results/fee.rs | 16 ++++ src/models/results/ledger.rs | 28 +++++++ src/models/results/mod.rs | 113 +++++++++++++++++++++++++++++ src/models/results/server_state.rs | 26 +++++++ 5 files changed, 191 insertions(+) create mode 100644 src/models/results/account_info.rs create mode 100644 src/models/results/fee.rs create mode 100644 src/models/results/ledger.rs create mode 100644 src/models/results/mod.rs create mode 100644 src/models/results/server_state.rs diff --git a/src/models/results/account_info.rs b/src/models/results/account_info.rs new file mode 100644 index 00000000..459e9b04 --- /dev/null +++ b/src/models/results/account_info.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +use crate::models::ledger::AccountRoot; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AccountInfo<'a> { + pub account_data: AccountRoot<'a>, +} diff --git a/src/models/results/fee.rs b/src/models/results/fee.rs new file mode 100644 index 00000000..59284566 --- /dev/null +++ b/src/models/results/fee.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +use crate::models::amount::XRPAmount; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Fee<'a> { + pub drops: Drops<'a>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Drops<'a> { + pub base_fee: XRPAmount<'a>, + pub median_fee: XRPAmount<'a>, + pub minimum_fee: XRPAmount<'a>, + pub open_ledger_fee: XRPAmount<'a>, +} diff --git a/src/models/results/ledger.rs b/src/models/results/ledger.rs new file mode 100644 index 00000000..5e0cb2d3 --- /dev/null +++ b/src/models/results/ledger.rs @@ -0,0 +1,28 @@ +use alloc::{borrow::Cow, vec::Vec}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Ledger<'a> { + pub ledger: LedgerInner<'a>, + pub ledger_hash: Cow<'a, str>, + pub ledger_index: u32, + pub validated: Option, + pub queue_data: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LedgerInner<'a> { + pub account_hash: Cow<'a, str>, + pub close_flags: u32, + pub close_time: u32, + pub close_time_human: Option>, + pub close_time_resolution: u32, + pub closed: bool, + pub ledger_hash: Cow<'a, str>, + pub ledger_index: u32, + pub parent_close_time: u32, + pub parent_hash: Cow<'a, str>, + pub total_coins: Cow<'a, str>, + pub transaction_hash: Cow<'a, str>, + pub transactions: Option>>, +} diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs new file mode 100644 index 00000000..9bb7db6d --- /dev/null +++ b/src/models/results/mod.rs @@ -0,0 +1,113 @@ +pub mod account_info; +pub mod fee; +pub mod ledger; +pub mod server_state; + +pub use account_info::*; +pub use fee::*; +pub use ledger::*; +pub use server_state::*; + +use alloc::{borrow::Cow, vec::Vec}; +use anyhow::Result; +use futures::{Stream, StreamExt}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use crate::Err; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ResponseStatus { + Success, + Error, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum ResponseType { + Response, + LedgerClosed, + Transaction, +} + +pub trait XRPLResponseFromStream Deserialize<'de>>: + StreamExt> +{ + async fn next_xrpl_response(&mut self) -> Option>> + where + Self: Unpin; + + async fn try_next_xrpl_response(&mut self) -> Result>> + where + Self: Unpin, + { + match self.next_xrpl_response().await { + Some(response) => response.map(Some), + None => Ok(None), + } + } +} + +impl XRPLResponseFromStream for S +where + S: Stream> + StreamExt> + Unpin, + T: for<'de> Deserialize<'de>, +{ + async fn next_xrpl_response(&mut self) -> Option>> + where + Self: StreamExt>, + { + let item = self.next().await; + match item { + Some(Ok(message)) => match serde_json::from_value(message) { + Ok(response) => Some(Ok(response)), + Err(error) => Some(Err!(error)), + }, + Some(Err(error)) => Some(Err!(error)), + None => None, + } + } +} + +/// A response from a XRPL node. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct XRPLResponse<'a, T> { + pub id: Option>, + pub result: T, + pub status: Option, + pub r#type: Option, + pub forwarded: Option, + pub warnings: Option>>, + pub warning: Option>, +} + +impl XRPLResponse<'_, T> { + pub fn is_successful(&self) -> bool { + match self.status { + Some(ResponseStatus::Success) => true, + _ => false, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct XRPLErrorResponse<'a, T> { + pub id: Cow<'a, str>, + pub error: Option>, + pub error_code: Option, + pub error_message: Option>, + pub request: Option, + pub status: Option, + pub r#type: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct XRPLWarning<'a> { + pub id: Cow<'a, str>, + pub message: Cow<'a, str>, + pub forwarded: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EmptyResult; diff --git a/src/models/results/server_state.rs b/src/models/results/server_state.rs new file mode 100644 index 00000000..1958bbcd --- /dev/null +++ b/src/models/results/server_state.rs @@ -0,0 +1,26 @@ +use alloc::borrow::Cow; +use serde::{Deserialize, Serialize}; + +use crate::models::amount::XRPAmount; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ServerState<'a> { + pub state: State<'a>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct State<'a> { + pub build_version: Cow<'a, str>, + pub network_id: Option, + pub validated_ledger: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ValidatedLedger<'a> { + pub base_fee_xrp: XRPAmount<'a>, + pub close_time: u32, + pub hash: Cow<'a, str>, + pub reserve_base: XRPAmount<'a>, + pub reserve_inc: XRPAmount<'a>, + pub seq: u32, +} From 8c8da6f891e85beff6fa8adf156b1935663042e0 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 15 Apr 2024 14:57:04 +0000 Subject: [PATCH 06/19] add autofill --- Cargo.toml | 3 +- src/asynch/account/mod.rs | 46 ++++++++ src/asynch/clients/exceptions.rs | 6 +- src/asynch/clients/mod.rs | 28 ++--- src/asynch/clients/tungstenite.rs | 58 ++++------ src/asynch/ledger/mod.rs | 62 ++++++---- src/asynch/mod.rs | 1 + src/asynch/transaction/mod.rs | 184 +++++++++++++++++++++++------- src/models/exceptions.rs | 9 +- src/models/mod.rs | 2 + 10 files changed, 275 insertions(+), 124 deletions(-) create mode 100644 src/asynch/account/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 449b1a98..7a1b888d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,9 +99,10 @@ harness = false [features] default = ["std", "core", "models", "utils", "tungstenite"] -models = ["core", "transactions", "requests", "ledger"] +models = ["core", "transactions", "requests", "ledger", "results"] transactions = ["core", "amounts", "currencies"] requests = ["core", "amounts", "currencies"] +results = ["core", "amounts", "currencies"] ledger = ["core", "amounts", "currencies"] amounts = ["core"] currencies = ["core"] diff --git a/src/asynch/account/mod.rs b/src/asynch/account/mod.rs new file mode 100644 index 00000000..0f7b6f5a --- /dev/null +++ b/src/asynch/account/mod.rs @@ -0,0 +1,46 @@ +use alloc::borrow::Cow; +use anyhow::Result; + +use crate::{ + core::addresscodec::{is_valid_xaddress, xaddress_to_classic_address}, + models::{ledger::AccountRoot, requests::AccountInfo, results}, +}; + +use super::clients::Client; + +pub async fn get_next_valid_seq_number<'a>( + address: Cow<'a, str>, + client: &'a mut impl Client<'a>, + ledger_index: Option>, +) -> Result { + let account_info = + get_account_root(address, client, ledger_index.unwrap_or("current".into())).await?; + Ok(account_info.sequence) +} + +pub async fn get_account_root<'a>( + address: Cow<'a, str>, + client: &'a mut impl Client<'a>, + ledger_index: Cow<'a, str>, +) -> Result> { + let mut classic_address = address; + if is_valid_xaddress(&classic_address) { + classic_address = xaddress_to_classic_address(&classic_address) + .unwrap() + .0 + .into(); + } + let account_info = client + .request::(AccountInfo::new( + None, + classic_address, + None, + Some(ledger_index), + None, + None, + None, + )) + .await?; + + Ok(account_info.result.account_data) +} diff --git a/src/asynch/clients/exceptions.rs b/src/asynch/clients/exceptions.rs index 9e385501..b0e249bd 100644 --- a/src/asynch/clients/exceptions.rs +++ b/src/asynch/clients/exceptions.rs @@ -12,7 +12,6 @@ pub enum XRPLClientException { #[derive(Debug, Error)] pub enum XRPLWebsocketException { - #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] #[error("Unable to connect to websocket")] UnableToConnect(tokio_tungstenite::tungstenite::Error), // FramerError @@ -33,6 +32,10 @@ pub enum XRPLWebsocketException { RxBufferTooSmall(usize), #[error("No response")] NoResponse, + #[error("No result")] + NoResult, + #[error("No info")] + NoInfo, #[error("Unexpected message type")] UnexpectedMessageType, } @@ -48,6 +51,7 @@ impl From> for XRPLWebsocketException { FramerError::WebSocket(e) => XRPLWebsocketException::WebSocket(e), FramerError::Disconnected => XRPLWebsocketException::Disconnected, FramerError::RxBufferTooSmall(e) => XRPLWebsocketException::RxBufferTooSmall(e), + FramerError::UnableToConnect(e) => XRPLWebsocketException::UnableToConnect(e), } } } diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index 0c181dde..548def7c 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -1,4 +1,4 @@ -use alloc::{borrow::Cow, string::String}; +use alloc::borrow::Cow; use anyhow::Result; pub mod exceptions; @@ -14,13 +14,15 @@ mod tungstenite; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] pub use embedded_websocket::*; use serde::{Deserialize, Serialize}; -use serde_json::Value; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] pub use tungstenite::*; +use crate::models::results::XRPLResponse; + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct CommonFields<'a> { - pub network_id: Option>, pub build_version: Option>, + pub network_id: Option, } pub trait WebsocketClient { @@ -29,22 +31,12 @@ pub trait WebsocketClient { } } -/// A response from a XRPL node. -// TODO: Once Responses are implemented, replace `result: Option` with the appropriate type. -#[derive(Serialize, Deserialize)] -pub struct XRPLResponse { - pub id: Option, - pub result: Option, - pub status: Option, - pub r#type: Option, - pub forwarded: Option, - pub warnings: Option, -} - pub trait Client<'a> { - async fn request(&mut self, req: impl Serialize) -> Result; + async fn request(&mut self, req: impl Serialize) -> Result> + where + T: for<'de> Deserialize<'de> + Clone; - async fn get_common_fields(self) -> Result>; + fn get_common_fields(&self) -> Option>; - async fn set_common_fields(&mut self, common_fields_response: &XRPLResponse) -> Result<()>; + async fn set_common_fields(&mut self) -> Result<()>; } diff --git a/src/asynch/clients/tungstenite.rs b/src/asynch/clients/tungstenite.rs index 417af837..8767c6df 100644 --- a/src/asynch/clients/tungstenite.rs +++ b/src/asynch/clients/tungstenite.rs @@ -1,16 +1,19 @@ use super::{ exceptions::XRPLWebsocketException, Client, CommonFields, WebsocketClient, WebsocketClosed, - WebsocketOpen, XRPLResponse, + WebsocketOpen, }; +use crate::models::requests; +use crate::models::results::{self, XRPLResponse}; use crate::Err; -use alloc::sync::Arc; +use crate::models::results::XRPLResponseFromStream; use anyhow::Result; use core::marker::PhantomData; use core::{pin::Pin, task::Poll}; use futures::{Sink, Stream}; -use futures_util::{SinkExt, TryStreamExt}; -use serde::Serialize; +use futures_util::SinkExt; +use serde::{Deserialize, Serialize}; +use serde_json::Value; use tokio::net::TcpStream; use tokio_tungstenite::{ connect_async as tungstenite_connect_async, MaybeTlsStream as TungsteniteMaybeTlsStream, @@ -81,7 +84,7 @@ where } impl<'a> Stream for AsyncWebsocketClient<'a, WebsocketOpen> { - type Item = Result; + type Item = Result; fn poll_next( mut self: Pin<&mut Self>, @@ -124,47 +127,30 @@ impl<'a> AsyncWebsocketClient<'a, WebsocketClosed> { } impl<'a> Client<'a> for AsyncWebsocketClient<'a, WebsocketOpen> { - async fn request(&mut self, req: impl Serialize) -> Result { + async fn request(&mut self, req: impl Serialize) -> Result> + where + T: for<'de> Deserialize<'de>, + { self.send(req).await?; - while let Ok(Some(response)) = self.try_next().await { + while let Ok(Some(response)) = self.try_next_xrpl_response().await { return Ok(response); } Err!(XRPLWebsocketException::::NoResponse) } - async fn get_common_fields(self) -> Result> { - todo!() + fn get_common_fields(&self) -> Option> { + self.common_fields.clone() } - async fn set_common_fields(&mut self, common_fields_response: &XRPLResponse) -> Result<()> { - let result = common_fields_response.result.clone(); - - let network_id = result - .as_ref() - .and_then(|result| { - result - .get("info") - .and_then(|info| info.get("network_id")) - .and_then(|network_id| network_id.as_str()) - .map(|network_id| network_id.into()) - }) - .clone(); - - let build_version = result - .as_ref() - .and_then(|result| { - result - .get("info") - .and_then(|info| info.get("build_version")) - .and_then(|build_version| build_version.as_str()) - .map(|build_version| build_version.into()) - }) - .clone(); - + async fn set_common_fields(&mut self) -> Result<()> { + let server_state = self + .request::(requests::ServerState::new(None)) + .await?; + let state = server_state.result.state.clone(); self.common_fields = Some(CommonFields { - network_id, - build_version, + network_id: state.network_id, + build_version: Some(state.build_version), }); Ok(()) diff --git a/src/asynch/ledger/mod.rs b/src/asynch/ledger/mod.rs index 4e381d26..5138b5fe 100644 --- a/src/asynch/ledger/mod.rs +++ b/src/asynch/ledger/mod.rs @@ -2,12 +2,39 @@ use core::cmp::min; use alloc::string::ToString; use anyhow::Result; -use serde_json::Value; -use crate::models::{amount::XRPAmount, requests::Fee}; +use crate::models::{ + amount::XRPAmount, + requests::{Fee, Ledger}, + results::{ + self, + fee::{Drops, Fee as FeeResult}, + }, +}; use super::clients::Client; +pub async fn get_latest_validated_ledger_sequence<'a>( + client: &'a mut impl Client<'a>, +) -> Result { + let ledger_response = client + .request::(Ledger::new( + None, + None, + None, + None, + None, + None, + Some("validated".into()), + None, + None, + None, + )) + .await?; + + Ok(ledger_response.result.ledger_index) +} + pub enum FeeType { Open, Minimum, @@ -15,41 +42,30 @@ pub enum FeeType { } pub async fn get_fee<'a>( - client: &'a mut impl Client, + client: &'a mut impl Client<'a>, max_fee: Option, fee_type: Option, -) -> Result<(XRPAmount<'a>, &'a mut impl Client)> { +) -> Result> { let fee_request = Fee::new(None); - match client.request(fee_request).await { + match client.request::>(fee_request).await { Ok(response) => { - let response_value = serde_json::to_value(&response).unwrap(); - let drops = response_value.get("result").unwrap().get("drops").unwrap(); - let fee = match_fee_type(fee_type, drops); + let drops = response.result.drops; + let fee = match_fee_type(fee_type, drops).unwrap(); if let Some(max_fee) = max_fee { - Ok((XRPAmount::from(min(max_fee, fee).to_string()), client)) + Ok(XRPAmount::from(min(max_fee, fee).to_string())) } else { - Ok((XRPAmount::from(fee.to_string()), client)) + Ok(XRPAmount::from(fee.to_string())) } } Err(err) => Err(err), } } -fn match_fee_type(fee_type: Option, drops: &Value) -> u16 { +fn match_fee_type<'a>(fee_type: Option, drops: Drops<'a>) -> Result { match fee_type { - None | Some(FeeType::Open) => drops - .get("open_ledger_fee") - .unwrap() - .to_string() - .parse() - .unwrap(), - Some(FeeType::Minimum) => drops - .get("minimum_fee") - .unwrap() - .to_string() - .parse() - .unwrap(), + None | Some(FeeType::Open) => Ok(drops.open_ledger_fee.0.to_string().parse().unwrap()), + Some(FeeType::Minimum) => Ok(drops.minimum_fee.0.to_string().parse().unwrap()), Some(FeeType::Dynamic) => unimplemented!("Dynamic fee calculation not yet implemented"), } } diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index 8c1b3084..112bb945 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -1,3 +1,4 @@ +pub mod account; #[cfg(any(feature = "tungstenite", feature = "embedded-ws"))] pub mod clients; pub mod ledger; diff --git a/src/asynch/transaction/mod.rs b/src/asynch/transaction/mod.rs index b294ed03..ec751f73 100644 --- a/src/asynch/transaction/mod.rs +++ b/src/asynch/transaction/mod.rs @@ -1,18 +1,19 @@ use core::any::Any; -use core::convert::TryFrom; use core::convert::TryInto; use alloc::borrow::Cow; +use alloc::string::String; use alloc::string::ToString; use alloc::vec::Vec; -use anyhow::Ok; use anyhow::Result; use rust_decimal::Decimal; use serde::Serialize; use strum::IntoEnumIterator; use crate::models::amount::XRPAmount; +use crate::models::exceptions::XRPLModelException; use crate::models::requests::ServerState; +use crate::models::results::server_state::ServerState as ServerStateResult; use crate::models::transactions::AutofilledTransaction; use crate::models::transactions::EscrowFinish; use crate::models::transactions::Transaction; @@ -22,80 +23,103 @@ use crate::Err; use self::exceptions::XRPLTransactionException; +use super::account::get_next_valid_seq_number; use super::clients::Client; -use super::clients::XRPLResponse; use super::ledger::get_fee; +use super::ledger::get_latest_validated_ledger_sequence; pub mod exceptions; const OWNER_RESERVE: &'static str = "2000000"; // 2 XRP +const RESTRICTED_NETWORKS: u16 = 1024; +const REQUIRED_NETWORKID_VERSION: &'static str = "1.11.0"; +const LEDGER_OFFSET: u8 = 20; pub async fn autofill<'a, F, T>( - transaction: &'a T, - client: Option<&'a mut impl Client>, + transaction: T, + client: &'a mut impl Client<'a>, signers_count: Option, -) -> Result<(AutofilledTransaction<'a, T>, Option<&'a mut impl Client>)> +) -> Result> where T: Transaction<'a, F> + Model + 'static, F: IntoEnumIterator + Serialize + core::fmt::Debug + PartialEq + 'a, { + let mut autofilled_txn = AutofilledTransaction::new(transaction, None); + set_network_id_and_build_version(client).await.unwrap(); + if autofilled_txn.netork_id.is_none() && txn_needs_network_id(client) { + autofilled_txn.netork_id = client.get_common_fields().unwrap().network_id; + } + let txn_common_fields = autofilled_txn.transaction.get_mut_common_fields(); + if txn_common_fields.sequence.is_none() { + txn_common_fields.sequence = + Some(get_next_valid_seq_number(txn_common_fields.account.clone(), client, None).await?); + } + if txn_common_fields.fee.is_none() { + txn_common_fields.fee = Some( + calculate_fee_per_transaction_type( + &autofilled_txn.transaction, + Some(client), + signers_count, + ) + .await?, + ); + } + if txn_common_fields.last_ledger_sequence.is_none() { + let ledger_sequence = get_latest_validated_ledger_sequence(client).await?; + txn_common_fields.last_ledger_sequence = Some(ledger_sequence + LEDGER_OFFSET as u32); + } + + Ok(autofilled_txn) } async fn check_fee<'a, F, T>( transaction: &'a T, - client: Option<&'a mut impl Client>, + client: Option<&'a mut impl Client<'a>>, signers_count: Option, -) -> Result> +) -> Result<()> where T: Transaction<'a, F> + Model + 'static, F: IntoEnumIterator + Serialize + core::fmt::Debug + PartialEq + 'a, { let fee_to_high = XRPAmount::from("1000"); - let (calculated_fee, client_ref) = - calculate_fee_per_transaction_type(transaction, client, signers_count) - .await - .unwrap(); + let calculated_fee = calculate_fee_per_transaction_type(transaction, client, signers_count) + .await + .unwrap(); let expected_fee = fee_to_high.max(calculated_fee); - let common_fields = transaction.as_common_fields(); + let common_fields = transaction.get_common_fields(); if let Some(fee) = &common_fields.fee { if fee < &expected_fee { return Err!(XRPLTransactionException::FeeUnusuallyHigh(fee.clone())); } } - Ok(client_ref) + Ok(()) } async fn calculate_fee_per_transaction_type<'a, T, F>( transaction: &'a T, - client: Option<&'a mut impl Client>, + client: Option<&'a mut impl Client<'a>>, signers_count: Option, -) -> Result<(XRPAmount<'a>, Option<&'a mut impl Client>)> +) -> Result> where T: Transaction<'a, F> + 'static, F: IntoEnumIterator + Serialize + core::fmt::Debug + PartialEq, { if let Some(client) = client { - let (net_fee, client_ref) = get_fee(client, None, None).await?; - let (mut base_fee, client_) = match transaction.get_transaction_type() { + let net_fee = get_fee(client, None, None).await?; + let mut base_fee = match transaction.get_transaction_type() { TransactionType::EscrowFinish => { - let base_fee = calculate_base_fee_for_escrow_finish(transaction, net_fee.clone()); - (base_fee, client_ref) + calculate_base_fee_for_escrow_finish(transaction, net_fee.clone()) } // TODO: same for TransactionType::AMMCreate - TransactionType::AccountDelete => { - let owner_reserve_response = client_ref.request(ServerState::new(None)).await?; - let owner_reserve = get_owner_reserve_from_response(owner_reserve_response); - - (owner_reserve, client_ref) - } - _ => (net_fee.clone(), client_ref), + TransactionType::AccountDelete => get_owner_reserve_from_response(client).await?, + _ => net_fee.clone(), }; if let Some(signers_count) = signers_count { base_fee += net_fee.clone() * (1 + signers_count); } - return Ok((base_fee.ceil(), Some(client_))); + Ok(base_fee.ceil()) } else { let net_fee = XRPAmount::from("10"); let mut base_fee = match transaction.get_transaction_type() { @@ -110,22 +134,20 @@ where base_fee += net_fee.clone() * (1 + signers_count); } - return Ok((base_fee.ceil(), None)); + Ok(base_fee.ceil()) } } -fn get_owner_reserve_from_response<'a>(response: XRPLResponse) -> XRPAmount<'a> { - let owner_reserve = response - .result - .unwrap() - .get("state") - .unwrap() - .get("validated_ledger") - .unwrap() - .get("reserve_inc") - .unwrap() - .clone(); - XRPAmount::try_from(owner_reserve).unwrap() +async fn get_owner_reserve_from_response<'a>( + client: &'a mut impl Client<'a>, +) -> Result> { + let owner_reserve_response = client + .request::>(ServerState::new(None)) + .await?; + match owner_reserve_response.result.state.validated_ledger { + Some(validated_ledger) => Ok(validated_ledger.reserve_base), + None => Err!(XRPLModelException::MissingField("validated_ledger")), + } } fn calculate_base_fee_for_escrow_finish<'a, T, F>( @@ -133,7 +155,7 @@ fn calculate_base_fee_for_escrow_finish<'a, T, F>( net_fee: XRPAmount<'a>, ) -> XRPAmount<'a> where - T: Transaction<'a, F> + 'static, + T: Transaction<'a, F> + 'static, // must outlive 'static for downcasting F: IntoEnumIterator + Serialize + core::fmt::Debug + PartialEq, { if transaction.get_transaction_type() == TransactionType::EscrowFinish { @@ -161,3 +183,81 @@ fn calculate_based_on_fulfillment<'a>( let base_fee_decimal: Decimal = base_fee_string.parse().unwrap(); XRPAmount::from(base_fee_decimal.ceil()) } + +async fn set_network_id_and_build_version<'a>(client: &'a mut impl Client<'a>) -> Result<()> { + if client.get_common_fields().is_none() { + client.set_common_fields().await?; + } + + Ok(()) +} + +fn txn_needs_network_id<'a>(client: &'a mut impl Client<'a>) -> bool { + if let Some(common_fields) = client.get_common_fields() { + common_fields.network_id.unwrap() > RESTRICTED_NETWORKS as u32 + && is_not_later_rippled_version( + REQUIRED_NETWORKID_VERSION.into(), + common_fields.build_version.unwrap().into(), + ) + } else { + false + } +} + +fn is_not_later_rippled_version(source: String, target: String) -> bool { + if source == target { + true + } else { + let source_decomp = source + .split('.') + .map(|i| i.to_string()) + .collect::>(); + let target_decomp = target + .split('.') + .map(|i| i.to_string()) + .collect::>(); + let (source_major, source_minor) = ( + source_decomp[0].parse::().unwrap(), + source_decomp[1].parse::().unwrap(), + ); + let (target_major, target_minor) = ( + target_decomp[0].parse::().unwrap(), + target_decomp[1].parse::().unwrap(), + ); + + if source_major != target_major { + source_major < target_major + } else if source_minor != target_minor { + source_minor < target_minor + } else { + let source_patch = source_decomp[2] + .split('-') + .map(|i| i.to_string()) + .collect::>(); + let target_patch = target_decomp[2] + .split('-') + .map(|i| i.to_string()) + .collect::>(); + let source_patch_version = source_patch[0].parse::().unwrap(); + let target_patch_version = target_patch[0].parse::().unwrap(); + + if source_patch_version != target_patch_version { + source_patch_version < target_patch_version + } else if source_patch.len() != target_patch.len() { + source_patch.len() < target_patch.len() + } else if source_patch.len() == 2 { + if source_patch[1].chars().nth(0).unwrap() + != target_patch[1].chars().nth(0).unwrap() + { + source_patch[1] < target_patch[1] + } else if source_patch[1].starts_with('b') { + &source_patch[1][1..] < &target_patch[1][1..] + } else { + &source_patch[1][2..] < &target_patch[1][2..] + } + } else { + false + } + } + } +} diff --git a/src/models/exceptions.rs b/src/models/exceptions.rs index 50f3183d..1acd1b47 100644 --- a/src/models/exceptions.rs +++ b/src/models/exceptions.rs @@ -4,15 +4,18 @@ use crate::models::requests::XRPLRequestException; use crate::models::transactions::XRPLTransactionException; use alloc::string::String; use serde::{Deserialize, Serialize}; -use strum_macros::Display; use thiserror_no_std::Error; -#[derive(Debug, PartialEq, Display)] -#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Error)] pub enum XRPLModelException<'a> { + #[error("Issued Currency can not be XRP")] InvalidICCannotBeXRP, + #[error("Transaction Model Error: {0}")] XRPLTransactionError(XRPLTransactionException<'a>), + #[error("Request Model Error: {0}")] XRPLRequestError(XRPLRequestException<'a>), + #[error("Missing Field: {0}")] + MissingField(&'a str), } #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] diff --git a/src/models/mod.rs b/src/models/mod.rs index 7c4a3165..ceb73419 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -14,6 +14,8 @@ pub mod model; #[cfg(feature = "requests")] #[allow(clippy::too_many_arguments)] pub mod requests; +#[cfg(feature = "results")] +pub mod results; #[cfg(feature = "transactions")] #[allow(clippy::too_many_arguments)] pub mod transactions; From 97f3b5a9ed98e986d00d5aac23355055d8660845 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sun, 28 Apr 2024 15:04:09 +0000 Subject: [PATCH 07/19] add remaining common transaction fields --- src/models/transactions/account_delete.rs | 3 +++ src/models/transactions/account_set.rs | 3 +++ src/models/transactions/check_cancel.rs | 3 +++ src/models/transactions/check_cash.rs | 3 +++ src/models/transactions/check_create.rs | 3 +++ src/models/transactions/deposit_preauth.rs | 3 +++ src/models/transactions/escrow_cancel.rs | 3 +++ src/models/transactions/escrow_create.rs | 3 +++ src/models/transactions/escrow_finish.rs | 3 +++ src/models/transactions/nftoken_accept_offer.rs | 3 +++ src/models/transactions/nftoken_burn.rs | 3 +++ src/models/transactions/nftoken_cancel_offer.rs | 3 +++ src/models/transactions/nftoken_create_offer.rs | 3 +++ src/models/transactions/nftoken_mint.rs | 3 +++ src/models/transactions/offer_cancel.rs | 3 +++ src/models/transactions/offer_create.rs | 3 +++ src/models/transactions/payment.rs | 3 +++ src/models/transactions/payment_channel_claim.rs | 3 +++ src/models/transactions/payment_channel_create.rs | 3 +++ src/models/transactions/payment_channel_fund.rs | 3 +++ .../transactions/pseudo_transactions/enable_amendment.rs | 3 +++ src/models/transactions/pseudo_transactions/set_fee.rs | 3 +++ src/models/transactions/pseudo_transactions/unl_modify.rs | 3 +++ src/models/transactions/set_regular_key.rs | 3 +++ src/models/transactions/signer_list_set.rs | 3 +++ src/models/transactions/ticket_create.rs | 3 +++ src/models/transactions/trust_set.rs | 3 +++ 27 files changed, 81 insertions(+) diff --git a/src/models/transactions/account_delete.rs b/src/models/transactions/account_delete.rs index 01939d68..5ebf23dc 100644 --- a/src/models/transactions/account_delete.rs +++ b/src/models/transactions/account_delete.rs @@ -83,10 +83,13 @@ impl<'a> AccountDelete<'a> { flags: None, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, destination, destination_tag, diff --git a/src/models/transactions/account_set.rs b/src/models/transactions/account_set.rs index 702dfe03..ba4d98d1 100644 --- a/src/models/transactions/account_set.rs +++ b/src/models/transactions/account_set.rs @@ -317,10 +317,13 @@ impl<'a> AccountSet<'a> { flags, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, clear_flag, domain, diff --git a/src/models/transactions/check_cancel.rs b/src/models/transactions/check_cancel.rs index 77608ee9..7f1d49cb 100644 --- a/src/models/transactions/check_cancel.rs +++ b/src/models/transactions/check_cancel.rs @@ -77,10 +77,13 @@ impl<'a> CheckCancel<'a> { flags: None, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, check_id, } diff --git a/src/models/transactions/check_cash.rs b/src/models/transactions/check_cash.rs index 7acd9b3c..29d6a656 100644 --- a/src/models/transactions/check_cash.rs +++ b/src/models/transactions/check_cash.rs @@ -110,10 +110,13 @@ impl<'a> CheckCash<'a> { flags: None, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, check_id, amount, diff --git a/src/models/transactions/check_create.rs b/src/models/transactions/check_create.rs index 61d9b631..51491bc6 100644 --- a/src/models/transactions/check_create.rs +++ b/src/models/transactions/check_create.rs @@ -92,10 +92,13 @@ impl<'a> CheckCreate<'a> { flags: None, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, destination, send_max, diff --git a/src/models/transactions/deposit_preauth.rs b/src/models/transactions/deposit_preauth.rs index 75d877b8..16ba8797 100644 --- a/src/models/transactions/deposit_preauth.rs +++ b/src/models/transactions/deposit_preauth.rs @@ -100,10 +100,13 @@ impl<'a> DepositPreauth<'a> { flags: None, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, authorize, unauthorize, diff --git a/src/models/transactions/escrow_cancel.rs b/src/models/transactions/escrow_cancel.rs index 789860cc..c6f9f4f3 100644 --- a/src/models/transactions/escrow_cancel.rs +++ b/src/models/transactions/escrow_cancel.rs @@ -77,10 +77,13 @@ impl<'a> EscrowCancel<'a> { flags: None, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, owner, offer_sequence, diff --git a/src/models/transactions/escrow_create.rs b/src/models/transactions/escrow_create.rs index 01b248a9..fdf49982 100644 --- a/src/models/transactions/escrow_create.rs +++ b/src/models/transactions/escrow_create.rs @@ -126,10 +126,13 @@ impl<'a> EscrowCreate<'a> { flags: None, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, amount, destination, diff --git a/src/models/transactions/escrow_finish.rs b/src/models/transactions/escrow_finish.rs index 7ddb5b38..bbc1396e 100644 --- a/src/models/transactions/escrow_finish.rs +++ b/src/models/transactions/escrow_finish.rs @@ -111,10 +111,13 @@ impl<'a> EscrowFinish<'a> { flags: None, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, owner, offer_sequence, diff --git a/src/models/transactions/nftoken_accept_offer.rs b/src/models/transactions/nftoken_accept_offer.rs index c8e46f7e..6c6e7345 100644 --- a/src/models/transactions/nftoken_accept_offer.rs +++ b/src/models/transactions/nftoken_accept_offer.rs @@ -146,10 +146,13 @@ impl<'a> NFTokenAcceptOffer<'a> { flags: None, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, nftoken_sell_offer, nftoken_buy_offer, diff --git a/src/models/transactions/nftoken_burn.rs b/src/models/transactions/nftoken_burn.rs index 57bd85f7..054385f6 100644 --- a/src/models/transactions/nftoken_burn.rs +++ b/src/models/transactions/nftoken_burn.rs @@ -85,10 +85,13 @@ impl<'a> NFTokenBurn<'a> { flags: None, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, nftoken_id, owner, diff --git a/src/models/transactions/nftoken_cancel_offer.rs b/src/models/transactions/nftoken_cancel_offer.rs index 159778bc..f92d5b06 100644 --- a/src/models/transactions/nftoken_cancel_offer.rs +++ b/src/models/transactions/nftoken_cancel_offer.rs @@ -106,10 +106,13 @@ impl<'a> NFTokenCancelOffer<'a> { flags: None, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, nftoken_offers, } diff --git a/src/models/transactions/nftoken_create_offer.rs b/src/models/transactions/nftoken_create_offer.rs index 4c2a8b12..1c0da365 100644 --- a/src/models/transactions/nftoken_create_offer.rs +++ b/src/models/transactions/nftoken_create_offer.rs @@ -207,10 +207,13 @@ impl<'a> NFTokenCreateOffer<'a> { flags, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, nftoken_id, amount, diff --git a/src/models/transactions/nftoken_mint.rs b/src/models/transactions/nftoken_mint.rs index caafdd2c..3a7dbabe 100644 --- a/src/models/transactions/nftoken_mint.rs +++ b/src/models/transactions/nftoken_mint.rs @@ -201,10 +201,13 @@ impl<'a> NFTokenMint<'a> { flags, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, nftoken_taxon, issuer, diff --git a/src/models/transactions/offer_cancel.rs b/src/models/transactions/offer_cancel.rs index cc57e262..81edfc2b 100644 --- a/src/models/transactions/offer_cancel.rs +++ b/src/models/transactions/offer_cancel.rs @@ -79,10 +79,13 @@ impl<'a> OfferCancel<'a> { flags: None, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, offer_sequence, } diff --git a/src/models/transactions/offer_create.rs b/src/models/transactions/offer_create.rs index 29c4ab79..6145789a 100644 --- a/src/models/transactions/offer_create.rs +++ b/src/models/transactions/offer_create.rs @@ -125,10 +125,13 @@ impl<'a> OfferCreate<'a> { flags, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, taker_gets, taker_pays, diff --git a/src/models/transactions/payment.rs b/src/models/transactions/payment.rs index f4ed94cf..d13b5569 100644 --- a/src/models/transactions/payment.rs +++ b/src/models/transactions/payment.rs @@ -224,10 +224,13 @@ impl<'a> Payment<'a> { flags, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, amount, destination, diff --git a/src/models/transactions/payment_channel_claim.rs b/src/models/transactions/payment_channel_claim.rs index f9e6f7b6..50017231 100644 --- a/src/models/transactions/payment_channel_claim.rs +++ b/src/models/transactions/payment_channel_claim.rs @@ -135,10 +135,13 @@ impl<'a> PaymentChannelClaim<'a> { flags, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, channel, balance, diff --git a/src/models/transactions/payment_channel_create.rs b/src/models/transactions/payment_channel_create.rs index 31d29356..aa4ae702 100644 --- a/src/models/transactions/payment_channel_create.rs +++ b/src/models/transactions/payment_channel_create.rs @@ -101,10 +101,13 @@ impl<'a> PaymentChannelCreate<'a> { flags: None, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, amount, destination, diff --git a/src/models/transactions/payment_channel_fund.rs b/src/models/transactions/payment_channel_fund.rs index 2b67e881..a85ded45 100644 --- a/src/models/transactions/payment_channel_fund.rs +++ b/src/models/transactions/payment_channel_fund.rs @@ -90,10 +90,13 @@ impl<'a> PaymentChannelFund<'a> { flags: None, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, amount, channel, diff --git a/src/models/transactions/pseudo_transactions/enable_amendment.rs b/src/models/transactions/pseudo_transactions/enable_amendment.rs index b493f9d2..36d8fc65 100644 --- a/src/models/transactions/pseudo_transactions/enable_amendment.rs +++ b/src/models/transactions/pseudo_transactions/enable_amendment.rs @@ -94,10 +94,13 @@ impl<'a> EnableAmendment<'a> { flags, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, amendment, ledger_sequence, diff --git a/src/models/transactions/pseudo_transactions/set_fee.rs b/src/models/transactions/pseudo_transactions/set_fee.rs index 89ed9d3c..7a54d2b7 100644 --- a/src/models/transactions/pseudo_transactions/set_fee.rs +++ b/src/models/transactions/pseudo_transactions/set_fee.rs @@ -81,10 +81,13 @@ impl<'a> SetFee<'a> { flags: None, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, base_fee, reference_fee_units, diff --git a/src/models/transactions/pseudo_transactions/unl_modify.rs b/src/models/transactions/pseudo_transactions/unl_modify.rs index 3a50da19..bd4f0f23 100644 --- a/src/models/transactions/pseudo_transactions/unl_modify.rs +++ b/src/models/transactions/pseudo_transactions/unl_modify.rs @@ -88,10 +88,13 @@ impl<'a> UNLModify<'a> { flags: None, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, ledger_sequence, unlmodify_disabling, diff --git a/src/models/transactions/set_regular_key.rs b/src/models/transactions/set_regular_key.rs index 683b7337..7c7a5027 100644 --- a/src/models/transactions/set_regular_key.rs +++ b/src/models/transactions/set_regular_key.rs @@ -83,10 +83,13 @@ impl<'a> SetRegularKey<'a> { flags: None, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, regular_key, } diff --git a/src/models/transactions/signer_list_set.rs b/src/models/transactions/signer_list_set.rs index 698ea089..7e29a6ba 100644 --- a/src/models/transactions/signer_list_set.rs +++ b/src/models/transactions/signer_list_set.rs @@ -196,10 +196,13 @@ impl<'a> SignerListSet<'a> { flags: None, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, signer_quorum, signer_entries, diff --git a/src/models/transactions/ticket_create.rs b/src/models/transactions/ticket_create.rs index 448d67cd..3c8e2875 100644 --- a/src/models/transactions/ticket_create.rs +++ b/src/models/transactions/ticket_create.rs @@ -77,10 +77,13 @@ impl<'a> TicketCreate<'a> { flags: None, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, ticket_count, } diff --git a/src/models/transactions/trust_set.rs b/src/models/transactions/trust_set.rs index b179ef5f..056841b8 100644 --- a/src/models/transactions/trust_set.rs +++ b/src/models/transactions/trust_set.rs @@ -115,10 +115,13 @@ impl<'a> TrustSet<'a> { flags, last_ledger_sequence, memos, + network_id: None, sequence, signers, + signing_pub_key: None, source_tag, ticket_sequence, + txn_signature: None, }, limit_amount, quality_in, From dd4f5f80110268af49b2989ae6398b95a2f92eae Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sun, 28 Apr 2024 15:05:05 +0000 Subject: [PATCH 08/19] utilize Arc and Mutex for inner tungstenite ws client --- src/asynch/clients/mod.rs | 6 +- src/asynch/clients/tungstenite.rs | 101 ++++++++++++++++++------------ 2 files changed, 64 insertions(+), 43 deletions(-) diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index 548def7c..a93aea52 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -32,11 +32,9 @@ pub trait WebsocketClient { } pub trait Client<'a> { - async fn request(&mut self, req: impl Serialize) -> Result> + async fn request(&self, req: impl Serialize) -> Result> where T: for<'de> Deserialize<'de> + Clone; - fn get_common_fields(&self) -> Option>; - - async fn set_common_fields(&mut self) -> Result<()>; + async fn get_common_fields(&self) -> Result>; } diff --git a/src/asynch/clients/tungstenite.rs b/src/asynch/clients/tungstenite.rs index 8767c6df..1e1a6cc5 100644 --- a/src/asynch/clients/tungstenite.rs +++ b/src/asynch/clients/tungstenite.rs @@ -6,11 +6,12 @@ use crate::models::requests; use crate::models::results::{self, XRPLResponse}; use crate::Err; -use crate::models::results::XRPLResponseFromStream; +use alloc::sync::Arc; use anyhow::Result; use core::marker::PhantomData; use core::{pin::Pin, task::Poll}; -use futures::{Sink, Stream}; +use futures::lock::Mutex; +use futures::{FutureExt, Sink, Stream, StreamExt}; use futures_util::SinkExt; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -23,48 +24,65 @@ use url::Url; pub use tokio_tungstenite::tungstenite::Message as TungsteniteMessage; -pub struct AsyncWebsocketClient<'a, Status = WebsocketClosed> { - common_fields: Option>, - inner: TungsteniteWebsocketStream>, +pub(crate) enum ContextWaker { + Read, + Write, +} + +pub type AsyncWebsocketConnection = + Arc>>>; + +pub struct AsyncWebsocketClient { + inner: AsyncWebsocketConnection, status: PhantomData, } -impl<'a, Status> WebsocketClient for AsyncWebsocketClient<'a, Status> {} +impl<'a, Status> WebsocketClient for AsyncWebsocketClient {} -impl<'a, I> Sink for AsyncWebsocketClient<'a, WebsocketOpen> +impl<'a, I> Sink for AsyncWebsocketClient where I: serde::Serialize, + Self: Unpin, { type Error = anyhow::Error; fn poll_ready( - mut self: core::pin::Pin<&mut Self>, + self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, ) -> core::task::Poll> { - match Pin::new(&mut self.inner).poll_ready(cx) { + let mut guard = futures::ready!(self.inner.lock().poll_unpin(cx)); + match Pin::new(&mut *guard).poll_ready(cx) { Poll::Ready(Ok(())) => Poll::Ready(Ok(())), Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), Poll::Pending => Poll::Pending, } } - fn start_send(mut self: core::pin::Pin<&mut Self>, item: I) -> Result<()> { + fn start_send(self: core::pin::Pin<&mut Self>, item: I) -> Result<()> { match serde_json::to_string(&item) { Ok(json) => { - match Pin::new(&mut self.inner).start_send(TungsteniteMessage::Text(json)) { - Ok(()) => Ok(()), - Err(error) => Err!(error), - } + // cannot use ready! macro here because of the return type + let _ = self.inner.lock().then(|mut guard| async move { + match Pin::new(&mut *guard) + .send(TungsteniteMessage::Text(json)) + .await + { + Ok(_) => Ok(()), + Err(error) => Err!(error), + } + }); + Ok(()) } Err(error) => Err!(error), } } fn poll_flush( - mut self: core::pin::Pin<&mut Self>, + self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, ) -> core::task::Poll> { - match Pin::new(&mut self.inner).poll_flush(cx) { + let mut guard = futures::ready!(self.inner.lock().poll_unpin(cx)); + match Pin::new(&mut *guard).poll_flush(cx) { Poll::Ready(Ok(())) => Poll::Ready(Ok(())), Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), Poll::Pending => Poll::Pending, @@ -72,10 +90,11 @@ where } fn poll_close( - mut self: core::pin::Pin<&mut Self>, + self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, ) -> core::task::Poll> { - match Pin::new(&mut self.inner).poll_close(cx) { + let mut guard = futures::ready!(self.inner.lock().poll_unpin(cx)); + match Pin::new(&mut *guard).poll_close(cx) { Poll::Ready(Ok(())) => Poll::Ready(Ok(())), Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), Poll::Pending => Poll::Pending, @@ -83,14 +102,15 @@ where } } -impl<'a> Stream for AsyncWebsocketClient<'a, WebsocketOpen> { +impl<'a> Stream for AsyncWebsocketClient { type Item = Result; fn poll_next( - mut self: Pin<&mut Self>, + self: Pin<&mut Self>, cx: &mut core::task::Context<'_>, ) -> Poll> { - match Pin::new(&mut self.inner).poll_next(cx) { + let mut guard = futures::ready!(self.inner.lock().poll_unpin(cx)); + match Pin::new(&mut *guard).poll_next(cx) { Poll::Ready(Some(item)) => match item { Ok(message) => match message { TungsteniteMessage::Text(response) => match serde_json::from_str(&response) { @@ -109,12 +129,11 @@ impl<'a> Stream for AsyncWebsocketClient<'a, WebsocketOpen> { } } -impl<'a> AsyncWebsocketClient<'a, WebsocketClosed> { - pub async fn open(uri: Url) -> Result> { +impl<'a> AsyncWebsocketClient { + pub async fn open(uri: Url) -> Result> { match tungstenite_connect_async(uri).await { Ok((websocket_stream, _)) => Ok(AsyncWebsocketClient { - common_fields: None, - inner: websocket_stream, + inner: Arc::new(Mutex::new(websocket_stream)), status: PhantomData::, }), Err(error) => { @@ -126,33 +145,37 @@ impl<'a> AsyncWebsocketClient<'a, WebsocketClosed> { } } -impl<'a> Client<'a> for AsyncWebsocketClient<'a, WebsocketOpen> { - async fn request(&mut self, req: impl Serialize) -> Result> +impl<'a> Client<'a> for AsyncWebsocketClient { + async fn request(&self, req: impl Serialize) -> Result> where T: for<'de> Deserialize<'de>, { - self.send(req).await?; - while let Ok(Some(response)) = self.try_next_xrpl_response().await { - return Ok(response); + let mut this = self.inner.lock().await; + let request = serde_json::to_string(&req).unwrap(); + this.send(TungsteniteMessage::Text(request)).await.unwrap(); + while let Some(Ok(message)) = this.next().await { + match message { + TungsteniteMessage::Text(response) => { + let response = serde_json::from_str(&response).unwrap(); + return Ok(response); + } + _ => return Err!(XRPLWebsocketException::::UnexpectedMessageType), + } } Err!(XRPLWebsocketException::::NoResponse) } - fn get_common_fields(&self) -> Option> { - self.common_fields.clone() - } - - async fn set_common_fields(&mut self) -> Result<()> { + async fn get_common_fields(&self) -> Result> { let server_state = self .request::(requests::ServerState::new(None)) .await?; - let state = server_state.result.state.clone(); - self.common_fields = Some(CommonFields { + let state = server_state.result.state; + let common_fields = CommonFields { network_id: state.network_id, build_version: Some(state.build_version), - }); + }; - Ok(()) + Ok(common_fields) } } From 5a3120a061964715b61c08a4a12dd1cd38402e60 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sun, 28 Apr 2024 15:06:01 +0000 Subject: [PATCH 09/19] fix autofill --- src/asynch/account/mod.rs | 4 +- src/asynch/ledger/mod.rs | 6 +- src/asynch/transaction/mod.rs | 155 ++++++++++----------------------- src/models/transactions/mod.rs | 56 +++++------- 4 files changed, 76 insertions(+), 145 deletions(-) diff --git a/src/asynch/account/mod.rs b/src/asynch/account/mod.rs index 0f7b6f5a..b3528b64 100644 --- a/src/asynch/account/mod.rs +++ b/src/asynch/account/mod.rs @@ -10,7 +10,7 @@ use super::clients::Client; pub async fn get_next_valid_seq_number<'a>( address: Cow<'a, str>, - client: &'a mut impl Client<'a>, + client: &'a impl Client<'a>, ledger_index: Option>, ) -> Result { let account_info = @@ -20,7 +20,7 @@ pub async fn get_next_valid_seq_number<'a>( pub async fn get_account_root<'a>( address: Cow<'a, str>, - client: &'a mut impl Client<'a>, + client: &'a impl Client<'a>, ledger_index: Cow<'a, str>, ) -> Result> { let mut classic_address = address; diff --git a/src/asynch/ledger/mod.rs b/src/asynch/ledger/mod.rs index 5138b5fe..355c422f 100644 --- a/src/asynch/ledger/mod.rs +++ b/src/asynch/ledger/mod.rs @@ -14,9 +14,7 @@ use crate::models::{ use super::clients::Client; -pub async fn get_latest_validated_ledger_sequence<'a>( - client: &'a mut impl Client<'a>, -) -> Result { +pub async fn get_latest_validated_ledger_sequence<'a>(client: &'a impl Client<'a>) -> Result { let ledger_response = client .request::(Ledger::new( None, @@ -42,7 +40,7 @@ pub enum FeeType { } pub async fn get_fee<'a>( - client: &'a mut impl Client<'a>, + client: &'a impl Client<'a>, max_fee: Option, fee_type: Option, ) -> Result> { diff --git a/src/asynch/transaction/mod.rs b/src/asynch/transaction/mod.rs index ec751f73..c324ca13 100644 --- a/src/asynch/transaction/mod.rs +++ b/src/asynch/transaction/mod.rs @@ -1,5 +1,5 @@ -use core::any::Any; use core::convert::TryInto; +use core::fmt::Debug; use alloc::borrow::Cow; use alloc::string::String; @@ -14,17 +14,14 @@ use crate::models::amount::XRPAmount; use crate::models::exceptions::XRPLModelException; use crate::models::requests::ServerState; use crate::models::results::server_state::ServerState as ServerStateResult; -use crate::models::transactions::AutofilledTransaction; -use crate::models::transactions::EscrowFinish; use crate::models::transactions::Transaction; use crate::models::transactions::TransactionType; use crate::models::Model; use crate::Err; -use self::exceptions::XRPLTransactionException; - use super::account::get_next_valid_seq_number; use super::clients::Client; +use super::clients::CommonFields; use super::ledger::get_fee; use super::ledger::get_latest_validated_ledger_sequence; @@ -36,111 +33,79 @@ const REQUIRED_NETWORKID_VERSION: &'static str = "1.11.0"; const LEDGER_OFFSET: u8 = 20; pub async fn autofill<'a, F, T>( - transaction: T, - client: &'a mut impl Client<'a>, + transaction: &'a mut T, + client: &'a impl Client<'a>, signers_count: Option, -) -> Result> +) -> Result<()> where - T: Transaction<'a, F> + Model + 'static, - F: IntoEnumIterator + Serialize + core::fmt::Debug + PartialEq + 'a, + T: Transaction<'a, F> + Model + Clone + 'static, + F: IntoEnumIterator + Serialize + Debug + PartialEq + 'a, { - let mut autofilled_txn = AutofilledTransaction::new(transaction, None); - set_network_id_and_build_version(client).await.unwrap(); - if autofilled_txn.netork_id.is_none() && txn_needs_network_id(client) { - autofilled_txn.netork_id = client.get_common_fields().unwrap().network_id; + let txn = transaction.clone(); + let txn_common_fields = transaction.get_mut_common_fields(); + let common_fields = client.get_common_fields().await?; + if txn_common_fields.network_id.is_none() && txn_needs_network_id(common_fields.clone()) { + txn_common_fields.network_id = common_fields.network_id; } - let txn_common_fields = autofilled_txn.transaction.get_mut_common_fields(); if txn_common_fields.sequence.is_none() { txn_common_fields.sequence = Some(get_next_valid_seq_number(txn_common_fields.account.clone(), client, None).await?); } if txn_common_fields.fee.is_none() { - txn_common_fields.fee = Some( - calculate_fee_per_transaction_type( - &autofilled_txn.transaction, - Some(client), - signers_count, - ) - .await?, - ); + txn_common_fields.fee = + Some(calculate_fee_per_transaction_type(txn, Some(client), signers_count).await?); } if txn_common_fields.last_ledger_sequence.is_none() { let ledger_sequence = get_latest_validated_ledger_sequence(client).await?; txn_common_fields.last_ledger_sequence = Some(ledger_sequence + LEDGER_OFFSET as u32); } - Ok(autofilled_txn) -} - -async fn check_fee<'a, F, T>( - transaction: &'a T, - client: Option<&'a mut impl Client<'a>>, - signers_count: Option, -) -> Result<()> -where - T: Transaction<'a, F> + Model + 'static, - F: IntoEnumIterator + Serialize + core::fmt::Debug + PartialEq + 'a, -{ - let fee_to_high = XRPAmount::from("1000"); - let calculated_fee = calculate_fee_per_transaction_type(transaction, client, signers_count) - .await - .unwrap(); - let expected_fee = fee_to_high.max(calculated_fee); - let common_fields = transaction.get_common_fields(); - if let Some(fee) = &common_fields.fee { - if fee < &expected_fee { - return Err!(XRPLTransactionException::FeeUnusuallyHigh(fee.clone())); - } - } - Ok(()) } async fn calculate_fee_per_transaction_type<'a, T, F>( - transaction: &'a T, - client: Option<&'a mut impl Client<'a>>, + transaction: T, + client: Option<&'a impl Client<'a>>, signers_count: Option, ) -> Result> where T: Transaction<'a, F> + 'static, - F: IntoEnumIterator + Serialize + core::fmt::Debug + PartialEq, + F: IntoEnumIterator + Serialize + Debug + PartialEq, { + let mut net_fee = XRPAmount::from("10"); + let mut base_fee; + if let Some(client) = client { - let net_fee = get_fee(client, None, None).await?; - let mut base_fee = match transaction.get_transaction_type() { - TransactionType::EscrowFinish => { - calculate_base_fee_for_escrow_finish(transaction, net_fee.clone()) - } + net_fee = get_fee(client, None, None).await?; + base_fee = match transaction.get_transaction_type() { + TransactionType::EscrowFinish => calculate_base_fee_for_escrow_finish( + net_fee.clone(), + Some(transaction.get_field_value("fulfillment").unwrap().into()), + ), // TODO: same for TransactionType::AMMCreate TransactionType::AccountDelete => get_owner_reserve_from_response(client).await?, _ => net_fee.clone(), }; - if let Some(signers_count) = signers_count { - base_fee += net_fee.clone() * (1 + signers_count); - } - - Ok(base_fee.ceil()) } else { - let net_fee = XRPAmount::from("10"); - let mut base_fee = match transaction.get_transaction_type() { - TransactionType::EscrowFinish => { - calculate_base_fee_for_escrow_finish(transaction, net_fee.clone()) - } + base_fee = match transaction.get_transaction_type() { + TransactionType::EscrowFinish => calculate_base_fee_for_escrow_finish( + net_fee.clone(), + Some(transaction.get_field_value("fulfillment").unwrap().into()), + ), // TODO: same for TransactionType::AMMCreate TransactionType::AccountDelete => XRPAmount::from(OWNER_RESERVE), _ => net_fee.clone(), }; - if let Some(signers_count) = signers_count { - base_fee += net_fee.clone() * (1 + signers_count); - } + } - Ok(base_fee.ceil()) + if let Some(signers_count) = signers_count { + base_fee += net_fee * (1 + signers_count); } + + Ok(base_fee.ceil()) } -async fn get_owner_reserve_from_response<'a>( - client: &'a mut impl Client<'a>, -) -> Result> { +async fn get_owner_reserve_from_response<'a>(client: &'a impl Client<'a>) -> Result> { let owner_reserve_response = client .request::>(ServerState::new(None)) .await?; @@ -150,25 +115,13 @@ async fn get_owner_reserve_from_response<'a>( } } -fn calculate_base_fee_for_escrow_finish<'a, T, F>( - transaction: &'a T, +fn calculate_base_fee_for_escrow_finish<'a>( net_fee: XRPAmount<'a>, -) -> XRPAmount<'a> -where - T: Transaction<'a, F> + 'static, // must outlive 'static for downcasting - F: IntoEnumIterator + Serialize + core::fmt::Debug + PartialEq, -{ - if transaction.get_transaction_type() == TransactionType::EscrowFinish { - // cast type to transaction `EscrowFinish` - let escrow_finish: Option<&EscrowFinish<'a>> = - (transaction as &dyn Any).downcast_ref::(); - if let Some(escrow_finish) = escrow_finish { - if let Some(fulfillment) = escrow_finish.fulfillment.clone() { - return calculate_based_on_fulfillment(fulfillment, net_fee); - } - } + fulfillment: Option>, +) -> XRPAmount<'a> { + if let Some(fulfillment) = fulfillment { + return calculate_based_on_fulfillment(fulfillment, net_fee); } - net_fee } @@ -184,24 +137,12 @@ fn calculate_based_on_fulfillment<'a>( XRPAmount::from(base_fee_decimal.ceil()) } -async fn set_network_id_and_build_version<'a>(client: &'a mut impl Client<'a>) -> Result<()> { - if client.get_common_fields().is_none() { - client.set_common_fields().await?; - } - - Ok(()) -} - -fn txn_needs_network_id<'a>(client: &'a mut impl Client<'a>) -> bool { - if let Some(common_fields) = client.get_common_fields() { - common_fields.network_id.unwrap() > RESTRICTED_NETWORKS as u32 - && is_not_later_rippled_version( - REQUIRED_NETWORKID_VERSION.into(), - common_fields.build_version.unwrap().into(), - ) - } else { - false - } +fn txn_needs_network_id<'a>(common_fields: CommonFields<'a>) -> bool { + common_fields.network_id.unwrap() > RESTRICTED_NETWORKS as u32 + && is_not_later_rippled_version( + REQUIRED_NETWORKID_VERSION.into(), + common_fields.build_version.unwrap().into(), + ) } fn is_not_later_rippled_version(source: String, target: String) -> bool { diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 17262670..3970165d 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -55,6 +55,7 @@ pub use signer_list_set::*; pub use ticket_create::*; pub use trust_set::*; +use crate::alloc::string::ToString; use crate::models::amount::XRPAmount; use crate::{_serde::txn_flags, serde_with_tag}; use alloc::borrow::Cow; @@ -104,38 +105,6 @@ pub enum TransactionType { UNLModify, } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, new)] -#[serde(rename_all = "PascalCase")] -pub struct AutofilledTransaction { - #[serde(flatten)] - pub transaction: T, - /// The network ID of the chain this transaction is intended for. - /// MUST BE OMITTED for Mainnet and some test networks. - /// REQUIRED on chains whose network ID is 1025 or higher. - pub netork_id: Option, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, new)] -#[serde(rename_all = "PascalCase")] -pub struct PreparedTransaction<'a, T> { - #[serde(flatten)] - pub transaction: T, - /// Hex representation of the public key that corresponds to the - /// private key used to sign this transaction. If an empty string, - /// indicates a multi-signature is present in the Signers field instead. - pub signing_pub_key: Cow<'a, str>, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, new)] -#[serde(rename_all = "PascalCase")] -pub struct SignedTransaction<'a, T> { - #[serde(flatten)] - pub prepared_transaction: PreparedTransaction<'a, T>, - /// The signature that verifies this transaction as originating - /// from the account it says it is from. - pub txn_signature: Cow<'a, str>, -} - /// The base fields for all transaction models. /// /// See Transaction Common Fields: @@ -174,6 +143,10 @@ where pub last_ledger_sequence: Option, /// Additional arbitrary information used to identify this transaction. pub memos: Option>, + /// The network ID of the chain this transaction is intended for. + /// MUST BE OMITTED for Mainnet and some test networks. + /// REQUIRED on chains whose network ID is 1025 or higher. + pub network_id: Option, /// The sequence number of the account sending the transaction. /// A transaction is only valid if the Sequence number is exactly /// 1 greater than the previous transaction from the same account. @@ -184,6 +157,10 @@ where /// made. Conventionally, a refund should specify the initial /// payment's SourceTag as the refund payment's DestinationTag. pub signers: Option>>, + /// Hex representation of the public key that corresponds to the + /// private key used to sign this transaction. If an empty string, + /// indicates a multi-signature is present in the Signers field instead. + pub signing_pub_key: Option>, /// Arbitrary integer used to identify the reason for this /// payment, or a sender on whose behalf this transaction /// is made. Conventionally, a refund should specify the initial @@ -193,6 +170,9 @@ where /// of a Sequence number. If this is provided, Sequence must /// be 0. Cannot be used with AccountTxnID. pub ticket_sequence: Option, + /// The signature that verifies this transaction as originating + /// from the account it says it is from. + pub txn_signature: Option>, } impl<'a, T> CommonFields<'a, T> @@ -207,10 +187,13 @@ where flags: Option>, last_ledger_sequence: Option, memos: Option>, + network_id: Option, sequence: Option, signers: Option>>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, + txn_signature: Option>, ) -> Self { CommonFields { account, @@ -220,10 +203,13 @@ where flags, last_ledger_sequence, memos, + network_id, sequence, signers, + signing_pub_key, source_tag, ticket_sequence, + txn_signature, } } } @@ -288,6 +274,7 @@ pub struct Signer<'a> { /// Standard functions for transactions. pub trait Transaction<'a, T> where + Self: Serialize, T: IntoEnumIterator + Serialize + Debug + PartialEq, { fn has_flag(&self, flag: &T) -> bool { @@ -300,6 +287,11 @@ where fn get_common_fields(&'a self) -> &'a CommonFields<'a, T>; fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, T>; + + fn get_field_value(&self, field: &str) -> Option { + let transaction = serde_json::to_value(self).unwrap(); + transaction.get(field).map(|v| v.to_string()) + } } #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Display, AsRefStr)] From 565d1a1b44fb1b330d9bc8e5a1d962e15d5b2c6b Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sun, 28 Apr 2024 15:12:36 +0000 Subject: [PATCH 10/19] github workflow: feature std is needed to enable tungstenite feature --- .github/workflows/unit_test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index a0a38090..66c808f3 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -25,7 +25,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: build - args: --release --no-default-features --features core,models,tungstenite + args: --release --no-default-features --features core,models,tungstenite,std - uses: actions-rs/cargo@v1 with: command: test @@ -33,7 +33,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test - args: --no-default-features --features core,models,tungstenite + args: --no-default-features --features core,models,tungstenite,std - uses: actions-rs/cargo@v1 with: command: test From 6c60bb5bc568c63efbd9a75eb2ec67f96b30ca65 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 15 Jun 2024 19:33:27 +0000 Subject: [PATCH 11/19] Revert "update from dev" This reverts commit 485a8137ee5b1a244b3d43783b2f3a071670d9f1, reversing changes made to 565d1a1b44fb1b330d9bc8e5a1d962e15d5b2c6b. --- Cargo.toml | 39 +-- src/asynch/clients/async_client.rs | 19 -- src/asynch/clients/client.rs | 40 --- src/asynch/clients/embedded_ws.rs | 165 ++++++++++ .../clients/{websocket => }/exceptions.rs | 27 +- src/asynch/clients/mod.rs | 44 ++- src/asynch/clients/tungstenite.rs | 181 +++++++++++ src/asynch/clients/websocket/codec.rs | 30 -- .../clients/websocket/embedded_websocket.rs | 297 ------------------ src/asynch/clients/websocket/mod.rs | 137 -------- src/asynch/clients/websocket/tungstenite.rs | 265 ---------------- .../clients/websocket/websocket_base.rs | 112 ------- src/models/requests/account_channels.rs | 12 +- src/models/requests/account_currencies.rs | 10 +- src/models/requests/account_info.rs | 10 +- src/models/requests/account_lines.rs | 10 +- src/models/requests/account_nfts.rs | 10 +- src/models/requests/account_objects.rs | 10 +- src/models/requests/account_offers.rs | 10 +- src/models/requests/account_tx.rs | 10 +- src/models/requests/book_offers.rs | 10 +- src/models/requests/channel_authorize.rs | 10 +- src/models/requests/channel_verify.rs | 10 +- src/models/requests/deposit_authorize.rs | 10 +- src/models/requests/fee.rs | 10 +- src/models/requests/gateway_balances.rs | 10 +- src/models/requests/ledger.rs | 10 +- src/models/requests/ledger_closed.rs | 10 +- src/models/requests/ledger_current.rs | 10 +- src/models/requests/ledger_data.rs | 10 +- src/models/requests/ledger_entry.rs | 10 +- src/models/requests/manifest.rs | 10 +- src/models/requests/mod.rs | 11 +- src/models/requests/nft_buy_offers.rs | 10 +- src/models/requests/nft_sell_offers.rs | 10 +- src/models/requests/no_ripple_check.rs | 10 +- src/models/requests/path_find.rs | 10 +- src/models/requests/ping.rs | 10 +- src/models/requests/random.rs | 10 +- src/models/requests/ripple_path_find.rs | 10 +- src/models/requests/server_info.rs | 10 +- src/models/requests/server_state.rs | 10 +- src/models/requests/submit.rs | 10 +- src/models/requests/submit_multisigned.rs | 10 +- src/models/requests/subscribe.rs | 10 +- src/models/requests/transaction_entry.rs | 10 +- src/models/requests/tx.rs | 10 +- src/models/requests/unsubscribe.rs | 10 +- src/models/results/mod.rs | 148 +++++---- src/models/transactions/escrow_finish.rs | 8 +- src/models/transactions/mod.rs | 8 - src/utils/mod.rs | 9 - tests/common/constants.rs | 8 +- tests/common/mod.rs | 96 +++--- tests/integration/clients/mod.rs | 143 +++++---- tests/integration/mod.rs | 2 + tests/integration_tests.rs | 18 +- 57 files changed, 739 insertions(+), 1420 deletions(-) delete mode 100644 src/asynch/clients/async_client.rs delete mode 100644 src/asynch/clients/client.rs create mode 100644 src/asynch/clients/embedded_ws.rs rename src/asynch/clients/{websocket => }/exceptions.rs (72%) create mode 100644 src/asynch/clients/tungstenite.rs delete mode 100644 src/asynch/clients/websocket/codec.rs delete mode 100644 src/asynch/clients/websocket/embedded_websocket.rs delete mode 100644 src/asynch/clients/websocket/mod.rs delete mode 100644 src/asynch/clients/websocket/tungstenite.rs delete mode 100644 src/asynch/clients/websocket/websocket_base.rs diff --git a/Cargo.toml b/Cargo.toml index 6c7b105e..7a1b888d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ chrono = { version = "0.4.19", default-features = false, features = [ "clock", ] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } -rand = { version = "0.8.5", default-features = false, features = ["getrandom"] } +rand = { version = "0.8.4", default-features = false, features = ["getrandom"] } serde = { version = "1.0.130", default-features = false, features = ["derive"] } serde_json = { version = "1.0.68", default-features = false, features = [ "alloc", @@ -54,27 +54,30 @@ serde_json = { version = "1.0.68", default-features = false, features = [ serde_with = "3.2.0" serde_repr = "0.1" zeroize = "1.5.7" -hashbrown = { version = "0.14.5", features = ["serde"] } +hashbrown = { version = "0.14.0", default-features = false, features = [ + "serde", +] } fnv = { version = "1.0.7", default-features = false } derive-new = { version = "0.5.9", default-features = false } thiserror-no-std = "2.0.2" anyhow = { version = "1.0.69", default-features = false } tokio = { version = "1.28.0", default-features = false, optional = true } url = { version = "2.2.2", default-features = false, optional = true } -futures = { version = "0.3.28", default-features = false, features = [ - "alloc", -], optional = true } +futures = { version = "0.3.28", default-features = false, optional = true } rand_core = { version = "0.6.4", default-features = false } tokio-tungstenite = { version = "0.20.0", optional = true } -embassy-sync = { version = "0.6.0", default-features = false } -embedded-io-async = "0.6.1" -futures-sink = { version = "0.3.30", default-features = false } -futures-core = { version = "0.3.30", default-features = false } -futures-util = { version = "0.3.30", optional = true } -tokio-util = { version = "0.7.7", features = ["codec"], optional = true } -bytes = { version = "1.4.0", default-features = false } -embassy-futures = "0.1.1" -embedded-websocket = { version = "0.9.3", optional = true } +futures-util = { version = "0.3.30", default-features = false, optional = true, features = [ + "async-await", + "async-await-macro", +] } + +[dependencies.embedded-websocket] +# git version needed to use `framer_async` +git = "https://github.com/ninjasource/embedded-websocket" +version = "0.9.2" +rev = "8d87d46f46fa0c75e099ca8aad37e8d00c8854f8" +default-features = false +optional = true [dev-dependencies] criterion = "0.5.1" @@ -83,7 +86,8 @@ cargo-husky = { version = "1.5.0", default-features = false, features = [ ] } tokio = { version = "1.28.0", features = ["full"] } tokio-util = { version = "0.7.7", features = ["codec"] } -rand = { version = "0.8.5", default-features = false, features = [ +bytes = { version = "1.4.0", default-features = false } +rand = { version = "0.8.4", default-features = false, features = [ "getrandom", "std", "std_rng", @@ -105,9 +109,9 @@ currencies = ["core"] tungstenite = [ "url", "futures", - "tokio/net", + "tokio/full", "tokio-tungstenite/native-tls", - "futures-util", + "futures-util/std", ] embedded-ws = ["url", "futures", "embedded-websocket"] core = ["utils"] @@ -125,5 +129,4 @@ std = [ "serde/std", "indexmap/std", "secp256k1/std", - "dep:tokio-util", ] diff --git a/src/asynch/clients/async_client.rs b/src/asynch/clients/async_client.rs deleted file mode 100644 index 57a776ab..00000000 --- a/src/asynch/clients/async_client.rs +++ /dev/null @@ -1,19 +0,0 @@ -use super::client::Client; -use crate::models::{requests::Request, results::XRPLResponse}; -use anyhow::Result; -use serde::{Deserialize, Serialize}; - -#[allow(async_fn_in_trait)] -pub trait AsyncClient<'a>: Client<'a> { - async fn request< - Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, - >( - &'a self, - request: Req, - ) -> Result> { - self.request_impl(request).await - } -} - -impl<'a, T: Client<'a>> AsyncClient<'a> for T {} diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs deleted file mode 100644 index 7b7cd241..00000000 --- a/src/asynch/clients/client.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::models::{requests::Request, results::XRPLResponse}; -#[cfg(feature = "std")] -use crate::utils::get_random_id; -use alloc::borrow::Cow; -use anyhow::Result; -use serde::{Deserialize, Serialize}; - -#[allow(async_fn_in_trait)] -pub trait Client<'a> { - async fn request_impl< - Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, - >( - &'a self, - request: Req, - ) -> Result>; - fn set_request_id< - Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, - >( - &'a self, - request: &mut Req, - ) -> Cow<'_, str> { - let common_fields = request.get_common_fields(); - let request_id: Cow<'_, str> = match common_fields.id.clone() { - Some(id) => id, - None => { - #[cfg(feature = "std")] - { - let mut rng = rand::thread_rng(); - Cow::Owned(get_random_id(&mut rng)) - } - #[cfg(not(feature = "std"))] - unimplemented!("get_random_id is not yet implemented for no_std. Please provide an `id` in the request."); - } - }; - request.get_common_fields_mut().id = Some(request_id.clone()); - request_id - } -} diff --git a/src/asynch/clients/embedded_ws.rs b/src/asynch/clients/embedded_ws.rs new file mode 100644 index 00000000..33d46924 --- /dev/null +++ b/src/asynch/clients/embedded_ws.rs @@ -0,0 +1,165 @@ +use super::WebsocketClient; +use super::{ + exceptions::XRPLWebsocketException, + {WebsocketClosed, WebsocketOpen}, +}; +use crate::Err; +use anyhow::Result; +use core::marker::PhantomData; +use core::{fmt::Debug, ops::Deref}; +pub use embedded_websocket::{ + framer_async::{ + Framer as EmbeddedWebsocketFramer, FramerError as EmbeddedWebsocketFramerError, + ReadResult as EmbeddedWebsocketReadMessageType, + }, + Client as EmbeddedWebsocketClient, Error as EmbeddedWebsocketError, + WebSocket as EmbeddedWebsocket, WebSocketCloseStatusCode as EmbeddedWebsocketCloseStatusCode, + WebSocketOptions as EmbeddedWebsocketOptions, + WebSocketSendMessageType as EmbeddedWebsocketSendMessageType, + WebSocketState as EmbeddedWebsocketState, +}; +use futures::{Sink, Stream}; +use rand_core::RngCore; + +pub struct AsyncWebsocketClient { + inner: EmbeddedWebsocketFramer, + status: PhantomData, +} + +impl WebsocketClient for AsyncWebsocketClient {} + +impl AsyncWebsocketClient { + /// Open a websocket connection. + pub async fn open<'a, B, E>( + stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), + buffer: &'a mut [u8], + rng: Rng, + websocket_options: &EmbeddedWebsocketOptions<'_>, + ) -> Result> + where + B: AsRef<[u8]>, + E: Debug, + { + let websocket = EmbeddedWebsocket::::new_client(rng); + let mut framer = EmbeddedWebsocketFramer::new(websocket); + match framer.connect(stream, buffer, websocket_options).await { + Ok(Some(_)) => {} + Ok(None) => {} + Err(error) => return Err!(XRPLWebsocketException::from(error)), + } + + Ok(AsyncWebsocketClient { + inner: framer, + status: PhantomData::, + }) + } +} + +impl AsyncWebsocketClient { + /// Encode a message to be sent over the websocket. + pub fn encode( + &mut self, + message_type: EmbeddedWebsocketSendMessageType, + end_of_message: bool, + from: &[u8], + to: &mut [u8], + ) -> Result + where + E: Debug, + { + match self + .inner + .encode::(message_type, end_of_message, from, to) + { + Ok(bytes_written) => Ok(bytes_written), + Err(error) => Err!(XRPLWebsocketException::from(error)), + } + } + + /// Send a message over the websocket. + pub async fn send<'b, E, R: serde::Serialize>( + &mut self, + stream: &mut (impl Sink<&'b [u8], Error = E> + Unpin), + stream_buf: &'b mut [u8], + end_of_message: bool, + frame_buf: R, + ) -> Result<()> + where + E: Debug, + { + match serde_json::to_vec(&frame_buf) { + Ok(frame_buf) => match self + .inner + .write( + stream, + stream_buf, + EmbeddedWebsocketSendMessageType::Text, + end_of_message, + frame_buf.as_slice(), + ) + .await + { + Ok(()) => Ok(()), + Err(error) => Err!(XRPLWebsocketException::from(error)), + }, + Err(error) => Err!(error), + } + } + + /// Close the websocket connection. + pub async fn close<'b, E>( + &mut self, + stream: &mut (impl Sink<&'b [u8], Error = E> + Unpin), + stream_buf: &'b mut [u8], + close_status: EmbeddedWebsocketCloseStatusCode, + status_description: Option<&str>, + ) -> Result<()> + where + E: Debug, + { + match self + .inner + .close(stream, stream_buf, close_status, status_description) + .await + { + Ok(()) => Ok(()), + Err(error) => Err!(XRPLWebsocketException::from(error)), + } + } + + /// Read a message from the websocket. + pub async fn next<'a, B: Deref, E>( + &'a mut self, + stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), + buffer: &'a mut [u8], + ) -> Option>> + // TODO: Change to Response as soon as implemented + where + E: Debug, + { + match self.inner.read(stream, buffer).await { + Some(Ok(read_result)) => Some(Ok(read_result)), + Some(Err(error)) => Some(Err!(XRPLWebsocketException::from(error))), + None => None, + } + } + + /// Read a message from the websocket. + /// + /// This is similar to the `next` method, but returns a `Result>` rather than an `Option>`, making for easy use with the ? operator. + pub async fn try_next<'a, B: Deref, E>( + &'a mut self, + stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), + buffer: &'a mut [u8], + ) -> Result>> + // TODO: Change to Response as soon as implemented + where + E: Debug, + { + match self.inner.read(stream, buffer).await { + Some(Ok(read_result)) => Ok(Some(read_result)), + Some(Err(error)) => Err!(XRPLWebsocketException::from(error)), + None => Ok(None), + } + } +} diff --git a/src/asynch/clients/websocket/exceptions.rs b/src/asynch/clients/exceptions.rs similarity index 72% rename from src/asynch/clients/websocket/exceptions.rs rename to src/asynch/clients/exceptions.rs index fd4c7ce7..b0e249bd 100644 --- a/src/asynch/clients/websocket/exceptions.rs +++ b/src/asynch/clients/exceptions.rs @@ -1,8 +1,6 @@ use core::fmt::Debug; use core::str::Utf8Error; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -use embedded_io_async::{Error as EmbeddedIoError, ErrorKind}; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] use embedded_websocket::framer_async::FramerError; use thiserror_no_std::Error; @@ -32,17 +30,14 @@ pub enum XRPLWebsocketException { Disconnected, #[error("Read buffer is too small (size: {0:?})")] RxBufferTooSmall(usize), + #[error("No response")] + NoResponse, + #[error("No result")] + NoResult, + #[error("No info")] + NoInfo, #[error("Unexpected message type")] UnexpectedMessageType, - #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] - #[error("Embedded I/O error: {0:?}")] - EmbeddedIoError(ErrorKind), - #[error("Missing request channel sender.")] - MissingRequestSender, - #[error("Missing request channel receiver.")] - MissingRequestReceiver, - #[error("Invalid message.")] - InvalidMessage, } #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] @@ -61,15 +56,5 @@ impl From> for XRPLWebsocketException { } } -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -impl EmbeddedIoError for XRPLWebsocketException { - fn kind(&self) -> ErrorKind { - match self { - XRPLWebsocketException::EmbeddedIoError(e) => e.kind(), - _ => ErrorKind::Other, - } - } -} - #[cfg(feature = "std")] impl alloc::error::Error for XRPLWebsocketException {} diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index 591a459f..a93aea52 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -1,6 +1,40 @@ -mod async_client; -mod client; -mod websocket; +use alloc::borrow::Cow; +use anyhow::Result; -pub use async_client::*; -pub use websocket::*; +pub mod exceptions; + +pub struct WebsocketOpen; +pub struct WebsocketClosed; + +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +mod embedded_websocket; +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +mod tungstenite; + +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +pub use embedded_websocket::*; +use serde::{Deserialize, Serialize}; +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +pub use tungstenite::*; + +use crate::models::results::XRPLResponse; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CommonFields<'a> { + pub build_version: Option>, + pub network_id: Option, +} + +pub trait WebsocketClient { + fn is_open(&self) -> bool { + core::any::type_name::() == core::any::type_name::() + } +} + +pub trait Client<'a> { + async fn request(&self, req: impl Serialize) -> Result> + where + T: for<'de> Deserialize<'de> + Clone; + + async fn get_common_fields(&self) -> Result>; +} diff --git a/src/asynch/clients/tungstenite.rs b/src/asynch/clients/tungstenite.rs new file mode 100644 index 00000000..1e1a6cc5 --- /dev/null +++ b/src/asynch/clients/tungstenite.rs @@ -0,0 +1,181 @@ +use super::{ + exceptions::XRPLWebsocketException, Client, CommonFields, WebsocketClient, WebsocketClosed, + WebsocketOpen, +}; +use crate::models::requests; +use crate::models::results::{self, XRPLResponse}; +use crate::Err; + +use alloc::sync::Arc; +use anyhow::Result; +use core::marker::PhantomData; +use core::{pin::Pin, task::Poll}; +use futures::lock::Mutex; +use futures::{FutureExt, Sink, Stream, StreamExt}; +use futures_util::SinkExt; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use tokio::net::TcpStream; +use tokio_tungstenite::{ + connect_async as tungstenite_connect_async, MaybeTlsStream as TungsteniteMaybeTlsStream, + WebSocketStream as TungsteniteWebsocketStream, +}; +use url::Url; + +pub use tokio_tungstenite::tungstenite::Message as TungsteniteMessage; + +pub(crate) enum ContextWaker { + Read, + Write, +} + +pub type AsyncWebsocketConnection = + Arc>>>; + +pub struct AsyncWebsocketClient { + inner: AsyncWebsocketConnection, + status: PhantomData, +} + +impl<'a, Status> WebsocketClient for AsyncWebsocketClient {} + +impl<'a, I> Sink for AsyncWebsocketClient +where + I: serde::Serialize, + Self: Unpin, +{ + type Error = anyhow::Error; + + fn poll_ready( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll> { + let mut guard = futures::ready!(self.inner.lock().poll_unpin(cx)); + match Pin::new(&mut *guard).poll_ready(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(())), + Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), + Poll::Pending => Poll::Pending, + } + } + + fn start_send(self: core::pin::Pin<&mut Self>, item: I) -> Result<()> { + match serde_json::to_string(&item) { + Ok(json) => { + // cannot use ready! macro here because of the return type + let _ = self.inner.lock().then(|mut guard| async move { + match Pin::new(&mut *guard) + .send(TungsteniteMessage::Text(json)) + .await + { + Ok(_) => Ok(()), + Err(error) => Err!(error), + } + }); + Ok(()) + } + Err(error) => Err!(error), + } + } + + fn poll_flush( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll> { + let mut guard = futures::ready!(self.inner.lock().poll_unpin(cx)); + match Pin::new(&mut *guard).poll_flush(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(())), + Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), + Poll::Pending => Poll::Pending, + } + } + + fn poll_close( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll> { + let mut guard = futures::ready!(self.inner.lock().poll_unpin(cx)); + match Pin::new(&mut *guard).poll_close(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(())), + Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), + Poll::Pending => Poll::Pending, + } + } +} + +impl<'a> Stream for AsyncWebsocketClient { + type Item = Result; + + fn poll_next( + self: Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> Poll> { + let mut guard = futures::ready!(self.inner.lock().poll_unpin(cx)); + match Pin::new(&mut *guard).poll_next(cx) { + Poll::Ready(Some(item)) => match item { + Ok(message) => match message { + TungsteniteMessage::Text(response) => match serde_json::from_str(&response) { + Ok(response) => Poll::Ready(Some(Ok(response))), + Err(error) => Poll::Ready(Some(Err!(error))), + }, + _ => Poll::Ready(Some(Err!( + XRPLWebsocketException::::UnexpectedMessageType + ))), + }, + Err(error) => Poll::Ready(Some(Err!(error))), + }, + Poll::Ready(None) => Poll::Ready(None), + Poll::Pending => Poll::Pending, + } + } +} + +impl<'a> AsyncWebsocketClient { + pub async fn open(uri: Url) -> Result> { + match tungstenite_connect_async(uri).await { + Ok((websocket_stream, _)) => Ok(AsyncWebsocketClient { + inner: Arc::new(Mutex::new(websocket_stream)), + status: PhantomData::, + }), + Err(error) => { + Err!(XRPLWebsocketException::UnableToConnect::( + error + )) + } + } + } +} + +impl<'a> Client<'a> for AsyncWebsocketClient { + async fn request(&self, req: impl Serialize) -> Result> + where + T: for<'de> Deserialize<'de>, + { + let mut this = self.inner.lock().await; + let request = serde_json::to_string(&req).unwrap(); + this.send(TungsteniteMessage::Text(request)).await.unwrap(); + while let Some(Ok(message)) = this.next().await { + match message { + TungsteniteMessage::Text(response) => { + let response = serde_json::from_str(&response).unwrap(); + return Ok(response); + } + _ => return Err!(XRPLWebsocketException::::UnexpectedMessageType), + } + } + + Err!(XRPLWebsocketException::::NoResponse) + } + + async fn get_common_fields(&self) -> Result> { + let server_state = self + .request::(requests::ServerState::new(None)) + .await?; + let state = server_state.result.state; + let common_fields = CommonFields { + network_id: state.network_id, + build_version: Some(state.build_version), + }; + + Ok(common_fields) + } +} diff --git a/src/asynch/clients/websocket/codec.rs b/src/asynch/clients/websocket/codec.rs deleted file mode 100644 index 95e9b09b..00000000 --- a/src/asynch/clients/websocket/codec.rs +++ /dev/null @@ -1,30 +0,0 @@ -use alloc::{io, vec::Vec}; -use bytes::{BufMut, BytesMut}; -use tokio_util::codec::{Decoder, Encoder}; - -pub struct Codec; - -impl Decoder for Codec { - type Item = Vec; - type Error = io::Error; - - fn decode(&mut self, src: &mut BytesMut) -> Result>, io::Error> { - if !src.is_empty() { - let len = src.len(); - let data = src.split_to(len).to_vec(); - Ok(Some(data)) - } else { - Ok(None) - } - } -} - -impl Encoder<&[u8]> for Codec { - type Error = io::Error; - - fn encode(&mut self, data: &[u8], buf: &mut BytesMut) -> Result<(), io::Error> { - buf.reserve(data.len()); - buf.put(data); - Ok(()) - } -} diff --git a/src/asynch/clients/websocket/embedded_websocket.rs b/src/asynch/clients/websocket/embedded_websocket.rs deleted file mode 100644 index 77c48dd8..00000000 --- a/src/asynch/clients/websocket/embedded_websocket.rs +++ /dev/null @@ -1,297 +0,0 @@ -use core::{ - fmt::{Debug, Display}, - marker::PhantomData, - ops::{Deref, DerefMut}, -}; - -use alloc::{ - string::{String, ToString}, - sync::Arc, -}; -use anyhow::Result; -use embassy_sync::blocking_mutex::raw::RawMutex; -use embassy_sync::mutex::Mutex; -use embedded_io_async::{ErrorType, Read, Write}; -use embedded_websocket::{ - framer_async::{Framer, ReadResult}, - Client, WebSocketClient, WebSocketOptions, WebSocketSendMessageType, -}; -use futures_core::Stream; -use futures_sink::Sink; -use rand_core::RngCore; -use serde::{Deserialize, Serialize}; -use url::Url; - -use super::{SingleExecutorMutex, WebsocketClosed, WebsocketOpen}; -use crate::{ - asynch::clients::{ - client::Client as ClientTrait, - websocket::websocket_base::{MessageHandler, WebsocketBase}, - }, - models::{requests::Request, results::XRPLResponse}, - Err, -}; - -use super::exceptions::XRPLWebsocketException; - -pub struct AsyncWebsocketClient< - const BUF: usize, - Tcp, - B, - E, - Rng: RngCore, - M = SingleExecutorMutex, - Status = WebsocketClosed, -> where - M: RawMutex, - B: Deref + AsRef<[u8]>, - Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, -{ - tcp: Arc>, - websocket: Arc>>, - tx_buffer: [u8; BUF], - websocket_base: Arc>>, - status: PhantomData, -} - -impl - AsyncWebsocketClient -where - M: RawMutex, - B: Deref + AsRef<[u8]>, - E: Debug + Display, - Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, -{ - pub async fn open( - rng: Rng, - tcp: Tcp, - url: Url, - ) -> Result> { - // replace the scheme with http or https - let scheme = match url.scheme() { - "wss" => "https", - "ws" => "http", - _ => url.scheme(), - }; - let port = match url.port() { - Some(port) => port, - None => match url.scheme() { - "wss" => 443, - "ws" => 80, - _ => 80, - }, - } - .to_string(); - let path = url.path(); - let host = match url.host_str() { - Some(host) => host, - None => return Err!(XRPLWebsocketException::::Disconnected), - }; - let origin = scheme.to_string() + "://" + host + ":" + &port + path; - let websocket_options = WebSocketOptions { - path, - host, - origin: &origin, - sub_protocols: None, - additional_headers: None, - }; - let websocket = Arc::new(Mutex::new(Framer::new(WebSocketClient::new_client(rng)))); - let tcp = Arc::new(Mutex::new(tcp)); - let mut buffer = [0; BUF]; - if let Err(error) = websocket - .lock() - .await - .connect( - tcp.lock().await.deref_mut(), - &mut buffer, - &websocket_options, - ) - .await - { - match error { - // FramerError::WebSocket(embedded_websocket::Error::HttpResponseCodeInvalid( - // Some(308), - // )) => (), - error => return Err!(XRPLWebsocketException::from(error)), - } - } - - Ok(AsyncWebsocketClient { - tcp, - websocket, - tx_buffer: buffer, - websocket_base: Arc::new(Mutex::new(WebsocketBase::new())), - status: PhantomData::, - }) - } -} - -impl - AsyncWebsocketClient -where - M: RawMutex, - B: Deref + AsRef<[u8]>, - E: Debug + Display, - Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, -{ - async fn do_write(&self, buf: &[u8]) -> Result::Error> { - let mut inner = self.websocket.lock().await; - let mut tcp = self.tcp.lock().await; - let mut buffer = self.tx_buffer; - match inner - .write( - tcp.deref_mut(), - &mut buffer, - WebSocketSendMessageType::Text, - false, - buf, - ) - .await - { - Ok(()) => Ok(buf.len()), - Err(error) => Err(XRPLWebsocketException::::from(error)), - } - } - - async fn do_read(&self, buf: &mut [u8]) -> Result::Error> { - let mut inner = self.websocket.lock().await; - let mut tcp = self.tcp.lock().await; - match inner.read(tcp.deref_mut(), buf).await { - Some(Ok(ReadResult::Text(t))) => Ok(t.len()), - Some(Ok(ReadResult::Binary(b))) => Ok(b.len()), - Some(Ok(ReadResult::Ping(_))) => Ok(0), - Some(Ok(ReadResult::Pong(_))) => Ok(0), - Some(Ok(ReadResult::Close(_))) => Err(XRPLWebsocketException::::Disconnected), - Some(Err(error)) => Err(XRPLWebsocketException::::from(error)), - None => Err(XRPLWebsocketException::::Disconnected), - } - } -} - -impl ErrorType - for AsyncWebsocketClient -where - M: RawMutex, - B: Deref + AsRef<[u8]>, - E: Debug + Display, - Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, -{ - type Error = XRPLWebsocketException; -} - -impl Write - for AsyncWebsocketClient -where - M: RawMutex, - B: Deref + AsRef<[u8]>, - E: Debug + Display, - Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, -{ - async fn write(&mut self, buf: &[u8]) -> Result { - self.do_write(buf).await - } -} - -impl Read - for AsyncWebsocketClient -where - M: RawMutex, - B: Deref + AsRef<[u8]>, - E: Debug + Display, - Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, -{ - async fn read(&mut self, buf: &mut [u8]) -> Result { - self.do_read(buf).await - } -} - -impl MessageHandler - for AsyncWebsocketClient -where - M: RawMutex, - B: Deref + AsRef<[u8]>, - E: Debug + Display, - Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, -{ - async fn setup_request_future(&mut self, id: String) { - let mut websocket_base = self.websocket_base.lock().await; - websocket_base.setup_request_future(id).await; - } - - async fn handle_message(&mut self, message: String) -> Result<()> { - let mut websocket_base = self.websocket_base.lock().await; - websocket_base.handle_message(message).await - } - - async fn pop_message(&mut self) -> String { - let mut websocket_base = self.websocket_base.lock().await; - websocket_base.pop_message().await - } - - async fn try_recv_request(&mut self, id: String) -> Result> { - let mut websocket_base = self.websocket_base.lock().await; - websocket_base.try_recv_request(id).await - } -} - -impl<'a, const BUF: usize, M, Tcp, B, E, Rng: RngCore> ClientTrait<'a> - for AsyncWebsocketClient -where - M: RawMutex, - B: Deref + AsRef<[u8]>, - E: Debug + Display, - Tcp: Stream> + for<'b> Sink<&'b [u8], Error = E> + Unpin, -{ - async fn request_impl< - Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, - >( - &'a self, - mut request: Req, - ) -> Result> { - // setup request future - let request_id = self.set_request_id::(&mut request); - let mut websocket_base = self.websocket_base.lock().await; - websocket_base - .setup_request_future(request_id.to_string()) - .await; - // send request - let request_string = match serde_json::to_string(&request) { - Ok(request_string) => request_string, - Err(error) => return Err!(error), - }; - if let Err(error) = self.do_write(request_string.as_bytes()).await { - return Err!(error); - } - // wait for response - loop { - let mut rx_buffer = [0; 1024]; - match self.do_read(&mut rx_buffer).await { - Ok(u_size) => { - // If the buffer is empty, continue to the next iteration. - if u_size == 0 { - continue; - } - let message_str = match core::str::from_utf8(&rx_buffer[..u_size]) { - Ok(response_str) => response_str, - Err(error) => return Err!(XRPLWebsocketException::::Utf8(error)), - }; - websocket_base - .handle_message(message_str.to_string()) - .await?; - let message_opt = websocket_base - .try_recv_request(request_id.to_string()) - .await?; - if let Some(message) = message_opt { - let response = match serde_json::from_str(&message) { - Ok(response) => response, - Err(error) => return Err!(error), - }; - return Ok(response); - } - } - Err(error) => return Err!(error), - } - } - } -} diff --git a/src/asynch/clients/websocket/mod.rs b/src/asynch/clients/websocket/mod.rs deleted file mode 100644 index 6cc1d398..00000000 --- a/src/asynch/clients/websocket/mod.rs +++ /dev/null @@ -1,137 +0,0 @@ -use core::fmt::{Debug, Display}; - -use crate::{models::results::XRPLResponse, Err}; -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -use alloc::string::String; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -use alloc::string::ToString; -use anyhow::Result; -use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -use embedded_io_async::{ErrorType, Read as EmbeddedIoRead, Write as EmbeddedIoWrite}; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -use exceptions::XRPLWebsocketException; -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -use futures::{Sink, SinkExt, Stream, StreamExt}; -use serde::{Deserialize, Serialize}; - -mod websocket_base; -use websocket_base::MessageHandler; - -#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] -pub mod codec; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -mod embedded_websocket; -pub mod exceptions; -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -mod tungstenite; - -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -pub use embedded_websocket::AsyncWebsocketClient; -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -pub use tungstenite::AsyncWebsocketClient; - -pub struct WebsocketOpen; -pub struct WebsocketClosed; - -pub type MultiExecutorMutex = CriticalSectionRawMutex; -pub type SingleExecutorMutex = NoopRawMutex; - -#[allow(async_fn_in_trait)] -pub trait XRPLWebsocketIO { - async fn xrpl_send(&mut self, message: Req) -> Result<()>; - async fn xrpl_receive< - Res: Serialize + for<'de> Deserialize<'de> + Debug, - Req: Serialize + for<'de> Deserialize<'de> + Debug, - >( - &mut self, - ) -> Result>>; -} - -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -impl XRPLWebsocketIO for T -where - ::Error: Display, -{ - async fn xrpl_send(&mut self, message: Req) -> Result<()> { - let message = match serde_json::to_string(&message) { - Ok(message) => message, - Err(error) => return Err!(error), - }; - let message_buffer = message.as_bytes(); - match self.write(message_buffer).await { - Ok(_) => Ok(()), - Err(e) => Err!(e), - } - } - - async fn xrpl_receive< - Res: Serialize + for<'de> Deserialize<'de> + Debug, - Req: Serialize + for<'de> Deserialize<'de> + Debug, - >( - &mut self, - ) -> Result>> { - let mut buffer = [0; 1024]; - loop { - match self.read(&mut buffer).await { - Ok(u_size) => { - // If the buffer is empty, continue to the next iteration. - if u_size == 0 { - continue; - } - let response_str = match core::str::from_utf8(&buffer[..u_size]) { - Ok(response_str) => response_str, - Err(error) => { - return Err!(XRPLWebsocketException::::Utf8(error)) - } - }; - self.handle_message(response_str.to_string()).await?; - let message = self.pop_message().await; - match serde_json::from_str(&message) { - Ok(response) => return Ok(response), - Err(error) => return Err!(error), - } - } - Err(error) => return Err!(error), - } - } - } -} - -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -impl XRPLWebsocketIO for T -where - T: Stream> + Sink + MessageHandler + Unpin, - >::Error: Debug + Display, -{ - async fn xrpl_send(&mut self, message: Req) -> Result<()> { - let message = match serde_json::to_string(&message) { - Ok(message) => message, - Err(error) => return Err!(error), - }; - match self.send(message).await { - Ok(()) => Ok(()), - Err(error) => Err!(error), - } - } - - async fn xrpl_receive< - Res: Serialize + for<'de> Deserialize<'de> + Debug, - Req: Serialize + for<'de> Deserialize<'de> + Debug, - >( - &mut self, - ) -> Result>> { - match self.next().await { - Some(Ok(item)) => { - self.handle_message(item).await?; - let message = self.pop_message().await; - match serde_json::from_str(&message) { - Ok(response) => Ok(response), - Err(error) => Err!(error), - } - } - Some(Err(error)) => Err!(error), - None => Ok(None), - } - } -} diff --git a/src/asynch/clients/websocket/tungstenite.rs b/src/asynch/clients/websocket/tungstenite.rs deleted file mode 100644 index 0f85ceba..00000000 --- a/src/asynch/clients/websocket/tungstenite.rs +++ /dev/null @@ -1,265 +0,0 @@ -use super::exceptions::XRPLWebsocketException; -use super::{SingleExecutorMutex, WebsocketClosed, WebsocketOpen}; -use crate::asynch::clients::client::Client; -use crate::asynch::clients::websocket::websocket_base::{MessageHandler, WebsocketBase}; -use crate::models::requests::Request; -use crate::models::results::XRPLResponse; -use crate::Err; - -use alloc::string::{String, ToString}; -use alloc::sync::Arc; -use anyhow::Result; -use core::marker::PhantomData; -use core::{pin::Pin, task::Poll}; -use embassy_futures::block_on; -use embassy_sync::blocking_mutex::raw::RawMutex; -use embassy_sync::mutex::Mutex; -use futures::{Sink, Stream, StreamExt}; -use futures_util::SinkExt; -use serde::{Deserialize, Serialize}; -use tokio::net::TcpStream; -use tokio_tungstenite::{ - connect_async as tungstenite_connect_async, MaybeTlsStream as TungsteniteMaybeTlsStream, - WebSocketStream as TungsteniteWebsocketStream, -}; -use url::Url; - -pub use tokio_tungstenite::tungstenite::Message as TungsteniteMessage; - -pub type AsyncWebsocketConnection = - Arc>>>; - -pub struct AsyncWebsocketClient -where - M: RawMutex, -{ - websocket: AsyncWebsocketConnection, - websocket_base: Arc>>, - status: PhantomData, -} - -impl Sink for AsyncWebsocketClient -where - M: RawMutex, - Self: Unpin, -{ - type Error = anyhow::Error; - - fn poll_ready( - self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { - let mut guard = block_on(self.websocket.lock()); - match Pin::new(&mut *guard).poll_ready(cx) { - Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), - Poll::Pending => Poll::Pending, - } - } - - fn start_send(self: core::pin::Pin<&mut Self>, item: String) -> Result<()> { - let mut guard = block_on(self.websocket.lock()); - match Pin::new(&mut *guard).start_send(TungsteniteMessage::Text(item)) { - Ok(()) => Ok(()), - Err(error) => Err!(error), - } - } - - fn poll_flush( - self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { - let mut guard = block_on(self.websocket.lock()); - match Pin::new(&mut *guard).poll_flush(cx) { - Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), - Poll::Pending => Poll::Pending, - } - } - - fn poll_close( - self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { - let mut guard = block_on(self.websocket.lock()); - match Pin::new(&mut *guard).poll_close(cx) { - Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), - Poll::Pending => Poll::Pending, - } - } -} - -impl Stream for AsyncWebsocketClient -where - M: RawMutex, -{ - type Item = Result; - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> Poll> { - let mut guard = block_on(self.websocket.lock()); - match Pin::new(&mut *guard).poll_next(cx) { - Poll::Ready(Some(item)) => match item { - Ok(message) => match message { - TungsteniteMessage::Text(response) => Poll::Ready(Some(Ok(response))), - TungsteniteMessage::Binary(response) => { - let response_string = match String::from_utf8(response) { - Ok(string) => string, - Err(error) => { - return Poll::Ready(Some(Err!(XRPLWebsocketException::< - anyhow::Error, - >::Utf8( - error.utf8_error() - )))); - } - }; - Poll::Ready(Some(Ok(response_string))) - } - TungsteniteMessage::Close(_) => Poll::Ready(Some(Err!( - XRPLWebsocketException::::Disconnected - ))), - _ => Poll::Ready(Some(Err!( - XRPLWebsocketException::::UnexpectedMessageType - ))), - }, - Err(error) => Poll::Ready(Some(Err!(error))), - }, - Poll::Ready(None) => Poll::Ready(None), - Poll::Pending => Poll::Pending, - } - } -} - -impl AsyncWebsocketClient -where - M: RawMutex, -{ - pub async fn open(uri: Url) -> Result> { - match tungstenite_connect_async(uri).await { - Ok((websocket_stream, _)) => Ok(AsyncWebsocketClient { - websocket: Arc::new(Mutex::new(websocket_stream)), - websocket_base: Arc::new(Mutex::new(WebsocketBase::new())), - status: PhantomData::, - }), - Err(error) => { - Err!(XRPLWebsocketException::UnableToConnect::( - error - )) - } - } - } -} - -impl MessageHandler for AsyncWebsocketClient -where - M: RawMutex, -{ - async fn setup_request_future(&mut self, id: String) { - let mut websocket_base = self.websocket_base.lock().await; - websocket_base.setup_request_future(id).await; - } - - async fn handle_message(&mut self, message: String) -> Result<()> { - let mut websocket_base = self.websocket_base.lock().await; - websocket_base.handle_message(message).await - } - - async fn pop_message(&mut self) -> String { - let mut websocket_base = self.websocket_base.lock().await; - websocket_base.pop_message().await - } - - async fn try_recv_request(&mut self, id: String) -> Result> { - let mut websocket_base = self.websocket_base.lock().await; - websocket_base.try_recv_request(id).await - } -} - -impl<'a, M> Client<'a> for AsyncWebsocketClient -where - M: RawMutex, -{ - async fn request_impl< - Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, - >( - &'a self, - mut request: Req, - ) -> Result> { - // setup request future - let request_id = self.set_request_id::(&mut request); - let mut websocket_base = self.websocket_base.lock().await; - websocket_base - .setup_request_future(request_id.to_string()) - .await; - // send request - let mut websocket = self.websocket.lock().await; - let request_string = match serde_json::to_string(&request) { - Ok(request_string) => request_string, - Err(error) => return Err!(error), - }; - if let Err(error) = websocket - .send(TungsteniteMessage::Text(request_string)) - .await - { - return Err!(error); - } - // wait for response - loop { - let message = websocket.next().await; - match message { - Some(Ok(TungsteniteMessage::Text(message))) => { - websocket_base.handle_message(message).await?; - let message_opt = websocket_base - .try_recv_request(request_id.to_string()) - .await?; - if let Some(message) = message_opt { - let response = match serde_json::from_str(&message) { - Ok(response) => response, - Err(error) => return Err!(error), - }; - return Ok(response); - } - } - Some(Ok(TungsteniteMessage::Binary(response))) => { - let message = match String::from_utf8(response) { - Ok(string) => string, - Err(error) => { - return Err!(XRPLWebsocketException::::Utf8( - error.utf8_error() - )); - } - }; - match serde_json::from_str(&message) { - Ok(response) => return Ok(response), - Err(error) => return Err!(error), - } - } - Some(Ok(TungsteniteMessage::Close(_))) => { - return Err!(XRPLWebsocketException::::Disconnected) - } - Some(Ok(_)) => { - return Err!(XRPLWebsocketException::::UnexpectedMessageType); - } - Some(Err(error)) => return Err!(error), - None => continue, - } - } - } - - // async fn get_common_fields(&self) -> Result> { - // let server_state = self - // .request::(requests::ServerState::new(None)) - // .await?; - // let state = server_state.result.state; - // let common_fields = CommonFields { - // network_id: state.network_id, - // build_version: Some(state.build_version), - // }; - - // Ok(common_fields) - // } -} diff --git a/src/asynch/clients/websocket/websocket_base.rs b/src/asynch/clients/websocket/websocket_base.rs deleted file mode 100644 index ab43e3a8..00000000 --- a/src/asynch/clients/websocket/websocket_base.rs +++ /dev/null @@ -1,112 +0,0 @@ -use alloc::string::{String, ToString}; -use anyhow::Result; -use embassy_sync::{blocking_mutex::raw::RawMutex, channel::Channel}; -use futures::channel::oneshot::{self, Receiver, Sender}; -use hashbrown::HashMap; -use serde_json::Value; - -use crate::{asynch::clients::exceptions::XRPLWebsocketException, Err}; - -const _MAX_CHANNEL_MSG_CNT: usize = 10; - -/// A struct that handles futures of websocket messages. -pub struct WebsocketBase -where - M: RawMutex, -{ - /// The messages the user requests, which means he is waiting for a specific `id`. - pending_requests: HashMap>, - request_senders: HashMap>, - /// The messages the user waits for when sending and receiving normally. - messages: Channel, -} - -impl WebsocketBase -where - M: RawMutex, -{ - pub fn new() -> Self { - Self { - pending_requests: HashMap::new(), - request_senders: HashMap::new(), - messages: Channel::new(), - } - } - - pub fn close(&mut self) { - self.pending_requests.clear(); - self.request_senders.clear(); - self.messages.clear(); - } -} - -pub(crate) trait MessageHandler { - /// Setup an empty future for a request. - async fn setup_request_future(&mut self, id: String); - async fn handle_message(&mut self, message: String) -> Result<()>; - async fn pop_message(&mut self) -> String; - async fn try_recv_request(&mut self, id: String) -> Result>; -} - -impl MessageHandler for WebsocketBase -where - M: RawMutex, -{ - async fn setup_request_future(&mut self, id: String) { - if self.pending_requests.contains_key(&id) { - return; - } - let (sender, receiver) = oneshot::channel::(); - self.pending_requests.insert(id.clone(), receiver); - self.request_senders.insert(id, sender); - } - - async fn handle_message(&mut self, message: String) -> Result<()> { - let message_value: Value = match serde_json::from_str(&message) { - Ok(value) => value, - Err(error) => return Err!(error), - }; - let id = match message_value.get("id") { - Some(id) => match id.as_str() { - Some(id) => id.to_string(), - None => return Err!(XRPLWebsocketException::::InvalidMessage), - }, - None => String::new(), - }; - if let Some(_receiver) = self.pending_requests.get(&id) { - let sender = match self.request_senders.remove(&id) { - Some(sender) => sender, - None => return Err!(XRPLWebsocketException::::MissingRequestSender), - }; - match sender.send(message) { - Ok(()) => (), - Err(error) => return Err!(error), - }; - } else { - self.messages.send(message).await; - } - Ok(()) - } - - async fn pop_message(&mut self) -> String { - self.messages.receive().await - } - - async fn try_recv_request(&mut self, id: String) -> Result> { - let fut = match self.pending_requests.get_mut(&id) { - Some(fut) => fut, - None => return Err!(XRPLWebsocketException::::MissingRequestReceiver), - }; - match fut.try_recv() { - Ok(Some(message)) => { - // Remove the future from the hashmap. - self.pending_requests.remove(&id); - Ok(Some(message)) - } - Ok(None) => Ok(None), - Err(error) => { - return Err!(error); - } - } - } -} diff --git a/src/models/requests/account_channels.rs b/src/models/requests/account_channels.rs index df0753ed..5965eb52 100644 --- a/src/models/requests/account_channels.rs +++ b/src/models/requests/account_channels.rs @@ -4,7 +4,7 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; -use super::{CommonFields, Request}; +use super::CommonFields; /// This request returns information about an account's Payment /// Channels. This includes only channels where the specified @@ -85,13 +85,3 @@ impl<'a> AccountChannels<'a> { } } } - -impl<'a> Request<'a> for AccountChannels<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields - } -} diff --git a/src/models/requests/account_currencies.rs b/src/models/requests/account_currencies.rs index 4ff2e373..c6307543 100644 --- a/src/models/requests/account_currencies.rs +++ b/src/models/requests/account_currencies.rs @@ -37,13 +37,9 @@ pub struct AccountCurrencies<'a> { impl<'a> Model for AccountCurrencies<'a> {} -impl<'a> Request<'a> for AccountCurrencies<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for AccountCurrencies<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/account_info.rs b/src/models/requests/account_info.rs index 84d8f42d..1db06aa4 100644 --- a/src/models/requests/account_info.rs +++ b/src/models/requests/account_info.rs @@ -44,13 +44,9 @@ pub struct AccountInfo<'a> { impl<'a> Model for AccountInfo<'a> {} -impl<'a> Request<'a> for AccountInfo<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for AccountInfo<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/account_lines.rs b/src/models/requests/account_lines.rs index 3571cf2a..432f74d4 100644 --- a/src/models/requests/account_lines.rs +++ b/src/models/requests/account_lines.rs @@ -38,13 +38,9 @@ pub struct AccountLines<'a> { impl<'a> Model for AccountLines<'a> {} -impl<'a> Request<'a> for AccountLines<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for AccountLines<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/account_nfts.rs b/src/models/requests/account_nfts.rs index a9e79047..2447f3af 100644 --- a/src/models/requests/account_nfts.rs +++ b/src/models/requests/account_nfts.rs @@ -29,13 +29,9 @@ pub struct AccountNfts<'a> { impl<'a> Model for AccountNfts<'a> {} -impl<'a> Request<'a> for AccountNfts<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for AccountNfts<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/account_objects.rs b/src/models/requests/account_objects.rs index d78c4381..c51bcc4f 100644 --- a/src/models/requests/account_objects.rs +++ b/src/models/requests/account_objects.rs @@ -63,13 +63,9 @@ pub struct AccountObjects<'a> { impl<'a> Model for AccountObjects<'a> {} -impl<'a> Request<'a> for AccountObjects<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for AccountObjects<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/account_offers.rs b/src/models/requests/account_offers.rs index 647d3f3e..5e2cfb02 100644 --- a/src/models/requests/account_offers.rs +++ b/src/models/requests/account_offers.rs @@ -40,13 +40,9 @@ pub struct AccountOffers<'a> { impl<'a> Model for AccountOffers<'a> {} -impl<'a> Request<'a> for AccountOffers<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for AccountOffers<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/account_tx.rs b/src/models/requests/account_tx.rs index 1f16e80a..48da5afd 100644 --- a/src/models/requests/account_tx.rs +++ b/src/models/requests/account_tx.rs @@ -53,13 +53,9 @@ pub struct AccountTx<'a> { impl<'a> Model for AccountTx<'a> {} -impl<'a> Request<'a> for AccountTx<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for AccountTx<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/book_offers.rs b/src/models/requests/book_offers.rs index ca27a3d5..57887d38 100644 --- a/src/models/requests/book_offers.rs +++ b/src/models/requests/book_offers.rs @@ -46,13 +46,9 @@ pub struct BookOffers<'a> { impl<'a> Model for BookOffers<'a> {} -impl<'a> Request<'a> for BookOffers<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for BookOffers<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/channel_authorize.rs b/src/models/requests/channel_authorize.rs index 466fa425..835cdd7e 100644 --- a/src/models/requests/channel_authorize.rs +++ b/src/models/requests/channel_authorize.rs @@ -80,13 +80,9 @@ impl<'a> Model for ChannelAuthorize<'a> { } } -impl<'a> Request<'a> for ChannelAuthorize<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for ChannelAuthorize<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/channel_verify.rs b/src/models/requests/channel_verify.rs index 59e814a9..949b6ad1 100644 --- a/src/models/requests/channel_verify.rs +++ b/src/models/requests/channel_verify.rs @@ -30,13 +30,9 @@ pub struct ChannelVerify<'a> { impl<'a> Model for ChannelVerify<'a> {} -impl<'a> Request<'a> for ChannelVerify<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for ChannelVerify<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/deposit_authorize.rs b/src/models/requests/deposit_authorize.rs index 2c58e473..5204bc69 100644 --- a/src/models/requests/deposit_authorize.rs +++ b/src/models/requests/deposit_authorize.rs @@ -30,13 +30,9 @@ pub struct DepositAuthorized<'a> { impl<'a> Model for DepositAuthorized<'a> {} -impl<'a> Request<'a> for DepositAuthorized<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for DepositAuthorized<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/fee.rs b/src/models/requests/fee.rs index 05052d96..2ab0ee8a 100644 --- a/src/models/requests/fee.rs +++ b/src/models/requests/fee.rs @@ -23,13 +23,9 @@ pub struct Fee<'a> { impl<'a> Model for Fee<'a> {} -impl<'a> Request<'a> for Fee<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for Fee<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/gateway_balances.rs b/src/models/requests/gateway_balances.rs index e5f38386..58dc61e7 100644 --- a/src/models/requests/gateway_balances.rs +++ b/src/models/requests/gateway_balances.rs @@ -36,13 +36,9 @@ pub struct GatewayBalances<'a> { impl<'a> Model for GatewayBalances<'a> {} -impl<'a> Request<'a> for GatewayBalances<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for GatewayBalances<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/ledger.rs b/src/models/requests/ledger.rs index d567cd41..7ba5faec 100644 --- a/src/models/requests/ledger.rs +++ b/src/models/requests/ledger.rs @@ -58,13 +58,9 @@ pub struct Ledger<'a> { impl<'a> Model for Ledger<'a> {} -impl<'a> Request<'a> for Ledger<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for Ledger<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/ledger_closed.rs b/src/models/requests/ledger_closed.rs index 7b601ea6..c65f486c 100644 --- a/src/models/requests/ledger_closed.rs +++ b/src/models/requests/ledger_closed.rs @@ -22,13 +22,9 @@ pub struct LedgerClosed<'a> { impl<'a> Model for LedgerClosed<'a> {} -impl<'a> Request<'a> for LedgerClosed<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for LedgerClosed<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/ledger_current.rs b/src/models/requests/ledger_current.rs index aec482f6..9b408ee6 100644 --- a/src/models/requests/ledger_current.rs +++ b/src/models/requests/ledger_current.rs @@ -22,13 +22,9 @@ pub struct LedgerCurrent<'a> { impl<'a> Model for LedgerCurrent<'a> {} -impl<'a> Request<'a> for LedgerCurrent<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for LedgerCurrent<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/ledger_data.rs b/src/models/requests/ledger_data.rs index c30bb753..c71e012c 100644 --- a/src/models/requests/ledger_data.rs +++ b/src/models/requests/ledger_data.rs @@ -36,13 +36,9 @@ pub struct LedgerData<'a> { impl<'a> Model for LedgerData<'a> {} -impl<'a> Request<'a> for LedgerData<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for LedgerData<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/ledger_entry.rs b/src/models/requests/ledger_entry.rs index 0022437c..1dbc4c1d 100644 --- a/src/models/requests/ledger_entry.rs +++ b/src/models/requests/ledger_entry.rs @@ -159,13 +159,9 @@ impl<'a> LedgerEntryError for LedgerEntry<'a> { } } -impl<'a> Request<'a> for LedgerEntry<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for LedgerEntry<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/manifest.rs b/src/models/requests/manifest.rs index e9f38d94..d2615195 100644 --- a/src/models/requests/manifest.rs +++ b/src/models/requests/manifest.rs @@ -27,13 +27,9 @@ pub struct Manifest<'a> { impl<'a> Model for Manifest<'a> {} -impl<'a> Request<'a> for Manifest<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for Manifest<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/mod.rs b/src/models/requests/mod.rs index 3d81058f..860b7504 100644 --- a/src/models/requests/mod.rs +++ b/src/models/requests/mod.rs @@ -148,9 +148,14 @@ pub struct CommonFields<'a> { pub id: Option>, } +impl Request for CommonFields<'_> { + fn get_command(&self) -> RequestMethod { + self.command.clone() + } +} + /// The base trait for all request models. /// Used to identify the model as a request. -pub trait Request<'a> { - fn get_common_fields(&self) -> &CommonFields<'a>; - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a>; +pub trait Request { + fn get_command(&self) -> RequestMethod; } diff --git a/src/models/requests/nft_buy_offers.rs b/src/models/requests/nft_buy_offers.rs index d8a4c4c5..3e9ce276 100644 --- a/src/models/requests/nft_buy_offers.rs +++ b/src/models/requests/nft_buy_offers.rs @@ -31,13 +31,9 @@ pub struct NftBuyOffers<'a> { impl<'a> Model for NftBuyOffers<'a> {} -impl<'a> Request<'a> for NftBuyOffers<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for NftBuyOffers<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/nft_sell_offers.rs b/src/models/requests/nft_sell_offers.rs index fa4dd6f9..7404d59c 100644 --- a/src/models/requests/nft_sell_offers.rs +++ b/src/models/requests/nft_sell_offers.rs @@ -19,13 +19,9 @@ pub struct NftSellOffers<'a> { impl<'a> Model for NftSellOffers<'a> {} -impl<'a> Request<'a> for NftSellOffers<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for NftSellOffers<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/no_ripple_check.rs b/src/models/requests/no_ripple_check.rs index 21bffb7f..f2467efa 100644 --- a/src/models/requests/no_ripple_check.rs +++ b/src/models/requests/no_ripple_check.rs @@ -58,13 +58,9 @@ pub struct NoRippleCheck<'a> { impl<'a> Model for NoRippleCheck<'a> {} -impl<'a> Request<'a> for NoRippleCheck<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for NoRippleCheck<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/path_find.rs b/src/models/requests/path_find.rs index 55b34865..e2ab7074 100644 --- a/src/models/requests/path_find.rs +++ b/src/models/requests/path_find.rs @@ -90,13 +90,9 @@ pub struct PathFind<'a> { impl<'a> Model for PathFind<'a> {} -impl<'a> Request<'a> for PathFind<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for PathFind<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/ping.rs b/src/models/requests/ping.rs index 5c70e863..079b81be 100644 --- a/src/models/requests/ping.rs +++ b/src/models/requests/ping.rs @@ -21,13 +21,9 @@ pub struct Ping<'a> { impl<'a> Model for Ping<'a> {} -impl<'a> Request<'a> for Ping<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for Ping<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/random.rs b/src/models/requests/random.rs index 19c7e986..8bc0029f 100644 --- a/src/models/requests/random.rs +++ b/src/models/requests/random.rs @@ -22,13 +22,9 @@ pub struct Random<'a> { impl<'a> Model for Random<'a> {} -impl<'a> Request<'a> for Random<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for Random<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/ripple_path_find.rs b/src/models/requests/ripple_path_find.rs index 4f7f5cfd..ac923963 100644 --- a/src/models/requests/ripple_path_find.rs +++ b/src/models/requests/ripple_path_find.rs @@ -62,13 +62,9 @@ pub struct RipplePathFind<'a> { impl<'a> Model for RipplePathFind<'a> {} -impl<'a> Request<'a> for RipplePathFind<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for RipplePathFind<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/server_info.rs b/src/models/requests/server_info.rs index 7044ee5d..378fc0f0 100644 --- a/src/models/requests/server_info.rs +++ b/src/models/requests/server_info.rs @@ -22,13 +22,9 @@ pub struct ServerInfo<'a> { impl<'a> Model for ServerInfo<'a> {} -impl<'a> Request<'a> for ServerInfo<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for ServerInfo<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/server_state.rs b/src/models/requests/server_state.rs index 85c910c5..4a0b7306 100644 --- a/src/models/requests/server_state.rs +++ b/src/models/requests/server_state.rs @@ -27,13 +27,9 @@ pub struct ServerState<'a> { impl<'a> Model for ServerState<'a> {} -impl<'a> Request<'a> for ServerState<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for ServerState<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/submit.rs b/src/models/requests/submit.rs index e68dec41..132a9910 100644 --- a/src/models/requests/submit.rs +++ b/src/models/requests/submit.rs @@ -49,13 +49,9 @@ pub struct Submit<'a> { impl<'a> Model for Submit<'a> {} -impl<'a> Request<'a> for Submit<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for Submit<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/submit_multisigned.rs b/src/models/requests/submit_multisigned.rs index 913539c9..82fdc1cc 100644 --- a/src/models/requests/submit_multisigned.rs +++ b/src/models/requests/submit_multisigned.rs @@ -30,13 +30,9 @@ pub struct SubmitMultisigned<'a> { impl<'a> Model for SubmitMultisigned<'a> {} -impl<'a> Request<'a> for SubmitMultisigned<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for SubmitMultisigned<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/subscribe.rs b/src/models/requests/subscribe.rs index fb501f87..0487ca22 100644 --- a/src/models/requests/subscribe.rs +++ b/src/models/requests/subscribe.rs @@ -77,13 +77,9 @@ pub struct Subscribe<'a> { impl<'a> Model for Subscribe<'a> {} -impl<'a> Request<'a> for Subscribe<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for Subscribe<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/transaction_entry.rs b/src/models/requests/transaction_entry.rs index 8f3c9885..24ad02cb 100644 --- a/src/models/requests/transaction_entry.rs +++ b/src/models/requests/transaction_entry.rs @@ -31,13 +31,9 @@ pub struct TransactionEntry<'a> { impl<'a> Model for TransactionEntry<'a> {} -impl<'a> Request<'a> for TransactionEntry<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for TransactionEntry<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/tx.rs b/src/models/requests/tx.rs index 29b88373..0dd547ee 100644 --- a/src/models/requests/tx.rs +++ b/src/models/requests/tx.rs @@ -34,13 +34,9 @@ pub struct Tx<'a> { impl<'a> Model for Tx<'a> {} -impl<'a> Request<'a> for Tx<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for Tx<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/requests/unsubscribe.rs b/src/models/requests/unsubscribe.rs index b056b688..90f123d0 100644 --- a/src/models/requests/unsubscribe.rs +++ b/src/models/requests/unsubscribe.rs @@ -62,13 +62,9 @@ pub struct Unsubscribe<'a> { impl<'a> Model for Unsubscribe<'a> {} -impl<'a> Request<'a> for Unsubscribe<'a> { - fn get_common_fields(&self) -> &CommonFields<'a> { - &self.common_fields - } - - fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { - &mut self.common_fields +impl<'a> Request for Unsubscribe<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs index 5c74b564..9bb7db6d 100644 --- a/src/models/results/mod.rs +++ b/src/models/results/mod.rs @@ -1,17 +1,29 @@ -use alloc::{borrow::Cow, string::ToString, vec::Vec}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +pub mod account_info; +pub mod fee; +pub mod ledger; +pub mod server_state; -mod fee; -pub use fee::{Fee as FeeResult, *}; +pub use account_info::*; +pub use fee::*; +pub use ledger::*; +pub use server_state::*; -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +use alloc::{borrow::Cow, vec::Vec}; +use anyhow::Result; +use futures::{Stream, StreamExt}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use crate::Err; + +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum ResponseStatus { Success, Error, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum ResponseType { Response, @@ -19,85 +31,83 @@ pub enum ResponseType { Transaction, } -#[derive(Debug, Clone, Serialize)] -pub struct XRPLResponse<'a, Res, Req> { - pub id: Option>, - pub error: Option>, - pub error_code: Option, - pub error_message: Option>, - pub forwarded: Option, - pub request: Option, - pub result: Option, - pub status: Option, - pub r#type: Option, - pub warning: Option>, - pub warnings: Option>>, +pub trait XRPLResponseFromStream Deserialize<'de>>: + StreamExt> +{ + async fn next_xrpl_response(&mut self) -> Option>> + where + Self: Unpin; + + async fn try_next_xrpl_response(&mut self) -> Result>> + where + Self: Unpin, + { + match self.next_xrpl_response().await { + Some(response) => response.map(Some), + None => Ok(None), + } + } } -impl<'a, 'de, Res, Req> Deserialize<'de> for XRPLResponse<'a, Res, Req> +impl XRPLResponseFromStream for S where - Res: DeserializeOwned, - Req: DeserializeOwned, + S: Stream> + StreamExt> + Unpin, + T: for<'de> Deserialize<'de>, { - fn deserialize(deserializer: D) -> Result + async fn next_xrpl_response(&mut self) -> Option>> where - D: serde::Deserializer<'de>, + Self: StreamExt>, { - // TODO: add validation for fields that can not coexist in the same response - let mut map = serde_json::Map::deserialize(deserializer)?; - if map.is_empty() { - return Err(serde::de::Error::custom("Empty response")); + let item = self.next().await; + match item { + Some(Ok(message)) => match serde_json::from_value(message) { + Ok(response) => Some(Ok(response)), + Err(error) => Some(Err!(error)), + }, + Some(Err(error)) => Some(Err!(error)), + None => None, } - Ok(XRPLResponse { - id: map.remove("id").map(|item| match item.as_str() { - Some(item_str) => Cow::Owned(item_str.to_string()), - None => Cow::Borrowed(""), - }), - error: map.remove("error").map(|item| match item.as_str() { - Some(item_str) => Cow::Owned(item_str.to_string()), - None => Cow::Borrowed(""), - }), - error_code: map - .remove("error_code") - .and_then(|v| v.as_i64()) - .map(|v| v as i32), - error_message: map.remove("error_message").map(|item| match item.as_str() { - Some(item_str) => Cow::Owned(item_str.to_string()), - None => Cow::Borrowed(""), - }), - forwarded: map.remove("forwarded").and_then(|v| v.as_bool()), - request: map - .remove("request") - .map(|v| serde_json::from_value(v).unwrap()), - result: map - .remove("result") - .map(|v| serde_json::from_value(v).unwrap()), - status: map - .remove("status") - .map(|v| serde_json::from_value(v).unwrap()), - r#type: map - .remove("type") - .map(|v| serde_json::from_value(v).unwrap()), - warning: map.remove("warning").map(|item| match item.as_str() { - Some(item_str) => Cow::Owned(item_str.to_string()), - None => Cow::Borrowed(""), - }), - warnings: map - .remove("warnings") - .and_then(|v| serde_json::from_value(v).ok()), - }) } } -impl<'a, Res, Req> XRPLResponse<'a, Res, Req> { - pub fn is_success(&self) -> bool { - self.status == Some(ResponseStatus::Success) +/// A response from a XRPL node. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct XRPLResponse<'a, T> { + pub id: Option>, + pub result: T, + pub status: Option, + pub r#type: Option, + pub forwarded: Option, + pub warnings: Option>>, + pub warning: Option>, +} + +impl XRPLResponse<'_, T> { + pub fn is_successful(&self) -> bool { + match self.status { + Some(ResponseStatus::Success) => true, + _ => false, + } } } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct XRPLErrorResponse<'a, T> { + pub id: Cow<'a, str>, + pub error: Option>, + pub error_code: Option, + pub error_message: Option>, + pub request: Option, + pub status: Option, + pub r#type: Option, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct XRPLWarning<'a> { pub id: Cow<'a, str>, pub message: Cow<'a, str>, pub forwarded: Option, } + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EmptyResult; diff --git a/src/models/transactions/escrow_finish.rs b/src/models/transactions/escrow_finish.rs index 4fab2f24..bbc1396e 100644 --- a/src/models/transactions/escrow_finish.rs +++ b/src/models/transactions/escrow_finish.rs @@ -169,8 +169,6 @@ mod test_escrow_finish_errors { #[cfg(test)] mod tests { - use serde_json::Value; - use super::*; #[test] @@ -195,9 +193,9 @@ mod tests { ); let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"EscrowFinish","Owner":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","OfferSequence":7,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100","Fulfillment":"A0028000"}"#; // Serialize - let default_json_value: Value = serde_json::from_str(default_json_str).unwrap(); - // let serialized_string = serde_json::to_string(&default_txn).unwrap(); - let serialized_value = serde_json::to_value(&default_txn).unwrap(); + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); assert_eq!(serialized_value, default_json_value); // Deserialize diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 5f8588d0..3970165d 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -135,7 +135,6 @@ where pub fee: Option>, /// Set of bit-flags for this transaction. #[serde(with = "txn_flags")] - #[serde(default = "optional_flag_collection_default")] pub flags: Option>, /// Highest ledger index this transaction can appear in. /// Specifying this field places a strict upper limit on how long @@ -239,13 +238,6 @@ where } } -fn optional_flag_collection_default() -> Option> -where - T: IntoEnumIterator + Serialize + core::fmt::Debug, -{ - None -} - serde_with_tag! { /// An arbitrary piece of data attached to a transaction. A /// transaction can have multiple Memo objects as an array diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 9fff8a72..41508f7c 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -8,10 +8,7 @@ pub use self::time_conversion::*; pub use self::xrpl_conversion::*; use crate::constants::*; -use alloc::string::String; -use alloc::string::ToString; use alloc::vec::Vec; -use rand::Rng; use regex::Regex; /// Determine if the address string is a hex address. @@ -68,12 +65,6 @@ pub fn is_iso_hex(value: &str) -> bool { regex.is_match(value) } -/// Generate a random id. -pub fn get_random_id(rng: &mut T) -> String { - let id: u32 = rng.gen(); - id.to_string() -} - /// Converter to byte array with endianness. pub trait ToBytes { /// Return the byte array of self. diff --git a/tests/common/constants.rs b/tests/common/constants.rs index 732059d2..fb33485a 100644 --- a/tests/common/constants.rs +++ b/tests/common/constants.rs @@ -1,6 +1,2 @@ -pub const ECHO_WS_SERVER: &'static str = "ws://ws.vi-server.org/mirror"; -pub const ECHO_WSS_SERVER: &'static str = "wss://ws.vi-server.org/mirror"; - -// pub const XRPL_TEST_NET: &'static str = "ws://s2.livenet.ripple.com/"; -pub const XRPL_WSS_TEST_NET: &'static str = "wss://testnet.xrpl-labs.com/"; -pub const XRPL_WS_TEST_NET: &'static str = "wss://s.altnet.rippletest.net:51233/"; +pub const ECHO_WS_SERVER: &'static str = "ws://ws.vi-server.org/mirror/"; +pub const ECHO_WSS_SERVER: &'static str = "wss://ws.vi-server.org/mirror/"; diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 4c451809..1c406bcd 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,64 +1,54 @@ -mod constants; +pub mod codec; -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -mod tungstenite_clients { - use super::constants::*; - use anyhow::anyhow; - use anyhow::Result; - use xrpl::asynch::clients::AsyncWebsocketClient; - use xrpl::asynch::clients::{SingleExecutorMutex, WebsocketOpen}; +use anyhow::anyhow; +use anyhow::Result; - pub async fn connect_to_wss_tungstinite_test_net( - ) -> Result> { - match XRPL_WSS_TEST_NET.parse() { - Ok(url) => match AsyncWebsocketClient::open(url).await { - Ok(websocket) => { - // assert!(websocket.is_open()); - Ok(websocket) - } - Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), - }, - Err(err) => Err(anyhow!("Error parsing url: {:?}", err)), - } - } -} +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +use tokio::net::TcpStream; +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +use tokio_util::codec::Framed; +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +use xrpl::asynch::clients::AsyncWebsocketClient; +use xrpl::asynch::clients::WebsocketOpen; +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +use xrpl::asynch::clients::{AsyncWebsocketClient, EmbeddedWebsocketOptions}; -#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] -mod embedded_ws_clients { - use super::constants::*; - use anyhow::anyhow; - use anyhow::Result; - use std::io; - use tokio::net::TcpStream; - use tokio_util::codec::Framed; - use xrpl::asynch::clients::codec::Codec; +mod constants; +pub use constants::*; - pub async fn connect_to_ws_embedded_websocket_tokio_echo( - stream: Framed, - ) -> Result< - AsyncWebsocketClient< - 4096, - Framed, - Vec, - io::Error, - rand_core::OsRng, - SingleExecutorMutex, - WebsocketOpen, - >, - > { - let rng = rand_core::OsRng; - let url = ECHO_WS_SERVER.parse().unwrap(); - match AsyncWebsocketClient::open(rng, stream, url).await { +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +pub async fn connect_to_wss_tungstinite_echo() -> Result> { + match ECHO_WSS_SERVER.parse() { + Ok(url) => match AsyncWebsocketClient::open(url).await { Ok(websocket) => { - // assert!(websocket.is_open()); + assert!(websocket.is_open()); Ok(websocket) } Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), - } + }, + Err(err) => Err(anyhow!("Error parsing url: {:?}", err)), } } -#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] -pub use embedded_ws_clients::*; -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -pub use tungstenite_clients::*; +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +pub async fn connect_to_ws_embedded_websocket_tokio_echo( + stream: &mut Framed, + buffer: &mut [u8], +) -> Result> { + let rng = rand::thread_rng(); + let websocket_options = EmbeddedWebsocketOptions { + path: "/mirror", + host: "ws.vi-server.org", + origin: "http://ws.vi-server.org:80", + sub_protocols: None, + additional_headers: None, + }; + + match AsyncWebsocketClient::open(stream, buffer, rng, &websocket_options).await { + Ok(websocket) => { + assert!(websocket.is_open()); + Ok(websocket) + } + Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), + } +} diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index d66e9d9d..9a76a87d 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -1,75 +1,100 @@ +use anyhow::anyhow; use anyhow::Result; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -pub async fn test_websocket_tungstenite_test_net() -> Result<()> { - use crate::common::connect_to_wss_tungstinite_test_net; - use xrpl::{ - asynch::clients::XRPLWebsocketIO, models::requests::Fee, models::results::FeeResult, - }; +pub async fn test_websocket_tungstenite_echo() -> Result<()> { + use super::common::connect_to_wss_tungstinite_echo; + use futures_util::{SinkExt, TryStreamExt}; + use xrpl::asynch::clients::TungsteniteMessage; + use xrpl::models::requests::AccountInfo; - let mut websocket = connect_to_wss_tungstinite_test_net().await?; - let fee = Fee::new(None); + let mut websocket = connect_to_wss_tungstinite_echo().await?; + let account_info = AccountInfo::new( + None, + "rJumr5e1HwiuV543H7bqixhtFreChWTaHH".into(), + None, + None, + None, + None, + None, + ); - websocket.xrpl_send(fee).await.unwrap(); - let message = websocket - .xrpl_receive::, Fee<'_>>() - .await - .unwrap(); - assert!(message.unwrap().result.is_some()); - Ok(()) -} + websocket.send(&account_info).await?; + while let Ok(Some(TungsteniteMessage::Text(response))) = websocket.try_next().await { + let account_info_echo = serde_json::from_str::(response.as_str()); + match account_info_echo { + Ok(account_info_echo) => { + assert_eq!(account_info, account_info_echo); + return Ok(()); + } + Err(err) => { + return Err(anyhow!("Error parsing response: {:?}", err)); + } + }; + } -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -pub async fn test_websocket_tungstenite_request() -> Result<()> { - use crate::common::connect_to_wss_tungstinite_test_net; - use xrpl::{asynch::clients::AsyncClient, models::requests::Fee, models::results::FeeResult}; - - let websocket = connect_to_wss_tungstinite_test_net().await?; - let fee = Fee::new(None); - - let message = websocket.request::, _>(fee).await.unwrap(); - assert!(message.result.is_some()); Ok(()) } -#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] pub async fn test_embedded_websocket_echo() -> Result<()> { - use crate::common::connect_to_ws_embedded_websocket_tokio_echo; + use super::common::{codec::Codec, connect_to_ws_embedded_websocket_tokio_echo}; use tokio_util::codec::Framed; - use xrpl::asynch::clients::codec::Codec; - use xrpl::asynch::clients::XRPLWebsocketIO; - use xrpl::models::requests::Fee; - use xrpl::models::results::FeeResult; + use xrpl::asynch::clients::EmbeddedWebsocketReadMessageType; + use xrpl::models::requests::AccountInfo; let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") .await - .unwrap(); - let framed = Framed::new(tcp_stream, Codec); - let mut websocket = connect_to_ws_embedded_websocket_tokio_echo(framed).await?; - let fee = Fee::new(None); - websocket.xrpl_send(fee).await?; - let _ = websocket - .xrpl_receive::, Fee<'_>>() - .await - .unwrap(); - Ok(()) -} - -#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] -pub async fn test_embedded_websocket_request() -> Result<()> { - use crate::common::connect_to_ws_embedded_websocket_tokio_echo; - use tokio_util::codec::Framed; - use xrpl::asynch::clients::codec::Codec; - use xrpl::asynch::clients::AsyncClient; - use xrpl::models::requests::Fee; - use xrpl::models::results::FeeResult; + .map_err(|_| anyhow!("Error connecting to websocket"))?; + let mut framed = Framed::new(tcp_stream, Codec::new()); + let mut buffer = [0u8; 4096]; + let mut websocket = + connect_to_ws_embedded_websocket_tokio_echo(&mut framed, &mut buffer).await?; + let account_info = AccountInfo::new( + None, + "rJumr5e1HwiuV543H7bqixhtFreChWTaHH".into(), + None, + None, + None, + None, + None, + ); + websocket + .send(&mut framed, &mut buffer, false, &account_info) + .await?; - let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") - .await - .unwrap(); - let framed = Framed::new(tcp_stream, Codec); - let websocket = connect_to_ws_embedded_websocket_tokio_echo(framed).await?; - let fee = Fee::new(None); - let _res = websocket.request::(fee).await?; - Ok(()) + let mut ping_counter = 0; + loop { + match websocket.try_next(&mut framed, &mut buffer).await? { + Some(message) => match message { + EmbeddedWebsocketReadMessageType::Ping(_) => { + ping_counter += 1; + if ping_counter > 1 { + return Err(anyhow!("Expected only one ping")); + } + } + EmbeddedWebsocketReadMessageType::Text(text) => { + match serde_json::from_str::(text) { + Ok(account_info_echo) => { + assert_eq!(account_info, account_info_echo); + return Ok(()); + } + Err(err) => { + return Err(anyhow!("Error parsing response: {:?}", err)); + } + } + } + EmbeddedWebsocketReadMessageType::Binary(_) => { + panic!("Expected text message found binary") + } + EmbeddedWebsocketReadMessageType::Pong(_) => { + panic!("Expected text message found pong") + } + EmbeddedWebsocketReadMessageType::Close(_) => { + panic!("Expected text message found close") + } + }, + None => return Err(anyhow!("No message received")), + } + } } diff --git a/tests/integration/mod.rs b/tests/integration/mod.rs index 705f46db..eb5264ce 100644 --- a/tests/integration/mod.rs +++ b/tests/integration/mod.rs @@ -1 +1,3 @@ +use super::common; + pub mod clients; diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 73d75eeb..179d20bf 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -5,24 +5,12 @@ mod integration; use anyhow::Result; -#[cfg(any(feature = "tungstenite", all(feature = "embedded-ws", feature = "std")))] #[tokio::test] async fn test_asynch_clients() -> Result<()> { #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] - return integration::clients::test_websocket_tungstenite_test_net().await; - #[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] + return integration::clients::test_websocket_tungstenite_echo().await; + #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] return integration::clients::test_embedded_websocket_echo().await; - #[allow(unreachable_code)] - Ok(()) -} - -#[cfg(any(feature = "tungstenite", feature = "embedded-ws", feature = "std"))] -#[tokio::test] -async fn test_asynch_clients_request() -> Result<()> { - #[cfg(all(feature = "tungstenite", feature = "std", not(feature = "embedded-ws")))] - return integration::clients::test_websocket_tungstenite_request().await; - #[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] - return integration::clients::test_embedded_websocket_request().await; - #[allow(unreachable_code)] + #[cfg(all(feature = "tungstenite", feature = "embedded-ws"))] Ok(()) } From b600b48b2029192c30bead591967c397d2f2acb0 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 15 Jun 2024 21:10:46 +0000 Subject: [PATCH 12/19] Merge remote-tracking branch 'origin/dev' into auto-fill-txn --- Cargo.toml | 18 +-- src/asynch/account/mod.rs | 10 +- src/asynch/clients/async_client.rs | 19 +++ src/asynch/clients/client.rs | 59 ++++++++ src/asynch/clients/mod.rs | 15 +- src/asynch/clients/websocket/codec.rs | 30 ++++ src/asynch/clients/websocket/exceptions.rs | 69 +++++++++ src/asynch/clients/websocket/tungstenite.rs | 13 -- src/asynch/ledger/mod.rs | 19 +-- src/asynch/transaction/mod.rs | 21 ++- src/models/requests/account_channels.rs | 12 +- src/models/requests/account_currencies.rs | 10 +- src/models/requests/account_info.rs | 10 +- src/models/requests/account_lines.rs | 10 +- src/models/requests/account_nfts.rs | 10 +- src/models/requests/account_objects.rs | 10 +- src/models/requests/account_offers.rs | 10 +- src/models/requests/account_tx.rs | 10 +- src/models/requests/book_offers.rs | 10 +- src/models/requests/channel_authorize.rs | 10 +- src/models/requests/channel_verify.rs | 10 +- src/models/requests/deposit_authorize.rs | 10 +- src/models/requests/fee.rs | 10 +- src/models/requests/gateway_balances.rs | 10 +- src/models/requests/ledger.rs | 10 +- src/models/requests/ledger_closed.rs | 10 +- src/models/requests/ledger_current.rs | 10 +- src/models/requests/ledger_data.rs | 10 +- src/models/requests/ledger_entry.rs | 10 +- src/models/requests/manifest.rs | 10 +- src/models/requests/mod.rs | 11 +- src/models/requests/nft_buy_offers.rs | 10 +- src/models/requests/nft_sell_offers.rs | 10 +- src/models/requests/no_ripple_check.rs | 10 +- src/models/requests/path_find.rs | 10 +- src/models/requests/ping.rs | 10 +- src/models/requests/random.rs | 10 +- src/models/requests/ripple_path_find.rs | 10 +- src/models/requests/server_info.rs | 10 +- src/models/requests/server_state.rs | 10 +- src/models/requests/submit.rs | 10 +- src/models/requests/submit_multisigned.rs | 10 +- src/models/requests/subscribe.rs | 10 +- src/models/requests/transaction_entry.rs | 10 +- src/models/requests/tx.rs | 10 +- src/models/requests/unsubscribe.rs | 10 +- src/models/results/mod.rs | 146 ++++++++++--------- src/utils/mod.rs | 9 ++ tests/common/mod.rs | 96 +++++++------ tests/integration/clients/mod.rs | 147 +++++++++----------- tests/integration_tests.rs | 2 +- 51 files changed, 676 insertions(+), 360 deletions(-) create mode 100644 src/asynch/clients/async_client.rs create mode 100644 src/asynch/clients/client.rs create mode 100644 src/asynch/clients/websocket/codec.rs create mode 100644 src/asynch/clients/websocket/exceptions.rs diff --git a/Cargo.toml b/Cargo.toml index 98d88022..be380d70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ chrono = { version = "0.4.19", default-features = false, features = [ "clock", ] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } -rand = { version = "0.8.4", default-features = false, features = ["getrandom"] } +rand = { version = "0.8.5", default-features = false, features = ["getrandom"] } serde = { version = "1.0.130", default-features = false, features = ["derive"] } serde_json = { version = "1.0.68", default-features = false, features = [ "alloc", @@ -54,16 +54,16 @@ serde_json = { version = "1.0.68", default-features = false, features = [ serde_with = "3.2.0" serde_repr = "0.1" zeroize = "1.5.7" -hashbrown = { version = "0.14.0", default-features = false, features = [ - "serde", -] } +hashbrown = { version = "0.14.5", features = ["serde"] } fnv = { version = "1.0.7", default-features = false } derive-new = { version = "0.5.9", default-features = false } thiserror-no-std = "2.0.2" anyhow = { version = "1.0.69", default-features = false } tokio = { version = "1.28.0", default-features = false, optional = true } url = { version = "2.2.2", default-features = false, optional = true } -futures = { version = "0.3.28", default-features = false, optional = true } +futures = { version = "0.3.28", default-features = false, features = [ + "alloc", +], optional = true } rand_core = { version = "0.6.4", default-features = false } tokio-tungstenite = { version = "0.20.0", optional = true } embassy-sync = { version = "0.6.0", default-features = false } @@ -86,8 +86,7 @@ cargo-husky = { version = "1.5.0", default-features = false, features = [ ] } tokio = { version = "1.28.0", features = ["full"] } tokio-util = { version = "0.7.7", features = ["codec"] } -bytes = { version = "1.4.0", default-features = false } -rand = { version = "0.8.4", default-features = false, features = [ +rand = { version = "0.8.5", default-features = false, features = [ "getrandom", "std", "std_rng", @@ -111,9 +110,9 @@ json-rpc-std = ["reqwest"] tungstenite = [ "url", "futures", - "tokio/full", + "tokio/net", "tokio-tungstenite/native-tls", - "futures-util/std", + "futures-util", ] embedded-ws = ["url", "futures", "embedded-websocket"] core = ["utils"] @@ -131,4 +130,5 @@ std = [ "serde/std", "indexmap/std", "secp256k1/std", + "dep:tokio-util", ] diff --git a/src/asynch/account/mod.rs b/src/asynch/account/mod.rs index b3528b64..12840fee 100644 --- a/src/asynch/account/mod.rs +++ b/src/asynch/account/mod.rs @@ -6,11 +6,11 @@ use crate::{ models::{ledger::AccountRoot, requests::AccountInfo, results}, }; -use super::clients::Client; +use super::clients::AsyncClient; pub async fn get_next_valid_seq_number<'a>( address: Cow<'a, str>, - client: &'a impl Client<'a>, + client: &'a impl AsyncClient<'a>, ledger_index: Option>, ) -> Result { let account_info = @@ -20,7 +20,7 @@ pub async fn get_next_valid_seq_number<'a>( pub async fn get_account_root<'a>( address: Cow<'a, str>, - client: &'a impl Client<'a>, + client: &'a impl AsyncClient<'a>, ledger_index: Cow<'a, str>, ) -> Result> { let mut classic_address = address; @@ -31,7 +31,7 @@ pub async fn get_account_root<'a>( .into(); } let account_info = client - .request::(AccountInfo::new( + .request::(AccountInfo::new( None, classic_address, None, @@ -42,5 +42,5 @@ pub async fn get_account_root<'a>( )) .await?; - Ok(account_info.result.account_data) + Ok(account_info.result.unwrap().account_data) } diff --git a/src/asynch/clients/async_client.rs b/src/asynch/clients/async_client.rs new file mode 100644 index 00000000..57a776ab --- /dev/null +++ b/src/asynch/clients/async_client.rs @@ -0,0 +1,19 @@ +use super::client::Client; +use crate::models::{requests::Request, results::XRPLResponse}; +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +#[allow(async_fn_in_trait)] +pub trait AsyncClient<'a>: Client<'a> { + async fn request< + Res: Serialize + for<'de> Deserialize<'de>, + Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, + >( + &'a self, + request: Req, + ) -> Result> { + self.request_impl(request).await + } +} + +impl<'a, T: Client<'a>> AsyncClient<'a> for T {} diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs new file mode 100644 index 00000000..d5021568 --- /dev/null +++ b/src/asynch/clients/client.rs @@ -0,0 +1,59 @@ +use crate::models::{ + requests::{Request, ServerState}, + results::{ServerState as ServerStateResult, XRPLResponse}, +}; +#[cfg(feature = "std")] +use crate::utils::get_random_id; +use alloc::borrow::Cow; +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +use super::CommonFields; + +#[allow(async_fn_in_trait)] +pub trait Client<'a> { + async fn request_impl< + Res: Serialize + for<'de> Deserialize<'de>, + Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, + >( + &'a self, + request: Req, + ) -> Result>; + + fn set_request_id< + Res: Serialize + for<'de> Deserialize<'de>, + Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, + >( + &'a self, + request: &mut Req, + ) -> Cow<'_, str> { + let common_fields = request.get_common_fields(); + let request_id: Cow<'_, str> = match common_fields.id.clone() { + Some(id) => id, + None => { + #[cfg(feature = "std")] + { + let mut rng = rand::thread_rng(); + Cow::Owned(get_random_id(&mut rng)) + } + #[cfg(not(feature = "std"))] + todo!("get_random_id is not yet implemented for no_std. Please provide an `id` in the request."); + } + }; + request.get_common_fields_mut().id = Some(request_id.clone()); + request_id + } + + async fn get_common_fields(&'a self) -> Result> { + let server_state = self + .request_impl::(ServerState::new(None)) + .await?; + let state = server_state.result.unwrap().state; + let common_fields = CommonFields { + network_id: state.network_id, + build_version: Some(state.build_version), + }; + + Ok(common_fields) + } +} diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index 9f0fba41..5bcb65ec 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -3,11 +3,20 @@ mod client; mod json_rpc; mod websocket; +use alloc::borrow::Cow; use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; -pub type MultiExecutorMutex = CriticalSectionRawMutex; -pub type SingleExecutorMutex = NoopRawMutex; - pub use async_client::*; +pub(crate) use client::*; pub use json_rpc::*; +use serde::{Deserialize, Serialize}; pub use websocket::*; + +pub type MultiExecutorMutex = CriticalSectionRawMutex; +pub type SingleExecutorMutex = NoopRawMutex; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CommonFields<'a> { + pub build_version: Option>, + pub network_id: Option, +} diff --git a/src/asynch/clients/websocket/codec.rs b/src/asynch/clients/websocket/codec.rs new file mode 100644 index 00000000..95e9b09b --- /dev/null +++ b/src/asynch/clients/websocket/codec.rs @@ -0,0 +1,30 @@ +use alloc::{io, vec::Vec}; +use bytes::{BufMut, BytesMut}; +use tokio_util::codec::{Decoder, Encoder}; + +pub struct Codec; + +impl Decoder for Codec { + type Item = Vec; + type Error = io::Error; + + fn decode(&mut self, src: &mut BytesMut) -> Result>, io::Error> { + if !src.is_empty() { + let len = src.len(); + let data = src.split_to(len).to_vec(); + Ok(Some(data)) + } else { + Ok(None) + } + } +} + +impl Encoder<&[u8]> for Codec { + type Error = io::Error; + + fn encode(&mut self, data: &[u8], buf: &mut BytesMut) -> Result<(), io::Error> { + buf.reserve(data.len()); + buf.put(data); + Ok(()) + } +} diff --git a/src/asynch/clients/websocket/exceptions.rs b/src/asynch/clients/websocket/exceptions.rs new file mode 100644 index 00000000..88b1161a --- /dev/null +++ b/src/asynch/clients/websocket/exceptions.rs @@ -0,0 +1,69 @@ +use core::fmt::Debug; +use core::str::Utf8Error; +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +use embedded_io_async::{Error as EmbeddedIoError, ErrorKind}; +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +use embedded_websocket::framer_async::FramerError; +use thiserror_no_std::Error; + +#[derive(Debug, Error)] +pub enum XRPLWebsocketException { + #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] + #[error("Unable to connect to websocket")] + UnableToConnect(tokio_tungstenite::tungstenite::Error), + // FramerError + #[error("I/O error: {0:?}")] + Io(E), + #[error("Frame too large (size: {0:?})")] + FrameTooLarge(usize), + #[error("Failed to interpret u8 to string (error: {0:?})")] + Utf8(Utf8Error), + #[error("Invalid HTTP header")] + HttpHeader, + #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] + #[error("Websocket error: {0:?}")] + WebSocket(embedded_websocket::Error), + #[error("Disconnected")] + Disconnected, + #[error("Read buffer is too small (size: {0:?})")] + RxBufferTooSmall(usize), + #[error("Unexpected message type")] + UnexpectedMessageType, + #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] + #[error("Embedded I/O error: {0:?}")] + EmbeddedIoError(ErrorKind), + #[error("Missing request channel sender.")] + MissingRequestSender, + #[error("Missing request channel receiver.")] + MissingRequestReceiver, + #[error("Invalid message.")] + InvalidMessage, +} + +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +impl From> for XRPLWebsocketException { + fn from(value: FramerError) -> Self { + match value { + FramerError::Io(e) => XRPLWebsocketException::Io(e), + FramerError::FrameTooLarge(e) => XRPLWebsocketException::FrameTooLarge(e), + FramerError::Utf8(e) => XRPLWebsocketException::Utf8(e), + FramerError::HttpHeader(_) => XRPLWebsocketException::HttpHeader, + FramerError::WebSocket(e) => XRPLWebsocketException::WebSocket(e), + FramerError::Disconnected => XRPLWebsocketException::Disconnected, + FramerError::RxBufferTooSmall(e) => XRPLWebsocketException::RxBufferTooSmall(e), + } + } +} + +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +impl EmbeddedIoError for XRPLWebsocketException { + fn kind(&self) -> ErrorKind { + match self { + XRPLWebsocketException::EmbeddedIoError(e) => e.kind(), + _ => ErrorKind::Other, + } + } +} + +#[cfg(feature = "std")] +impl alloc::error::Error for XRPLWebsocketException {} diff --git a/src/asynch/clients/websocket/tungstenite.rs b/src/asynch/clients/websocket/tungstenite.rs index acb0c4a3..3692bea1 100644 --- a/src/asynch/clients/websocket/tungstenite.rs +++ b/src/asynch/clients/websocket/tungstenite.rs @@ -250,17 +250,4 @@ where } } } - - // async fn get_common_fields(&self) -> Result> { - // let server_state = self - // .request::(requests::ServerState::new(None)) - // .await?; - // let state = server_state.result.state; - // let common_fields = CommonFields { - // network_id: state.network_id, - // build_version: Some(state.build_version), - // }; - - // Ok(common_fields) - // } } diff --git a/src/asynch/ledger/mod.rs b/src/asynch/ledger/mod.rs index 355c422f..75994ff3 100644 --- a/src/asynch/ledger/mod.rs +++ b/src/asynch/ledger/mod.rs @@ -7,16 +7,17 @@ use crate::models::{ amount::XRPAmount, requests::{Fee, Ledger}, results::{ - self, - fee::{Drops, Fee as FeeResult}, + self, {Drops, Fee as FeeResult}, }, }; -use super::clients::Client; +use super::clients::AsyncClient; -pub async fn get_latest_validated_ledger_sequence<'a>(client: &'a impl Client<'a>) -> Result { +pub async fn get_latest_validated_ledger_sequence<'a>( + client: &'a impl AsyncClient<'a>, +) -> Result { let ledger_response = client - .request::(Ledger::new( + .request::(Ledger::new( None, None, None, @@ -30,7 +31,7 @@ pub async fn get_latest_validated_ledger_sequence<'a>(client: &'a impl Client<'a )) .await?; - Ok(ledger_response.result.ledger_index) + Ok(ledger_response.result.unwrap().ledger_index) } pub enum FeeType { @@ -40,14 +41,14 @@ pub enum FeeType { } pub async fn get_fee<'a>( - client: &'a impl Client<'a>, + client: &'a impl AsyncClient<'a>, max_fee: Option, fee_type: Option, ) -> Result> { let fee_request = Fee::new(None); - match client.request::>(fee_request).await { + match client.request::, _>(fee_request).await { Ok(response) => { - let drops = response.result.drops; + let drops = response.result.unwrap().drops; let fee = match_fee_type(fee_type, drops).unwrap(); if let Some(max_fee) = max_fee { diff --git a/src/asynch/transaction/mod.rs b/src/asynch/transaction/mod.rs index c324ca13..8f3ba49b 100644 --- a/src/asynch/transaction/mod.rs +++ b/src/asynch/transaction/mod.rs @@ -13,14 +13,14 @@ use strum::IntoEnumIterator; use crate::models::amount::XRPAmount; use crate::models::exceptions::XRPLModelException; use crate::models::requests::ServerState; -use crate::models::results::server_state::ServerState as ServerStateResult; +use crate::models::results::ServerState as ServerStateResult; use crate::models::transactions::Transaction; use crate::models::transactions::TransactionType; use crate::models::Model; use crate::Err; use super::account::get_next_valid_seq_number; -use super::clients::Client; +use super::clients::AsyncClient; use super::clients::CommonFields; use super::ledger::get_fee; use super::ledger::get_latest_validated_ledger_sequence; @@ -34,7 +34,7 @@ const LEDGER_OFFSET: u8 = 20; pub async fn autofill<'a, F, T>( transaction: &'a mut T, - client: &'a impl Client<'a>, + client: &'a impl AsyncClient<'a>, signers_count: Option, ) -> Result<()> where @@ -65,7 +65,7 @@ where async fn calculate_fee_per_transaction_type<'a, T, F>( transaction: T, - client: Option<&'a impl Client<'a>>, + client: Option<&'a impl AsyncClient<'a>>, signers_count: Option, ) -> Result> where @@ -105,11 +105,18 @@ where Ok(base_fee.ceil()) } -async fn get_owner_reserve_from_response<'a>(client: &'a impl Client<'a>) -> Result> { +async fn get_owner_reserve_from_response<'a>( + client: &'a impl AsyncClient<'a>, +) -> Result> { let owner_reserve_response = client - .request::>(ServerState::new(None)) + .request::, _>(ServerState::new(None)) .await?; - match owner_reserve_response.result.state.validated_ledger { + match owner_reserve_response + .result + .unwrap() + .state + .validated_ledger + { Some(validated_ledger) => Ok(validated_ledger.reserve_base), None => Err!(XRPLModelException::MissingField("validated_ledger")), } diff --git a/src/models/requests/account_channels.rs b/src/models/requests/account_channels.rs index 5965eb52..df0753ed 100644 --- a/src/models/requests/account_channels.rs +++ b/src/models/requests/account_channels.rs @@ -4,7 +4,7 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; -use super::CommonFields; +use super::{CommonFields, Request}; /// This request returns information about an account's Payment /// Channels. This includes only channels where the specified @@ -85,3 +85,13 @@ impl<'a> AccountChannels<'a> { } } } + +impl<'a> Request<'a> for AccountChannels<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields + } +} diff --git a/src/models/requests/account_currencies.rs b/src/models/requests/account_currencies.rs index c6307543..4ff2e373 100644 --- a/src/models/requests/account_currencies.rs +++ b/src/models/requests/account_currencies.rs @@ -37,9 +37,13 @@ pub struct AccountCurrencies<'a> { impl<'a> Model for AccountCurrencies<'a> {} -impl<'a> Request for AccountCurrencies<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountCurrencies<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/account_info.rs b/src/models/requests/account_info.rs index 1db06aa4..84d8f42d 100644 --- a/src/models/requests/account_info.rs +++ b/src/models/requests/account_info.rs @@ -44,9 +44,13 @@ pub struct AccountInfo<'a> { impl<'a> Model for AccountInfo<'a> {} -impl<'a> Request for AccountInfo<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountInfo<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/account_lines.rs b/src/models/requests/account_lines.rs index 432f74d4..3571cf2a 100644 --- a/src/models/requests/account_lines.rs +++ b/src/models/requests/account_lines.rs @@ -38,9 +38,13 @@ pub struct AccountLines<'a> { impl<'a> Model for AccountLines<'a> {} -impl<'a> Request for AccountLines<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountLines<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/account_nfts.rs b/src/models/requests/account_nfts.rs index 2447f3af..a9e79047 100644 --- a/src/models/requests/account_nfts.rs +++ b/src/models/requests/account_nfts.rs @@ -29,9 +29,13 @@ pub struct AccountNfts<'a> { impl<'a> Model for AccountNfts<'a> {} -impl<'a> Request for AccountNfts<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountNfts<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/account_objects.rs b/src/models/requests/account_objects.rs index c51bcc4f..d78c4381 100644 --- a/src/models/requests/account_objects.rs +++ b/src/models/requests/account_objects.rs @@ -63,9 +63,13 @@ pub struct AccountObjects<'a> { impl<'a> Model for AccountObjects<'a> {} -impl<'a> Request for AccountObjects<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountObjects<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/account_offers.rs b/src/models/requests/account_offers.rs index 5e2cfb02..647d3f3e 100644 --- a/src/models/requests/account_offers.rs +++ b/src/models/requests/account_offers.rs @@ -40,9 +40,13 @@ pub struct AccountOffers<'a> { impl<'a> Model for AccountOffers<'a> {} -impl<'a> Request for AccountOffers<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountOffers<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/account_tx.rs b/src/models/requests/account_tx.rs index 48da5afd..1f16e80a 100644 --- a/src/models/requests/account_tx.rs +++ b/src/models/requests/account_tx.rs @@ -53,9 +53,13 @@ pub struct AccountTx<'a> { impl<'a> Model for AccountTx<'a> {} -impl<'a> Request for AccountTx<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountTx<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/book_offers.rs b/src/models/requests/book_offers.rs index 57887d38..ca27a3d5 100644 --- a/src/models/requests/book_offers.rs +++ b/src/models/requests/book_offers.rs @@ -46,9 +46,13 @@ pub struct BookOffers<'a> { impl<'a> Model for BookOffers<'a> {} -impl<'a> Request for BookOffers<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for BookOffers<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/channel_authorize.rs b/src/models/requests/channel_authorize.rs index 835cdd7e..466fa425 100644 --- a/src/models/requests/channel_authorize.rs +++ b/src/models/requests/channel_authorize.rs @@ -80,9 +80,13 @@ impl<'a> Model for ChannelAuthorize<'a> { } } -impl<'a> Request for ChannelAuthorize<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for ChannelAuthorize<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/channel_verify.rs b/src/models/requests/channel_verify.rs index 949b6ad1..59e814a9 100644 --- a/src/models/requests/channel_verify.rs +++ b/src/models/requests/channel_verify.rs @@ -30,9 +30,13 @@ pub struct ChannelVerify<'a> { impl<'a> Model for ChannelVerify<'a> {} -impl<'a> Request for ChannelVerify<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for ChannelVerify<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/deposit_authorize.rs b/src/models/requests/deposit_authorize.rs index 5204bc69..2c58e473 100644 --- a/src/models/requests/deposit_authorize.rs +++ b/src/models/requests/deposit_authorize.rs @@ -30,9 +30,13 @@ pub struct DepositAuthorized<'a> { impl<'a> Model for DepositAuthorized<'a> {} -impl<'a> Request for DepositAuthorized<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for DepositAuthorized<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/fee.rs b/src/models/requests/fee.rs index 2ab0ee8a..05052d96 100644 --- a/src/models/requests/fee.rs +++ b/src/models/requests/fee.rs @@ -23,9 +23,13 @@ pub struct Fee<'a> { impl<'a> Model for Fee<'a> {} -impl<'a> Request for Fee<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Fee<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/gateway_balances.rs b/src/models/requests/gateway_balances.rs index 58dc61e7..e5f38386 100644 --- a/src/models/requests/gateway_balances.rs +++ b/src/models/requests/gateway_balances.rs @@ -36,9 +36,13 @@ pub struct GatewayBalances<'a> { impl<'a> Model for GatewayBalances<'a> {} -impl<'a> Request for GatewayBalances<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for GatewayBalances<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ledger.rs b/src/models/requests/ledger.rs index 7ba5faec..d567cd41 100644 --- a/src/models/requests/ledger.rs +++ b/src/models/requests/ledger.rs @@ -58,9 +58,13 @@ pub struct Ledger<'a> { impl<'a> Model for Ledger<'a> {} -impl<'a> Request for Ledger<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Ledger<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ledger_closed.rs b/src/models/requests/ledger_closed.rs index c65f486c..7b601ea6 100644 --- a/src/models/requests/ledger_closed.rs +++ b/src/models/requests/ledger_closed.rs @@ -22,9 +22,13 @@ pub struct LedgerClosed<'a> { impl<'a> Model for LedgerClosed<'a> {} -impl<'a> Request for LedgerClosed<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for LedgerClosed<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ledger_current.rs b/src/models/requests/ledger_current.rs index 9b408ee6..aec482f6 100644 --- a/src/models/requests/ledger_current.rs +++ b/src/models/requests/ledger_current.rs @@ -22,9 +22,13 @@ pub struct LedgerCurrent<'a> { impl<'a> Model for LedgerCurrent<'a> {} -impl<'a> Request for LedgerCurrent<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for LedgerCurrent<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ledger_data.rs b/src/models/requests/ledger_data.rs index c71e012c..c30bb753 100644 --- a/src/models/requests/ledger_data.rs +++ b/src/models/requests/ledger_data.rs @@ -36,9 +36,13 @@ pub struct LedgerData<'a> { impl<'a> Model for LedgerData<'a> {} -impl<'a> Request for LedgerData<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for LedgerData<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ledger_entry.rs b/src/models/requests/ledger_entry.rs index 1dbc4c1d..0022437c 100644 --- a/src/models/requests/ledger_entry.rs +++ b/src/models/requests/ledger_entry.rs @@ -159,9 +159,13 @@ impl<'a> LedgerEntryError for LedgerEntry<'a> { } } -impl<'a> Request for LedgerEntry<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for LedgerEntry<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/manifest.rs b/src/models/requests/manifest.rs index d2615195..e9f38d94 100644 --- a/src/models/requests/manifest.rs +++ b/src/models/requests/manifest.rs @@ -27,9 +27,13 @@ pub struct Manifest<'a> { impl<'a> Model for Manifest<'a> {} -impl<'a> Request for Manifest<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Manifest<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/mod.rs b/src/models/requests/mod.rs index 860b7504..3d81058f 100644 --- a/src/models/requests/mod.rs +++ b/src/models/requests/mod.rs @@ -148,14 +148,9 @@ pub struct CommonFields<'a> { pub id: Option>, } -impl Request for CommonFields<'_> { - fn get_command(&self) -> RequestMethod { - self.command.clone() - } -} - /// The base trait for all request models. /// Used to identify the model as a request. -pub trait Request { - fn get_command(&self) -> RequestMethod; +pub trait Request<'a> { + fn get_common_fields(&self) -> &CommonFields<'a>; + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a>; } diff --git a/src/models/requests/nft_buy_offers.rs b/src/models/requests/nft_buy_offers.rs index 3e9ce276..d8a4c4c5 100644 --- a/src/models/requests/nft_buy_offers.rs +++ b/src/models/requests/nft_buy_offers.rs @@ -31,9 +31,13 @@ pub struct NftBuyOffers<'a> { impl<'a> Model for NftBuyOffers<'a> {} -impl<'a> Request for NftBuyOffers<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for NftBuyOffers<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/nft_sell_offers.rs b/src/models/requests/nft_sell_offers.rs index 7404d59c..fa4dd6f9 100644 --- a/src/models/requests/nft_sell_offers.rs +++ b/src/models/requests/nft_sell_offers.rs @@ -19,9 +19,13 @@ pub struct NftSellOffers<'a> { impl<'a> Model for NftSellOffers<'a> {} -impl<'a> Request for NftSellOffers<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for NftSellOffers<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/no_ripple_check.rs b/src/models/requests/no_ripple_check.rs index f2467efa..21bffb7f 100644 --- a/src/models/requests/no_ripple_check.rs +++ b/src/models/requests/no_ripple_check.rs @@ -58,9 +58,13 @@ pub struct NoRippleCheck<'a> { impl<'a> Model for NoRippleCheck<'a> {} -impl<'a> Request for NoRippleCheck<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for NoRippleCheck<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/path_find.rs b/src/models/requests/path_find.rs index e2ab7074..55b34865 100644 --- a/src/models/requests/path_find.rs +++ b/src/models/requests/path_find.rs @@ -90,9 +90,13 @@ pub struct PathFind<'a> { impl<'a> Model for PathFind<'a> {} -impl<'a> Request for PathFind<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for PathFind<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ping.rs b/src/models/requests/ping.rs index 079b81be..5c70e863 100644 --- a/src/models/requests/ping.rs +++ b/src/models/requests/ping.rs @@ -21,9 +21,13 @@ pub struct Ping<'a> { impl<'a> Model for Ping<'a> {} -impl<'a> Request for Ping<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Ping<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/random.rs b/src/models/requests/random.rs index 8bc0029f..19c7e986 100644 --- a/src/models/requests/random.rs +++ b/src/models/requests/random.rs @@ -22,9 +22,13 @@ pub struct Random<'a> { impl<'a> Model for Random<'a> {} -impl<'a> Request for Random<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Random<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ripple_path_find.rs b/src/models/requests/ripple_path_find.rs index ac923963..4f7f5cfd 100644 --- a/src/models/requests/ripple_path_find.rs +++ b/src/models/requests/ripple_path_find.rs @@ -62,9 +62,13 @@ pub struct RipplePathFind<'a> { impl<'a> Model for RipplePathFind<'a> {} -impl<'a> Request for RipplePathFind<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for RipplePathFind<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/server_info.rs b/src/models/requests/server_info.rs index 378fc0f0..7044ee5d 100644 --- a/src/models/requests/server_info.rs +++ b/src/models/requests/server_info.rs @@ -22,9 +22,13 @@ pub struct ServerInfo<'a> { impl<'a> Model for ServerInfo<'a> {} -impl<'a> Request for ServerInfo<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for ServerInfo<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/server_state.rs b/src/models/requests/server_state.rs index 4a0b7306..85c910c5 100644 --- a/src/models/requests/server_state.rs +++ b/src/models/requests/server_state.rs @@ -27,9 +27,13 @@ pub struct ServerState<'a> { impl<'a> Model for ServerState<'a> {} -impl<'a> Request for ServerState<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for ServerState<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/submit.rs b/src/models/requests/submit.rs index 132a9910..e68dec41 100644 --- a/src/models/requests/submit.rs +++ b/src/models/requests/submit.rs @@ -49,9 +49,13 @@ pub struct Submit<'a> { impl<'a> Model for Submit<'a> {} -impl<'a> Request for Submit<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Submit<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/submit_multisigned.rs b/src/models/requests/submit_multisigned.rs index 82fdc1cc..913539c9 100644 --- a/src/models/requests/submit_multisigned.rs +++ b/src/models/requests/submit_multisigned.rs @@ -30,9 +30,13 @@ pub struct SubmitMultisigned<'a> { impl<'a> Model for SubmitMultisigned<'a> {} -impl<'a> Request for SubmitMultisigned<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for SubmitMultisigned<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/subscribe.rs b/src/models/requests/subscribe.rs index 0487ca22..fb501f87 100644 --- a/src/models/requests/subscribe.rs +++ b/src/models/requests/subscribe.rs @@ -77,9 +77,13 @@ pub struct Subscribe<'a> { impl<'a> Model for Subscribe<'a> {} -impl<'a> Request for Subscribe<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Subscribe<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/transaction_entry.rs b/src/models/requests/transaction_entry.rs index 24ad02cb..8f3c9885 100644 --- a/src/models/requests/transaction_entry.rs +++ b/src/models/requests/transaction_entry.rs @@ -31,9 +31,13 @@ pub struct TransactionEntry<'a> { impl<'a> Model for TransactionEntry<'a> {} -impl<'a> Request for TransactionEntry<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for TransactionEntry<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/tx.rs b/src/models/requests/tx.rs index 0dd547ee..29b88373 100644 --- a/src/models/requests/tx.rs +++ b/src/models/requests/tx.rs @@ -34,9 +34,13 @@ pub struct Tx<'a> { impl<'a> Model for Tx<'a> {} -impl<'a> Request for Tx<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Tx<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/unsubscribe.rs b/src/models/requests/unsubscribe.rs index 90f123d0..b056b688 100644 --- a/src/models/requests/unsubscribe.rs +++ b/src/models/requests/unsubscribe.rs @@ -62,9 +62,13 @@ pub struct Unsubscribe<'a> { impl<'a> Model for Unsubscribe<'a> {} -impl<'a> Request for Unsubscribe<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Unsubscribe<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs index 9bb7db6d..ae5db19e 100644 --- a/src/models/results/mod.rs +++ b/src/models/results/mod.rs @@ -1,29 +1,25 @@ -pub mod account_info; -pub mod fee; -pub mod ledger; -pub mod server_state; +mod account_info; +mod fee; +mod ledger; +mod server_state; pub use account_info::*; pub use fee::*; pub use ledger::*; pub use server_state::*; -use alloc::{borrow::Cow, vec::Vec}; +use alloc::{borrow::Cow, string::ToString, vec::Vec}; use anyhow::Result; -use futures::{Stream, StreamExt}; -use serde::{Deserialize, Serialize}; -use serde_json::Value; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use crate::Err; - -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum ResponseStatus { Success, Error, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub enum ResponseType { Response, @@ -31,83 +27,85 @@ pub enum ResponseType { Transaction, } -pub trait XRPLResponseFromStream Deserialize<'de>>: - StreamExt> -{ - async fn next_xrpl_response(&mut self) -> Option>> - where - Self: Unpin; - - async fn try_next_xrpl_response(&mut self) -> Result>> - where - Self: Unpin, - { - match self.next_xrpl_response().await { - Some(response) => response.map(Some), - None => Ok(None), - } - } +#[derive(Debug, Clone, Serialize, PartialEq, Eq)] +pub struct XRPLResponse<'a, Res, Req> { + pub id: Option>, + pub error: Option>, + pub error_code: Option, + pub error_message: Option>, + pub forwarded: Option, + pub request: Option, + pub result: Option, + pub status: Option, + pub r#type: Option, + pub warning: Option>, + pub warnings: Option>>, } -impl XRPLResponseFromStream for S +impl<'a, 'de, Res, Req> Deserialize<'de> for XRPLResponse<'a, Res, Req> where - S: Stream> + StreamExt> + Unpin, - T: for<'de> Deserialize<'de>, + Res: DeserializeOwned, + Req: DeserializeOwned, { - async fn next_xrpl_response(&mut self) -> Option>> + fn deserialize(deserializer: D) -> Result where - Self: StreamExt>, + D: serde::Deserializer<'de>, { - let item = self.next().await; - match item { - Some(Ok(message)) => match serde_json::from_value(message) { - Ok(response) => Some(Ok(response)), - Err(error) => Some(Err!(error)), - }, - Some(Err(error)) => Some(Err!(error)), - None => None, + // TODO: add validation for fields that can not coexist in the same response + let mut map = serde_json::Map::deserialize(deserializer)?; + if map.is_empty() { + return Err(serde::de::Error::custom("Empty response")); } + Ok(XRPLResponse { + id: map.remove("id").map(|item| match item.as_str() { + Some(item_str) => Cow::Owned(item_str.to_string()), + None => Cow::Borrowed(""), + }), + error: map.remove("error").map(|item| match item.as_str() { + Some(item_str) => Cow::Owned(item_str.to_string()), + None => Cow::Borrowed(""), + }), + error_code: map + .remove("error_code") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + error_message: map.remove("error_message").map(|item| match item.as_str() { + Some(item_str) => Cow::Owned(item_str.to_string()), + None => Cow::Borrowed(""), + }), + forwarded: map.remove("forwarded").and_then(|v| v.as_bool()), + request: map + .remove("request") + .map(|v| serde_json::from_value(v).unwrap()), + result: map + .remove("result") + .map(|v| serde_json::from_value(v).unwrap()), + status: map + .remove("status") + .map(|v| serde_json::from_value(v).unwrap()), + r#type: map + .remove("type") + .map(|v| serde_json::from_value(v).unwrap()), + warning: map.remove("warning").map(|item| match item.as_str() { + Some(item_str) => Cow::Owned(item_str.to_string()), + None => Cow::Borrowed(""), + }), + warnings: map + .remove("warnings") + .and_then(|v| serde_json::from_value(v).ok()), + }) } } -/// A response from a XRPL node. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct XRPLResponse<'a, T> { - pub id: Option>, - pub result: T, - pub status: Option, - pub r#type: Option, - pub forwarded: Option, - pub warnings: Option>>, - pub warning: Option>, -} - -impl XRPLResponse<'_, T> { - pub fn is_successful(&self) -> bool { - match self.status { - Some(ResponseStatus::Success) => true, - _ => false, - } +impl<'a, Res, Req> XRPLResponse<'a, Res, Req> { + pub fn is_success(&self) -> bool { + self.status == Some(ResponseStatus::Success) } } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct XRPLErrorResponse<'a, T> { - pub id: Cow<'a, str>, - pub error: Option>, - pub error_code: Option, - pub error_message: Option>, - pub request: Option, - pub status: Option, - pub r#type: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct XRPLWarning<'a> { pub id: Cow<'a, str>, pub message: Cow<'a, str>, pub forwarded: Option, } - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct EmptyResult; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 41508f7c..9fff8a72 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -8,7 +8,10 @@ pub use self::time_conversion::*; pub use self::xrpl_conversion::*; use crate::constants::*; +use alloc::string::String; +use alloc::string::ToString; use alloc::vec::Vec; +use rand::Rng; use regex::Regex; /// Determine if the address string is a hex address. @@ -65,6 +68,12 @@ pub fn is_iso_hex(value: &str) -> bool { regex.is_match(value) } +/// Generate a random id. +pub fn get_random_id(rng: &mut T) -> String { + let id: u32 = rng.gen(); + id.to_string() +} + /// Converter to byte array with endianness. pub trait ToBytes { /// Return the byte array of self. diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 1c406bcd..4c451809 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,54 +1,64 @@ -pub mod codec; - -use anyhow::anyhow; -use anyhow::Result; +mod constants; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -use tokio::net::TcpStream; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -use tokio_util::codec::Framed; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -use xrpl::asynch::clients::AsyncWebsocketClient; -use xrpl::asynch::clients::WebsocketOpen; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -use xrpl::asynch::clients::{AsyncWebsocketClient, EmbeddedWebsocketOptions}; +mod tungstenite_clients { + use super::constants::*; + use anyhow::anyhow; + use anyhow::Result; + use xrpl::asynch::clients::AsyncWebsocketClient; + use xrpl::asynch::clients::{SingleExecutorMutex, WebsocketOpen}; -mod constants; -pub use constants::*; + pub async fn connect_to_wss_tungstinite_test_net( + ) -> Result> { + match XRPL_WSS_TEST_NET.parse() { + Ok(url) => match AsyncWebsocketClient::open(url).await { + Ok(websocket) => { + // assert!(websocket.is_open()); + Ok(websocket) + } + Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), + }, + Err(err) => Err(anyhow!("Error parsing url: {:?}", err)), + } + } +} -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -pub async fn connect_to_wss_tungstinite_echo() -> Result> { - match ECHO_WSS_SERVER.parse() { - Ok(url) => match AsyncWebsocketClient::open(url).await { +#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] +mod embedded_ws_clients { + use super::constants::*; + use anyhow::anyhow; + use anyhow::Result; + use std::io; + use tokio::net::TcpStream; + use tokio_util::codec::Framed; + use xrpl::asynch::clients::codec::Codec; + + pub async fn connect_to_ws_embedded_websocket_tokio_echo( + stream: Framed, + ) -> Result< + AsyncWebsocketClient< + 4096, + Framed, + Vec, + io::Error, + rand_core::OsRng, + SingleExecutorMutex, + WebsocketOpen, + >, + > { + let rng = rand_core::OsRng; + let url = ECHO_WS_SERVER.parse().unwrap(); + match AsyncWebsocketClient::open(rng, stream, url).await { Ok(websocket) => { - assert!(websocket.is_open()); + // assert!(websocket.is_open()); Ok(websocket) } Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), - }, - Err(err) => Err(anyhow!("Error parsing url: {:?}", err)), - } -} - -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -pub async fn connect_to_ws_embedded_websocket_tokio_echo( - stream: &mut Framed, - buffer: &mut [u8], -) -> Result> { - let rng = rand::thread_rng(); - let websocket_options = EmbeddedWebsocketOptions { - path: "/mirror", - host: "ws.vi-server.org", - origin: "http://ws.vi-server.org:80", - sub_protocols: None, - additional_headers: None, - }; - - match AsyncWebsocketClient::open(stream, buffer, rng, &websocket_options).await { - Ok(websocket) => { - assert!(websocket.is_open()); - Ok(websocket) } - Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), } } + +#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] +pub use embedded_ws_clients::*; +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +pub use tungstenite_clients::*; diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index 25ea0ed9..9dedd232 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -1,102 +1,79 @@ -use anyhow::anyhow; use anyhow::Result; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -pub async fn test_websocket_tungstenite_echo() -> Result<()> { - use super::common::connect_to_wss_tungstinite_echo; - use futures_util::{SinkExt, TryStreamExt}; - use xrpl::asynch::clients::TungsteniteMessage; - use xrpl::models::requests::AccountInfo; +pub async fn test_websocket_tungstenite_test_net() -> Result<()> { + use crate::common::connect_to_wss_tungstinite_test_net; + use xrpl::{ + asynch::clients::XRPLWebsocketIO, models::requests::Fee, models::results::Fee as FeeResult, + }; + + let mut websocket = connect_to_wss_tungstinite_test_net().await?; + let fee = Fee::new(None); - let mut websocket = connect_to_wss_tungstinite_echo().await?; - let account_info = AccountInfo::new( - None, - "rJumr5e1HwiuV543H7bqixhtFreChWTaHH".into(), - None, - None, - None, - None, - None, - ); + websocket.xrpl_send(fee).await.unwrap(); + let message = websocket + .xrpl_receive::, Fee<'_>>() + .await + .unwrap(); + assert!(message.unwrap().result.is_some()); + Ok(()) +} + +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +pub async fn test_websocket_tungstenite_request() -> Result<()> { + use crate::common::connect_to_wss_tungstinite_test_net; + use xrpl::{ + asynch::clients::AsyncClient, models::requests::Fee, models::results::Fee as FeeResult, + }; - websocket.send(&account_info).await?; - while let Ok(Some(TungsteniteMessage::Text(response))) = websocket.try_next().await { - let account_info_echo = serde_json::from_str::(response.as_str()); - match account_info_echo { - Ok(account_info_echo) => { - assert_eq!(account_info, account_info_echo); - return Ok(()); - } - Err(err) => { - return Err(anyhow!("Error parsing response: {:?}", err)); - } - }; - } + let websocket = connect_to_wss_tungstinite_test_net().await?; + let fee = Fee::new(None); + let message = websocket.request::, _>(fee).await.unwrap(); + assert!(message.result.is_some()); Ok(()) } -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] pub async fn test_embedded_websocket_echo() -> Result<()> { - use super::common::{codec::Codec, connect_to_ws_embedded_websocket_tokio_echo}; + use crate::common::connect_to_ws_embedded_websocket_tokio_echo; use tokio_util::codec::Framed; - use xrpl::asynch::clients::EmbeddedWebsocketReadMessageType; - use xrpl::models::requests::AccountInfo; + use xrpl::asynch::clients::codec::Codec; + use xrpl::asynch::clients::XRPLWebsocketIO; + use xrpl::models::requests::Fee; + use xrpl::models::results::FeeResult; let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") .await - .map_err(|_| anyhow!("Error connecting to websocket"))?; - let mut framed = Framed::new(tcp_stream, Codec::new()); - let mut buffer = [0u8; 4096]; - let mut websocket = - connect_to_ws_embedded_websocket_tokio_echo(&mut framed, &mut buffer).await?; - let account_info = AccountInfo::new( - None, - "rJumr5e1HwiuV543H7bqixhtFreChWTaHH".into(), - None, - None, - None, - None, - None, - ); - websocket - .send(&mut framed, &mut buffer, false, &account_info) - .await?; + .unwrap(); + let framed = Framed::new(tcp_stream, Codec); + let mut websocket = connect_to_ws_embedded_websocket_tokio_echo(framed).await?; + let fee = Fee::new(None); + websocket.xrpl_send(fee).await?; + let _ = websocket + .xrpl_receive::, Fee<'_>>() + .await + .unwrap(); + Ok(()) +} - let mut ping_counter = 0; - loop { - match websocket.try_next(&mut framed, &mut buffer).await? { - Some(message) => match message { - EmbeddedWebsocketReadMessageType::Ping(_) => { - ping_counter += 1; - if ping_counter > 1 { - return Err(anyhow!("Expected only one ping")); - } - } - EmbeddedWebsocketReadMessageType::Text(text) => { - match serde_json::from_str::(text) { - Ok(account_info_echo) => { - assert_eq!(account_info, account_info_echo); - return Ok(()); - } - Err(err) => { - return Err(anyhow!("Error parsing response: {:?}", err)); - } - } - } - EmbeddedWebsocketReadMessageType::Binary(_) => { - panic!("Expected text message found binary") - } - EmbeddedWebsocketReadMessageType::Pong(_) => { - panic!("Expected text message found pong") - } - EmbeddedWebsocketReadMessageType::Close(_) => { - panic!("Expected text message found close") - } - }, - None => return Err(anyhow!("No message received")), - } - } +#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] +pub async fn test_embedded_websocket_request() -> Result<()> { + use crate::common::connect_to_ws_embedded_websocket_tokio_echo; + use tokio_util::codec::Framed; + use xrpl::asynch::clients::codec::Codec; + use xrpl::asynch::clients::AsyncClient; + use xrpl::models::requests::Fee; + use xrpl::models::results::FeeResult; + + let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") + .await + .unwrap(); + let framed = Framed::new(tcp_stream, Codec); + let websocket = connect_to_ws_embedded_websocket_tokio_echo(framed).await?; + let fee = Fee::new(None); + let _res = websocket.request::(fee).await?; + Ok(()) } #[cfg(all(feature = "json-rpc-std", not(feature = "json-rpc")))] @@ -104,7 +81,7 @@ pub async fn test_json_rpc_std() -> Result<()> { use xrpl::{ asynch::clients::{AsyncClient, AsyncJsonRpcClient, SingleExecutorMutex}, models::requests::Fee, - models::results::FeeResult, + models::results::Fee as FeeResult, }; let client: AsyncJsonRpcClient = AsyncJsonRpcClient::new("https://s1.ripple.com:51234/".parse().unwrap()); diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index e9e36cee..12f80a75 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -8,7 +8,7 @@ use anyhow::Result; #[tokio::test] async fn test_asynch_clients() -> Result<()> { #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] - return integration::clients::test_websocket_tungstenite_echo().await; + return integration::clients::test_websocket_tungstenite_test_net().await; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] return integration::clients::test_embedded_websocket_echo().await; #[cfg(all(feature = "tungstenite", feature = "embedded-ws"))] From 6fe51e8d10380ebedc52da651b465380e9fe6ecf Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sun, 16 Jun 2024 15:35:48 +0000 Subject: [PATCH 13/19] add testing --- src/asynch/transaction/mod.rs | 60 ++++++++++++++++++++++++++++-- src/models/amount/xrp_amount.rs | 19 +++++++++- src/models/results/server_state.rs | 2 +- src/models/transactions/mod.rs | 8 ++++ 4 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/asynch/transaction/mod.rs b/src/asynch/transaction/mod.rs index 8f3ba49b..c8e62ea8 100644 --- a/src/asynch/transaction/mod.rs +++ b/src/asynch/transaction/mod.rs @@ -38,7 +38,7 @@ pub async fn autofill<'a, F, T>( signers_count: Option, ) -> Result<()> where - T: Transaction<'a, F> + Model + Clone + 'static, + T: Transaction<'a, F> + Model + Clone, F: IntoEnumIterator + Serialize + Debug + PartialEq + 'a, { let txn = transaction.clone(); @@ -69,7 +69,7 @@ async fn calculate_fee_per_transaction_type<'a, T, F>( signers_count: Option, ) -> Result> where - T: Transaction<'a, F> + 'static, + T: Transaction<'a, F>, F: IntoEnumIterator + Serialize + Debug + PartialEq, { let mut net_fee = XRPAmount::from("10"); @@ -117,7 +117,7 @@ async fn get_owner_reserve_from_response<'a>( .state .validated_ledger { - Some(validated_ledger) => Ok(validated_ledger.reserve_base), + Some(validated_ledger) => Ok(validated_ledger.reserve_base.into()), None => Err!(XRPLModelException::MissingField("validated_ledger")), } } @@ -209,3 +209,57 @@ fn is_not_later_rippled_version(source: String, target: String) -> bool { } } } + +#[cfg(test)] +mod test_autofill { + use alloc::println; + use anyhow::Result; + + use super::autofill; + use crate::{ + asynch::clients::{AsyncWebsocketClient, SingleExecutorMutex}, + models::{ + amount::{IssuedCurrencyAmount, XRPAmount}, + transactions::OfferCreate, + }, + wallet::Wallet, + }; + + #[tokio::test] + async fn test_autofill_txn() -> Result<()> { + let wallet = Wallet::create(None).unwrap(); + let mut txn = OfferCreate::new( + wallet.classic_address.clone().into(), + None, + None, + None, + Some(72779837), + None, + Some(1), + None, + None, + None, + XRPAmount::from("1000000").into(), + IssuedCurrencyAmount::new( + "USD".into(), + "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq".into(), + "0.3".into(), + ) + .into(), + None, + None, + ); + let client = AsyncWebsocketClient::::open( + "wss://testnet.xrpl-labs.com/".parse().unwrap(), + ) + .await + .unwrap(); + { + let txn1 = &mut txn; + autofill(txn1, &client, None).await.unwrap(); + } + println!("{:?}", txn); + + Ok(()) + } +} diff --git a/src/models/amount/xrp_amount.rs b/src/models/amount/xrp_amount.rs index 62185d41..ac8fff2c 100644 --- a/src/models/amount/xrp_amount.rs +++ b/src/models/amount/xrp_amount.rs @@ -14,11 +14,22 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// Represents an amount of XRP in Drops. -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Default)] pub struct XRPAmount<'a>(pub Cow<'a, str>); impl<'a> Model for XRPAmount<'a> {} +// implement Deserializing from Cow, &str, String, Decimal, f64, u32, and Value +impl<'de, 'a> Deserialize<'de> for XRPAmount<'a> { + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + let amount_string = Value::deserialize(deserializer)?; + Ok(XRPAmount::try_from(amount_string).map_err(serde::de::Error::custom)?) + } +} + impl<'a> From> for XRPAmount<'a> { fn from(value: Cow<'a, str>) -> Self { Self(value) @@ -49,6 +60,12 @@ impl<'a> From for XRPAmount<'a> { } } +impl<'a> From for XRPAmount<'a> { + fn from(value: u32) -> Self { + Self(value.to_string().into()) + } +} + impl<'a> TryFrom for XRPAmount<'a> { type Error = XRPLAmountException; diff --git a/src/models/results/server_state.rs b/src/models/results/server_state.rs index 1958bbcd..39963cbe 100644 --- a/src/models/results/server_state.rs +++ b/src/models/results/server_state.rs @@ -17,7 +17,7 @@ pub struct State<'a> { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ValidatedLedger<'a> { - pub base_fee_xrp: XRPAmount<'a>, + pub base_fee: XRPAmount<'a>, pub close_time: u32, pub hash: Cow<'a, str>, pub reserve_base: XRPAmount<'a>, diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 3970165d..5f8588d0 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -135,6 +135,7 @@ where pub fee: Option>, /// Set of bit-flags for this transaction. #[serde(with = "txn_flags")] + #[serde(default = "optional_flag_collection_default")] pub flags: Option>, /// Highest ledger index this transaction can appear in. /// Specifying this field places a strict upper limit on how long @@ -238,6 +239,13 @@ where } } +fn optional_flag_collection_default() -> Option> +where + T: IntoEnumIterator + Serialize + core::fmt::Debug, +{ + None +} + serde_with_tag! { /// An arbitrary piece of data attached to a transaction. A /// transaction can have multiple Memo objects as an array From dd391bfefffb49b1eaca71b37824acb06069dd6b Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 17 Jun 2024 15:51:12 +0000 Subject: [PATCH 14/19] fix lifetime issues --- src/asynch/account/mod.rs | 4 +- src/asynch/clients/async_client.rs | 9 +- src/asynch/clients/client.rs | 14 +-- src/asynch/clients/json_rpc/mod.rs | 7 +- src/asynch/clients/mod.rs | 1 - src/asynch/clients/websocket/tungstenite.rs | 7 +- src/asynch/ledger/mod.rs | 10 +- src/asynch/transaction/mod.rs | 91 +++++++++---------- src/models/amount/xrp_amount.rs | 1 + src/models/results/ledger.rs | 2 +- src/models/transactions/account_delete.rs | 14 +-- src/models/transactions/account_set.rs | 4 +- src/models/transactions/check_cancel.rs | 4 +- src/models/transactions/check_cash.rs | 4 +- src/models/transactions/check_create.rs | 4 +- src/models/transactions/deposit_preauth.rs | 4 +- src/models/transactions/escrow_cancel.rs | 4 +- src/models/transactions/escrow_create.rs | 4 +- src/models/transactions/escrow_finish.rs | 4 +- src/models/transactions/mod.rs | 8 +- .../transactions/nftoken_accept_offer.rs | 4 +- src/models/transactions/nftoken_burn.rs | 4 +- .../transactions/nftoken_cancel_offer.rs | 4 +- .../transactions/nftoken_create_offer.rs | 4 +- src/models/transactions/nftoken_mint.rs | 4 +- src/models/transactions/offer_cancel.rs | 4 +- src/models/transactions/offer_create.rs | 4 +- src/models/transactions/payment.rs | 4 +- .../transactions/payment_channel_claim.rs | 4 +- .../transactions/payment_channel_create.rs | 4 +- .../transactions/payment_channel_fund.rs | 4 +- .../pseudo_transactions/enable_amendment.rs | 4 +- .../pseudo_transactions/set_fee.rs | 4 +- .../pseudo_transactions/unl_modify.rs | 4 +- src/models/transactions/set_regular_key.rs | 4 +- src/models/transactions/signer_list_set.rs | 4 +- src/models/transactions/ticket_create.rs | 4 +- src/models/transactions/trust_set.rs | 4 +- tests/integration/clients/mod.rs | 2 +- tests/integration/mod.rs | 2 - 40 files changed, 134 insertions(+), 142 deletions(-) diff --git a/src/asynch/account/mod.rs b/src/asynch/account/mod.rs index 12840fee..5c202636 100644 --- a/src/asynch/account/mod.rs +++ b/src/asynch/account/mod.rs @@ -10,7 +10,7 @@ use super::clients::AsyncClient; pub async fn get_next_valid_seq_number<'a>( address: Cow<'a, str>, - client: &'a impl AsyncClient<'a>, + client: &'a impl AsyncClient, ledger_index: Option>, ) -> Result { let account_info = @@ -20,7 +20,7 @@ pub async fn get_next_valid_seq_number<'a>( pub async fn get_account_root<'a>( address: Cow<'a, str>, - client: &'a impl AsyncClient<'a>, + client: &'a impl AsyncClient, ledger_index: Cow<'a, str>, ) -> Result> { let mut classic_address = address; diff --git a/src/asynch/clients/async_client.rs b/src/asynch/clients/async_client.rs index 57a776ab..f9705967 100644 --- a/src/asynch/clients/async_client.rs +++ b/src/asynch/clients/async_client.rs @@ -4,16 +4,17 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; #[allow(async_fn_in_trait)] -pub trait AsyncClient<'a>: Client<'a> { +pub trait AsyncClient: Client { async fn request< + 'a, Res: Serialize + for<'de> Deserialize<'de>, Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &'a self, + &self, request: Req, - ) -> Result> { + ) -> Result> { self.request_impl(request).await } } -impl<'a, T: Client<'a>> AsyncClient<'a> for T {} +impl<'a, T: Client> AsyncClient for T {} diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs index d5021568..2847b370 100644 --- a/src/asynch/clients/client.rs +++ b/src/asynch/clients/client.rs @@ -11,22 +11,24 @@ use serde::{Deserialize, Serialize}; use super::CommonFields; #[allow(async_fn_in_trait)] -pub trait Client<'a> { +pub trait Client { async fn request_impl< + 'a, Res: Serialize + for<'de> Deserialize<'de>, Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &'a self, + &self, request: Req, - ) -> Result>; + ) -> Result>; fn set_request_id< + 'a, Res: Serialize + for<'de> Deserialize<'de>, Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &'a self, + &self, request: &mut Req, - ) -> Cow<'_, str> { + ) -> Cow<'a, str> { let common_fields = request.get_common_fields(); let request_id: Cow<'_, str> = match common_fields.id.clone() { Some(id) => id, @@ -44,7 +46,7 @@ pub trait Client<'a> { request_id } - async fn get_common_fields(&'a self) -> Result> { + async fn get_common_fields(&self) -> Result> { let server_state = self .request_impl::(ServerState::new(None)) .await?; diff --git a/src/asynch/clients/json_rpc/mod.rs b/src/asynch/clients/json_rpc/mod.rs index 72bc61f9..e08eea99 100644 --- a/src/asynch/clients/json_rpc/mod.rs +++ b/src/asynch/clients/json_rpc/mod.rs @@ -67,17 +67,18 @@ mod std_client { } } - impl<'a, M> Client<'a> for AsyncJsonRpcClient + impl Client for AsyncJsonRpcClient where M: RawMutex, { async fn request_impl< + 'a, Res: Serialize + for<'de> Deserialize<'de>, Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &'a self, + &self, request: Req, - ) -> Result> { + ) -> Result> { let client = self.client.lock().await; match client .post(self.url.as_ref()) diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index 5bcb65ec..9dfe17f7 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -7,7 +7,6 @@ use alloc::borrow::Cow; use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; pub use async_client::*; -pub(crate) use client::*; pub use json_rpc::*; use serde::{Deserialize, Serialize}; pub use websocket::*; diff --git a/src/asynch/clients/websocket/tungstenite.rs b/src/asynch/clients/websocket/tungstenite.rs index 3692bea1..182b47ab 100644 --- a/src/asynch/clients/websocket/tungstenite.rs +++ b/src/asynch/clients/websocket/tungstenite.rs @@ -179,17 +179,18 @@ where } } -impl<'a, M> Client<'a> for AsyncWebsocketClient +impl Client for AsyncWebsocketClient where M: RawMutex, { async fn request_impl< + 'a, Res: Serialize + for<'de> Deserialize<'de>, Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &'a self, + &self, mut request: Req, - ) -> Result> { + ) -> Result> { // setup request future let request_id = self.set_request_id::(&mut request); let mut websocket_base = self.websocket_base.lock().await; diff --git a/src/asynch/ledger/mod.rs b/src/asynch/ledger/mod.rs index 75994ff3..0b1efd69 100644 --- a/src/asynch/ledger/mod.rs +++ b/src/asynch/ledger/mod.rs @@ -13,9 +13,7 @@ use crate::models::{ use super::clients::AsyncClient; -pub async fn get_latest_validated_ledger_sequence<'a>( - client: &'a impl AsyncClient<'a>, -) -> Result { +pub async fn get_latest_validated_ledger_sequence<'a>(client: &'a impl AsyncClient) -> Result { let ledger_response = client .request::(Ledger::new( None, @@ -41,8 +39,8 @@ pub enum FeeType { } pub async fn get_fee<'a>( - client: &'a impl AsyncClient<'a>, - max_fee: Option, + client: &'a impl AsyncClient, + max_fee: Option, fee_type: Option, ) -> Result> { let fee_request = Fee::new(None); @@ -61,7 +59,7 @@ pub async fn get_fee<'a>( } } -fn match_fee_type<'a>(fee_type: Option, drops: Drops<'a>) -> Result { +fn match_fee_type<'a>(fee_type: Option, drops: Drops<'a>) -> Result { match fee_type { None | Some(FeeType::Open) => Ok(drops.open_ledger_fee.0.to_string().parse().unwrap()), Some(FeeType::Minimum) => Ok(drops.minimum_fee.0.to_string().parse().unwrap()), diff --git a/src/asynch/transaction/mod.rs b/src/asynch/transaction/mod.rs index c8e62ea8..d474a798 100644 --- a/src/asynch/transaction/mod.rs +++ b/src/asynch/transaction/mod.rs @@ -1,15 +1,8 @@ -use core::convert::TryInto; -use core::fmt::Debug; - -use alloc::borrow::Cow; -use alloc::string::String; -use alloc::string::ToString; -use alloc::vec::Vec; -use anyhow::Result; -use rust_decimal::Decimal; -use serde::Serialize; -use strum::IntoEnumIterator; - +use super::account::get_next_valid_seq_number; +use super::clients::AsyncClient; +use super::clients::CommonFields; +use super::ledger::get_fee; +use super::ledger::get_latest_validated_ledger_sequence; use crate::models::amount::XRPAmount; use crate::models::exceptions::XRPLModelException; use crate::models::requests::ServerState; @@ -18,12 +11,16 @@ use crate::models::transactions::Transaction; use crate::models::transactions::TransactionType; use crate::models::Model; use crate::Err; - -use super::account::get_next_valid_seq_number; -use super::clients::AsyncClient; -use super::clients::CommonFields; -use super::ledger::get_fee; -use super::ledger::get_latest_validated_ledger_sequence; +use alloc::borrow::Cow; +use alloc::string::String; +use alloc::string::ToString; +use alloc::vec::Vec; +use anyhow::Result; +use core::convert::TryInto; +use core::fmt::Debug; +use rust_decimal::Decimal; +use serde::Serialize; +use strum::IntoEnumIterator; pub mod exceptions; @@ -32,14 +29,15 @@ const RESTRICTED_NETWORKS: u16 = 1024; const REQUIRED_NETWORKID_VERSION: &'static str = "1.11.0"; const LEDGER_OFFSET: u8 = 20; -pub async fn autofill<'a, F, T>( - transaction: &'a mut T, - client: &'a impl AsyncClient<'a>, +pub async fn autofill<'a, 'b, F, T>( + transaction: &mut T, + client: &'a impl AsyncClient, signers_count: Option, ) -> Result<()> where - T: Transaction<'a, F> + Model + Clone, - F: IntoEnumIterator + Serialize + Debug + PartialEq + 'a, + 'a: 'b, + T: Transaction<'b, F> + Model + Clone, + F: IntoEnumIterator + Serialize + Debug + PartialEq, { let txn = transaction.clone(); let txn_common_fields = transaction.get_mut_common_fields(); @@ -63,18 +61,18 @@ where Ok(()) } -async fn calculate_fee_per_transaction_type<'a, T, F>( +pub async fn calculate_fee_per_transaction_type<'a, 'b, T, F>( transaction: T, - client: Option<&'a impl AsyncClient<'a>>, + client: Option<&'a impl AsyncClient>, signers_count: Option, -) -> Result> +) -> Result> where - T: Transaction<'a, F>, + 'a: 'b, + T: Transaction<'b, F>, F: IntoEnumIterator + Serialize + Debug + PartialEq, { let mut net_fee = XRPAmount::from("10"); let mut base_fee; - if let Some(client) = client { net_fee = get_fee(client, None, None).await?; base_fee = match transaction.get_transaction_type() { @@ -97,7 +95,6 @@ where _ => net_fee.clone(), }; } - if let Some(signers_count) = signers_count { base_fee += net_fee * (1 + signers_count); } @@ -105,11 +102,9 @@ where Ok(base_fee.ceil()) } -async fn get_owner_reserve_from_response<'a>( - client: &'a impl AsyncClient<'a>, -) -> Result> { +async fn get_owner_reserve_from_response(client: &impl AsyncClient) -> Result> { let owner_reserve_response = client - .request::, _>(ServerState::new(None)) + .request::, _>(ServerState::new(None)) .await?; match owner_reserve_response .result @@ -141,10 +136,11 @@ fn calculate_based_on_fulfillment<'a>( let base_fee_string = (net_fee_f64 * (33.0 + (fulfillment_bytes.len() as f64 / 16.0))).to_string(); let base_fee_decimal: Decimal = base_fee_string.parse().unwrap(); + XRPAmount::from(base_fee_decimal.ceil()) } -fn txn_needs_network_id<'a>(common_fields: CommonFields<'a>) -> bool { +fn txn_needs_network_id(common_fields: CommonFields<'_>) -> bool { common_fields.network_id.unwrap() > RESTRICTED_NETWORKS as u32 && is_not_later_rippled_version( REQUIRED_NETWORKID_VERSION.into(), @@ -172,7 +168,6 @@ fn is_not_later_rippled_version(source: String, target: String) -> bool { target_decomp[0].parse::().unwrap(), target_decomp[1].parse::().unwrap(), ); - if source_major != target_major { source_major < target_major } else if source_minor != target_minor { @@ -188,7 +183,6 @@ fn is_not_later_rippled_version(source: String, target: String) -> bool { .collect::>(); let source_patch_version = source_patch[0].parse::().unwrap(); let target_patch_version = target_patch[0].parse::().unwrap(); - if source_patch_version != target_patch_version { source_patch_version < target_patch_version } else if source_patch.len() != target_patch.len() { @@ -212,30 +206,26 @@ fn is_not_later_rippled_version(source: String, target: String) -> bool { #[cfg(test)] mod test_autofill { - use alloc::println; - use anyhow::Result; - use super::autofill; use crate::{ asynch::clients::{AsyncWebsocketClient, SingleExecutorMutex}, models::{ amount::{IssuedCurrencyAmount, XRPAmount}, - transactions::OfferCreate, + transactions::{OfferCreate, Transaction}, }, - wallet::Wallet, }; + use anyhow::Result; #[tokio::test] async fn test_autofill_txn() -> Result<()> { - let wallet = Wallet::create(None).unwrap(); let mut txn = OfferCreate::new( - wallet.classic_address.clone().into(), + "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq".into(), + None, + None, None, None, None, - Some(72779837), None, - Some(1), None, None, None, @@ -254,11 +244,12 @@ mod test_autofill { ) .await .unwrap(); - { - let txn1 = &mut txn; - autofill(txn1, &client, None).await.unwrap(); - } - println!("{:?}", txn); + autofill(&mut txn, &client, None).await?; + + assert!(txn.get_common_fields().network_id.is_none()); + assert!(txn.get_common_fields().sequence.is_some()); + assert!(txn.get_common_fields().fee.is_some()); + assert!(txn.get_common_fields().last_ledger_sequence.is_some()); Ok(()) } diff --git a/src/models/amount/xrp_amount.rs b/src/models/amount/xrp_amount.rs index ac8fff2c..f677e3a1 100644 --- a/src/models/amount/xrp_amount.rs +++ b/src/models/amount/xrp_amount.rs @@ -72,6 +72,7 @@ impl<'a> TryFrom for XRPAmount<'a> { fn try_from(value: Value) -> Result { let amount_string = serde_json::to_string(&value).map_err(XRPLAmountException::FromSerdeError)?; + let amount_string = amount_string.clone().replace("\"", ""); Ok(Self(amount_string.into())) } } diff --git a/src/models/results/ledger.rs b/src/models/results/ledger.rs index 5e0cb2d3..69cad0a6 100644 --- a/src/models/results/ledger.rs +++ b/src/models/results/ledger.rs @@ -19,7 +19,7 @@ pub struct LedgerInner<'a> { pub close_time_resolution: u32, pub closed: bool, pub ledger_hash: Cow<'a, str>, - pub ledger_index: u32, + pub ledger_index: Cow<'a, str>, pub parent_close_time: u32, pub parent_hash: Cow<'a, str>, pub total_coins: Cow<'a, str>, diff --git a/src/models/transactions/account_delete.rs b/src/models/transactions/account_delete.rs index 5ebf23dc..199d17eb 100644 --- a/src/models/transactions/account_delete.rs +++ b/src/models/transactions/account_delete.rs @@ -44,24 +44,24 @@ pub struct AccountDelete<'a> { pub destination_tag: Option, } -impl<'a> Model for AccountDelete<'a> {} +impl Model for AccountDelete<'_> {} impl<'a> Transaction<'a, NoFlags> for AccountDelete<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.get_transaction_type() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { &mut self.common_fields } } -impl<'a> AccountDelete<'a> { - pub fn new( +impl AccountDelete<'_> { + pub fn new<'a>( account: Cow<'a, str>, account_txn_id: Option>, fee: Option>, @@ -73,8 +73,8 @@ impl<'a> AccountDelete<'a> { ticket_sequence: Option, destination: Cow<'a, str>, destination_tag: Option, - ) -> Self { - Self { + ) -> AccountDelete<'a> { + AccountDelete { common_fields: CommonFields { account, transaction_type: TransactionType::AccountDelete, diff --git a/src/models/transactions/account_set.rs b/src/models/transactions/account_set.rs index 4634934e..38cdc81b 100644 --- a/src/models/transactions/account_set.rs +++ b/src/models/transactions/account_set.rs @@ -150,11 +150,11 @@ impl<'a> Transaction<'a, AccountSetFlag> for AccountSet<'a> { self.common_fields.get_transaction_type() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, AccountSetFlag> { + fn get_common_fields(&self) -> &CommonFields<'_, AccountSetFlag> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, AccountSetFlag> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, AccountSetFlag> { &mut self.common_fields } } diff --git a/src/models/transactions/check_cancel.rs b/src/models/transactions/check_cancel.rs index 7f1d49cb..ccc08ee2 100644 --- a/src/models/transactions/check_cancel.rs +++ b/src/models/transactions/check_cancel.rs @@ -46,11 +46,11 @@ impl<'a> Transaction<'a, NoFlags> for CheckCancel<'a> { self.common_fields.get_transaction_type() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/check_cash.rs b/src/models/transactions/check_cash.rs index 29d6a656..9501714e 100644 --- a/src/models/transactions/check_cash.rs +++ b/src/models/transactions/check_cash.rs @@ -61,11 +61,11 @@ impl<'a> Transaction<'a, NoFlags> for CheckCash<'a> { self.common_fields.get_transaction_type() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/check_create.rs b/src/models/transactions/check_create.rs index 51491bc6..7ab80f77 100644 --- a/src/models/transactions/check_create.rs +++ b/src/models/transactions/check_create.rs @@ -57,11 +57,11 @@ impl<'a> Transaction<'a, NoFlags> for CheckCreate<'a> { self.common_fields.get_transaction_type() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/deposit_preauth.rs b/src/models/transactions/deposit_preauth.rs index 16ba8797..065cac77 100644 --- a/src/models/transactions/deposit_preauth.rs +++ b/src/models/transactions/deposit_preauth.rs @@ -52,11 +52,11 @@ impl<'a> Transaction<'a, NoFlags> for DepositPreauth<'a> { self.common_fields.get_transaction_type() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/escrow_cancel.rs b/src/models/transactions/escrow_cancel.rs index c6f9f4f3..138207dc 100644 --- a/src/models/transactions/escrow_cancel.rs +++ b/src/models/transactions/escrow_cancel.rs @@ -45,11 +45,11 @@ impl<'a> Transaction<'a, NoFlags> for EscrowCancel<'a> { self.common_fields.get_transaction_type() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/escrow_create.rs b/src/models/transactions/escrow_create.rs index fdf49982..47bca1aa 100644 --- a/src/models/transactions/escrow_create.rs +++ b/src/models/transactions/escrow_create.rs @@ -70,11 +70,11 @@ impl<'a> Transaction<'a, NoFlags> for EscrowCreate<'a> { self.common_fields.get_transaction_type() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/escrow_finish.rs b/src/models/transactions/escrow_finish.rs index bbc1396e..e9812822 100644 --- a/src/models/transactions/escrow_finish.rs +++ b/src/models/transactions/escrow_finish.rs @@ -61,11 +61,11 @@ impl<'a> Transaction<'a, NoFlags> for EscrowFinish<'a> { self.common_fields.transaction_type.clone() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 5f8588d0..422d01cc 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -230,11 +230,11 @@ where self.transaction_type.clone() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, T> { + fn get_common_fields(&self) -> &CommonFields<'_, T> { self } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, T> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, T> { self } } @@ -292,9 +292,9 @@ where fn get_transaction_type(&self) -> TransactionType; - fn get_common_fields(&'a self) -> &'a CommonFields<'a, T>; + fn get_common_fields(&self) -> &CommonFields<'_, T>; - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, T>; + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, T>; fn get_field_value(&self, field: &str) -> Option { let transaction = serde_json::to_value(self).unwrap(); diff --git a/src/models/transactions/nftoken_accept_offer.rs b/src/models/transactions/nftoken_accept_offer.rs index 6c6e7345..458b4882 100644 --- a/src/models/transactions/nftoken_accept_offer.rs +++ b/src/models/transactions/nftoken_accept_offer.rs @@ -75,11 +75,11 @@ impl<'a> Transaction<'a, NoFlags> for NFTokenAcceptOffer<'a> { self.common_fields.transaction_type.clone() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/nftoken_burn.rs b/src/models/transactions/nftoken_burn.rs index 054385f6..86c14679 100644 --- a/src/models/transactions/nftoken_burn.rs +++ b/src/models/transactions/nftoken_burn.rs @@ -53,11 +53,11 @@ impl<'a> Transaction<'a, NoFlags> for NFTokenBurn<'a> { self.common_fields.transaction_type.clone() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/nftoken_cancel_offer.rs b/src/models/transactions/nftoken_cancel_offer.rs index f92d5b06..6dbbab0b 100644 --- a/src/models/transactions/nftoken_cancel_offer.rs +++ b/src/models/transactions/nftoken_cancel_offer.rs @@ -61,11 +61,11 @@ impl<'a> Transaction<'a, NoFlags> for NFTokenCancelOffer<'a> { self.common_fields.transaction_type.clone() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/nftoken_create_offer.rs b/src/models/transactions/nftoken_create_offer.rs index 1c0da365..c211c3e1 100644 --- a/src/models/transactions/nftoken_create_offer.rs +++ b/src/models/transactions/nftoken_create_offer.rs @@ -105,11 +105,11 @@ impl<'a> Transaction<'a, NFTokenCreateOfferFlag> for NFTokenCreateOffer<'a> { self.common_fields.transaction_type.clone() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NFTokenCreateOfferFlag> { + fn get_common_fields(&self) -> &CommonFields<'_, NFTokenCreateOfferFlag> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NFTokenCreateOfferFlag> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NFTokenCreateOfferFlag> { &mut self.common_fields } } diff --git a/src/models/transactions/nftoken_mint.rs b/src/models/transactions/nftoken_mint.rs index 3a7dbabe..ae4413b3 100644 --- a/src/models/transactions/nftoken_mint.rs +++ b/src/models/transactions/nftoken_mint.rs @@ -114,11 +114,11 @@ impl<'a> Transaction<'a, NFTokenMintFlag> for NFTokenMint<'a> { self.common_fields.transaction_type.clone() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NFTokenMintFlag> { + fn get_common_fields(&self) -> &CommonFields<'_, NFTokenMintFlag> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NFTokenMintFlag> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NFTokenMintFlag> { &mut self.common_fields } } diff --git a/src/models/transactions/offer_cancel.rs b/src/models/transactions/offer_cancel.rs index 81edfc2b..b81f24c4 100644 --- a/src/models/transactions/offer_cancel.rs +++ b/src/models/transactions/offer_cancel.rs @@ -48,11 +48,11 @@ impl<'a> Transaction<'a, NoFlags> for OfferCancel<'a> { self.common_fields.transaction_type.clone() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/offer_create.rs b/src/models/transactions/offer_create.rs index 6145789a..157dddc8 100644 --- a/src/models/transactions/offer_create.rs +++ b/src/models/transactions/offer_create.rs @@ -90,11 +90,11 @@ impl<'a> Transaction<'a, OfferCreateFlag> for OfferCreate<'a> { self.common_fields.transaction_type.clone() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, OfferCreateFlag> { + fn get_common_fields(&self) -> &CommonFields<'_, OfferCreateFlag> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, OfferCreateFlag> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, OfferCreateFlag> { &mut self.common_fields } } diff --git a/src/models/transactions/payment.rs b/src/models/transactions/payment.rs index d13b5569..a75acaba 100644 --- a/src/models/transactions/payment.rs +++ b/src/models/transactions/payment.rs @@ -114,11 +114,11 @@ impl<'a> Transaction<'a, PaymentFlag> for Payment<'a> { self.common_fields.transaction_type.clone() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, PaymentFlag> { + fn get_common_fields(&self) -> &CommonFields<'_, PaymentFlag> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, PaymentFlag> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, PaymentFlag> { &mut self.common_fields } } diff --git a/src/models/transactions/payment_channel_claim.rs b/src/models/transactions/payment_channel_claim.rs index 50017231..98cc82f5 100644 --- a/src/models/transactions/payment_channel_claim.rs +++ b/src/models/transactions/payment_channel_claim.rs @@ -99,11 +99,11 @@ impl<'a> Transaction<'a, PaymentChannelClaimFlag> for PaymentChannelClaim<'a> { self.common_fields.transaction_type.clone() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, PaymentChannelClaimFlag> { + fn get_common_fields(&self) -> &CommonFields<'_, PaymentChannelClaimFlag> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, PaymentChannelClaimFlag> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, PaymentChannelClaimFlag> { &mut self.common_fields } } diff --git a/src/models/transactions/payment_channel_create.rs b/src/models/transactions/payment_channel_create.rs index aa4ae702..3065eab2 100644 --- a/src/models/transactions/payment_channel_create.rs +++ b/src/models/transactions/payment_channel_create.rs @@ -65,11 +65,11 @@ impl<'a> Transaction<'a, NoFlags> for PaymentChannelCreate<'a> { self.common_fields.transaction_type.clone() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/payment_channel_fund.rs b/src/models/transactions/payment_channel_fund.rs index a85ded45..6b83a694 100644 --- a/src/models/transactions/payment_channel_fund.rs +++ b/src/models/transactions/payment_channel_fund.rs @@ -57,11 +57,11 @@ impl<'a> Transaction<'a, NoFlags> for PaymentChannelFund<'a> { self.common_fields.transaction_type.clone() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/pseudo_transactions/enable_amendment.rs b/src/models/transactions/pseudo_transactions/enable_amendment.rs index 36d8fc65..3e2deab6 100644 --- a/src/models/transactions/pseudo_transactions/enable_amendment.rs +++ b/src/models/transactions/pseudo_transactions/enable_amendment.rs @@ -61,11 +61,11 @@ impl<'a> Transaction<'a, EnableAmendmentFlag> for EnableAmendment<'a> { self.common_fields.transaction_type.clone() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, EnableAmendmentFlag> { + fn get_common_fields(&self) -> &CommonFields<'_, EnableAmendmentFlag> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, EnableAmendmentFlag> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, EnableAmendmentFlag> { &mut self.common_fields } } diff --git a/src/models/transactions/pseudo_transactions/set_fee.rs b/src/models/transactions/pseudo_transactions/set_fee.rs index 7a54d2b7..e64e8614 100644 --- a/src/models/transactions/pseudo_transactions/set_fee.rs +++ b/src/models/transactions/pseudo_transactions/set_fee.rs @@ -46,11 +46,11 @@ impl<'a> Transaction<'a, NoFlags> for SetFee<'a> { self.common_fields.transaction_type.clone() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/pseudo_transactions/unl_modify.rs b/src/models/transactions/pseudo_transactions/unl_modify.rs index bd4f0f23..b84fb2ec 100644 --- a/src/models/transactions/pseudo_transactions/unl_modify.rs +++ b/src/models/transactions/pseudo_transactions/unl_modify.rs @@ -55,11 +55,11 @@ impl<'a> Transaction<'a, NoFlags> for UNLModify<'a> { self.common_fields.transaction_type.clone() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/set_regular_key.rs b/src/models/transactions/set_regular_key.rs index 7c7a5027..a7fdb8ae 100644 --- a/src/models/transactions/set_regular_key.rs +++ b/src/models/transactions/set_regular_key.rs @@ -52,11 +52,11 @@ impl<'a> Transaction<'a, NoFlags> for SetRegularKey<'a> { self.common_fields.transaction_type.clone() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/signer_list_set.rs b/src/models/transactions/signer_list_set.rs index 7e29a6ba..9ee3fd7c 100644 --- a/src/models/transactions/signer_list_set.rs +++ b/src/models/transactions/signer_list_set.rs @@ -79,11 +79,11 @@ impl<'a> Transaction<'a, NoFlags> for SignerListSet<'a> { self.common_fields.transaction_type.clone() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/ticket_create.rs b/src/models/transactions/ticket_create.rs index 3c8e2875..e01a4bb2 100644 --- a/src/models/transactions/ticket_create.rs +++ b/src/models/transactions/ticket_create.rs @@ -46,11 +46,11 @@ impl<'a> Transaction<'a, NoFlags> for TicketCreate<'a> { self.common_fields.get_transaction_type() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, NoFlags> { + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, NoFlags> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { &mut self.common_fields } } diff --git a/src/models/transactions/trust_set.rs b/src/models/transactions/trust_set.rs index 056841b8..8b5bb593 100644 --- a/src/models/transactions/trust_set.rs +++ b/src/models/transactions/trust_set.rs @@ -81,11 +81,11 @@ impl<'a> Transaction<'a, TrustSetFlag> for TrustSet<'a> { self.common_fields.transaction_type.clone() } - fn get_common_fields(&'a self) -> &'a CommonFields<'a, TrustSetFlag> { + fn get_common_fields(&self) -> &CommonFields<'_, TrustSetFlag> { &self.common_fields } - fn get_mut_common_fields(&'a mut self) -> &'a mut CommonFields<'a, TrustSetFlag> { + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, TrustSetFlag> { &mut self.common_fields } } diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index 9dedd232..e3802017 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -84,7 +84,7 @@ pub async fn test_json_rpc_std() -> Result<()> { models::results::Fee as FeeResult, }; let client: AsyncJsonRpcClient = - AsyncJsonRpcClient::new("https://s1.ripple.com:51234/".parse().unwrap()); + AsyncJsonRpcClient::new("https://testnet.xrpl-labs.com".parse().unwrap()); let fee_result = client .request::(Fee::new(None)) .await diff --git a/tests/integration/mod.rs b/tests/integration/mod.rs index eb5264ce..705f46db 100644 --- a/tests/integration/mod.rs +++ b/tests/integration/mod.rs @@ -1,3 +1 @@ -use super::common; - pub mod clients; From e422157d46b6014674b4c2484a9d836eff164629 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 17 Jun 2024 15:52:47 +0000 Subject: [PATCH 15/19] fix lifetime issues --- .cargo-husky/hooks/pre-commit | 4 +-- Cargo.toml | 4 +-- src/asynch/clients/json_rpc/mod.rs | 15 ++++---- .../clients/websocket/embedded_websocket.rs | 7 ++-- src/asynch/clients/websocket/mod.rs | 2 ++ src/asynch/ledger/mod.rs | 2 +- src/asynch/mod.rs | 25 ++++++++++++- src/asynch/transaction/mod.rs | 11 +++--- src/models/amount/xrp_amount.rs | 4 +-- tests/common/codec.rs | 35 ------------------- tests/integration/clients/mod.rs | 2 +- tests/integration_tests.rs | 16 +++++++-- 12 files changed, 66 insertions(+), 61 deletions(-) delete mode 100644 tests/common/codec.rs diff --git a/.cargo-husky/hooks/pre-commit b/.cargo-husky/hooks/pre-commit index 3dfe18e5..cc28bd84 100755 --- a/.cargo-husky/hooks/pre-commit +++ b/.cargo-husky/hooks/pre-commit @@ -5,8 +5,8 @@ echo 'Running all pre-commit checks:' cargo fmt cargo test --no-default-features --features core,models,utils cargo test --no-default-features --features core,models,utils,embedded-ws -cargo test --no-default-features --features core,models,utils,tungstenite -cargo test --no-default-features --features core,models,utils,json-rpc-std +cargo test --no-default-features --features std,core,models,utils,tungstenite +cargo test --no-default-features --features std,core,models,utils,json-rpc-std cargo test --all-features cargo clippy --fix --allow-staged cargo doc --no-deps diff --git a/Cargo.toml b/Cargo.toml index be380d70..4d10e158 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,8 +105,8 @@ results = ["core", "amounts", "currencies"] ledger = ["core", "amounts", "currencies"] amounts = ["core"] currencies = ["core"] -json-rpc = ["reqwless", "embedded-nal-async"] -json-rpc-std = ["reqwest"] +json-rpc = ["url", "reqwless", "embedded-nal-async"] +json-rpc-std = ["url", "reqwest"] tungstenite = [ "url", "futures", diff --git a/src/asynch/clients/json_rpc/mod.rs b/src/asynch/clients/json_rpc/mod.rs index e08eea99..7af899fc 100644 --- a/src/asynch/clients/json_rpc/mod.rs +++ b/src/asynch/clients/json_rpc/mod.rs @@ -29,7 +29,7 @@ fn request_to_json_rpc(request: &impl Serialize) -> Result { } } -#[cfg(feature = "json-rpc-std")] +#[cfg(all(feature = "json-rpc-std", not(feature = "json-rpc")))] mod std_client { use super::*; use reqwest::Client as HttpClient; @@ -96,7 +96,7 @@ mod std_client { } } -#[cfg(feature = "json-rpc")] +#[cfg(all(feature = "json-rpc", not(feature = "json-rpc-std")))] mod no_std_client { use super::*; use embedded_nal_async::{Dns, TcpConnect}; @@ -138,19 +138,20 @@ mod no_std_client { } } - impl<'a, const BUF: usize, T, D, M> Client<'a> for AsyncJsonRpcClient<'a, BUF, T, D, M> + impl Client for AsyncJsonRpcClient<'_, BUF, T, D, M> where M: RawMutex, - T: TcpConnect + 'a, - D: Dns + 'a, + T: TcpConnect, + D: Dns, { async fn request_impl< + 'a, Res: Serialize + for<'de> Deserialize<'de>, Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &'a self, + &self, request: Req, - ) -> Result> { + ) -> Result> { let request_json_rpc = request_to_json_rpc(&request)?; let request_buf = request_json_rpc.as_bytes(); let mut rx_buffer = [0; BUF]; diff --git a/src/asynch/clients/websocket/embedded_websocket.rs b/src/asynch/clients/websocket/embedded_websocket.rs index cd53eaca..7db18186 100644 --- a/src/asynch/clients/websocket/embedded_websocket.rs +++ b/src/asynch/clients/websocket/embedded_websocket.rs @@ -235,7 +235,7 @@ where } } -impl<'a, const BUF: usize, M, Tcp, B, E, Rng: RngCore> ClientTrait<'a> +impl ClientTrait for AsyncWebsocketClient where M: RawMutex, @@ -244,12 +244,13 @@ where Tcp: Stream> + for<'b> Sink<&'b [u8], Error = E> + Unpin, { async fn request_impl< + 'a, Res: Serialize + for<'de> Deserialize<'de>, Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &'a self, + &self, mut request: Req, - ) -> Result> { + ) -> Result> { // setup request future let request_id = self.set_request_id::(&mut request); let mut websocket_base = self.websocket_base.lock().await; diff --git a/src/asynch/clients/websocket/mod.rs b/src/asynch/clients/websocket/mod.rs index 0f4bdc82..b8ad2f88 100644 --- a/src/asynch/clients/websocket/mod.rs +++ b/src/asynch/clients/websocket/mod.rs @@ -12,7 +12,9 @@ use embedded_io_async::{ErrorType, Read as EmbeddedIoRead, Write as EmbeddedIoWr use futures::{Sink, SinkExt, Stream, StreamExt}; use serde::{Deserialize, Serialize}; +#[cfg(any(feature = "embedded-ws", feature = "tungstenite"))] mod websocket_base; +#[cfg(any(feature = "embedded-ws", feature = "tungstenite"))] use websocket_base::MessageHandler; #[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] diff --git a/src/asynch/ledger/mod.rs b/src/asynch/ledger/mod.rs index 0b1efd69..d17c107a 100644 --- a/src/asynch/ledger/mod.rs +++ b/src/asynch/ledger/mod.rs @@ -59,7 +59,7 @@ pub async fn get_fee<'a>( } } -fn match_fee_type<'a>(fee_type: Option, drops: Drops<'a>) -> Result { +fn match_fee_type(fee_type: Option, drops: Drops<'_>) -> Result { match fee_type { None | Some(FeeType::Open) => Ok(drops.open_ledger_fee.0.to_string().parse().unwrap()), Some(FeeType::Minimum) => Ok(drops.minimum_fee.0.to_string().parse().unwrap()), diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index 112bb945..9e659893 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -1,5 +1,28 @@ +#[cfg(any( + feature = "tungstenite", + feature = "embedded-ws", + feature = "json-rpc-std", + feature = "json-rpc" +))] pub mod account; -#[cfg(any(feature = "tungstenite", feature = "embedded-ws"))] +#[cfg(any( + feature = "tungstenite", + feature = "embedded-ws", + feature = "json-rpc-std", + feature = "json-rpc" +))] pub mod clients; +#[cfg(any( + feature = "tungstenite", + feature = "embedded-ws", + feature = "json-rpc-std", + feature = "json-rpc" +))] pub mod ledger; +#[cfg(any( + feature = "tungstenite", + feature = "embedded-ws", + feature = "json-rpc-std", + feature = "json-rpc" +))] pub mod transaction; diff --git a/src/asynch/transaction/mod.rs b/src/asynch/transaction/mod.rs index d474a798..201869a7 100644 --- a/src/asynch/transaction/mod.rs +++ b/src/asynch/transaction/mod.rs @@ -24,9 +24,9 @@ use strum::IntoEnumIterator; pub mod exceptions; -const OWNER_RESERVE: &'static str = "2000000"; // 2 XRP +const OWNER_RESERVE: &str = "2000000"; // 2 XRP const RESTRICTED_NETWORKS: u16 = 1024; -const REQUIRED_NETWORKID_VERSION: &'static str = "1.11.0"; +const REQUIRED_NETWORKID_VERSION: &str = "1.11.0"; const LEDGER_OFFSET: u8 = 20; pub async fn autofill<'a, 'b, F, T>( @@ -112,7 +112,7 @@ async fn get_owner_reserve_from_response(client: &impl AsyncClient) -> Result Ok(validated_ledger.reserve_base.into()), + Some(validated_ledger) => Ok(validated_ledger.reserve_base), None => Err!(XRPLModelException::MissingField("validated_ledger")), } } @@ -188,8 +188,8 @@ fn is_not_later_rippled_version(source: String, target: String) -> bool { } else if source_patch.len() != target_patch.len() { source_patch.len() < target_patch.len() } else if source_patch.len() == 2 { - if source_patch[1].chars().nth(0).unwrap() - != target_patch[1].chars().nth(0).unwrap() + if source_patch[1].chars().next().unwrap() + != target_patch[1].chars().next().unwrap() { source_patch[1] < target_patch[1] } else if source_patch[1].starts_with('b') { @@ -204,6 +204,7 @@ fn is_not_later_rippled_version(source: String, target: String) -> bool { } } +#[cfg(all(feature = "tungstenite", feature = "std", not(feature = "embedded-ws")))] #[cfg(test)] mod test_autofill { use super::autofill; diff --git a/src/models/amount/xrp_amount.rs b/src/models/amount/xrp_amount.rs index f677e3a1..c9bd73be 100644 --- a/src/models/amount/xrp_amount.rs +++ b/src/models/amount/xrp_amount.rs @@ -26,7 +26,7 @@ impl<'de, 'a> Deserialize<'de> for XRPAmount<'a> { D: serde::Deserializer<'de>, { let amount_string = Value::deserialize(deserializer)?; - Ok(XRPAmount::try_from(amount_string).map_err(serde::de::Error::custom)?) + XRPAmount::try_from(amount_string).map_err(serde::de::Error::custom) } } @@ -167,7 +167,7 @@ impl<'a> Mul for XRPAmount<'a> { fn mul(self, other: u8) -> Self { let self_decimal: Decimal = self.try_into().unwrap(); - let other_decimal: Decimal = other.try_into().unwrap(); + let other_decimal: Decimal = other.into(); let result_decimal = self_decimal * other_decimal; result_decimal.into() } diff --git a/tests/common/codec.rs b/tests/common/codec.rs deleted file mode 100644 index 7e074866..00000000 --- a/tests/common/codec.rs +++ /dev/null @@ -1,35 +0,0 @@ -use bytes::{BufMut, BytesMut}; -use std::io; -use tokio_util::codec::{Decoder, Encoder}; - -pub struct Codec {} - -impl Codec { - pub fn new() -> Self { - Codec {} - } -} - -impl Decoder for Codec { - type Item = BytesMut; - type Error = io::Error; - - fn decode(&mut self, buf: &mut BytesMut) -> Result, io::Error> { - if !buf.is_empty() { - let len = buf.len(); - Ok(Some(buf.split_to(len))) - } else { - Ok(None) - } - } -} - -impl Encoder<&[u8]> for Codec { - type Error = io::Error; - - fn encode(&mut self, data: &[u8], buf: &mut BytesMut) -> Result<(), io::Error> { - buf.reserve(data.len()); - buf.put(data); - Ok(()) - } -} diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index e3802017..9dedd232 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -84,7 +84,7 @@ pub async fn test_json_rpc_std() -> Result<()> { models::results::Fee as FeeResult, }; let client: AsyncJsonRpcClient = - AsyncJsonRpcClient::new("https://testnet.xrpl-labs.com".parse().unwrap()); + AsyncJsonRpcClient::new("https://s1.ripple.com:51234/".parse().unwrap()); let fee_result = client .request::(Fee::new(None)) .await diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 12f80a75..23d401ff 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -5,13 +5,25 @@ mod integration; use anyhow::Result; +#[cfg(any(feature = "tungstenite", all(feature = "embedded-ws", feature = "std")))] #[tokio::test] async fn test_asynch_clients() -> Result<()> { #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] return integration::clients::test_websocket_tungstenite_test_net().await; - #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] + #[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] return integration::clients::test_embedded_websocket_echo().await; - #[cfg(all(feature = "tungstenite", feature = "embedded-ws"))] + #[allow(unreachable_code)] + Ok(()) +} + +#[cfg(any(feature = "tungstenite", feature = "embedded-ws", feature = "std"))] +#[tokio::test] +async fn test_asynch_clients_request() -> Result<()> { + #[cfg(all(feature = "tungstenite", feature = "std", not(feature = "embedded-ws")))] + return integration::clients::test_websocket_tungstenite_request().await; + #[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] + return integration::clients::test_embedded_websocket_request().await; + #[allow(unreachable_code)] Ok(()) } From 53e6c5b17b12269c6fcc1d423c4a4cb88b5d4aed Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 17 Jun 2024 16:22:15 +0000 Subject: [PATCH 16/19] remove old clients --- src/asynch/clients/embedded_ws.rs | 165 --------------------------- src/asynch/clients/exceptions.rs | 60 ---------- src/asynch/clients/tungstenite.rs | 181 ------------------------------ 3 files changed, 406 deletions(-) delete mode 100644 src/asynch/clients/embedded_ws.rs delete mode 100644 src/asynch/clients/exceptions.rs delete mode 100644 src/asynch/clients/tungstenite.rs diff --git a/src/asynch/clients/embedded_ws.rs b/src/asynch/clients/embedded_ws.rs deleted file mode 100644 index 33d46924..00000000 --- a/src/asynch/clients/embedded_ws.rs +++ /dev/null @@ -1,165 +0,0 @@ -use super::WebsocketClient; -use super::{ - exceptions::XRPLWebsocketException, - {WebsocketClosed, WebsocketOpen}, -}; -use crate::Err; -use anyhow::Result; -use core::marker::PhantomData; -use core::{fmt::Debug, ops::Deref}; -pub use embedded_websocket::{ - framer_async::{ - Framer as EmbeddedWebsocketFramer, FramerError as EmbeddedWebsocketFramerError, - ReadResult as EmbeddedWebsocketReadMessageType, - }, - Client as EmbeddedWebsocketClient, Error as EmbeddedWebsocketError, - WebSocket as EmbeddedWebsocket, WebSocketCloseStatusCode as EmbeddedWebsocketCloseStatusCode, - WebSocketOptions as EmbeddedWebsocketOptions, - WebSocketSendMessageType as EmbeddedWebsocketSendMessageType, - WebSocketState as EmbeddedWebsocketState, -}; -use futures::{Sink, Stream}; -use rand_core::RngCore; - -pub struct AsyncWebsocketClient { - inner: EmbeddedWebsocketFramer, - status: PhantomData, -} - -impl WebsocketClient for AsyncWebsocketClient {} - -impl AsyncWebsocketClient { - /// Open a websocket connection. - pub async fn open<'a, B, E>( - stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), - buffer: &'a mut [u8], - rng: Rng, - websocket_options: &EmbeddedWebsocketOptions<'_>, - ) -> Result> - where - B: AsRef<[u8]>, - E: Debug, - { - let websocket = EmbeddedWebsocket::::new_client(rng); - let mut framer = EmbeddedWebsocketFramer::new(websocket); - match framer.connect(stream, buffer, websocket_options).await { - Ok(Some(_)) => {} - Ok(None) => {} - Err(error) => return Err!(XRPLWebsocketException::from(error)), - } - - Ok(AsyncWebsocketClient { - inner: framer, - status: PhantomData::, - }) - } -} - -impl AsyncWebsocketClient { - /// Encode a message to be sent over the websocket. - pub fn encode( - &mut self, - message_type: EmbeddedWebsocketSendMessageType, - end_of_message: bool, - from: &[u8], - to: &mut [u8], - ) -> Result - where - E: Debug, - { - match self - .inner - .encode::(message_type, end_of_message, from, to) - { - Ok(bytes_written) => Ok(bytes_written), - Err(error) => Err!(XRPLWebsocketException::from(error)), - } - } - - /// Send a message over the websocket. - pub async fn send<'b, E, R: serde::Serialize>( - &mut self, - stream: &mut (impl Sink<&'b [u8], Error = E> + Unpin), - stream_buf: &'b mut [u8], - end_of_message: bool, - frame_buf: R, - ) -> Result<()> - where - E: Debug, - { - match serde_json::to_vec(&frame_buf) { - Ok(frame_buf) => match self - .inner - .write( - stream, - stream_buf, - EmbeddedWebsocketSendMessageType::Text, - end_of_message, - frame_buf.as_slice(), - ) - .await - { - Ok(()) => Ok(()), - Err(error) => Err!(XRPLWebsocketException::from(error)), - }, - Err(error) => Err!(error), - } - } - - /// Close the websocket connection. - pub async fn close<'b, E>( - &mut self, - stream: &mut (impl Sink<&'b [u8], Error = E> + Unpin), - stream_buf: &'b mut [u8], - close_status: EmbeddedWebsocketCloseStatusCode, - status_description: Option<&str>, - ) -> Result<()> - where - E: Debug, - { - match self - .inner - .close(stream, stream_buf, close_status, status_description) - .await - { - Ok(()) => Ok(()), - Err(error) => Err!(XRPLWebsocketException::from(error)), - } - } - - /// Read a message from the websocket. - pub async fn next<'a, B: Deref, E>( - &'a mut self, - stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), - buffer: &'a mut [u8], - ) -> Option>> - // TODO: Change to Response as soon as implemented - where - E: Debug, - { - match self.inner.read(stream, buffer).await { - Some(Ok(read_result)) => Some(Ok(read_result)), - Some(Err(error)) => Some(Err!(XRPLWebsocketException::from(error))), - None => None, - } - } - - /// Read a message from the websocket. - /// - /// This is similar to the `next` method, but returns a `Result>` rather than an `Option>`, making for easy use with the ? operator. - pub async fn try_next<'a, B: Deref, E>( - &'a mut self, - stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), - buffer: &'a mut [u8], - ) -> Result>> - // TODO: Change to Response as soon as implemented - where - E: Debug, - { - match self.inner.read(stream, buffer).await { - Some(Ok(read_result)) => Ok(Some(read_result)), - Some(Err(error)) => Err!(XRPLWebsocketException::from(error)), - None => Ok(None), - } - } -} diff --git a/src/asynch/clients/exceptions.rs b/src/asynch/clients/exceptions.rs deleted file mode 100644 index b0e249bd..00000000 --- a/src/asynch/clients/exceptions.rs +++ /dev/null @@ -1,60 +0,0 @@ -use core::fmt::Debug; -use core::str::Utf8Error; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -use embedded_websocket::framer_async::FramerError; -use thiserror_no_std::Error; - -#[derive(Debug, Error)] -pub enum XRPLClientException { - #[error("{0:?}")] - Serde(#[from] serde_json::Error), -} - -#[derive(Debug, Error)] -pub enum XRPLWebsocketException { - #[error("Unable to connect to websocket")] - UnableToConnect(tokio_tungstenite::tungstenite::Error), - // FramerError - #[error("I/O error: {0:?}")] - Io(E), - #[error("Frame too large (size: {0:?})")] - FrameTooLarge(usize), - #[error("Failed to interpret u8 to string (error: {0:?})")] - Utf8(Utf8Error), - #[error("Invalid HTTP header")] - HttpHeader, - #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] - #[error("Websocket error: {0:?}")] - WebSocket(embedded_websocket::Error), - #[error("Disconnected")] - Disconnected, - #[error("Read buffer is too small (size: {0:?})")] - RxBufferTooSmall(usize), - #[error("No response")] - NoResponse, - #[error("No result")] - NoResult, - #[error("No info")] - NoInfo, - #[error("Unexpected message type")] - UnexpectedMessageType, -} - -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -impl From> for XRPLWebsocketException { - fn from(value: FramerError) -> Self { - match value { - FramerError::Io(e) => XRPLWebsocketException::Io(e), - FramerError::FrameTooLarge(e) => XRPLWebsocketException::FrameTooLarge(e), - FramerError::Utf8(e) => XRPLWebsocketException::Utf8(e), - FramerError::HttpHeader(_) => XRPLWebsocketException::HttpHeader, - FramerError::WebSocket(e) => XRPLWebsocketException::WebSocket(e), - FramerError::Disconnected => XRPLWebsocketException::Disconnected, - FramerError::RxBufferTooSmall(e) => XRPLWebsocketException::RxBufferTooSmall(e), - FramerError::UnableToConnect(e) => XRPLWebsocketException::UnableToConnect(e), - } - } -} - -#[cfg(feature = "std")] -impl alloc::error::Error for XRPLWebsocketException {} diff --git a/src/asynch/clients/tungstenite.rs b/src/asynch/clients/tungstenite.rs deleted file mode 100644 index 1e1a6cc5..00000000 --- a/src/asynch/clients/tungstenite.rs +++ /dev/null @@ -1,181 +0,0 @@ -use super::{ - exceptions::XRPLWebsocketException, Client, CommonFields, WebsocketClient, WebsocketClosed, - WebsocketOpen, -}; -use crate::models::requests; -use crate::models::results::{self, XRPLResponse}; -use crate::Err; - -use alloc::sync::Arc; -use anyhow::Result; -use core::marker::PhantomData; -use core::{pin::Pin, task::Poll}; -use futures::lock::Mutex; -use futures::{FutureExt, Sink, Stream, StreamExt}; -use futures_util::SinkExt; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use tokio::net::TcpStream; -use tokio_tungstenite::{ - connect_async as tungstenite_connect_async, MaybeTlsStream as TungsteniteMaybeTlsStream, - WebSocketStream as TungsteniteWebsocketStream, -}; -use url::Url; - -pub use tokio_tungstenite::tungstenite::Message as TungsteniteMessage; - -pub(crate) enum ContextWaker { - Read, - Write, -} - -pub type AsyncWebsocketConnection = - Arc>>>; - -pub struct AsyncWebsocketClient { - inner: AsyncWebsocketConnection, - status: PhantomData, -} - -impl<'a, Status> WebsocketClient for AsyncWebsocketClient {} - -impl<'a, I> Sink for AsyncWebsocketClient -where - I: serde::Serialize, - Self: Unpin, -{ - type Error = anyhow::Error; - - fn poll_ready( - self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { - let mut guard = futures::ready!(self.inner.lock().poll_unpin(cx)); - match Pin::new(&mut *guard).poll_ready(cx) { - Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), - Poll::Pending => Poll::Pending, - } - } - - fn start_send(self: core::pin::Pin<&mut Self>, item: I) -> Result<()> { - match serde_json::to_string(&item) { - Ok(json) => { - // cannot use ready! macro here because of the return type - let _ = self.inner.lock().then(|mut guard| async move { - match Pin::new(&mut *guard) - .send(TungsteniteMessage::Text(json)) - .await - { - Ok(_) => Ok(()), - Err(error) => Err!(error), - } - }); - Ok(()) - } - Err(error) => Err!(error), - } - } - - fn poll_flush( - self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { - let mut guard = futures::ready!(self.inner.lock().poll_unpin(cx)); - match Pin::new(&mut *guard).poll_flush(cx) { - Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), - Poll::Pending => Poll::Pending, - } - } - - fn poll_close( - self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { - let mut guard = futures::ready!(self.inner.lock().poll_unpin(cx)); - match Pin::new(&mut *guard).poll_close(cx) { - Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), - Poll::Pending => Poll::Pending, - } - } -} - -impl<'a> Stream for AsyncWebsocketClient { - type Item = Result; - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> Poll> { - let mut guard = futures::ready!(self.inner.lock().poll_unpin(cx)); - match Pin::new(&mut *guard).poll_next(cx) { - Poll::Ready(Some(item)) => match item { - Ok(message) => match message { - TungsteniteMessage::Text(response) => match serde_json::from_str(&response) { - Ok(response) => Poll::Ready(Some(Ok(response))), - Err(error) => Poll::Ready(Some(Err!(error))), - }, - _ => Poll::Ready(Some(Err!( - XRPLWebsocketException::::UnexpectedMessageType - ))), - }, - Err(error) => Poll::Ready(Some(Err!(error))), - }, - Poll::Ready(None) => Poll::Ready(None), - Poll::Pending => Poll::Pending, - } - } -} - -impl<'a> AsyncWebsocketClient { - pub async fn open(uri: Url) -> Result> { - match tungstenite_connect_async(uri).await { - Ok((websocket_stream, _)) => Ok(AsyncWebsocketClient { - inner: Arc::new(Mutex::new(websocket_stream)), - status: PhantomData::, - }), - Err(error) => { - Err!(XRPLWebsocketException::UnableToConnect::( - error - )) - } - } - } -} - -impl<'a> Client<'a> for AsyncWebsocketClient { - async fn request(&self, req: impl Serialize) -> Result> - where - T: for<'de> Deserialize<'de>, - { - let mut this = self.inner.lock().await; - let request = serde_json::to_string(&req).unwrap(); - this.send(TungsteniteMessage::Text(request)).await.unwrap(); - while let Some(Ok(message)) = this.next().await { - match message { - TungsteniteMessage::Text(response) => { - let response = serde_json::from_str(&response).unwrap(); - return Ok(response); - } - _ => return Err!(XRPLWebsocketException::::UnexpectedMessageType), - } - } - - Err!(XRPLWebsocketException::::NoResponse) - } - - async fn get_common_fields(&self) -> Result> { - let server_state = self - .request::(requests::ServerState::new(None)) - .await?; - let state = server_state.result.state; - let common_fields = CommonFields { - network_id: state.network_id, - build_version: Some(state.build_version), - }; - - Ok(common_fields) - } -} From 44c3649f4f0b99b1903a0e3e4530d40ebdf6d573 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Tue, 18 Jun 2024 07:43:22 +0000 Subject: [PATCH 17/19] sort imports and fix dockerfile --- .devcontainer/Dockerfile | 2 +- src/asynch/clients/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 95eea2ad..f56adafc 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/devcontainers/rust:1-1-bookworm +FROM mcr.microsoft.com/devcontainers/rust:1.0-1-bullseye # [Optional] Uncomment this section to install additional OS packages. # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index 9dfe17f7..55648352 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -5,10 +5,10 @@ mod websocket; use alloc::borrow::Cow; use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; +use serde::{Deserialize, Serialize}; pub use async_client::*; pub use json_rpc::*; -use serde::{Deserialize, Serialize}; pub use websocket::*; pub type MultiExecutorMutex = CriticalSectionRawMutex; From f5b497084260bc6a297fb7dda9712ed744d0c2ab Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Tue, 18 Jun 2024 17:04:07 +0000 Subject: [PATCH 18/19] handle unwraps --- src/asynch/account/mod.rs | 11 +- src/asynch/clients/client.rs | 2 +- src/asynch/ledger/mod.rs | 12 +- src/asynch/transaction/exceptions.rs | 7 + src/asynch/transaction/mod.rs | 123 ++++++++++----- src/models/amount/exceptions.rs | 4 +- src/models/amount/issued_currency_amount.rs | 98 +----------- src/models/amount/mod.rs | 3 +- src/models/amount/xrp_amount.rs | 147 ++++-------------- src/models/results/exceptions.rs | 10 ++ src/models/results/mod.rs | 69 ++++++-- src/models/transactions/mod.rs | 9 +- .../transactions/nftoken_accept_offer.rs | 23 +-- .../transactions/nftoken_create_offer.rs | 25 +-- 14 files changed, 222 insertions(+), 321 deletions(-) create mode 100644 src/models/results/exceptions.rs diff --git a/src/asynch/account/mod.rs b/src/asynch/account/mod.rs index 5c202636..bf4078f4 100644 --- a/src/asynch/account/mod.rs +++ b/src/asynch/account/mod.rs @@ -4,6 +4,7 @@ use anyhow::Result; use crate::{ core::addresscodec::{is_valid_xaddress, xaddress_to_classic_address}, models::{ledger::AccountRoot, requests::AccountInfo, results}, + Err, }; use super::clients::AsyncClient; @@ -25,10 +26,10 @@ pub async fn get_account_root<'a>( ) -> Result> { let mut classic_address = address; if is_valid_xaddress(&classic_address) { - classic_address = xaddress_to_classic_address(&classic_address) - .unwrap() - .0 - .into(); + classic_address = match xaddress_to_classic_address(&classic_address) { + Ok(addr) => addr.0.into(), + Err(e) => return Err!(e), + }; } let account_info = client .request::(AccountInfo::new( @@ -42,5 +43,5 @@ pub async fn get_account_root<'a>( )) .await?; - Ok(account_info.result.unwrap().account_data) + Ok(account_info.try_into_result()?.account_data) } diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs index 2847b370..772ffe0e 100644 --- a/src/asynch/clients/client.rs +++ b/src/asynch/clients/client.rs @@ -50,7 +50,7 @@ pub trait Client { let server_state = self .request_impl::(ServerState::new(None)) .await?; - let state = server_state.result.unwrap().state; + let state = server_state.try_into_result()?.state; let common_fields = CommonFields { network_id: state.network_id, build_version: Some(state.build_version), diff --git a/src/asynch/ledger/mod.rs b/src/asynch/ledger/mod.rs index d17c107a..c16d718f 100644 --- a/src/asynch/ledger/mod.rs +++ b/src/asynch/ledger/mod.rs @@ -1,4 +1,4 @@ -use core::cmp::min; +use core::{cmp::min, convert::TryInto}; use alloc::string::ToString; use anyhow::Result; @@ -29,7 +29,7 @@ pub async fn get_latest_validated_ledger_sequence<'a>(client: &'a impl AsyncClie )) .await?; - Ok(ledger_response.result.unwrap().ledger_index) + Ok(ledger_response.try_into_result()?.ledger_index) } pub enum FeeType { @@ -46,8 +46,8 @@ pub async fn get_fee<'a>( let fee_request = Fee::new(None); match client.request::, _>(fee_request).await { Ok(response) => { - let drops = response.result.unwrap().drops; - let fee = match_fee_type(fee_type, drops).unwrap(); + let drops = response.try_into_result()?.drops; + let fee = match_fee_type(fee_type, drops)?; if let Some(max_fee) = max_fee { Ok(XRPAmount::from(min(max_fee, fee).to_string())) @@ -61,8 +61,8 @@ pub async fn get_fee<'a>( fn match_fee_type(fee_type: Option, drops: Drops<'_>) -> Result { match fee_type { - None | Some(FeeType::Open) => Ok(drops.open_ledger_fee.0.to_string().parse().unwrap()), - Some(FeeType::Minimum) => Ok(drops.minimum_fee.0.to_string().parse().unwrap()), + None | Some(FeeType::Open) => Ok(drops.open_ledger_fee.try_into()?), + Some(FeeType::Minimum) => Ok(drops.minimum_fee.try_into()?), Some(FeeType::Dynamic) => unimplemented!("Dynamic fee calculation not yet implemented"), } } diff --git a/src/asynch/transaction/exceptions.rs b/src/asynch/transaction/exceptions.rs index 4afc1d47..92e8d8f9 100644 --- a/src/asynch/transaction/exceptions.rs +++ b/src/asynch/transaction/exceptions.rs @@ -1,3 +1,6 @@ +use core::num::ParseIntError; + +use alloc::borrow::Cow; use thiserror_no_std::Error; use crate::models::amount::XRPAmount; @@ -6,4 +9,8 @@ use crate::models::amount::XRPAmount; pub enum XRPLTransactionException<'a> { #[error("Fee of {0:?} Drops is much higher than a typical XRP transaction fee. This may be a mistake. If intentional, please use `check_fee = false`")] FeeUnusuallyHigh(XRPAmount<'a>), + #[error("Unable to parse rippled version: {0}")] + ParseRippledVersionError(ParseIntError), + #[error("Invalid rippled version: {0}")] + InvalidRippledVersion(Cow<'a, str>), } diff --git a/src/asynch/transaction/mod.rs b/src/asynch/transaction/mod.rs index 201869a7..6568723f 100644 --- a/src/asynch/transaction/mod.rs +++ b/src/asynch/transaction/mod.rs @@ -18,6 +18,7 @@ use alloc::vec::Vec; use anyhow::Result; use core::convert::TryInto; use core::fmt::Debug; +use exceptions::XRPLTransactionException; use rust_decimal::Decimal; use serde::Serialize; use strum::IntoEnumIterator; @@ -42,7 +43,7 @@ where let txn = transaction.clone(); let txn_common_fields = transaction.get_mut_common_fields(); let common_fields = client.get_common_fields().await?; - if txn_common_fields.network_id.is_none() && txn_needs_network_id(common_fields.clone()) { + if txn_common_fields.network_id.is_none() && txn_needs_network_id(common_fields.clone())? { txn_common_fields.network_id = common_fields.network_id; } if txn_common_fields.sequence.is_none() { @@ -72,14 +73,16 @@ where F: IntoEnumIterator + Serialize + Debug + PartialEq, { let mut net_fee = XRPAmount::from("10"); - let mut base_fee; + let base_fee; if let Some(client) = client { net_fee = get_fee(client, None, None).await?; base_fee = match transaction.get_transaction_type() { TransactionType::EscrowFinish => calculate_base_fee_for_escrow_finish( net_fee.clone(), - Some(transaction.get_field_value("fulfillment").unwrap().into()), - ), + transaction + .get_field_value("fulfillment")? + .map(|fulfillment| fulfillment.into()), + )?, // TODO: same for TransactionType::AMMCreate TransactionType::AccountDelete => get_owner_reserve_from_response(client).await?, _ => net_fee.clone(), @@ -88,18 +91,23 @@ where base_fee = match transaction.get_transaction_type() { TransactionType::EscrowFinish => calculate_base_fee_for_escrow_finish( net_fee.clone(), - Some(transaction.get_field_value("fulfillment").unwrap().into()), - ), + transaction + .get_field_value("fulfillment")? + .map(|fulfillment| fulfillment.into()), + )?, // TODO: same for TransactionType::AMMCreate TransactionType::AccountDelete => XRPAmount::from(OWNER_RESERVE), _ => net_fee.clone(), }; } + let mut base_fee_decimal: Decimal = base_fee.try_into()?; if let Some(signers_count) = signers_count { - base_fee += net_fee * (1 + signers_count); + let net_fee_decimal: Decimal = net_fee.try_into()?; + let signer_count_fee_decimal: Decimal = (1 + signers_count).into(); + base_fee_decimal += &(net_fee_decimal * signer_count_fee_decimal); } - Ok(base_fee.ceil()) + Ok(base_fee_decimal.ceil().into()) } async fn get_owner_reserve_from_response(client: &impl AsyncClient) -> Result> { @@ -107,8 +115,7 @@ async fn get_owner_reserve_from_response(client: &impl AsyncClient) -> Result, _>(ServerState::new(None)) .await?; match owner_reserve_response - .result - .unwrap() + .try_into_result()? .state .validated_ledger { @@ -120,37 +127,53 @@ async fn get_owner_reserve_from_response(client: &impl AsyncClient) -> Result( net_fee: XRPAmount<'a>, fulfillment: Option>, -) -> XRPAmount<'a> { +) -> Result> { if let Some(fulfillment) = fulfillment { - return calculate_based_on_fulfillment(fulfillment, net_fee); + calculate_based_on_fulfillment(fulfillment, net_fee) + } else { + Ok(net_fee) } - net_fee } fn calculate_based_on_fulfillment<'a>( fulfillment: Cow, - net_fee: XRPAmount<'a>, -) -> XRPAmount<'a> { + net_fee: XRPAmount<'_>, +) -> Result> { let fulfillment_bytes: Vec = fulfillment.chars().map(|c| c as u8).collect(); - let net_fee_f64: f64 = net_fee.try_into().unwrap(); + let net_fee_f64: f64 = net_fee.try_into()?; let base_fee_string = (net_fee_f64 * (33.0 + (fulfillment_bytes.len() as f64 / 16.0))).to_string(); - let base_fee_decimal: Decimal = base_fee_string.parse().unwrap(); + let base_fee: XRPAmount = base_fee_string.into(); + let base_fee_decimal: Decimal = base_fee.try_into()?; - XRPAmount::from(base_fee_decimal.ceil()) + Ok(base_fee_decimal.ceil().into()) } -fn txn_needs_network_id(common_fields: CommonFields<'_>) -> bool { - common_fields.network_id.unwrap() > RESTRICTED_NETWORKS as u32 - && is_not_later_rippled_version( - REQUIRED_NETWORKID_VERSION.into(), - common_fields.build_version.unwrap().into(), - ) +fn txn_needs_network_id(common_fields: CommonFields<'_>) -> Result { + let is_higher_restricted_networks = if let Some(network_id) = common_fields.network_id { + network_id > RESTRICTED_NETWORKS as u32 + } else { + false + }; + if let Some(build_version) = common_fields.build_version { + match is_not_later_rippled_version(REQUIRED_NETWORKID_VERSION.into(), build_version.into()) + { + Ok(is_not_later_rippled_version) => { + Ok(is_higher_restricted_networks && is_not_later_rippled_version) + } + Err(e) => Err!(e), + } + } else { + Ok(false) + } } -fn is_not_later_rippled_version(source: String, target: String) -> bool { +fn is_not_later_rippled_version<'a>( + source: String, + target: String, +) -> Result> { if source == target { - true + Ok(true) } else { let source_decomp = source .split('.') @@ -161,17 +184,25 @@ fn is_not_later_rippled_version(source: String, target: String) -> bool { .map(|i| i.to_string()) .collect::>(); let (source_major, source_minor) = ( - source_decomp[0].parse::().unwrap(), - source_decomp[1].parse::().unwrap(), + source_decomp[0] + .parse::() + .map_err(XRPLTransactionException::ParseRippledVersionError)?, + source_decomp[1] + .parse::() + .map_err(XRPLTransactionException::ParseRippledVersionError)?, ); let (target_major, target_minor) = ( - target_decomp[0].parse::().unwrap(), - target_decomp[1].parse::().unwrap(), + target_decomp[0] + .parse::() + .map_err(XRPLTransactionException::ParseRippledVersionError)?, + target_decomp[1] + .parse::() + .map_err(XRPLTransactionException::ParseRippledVersionError)?, ); if source_major != target_major { - source_major < target_major + Ok(source_major < target_major) } else if source_minor != target_minor { - source_minor < target_minor + Ok(source_minor < target_minor) } else { let source_patch = source_decomp[2] .split('-') @@ -181,24 +212,30 @@ fn is_not_later_rippled_version(source: String, target: String) -> bool { .split('-') .map(|i| i.to_string()) .collect::>(); - let source_patch_version = source_patch[0].parse::().unwrap(); - let target_patch_version = target_patch[0].parse::().unwrap(); + let source_patch_version = source_patch[0] + .parse::() + .map_err(XRPLTransactionException::ParseRippledVersionError)?; + let target_patch_version = target_patch[0] + .parse::() + .map_err(XRPLTransactionException::ParseRippledVersionError)?; if source_patch_version != target_patch_version { - source_patch_version < target_patch_version + Ok(source_patch_version < target_patch_version) } else if source_patch.len() != target_patch.len() { - source_patch.len() < target_patch.len() + Ok(source_patch.len() < target_patch.len()) } else if source_patch.len() == 2 { - if source_patch[1].chars().next().unwrap() - != target_patch[1].chars().next().unwrap() - { - source_patch[1] < target_patch[1] + if source_patch[1].chars().next().ok_or( + XRPLTransactionException::InvalidRippledVersion("source patch version".into()), + )? != target_patch[1].chars().next().ok_or( + XRPLTransactionException::InvalidRippledVersion("target patch version".into()), + )? { + Ok(source_patch[1] < target_patch[1]) } else if source_patch[1].starts_with('b') { - &source_patch[1][1..] < &target_patch[1][1..] + Ok(&source_patch[1][1..] < &target_patch[1][1..]) } else { - &source_patch[1][2..] < &target_patch[1][2..] + Ok(&source_patch[1][2..] < &target_patch[1][2..]) } } else { - false + Ok(false) } } } diff --git a/src/models/amount/exceptions.rs b/src/models/amount/exceptions.rs index f20b9e1d..bbef37f1 100644 --- a/src/models/amount/exceptions.rs +++ b/src/models/amount/exceptions.rs @@ -6,8 +6,10 @@ use thiserror_no_std::Error; pub enum XRPLAmountException { #[error("Unable to convert amount `value` into `Decimal`.")] ToDecimalError(#[from] rust_decimal::Error), - #[error("Unable to convert amount `value` into `f64`.")] + #[error("Unable to convert amount float.")] ToFloatError(#[from] ParseFloatError), + #[error("Unable to convert amount integer.")] + ToIntError(#[from] core::num::ParseIntError), #[error("{0:?}")] FromSerdeError(#[from] serde_json::Error), } diff --git a/src/models/amount/issued_currency_amount.rs b/src/models/amount/issued_currency_amount.rs index 5a33549a..6c1d183c 100644 --- a/src/models/amount/issued_currency_amount.rs +++ b/src/models/amount/issued_currency_amount.rs @@ -1,10 +1,8 @@ -use crate::models::amount::exceptions::XRPLAmountException; use crate::models::Model; +use crate::{models::amount::exceptions::XRPLAmountException, Err}; use alloc::borrow::Cow; -use alloc::string::ToString; -use core::ops::{AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; +use core::convert::TryInto; use core::str::FromStr; -use core::{convert::TryInto, ops::Add}; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; @@ -28,12 +26,12 @@ impl<'a> IssuedCurrencyAmount<'a> { } impl<'a> TryInto for IssuedCurrencyAmount<'a> { - type Error = XRPLAmountException; + type Error = anyhow::Error; fn try_into(self) -> Result { match Decimal::from_str(&self.value) { Ok(decimal) => Ok(decimal), - Err(decimal_error) => Err(XRPLAmountException::ToDecimalError(decimal_error)), + Err(decimal_error) => Err!(XRPLAmountException::ToDecimalError(decimal_error)), } } } @@ -49,91 +47,3 @@ impl<'a> Ord for IssuedCurrencyAmount<'a> { self.value.cmp(&other.value) } } - -impl<'a> Add for IssuedCurrencyAmount<'a> { - type Output = Self; - - fn add(self, other: Self) -> Self { - let value = - Decimal::from_str(&self.value).unwrap() + Decimal::from_str(&other.value).unwrap(); - Self { - currency: self.currency, - issuer: self.issuer, - value: value.to_string().into(), - } - } -} - -impl<'a> AddAssign for IssuedCurrencyAmount<'a> { - fn add_assign(&mut self, other: Self) { - let value = - Decimal::from_str(&self.value).unwrap() + Decimal::from_str(&other.value).unwrap(); - self.value = value.to_string().into(); - } -} - -impl<'a> Sub for IssuedCurrencyAmount<'a> { - type Output = Self; - - fn sub(self, other: Self) -> Self { - let value = - Decimal::from_str(&self.value).unwrap() - Decimal::from_str(&other.value).unwrap(); - Self { - currency: self.currency, - issuer: self.issuer, - value: value.to_string().into(), - } - } -} - -impl<'a> SubAssign for IssuedCurrencyAmount<'a> { - fn sub_assign(&mut self, other: Self) { - let value = - Decimal::from_str(&self.value).unwrap() - Decimal::from_str(&other.value).unwrap(); - self.value = value.to_string().into(); - } -} - -impl<'a> Mul for IssuedCurrencyAmount<'a> { - type Output = Self; - - fn mul(self, other: Self) -> Self { - let value = - Decimal::from_str(&self.value).unwrap() * Decimal::from_str(&other.value).unwrap(); - Self { - currency: self.currency, - issuer: self.issuer, - value: value.to_string().into(), - } - } -} - -impl<'a> MulAssign for IssuedCurrencyAmount<'a> { - fn mul_assign(&mut self, other: Self) { - let value = - Decimal::from_str(&self.value).unwrap() * Decimal::from_str(&other.value).unwrap(); - self.value = value.to_string().into(); - } -} - -impl<'a> Div for IssuedCurrencyAmount<'a> { - type Output = Self; - - fn div(self, other: Self) -> Self { - let value = - Decimal::from_str(&self.value).unwrap() / Decimal::from_str(&other.value).unwrap(); - Self { - currency: self.currency, - issuer: self.issuer, - value: value.to_string().into(), - } - } -} - -impl<'a> DivAssign for IssuedCurrencyAmount<'a> { - fn div_assign(&mut self, other: Self) { - let value = - Decimal::from_str(&self.value).unwrap() / Decimal::from_str(&other.value).unwrap(); - self.value = value.to_string().into(); - } -} diff --git a/src/models/amount/mod.rs b/src/models/amount/mod.rs index 85430986..313e7107 100644 --- a/src/models/amount/mod.rs +++ b/src/models/amount/mod.rs @@ -7,7 +7,6 @@ pub use issued_currency_amount::*; use rust_decimal::Decimal; pub use xrp_amount::*; -use crate::models::amount::exceptions::XRPLAmountException; use crate::models::Model; use serde::{Deserialize, Serialize}; use strum_macros::Display; @@ -20,7 +19,7 @@ pub enum Amount<'a> { } impl<'a> TryInto for Amount<'a> { - type Error = XRPLAmountException; + type Error = anyhow::Error; fn try_into(self) -> Result { match self { diff --git a/src/models/amount/xrp_amount.rs b/src/models/amount/xrp_amount.rs index c9bd73be..b4e6a6a5 100644 --- a/src/models/amount/xrp_amount.rs +++ b/src/models/amount/xrp_amount.rs @@ -1,14 +1,12 @@ -use crate::models::amount::exceptions::XRPLAmountException; use crate::models::Model; +use crate::{models::amount::exceptions::XRPLAmountException, Err}; use alloc::{ borrow::Cow, string::{String, ToString}, }; -use core::{ - convert::{TryFrom, TryInto}, - ops::{Add, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}, -}; -use core::{ops::AddAssign, str::FromStr}; +use anyhow::Result; +use core::convert::{TryFrom, TryInto}; +use core::str::FromStr; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -67,34 +65,48 @@ impl<'a> From for XRPAmount<'a> { } impl<'a> TryFrom for XRPAmount<'a> { - type Error = XRPLAmountException; + type Error = anyhow::Error; fn try_from(value: Value) -> Result { - let amount_string = - serde_json::to_string(&value).map_err(XRPLAmountException::FromSerdeError)?; - let amount_string = amount_string.clone().replace("\"", ""); - Ok(Self(amount_string.into())) + match serde_json::to_string(&value) { + Ok(amount_string) => { + let amount_string = amount_string.clone().replace("\"", ""); + Ok(Self(amount_string.into())) + } + Err(serde_error) => Err!(XRPLAmountException::FromSerdeError(serde_error)), + } } } impl<'a> TryInto for XRPAmount<'a> { - type Error = XRPLAmountException; + type Error = anyhow::Error; fn try_into(self) -> Result { match self.0.parse::() { Ok(f64_value) => Ok(f64_value), - Err(parse_error) => Err(XRPLAmountException::ToFloatError(parse_error)), + Err(parse_error) => Err!(XRPLAmountException::ToFloatError(parse_error)), + } + } +} + +impl<'a> TryInto for XRPAmount<'a> { + type Error = anyhow::Error; + + fn try_into(self) -> Result { + match self.0.parse::() { + Ok(u32_value) => Ok(u32_value), + Err(parse_error) => Err!(XRPLAmountException::ToIntError(parse_error)), } } } impl<'a> TryInto for XRPAmount<'a> { - type Error = XRPLAmountException; + type Error = anyhow::Error; fn try_into(self) -> Result { match Decimal::from_str(&self.0) { Ok(decimal) => Ok(decimal), - Err(decimal_error) => Err(XRPLAmountException::ToDecimalError(decimal_error)), + Err(decimal_error) => Err!(XRPLAmountException::ToDecimalError(decimal_error)), } } } @@ -110,108 +122,3 @@ impl<'a> Ord for XRPAmount<'a> { self.0.cmp(&other.0) } } - -impl<'a> Add for XRPAmount<'a> { - type Output = Self; - - fn add(self, other: Self) -> Self { - let self_decimal: Decimal = self.try_into().unwrap(); - let other_decimal: Decimal = other.try_into().unwrap(); - let result_decimal = self_decimal + other_decimal; - result_decimal.into() - } -} - -impl<'a> AddAssign for XRPAmount<'a> { - fn add_assign(&mut self, other: Self) { - let self_decimal: Decimal = self.clone().try_into().unwrap(); - let other_decimal: Decimal = other.try_into().unwrap(); - let result_decimal = self_decimal + other_decimal; - *self = result_decimal.into(); - } -} - -impl<'a> Sub for XRPAmount<'a> { - type Output = Self; - - fn sub(self, other: Self) -> Self { - let self_decimal: Decimal = self.try_into().unwrap(); - let other_decimal: Decimal = other.try_into().unwrap(); - let result_decimal = self_decimal - other_decimal; - result_decimal.into() - } -} - -impl<'a> SubAssign for XRPAmount<'a> { - fn sub_assign(&mut self, other: Self) { - let self_decimal: Decimal = self.clone().try_into().unwrap(); - let other_decimal: Decimal = other.try_into().unwrap(); - let result_decimal = self_decimal - other_decimal; - *self = result_decimal.into(); - } -} - -impl<'a> Mul for XRPAmount<'a> { - type Output = Self; - - fn mul(self, other: Self) -> Self { - let self_decimal: Decimal = self.try_into().unwrap(); - let other_decimal: Decimal = other.try_into().unwrap(); - let result_decimal = self_decimal * other_decimal; - result_decimal.into() - } -} - -impl<'a> Mul for XRPAmount<'a> { - type Output = Self; - - fn mul(self, other: u8) -> Self { - let self_decimal: Decimal = self.try_into().unwrap(); - let other_decimal: Decimal = other.into(); - let result_decimal = self_decimal * other_decimal; - result_decimal.into() - } -} - -impl<'a> MulAssign for XRPAmount<'a> { - fn mul_assign(&mut self, other: Self) { - let self_decimal: Decimal = self.clone().try_into().unwrap(); - let other_decimal: Decimal = other.try_into().unwrap(); - let result_decimal = self_decimal * other_decimal; - *self = result_decimal.into(); - } -} - -impl<'a> Div for XRPAmount<'a> { - type Output = Self; - - fn div(self, other: Self) -> Self { - let self_decimal: Decimal = self.try_into().unwrap(); - let other_decimal: Decimal = other.try_into().unwrap(); - let result_decimal = self_decimal / other_decimal; - result_decimal.into() - } -} - -impl<'a> DivAssign for XRPAmount<'a> { - fn div_assign(&mut self, other: Self) { - let self_decimal: Decimal = self.clone().try_into().unwrap(); - let other_decimal: Decimal = other.try_into().unwrap(); - let result_decimal = self_decimal / other_decimal; - *self = result_decimal.into(); - } -} - -impl XRPAmount<'_> { - pub fn ceil(&self) -> Self { - let decimal: Decimal = self.clone().try_into().unwrap(); - let result_decimal = decimal.ceil(); - result_decimal.into() - } - - pub fn floor(&self) -> Self { - let decimal: Decimal = self.clone().try_into().unwrap(); - let result_decimal = decimal.floor(); - result_decimal.into() - } -} diff --git a/src/models/results/exceptions.rs b/src/models/results/exceptions.rs new file mode 100644 index 00000000..c43af12d --- /dev/null +++ b/src/models/results/exceptions.rs @@ -0,0 +1,10 @@ +use alloc::string::String; +use thiserror_no_std::Error; + +#[derive(Debug, Error)] +pub enum XRPLResultException { + #[error("Response error: {0}")] + ResponseError(String), + #[error("Expected result or error in the response.")] + ExpectedResultOrError, +} diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs index ae5db19e..ac618968 100644 --- a/src/models/results/mod.rs +++ b/src/models/results/mod.rs @@ -1,17 +1,21 @@ mod account_info; +mod exceptions; mod fee; mod ledger; mod server_state; pub use account_info::*; +pub use exceptions::*; pub use fee::*; pub use ledger::*; pub use server_state::*; -use alloc::{borrow::Cow, string::ToString, vec::Vec}; +use alloc::{borrow::Cow, format, string::ToString, vec::Vec}; use anyhow::Result; use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use crate::Err; + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum ResponseStatus { @@ -56,6 +60,34 @@ where if map.is_empty() { return Err(serde::de::Error::custom("Empty response")); } + let request = match map.remove("request") { + Some(request) => match serde_json::from_value(request) { + Ok(request) => request, + Err(error) => return Err(serde::de::Error::custom(error.to_string())), + }, + None => None, + }; + let result = match map.remove("result") { + Some(result) => match serde_json::from_value(result) { + Ok(result) => result, + Err(error) => return Err(serde::de::Error::custom(error.to_string())), + }, + None => None, + }; + let status = match map.remove("status") { + Some(status) => match serde_json::from_value(status) { + Ok(status) => status, + Err(error) => return Err(serde::de::Error::custom(error.to_string())), + }, + None => None, + }; + let r#type = match map.remove("type") { + Some(r#type) => match serde_json::from_value(r#type) { + Ok(r#type) => r#type, + Err(error) => return Err(serde::de::Error::custom(error.to_string())), + }, + None => None, + }; Ok(XRPLResponse { id: map.remove("id").map(|item| match item.as_str() { Some(item_str) => Cow::Owned(item_str.to_string()), @@ -74,18 +106,10 @@ where None => Cow::Borrowed(""), }), forwarded: map.remove("forwarded").and_then(|v| v.as_bool()), - request: map - .remove("request") - .map(|v| serde_json::from_value(v).unwrap()), - result: map - .remove("result") - .map(|v| serde_json::from_value(v).unwrap()), - status: map - .remove("status") - .map(|v| serde_json::from_value(v).unwrap()), - r#type: map - .remove("type") - .map(|v| serde_json::from_value(v).unwrap()), + request, + result, + status, + r#type, warning: map.remove("warning").map(|item| match item.as_str() { Some(item_str) => Cow::Owned(item_str.to_string()), None => Cow::Borrowed(""), @@ -97,10 +121,27 @@ where } } -impl<'a, Res, Req> XRPLResponse<'a, Res, Req> { +impl XRPLResponse<'_, Res, Req> { pub fn is_success(&self) -> bool { self.status == Some(ResponseStatus::Success) } + + pub fn try_into_result(self) -> Result { + match self.result { + Some(result) => Ok(result), + None => { + if let Some(error) = self.error { + Err!(XRPLResultException::ResponseError(format!( + "{}: {}", + error, + self.error_message.unwrap_or_default() + ))) + } else { + Err!(XRPLResultException::ExpectedResultOrError) + } + } + } + } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 422d01cc..c31ef68f 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -57,6 +57,7 @@ pub use trust_set::*; use crate::alloc::string::ToString; use crate::models::amount::XRPAmount; +use crate::Err; use crate::{_serde::txn_flags, serde_with_tag}; use alloc::borrow::Cow; use alloc::string::String; @@ -296,9 +297,11 @@ where fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, T>; - fn get_field_value(&self, field: &str) -> Option { - let transaction = serde_json::to_value(self).unwrap(); - transaction.get(field).map(|v| v.to_string()) + fn get_field_value(&self, field: &str) -> Result> { + match serde_json::to_value(self) { + Ok(value) => Ok(value.get(field).map(|v| v.to_string())), + Err(e) => Err!(e), + } } } diff --git a/src/models/transactions/nftoken_accept_offer.rs b/src/models/transactions/nftoken_accept_offer.rs index 458b4882..cc5c0cc4 100644 --- a/src/models/transactions/nftoken_accept_offer.rs +++ b/src/models/transactions/nftoken_accept_offer.rs @@ -7,7 +7,6 @@ use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use crate::models::amount::exceptions::XRPLAmountException; use crate::models::amount::XRPAmount; use crate::models::transactions::XRPLNFTokenAcceptOfferException; use crate::models::NoFlags; @@ -101,20 +100,14 @@ impl<'a> NFTokenAcceptOfferError for NFTokenAcceptOffer<'a> { } fn _get_nftoken_broker_fee_error(&self) -> Result<()> { if let Some(nftoken_broker_fee) = &self.nftoken_broker_fee { - let nftoken_broker_fee_decimal: Result = - nftoken_broker_fee.clone().try_into(); - match nftoken_broker_fee_decimal { - Ok(nftoken_broker_fee_dec) => { - if nftoken_broker_fee_dec.is_zero() { - Err!(XRPLNFTokenAcceptOfferException::ValueZero { - field: "nftoken_broker_fee".into(), - resource: "".into(), - }) - } else { - Ok(()) - } - } - Err(decimal_error) => Err!(decimal_error), + let nftoken_broker_fee: Decimal = nftoken_broker_fee.clone().try_into()?; + if nftoken_broker_fee.is_zero() { + Err!(XRPLNFTokenAcceptOfferException::ValueZero { + field: "nftoken_broker_fee".into(), + resource: "".into(), + }) + } else { + Ok(()) } } else { Ok(()) diff --git a/src/models/transactions/nftoken_create_offer.rs b/src/models/transactions/nftoken_create_offer.rs index c211c3e1..32bce3d4 100644 --- a/src/models/transactions/nftoken_create_offer.rs +++ b/src/models/transactions/nftoken_create_offer.rs @@ -13,7 +13,6 @@ use crate::models::{ transactions::{Memo, Signer, Transaction, TransactionType}, }; -use crate::models::amount::exceptions::XRPLAmountException; use crate::models::amount::{Amount, XRPAmount}; use crate::models::transactions::XRPLNFTokenCreateOfferException; use crate::Err; @@ -116,22 +115,14 @@ impl<'a> Transaction<'a, NFTokenCreateOfferFlag> for NFTokenCreateOffer<'a> { impl<'a> NFTokenCreateOfferError for NFTokenCreateOffer<'a> { fn _get_amount_error(&self) -> Result<()> { - let amount_into_decimal: Result = - self.amount.clone().try_into(); - match amount_into_decimal { - Ok(amount) => { - if !self.has_flag(&NFTokenCreateOfferFlag::TfSellOffer) && amount.is_zero() { - Err!(XRPLNFTokenCreateOfferException::ValueZero { - field: "amount".into(), - resource: "".into(), - }) - } else { - Ok(()) - } - } - Err(decimal_error) => { - Err!(decimal_error) - } + let amount: Decimal = self.amount.clone().try_into()?; + if !self.has_flag(&NFTokenCreateOfferFlag::TfSellOffer) && amount.is_zero() { + Err!(XRPLNFTokenCreateOfferException::ValueZero { + field: "amount".into(), + resource: "".into(), + }) + } else { + Ok(()) } } From 586afdd5156b41a850b4f36ba275c5616995cb87 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Tue, 18 Jun 2024 17:14:47 +0000 Subject: [PATCH 19/19] remove unnecessary lifetimes --- src/asynch/account/mod.rs | 14 +++++++------- src/asynch/ledger/mod.rs | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/asynch/account/mod.rs b/src/asynch/account/mod.rs index bf4078f4..ad03bc2f 100644 --- a/src/asynch/account/mod.rs +++ b/src/asynch/account/mod.rs @@ -9,10 +9,10 @@ use crate::{ use super::clients::AsyncClient; -pub async fn get_next_valid_seq_number<'a>( - address: Cow<'a, str>, - client: &'a impl AsyncClient, - ledger_index: Option>, +pub async fn get_next_valid_seq_number( + address: Cow<'_, str>, + client: &impl AsyncClient, + ledger_index: Option>, ) -> Result { let account_info = get_account_root(address, client, ledger_index.unwrap_or("current".into())).await?; @@ -20,9 +20,9 @@ pub async fn get_next_valid_seq_number<'a>( } pub async fn get_account_root<'a>( - address: Cow<'a, str>, - client: &'a impl AsyncClient, - ledger_index: Cow<'a, str>, + address: Cow<'_, str>, + client: &impl AsyncClient, + ledger_index: Cow<'_, str>, ) -> Result> { let mut classic_address = address; if is_valid_xaddress(&classic_address) { diff --git a/src/asynch/ledger/mod.rs b/src/asynch/ledger/mod.rs index c16d718f..78d19f95 100644 --- a/src/asynch/ledger/mod.rs +++ b/src/asynch/ledger/mod.rs @@ -13,7 +13,7 @@ use crate::models::{ use super::clients::AsyncClient; -pub async fn get_latest_validated_ledger_sequence<'a>(client: &'a impl AsyncClient) -> Result { +pub async fn get_latest_validated_ledger_sequence(client: &impl AsyncClient) -> Result { let ledger_response = client .request::(Ledger::new( None, @@ -38,13 +38,13 @@ pub enum FeeType { Dynamic, } -pub async fn get_fee<'a>( - client: &'a impl AsyncClient, +pub async fn get_fee( + client: &impl AsyncClient, max_fee: Option, fee_type: Option, -) -> Result> { +) -> Result> { let fee_request = Fee::new(None); - match client.request::, _>(fee_request).await { + match client.request::, _>(fee_request).await { Ok(response) => { let drops = response.try_into_result()?.drops; let fee = match_fee_type(fee_type, drops)?;