From 2597c29023b7d0b72ef67f746e7b3a711e61dfa1 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 6 Sep 2024 19:13:28 +0000 Subject: [PATCH 01/10] add faucet wallet generation --- Cargo.toml | 14 +- src/asynch/account/mod.rs | 27 ++-- src/asynch/clients/client.rs | 3 + src/asynch/clients/json_rpc/exceptions.rs | 4 + src/asynch/clients/json_rpc/mod.rs | 146 ++++++++++++++------- src/asynch/clients/websocket/_std.rs | 6 + src/asynch/mod.rs | 2 + src/asynch/wallet/exceptions.rs | 13 ++ src/asynch/wallet/mod.rs | 152 ++++++++++++++++++++++ src/models/amount/xrp_amount.rs | 16 ++- src/models/requests/mod.rs | 11 ++ src/models/results/mod.rs | 3 +- tests/integration/clients/mod.rs | 2 +- 13 files changed, 325 insertions(+), 74 deletions(-) create mode 100644 src/asynch/wallet/exceptions.rs create mode 100644 src/asynch/wallet/mod.rs diff --git a/Cargo.toml b/Cargo.toml index deda7588..2122cdb1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,16 +105,22 @@ default = [ "models", "utils", "helpers", - "websocket-std", + "json-rpc-std", ] models = ["core", "transactions", "requests", "ledger", "results"] transactions = ["core", "amounts", "currencies"] requests = ["core", "amounts", "currencies"] results = ["core", "amounts", "currencies"] ledger = ["core", "amounts", "currencies"] -helpers = ["account-helpers", "ledger-helpers", "transaction-helpers"] +helpers = [ + "account-helpers", + "ledger-helpers", + "transaction-helpers", + "wallet-helpers", +] account-helpers = ["amounts", "currencies", "requests", "results"] ledger-helpers = ["amounts", "currencies", "requests", "results"] +wallet-helpers = ["requests", "results"] transaction-helpers = [ "wallet", "amounts", @@ -126,8 +132,8 @@ transaction-helpers = [ ] amounts = ["core"] currencies = ["core"] -json-rpc = ["url", "reqwless", "embedded-nal-async"] -json-rpc-std = ["url", "reqwest"] +json-rpc = ["url", "reqwless", "embassy-sync", "embedded-nal-async"] +json-rpc-std = ["url", "reqwest", "embassy-sync", "tokio"] wallet = ["core"] websocket = [ "url", diff --git a/src/asynch/account/mod.rs b/src/asynch/account/mod.rs index e392853b..8111972f 100644 --- a/src/asynch/account/mod.rs +++ b/src/asynch/account/mod.rs @@ -1,4 +1,4 @@ -use alloc::borrow::Cow; +use alloc::{borrow::Cow, dbg}; use anyhow::Result; use crate::{ @@ -69,20 +69,17 @@ where Err(e) => return Err!(e), }; } - let account_info = client - .request( - AccountInfo::new( - None, - classic_address, - None, - Some(ledger_index), - None, - None, - None, - ) - .into(), - ) - .await?; + let request = AccountInfo::new( + None, + classic_address, + None, + Some(ledger_index), + None, + Some(true), + None, + ) + .into(); + let account_info = client.request(request).await?; Ok(account_info .try_into_result::>()? diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs index 0fec4d50..7a678ae2 100644 --- a/src/asynch/clients/client.rs +++ b/src/asynch/clients/client.rs @@ -6,11 +6,14 @@ use crate::models::{ use crate::utils::get_random_id; use alloc::borrow::{Cow, ToOwned}; use anyhow::Result; +use url::Url; #[allow(async_fn_in_trait)] pub trait Client { async fn request_impl<'a: 'b, 'b>(&self, request: XRPLRequest<'a>) -> Result>; + fn get_host(&self) -> Url; + fn set_request_id(&self, request: &mut XRPLRequest<'_>) -> () { let common_fields = request.get_common_fields_mut(); common_fields.id = match &common_fields.id { diff --git a/src/asynch/clients/json_rpc/exceptions.rs b/src/asynch/clients/json_rpc/exceptions.rs index 95e7a47a..21fc86f5 100644 --- a/src/asynch/clients/json_rpc/exceptions.rs +++ b/src/asynch/clients/json_rpc/exceptions.rs @@ -1,3 +1,4 @@ +use reqwest::Response; use thiserror_no_std::Error; #[derive(Debug, Error)] @@ -5,4 +6,7 @@ pub enum XRPLJsonRpcException { #[cfg(feature = "json-rpc")] #[error("Reqwless error")] ReqwlessError, + #[cfg(feature = "json-rpc-std")] + #[error("Request error: {0:?}")] + RequestError(Response), } diff --git a/src/asynch/clients/json_rpc/mod.rs b/src/asynch/clients/json_rpc/mod.rs index 95a8c4f2..c0a21fd5 100644 --- a/src/asynch/clients/json_rpc/mod.rs +++ b/src/asynch/clients/json_rpc/mod.rs @@ -1,93 +1,139 @@ -use alloc::{string::String, sync::Arc}; +use alloc::{ + dbg, + string::{String, ToString}, + sync::Arc, + vec, +}; use anyhow::Result; use embassy_sync::{blocking_mutex::raw::RawMutex, mutex::Mutex}; use serde::Serialize; +use serde_json::{Map, Value}; +use url::Url; -use crate::{models::results::XRPLResponse, Err}; +use crate::{ + asynch::wallet::get_faucet_url, + models::{requests::FundFaucet, results::XRPLResponse}, + Err, +}; mod exceptions; pub use exceptions::XRPLJsonRpcException; -use super::{client::Client, SingleExecutorMutex}; +use super::client::Client; + +pub trait XRPLFaucet: Client { + fn get_faucet_url(&self, url: Option) -> Result + where + Self: Sized + Client, + { + get_faucet_url(self, url) + } + + async fn request_funding(&self, url: Option, request: FundFaucet<'_>) -> Result<()>; +} /// Renames the requests field `command` to `method` for JSON-RPC. -fn request_to_json_rpc(request: &impl Serialize) -> Result { +fn request_to_json_rpc(request: &impl Serialize) -> Result { + let mut json_rpc_request = Map::new(); let mut request = match serde_json::to_value(request) { - Ok(request) => request, + Ok(request) => match request.as_object().cloned() { + Some(request) => request, + None => todo!("Handle non-object requests"), + }, Err(error) => return Err!(error), }; - if let Some(command) = request.get_mut("command") { - let method = command.take(); - request["method"] = method; - } - match serde_json::to_string(&request) { - Ok(request) => Ok(request), - Err(error) => Err!(error), + if let Some(command) = request.remove("command") { + json_rpc_request.insert("method".to_string(), command); + json_rpc_request.insert( + "params".to_string(), + serde_json::Value::Array(vec![Value::Object(request)]), + ); } + + Ok(Value::Object(json_rpc_request)) } #[cfg(feature = "json-rpc-std")] mod _std { - use crate::models::requests::XRPLRequest; + use crate::models::requests::{FundFaucet, XRPLRequest}; use super::*; + use alloc::{dbg, format, string::ToString}; use reqwest::Client as HttpClient; use url::Url; - pub struct AsyncJsonRpcClient - where - M: RawMutex, - { + pub struct AsyncJsonRpcClient { url: Url, - client: Arc>, } - impl AsyncJsonRpcClient - where - M: RawMutex, - { - pub fn new(url: Url) -> Self { - Self { - url, - client: Arc::new(Mutex::new(HttpClient::new())), - } + impl AsyncJsonRpcClient { + pub fn connect(url: Url) -> Self { + Self { url } } } - impl AsyncJsonRpcClient - where - M: RawMutex, - { - fn from(url: Url, client: HttpClient) -> Self { - Self { - url, - client: Arc::new(Mutex::new(client)), - } - } - } - - impl Client for AsyncJsonRpcClient - where - M: RawMutex, - { + impl Client for AsyncJsonRpcClient { async fn request_impl<'a: 'b, 'b>( &self, request: XRPLRequest<'a>, ) -> Result> { - let client = self.client.lock().await; - match client + let client = HttpClient::new(); + let request_json_rpc = request_to_json_rpc(&request)?; + dbg!(&request_json_rpc); + let response = client .post(self.url.as_ref()) - .body(request_to_json_rpc(&request)?) + .json(&request_json_rpc) .send() - .await - { - Ok(response) => match response.json().await { - Ok(response) => Ok(response), + .await; + dbg!(&response); + match response { + Ok(response) => match response.text().await { + Ok(response) => { + dbg!(&response); + Ok(serde_json::from_str::>(&response).unwrap()) + } Err(error) => Err!(error), }, Err(error) => Err!(error), } } + + fn get_host(&self) -> Url { + self.url.clone() + } + } + + impl XRPLFaucet for AsyncJsonRpcClient { + async fn request_funding(&self, url: Option, request: FundFaucet<'_>) -> Result<()> { + let faucet_url = self.get_faucet_url(url)?; + let client = HttpClient::new(); + let request_json_rpc = serde_json::to_value(&request).unwrap(); + dbg!(&request_json_rpc); + let response = client + .post(&faucet_url.to_string()) + .json(&request_json_rpc) + .send() + .await; + dbg!(&response); + match response { + Ok(response) => { + if response.status().is_success() { + dbg!("Success"); + dbg!(&response); + Ok(()) + } else { + dbg!("Error"); + dbg!(&response); + todo!() + // Err!(XRPLJsonRpcException::RequestError()) + } + } + Err(error) => { + dbg!("req Error"); + Err!(error) + } + } + } } } diff --git a/src/asynch/clients/websocket/_std.rs b/src/asynch/clients/websocket/_std.rs index a2935fce..ef05d70c 100644 --- a/src/asynch/clients/websocket/_std.rs +++ b/src/asynch/clients/websocket/_std.rs @@ -30,6 +30,7 @@ where { websocket: Arc>, websocket_base: Arc>>, + uri: Url, status: PhantomData, } @@ -141,6 +142,7 @@ where Ok(AsyncWebsocketClient { websocket: Arc::new(Mutex::new(stream)), websocket_base: Arc::new(Mutex::new(WebsocketBase::new())), + uri, status: PhantomData::, }) } @@ -199,6 +201,10 @@ impl Client for AsyncWebsocketClient where M: RawMutex, { + fn get_host(&self) -> Url { + self.uri.clone() + } + async fn request_impl<'a: 'b, 'b>( &self, mut request: XRPLRequest<'a>, diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index cc98777f..fc5acf16 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -11,3 +11,5 @@ pub mod clients; pub mod ledger; #[cfg(feature = "transaction-helpers")] pub mod transaction; +#[cfg(feature = "wallet-helpers")] +pub mod wallet; diff --git a/src/asynch/wallet/exceptions.rs b/src/asynch/wallet/exceptions.rs new file mode 100644 index 00000000..9571cd29 --- /dev/null +++ b/src/asynch/wallet/exceptions.rs @@ -0,0 +1,13 @@ +use thiserror_no_std::Error; + +#[derive(Error, Debug)] +pub enum XRPLFaucetException { + #[error( + "Cannot fund an account on an issuing chain. Accounts must be created via the bridge." + )] + CannotFundSidechainAccount, + #[error("Cannot derive a faucet URL from the client host.")] + CannotDeriveFaucetUrl, + #[error("Funding request timed out.")] + FundingTimeout, +} diff --git a/src/asynch/wallet/mod.rs b/src/asynch/wallet/mod.rs new file mode 100644 index 00000000..b21b3a99 --- /dev/null +++ b/src/asynch/wallet/mod.rs @@ -0,0 +1,152 @@ +pub mod exceptions; + +use alloc::{borrow::Cow, dbg}; +use anyhow::Result; +use exceptions::XRPLFaucetException; +use url::Url; + +use crate::{ + asynch::account::get_next_valid_seq_number, + models::{amount::XRPAmount, requests::FundFaucet}, + wallet::Wallet, + Err, +}; + +use super::{ + account::get_xrp_balance, + clients::{AsyncClient, Client, XRPLFaucet}, +}; + +const TEST_FAUCET_URL: &'static str = "https://faucet.altnet.rippletest.net/accounts"; +const DEV_FAUCET_URL: &'static str = "https://faucet.devnet.rippletest.net/accounts"; + +const TIMEOUT_SECS: u8 = 40; + +pub async fn generate_faucet_wallet<'a, C>( + client: &C, + wallet: Option, + faucet_host: Option, + usage_context: Option>, + user_agent: Option>, +) -> Result +where + C: XRPLFaucet + Client, +{ + let faucet_url = get_faucet_url(client, faucet_host)?; + let wallet = match wallet { + Some(wallet) => wallet, + None => match Wallet::create(None) { + Ok(wallet) => wallet, + Err(error) => return Err!(error), + }, + }; + let address = &wallet.classic_address; + dbg!(address); + let starting_balance = 0.into(); // check_balance(client, address.into()).await; + let user_agent = user_agent.unwrap_or("xrpl-rust".into()); + fund_wallet( + client, + faucet_url, + address.into(), + usage_context, + Some(user_agent), + ) + .await?; + let mut is_funded = false; + for _ in 0..TIMEOUT_SECS { + // wait 1 second + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + if !is_funded { + let balance = check_balance(client, address.into()).await; + dbg!(&balance); + dbg!(&starting_balance); + if balance > starting_balance { + is_funded = true; + } + } else { + // wait until the ledger knows about the wallets existence + match get_next_valid_seq_number(address.into(), client, None).await { + Ok(_sequence) => { + return Ok(wallet); + } + Err(_) => continue, + } + } + } + + Err!(XRPLFaucetException::FundingTimeout) +} + +pub fn get_faucet_url(client: &C, url: Option) -> Result +where + C: Client, +{ + if let Some(url) = url { + Ok(url) + } else { + let host = client.get_host(); + let host_str = host.host_str().unwrap(); + if host_str.contains("altnet") || host_str.contains("testnet") { + match Url::parse(TEST_FAUCET_URL) { + Ok(url) => return Ok(url), + Err(error) => return Err!(error), + } + } else if host_str.contains("devnet") { + match Url::parse(DEV_FAUCET_URL) { + Ok(url) => Ok(url), + Err(error) => Err!(error), + } + } else if host_str.contains("sidechain-net2") { + Err!(XRPLFaucetException::CannotFundSidechainAccount) + } else { + Err!(XRPLFaucetException::CannotDeriveFaucetUrl) + } + } +} + +async fn check_balance<'a: 'b, 'b, C>(client: &C, address: Cow<'a, str>) -> XRPAmount<'b> +where + C: Client, +{ + get_xrp_balance(address, client, None).await.unwrap() + // .unwrap_or(XRPAmount::default()) +} + +async fn fund_wallet<'a: 'b, 'b, C>( + client: &C, + faucet_url: Url, + address: Cow<'a, str>, + usage_context: Option>, + user_agent: Option>, +) -> Result<()> +where + C: XRPLFaucet + Client, +{ + let request = FundFaucet { + destination: address, + usage_context, + user_agent, + }; + client.request_funding(Some(faucet_url), request).await?; + + Ok(()) +} + +#[cfg(test)] +mod test_faucet_wallet_generation { + use super::*; + use crate::asynch::clients::json_rpc::AsyncJsonRpcClient; + use alloc::dbg; + use url::Url; + + #[tokio::test] + async fn test_generate_faucet_wallet() { + let client = + AsyncJsonRpcClient::connect(Url::parse("https://testnet.xrpl-labs.com/").unwrap()); + let wallet = generate_faucet_wallet(&client, None, None, None, None) + .await + .unwrap(); + dbg!(&wallet); + assert_eq!(wallet.classic_address.len(), 34); + } +} diff --git a/src/models/amount/xrp_amount.rs b/src/models/amount/xrp_amount.rs index 91c3d6a1..17add31e 100644 --- a/src/models/amount/xrp_amount.rs +++ b/src/models/amount/xrp_amount.rs @@ -12,11 +12,17 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// Represents an amount of XRP in Drops. -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Default)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize)] pub struct XRPAmount<'a>(pub Cow<'a, str>); impl<'a> Model for XRPAmount<'a> {} +impl Default for XRPAmount<'_> { + fn default() -> Self { + Self("0".into()) + } +} + // 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> @@ -121,12 +127,16 @@ 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)) + let self_decimal: Decimal = self.clone().try_into().unwrap(); + let other_decimal: Decimal = other.clone().try_into().unwrap(); + Some(self_decimal.cmp(&other_decimal)) } } impl<'a> Ord for XRPAmount<'a> { fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.0.cmp(&other.0) + let self_decimal: Decimal = self.clone().try_into().unwrap(); + let other_decimal: Decimal = other.clone().try_into().unwrap(); + self_decimal.cmp(&other_decimal) } } diff --git a/src/models/requests/mod.rs b/src/models/requests/mod.rs index e77e91c8..1c4f318a 100644 --- a/src/models/requests/mod.rs +++ b/src/models/requests/mod.rs @@ -486,3 +486,14 @@ pub trait Request<'a> { fn get_common_fields(&self) -> &CommonFields<'a>; fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a>; } + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +#[serde(rename_all = "camelCase")] +#[skip_serializing_none] +pub struct FundFaucet<'a> { + pub destination: Cow<'a, str>, + #[serde(skip_serializing_if = "Option::is_none")] + pub usage_context: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub user_agent: Option>, +} diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs index adbf2c79..517ca7d3 100644 --- a/src/models/results/mod.rs +++ b/src/models/results/mod.rs @@ -2,7 +2,7 @@ use core::convert::{TryFrom, TryInto}; use alloc::{ borrow::Cow, - format, + dbg, format, string::{String, ToString}, vec::Vec, }; @@ -219,6 +219,7 @@ impl<'a> XRPLResponse<'a> { } pub fn try_into_result, Error = anyhow::Error>>(self) -> Result { + dbg!(self.result.clone()); match self.result { Some(result) => result.try_into(), None => { diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index a116b0f4..e4b68521 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -71,7 +71,7 @@ pub async fn test_json_rpc_std() -> Result<()> { models::requests::Fee, }; let client: AsyncJsonRpcClient = - AsyncJsonRpcClient::new("https://s1.ripple.com:51234/".parse().unwrap()); + AsyncJsonRpcClient::connect("https://s1.ripple.com:51234/".parse().unwrap()); let fee_result = client.request(Fee::new(None).into()).await.unwrap(); assert!(fee_result.result.is_some()); Ok(()) From 88eadd44b71abe894658260a62fa91d706ac646b Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Wed, 4 Sep 2024 16:04:17 +0000 Subject: [PATCH 02/10] add submit_and_wait --- .gitignore | 2 + Cargo.toml | 4 + rustc-ice-2024-08-27T11_36_40-17775.txt | 59 ------- src/asynch/transaction/exceptions.rs | 16 ++ src/asynch/transaction/mod.rs | 205 +++++++++++----------- src/asynch/transaction/submit_and_wait.rs | 204 +++++++++++++++++++++ src/core/binarycodec/mod.rs | 4 +- src/core/definitions/mod.rs | 10 +- src/core/types/mod.rs | 3 + src/models/requests/tx.rs | 4 + src/models/results/exceptions.rs | 8 + src/models/results/mod.rs | 159 +++++++++++++++-- src/models/results/submit.rs | 28 +-- src/models/results/tx.rs | 40 +++++ src/models/transactions/exceptions.rs | 1 + src/models/transactions/mod.rs | 73 +++++++- 16 files changed, 631 insertions(+), 189 deletions(-) delete mode 100644 rustc-ice-2024-08-27T11_36_40-17775.txt create mode 100644 src/asynch/transaction/submit_and_wait.rs create mode 100644 src/models/results/tx.rs diff --git a/.gitignore b/.gitignore index c59e98f8..e3e4c984 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ Cargo.lock src/main.rs **/.DS_Store + +rustc-ice* diff --git a/Cargo.toml b/Cargo.toml index 2122cdb1..fd1ddbea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,6 +87,7 @@ embedded-nal-async = { version = "0.7.1", optional = true } reqwless = { version = "0.12.1", optional = true } # json-rpc-std reqwest = { version = "0.12.5", features = ["json"], optional = true } +embassy-time = { version = "0.3.2", optional = true } [dev-dependencies] criterion = "0.5.1" @@ -129,6 +130,7 @@ transaction-helpers = [ "results", "transactions", "ledger", + "embassy-time", ] amounts = ["core"] currencies = ["core"] @@ -166,4 +168,6 @@ std = [ "serde/std", "indexmap/std", "secp256k1/std", + "embassy-time/std", ] +embassy-time = ["dep:embassy-time"] diff --git a/rustc-ice-2024-08-27T11_36_40-17775.txt b/rustc-ice-2024-08-27T11_36_40-17775.txt deleted file mode 100644 index d13252de..00000000 --- a/rustc-ice-2024-08-27T11_36_40-17775.txt +++ /dev/null @@ -1,59 +0,0 @@ -thread 'rustc' panicked at /rustc/80eb5a8e910e5185d47cdefe3732d839c78a5e7e/compiler/rustc_query_system/src/query/plumbing.rs:727:9: -Found unstable fingerprints for evaluate_obligation(9acaf53e42db4106-b70f9be4bd000590): Ok(EvaluatedToAmbig) -stack backtrace: - 0: 0x7f2385fa3de5 - std::backtrace::Backtrace::create::h5fc94655595d7e27 - 1: 0x7f2384710fe5 - std::backtrace::Backtrace::force_capture::h3ae8cd2c99c0732c - 2: 0x7f23838b111e - std[edc1ba278773f0d3]::panicking::update_hook::>::{closure#0} - 3: 0x7f2384728e27 - std::panicking::rust_panic_with_hook::h9e59a429b59b11d4 - 4: 0x7f2384728ae7 - std::panicking::begin_panic_handler::{{closure}}::h97d6e0922a833e87 - 5: 0x7f23847262e9 - std::sys::backtrace::__rust_end_short_backtrace::ha09625205ff6b122 - 6: 0x7f23847287b4 - rust_begin_unwind - 7: 0x7f2381922f53 - core::panicking::panic_fmt::h089121e4cbbc3220 - 8: 0x7f23841d9c41 - rustc_query_system[cb6aa6728468b543]::query::plumbing::incremental_verify_ich_failed:: - 9: 0x7f238558c6af - rustc_query_system[cb6aa6728468b543]::query::plumbing::try_execute_query::>, rustc_middle[354bbee2ce4c460]::query::erase::Erased<[u8; 2usize]>>, false, false, false>, rustc_query_impl[2dfeb81381ece16c]::plumbing::QueryCtxt, true> - 10: 0x7f238558ab62 - rustc_query_impl[2dfeb81381ece16c]::query_impl::evaluate_obligation::get_query_incr::__rust_end_short_backtrace - 11: 0x7f238246b63e - ::evaluate_obligation_no_overflow - 12: 0x7f2381749e92 - , ::consider_candidates::{closure#0}>, ::consider_candidates::{closure#1}> as core[8c2a069408211e69]::iter::traits::iterator::Iterator>::next - 13: 0x7f238552a7b4 - ::pick_method - 14: 0x7f2385529ed4 - ::pick_core - 15: 0x7f2382485c0e - ::lookup_probe - 16: 0x7f2385931fec - ::check_expr_with_expectation_and_args - 17: 0x7f23859345ae - ::check_expr_with_expectation_and_args - 18: 0x7f2385935734 - ::check_expr_with_expectation_and_args - 19: 0x7f2385935734 - ::check_expr_with_expectation_and_args - 20: 0x7f2385933b44 - ::check_expr_with_expectation_and_args - 21: 0x7f238593362e - ::check_expr_with_expectation_and_args - 22: 0x7f238592aa16 - ::check_block_with_expected - 23: 0x7f2385931ed3 - ::check_expr_with_expectation_and_args - 24: 0x7f238593376d - ::check_expr_with_expectation_and_args - 25: 0x7f238592aa16 - ::check_block_with_expected - 26: 0x7f2385931ed3 - ::check_expr_with_expectation_and_args - 27: 0x7f23851bac37 - rustc_hir_typeck[d8d14d8a3ea8603d]::check::check_fn - 28: 0x7f23852c6edf - rustc_hir_typeck[d8d14d8a3ea8603d]::typeck - 29: 0x7f23852c6933 - rustc_query_impl[2dfeb81381ece16c]::plumbing::__rust_begin_short_backtrace::> - 30: 0x7f2385251b4a - rustc_query_system[cb6aa6728468b543]::query::plumbing::try_execute_query::>, false, false, false>, rustc_query_impl[2dfeb81381ece16c]::plumbing::QueryCtxt, true> - 31: 0x7f2385334054 - rustc_query_impl[2dfeb81381ece16c]::query_impl::typeck::get_query_incr::__rust_end_short_backtrace - 32: 0x7f238524da5b - ::par_body_owners::::{closure#0} - 33: 0x7f238524b7a4 - rustc_hir_analysis[618f1997a162b265]::check_crate - 34: 0x7f238572bcbf - rustc_interface[fc21679c26087701]::passes::run_required_analyses - 35: 0x7f2385a97e5e - rustc_interface[fc21679c26087701]::passes::analysis - 36: 0x7f2385a97e31 - rustc_query_impl[2dfeb81381ece16c]::plumbing::__rust_begin_short_backtrace::> - 37: 0x7f2385f83c0d - rustc_query_system[cb6aa6728468b543]::query::plumbing::try_execute_query::>, false, false, false>, rustc_query_impl[2dfeb81381ece16c]::plumbing::QueryCtxt, true> - 38: 0x7f2385f838ba - rustc_query_impl[2dfeb81381ece16c]::query_impl::analysis::get_query_incr::__rust_end_short_backtrace - 39: 0x7f2385d12229 - rustc_interface[fc21679c26087701]::interface::run_compiler::, rustc_driver_impl[734e2c9770122f8]::run_compiler::{closure#0}>::{closure#1} - 40: 0x7f2385de6f44 - std[edc1ba278773f0d3]::sys::backtrace::__rust_begin_short_backtrace::, rustc_driver_impl[734e2c9770122f8]::run_compiler::{closure#0}>::{closure#1}, core[8c2a069408211e69]::result::Result<(), rustc_span[e1b931742f0b02c1]::ErrorGuaranteed>>::{closure#0}, core[8c2a069408211e69]::result::Result<(), rustc_span[e1b931742f0b02c1]::ErrorGuaranteed>>::{closure#0}::{closure#0}, core[8c2a069408211e69]::result::Result<(), rustc_span[e1b931742f0b02c1]::ErrorGuaranteed>> - 41: 0x7f2385de75b0 - <::spawn_unchecked_, rustc_driver_impl[734e2c9770122f8]::run_compiler::{closure#0}>::{closure#1}, core[8c2a069408211e69]::result::Result<(), rustc_span[e1b931742f0b02c1]::ErrorGuaranteed>>::{closure#0}, core[8c2a069408211e69]::result::Result<(), rustc_span[e1b931742f0b02c1]::ErrorGuaranteed>>::{closure#0}::{closure#0}, core[8c2a069408211e69]::result::Result<(), rustc_span[e1b931742f0b02c1]::ErrorGuaranteed>>::{closure#1} as core[8c2a069408211e69]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x7f2385de792b - std::sys::pal::unix::thread::Thread::new::thread_start::he6ae6a1223d421a8 - 43: 0x7f2380330ea7 - start_thread - 44: 0x7f2380250a6f - clone - 45: 0x0 - - - -rustc version: 1.82.0-nightly (80eb5a8e9 2024-08-13) -platform: x86_64-unknown-linux-gnu - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `models::transactions::payment::Payment<'a>: models::transactions::Transaction<'b, ^1_2>` -#1 [typeck] type-checking `models::transactions::payment::::_get_partial_payment_error` -#2 [analysis] running analysis passes on this crate -end of query stack diff --git a/src/asynch/transaction/exceptions.rs b/src/asynch/transaction/exceptions.rs index 0b4456c3..4e37a1ca 100644 --- a/src/asynch/transaction/exceptions.rs +++ b/src/asynch/transaction/exceptions.rs @@ -21,4 +21,20 @@ pub enum XRPLSignTransactionException<'a> { TagFieldMismatch(&'a str), #[error("Fee value of {0:?} is likely entered incorrectly, since it is much larger than the typical XRP transaction cost. If this is intentional, use `check_fee=Some(false)`.")] FeeTooHigh(Cow<'a, str>), + #[error("Wallet is required to sign transaction")] + WalletRequired, +} + +#[derive(Debug, Clone, PartialEq, Eq, Error)] +pub enum XRPLSubmitAndWaitException<'a> { + #[error("Transaction submission failed: {0}")] + SubmissionFailed(Cow<'a, str>), + #[error("The latest validated ledger sequence {validated_ledger_sequence} is greater than the LastLedgerSequence {last_ledger_sequence} in the Transaction. Prelim result: {prelim_result}")] + SubmissionTimeout { + last_ledger_sequence: u32, + validated_ledger_sequence: u32, + prelim_result: Cow<'a, str>, + }, + #[error("Expected field in the transaction metadata: {0}")] + ExpectedFieldInTxMeta(Cow<'a, str>), } diff --git a/src/asynch/transaction/mod.rs b/src/asynch/transaction/mod.rs index 607c07dd..c23b63ce 100644 --- a/src/asynch/transaction/mod.rs +++ b/src/asynch/transaction/mod.rs @@ -1,4 +1,7 @@ pub mod exceptions; +mod submit_and_wait; + +pub use submit_and_wait::*; use crate::{ asynch::{ @@ -45,6 +48,70 @@ const RESTRICTED_NETWORKS: u16 = 1024; const REQUIRED_NETWORKID_VERSION: &str = "1.11.0"; const LEDGER_OFFSET: u8 = 20; +pub fn sign<'a, T, F>(transaction: &mut T, wallet: &Wallet, multisign: bool) -> Result<()> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, +{ + if multisign { + let serialized_for_signing = + encode_for_multisigning(transaction, wallet.classic_address.clone().into())?; + let serialized_bytes = match hex::decode(serialized_for_signing) { + Ok(bytes) => bytes, + Err(e) => return Err!(e), + }; + let signature = match keypairs_sign(&serialized_bytes, &wallet.private_key) { + Ok(signature) => signature, + Err(e) => return Err!(e), + }; + let signer = Signer::new( + wallet.classic_address.clone().into(), + signature.into(), + wallet.public_key.clone().into(), + ); + transaction.get_mut_common_fields().signers = Some(vec![signer]); + + Ok(()) + } else { + prepare_transaction(transaction, wallet)?; + let serialized_for_signing = encode_for_signing(transaction)?; + let serialized_bytes = match hex::decode(serialized_for_signing) { + Ok(bytes) => bytes, + Err(e) => return Err!(e), + }; + let signature = match keypairs_sign(&serialized_bytes, &wallet.private_key) { + Ok(signature) => signature, + Err(e) => return Err!(e), + }; + transaction.get_mut_common_fields().txn_signature = Some(signature.into()); + + Ok(()) + } +} + +pub async fn sign_and_submit<'a, 'b, T, F, C>( + transaction: &mut T, + client: &'b C, + wallet: &Wallet, + autofill: bool, + check_fee: bool, +) -> Result> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, + C: AsyncClient, +{ + if autofill { + autofill_and_sign(transaction, client, wallet, check_fee).await?; + } else { + if check_fee { + check_txn_fee(transaction, client).await?; + } + sign(transaction, wallet, false)?; + } + submit(transaction, client).await +} + pub async fn autofill<'a, 'b, F, T, C>( transaction: &mut T, client: &'b C, @@ -77,6 +144,44 @@ where Ok(()) } +pub async fn autofill_and_sign<'a, 'b, T, F, C>( + transaction: &mut T, + client: &'b C, + wallet: &Wallet, + check_fee: bool, +) -> Result<()> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, + C: AsyncClient, +{ + if check_fee { + check_txn_fee(transaction, client).await?; + } + autofill(transaction, client, None).await?; + sign(transaction, wallet, false)?; + + Ok(()) +} + +pub async fn submit<'a, T, F, C>(transaction: &T, client: &C) -> Result> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, + C: AsyncClient, +{ + let txn_blob = encode(transaction)?; + let req = Submit::new(None, txn_blob.into(), None); + let res = client.request(req.into()).await?; + match res.try_into_result::>() { + Ok(value) => { + let submit_result = SubmitResult::from(value); + Ok(submit_result) + } + Err(e) => Err!(e), + } +} + pub async fn calculate_fee_per_transaction_type<'a, 'b, 'c, T, F, C>( transaction: &T, client: Option<&'b C>, @@ -260,102 +365,6 @@ enum AccountFieldType { Destination, } -pub async fn sign_and_submit<'a, 'b, T, F, C>( - transaction: &mut T, - client: &'b C, - wallet: &Wallet, - autofill: bool, - check_fee: bool, -) -> Result> -where - F: IntoEnumIterator + Serialize + Debug + PartialEq, - T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, - C: AsyncClient, -{ - if autofill { - autofill_and_sign(transaction, client, wallet, check_fee).await?; - } else { - if check_fee { - check_txn_fee(transaction, client).await?; - } - sign(transaction, wallet, false)?; - } - submit(transaction, client).await -} - -pub fn sign<'a, T, F>(transaction: &mut T, wallet: &Wallet, multisign: bool) -> Result<()> -where - F: IntoEnumIterator + Serialize + Debug + PartialEq, - T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, -{ - if multisign { - let serialized_for_signing = - encode_for_multisigning(transaction, wallet.classic_address.clone().into())?; - let serialized_bytes = hex::decode(serialized_for_signing).unwrap(); - let signature = keypairs_sign(&serialized_bytes, &wallet.private_key).unwrap(); - let signer = Signer::new( - wallet.classic_address.clone().into(), - signature.into(), - wallet.public_key.clone().into(), - ); - transaction.get_mut_common_fields().signers = Some(vec![signer]); - - Ok(()) - } else { - prepare_transaction(transaction, wallet)?; - let serialized_for_signing = encode_for_signing(transaction)?; - let serialized_bytes = match hex::decode(serialized_for_signing) { - Ok(bytes) => bytes, - Err(e) => return Err!(e), - }; - let signature = match keypairs_sign(&serialized_bytes, &wallet.private_key) { - Ok(signature) => signature, - Err(e) => return Err!(e), - }; - transaction.get_mut_common_fields().txn_signature = Some(signature.into()); - - Ok(()) - } -} - -pub async fn autofill_and_sign<'a, 'b, T, F, C>( - transaction: &mut T, - client: &'b C, - wallet: &Wallet, - check_fee: bool, -) -> Result<()> -where - F: IntoEnumIterator + Serialize + Debug + PartialEq, - T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, - C: AsyncClient, -{ - if check_fee { - check_txn_fee(transaction, client).await?; - } - autofill(transaction, client, None).await?; - sign(transaction, wallet, false)?; - - Ok(()) -} - -pub async fn submit<'a, T, F, C>(transaction: &T, client: &C) -> Result> -where - F: IntoEnumIterator + Serialize + Debug + PartialEq, - T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, - C: AsyncClient, -{ - let txn_blob = encode(transaction)?; - let req = Submit::new(None, txn_blob.into(), None); - let res = client.request(req.into()).await?; - match res.try_into_result::>() { - Ok(value) => { - let submit_result = SubmitResult::from(value); - Ok(submit_result) - } - Err(e) => Err!(e), - } -} - async fn check_txn_fee<'a, 'b, T, F, C>(transaction: &mut T, client: &'b C) -> Result<()> where F: IntoEnumIterator + Serialize + Debug + PartialEq, @@ -538,8 +547,8 @@ mod test_sign { wallet::Wallet, }; - #[test] - fn test_sign() { + #[tokio::test] + async fn test_sign() { let wallet = Wallet::new("sEdT7wHTCLzDG7ueaw4hroSTBvH7Mk5", 0).unwrap(); let mut tx = AccountSet::new( Cow::from(wallet.classic_address.clone()), diff --git a/src/asynch/transaction/submit_and_wait.rs b/src/asynch/transaction/submit_and_wait.rs new file mode 100644 index 00000000..3cf0fd5d --- /dev/null +++ b/src/asynch/transaction/submit_and_wait.rs @@ -0,0 +1,204 @@ +use core::fmt::Debug; + +use alloc::{borrow::Cow, format}; +use anyhow::{Ok, Result}; +use serde::{de::DeserializeOwned, Serialize}; +use serde_json::Value; +use strum::IntoEnumIterator; + +use crate::{ + asynch::{ + clients::AsyncClient, + ledger::get_latest_validated_ledger_sequence, + transaction::{ + autofill, check_txn_fee, + exceptions::{XRPLSignTransactionException, XRPLSubmitAndWaitException}, + sign, submit, + }, + }, + models::{requests, results, transactions::Transaction, Model}, + wallet::Wallet, + Err, +}; + +pub async fn submit_and_wait<'a: 'b, 'b, T, F, C>( + transaction: &'b mut T, + client: &C, + wallet: Option<&Wallet>, + check_fee: Option, + autofill: Option, +) -> Result> +where + T: Transaction<'a, F> + Model + Clone + DeserializeOwned + Debug, + F: IntoEnumIterator + Serialize + Debug + PartialEq + Debug + Clone + 'a, + C: AsyncClient, +{ + get_signed_transaction(transaction, client, wallet, check_fee, autofill).await?; + send_reliable_submission(transaction, client).await +} + +async fn send_reliable_submission<'a: 'b, 'b, T, F, C>( + transaction: &'b mut T, + client: &C, +) -> Result> +where + T: Transaction<'a, F> + Model + Clone + DeserializeOwned + Debug, + F: IntoEnumIterator + Serialize + Debug + PartialEq + Debug + Clone + 'a, + C: AsyncClient, +{ + let tx_hash = transaction.get_hash()?; + let submit_response = submit(transaction, client).await?; + let prelim_result = submit_response.engine_result; + if &prelim_result[0..3] == "tem" { + let message = format!( + "{}: {}", + prelim_result, submit_response.engine_result_message + ); + Err!(XRPLSubmitAndWaitException::SubmissionFailed(message.into())) + } else { + wait_for_final_transaction_result( + tx_hash, + client, + transaction + .get_common_fields() + .last_ledger_sequence + .unwrap(), // safe to unwrap because we autofilled the transaction + ) + .await + } +} + +async fn wait_for_final_transaction_result<'a: 'b, 'b, C>( + tx_hash: Cow<'a, str>, + client: &C, + last_ledger_sequence: u32, +) -> Result> +where + C: AsyncClient, +{ + let mut validated_ledger_sequence = 0; + while validated_ledger_sequence < last_ledger_sequence { + validated_ledger_sequence = get_latest_validated_ledger_sequence(client).await?; + // sleep for 1 second + // embassy_time::Timer::after_secs(1).await; + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + let response = client + .request(requests::Tx::new(None, None, None, None, Some(tx_hash.clone())).into()) + .await?; + if response.is_success() { + if let Some(error) = response.error { + if error == "txnNotFound" { + continue; + } else { + return Err!(XRPLSubmitAndWaitException::SubmissionFailed( + format!("{}: {}", error, response.error_message.unwrap_or("".into())) + .into() + )); + } + } else { + let opt_result = response.try_into_opt_result::()?; + let validated = opt_result.try_get_typed("validated")?; + if validated { + let result = opt_result.try_into_result()?; + let return_code = match result.meta.get("TransactionResult") { + Some(Value::String(s)) => s, + _ => { + return Err!(XRPLSubmitAndWaitException::ExpectedFieldInTxMeta( + "TransactionResult".into() + )); + } + }; + if return_code != "tesSUCCESS" { + return Err!(XRPLSubmitAndWaitException::SubmissionFailed( + return_code.into() + )); + } else { + return Ok(result); + } + } + } + } + } + return Err!(XRPLSubmitAndWaitException::SubmissionFailed( + "Transaction not included in ledger".into() + )); +} + +async fn get_signed_transaction<'a, T, F, C>( + transaction: &mut T, + client: &C, + wallet: Option<&Wallet>, + do_check_fee: Option, + do_autofill: Option, +) -> Result<()> +where + T: Transaction<'a, F> + Model + Clone + DeserializeOwned + Debug, + F: IntoEnumIterator + Serialize + Debug + PartialEq + Debug + Clone, + C: AsyncClient, +{ + if transaction.get_common_fields().is_signed() { + return Ok(()); + } + if let Some(wallet) = wallet { + if let Some(check_fee) = do_check_fee { + if check_fee { + check_txn_fee(transaction, client).await?; + } + } + if let Some(do_autofill) = do_autofill { + if do_autofill { + autofill(transaction, client, None).await?; + } + } + if transaction.get_common_fields().signers.as_ref().is_some() { + sign(transaction, wallet, true) + } else { + sign(transaction, wallet, false) + } + } else { + Err!(XRPLSignTransactionException::WalletRequired) + } +} + +#[cfg(test)] +mod test_submit_and_wait { + use super::*; + use crate::{ + asynch::clients::{AsyncWebsocketClient, SingleExecutorMutex}, + models::transactions::AccountSet, + wallet::Wallet, + }; + + #[tokio::test] + async fn test_submit_and_wait() { + let wallet = Wallet::new("sEdT7wHTCLzDG7ueaw4hroSTBvH7Mk5", 0).unwrap(); + let mut tx = AccountSet::new( + Cow::from(wallet.classic_address.clone()), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + Some("6578616d706c652e636f6d".into()), // "example.com" + None, + None, + None, + None, + None, + None, + ); + let client = AsyncWebsocketClient::::open( + "wss://testnet.xrpl-labs.com/".parse().unwrap(), + ) + .await + .unwrap(); + submit_and_wait(&mut tx, &client, Some(&wallet), Some(true), Some(true)) + .await + .unwrap(); + } +} diff --git a/src/core/binarycodec/mod.rs b/src/core/binarycodec/mod.rs index d28b44fb..713872fd 100644 --- a/src/core/binarycodec/mod.rs +++ b/src/core/binarycodec/mod.rs @@ -74,6 +74,7 @@ where if let Some(p) = prefix { buffer.extend(p); } + let json_value = match serde_json::to_value(prepared_transaction) { Ok(v) => v, Err(e) => { @@ -81,12 +82,11 @@ where } }; let st_object = STObject::try_from_value(json_value, signing_only)?; - buffer.extend(st_object.as_ref()); + if let Some(s) = suffix { buffer.extend(s); } - let hex_string = buffer.encode_hex_upper::(); Ok(hex_string) diff --git a/src/core/definitions/mod.rs b/src/core/definitions/mod.rs index 554c0269..42ad99ed 100644 --- a/src/core/definitions/mod.rs +++ b/src/core/definitions/mod.rs @@ -152,15 +152,15 @@ impl ToBytes for FieldHeader { let shift = (self.type_code << 4) as u8; header_bytes.extend_from_slice(&shift.to_be_bytes()); - header_bytes.extend_from_slice(&self.field_code.to_be_bytes()); + header_bytes.extend_from_slice(&(self.field_code as u8).to_be_bytes()); } } else if self.field_code < 16 { - header_bytes.extend_from_slice(&self.field_code.to_be_bytes()); - header_bytes.extend_from_slice(&self.type_code.to_be_bytes()); + header_bytes.extend_from_slice(&(self.field_code as u8).to_be_bytes()); + header_bytes.extend_from_slice(&(self.type_code as u8).to_be_bytes()); } else { header_bytes.extend_from_slice(&[0]); - header_bytes.extend_from_slice(&self.type_code.to_be_bytes()); - header_bytes.extend_from_slice(&self.field_code.to_be_bytes()); + header_bytes.extend_from_slice(&(self.type_code as u8).to_be_bytes()); + header_bytes.extend_from_slice(&(self.field_code as u8).to_be_bytes()); } header_bytes diff --git a/src/core/types/mod.rs b/src/core/types/mod.rs index dac7bbb8..a34da2c6 100644 --- a/src/core/types/mod.rs +++ b/src/core/types/mod.rs @@ -298,6 +298,7 @@ impl STObject { /// assert_eq!(hex, buffer); /// ``` pub fn try_from_value(value: Value, signing_only: bool) -> Result { + // dbg!(&value); let object = match value { Value::Object(map) => map, _ => return Err!(exceptions::XRPLSerializeMapException::ExpectedObject), @@ -381,6 +382,7 @@ impl STObject { } } + // dbg!(&value_xaddress_handled); let mut sorted_keys: Vec = Vec::new(); for (field, _) in &value_xaddress_handled { let field_instance = get_field_instance(&field); @@ -411,6 +413,7 @@ impl STObject { associated_value.to_owned(), )?; let associated_value: SerializedType = associated_value.into(); + // dbg!(&field_instance, &associated_value.to_string(),); if field_instance.name == "TransactionType" && associated_value.to_string() == UNL_MODIFY_TX_TYPE { diff --git a/src/models/requests/tx.rs b/src/models/requests/tx.rs index 29b88373..ce21e3c4 100644 --- a/src/models/requests/tx.rs +++ b/src/models/requests/tx.rs @@ -30,6 +30,8 @@ pub struct Tx<'a> { /// the server cannot find the transaction, it confirms whether /// it was able to search all the ledgers in this range. pub min_ledger: Option, + /// The 256-bit hash of the transaction to look up, as hexadecimal. + pub transaction: Option>, } impl<'a> Model for Tx<'a> {} @@ -50,6 +52,7 @@ impl<'a> Tx<'a> { binary: Option, max_ledger: Option, min_ledger: Option, + transaction: Option>, ) -> Self { Self { common_fields: CommonFields { @@ -59,6 +62,7 @@ impl<'a> Tx<'a> { binary, min_ledger, max_ledger, + transaction, } } } diff --git a/src/models/results/exceptions.rs b/src/models/results/exceptions.rs index 6f5dd316..1900e349 100644 --- a/src/models/results/exceptions.rs +++ b/src/models/results/exceptions.rs @@ -1,6 +1,8 @@ use alloc::string::String; use thiserror_no_std::Error; +use super::XRPLOtherResult; + #[derive(Debug, Error)] pub enum XRPLResultException { #[error("Response error: {0}")] @@ -9,4 +11,10 @@ pub enum XRPLResultException { ExpectedResultOrError, #[error("Unexpected result type (expected {0:?}, got {1:?}).")] UnexpectedResultType(String, String), + #[error("Index not found.")] + IndexNotFound, + #[error("Called unwrap on `XRPLOtherResult`.")] + UnwrapOnOther, + #[error("Expected a XRPL Result model but got `XRPLOtherResult`: {0:?}.")] + ExpectedResult(XRPLOtherResult), } diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs index 517ca7d3..678f9547 100644 --- a/src/models/results/mod.rs +++ b/src/models/results/mod.rs @@ -1,15 +1,15 @@ use core::convert::{TryFrom, TryInto}; use alloc::{ - borrow::Cow, - dbg, format, + borrow::{Cow, ToOwned}, + format, string::{String, ToString}, vec::Vec, }; use anyhow::Result; use exceptions::XRPLResultException; -use serde::{Deserialize, Serialize}; -use serde_json::{Map, Value}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_json::{value::Index, Map, Value}; pub mod account_info; pub mod account_tx; @@ -18,6 +18,7 @@ pub mod fee; pub mod ledger; pub mod server_state; pub mod submit; +pub mod tx; pub use account_info::*; pub use account_tx::*; @@ -25,10 +26,108 @@ pub use fee::*; pub use ledger::*; pub use server_state::*; pub use submit::*; +pub use tx::*; use crate::Err; use super::requests::XRPLRequest; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum XRPLOptionalResult { + Result(T), + Other(XRPLOtherResult), +} + +impl XRPLOptionalResult { + pub fn unwrap(self) -> T { + match self { + XRPLOptionalResult::Result(result) => result, + XRPLOptionalResult::Other(_) => { + panic!("{}", XRPLResultException::UnwrapOnOther.to_string()) + } + } + } + + /// Try to convert the result into an expected XRPL result. + pub fn try_into_result(self) -> Result { + match self { + XRPLOptionalResult::Result(result) => Ok(result), + XRPLOptionalResult::Other(other) => Err!(XRPLResultException::ExpectedResult(other)), + } + } + + /// Get a value from the result by index. + pub fn try_get_typed(&self, index: I) -> Result + where + T: Serialize, + I: Index, + U: DeserializeOwned, + { + match self { + XRPLOptionalResult::Result(result) => match serde_json::to_value(result) { + Ok(value) => match value.get(index) { + Some(value) => match serde_json::from_value(value.to_owned()) { + Ok(value) => Ok(value), + Err(e) => Err!(e), + }, + None => Err!(XRPLResultException::IndexNotFound), + }, + Err(e) => Err!(e), + }, + XRPLOptionalResult::Other(other) => other.try_get_typed(index), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct XRPLOtherResult(Value); + +impl TryFrom> for XRPLOtherResult { + type Error = anyhow::Error; + + fn try_from(result: XRPLResult) -> Result { + match result { + XRPLResult::Other(value) => Ok(value), + res => Err!(XRPLResultException::UnexpectedResultType( + "Other".to_string(), + res.get_name() + )), + } + } +} + +impl From for XRPLOtherResult { + fn from(value: Value) -> Self { + XRPLOtherResult(value) + } +} + +impl Into for XRPLOtherResult { + fn into(self) -> Value { + self.0 + } +} + +impl XRPLOtherResult { + pub fn get(&self, index: impl Index) -> Option<&Value> { + self.0.get(index) + } + + pub fn try_get_typed(&self, index: I) -> Result + where + I: Index, + T: DeserializeOwned, + { + match self.0.get(index) { + Some(value) => match serde_json::from_value(value.clone()) { + Ok(value) => Ok(value), + Err(e) => Err!(e), + }, + None => Err!(XRPLResultException::IndexNotFound), + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(untagged)] pub enum XRPLResult<'a> { @@ -38,7 +137,8 @@ pub enum XRPLResult<'a> { Ledger(Ledger<'a>), ServerState(ServerState<'a>), Submit(Submit<'a>), - Other(Value), + Tx(Tx<'a>), + Other(XRPLOtherResult), } impl<'a> From> for XRPLResult<'a> { @@ -77,9 +177,21 @@ impl<'a> From> for XRPLResult<'a> { } } +impl<'a> From> for XRPLResult<'a> { + fn from(tx: Tx<'a>) -> Self { + XRPLResult::Tx(tx) + } +} + impl<'a> From for XRPLResult<'a> { fn from(value: Value) -> Self { - XRPLResult::Other(value) + XRPLResult::Other(XRPLOtherResult(value)) + } +} + +impl<'a> From for XRPLResult<'a> { + fn from(other: XRPLOtherResult) -> Self { + XRPLResult::Other(other) } } @@ -88,7 +200,7 @@ impl<'a> TryInto for XRPLResult<'a> { fn try_into(self) -> Result { match self { - XRPLResult::Other(value) => Ok(value), + XRPLResult::Other(XRPLOtherResult(value)) => Ok(value), res => match serde_json::to_value(res) { Ok(value) => Ok(value), Err(e) => Err!(e), @@ -106,6 +218,7 @@ impl XRPLResult<'_> { XRPLResult::Ledger(_) => "Ledger".to_string(), XRPLResult::ServerState(_) => "ServerState".to_string(), XRPLResult::Submit(_) => "Submit".to_string(), + XRPLResult::Tx(_) => "Tx".to_string(), XRPLResult::Other(_) => "Other".to_string(), } } @@ -213,15 +326,31 @@ impl<'a, 'de> Deserialize<'de> for XRPLResponse<'a> { } } +impl TryInto for XRPLResponse<'_> { + type Error = anyhow::Error; + + fn try_into(self) -> Result { + match serde_json::to_value(self) { + Ok(value) => Ok(value), + Err(e) => Err!(e), + } + } +} + impl<'a> XRPLResponse<'a> { pub fn is_success(&self) -> bool { self.status == Some(ResponseStatus::Success) } - pub fn try_into_result, Error = anyhow::Error>>(self) -> Result { - dbg!(self.result.clone()); + pub fn try_into_opt_result(self) -> Result> + where + T: TryFrom, Error = anyhow::Error>, + { match self.result { - Some(result) => result.try_into(), + Some(result) => match result.clone().try_into() { + Ok(result) => Ok(XRPLOptionalResult::Result(result)), + Err(_) => Ok(XRPLOptionalResult::Other(result.try_into()?)), + }, None => { if let Some(error) = self.error { Err!(XRPLResultException::ResponseError(format!( @@ -235,6 +364,16 @@ impl<'a> XRPLResponse<'a> { } } } + + pub fn try_into_result(self) -> Result + where + T: TryFrom, Error = anyhow::Error>, + { + match self.try_into_opt_result()? { + XRPLOptionalResult::Result(result) => Ok(result), + XRPLOptionalResult::Other(other) => Err!(XRPLResultException::ExpectedResult(other)), + } + } } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/models/results/submit.rs b/src/models/results/submit.rs index 3afd25c8..1b55706b 100644 --- a/src/models/results/submit.rs +++ b/src/models/results/submit.rs @@ -11,20 +11,20 @@ use super::XRPLResult; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Submit<'a> { - engine_result: Cow<'a, str>, - engine_result_code: i32, - engine_result_message: Cow<'a, str>, - tx_blob: Cow<'a, str>, - tx_json: Value, - accepted: Option, - account_sequence_available: Option, - account_sequence_next: Option, - applied: Option, - broadcast: Option, - kept: Option, - queued: Option, - open_ledger_cost: Option>, - validated_ledger_index: Option, + pub engine_result: Cow<'a, str>, + pub engine_result_code: i32, + pub engine_result_message: Cow<'a, str>, + pub tx_blob: Cow<'a, str>, + pub tx_json: Value, + pub accepted: Option, + pub account_sequence_available: Option, + pub account_sequence_next: Option, + pub applied: Option, + pub broadcast: Option, + pub kept: Option, + pub queued: Option, + pub open_ledger_cost: Option>, + pub validated_ledger_index: Option, } impl<'a> TryFrom> for Submit<'a> { diff --git a/src/models/results/tx.rs b/src/models/results/tx.rs new file mode 100644 index 00000000..cecdf63b --- /dev/null +++ b/src/models/results/tx.rs @@ -0,0 +1,40 @@ +use core::convert::TryFrom; + +use alloc::borrow::Cow; +use anyhow::Result; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use crate::{models::results::exceptions::XRPLResultException, Err}; + +use super::XRPLResult; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Tx<'a> { + pub ctid: Cow<'a, str>, + pub date: u32, + pub hash: Cow<'a, str>, + pub ledger_index: u32, + pub meta: Value, + /// Various fields of the transaction + #[serde(flatten)] + pub various: Value, + pub validated: Option, + /// (Deprecated) Alias for `ledger_index` + #[serde(rename = "inLedger")] + pub in_ledger: Option, +} + +impl<'a> TryFrom> for Tx<'a> { + type Error = anyhow::Error; + + fn try_from(result: XRPLResult<'a>) -> Result { + match result { + XRPLResult::Tx(tx) => Ok(tx), + res => Err!(XRPLResultException::UnexpectedResultType( + "Tx".to_string(), + res.get_name() + )), + } + } +} diff --git a/src/models/transactions/exceptions.rs b/src/models/transactions/exceptions.rs index bbee7124..c36bc9b6 100644 --- a/src/models/transactions/exceptions.rs +++ b/src/models/transactions/exceptions.rs @@ -17,6 +17,7 @@ pub enum XRPLTransactionException<'a> { XRPLNFTokenMintError(XRPLNFTokenMintException<'a>), XRPLPaymentError(XRPLPaymentException<'a>), XRPLSignerListSetError(XRPLSignerListSetException<'a>), + TxMustBeSigned, } #[cfg(feature = "std")] diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index ff72c422..58fda72d 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -29,6 +29,7 @@ use core::fmt::Debug; pub use account_delete::*; pub use account_set::*; +use alloc::format; pub use check_cancel::*; pub use check_cash::*; pub use check_create::*; @@ -50,11 +51,14 @@ pub use payment_channel_create::*; pub use payment_channel_fund::*; pub use pseudo_transactions::*; +use serde::de::DeserializeOwned; pub use set_regular_key::*; +use sha2::{Digest, Sha512}; pub use signer_list_set::*; pub use ticket_create::*; pub use trust_set::*; +use crate::core::binarycodec::encode; use crate::models::amount::XRPAmount; use crate::Err; use crate::{_serde::txn_flags, serde_with_tag}; @@ -71,6 +75,8 @@ use strum_macros::{AsRefStr, Display}; use super::FlagCollection; +const TRANSACTION_HASH_PREFIX: u32 = 0x54584E00; + /// Enum containing the different Transaction types. #[derive(Debug, Clone, Serialize, Deserialize, Display, PartialEq, Eq)] pub enum TransactionType { @@ -238,7 +244,7 @@ where impl CommonFields<'_, T> where - T: IntoEnumIterator + Serialize + core::fmt::Debug, + T: IntoEnumIterator + Serialize + Debug + PartialEq + Clone, { pub fn is_signed(&self) -> bool { if let Some(signers) = &self.signers { @@ -338,6 +344,31 @@ where Err(e) => Err!(e), } } + + /// Hashes the Transaction object as the ledger does. Only valid for signed + /// Transaction objects. + fn get_hash(&self) -> Result> + where + Self: Serialize + DeserializeOwned + Debug + Clone, + { + // if !self.is_signed() { + // return Err!(XRPLTransactionException::TxMustBeSigned); + // } + let prefix = format!("{:X}", TRANSACTION_HASH_PREFIX); + let encoded_tx = encode(self)?; + let encoded = prefix + &encoded_tx; + let encoded_bytes = match hex::decode(&encoded) { + Ok(bytes) => bytes, + Err(e) => return Err!(e), + }; + let mut hasher = Sha512::new(); + hasher.update(&encoded_bytes); + let hash = hasher.finalize(); + let hex_string = hex::encode_upper(hash); + let result = hex_string[..64].to_string(); + + Ok(result.into()) + } } #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Display, AsRefStr)] @@ -351,3 +382,43 @@ pub enum Flag { TrustSet(TrustSetFlag), EnableAmendment(EnableAmendmentFlag), } + +#[cfg(test)] +mod test_tx_common_fields { + use super::*; + use crate::{ + asynch::transaction::sign, + models::{amount::IssuedCurrencyAmount, transactions::OfferCreate}, + wallet::Wallet, + }; + + #[tokio::test] + async fn test_get_hash() { + let mut wallet = Wallet::new("sEdT7wHTCLzDG7ueaw4hroSTBvH7Mk5", 0).unwrap(); + let mut txn = OfferCreate::new( + "rLyttXLh7Ttca9CMUaD3exVoXY2fn2zwj3".into(), + None, + Some("10".into()), + Some(FlagCollection::default()), + Some(16409087), + None, + Some(16409064), + None, + None, + None, + "13100000".into(), + IssuedCurrencyAmount::new( + "USD".into(), + "rLyttXLh7Ttca9CMUaD3exVoXY2fn2zwj3".into(), + "10".into(), + ) + .into(), + None, + None, + ); + sign(&mut txn, &mut wallet, false).unwrap(); + let expected_hash = "39530980D3D6F848E619BF05A57988D42A62075289B99C5728CBDE0D1710284B"; + + assert_eq!(&txn.get_hash().unwrap(), expected_hash); + } +} From 86db1178f393488285ba886bcd3149d08d83cf2a Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 6 Sep 2024 19:15:19 +0000 Subject: [PATCH 03/10] try fix tests --- src/asynch/transaction/submit_and_wait.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/asynch/transaction/submit_and_wait.rs b/src/asynch/transaction/submit_and_wait.rs index 3cf0fd5d..1b4b147b 100644 --- a/src/asynch/transaction/submit_and_wait.rs +++ b/src/asynch/transaction/submit_and_wait.rs @@ -160,6 +160,11 @@ where } } +#[cfg(all( + feature = "websocket-std", + feature = "transactions", + feature = "wallet" +))] #[cfg(test)] mod test_submit_and_wait { use super::*; From 7189ba7fe931c9ef9c3024a058e031c10383f9e5 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 6 Sep 2024 19:19:30 +0000 Subject: [PATCH 04/10] try fix tests --- src/asynch/transaction/submit_and_wait.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/asynch/transaction/submit_and_wait.rs b/src/asynch/transaction/submit_and_wait.rs index 1b4b147b..0f9b456f 100644 --- a/src/asynch/transaction/submit_and_wait.rs +++ b/src/asynch/transaction/submit_and_wait.rs @@ -162,6 +162,7 @@ where #[cfg(all( feature = "websocket-std", + not(feature = "websocket"), feature = "transactions", feature = "wallet" ))] From 255533e9e4143ea1d399c01f8b74814cbc203812 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 6 Sep 2024 19:24:20 +0000 Subject: [PATCH 05/10] try fix tests --- src/models/transactions/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 58fda72d..a6b109b8 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -383,6 +383,14 @@ pub enum Flag { EnableAmendment(EnableAmendmentFlag), } +#[cfg(all( + feature = "websocket-std", + not(feature = "websocket"), + feature = "transactions", + feature = "transaction-helpers", + feature = "amounts", + feature = "wallet" +))] #[cfg(test)] mod test_tx_common_fields { use super::*; From 47664be9b45e7f3cb55e011f2b5b8800e1eb4e5e Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 6 Sep 2024 19:30:43 +0000 Subject: [PATCH 06/10] try fix tests --- .github/workflows/unit_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index 6f2bb2d0..c8feaf76 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -41,4 +41,4 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test - args: --no-default-features --features websocket-std,helpers + args: --no-default-features --features std,websocket-std,helpers From b76e270b5d4176d58d3309d347f336dc0eed60a2 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 6 Sep 2024 20:12:38 +0000 Subject: [PATCH 07/10] finalize --- src/asynch/account/mod.rs | 4 ++-- src/asynch/clients/json_rpc/mod.rs | 21 +++------------------ src/asynch/wallet/mod.rs | 22 +++++++++++----------- 3 files changed, 16 insertions(+), 31 deletions(-) diff --git a/src/asynch/account/mod.rs b/src/asynch/account/mod.rs index 8111972f..0a629024 100644 --- a/src/asynch/account/mod.rs +++ b/src/asynch/account/mod.rs @@ -1,4 +1,4 @@ -use alloc::{borrow::Cow, dbg}; +use alloc::borrow::Cow; use anyhow::Result; use crate::{ @@ -75,7 +75,7 @@ where None, Some(ledger_index), None, - Some(true), + None, None, ) .into(); diff --git a/src/asynch/clients/json_rpc/mod.rs b/src/asynch/clients/json_rpc/mod.rs index c0a21fd5..b0290b4e 100644 --- a/src/asynch/clients/json_rpc/mod.rs +++ b/src/asynch/clients/json_rpc/mod.rs @@ -1,11 +1,5 @@ -use alloc::{ - dbg, - string::{String, ToString}, - sync::Arc, - vec, -}; +use alloc::{string::ToString, vec}; use anyhow::Result; -use embassy_sync::{blocking_mutex::raw::RawMutex, mutex::Mutex}; use serde::Serialize; use serde_json::{Map, Value}; use url::Url; @@ -21,6 +15,7 @@ pub use exceptions::XRPLJsonRpcException; use super::client::Client; +#[allow(async_fn_in_trait)] pub trait XRPLFaucet: Client { fn get_faucet_url(&self, url: Option) -> Result where @@ -58,7 +53,7 @@ mod _std { use crate::models::requests::{FundFaucet, XRPLRequest}; use super::*; - use alloc::{dbg, format, string::ToString}; + use alloc::string::ToString; use reqwest::Client as HttpClient; use url::Url; @@ -79,17 +74,14 @@ mod _std { ) -> Result> { let client = HttpClient::new(); let request_json_rpc = request_to_json_rpc(&request)?; - dbg!(&request_json_rpc); let response = client .post(self.url.as_ref()) .json(&request_json_rpc) .send() .await; - dbg!(&response); match response { Ok(response) => match response.text().await { Ok(response) => { - dbg!(&response); Ok(serde_json::from_str::>(&response).unwrap()) } Err(error) => Err!(error), @@ -108,28 +100,21 @@ mod _std { let faucet_url = self.get_faucet_url(url)?; let client = HttpClient::new(); let request_json_rpc = serde_json::to_value(&request).unwrap(); - dbg!(&request_json_rpc); let response = client .post(&faucet_url.to_string()) .json(&request_json_rpc) .send() .await; - dbg!(&response); match response { Ok(response) => { if response.status().is_success() { - dbg!("Success"); - dbg!(&response); Ok(()) } else { - dbg!("Error"); - dbg!(&response); todo!() // Err!(XRPLJsonRpcException::RequestError()) } } Err(error) => { - dbg!("req Error"); Err!(error) } } diff --git a/src/asynch/wallet/mod.rs b/src/asynch/wallet/mod.rs index b21b3a99..99330cde 100644 --- a/src/asynch/wallet/mod.rs +++ b/src/asynch/wallet/mod.rs @@ -1,6 +1,6 @@ pub mod exceptions; -use alloc::{borrow::Cow, dbg}; +use alloc::borrow::Cow; use anyhow::Result; use exceptions::XRPLFaucetException; use url::Url; @@ -14,7 +14,7 @@ use crate::{ use super::{ account::get_xrp_balance, - clients::{AsyncClient, Client, XRPLFaucet}, + clients::{Client, XRPLFaucet}, }; const TEST_FAUCET_URL: &'static str = "https://faucet.altnet.rippletest.net/accounts"; @@ -41,8 +41,7 @@ where }, }; let address = &wallet.classic_address; - dbg!(address); - let starting_balance = 0.into(); // check_balance(client, address.into()).await; + let starting_balance = check_balance(client, address.into()).await; let user_agent = user_agent.unwrap_or("xrpl-rust".into()); fund_wallet( client, @@ -58,8 +57,6 @@ where tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; if !is_funded { let balance = check_balance(client, address.into()).await; - dbg!(&balance); - dbg!(&starting_balance); if balance > starting_balance { is_funded = true; } @@ -108,8 +105,9 @@ async fn check_balance<'a: 'b, 'b, C>(client: &C, address: Cow<'a, str>) -> XRPA where C: Client, { - get_xrp_balance(address, client, None).await.unwrap() - // .unwrap_or(XRPAmount::default()) + get_xrp_balance(address, client, None) + .await + .unwrap_or(XRPAmount::default()) } async fn fund_wallet<'a: 'b, 'b, C>( @@ -132,11 +130,11 @@ where Ok(()) } +#[cfg(all(feature = "json-rpc-std", feature = "helpers", feature = "models"))] #[cfg(test)] mod test_faucet_wallet_generation { use super::*; use crate::asynch::clients::json_rpc::AsyncJsonRpcClient; - use alloc::dbg; use url::Url; #[tokio::test] @@ -146,7 +144,9 @@ mod test_faucet_wallet_generation { let wallet = generate_faucet_wallet(&client, None, None, None, None) .await .unwrap(); - dbg!(&wallet); - assert_eq!(wallet.classic_address.len(), 34); + let balance = get_xrp_balance(wallet.classic_address.clone().into(), &client, None) + .await + .unwrap(); + assert!(balance > 0.into()); } } From 200f9b505d630feb44f2292b7a66550001378f5c Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 7 Sep 2024 08:39:50 +0000 Subject: [PATCH 08/10] fix github tests --- Cargo.toml | 2 +- src/asynch/clients/json_rpc/mod.rs | 11 +++++++++-- src/asynch/mod.rs | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fd1ddbea..0a9c4cc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,7 +106,7 @@ default = [ "models", "utils", "helpers", - "json-rpc-std", + "websocket-std", ] models = ["core", "transactions", "requests", "ledger", "results"] transactions = ["core", "amounts", "currencies"] diff --git a/src/asynch/clients/json_rpc/mod.rs b/src/asynch/clients/json_rpc/mod.rs index b0290b4e..dd1a2623 100644 --- a/src/asynch/clients/json_rpc/mod.rs +++ b/src/asynch/clients/json_rpc/mod.rs @@ -124,9 +124,11 @@ mod _std { #[cfg(feature = "json-rpc")] mod _no_std { - use crate::models::requests::XRPLRequest; + use crate::{asynch::clients::SingleExecutorMutex, models::requests::XRPLRequest}; use super::*; + use alloc::sync::Arc; + use embassy_sync::{blocking_mutex::raw::RawMutex, mutex::Mutex}; use embedded_nal_async::{Dns, TcpConnect}; use reqwless::{ client::{HttpClient, TlsConfig}, @@ -177,7 +179,8 @@ mod _no_std { request: XRPLRequest<'a>, ) -> Result> { let request_json_rpc = request_to_json_rpc(&request)?; - let request_buf = request_json_rpc.as_bytes(); + let request_string = request_json_rpc.to_string(); + let request_buf = request_string.as_bytes(); let mut rx_buffer = [0; BUF]; let mut client = self.client.lock().await; let response = match client.request(Method::POST, self.url.as_str()).await { @@ -201,6 +204,10 @@ mod _no_std { response } + + fn get_host(&self) -> Url { + self.url.clone() + } } } diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index fc5acf16..b045b44c 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -11,5 +11,5 @@ pub mod clients; pub mod ledger; #[cfg(feature = "transaction-helpers")] pub mod transaction; -#[cfg(feature = "wallet-helpers")] +#[cfg(all(feature = "wallet-helpers", feature = "json-rpc-std"))] pub mod wallet; From 901c53a8d20b396abb7a83614f98c3d1522ebafd Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 7 Sep 2024 08:43:32 +0000 Subject: [PATCH 09/10] fix github tests --- src/asynch/wallet/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/asynch/wallet/mod.rs b/src/asynch/wallet/mod.rs index 99330cde..9e26c6ab 100644 --- a/src/asynch/wallet/mod.rs +++ b/src/asynch/wallet/mod.rs @@ -130,7 +130,12 @@ where Ok(()) } -#[cfg(all(feature = "json-rpc-std", feature = "helpers", feature = "models"))] +#[cfg(all( + feature = "json-rpc-std", + not(feature = "json-rpc"), + feature = "helpers", + feature = "models" +))] #[cfg(test)] mod test_faucet_wallet_generation { use super::*; From 696b32d7c3e507ea2ee42dbdd70cf310c7fae794 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 7 Sep 2024 08:50:51 +0000 Subject: [PATCH 10/10] fix github tests --- src/asynch/clients/websocket/_no_std.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/asynch/clients/websocket/_no_std.rs b/src/asynch/clients/websocket/_no_std.rs index 99bbc4d5..2f3926d2 100644 --- a/src/asynch/clients/websocket/_no_std.rs +++ b/src/asynch/clients/websocket/_no_std.rs @@ -54,6 +54,7 @@ pub struct AsyncWebsocketClient< websocket: Arc>>, tx_buffer: [u8; BUF], websocket_base: Arc>>, + uri: Url, status: PhantomData, } @@ -124,6 +125,7 @@ where websocket, tx_buffer: buffer, websocket_base: Arc::new(Mutex::new(WebsocketBase::new())), + uri: url, status: PhantomData::, }) } @@ -245,6 +247,10 @@ where E: Debug + Display, Tcp: Stream> + for<'b> Sink<&'b [u8], Error = E> + Unpin, { + fn get_host(&self) -> Url { + self.uri.clone() + } + async fn request_impl<'a: 'b, 'b>( &self, mut request: XRPLRequest<'a>,