From 52c5f8456a7a3828dc3ec52fb86f565f7bcaceb4 Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Thu, 4 Apr 2024 17:46:29 +0900 Subject: [PATCH 1/4] try with out [async_trait] --- Cargo.lock | 51 ++++++++++++++++++++++ Cargo.toml | 1 + examples/ethereum-rpc/src/main.rs | 10 +++-- reqwest-enum/Cargo.toml | 3 +- reqwest-enum/src/jsonrpc.rs | 2 + reqwest-enum/src/provider.rs | 72 +++++++++++++++++++++++++++++++ 6 files changed, 135 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 852ffbe..abc34c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,6 +202,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.30" @@ -209,6 +224,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -217,6 +233,34 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.30" @@ -235,10 +279,16 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -709,6 +759,7 @@ name = "reqwest-enum" version = "0.1.0" dependencies = [ "async-trait", + "futures", "reqwest", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 5693056..aa9acc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,4 @@ serde = { version = "^1.0.0", features = ["derive"] } serde_json = "^1.0.0" async-trait = "^0.1.0" +futures = "^0.3.0" diff --git a/examples/ethereum-rpc/src/main.rs b/examples/ethereum-rpc/src/main.rs index d0db046..aa3276a 100644 --- a/examples/ethereum-rpc/src/main.rs +++ b/examples/ethereum-rpc/src/main.rs @@ -1,14 +1,18 @@ extern crate reqwest_enum; use ethereum_rpc::EthereumRPC; use reqwest_enum::jsonrpc::JsonRpcResult; -use reqwest_enum::provider::{JsonRpcProviderType, Provider}; +use reqwest_enum::provider::{JsonRpcProviderType, JsonRpcProviderType2, Provider}; #[tokio::main] async fn main() -> Result<(), Box> { let provider = Provider::::default(); - let targets = vec![EthereumRPC::ChainId, EthereumRPC::GasPrice]; - let results: Vec> = provider.batch(targets).await?; + let targets = vec![ + EthereumRPC::ChainId, + EthereumRPC::GasPrice, + EthereumRPC::BlockNumber, + ]; + let results: Vec> = provider.batch_chunk_by(targets, 2).await?; for result in results { match result { JsonRpcResult::Value(response) => { diff --git a/reqwest-enum/Cargo.toml b/reqwest-enum/Cargo.toml index 81b13fd..2909cc6 100644 --- a/reqwest-enum/Cargo.toml +++ b/reqwest-enum/Cargo.toml @@ -13,10 +13,11 @@ edition = { workspace = true } [features] default = ["jsonrpc"] -jsonrpc = [] +jsonrpc = ["dep:futures"] [dependencies] reqwest = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } async-trait = { workspace = true } +futures = { workspace = true, optional = true } diff --git a/reqwest-enum/src/jsonrpc.rs b/reqwest-enum/src/jsonrpc.rs index 6bc468f..6c61569 100644 --- a/reqwest-enum/src/jsonrpc.rs +++ b/reqwest-enum/src/jsonrpc.rs @@ -60,8 +60,10 @@ pub struct JsonRpcError { pub message: String, } +#[cfg(feature = "jsonrpc")] impl std::error::Error for JsonRpcError {} +#[cfg(feature = "jsonrpc")] impl From for JsonRpcError { fn from(err: reqwest::Error) -> Self { JsonRpcError { diff --git a/reqwest-enum/src/provider.rs b/reqwest-enum/src/provider.rs index e03b2d4..59daec6 100644 --- a/reqwest-enum/src/provider.rs +++ b/reqwest-enum/src/provider.rs @@ -31,6 +31,16 @@ pub trait JsonRpcProviderType: ProviderType { ) -> Result>, JsonRpcError>; } +use core::future::Future; +use futures::future::join_all; +pub trait JsonRpcProviderType2: ProviderType { + fn batch_chunk_by( + &self, + targets: Vec, + chunk_size: usize, + ) -> impl Future>, JsonRpcError>>; +} + pub type EndpointFn = fn(target: &T) -> String; pub struct Provider { /// endpoint closure to customize the endpoint (url / path) @@ -94,6 +104,68 @@ where } } +impl JsonRpcProviderType2 for Provider +where + T: JsonRpcTarget + Send, +{ + async fn batch_chunk_by( + &self, + targets: Vec, + chunk_size: usize, + ) -> Result>, JsonRpcError> { + if targets.is_empty() || chunk_size == 0 { + return Err(JsonRpcError { + code: -32600, + message: "Invalid Request".into(), + }); + } + + let chunk_targets = targets.chunks(chunk_size).collect::>(); + let mut rpc_requests = Vec::::new(); + + for (chunk_idx, chunk) in chunk_targets.into_iter().enumerate() { + let target = &chunk[0]; + let mut request = self.request_builder(target); + let mut requests = Vec::::new(); + for (k, v) in chunk.iter().enumerate() { + let request = JsonRpcRequest::new( + v.method_name(), + v.params(), + (chunk_idx * chunk_size + k) as u64, + ); + requests.push(request); + } + + request = request.body(HTTPBody::from_array(&requests).inner); + rpc_requests.push(request); + } + let bodies = join_all(rpc_requests.into_iter().map(|request| async move { + let response = request.send().await?; + let body = response.json::>>().await?; + Ok(body) + })) + .await; + + let mut results = Vec::>::new(); + let mut error: Option = None; + + for result in bodies { + match result { + Ok(body) => { + results.extend(body); + } + Err(err) => { + error = Some(err); + } + } + } + if let Some(err) = error { + return Err(err); + } + Ok(results) + } +} + impl Provider where T: Target, From 94f57c8259a2bd78747d283093f52ab9626284b1 Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Thu, 4 Apr 2024 17:53:21 +0900 Subject: [PATCH 2/4] remove async-trait --- Cargo.lock | 12 ----------- examples/ethereum-rpc/src/main.rs | 2 +- reqwest-enum/Cargo.toml | 1 - reqwest-enum/src/provider.rs | 35 ++++++++++++------------------- 4 files changed, 14 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index abc34c3..19cd3ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,17 +39,6 @@ dependencies = [ "syn", ] -[[package]] -name = "async-trait" -version = "0.1.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "autocfg" version = "1.2.0" @@ -758,7 +747,6 @@ dependencies = [ name = "reqwest-enum" version = "0.1.0" dependencies = [ - "async-trait", "futures", "reqwest", "serde", diff --git a/examples/ethereum-rpc/src/main.rs b/examples/ethereum-rpc/src/main.rs index aa3276a..4cfe74e 100644 --- a/examples/ethereum-rpc/src/main.rs +++ b/examples/ethereum-rpc/src/main.rs @@ -1,7 +1,7 @@ extern crate reqwest_enum; use ethereum_rpc::EthereumRPC; use reqwest_enum::jsonrpc::JsonRpcResult; -use reqwest_enum::provider::{JsonRpcProviderType, JsonRpcProviderType2, Provider}; +use reqwest_enum::provider::{JsonRpcProviderType, Provider}; #[tokio::main] async fn main() -> Result<(), Box> { diff --git a/reqwest-enum/Cargo.toml b/reqwest-enum/Cargo.toml index 2909cc6..5a4952f 100644 --- a/reqwest-enum/Cargo.toml +++ b/reqwest-enum/Cargo.toml @@ -19,5 +19,4 @@ jsonrpc = ["dep:futures"] reqwest = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -async-trait = { workspace = true } futures = { workspace = true, optional = true } diff --git a/reqwest-enum/src/provider.rs b/reqwest-enum/src/provider.rs index 59daec6..ecad371 100644 --- a/reqwest-enum/src/provider.rs +++ b/reqwest-enum/src/provider.rs @@ -1,39 +1,38 @@ #[cfg(feature = "jsonrpc")] use crate::jsonrpc::{JsonRpcError, JsonRpcRequest, JsonRpcResult, JsonRpcTarget}; +#[cfg(feature = "jsonrpc")] +use futures::future::join_all; + use crate::{ http::{HTTPBody, HTTPResponse}, target::Target, }; - -use async_trait::async_trait; +use core::future::Future; use reqwest::{Client, Error}; use serde::de::DeserializeOwned; -#[async_trait] -pub trait ProviderType { +pub trait ProviderType: Send { /// request to target and return http response - async fn request(&self, target: T) -> Result; + fn request(&self, target: T) -> impl Future>; } -#[async_trait] pub trait JsonProviderType: ProviderType { /// request and deserialize response to json using serde - async fn request_json(&self, target: T) -> Result; + fn request_json( + &self, + target: T, + ) -> impl Future>; } #[cfg(feature = "jsonrpc")] -#[async_trait] + pub trait JsonRpcProviderType: ProviderType { /// batch isomorphic JSON-RPC requests - async fn batch( + fn batch( &self, targets: Vec, - ) -> Result>, JsonRpcError>; -} + ) -> impl Future>, JsonRpcError>>; -use core::future::Future; -use futures::future::join_all; -pub trait JsonRpcProviderType2: ProviderType { fn batch_chunk_by( &self, targets: Vec, @@ -48,7 +47,6 @@ pub struct Provider { client: Client, } -#[async_trait] impl ProviderType for Provider where T: Target + Send, @@ -60,7 +58,6 @@ where } } -#[async_trait] impl JsonProviderType for Provider where T: Target + Send, @@ -73,7 +70,6 @@ where } #[cfg(feature = "jsonrpc")] -#[async_trait] impl JsonRpcProviderType for Provider where T: JsonRpcTarget + Send, @@ -102,12 +98,7 @@ where let body = response.json::>>().await?; Ok(body) } -} -impl JsonRpcProviderType2 for Provider -where - T: JsonRpcTarget + Send, -{ async fn batch_chunk_by( &self, targets: Vec, From a8882ff947b5c8f4605d428cd0494c0ae293ad9b Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Thu, 4 Apr 2024 17:55:41 +0900 Subject: [PATCH 3/4] bump version --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 19cd3ef..0d40be5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,7 +145,7 @@ dependencies = [ [[package]] name = "ethereum-rpc" -version = "0.1.0" +version = "0.1.1" dependencies = [ "reqwest", "reqwest-enum", @@ -745,7 +745,7 @@ dependencies = [ [[package]] name = "reqwest-enum" -version = "0.1.0" +version = "0.1.1" dependencies = [ "futures", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index aa9acc4..66b8928 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -package.version = "0.1.0" +package.version = "0.1.1" package.edition = "2021" package.documentation = "https://docs.rs/reqwest_enum" package.authors = ["Tao Xu "] From 0c41e22754ff26cb2941ecee9112128994da7a6e Mon Sep 17 00:00:00 2001 From: hewigovens <360470+hewigovens@users.noreply.github.com> Date: Thu, 4 Apr 2024 19:38:20 +0900 Subject: [PATCH 4/4] Fix JsonRpcErrorResponse error type --- examples/ethereum-rpc/src/ethereum_rpc.rs | 51 ++++++++++++++----- examples/ethereum-rpc/src/main.rs | 7 ++- .../ethereum-rpc/tests/integration_test.rs | 21 +++++++- reqwest-enum/src/jsonrpc.rs | 6 +-- reqwest-enum/src/provider.rs | 4 +- 5 files changed, 70 insertions(+), 19 deletions(-) diff --git a/examples/ethereum-rpc/src/ethereum_rpc.rs b/examples/ethereum-rpc/src/ethereum_rpc.rs index 3f2e28f..b9a181f 100644 --- a/examples/ethereum-rpc/src/ethereum_rpc.rs +++ b/examples/ethereum-rpc/src/ethereum_rpc.rs @@ -6,7 +6,7 @@ use reqwest_enum::{ target::Target, }; use serde::{Deserialize, Serialize}; -use serde_json::Value; +use serde_json::{Number, Value}; use std::collections::HashMap; #[derive(Serialize, Deserialize)] @@ -20,6 +20,7 @@ pub struct TransactionObject { } pub enum BlockParameter { + // hexadecimal block number Number(&'static str), Latest, Earliest, @@ -48,21 +49,33 @@ impl From<&BlockParameter> for serde_json::Value { } } +fn u64_to_value(val: &u64) -> serde_json::Value { + Value::Number(Number::from(*val)) +} + pub enum EthereumRPC { + BlockNumber, + BlobBaseFee, ChainId, + Call(TransactionObject, BlockParameter), + EstimateGas(TransactionObject), + // blockCount, newestBlock, rewardPercentiles + FeeHistory(u64, BlockParameter, Vec), GasPrice, - BlockNumber, + // addrees GetBalance(&'static str), - GetBlockByNumber(&'static str, bool), + GetBlockByNumber(BlockParameter, bool), + GetCode(&'static str, BlockParameter), + // address, blockNumber GetTransactionCount(&'static str, BlockParameter), - Call(TransactionObject, BlockParameter), - EstimateGas(TransactionObject), SendRawTransaction(&'static str), + Syncing, } impl JsonRpcTarget for EthereumRPC { fn method_name(&self) -> &'static str { match self { + EthereumRPC::Syncing => "eth_syncing", EthereumRPC::ChainId => "eth_chainId", EthereumRPC::GasPrice => "eth_gasPrice", EthereumRPC::BlockNumber => "eth_blockNumber", @@ -72,6 +85,9 @@ impl JsonRpcTarget for EthereumRPC { EthereumRPC::GetTransactionCount(_, _) => "eth_getTransactionCount", EthereumRPC::Call(_, _) => "eth_call", EthereumRPC::EstimateGas(_) => "eth_estimateGas", + EthereumRPC::FeeHistory(_, _, _) => "eth_feeHistory", + EthereumRPC::GetCode(_, _) => "eth_getCode", + EthereumRPC::BlobBaseFee => "eth_blobBaseFee", } } @@ -85,10 +101,7 @@ impl JsonRpcTarget for EthereumRPC { vec![Value::String(tx.to_string())] } EthereumRPC::GetBlockByNumber(block, full) => { - vec![ - Value::String(block.to_string()), - Value::Bool(full.to_owned()), - ] + vec![block.into(), Value::Bool(full.to_owned())] } EthereumRPC::GetTransactionCount(address, block) => { vec![Value::String(address.to_string()), block.into()] @@ -101,9 +114,23 @@ impl JsonRpcTarget for EthereumRPC { let value = serde_json::to_value(tx).unwrap(); vec![value] } - EthereumRPC::ChainId => vec![], - EthereumRPC::GasPrice => vec![], - EthereumRPC::BlockNumber => vec![], + EthereumRPC::FeeHistory(block_count, block, reward_percentiles) => { + let mut params = vec![ + u64_to_value(block_count), + block.into(), + Value::Array(reward_percentiles.iter().map(u64_to_value).collect()), + ]; + params.push(Value::Bool(false)); + params + } + EthereumRPC::GetCode(address, block) => { + vec![Value::String(address.to_string()), block.into()] + } + EthereumRPC::ChainId + | EthereumRPC::GasPrice + | EthereumRPC::BlockNumber + | EthereumRPC::Syncing + | EthereumRPC::BlobBaseFee => vec![], } } } diff --git a/examples/ethereum-rpc/src/main.rs b/examples/ethereum-rpc/src/main.rs index 4cfe74e..b382ce0 100644 --- a/examples/ethereum-rpc/src/main.rs +++ b/examples/ethereum-rpc/src/main.rs @@ -1,5 +1,5 @@ extern crate reqwest_enum; -use ethereum_rpc::EthereumRPC; +use ethereum_rpc::{BlockParameter, EthereumRPC}; use reqwest_enum::jsonrpc::JsonRpcResult; use reqwest_enum::provider::{JsonRpcProviderType, Provider}; @@ -11,6 +11,11 @@ async fn main() -> Result<(), Box> { EthereumRPC::ChainId, EthereumRPC::GasPrice, EthereumRPC::BlockNumber, + EthereumRPC::GetBalance("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"), + EthereumRPC::GetCode( + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + BlockParameter::Latest, + ), ]; let results: Vec> = provider.batch_chunk_by(targets, 2).await?; for result in results { diff --git a/examples/ethereum-rpc/tests/integration_test.rs b/examples/ethereum-rpc/tests/integration_test.rs index fda70dd..0303824 100644 --- a/examples/ethereum-rpc/tests/integration_test.rs +++ b/examples/ethereum-rpc/tests/integration_test.rs @@ -2,7 +2,7 @@ mod ethereum_rpc_test { use ethereum_rpc::{BlockParameter, EthereumRPC}; - use reqwest_enum::jsonrpc::JsonRpcResponse; + use reqwest_enum::jsonrpc::{JsonRpcResponse, JsonRpcResult}; use reqwest_enum::provider::{JsonProviderType, Provider}; const TEST_ADDRESS: &str = "0xee5f5c53ce2159fc6dd4b0571e86a4a390d04846"; @@ -56,4 +56,23 @@ mod ethereum_rpc_test { .unwrap(); assert_eq!(response.result, "0x0"); } + + #[tokio::test] + async fn test_syncing() { + let provider = Provider::::default(); + let response: JsonRpcResponse = + provider.request_json(EthereumRPC::Syncing).await.unwrap(); + assert!(!response.result); + } + + #[tokio::test] + async fn test_blob_base_fee() { + let provider = Provider::::default(); + let result: JsonRpcResult = provider + .request_json(EthereumRPC::BlobBaseFee) + .await + .expect("request error"); + + assert!(matches!(result, JsonRpcResult::Error(_))); + } } diff --git a/reqwest-enum/src/jsonrpc.rs b/reqwest-enum/src/jsonrpc.rs index 6c61569..5f5ad39 100644 --- a/reqwest-enum/src/jsonrpc.rs +++ b/reqwest-enum/src/jsonrpc.rs @@ -40,7 +40,7 @@ impl From for HTTPBody { #[cfg(feature = "jsonrpc")] #[derive(Debug, Serialize, Deserialize)] pub struct JsonRpcResponse { - pub id: u64, + pub id: JsonRpcId, pub jsonrpc: String, pub result: T, } @@ -49,8 +49,8 @@ pub struct JsonRpcResponse { #[derive(Debug, Serialize, Deserialize)] pub struct JsonRpcErrorResponse { pub jsonrpc: String, - pub id: u64, - pub error: String, + pub id: JsonRpcId, + pub error: JsonRpcError, } #[cfg(feature = "jsonrpc")] diff --git a/reqwest-enum/src/provider.rs b/reqwest-enum/src/provider.rs index ecad371..e7acc5c 100644 --- a/reqwest-enum/src/provider.rs +++ b/reqwest-enum/src/provider.rs @@ -89,7 +89,7 @@ where let mut request = self.request_builder(target); let mut requests = Vec::::new(); for (k, v) in targets.iter().enumerate() { - let request = JsonRpcRequest::new(v.method_name(), v.params(), k as u64); + let request = JsonRpcRequest::new(v.method_name(), v.params(), (k + 1) as u64); requests.push(request); } @@ -122,7 +122,7 @@ where let request = JsonRpcRequest::new( v.method_name(), v.params(), - (chunk_idx * chunk_size + k) as u64, + (chunk_idx * chunk_size + k + 1) as u64, ); requests.push(request); }