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: simplify EthRpcClient #278

Merged
merged 42 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
282621b
243: copied client
gregorydemay Sep 13, 2024
a0ab713
243: copied eth_rpc_client
gregorydemay Sep 13, 2024
ab382a0
243: copied providers
gregorydemay Sep 13, 2024
cca8be7
243: copied eth_rpc
gregorydemay Sep 13, 2024
b3ab56f
243: copied requests
gregorydemay Sep 13, 2024
3476f9b
243: copied responses
gregorydemay Sep 13, 2024
7095174
243: copied tests
gregorydemay Sep 13, 2024
e819020
243: fixing rpc_client
gregorydemay Sep 13, 2024
82798b7
243: copy numeric
gregorydemay Sep 13, 2024
5226948
243: copy checked_amount
gregorydemay Sep 13, 2024
4511a34
243: fix checked_amount
gregorydemay Sep 13, 2024
671b94f
243: fix numeric
gregorydemay Sep 13, 2024
e1753fb
243: copy eth_rpc tests
gregorydemay Sep 13, 2024
7f69fe6
243: fix requests
gregorydemay Sep 13, 2024
317915c
243: fix response
gregorydemay Sep 13, 2024
821599b
243: remove reduce_with_min_by_key
gregorydemay Sep 13, 2024
8a2fac8
243: copy eth_rpc_error
gregorydemay Sep 13, 2024
5b97b19
243: fix eth_rpc_error
gregorydemay Sep 13, 2024
cb3484c
243: fix eth_rpc
gregorydemay Sep 13, 2024
8bcbf18
243: clean-up
gregorydemay Sep 13, 2024
6c93dc5
243: fix client instantiation and eth_get_logs
gregorydemay Sep 13, 2024
e081cac
243: fix eth_get_block_by_number
gregorydemay Sep 13, 2024
6b4e493
243: fix eth_get_transaction_receipt
gregorydemay Sep 13, 2024
344d59b
243: fix eth_get_transaction_count
gregorydemay Sep 13, 2024
daf4e9f
243: fix eth_fee_history
gregorydemay Sep 13, 2024
7f44a2b
243: fix eth_send_raw_transaction
gregorydemay Sep 13, 2024
e036b22
243: it compiles!
gregorydemay Sep 13, 2024
c552b93
243: remove has_http_outcall_error_matching
gregorydemay Sep 13, 2024
ee10dd2
243: tests compile!
gregorydemay Sep 13, 2024
4e5ef50
243: clean-up
gregorydemay Sep 13, 2024
4300498
243: fix unit tests
gregorydemay Sep 16, 2024
dfebc55
243: remove cketh_common
gregorydemay Sep 16, 2024
d0c8094
243: Clippy
gregorydemay Sep 16, 2024
79946f5
243: format
gregorydemay Sep 16, 2024
714a12a
243: format
gregorydemay Sep 16, 2024
9e733ad
Merge branch 'main' of https://github.com/internet-computer-protocol/…
rvanasa Sep 16, 2024
bee5cd7
243: simplify instantiation
gregorydemay Sep 17, 2024
68ee463
243: remove providers.rs
gregorydemay Sep 17, 2024
ff50c3a
243: unit tests
gregorydemay Sep 17, 2024
c861949
243: remove CanisterTransport
gregorydemay Sep 17, 2024
d5f38cb
Merge branch 'main' into gdemay/243-cketh-client-simplify
gregorydemay Sep 18, 2024
f37a378
243: re-remove metrics
gregorydemay Sep 18, 2024
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: 0 additions & 1 deletion Cargo.lock

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

3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@ serde = { workspace = true }
serde_json = { workspace = true }
thousands = "0.2"
url = "2.5"
async-trait = "0.1"
hex = "0.4"
ethers-core = "2.0"
zeroize = {version = "1.8", features = ["zeroize_derive"]}
zeroize = { version = "1.8", features = ["zeroize_derive"] }

[dev-dependencies]
assert_matches = "1.5"
Expand Down
80 changes: 8 additions & 72 deletions src/candid_rpc.rs
Original file line number Diff line number Diff line change
@@ -1,79 +1,15 @@
mod cketh_conversion;

use async_trait::async_trait;
use candid::Nat;
use ethers_core::{types::Transaction, utils::rlp};
use evm_rpc_types::{
Hex, Hex32, MultiRpcResult, ProviderError, RpcApi, RpcError, RpcResult, RpcService,
RpcServices, ValidationError,
};
use ic_cdk::api::management_canister::http_request::{CanisterHttpRequestArgument, HttpResponse};

use crate::constants::{
DEFAULT_ETH_MAINNET_SERVICES, DEFAULT_ETH_SEPOLIA_SERVICES, DEFAULT_L2_MAINNET_SERVICES,
};
use crate::rpc_client::{EthRpcClient, MultiCallError, RpcTransport};
use crate::rpc_client::{EthRpcClient, MultiCallError};
use crate::{
accounting::get_http_request_cost,
add_metric_entry,
constants::ETH_GET_LOGS_MAX_BLOCKS,
http::http_request,
providers::resolve_rpc_service,
types::{MetricRpcHost, MetricRpcMethod, ResolvedRpcService, RpcMethod},
types::{MetricRpcHost, ResolvedRpcService, RpcMethod},
};

#[derive(Clone, Debug, PartialEq, Eq)]
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())
}

async fn http_request(
service: &RpcService,
method: &str,
request: CanisterHttpRequestArgument,
effective_response_size_estimate: u64,
) -> Result<HttpResponse, RpcError> {
let service = resolve_rpc_service(service.clone())?;
let cycles_cost = get_http_request_cost(
request
.body
.as_ref()
.map(|bytes| bytes.len() as u64)
.unwrap_or_default(),
effective_response_size_estimate,
);
let rpc_method = MetricRpcMethod(method.to_string());
http_request(rpc_method, service, request, cycles_cost).await
}
}

fn check_services<T>(services: Vec<T>) -> RpcResult<Vec<T>> {
if services.is_empty() {
Err(ProviderError::ProviderNotFound)?;
}
Ok(services)
}

fn get_rpc_client(
source: RpcServices,
config: evm_rpc_types::RpcConfig,
) -> RpcResult<EthRpcClient<CanisterTransport>> {
use crate::candid_rpc::cketh_conversion::{into_ethereum_network, into_rpc_services};

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(EthRpcClient::new(chain, Some(providers), config))
}
use candid::Nat;
use ethers_core::{types::Transaction, utils::rlp};
use evm_rpc_types::{Hex, Hex32, MultiRpcResult, RpcResult, ValidationError};

fn process_result<T>(method: RpcMethod, result: Result<T, MultiCallError<T>>) -> MultiRpcResult<T> {
match result {
Expand Down Expand Up @@ -106,7 +42,7 @@ fn process_result<T>(method: RpcMethod, result: Result<T, MultiCallError<T>>) ->
}

pub struct CandidRpcClient {
client: EthRpcClient<CanisterTransport>,
client: EthRpcClient,
}

impl CandidRpcClient {
Expand All @@ -115,7 +51,7 @@ impl CandidRpcClient {
config: Option<evm_rpc_types::RpcConfig>,
) -> RpcResult<Self> {
Ok(Self {
client: get_rpc_client(source, config.unwrap_or_default())?,
client: EthRpcClient::new(source, config)?,
})
}

Expand Down Expand Up @@ -232,7 +168,7 @@ fn get_transaction_hash(raw_signed_transaction_hex: &Hex) -> Option<Hex32> {
mod test {
use super::*;
use crate::rpc_client::{MultiCallError, MultiCallResults};
use evm_rpc_types::RpcError;
use evm_rpc_types::{ProviderError, RpcError};

#[test]
fn test_process_result_mapping() {
Expand Down
59 changes: 0 additions & 59 deletions src/candid_rpc/cketh_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,65 +182,6 @@ pub(super) fn from_send_raw_transaction_result(
}
}

pub(super) fn into_ethereum_network(
source: &evm_rpc_types::RpcServices,
) -> crate::rpc_client::EthereumNetwork {
match &source {
evm_rpc_types::RpcServices::Custom { chain_id, .. } => {
crate::rpc_client::EthereumNetwork::from(*chain_id)
}
evm_rpc_types::RpcServices::EthMainnet(_) => crate::rpc_client::EthereumNetwork::MAINNET,
evm_rpc_types::RpcServices::EthSepolia(_) => crate::rpc_client::EthereumNetwork::SEPOLIA,
evm_rpc_types::RpcServices::ArbitrumOne(_) => crate::rpc_client::EthereumNetwork::ARBITRUM,
evm_rpc_types::RpcServices::BaseMainnet(_) => crate::rpc_client::EthereumNetwork::BASE,
evm_rpc_types::RpcServices::OptimismMainnet(_) => {
crate::rpc_client::EthereumNetwork::OPTIMISM
}
}
}

pub(super) fn into_rpc_services(
source: evm_rpc_types::RpcServices,
default_eth_mainnet_services: &[evm_rpc_types::EthMainnetService],
default_eth_sepolia_services: &[evm_rpc_types::EthSepoliaService],
default_l2_mainnet_services: &[evm_rpc_types::L2MainnetService],
) -> Vec<evm_rpc_types::RpcService> {
match source {
evm_rpc_types::RpcServices::Custom {
chain_id: _,
services,
} => services
.into_iter()
.map(evm_rpc_types::RpcService::Custom)
.collect(),
evm_rpc_types::RpcServices::EthMainnet(services) => services
.unwrap_or_else(|| default_eth_mainnet_services.to_vec())
.into_iter()
.map(evm_rpc_types::RpcService::EthMainnet)
.collect(),
evm_rpc_types::RpcServices::EthSepolia(services) => services
.unwrap_or_else(|| default_eth_sepolia_services.to_vec())
.into_iter()
.map(evm_rpc_types::RpcService::EthSepolia)
.collect(),
evm_rpc_types::RpcServices::ArbitrumOne(services) => services
.unwrap_or_else(|| default_l2_mainnet_services.to_vec())
.into_iter()
.map(evm_rpc_types::RpcService::ArbitrumOne)
.collect(),
evm_rpc_types::RpcServices::BaseMainnet(services) => services
.unwrap_or_else(|| default_l2_mainnet_services.to_vec())
.into_iter()
.map(evm_rpc_types::RpcService::BaseMainnet)
.collect(),
evm_rpc_types::RpcServices::OptimismMainnet(services) => services
.unwrap_or_else(|| default_l2_mainnet_services.to_vec())
.into_iter()
.map(evm_rpc_types::RpcService::OptimismMainnet)
.collect(),
}
}

pub(super) fn into_hash(value: Hex32) -> Hash {
Hash(value.into())
}
Expand Down
19 changes: 0 additions & 19 deletions src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use evm_rpc_types::{EthMainnetService, EthSepoliaService, L2MainnetService};

// HTTP outcall cost calculation
// See https://internetcomputer.org/docs/current/developer-docs/gas-cost#special-features
pub const INGRESS_OVERHEAD_BYTES: u128 = 100;
Expand Down Expand Up @@ -36,23 +34,6 @@ pub const API_KEY_REPLACE_STRING: &str = "{API_KEY}";
pub const VALID_API_KEY_CHARS: &str =
"0123456789ABCDEFGHIJKLMNOPQRTSUVWXYZabcdefghijklmnopqrstuvwxyz$-_.+!*";

// Providers used by default (when passing `null` with `RpcServices`)
pub const DEFAULT_ETH_MAINNET_SERVICES: &[EthMainnetService] = &[
EthMainnetService::Ankr,
EthMainnetService::Cloudflare,
EthMainnetService::PublicNode,
];
pub const DEFAULT_ETH_SEPOLIA_SERVICES: &[EthSepoliaService] = &[
EthSepoliaService::Ankr,
EthSepoliaService::BlockPi,
EthSepoliaService::PublicNode,
];
pub const DEFAULT_L2_MAINNET_SERVICES: &[L2MainnetService] = &[
L2MainnetService::Ankr,
L2MainnetService::BlockPi,
L2MainnetService::PublicNode,
];

pub const CONTENT_TYPE_HEADER_LOWERCASE: &str = "content-type";
pub const CONTENT_TYPE_VALUE: &str = "application/json";

Expand Down
43 changes: 31 additions & 12 deletions src/rpc_client/eth_rpc/mod.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
//! This module contains definitions for communicating with an Ethereum API using the [JSON RPC](https://ethereum.org/en/developers/docs/apis/json-rpc/)
//! interface.

use crate::accounting::get_http_request_cost;
use crate::logs::{DEBUG, TRACE_HTTP};
use crate::memory::next_request_id;
use crate::providers::resolve_rpc_service;
use crate::rpc_client::checked_amount::CheckedAmountOf;
use crate::rpc_client::eth_rpc_error::{sanitize_send_raw_transaction_result, Parser};
use crate::rpc_client::numeric::{BlockNumber, LogIndex, TransactionCount, Wei, WeiPerGas};
use crate::rpc_client::responses::TransactionReceipt;
use crate::rpc_client::RpcTransport;
use crate::types::MetricRpcMethod;
use candid::candid_method;
use ethnum;
use evm_rpc_types::{HttpOutcallError, JsonRpcError, RpcError, RpcService};
use evm_rpc_types::{HttpOutcallError, JsonRpcError, ProviderError, RpcApi, RpcError, RpcService};
use ic_canister_log::log;
use ic_cdk::api::call::RejectionCode;
use ic_cdk::api::management_canister::http_request::{
Expand Down Expand Up @@ -615,14 +617,13 @@ impl<T: HttpResponsePayload> HttpResponsePayload for Option<T> {}
impl HttpResponsePayload for TransactionCount {}

/// Calls a JSON-RPC method on an Ethereum node at the specified URL.
pub async fn call<T, I, O>(
pub async fn call<I, O>(
provider: &RpcService,
method: impl Into<String>,
params: I,
mut response_size_estimate: ResponseSizeEstimate,
) -> Result<O, RpcError>
where
T: RpcTransport,
I: Serialize,
O: DeserializeOwned + HttpResponsePayload,
{
Expand All @@ -633,7 +634,7 @@ where
method: eth_method.clone(),
id: 1,
};
let api = T::resolve_api(provider)?;
let api = resolve_api(provider)?;
let url = &api.url;
let mut headers = vec![HttpHeader {
name: "Content-Type".to_string(),
Expand Down Expand Up @@ -673,13 +674,8 @@ where
)),
};

let response = match T::http_request(
provider,
&eth_method,
request,
effective_size_estimate,
)
.await
let response = match http_request(provider, &eth_method, request, effective_size_estimate)
.await
{
Err(RpcError::HttpOutcallError(HttpOutcallError::IcError { code, message }))
if is_response_too_large(&code, &message) =>
Expand Down Expand Up @@ -730,6 +726,29 @@ where
}
}

fn resolve_api(service: &RpcService) -> Result<RpcApi, ProviderError> {
Ok(resolve_rpc_service(service.clone())?.api())
}

async fn http_request(
service: &RpcService,
method: &str,
request: CanisterHttpRequestArgument,
effective_response_size_estimate: u64,
) -> Result<HttpResponse, RpcError> {
let service = resolve_rpc_service(service.clone())?;
let cycles_cost = get_http_request_cost(
request
.body
.as_ref()
.map(|bytes| bytes.len() as u64)
.unwrap_or_default(),
effective_response_size_estimate,
);
let rpc_method = MetricRpcMethod(method.to_string());
crate::http::http_request(rpc_method, service, request, cycles_cost).await
}

fn http_status_code(response: &HttpResponse) -> u16 {
use num_traits::cast::ToPrimitive;
// HTTP status code are always 3 decimal digits, hence at most 999.
Expand Down
Loading