Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: move types related to providers to evm-rpc-types crate #271

Merged
merged 4 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This trait is currently defined in cketh_common and that's why we need to still types from that crate here.

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