Skip to content

Commit

Permalink
add transaction auto filling as in #76 (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
LimpidCrypto committed Oct 6, 2024
1 parent 900bc1d commit ddf8c53
Show file tree
Hide file tree
Showing 52 changed files with 1,290 additions and 179 deletions.
5 changes: 3 additions & 2 deletions .cargo-husky/hooks/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ set -e
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 std,models,utils,websocket,websocket-codec
cargo test --no-default-features --features core,models,utils,websocket-std
cargo test --no-default-features --features core,models,utils,json-rpc-std
cargo test --no-default-features --features websocket-std,helpers
cargo test --all-features
cargo clippy --fix --allow-staged
cargo doc --no-deps
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/unit_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,8 @@ jobs:
- uses: actions-rs/cargo@v1
with:
command: test
args: --no-default-features --features core,models,websocket
args: --no-default-features --features std,models,websocket,websocket-codec
- uses: actions-rs/cargo@v1
with:
command: test
args: --no-default-features --features websocket-std,helpers
99 changes: 51 additions & 48 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,11 @@ proc-macro = true

[dependencies]
lazy_static = "1.4.0"
sha2 = { version = "0.10.2", default-features = false }
sha2 = "0.10.2"
rand_hc = "0.3.1"
ripemd = "0.1.1"
ed25519-dalek = { version = "2.1.1", default-features = false, features = [
"alloc",
"zeroize",
] }
secp256k1 = { version = "0.29.0", default-features = false, features = [
ed25519-dalek = "1.0.1"
secp256k1 = { version = "0.27.0", default-features = false, features = [
"alloc",
] }
bs58 = { version = "0.5.0", default-features = false, features = [
Expand All @@ -38,8 +35,8 @@ bs58 = { version = "0.5.0", default-features = false, features = [
] }
indexmap = { version = "2.0.0", features = ["serde"] }
regex = { version = "1.5.4", default-features = false }
strum = { version = "0.26.3", default-features = false }
strum_macros = { version = "0.26.4", default-features = false }
strum = { version = "0.25.0", default-features = false }
strum_macros = { version = "0.25.2", default-features = false }
crypto-bigint = { version = "0.5.1" }
rust_decimal = { version = "1.17.0", default-features = false, features = [
"serde",
Expand All @@ -59,75 +56,81 @@ serde_repr = "0.1"
zeroize = "1.5.7"
hashbrown = { version = "0.14.5", features = ["serde"] }
fnv = { version = "1.0.7", default-features = false }
derive-new = { version = "0.6.0", 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 }

# networking
url = { version = "2.2.1", default-features = false, optional = true }
# websocket
embassy-sync = { version = "0.6.0", optional = true }
embedded-io-async = { version = "0.6.1", optional = true }
embedded-websocket = { version = "0.9.3", default-features = false, optional = true }
futures = { version = "0.3.30", optional = true }
# websocket-codec
bytes = { version = "1.7.1", default-features = false, optional = true }
tokio-util = { version = "0.7.11", features = ["codec"], optional = true }
# websocket-std
url = { version = "2.2.2", default-features = false, optional = true }
futures = { version = "0.3.30", default-features = false, features = [
"alloc",
], optional = true }
embassy-sync = { version = "0.6.0", default-features = false }
rand_core = { version = "0.6.4", default-features = false }
# std websocket
embassy-futures = { version = "0.1.1", optional = true }
tokio = { version = "1.0", default-features = false, features = [
"net",
tokio-tungstenite = { version = "0.23.1", features = [
"native-tls",
], optional = true }
tokio-tungstenite = { version = "0.23.1", optional = true }
embedded-nal-async = { version = "0.7.1", optional = true }
# json-rpc
reqwless = { version = "0.12.1", optional = true }
# json-rpc-std
tungstenite = { version = "0.23.0", optional = true }
tokio-util = { version = "0.7.11", features = ["codec"], optional = true }
tokio = { version = "1.0", features = ["full"], optional = true }
# no-std websocket
bytes = { version = "1.4.0", default-features = false, optional = true }
embedded-io-async = { version = "0.6.1", optional = true }
embedded-websocket = { version = "0.9.3", optional = true }
# std json-rpc
reqwest = { version = "0.12.5", features = ["json"], optional = true }
# no-std json-rpc
reqwless = { version = "0.12.0", optional = true }
embedded-nal-async = { version = "0.7.1", optional = true }

[dev-dependencies]
criterion = "0.5.1"
tokio = { version = "1.0", features = ["full"] }
tokio-util = { version = "0.7.11", features = ["codec"] }

[[bench]]
name = "benchmarks"
harness = false

[features]
default = ["std", "utils", "wallet", "models", "websocket"]
models = ["transactions", "ledger", "requests", "results"]
transactions = ["amounts", "currencies"]
requests = ["amounts", "currencies"]
results = ["amounts", "currencies"]
ledger = ["amounts", "currencies"]
amounts = []
currencies = []
json-rpc = ["requests", "results", "url"]
json-rpc-std = ["requests", "results", "url"]
websocket = [
default = ["std", "core", "models", "utils", "helpers", "websocket-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"]
account-helpers = ["amounts", "currencies", "requests", "results"]
ledger-helpers = ["amounts", "currencies", "requests", "results"]
transaction-helpers = [
"amounts",
"currencies",
"requests",
"results",
"url",
"embassy-sync",
"embedded-io-async",
"embedded-websocket",
"futures",
"transactions",
"ledger",
]
websocket-codec = ["requests", "results", "bytes", "tokio-util"]
amounts = ["core"]
currencies = ["core"]
json-rpc = ["url", "reqwless", "embedded-nal-async"]
json-rpc-std = ["url", "reqwest"]
websocket = ["url", "futures", "embedded-websocket", "embedded-io-async"]
websocket-codec = ["bytes", "tokio-util"]
websocket-std = [
"requests",
"results",
"url",
"embassy-sync",
"futures",
"embassy-futures",
"tungstenite",
"tokio",
"tokio-tungstenite",
"embassy-futures",
]
core = ["utils"]
wallet = ["core"]
utils = []
std = [
"embedded-websocket/std",
"futures/std",
"rand/std",
"regex/std",
"chrono/std",
Expand Down
2 changes: 1 addition & 1 deletion examples/std/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ path = "src/bin/wallet/generate_wallet.rs"
required-features = []

[[bin]]
name = "tungstenite"
name = "websocket-std"
path = "src/bin/tokio/net/tungstenite.rs"
required-features = ["tokio"]

Expand Down
52 changes: 52 additions & 0 deletions src/asynch/account/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use alloc::borrow::Cow;
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;

pub async fn get_next_valid_seq_number(
address: Cow<'_, str>,
client: &impl AsyncClient,
ledger_index: Option<Cow<'_, str>>,
) -> Result<u32> {
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: &impl AsyncClient,
ledger_index: Cow<'a, str>,
) -> Result<AccountRoot<'a>> {
let mut classic_address = address;
if is_valid_xaddress(&classic_address) {
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(
None,
classic_address,
None,
Some(ledger_index),
None,
None,
None,
)
.into(),
)
.await?;

Ok(account_info
.try_into_result::<results::AccountInfo<'_>>()?
.account_data)
}
20 changes: 18 additions & 2 deletions src/asynch/clients/async_client.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
use super::client::Client;
use crate::models::{requests::XRPLRequest, results::XRPLResponse};
use super::{client::Client, CommonFields};
use crate::models::{
requests::{ServerState, XRPLRequest},
results::{ServerState as ServerStateResult, XRPLResponse},
};
use anyhow::Result;

#[allow(async_fn_in_trait)]
pub trait AsyncClient: Client {
async fn request<'a: 'b, 'b>(&self, request: XRPLRequest<'a>) -> Result<XRPLResponse<'b>> {
self.request_impl(request).await
}

async fn get_common_fields(&self) -> Result<CommonFields<'_>> {
let server_state = self.request(ServerState::new(None).into()).await?;
let state = server_state
.try_into_result::<ServerStateResult<'_>>()?
.state;
let common_fields = CommonFields {
network_id: state.network_id,
build_version: Some(state.build_version),
};

Ok(common_fields)
}
}

impl<T: Client> AsyncClient for T {}
8 changes: 8 additions & 0 deletions src/asynch/clients/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod json_rpc;
#[cfg(any(feature = "websocket-std", feature = "websocket"))]
pub mod websocket;

use alloc::borrow::Cow;
use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex};
pub type MultiExecutorMutex = CriticalSectionRawMutex;
pub type SingleExecutorMutex = NoopRawMutex;
Expand All @@ -13,5 +14,12 @@ pub use async_client::*;
pub use client::*;
#[cfg(any(feature = "json-rpc-std", feature = "json-rpc"))]
pub use json_rpc::*;
use serde::{Deserialize, Serialize};
#[cfg(any(feature = "websocket-std", feature = "websocket"))]
pub use websocket::*;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommonFields<'a> {
pub build_version: Option<Cow<'a, str>>,
pub network_id: Option<u32>,
}
71 changes: 71 additions & 0 deletions src/asynch/ledger/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use core::{cmp::min, convert::TryInto};

use alloc::string::ToString;
use anyhow::Result;

use crate::models::{
amount::XRPAmount,
requests::{Fee, Ledger},
results::{Drops, Fee as FeeResult, Ledger as LedgerResult},
};

use super::clients::AsyncClient;

pub async fn get_latest_validated_ledger_sequence(client: &impl AsyncClient) -> Result<u32> {
let ledger_response = client
.request(
Ledger::new(
None,
None,
None,
None,
None,
None,
Some("validated".into()),
None,
None,
None,
)
.into(),
)
.await?;

Ok(ledger_response
.try_into_result::<LedgerResult<'_>>()?
.ledger_index)
}

pub enum FeeType {
Open,
Minimum,
Dynamic,
}

pub async fn get_fee(
client: &impl AsyncClient,
max_fee: Option<u32>,
fee_type: Option<FeeType>,
) -> Result<XRPAmount<'_>> {
let fee_request = Fee::new(None);
match client.request(fee_request.into()).await {
Ok(response) => {
let drops = response.try_into_result::<FeeResult<'_>>()?.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()))
} else {
Ok(XRPAmount::from(fee.to_string()))
}
}
Err(err) => Err(err),
}
}

fn match_fee_type(fee_type: Option<FeeType>, drops: Drops<'_>) -> Result<u32> {
match fee_type {
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"),
}
}
6 changes: 6 additions & 0 deletions src/asynch/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
#[cfg(feature = "account-helpers")]
pub mod account;
#[cfg(any(
feature = "websocket-std",
feature = "websocket",
feature = "json-rpc-std",
feature = "json-rpc"
))]
pub mod clients;
#[cfg(feature = "ledger-helpers")]
pub mod ledger;
#[cfg(feature = "transaction-helpers")]
pub mod transaction;
16 changes: 16 additions & 0 deletions src/asynch/transaction/exceptions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use core::num::ParseIntError;

use alloc::borrow::Cow;
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>),
#[error("Unable to parse rippled version: {0}")]
ParseRippledVersionError(ParseIntError),
#[error("Invalid rippled version: {0}")]
InvalidRippledVersion(Cow<'a, str>),
}
Loading

0 comments on commit ddf8c53

Please sign in to comment.