Skip to content

Commit

Permalink
refactor: move types related to providers to evm-rpc-types crate (#271
Browse files Browse the repository at this point in the history
)

Follow-up on #257 to move the following types to the `evm_rpc_types`
crate:

1. `EthMainnetService`
2. `EthSepoliaService`
3. `HttpHeader`
4. `L2MainnetService`
5. `RpcApi`
6. `RpcConfig`
7. `RpcService`
8. `RpcServices`


so that the public API of all methods in `main.rs` only use types from
that crate as arguments.
  • Loading branch information
gregorydemay authored Sep 13, 2024
1 parent e2158f0 commit 27f43f0
Show file tree
Hide file tree
Showing 12 changed files with 636 additions and 174 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion evm_rpc_types/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- v1.0 `Nat256`: transparent wrapper around a `Nat` to guarantee that it fits in 256 bits.
- v1.0 `HexByte`, `Hex20`, `Hex32`, `Hex256` and `Hex` : Candid types wrapping an amount of bytes (`u8` for `HexByte`, `[u8; N]` for `HexN`, and `Vec<u8>` for `Hex`) that can be represented as an hexadecimal string (prefixed by `0x`) when serialized.
- v1.0 `HexByte`, `Hex20`, `Hex32`, `Hex256` and `Hex` : Candid types wrapping an amount of bytes (`u8` for `HexByte`,
`[u8; N]` for `HexN`, and `Vec<u8>` for `Hex`) that can be represented as an hexadecimal string (prefixed by `0x`)
when serialized.
- v1.0 Move `Block` to this crate.
- v1.0 Move `BlockTag` to this crate.
- v1.0 Move `FeeHistoryArgs` and `FeeHistory` to this crate.
- v1.0 Move `GetLogsArgs` and `LogEntry` to this crate.
- v1.0 Move `GetTransactionCountArgs` to this crate.
- v1.0 Move `RpcConfig` to this crate.
- v1.0 Move `SendRawTransactionStatus` to this crate.
- v1.0 Move `TransactionReceipt` to this crate.
- v1.0 Move providers-related struct `EthMainnetService`, `EthSepoliaService`, `HttpHeader`, `L2MainnetService`,
`RpcApi`, `RpcConfig`,
`RpcService`, `RpcServices` to this crate.
1 change: 1 addition & 0 deletions evm_rpc_types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ edition = "2021"
[dependencies]
candid = { workspace = true }
hex = { workspace = true }
ic-cdk = { workspace = true }
num-bigint = { workspace = true }
serde = { workspace = true }

Expand Down
5 changes: 5 additions & 0 deletions evm_rpc_types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ use std::str::FromStr;

mod request;
mod response;
mod rpc_client;

pub use request::{FeeHistoryArgs, GetLogsArgs, GetTransactionCountArgs};
pub use response::{Block, FeeHistory, LogEntry, SendRawTransactionStatus, TransactionReceipt};
pub use rpc_client::{
EthMainnetService, EthSepoliaService, HttpHeader, L2MainnetService, RpcApi, RpcConfig,
RpcService, RpcServices,
};

#[derive(Clone, Debug, PartialEq, Eq, CandidType, Deserialize, Default)]
pub enum BlockTag {
Expand Down
91 changes: 91 additions & 0 deletions evm_rpc_types/src/rpc_client/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
pub use ic_cdk::api::management_canister::http_request::HttpHeader;

use candid::{CandidType, Deserialize};
use serde::Serialize;

#[derive(Clone, Debug, PartialEq, Eq, Default, CandidType, Deserialize)]
pub struct RpcConfig {
#[serde(rename = "responseSizeEstimate")]
pub response_size_estimate: Option<u64>,
}

#[derive(Clone, CandidType, Deserialize)]
pub enum RpcServices {
Custom {
#[serde(rename = "chainId")]
chain_id: u64,
services: Vec<RpcApi>,
},
EthMainnet(Option<Vec<EthMainnetService>>),
EthSepolia(Option<Vec<EthSepoliaService>>),
ArbitrumOne(Option<Vec<L2MainnetService>>),
BaseMainnet(Option<Vec<L2MainnetService>>),
OptimismMainnet(Option<Vec<L2MainnetService>>),
}

#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize, CandidType)]
pub struct RpcApi {
pub url: String,
pub headers: Option<Vec<HttpHeader>>,
}

#[derive(
Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize, CandidType,
)]
pub enum EthMainnetService {
Alchemy,
Ankr,
BlockPi,
PublicNode,
Cloudflare,
Llama,
}

#[derive(
Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize, CandidType,
)]
pub enum EthSepoliaService {
Alchemy,
Ankr,
BlockPi,
PublicNode,
Sepolia,
}

#[derive(
Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize, CandidType,
)]
pub enum L2MainnetService {
Alchemy,
Ankr,
BlockPi,
PublicNode,
Llama,
}

#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize, CandidType)]
pub enum RpcService {
Chain(u64),
Provider(u64),
Custom(RpcApi),
EthMainnet(EthMainnetService),
EthSepolia(EthSepoliaService),
ArbitrumOne(L2MainnetService),
BaseMainnet(L2MainnetService),
OptimismMainnet(L2MainnetService),
}

impl std::fmt::Debug for RpcService {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RpcService::Chain(chain_id) => write!(f, "Chain({})", chain_id),
RpcService::Provider(provider_id) => write!(f, "Provider({})", provider_id),
RpcService::Custom(_) => write!(f, "Custom(..)"), // Redact credentials
RpcService::EthMainnet(service) => write!(f, "{:?}", service),
RpcService::EthSepolia(service) => write!(f, "{:?}", service),
RpcService::ArbitrumOne(service)
| RpcService::BaseMainnet(service)
| RpcService::OptimismMainnet(service) => write!(f, "{:?}", service),
}
}
}
136 changes: 53 additions & 83 deletions src/candid_rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,10 @@ use async_trait::async_trait;
use candid::Nat;
use cketh_common::{
eth_rpc::{ProviderError, ValidationError},
eth_rpc_client::{
providers::{RpcApi, RpcService},
EthRpcClient as CkEthRpcClient, MultiCallError, RpcConfig, RpcTransport,
},
lifecycle::EthereumNetwork,
eth_rpc_client::{EthRpcClient as CkEthRpcClient, MultiCallError, RpcTransport},
};
use ethers_core::{types::Transaction, utils::rlp};
use evm_rpc_types::{Hex, Hex32};
use evm_rpc_types::{Hex, Hex32, RpcServices};
use ic_cdk::api::management_canister::http_request::{CanisterHttpRequestArgument, HttpResponse};

use crate::{
Expand All @@ -25,7 +21,6 @@ use crate::{
providers::resolve_rpc_service,
types::{
MetricRpcHost, MetricRpcMethod, MultiRpcResult, ResolvedRpcService, RpcMethod, RpcResult,
RpcServices,
},
};

Expand All @@ -35,17 +30,23 @@ struct CanisterTransport;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl RpcTransport for CanisterTransport {
fn resolve_api(service: &RpcService) -> Result<RpcApi, ProviderError> {
Ok(resolve_rpc_service(service.clone())?.api())
fn resolve_api(
service: &cketh_common::eth_rpc_client::providers::RpcService,
) -> Result<cketh_common::eth_rpc_client::providers::RpcApi, ProviderError> {
use crate::candid_rpc::cketh_conversion::{from_rpc_service, into_rpc_api};
Ok(into_rpc_api(
resolve_rpc_service(from_rpc_service(service.clone()))?.api(),
))
}

async fn http_request(
service: &RpcService,
service: &cketh_common::eth_rpc_client::providers::RpcService,
method: &str,
request: CanisterHttpRequestArgument,
effective_response_size_estimate: u64,
) -> RpcResult<HttpResponse> {
let service = resolve_rpc_service(service.clone())?;
use crate::candid_rpc::cketh_conversion::from_rpc_service;
let service = resolve_rpc_service(from_rpc_service(service.clone()))?;
let cycles_cost = get_http_request_cost(
request
.body
Expand All @@ -68,81 +69,33 @@ fn check_services<T>(services: Vec<T>) -> RpcResult<Vec<T>> {

fn get_rpc_client(
source: RpcServices,
config: RpcConfig,
config: evm_rpc_types::RpcConfig,
) -> RpcResult<CkEthRpcClient<CanisterTransport>> {
Ok(match source {
RpcServices::Custom { chain_id, services } => CkEthRpcClient::new(
EthereumNetwork(chain_id),
Some(
check_services(services)?
.into_iter()
.map(RpcService::Custom)
.collect(),
),
config,
),
RpcServices::EthMainnet(services) => CkEthRpcClient::new(
EthereumNetwork::MAINNET,
Some(
check_services(services.unwrap_or_else(|| DEFAULT_ETH_MAINNET_SERVICES.to_vec()))?
.into_iter()
.map(RpcService::EthMainnet)
.collect(),
),
config,
),
RpcServices::EthSepolia(services) => CkEthRpcClient::new(
EthereumNetwork::SEPOLIA,
Some(
check_services(services.unwrap_or_else(|| DEFAULT_ETH_SEPOLIA_SERVICES.to_vec()))?
.into_iter()
.map(RpcService::EthSepolia)
.collect(),
),
config,
),
RpcServices::ArbitrumOne(services) => CkEthRpcClient::new(
EthereumNetwork::ARBITRUM,
Some(
check_services(services.unwrap_or_else(|| DEFAULT_L2_MAINNET_SERVICES.to_vec()))?
.into_iter()
.map(RpcService::ArbitrumOne)
.collect(),
),
config,
),
RpcServices::BaseMainnet(services) => CkEthRpcClient::new(
EthereumNetwork::BASE,
Some(
check_services(services.unwrap_or_else(|| DEFAULT_L2_MAINNET_SERVICES.to_vec()))?
.into_iter()
.map(RpcService::BaseMainnet)
.collect(),
),
config,
),
RpcServices::OptimismMainnet(services) => CkEthRpcClient::new(
EthereumNetwork::OPTIMISM,
Some(
check_services(services.unwrap_or_else(|| DEFAULT_L2_MAINNET_SERVICES.to_vec()))?
.into_iter()
.map(RpcService::OptimismMainnet)
.collect(),
),
config,
),
})
use crate::candid_rpc::cketh_conversion::{
into_ethereum_network, into_rpc_config, into_rpc_services,
};

let config = into_rpc_config(config);
let chain = into_ethereum_network(&source);
let providers = check_services(into_rpc_services(
source,
DEFAULT_ETH_MAINNET_SERVICES,
DEFAULT_ETH_SEPOLIA_SERVICES,
DEFAULT_L2_MAINNET_SERVICES,
))?;
Ok(CkEthRpcClient::new(chain, Some(providers), config))
}

fn process_result<T>(method: RpcMethod, result: Result<T, MultiCallError<T>>) -> MultiRpcResult<T> {
use crate::candid_rpc::cketh_conversion::from_rpc_service;
match result {
Ok(value) => MultiRpcResult::Consistent(Ok(value)),
Err(err) => match err {
MultiCallError::ConsistentError(err) => MultiRpcResult::Consistent(Err(err)),
MultiCallError::InconsistentResults(multi_call_results) => {
multi_call_results.results.iter().for_each(|(service, _)| {
if let Ok(ResolvedRpcService::Provider(provider)) =
resolve_rpc_service(service.clone())
resolve_rpc_service(from_rpc_service(service.clone()))
{
add_metric_entry!(
inconsistent_responses,
Expand All @@ -158,7 +111,13 @@ fn process_result<T>(method: RpcMethod, result: Result<T, MultiCallError<T>>) ->
)
}
});
MultiRpcResult::Inconsistent(multi_call_results.results.into_iter().collect())
MultiRpcResult::Inconsistent(
multi_call_results
.results
.into_iter()
.map(|(service, result)| (from_rpc_service(service), result))
.collect(),
)
}
},
}
Expand All @@ -169,7 +128,10 @@ pub struct CandidRpcClient {
}

impl CandidRpcClient {
pub fn new(source: RpcServices, config: Option<RpcConfig>) -> RpcResult<Self> {
pub fn new(
source: evm_rpc_types::RpcServices,
config: Option<evm_rpc_types::RpcConfig>,
) -> RpcResult<Self> {
Ok(Self {
client: get_rpc_client(source, config.unwrap_or_default())?,
})
Expand Down Expand Up @@ -287,11 +249,13 @@ fn get_transaction_hash(raw_signed_transaction_hex: &Hex) -> Option<Hex32> {
#[cfg(test)]
mod test {
use super::*;
use crate::candid_rpc::cketh_conversion::into_rpc_service;
use cketh_common::eth_rpc::RpcError;

#[test]
fn test_process_result_mapping() {
use cketh_common::eth_rpc_client::{providers::EthMainnetService, MultiCallResults};
use cketh_common::eth_rpc_client::MultiCallResults;
use evm_rpc_types::{EthMainnetService, RpcService};

let method = RpcMethod::EthGetTransactionCount;

Expand Down Expand Up @@ -325,9 +289,12 @@ mod test {
process_result(
method,
Err(MultiCallError::InconsistentResults(MultiCallResults {
results: vec![(RpcService::EthMainnet(EthMainnetService::Ankr), Ok(5))]
.into_iter()
.collect(),
results: vec![(
into_rpc_service(RpcService::EthMainnet(EthMainnetService::Ankr)),
Ok(5)
)]
.into_iter()
.collect(),
}))
),
MultiRpcResult::Inconsistent(vec![(
Expand All @@ -340,9 +307,12 @@ mod test {
method,
Err(MultiCallError::InconsistentResults(MultiCallResults {
results: vec![
(RpcService::EthMainnet(EthMainnetService::Ankr), Ok(5)),
(
RpcService::EthMainnet(EthMainnetService::Cloudflare),
into_rpc_service(RpcService::EthMainnet(EthMainnetService::Ankr)),
Ok(5)
),
(
into_rpc_service(RpcService::EthMainnet(EthMainnetService::Cloudflare)),
Err(RpcError::ProviderError(ProviderError::NoPermission))
)
]
Expand Down
Loading

0 comments on commit 27f43f0

Please sign in to comment.