From 282621bfb686b9dc5ce70dcf8eeb6eee35911d0e Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 11:19:44 +0200 Subject: [PATCH 01/42] 243: copied client --- src/candid_rpc.rs | 83 +++++++++++++++++-------------------------- src/lib.rs | 1 + src/rpc_client/mod.rs | 72 +++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 50 deletions(-) create mode 100644 src/rpc_client/mod.rs diff --git a/src/candid_rpc.rs b/src/candid_rpc.rs index 0b8824cb..ae400a98 100644 --- a/src/candid_rpc.rs +++ b/src/candid_rpc.rs @@ -2,14 +2,15 @@ mod cketh_conversion; use async_trait::async_trait; use candid::Nat; -use cketh_common::eth_rpc_client::{EthRpcClient as CkEthRpcClient, MultiCallError, RpcTransport}; use ethers_core::{types::Transaction, utils::rlp}; use evm_rpc_types::{ - Hex, Hex32, MultiRpcResult, ProviderError, RpcResult, RpcServices, ValidationError, + Hex, Hex32, MultiRpcResult, ProviderError, RpcApi, RpcError, RpcResult, RpcService, + RpcServices, ValidationError, }; use ic_cdk::api::management_canister::http_request::{CanisterHttpRequestArgument, HttpResponse}; use crate::candid_rpc::cketh_conversion::into_rpc_error; +use crate::rpc_client::{EthRpcClient, MultiCallError, RpcTransport}; use crate::{ accounting::get_http_request_cost, add_metric_entry, @@ -28,29 +29,17 @@ 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: &cketh_common::eth_rpc_client::providers::RpcService, - ) -> Result - { - use crate::candid_rpc::cketh_conversion::{ - from_rpc_service, into_provider_error, into_rpc_api, - }; - Ok(into_rpc_api( - resolve_rpc_service(from_rpc_service(service.clone())) - .map_err(into_provider_error)? - .api(), - )) + fn resolve_api(service: &RpcService) -> Result { + Ok(resolve_rpc_service(service.clone())?.api()) } async fn http_request( - service: &cketh_common::eth_rpc_client::providers::RpcService, + service: &RpcService, method: &str, request: CanisterHttpRequestArgument, effective_response_size_estimate: u64, - ) -> Result { - use crate::candid_rpc::cketh_conversion::{from_rpc_service, into_provider_error}; - let service = - resolve_rpc_service(from_rpc_service(service.clone())).map_err(into_provider_error)?; + ) -> Result { + let service = resolve_rpc_service(service.clone())?; let cycles_cost = get_http_request_cost( request .body @@ -60,9 +49,7 @@ impl RpcTransport for CanisterTransport { effective_response_size_estimate, ); let rpc_method = MetricRpcMethod(method.to_string()); - http_request(rpc_method, service, request, cycles_cost) - .await - .map_err(into_rpc_error) + http_request(rpc_method, service, request, cycles_cost).await } } @@ -73,37 +60,34 @@ fn check_services(services: Vec) -> RpcResult> { Ok(services) } -fn get_rpc_client( - source: RpcServices, - config: evm_rpc_types::RpcConfig, -) -> RpcResult> { - 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 get_rpc_client( +// source: RpcServices, +// config: evm_rpc_types::RpcConfig, +// ) -> RpcResult> { +// 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(method: RpcMethod, result: Result>) -> MultiRpcResult { - use crate::candid_rpc::cketh_conversion::{from_rpc_error, from_rpc_service}; match result { Ok(value) => MultiRpcResult::Consistent(Ok(value)), Err(err) => match err { - MultiCallError::ConsistentError(err) => { - MultiRpcResult::Consistent(Err(from_rpc_error(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(from_rpc_service(service.clone())) + resolve_rpc_service(service.clone()) { add_metric_entry!( inconsistent_responses, @@ -123,9 +107,7 @@ fn process_result(method: RpcMethod, result: Result>) -> multi_call_results .results .into_iter() - .map(|(service, result)| { - (from_rpc_service(service), result.map_err(from_rpc_error)) - }) + .map(|(service, result)| (service, result)) .collect(), ) } @@ -134,7 +116,7 @@ fn process_result(method: RpcMethod, result: Result>) -> } pub struct CandidRpcClient { - client: CkEthRpcClient, + client: EthRpcClient, } impl CandidRpcClient { @@ -143,7 +125,7 @@ impl CandidRpcClient { config: Option, ) -> RpcResult { Ok(Self { - client: get_rpc_client(source, config.unwrap_or_default())?, + client: EthRpcClient::new(source, config.unwrap_or_default())?, }) } @@ -260,6 +242,7 @@ fn get_transaction_hash(raw_signed_transaction_hex: &Hex) -> Option { mod test { use super::*; use crate::candid_rpc::cketh_conversion::into_rpc_service; + use crate::rpc_client::MultiCallError; use evm_rpc_types::RpcError; #[test] diff --git a/src/lib.rs b/src/lib.rs index 06b533a7..e5392bf5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ pub mod http; pub mod memory; pub mod metrics; pub mod providers; +mod rpc_client; pub mod types; pub mod util; pub mod validate; diff --git a/src/rpc_client/mod.rs b/src/rpc_client/mod.rs new file mode 100644 index 00000000..ab768602 --- /dev/null +++ b/src/rpc_client/mod.rs @@ -0,0 +1,72 @@ +use async_trait::async_trait; +use candid::CandidType; +use evm_rpc_types::{ProviderError, RpcApi, RpcConfig, RpcError, RpcService, RpcServices}; +use ic_cdk::api::management_canister::http_request::{CanisterHttpRequestArgument, HttpResponse}; +use serde_json::to_vec; +use std::collections::BTreeMap; +use std::fmt::Debug; +use std::marker::PhantomData; + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +pub trait RpcTransport: Debug { + fn resolve_api(provider: &RpcService) -> Result; + + async fn http_request( + provider: &RpcService, + method: &str, + request: CanisterHttpRequestArgument, + effective_size_estimate: u64, + ) -> Result; +} + +// Placeholder during refactoring +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DefaultTransport; + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl RpcTransport for cketh_common::eth_rpc_client::DefaultTransport { + fn resolve_api(_provider: &RpcService) -> Result { + unimplemented!() + } + + async fn http_request( + _provider: &RpcService, + _method: &str, + _request: CanisterHttpRequestArgument, + _effective_size_estimate: u64, + ) -> Result { + unimplemented!() + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct EthereumNetwork(#[n(0)] u64); + +/// Aggregates responses of different providers to the same query. +/// Guaranteed to be non-empty. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MultiCallResults { + pub results: BTreeMap>, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum MultiCallError { + ConsistentError(RpcError), + InconsistentResults(MultiCallResults), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct EthRpcClient { + chain: EthereumNetwork, + providers: Option>, + config: RpcConfig, + phantom: PhantomData, +} + +impl EthRpcClient { + pub fn new(source: RpcServices, config: RpcConfig) -> Result { + todo!() + } +} From a0ab71337979c8841d776aea5eb89a50dcd41ba0 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 11:30:09 +0200 Subject: [PATCH 02/42] 243: copied eth_rpc_client --- Cargo.lock | 1 + Cargo.toml | 2 + src/rpc_client/mod.rs | 476 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 463 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 878a130a..7be20b42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1591,6 +1591,7 @@ dependencies = [ "candid", "ethers-core", "evm_rpc_types", + "futures", "getrandom 0.2.15", "hex", "ic-base-types 0.8.0", diff --git a/Cargo.toml b/Cargo.toml index 9d88c4f2..4f9e6cd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ inherits = "release" [dependencies] candid = { workspace = true } evm_rpc_types = { path = "evm_rpc_types" } +futures = { workspace = true } getrandom = { workspace = true } ic-canisters-http-types = { workspace = true } ic-nervous-system-common = { workspace = true } @@ -51,6 +52,7 @@ assert_matches = "1.5" [workspace.dependencies] candid = { version = "0.9" } +futures = "0.3.30" getrandom = { version = "0.2", features = ["custom"] } hex = "0.4.3" ic-canisters-http-types = { git = "https://github.com/dfinity/ic", rev = "release-2023-09-27_23-01" } diff --git a/src/rpc_client/mod.rs b/src/rpc_client/mod.rs index ab768602..e6d397ad 100644 --- a/src/rpc_client/mod.rs +++ b/src/rpc_client/mod.rs @@ -1,12 +1,37 @@ +use crate::eth_rpc::{ + self, are_errors_consistent, Block, BlockSpec, FeeHistory, FeeHistoryParams, GetLogsParam, + Hash, HttpOutcallError, HttpResponsePayload, JsonRpcError, LogEntry, ProviderError, + ResponseSizeEstimate, RpcError, SendRawTransactionResult, +}; +use crate::eth_rpc_client::eth_rpc::HEADER_SIZE_LIMIT; +use crate::eth_rpc_client::providers::{ + RpcService, MAINNET_PROVIDERS, SEPOLIA_PROVIDERS, UNKNOWN_PROVIDERS, + ARBITRUM_PROVIDERS, BASE_PROVIDERS, OPTIMISM_PROVIDERS, +}; +use crate::eth_rpc_client::requests::GetTransactionCountParams; +use crate::eth_rpc_client::responses::TransactionReceipt; +use crate::lifecycle::EthereumNetwork; +use crate::logs::{DEBUG, INFO}; +use crate::numeric::TransactionCount; +use crate::state::State; use async_trait::async_trait; use candid::CandidType; -use evm_rpc_types::{ProviderError, RpcApi, RpcConfig, RpcError, RpcService, RpcServices}; +use ic_canister_log::log; use ic_cdk::api::management_canister::http_request::{CanisterHttpRequestArgument, HttpResponse}; -use serde_json::to_vec; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::collections::BTreeMap; use std::fmt::Debug; use std::marker::PhantomData; +use self::providers::RpcApi; + +pub mod providers; +pub mod requests; +pub mod responses; + +#[cfg(test)] +mod tests; + #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait RpcTransport: Debug { @@ -26,7 +51,7 @@ pub struct DefaultTransport; #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] -impl RpcTransport for cketh_common::eth_rpc_client::DefaultTransport { +impl RpcTransport for DefaultTransport { fn resolve_api(_provider: &RpcService) -> Result { unimplemented!() } @@ -41,32 +66,451 @@ impl RpcTransport for cketh_common::eth_rpc_client::DefaultTransport { } } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct EthereumNetwork(#[n(0)] u64); +#[derive(Clone, Debug, PartialEq, Eq, Default, CandidType, Deserialize)] +pub struct RpcConfig { + #[serde(rename = "responseSizeEstimate")] + pub response_size_estimate: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct EthRpcClient { + chain: EthereumNetwork, + providers: Option>, + config: RpcConfig, + phantom: PhantomData, +} + +impl EthRpcClient { + pub const fn new( + chain: EthereumNetwork, + providers: Option>, + config: RpcConfig, + ) -> Self { + Self { + chain, + providers, + config, + phantom: PhantomData, + } + } + + pub fn from_state(state: &State) -> Self { + Self::new(state.ethereum_network(), None, RpcConfig::default()) + } + + fn providers(&self) -> &[RpcService] { + match self.providers { + Some(ref providers) => providers, + None => match self.chain { + EthereumNetwork::MAINNET => MAINNET_PROVIDERS, + EthereumNetwork::SEPOLIA => SEPOLIA_PROVIDERS, + EthereumNetwork::ARBITRUM => ARBITRUM_PROVIDERS, + EthereumNetwork::BASE => BASE_PROVIDERS, + EthereumNetwork::OPTIMISM => OPTIMISM_PROVIDERS, + _ => UNKNOWN_PROVIDERS, + }, + } + } + + fn response_size_estimate(&self, estimate: u64) -> ResponseSizeEstimate { + ResponseSizeEstimate::new(self.config.response_size_estimate.unwrap_or(estimate)) + } + + /// Query all providers in sequence until one returns an ok result + /// (which could still be a JsonRpcResult::Error). + /// If none of the providers return an ok result, return the last error. + /// This method is useful in case a provider is temporarily down but should only be for + /// querying data that is **not** critical since the returned value comes from a single provider. + async fn sequential_call_until_ok( + &self, + method: impl Into + Clone, + params: I, + response_size_estimate: ResponseSizeEstimate, + ) -> Result + where + I: Serialize + Clone, + O: DeserializeOwned + HttpResponsePayload + Debug, + { + let mut last_result: Option> = None; + for provider in self.providers() { + log!( + DEBUG, + "[sequential_call_until_ok]: calling provider: {:?}", + provider + ); + let result = eth_rpc::call::( + provider, + method.clone(), + params.clone(), + response_size_estimate, + ) + .await; + match result { + Ok(value) => return Ok(value), + Err(RpcError::JsonRpcError(json_rpc_error @ JsonRpcError { .. })) => { + log!( + INFO, + "{provider:?} returned JSON-RPC error {json_rpc_error:?}", + ); + last_result = Some(Err(json_rpc_error.into())); + } + Err(e) => { + log!(INFO, "Querying {provider:?} returned error {e:?}"); + last_result = Some(Err(e)); + } + }; + } + last_result.unwrap_or_else(|| panic!("BUG: No providers in RPC client {:?}", self)) + } + + /// Query all providers in parallel and return all results. + /// It's up to the caller to decide how to handle the results, which could be inconsistent among one another, + /// (e.g., if different providers gave different responses). + /// This method is useful for querying data that is critical for the system to ensure that there is no single point of failure, + /// e.g., ethereum logs upon which ckETH will be minted. + async fn parallel_call( + &self, + method: impl Into + Clone, + params: I, + response_size_estimate: ResponseSizeEstimate, + ) -> MultiCallResults + where + I: Serialize + Clone, + O: DeserializeOwned + HttpResponsePayload, + { + let providers = self.providers(); + let results = { + let mut fut = Vec::with_capacity(providers.len()); + for provider in providers { + log!(DEBUG, "[parallel_call]: will call provider: {:?}", provider); + fut.push(async { + eth_rpc::call::( + provider, + method.clone(), + params.clone(), + response_size_estimate, + ) + .await + }); + } + futures::future::join_all(fut).await + }; + MultiCallResults::from_non_empty_iter(providers.iter().cloned().zip(results.into_iter())) + } + + pub async fn eth_get_logs( + &self, + params: GetLogsParam, + ) -> Result, MultiCallError>> { + let results: MultiCallResults> = self + .parallel_call( + "eth_getLogs", + vec![params], + self.response_size_estimate(1024 + HEADER_SIZE_LIMIT), + ) + .await; + results.reduce_with_equality() + } + + pub async fn eth_get_block_by_number( + &self, + block: BlockSpec, + ) -> Result> { + use crate::eth_rpc::GetBlockByNumberParams; + + let expected_block_size = match self.chain { + EthereumNetwork::SEPOLIA => 12 * 1024, + EthereumNetwork::MAINNET => 24 * 1024, + _ => 24 * 1024, // Default for unknown networks + }; + + let results: MultiCallResults = self + .parallel_call( + "eth_getBlockByNumber", + GetBlockByNumberParams { + block, + include_full_transactions: false, + }, + self.response_size_estimate(expected_block_size + HEADER_SIZE_LIMIT), + ) + .await; + results.reduce_with_equality() + } + + pub async fn eth_get_transaction_receipt( + &self, + tx_hash: Hash, + ) -> Result, MultiCallError>> { + let results: MultiCallResults> = self + .parallel_call( + "eth_getTransactionReceipt", + vec![tx_hash], + self.response_size_estimate(700 + HEADER_SIZE_LIMIT), + ) + .await; + results.reduce_with_equality() + } + + pub async fn eth_fee_history( + &self, + params: FeeHistoryParams, + ) -> Result> { + // A typical response is slightly above 300 bytes. + let results: MultiCallResults = self + .parallel_call( + "eth_feeHistory", + params, + self.response_size_estimate(512 + HEADER_SIZE_LIMIT), + ) + .await; + results.reduce_with_strict_majority_by_key(|fee_history| fee_history.oldest_block) + } + + pub async fn eth_send_raw_transaction( + &self, + raw_signed_transaction_hex: String, + ) -> Result { + // A successful reply is under 256 bytes, but we expect most calls to end with an error + // since we submit the same transaction from multiple nodes. + self.sequential_call_until_ok( + "eth_sendRawTransaction", + vec![raw_signed_transaction_hex], + self.response_size_estimate(256 + HEADER_SIZE_LIMIT), + ) + .await + } + + pub async fn multi_eth_send_raw_transaction( + &self, + raw_signed_transaction_hex: String, + ) -> Result> { + self.parallel_call( + "eth_sendRawTransaction", + vec![raw_signed_transaction_hex], + self.response_size_estimate(256 + HEADER_SIZE_LIMIT), + ) + .await + .reduce_with_equality() + } + + pub async fn eth_get_transaction_count( + &self, + params: GetTransactionCountParams, + ) -> MultiCallResults { + self.parallel_call( + "eth_getTransactionCount", + params, + self.response_size_estimate(50 + HEADER_SIZE_LIMIT), + ) + .await + } +} /// Aggregates responses of different providers to the same query. /// Guaranteed to be non-empty. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, CandidType)] pub struct MultiCallResults { pub results: BTreeMap>, } -#[derive(Debug, PartialEq, Eq)] +impl MultiCallResults { + fn from_non_empty_iter)>>( + iter: I, + ) -> Self { + let results = BTreeMap::from_iter(iter); + if results.is_empty() { + panic!("BUG: MultiCallResults cannot be empty!") + } + Self { results } + } +} + +impl MultiCallResults { + /// Expects all results to be ok or return the following error: + /// * MultiCallError::ConsistentJsonRpcError: all errors are the same JSON-RPC error. + /// * MultiCallError::ConsistentHttpOutcallError: all errors are the same HTTP outcall error. + /// * MultiCallError::InconsistentResults if there are different errors. + fn all_ok(self) -> Result, MultiCallError> { + let mut has_ok = false; + let mut first_error: Option<(RpcService, &Result)> = None; + for (provider, result) in self.results.iter() { + match result { + Ok(_value) => { + has_ok = true; + } + _ => match first_error { + None => { + first_error = Some((provider.clone(), result)); + } + Some((first_error_provider, error)) => { + if !are_errors_consistent(&error, result) { + return Err(MultiCallError::InconsistentResults(self)); + } + first_error = Some((first_error_provider, error)); + } + }, + } + } + match first_error { + None => Ok(self + .results + .into_iter() + .map(|(provider, result)| { + (provider, result.expect("BUG: all results should be ok")) + }) + .collect()), + Some((_, Err(error))) => { + if has_ok { + Err(MultiCallError::InconsistentResults(self)) + } else { + Err(MultiCallError::ConsistentError(error.clone())) + } + } + Some((_, Ok(_))) => { + panic!("BUG: first_error should be an error type") + } + } + } +} + +#[derive(Debug, PartialEq, Eq, CandidType)] +pub enum SingleCallError { + HttpOutcallError(HttpOutcallError), + JsonRpcError { code: i64, message: String }, +} +#[derive(Debug, PartialEq, Eq, CandidType)] pub enum MultiCallError { ConsistentError(RpcError), InconsistentResults(MultiCallResults), } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct EthRpcClient { - chain: EthereumNetwork, - providers: Option>, - config: RpcConfig, - phantom: PhantomData, +impl MultiCallError { + pub fn has_http_outcall_error_matching bool>( + &self, + predicate: P, + ) -> bool { + match self { + MultiCallError::ConsistentError(RpcError::HttpOutcallError(error)) => predicate(error), + MultiCallError::ConsistentError(_) => false, + MultiCallError::InconsistentResults(results) => { + results.results.values().any(|result| match result { + Err(RpcError::HttpOutcallError(error)) => predicate(error), + _ => false, + }) + } + } + } } -impl EthRpcClient { - pub fn new(source: RpcServices, config: RpcConfig) -> Result { - todo!() +impl MultiCallResults { + pub fn reduce_with_equality(self) -> Result> { + let mut results = self.all_ok()?.into_iter(); + let (base_node_provider, base_result) = results + .next() + .expect("BUG: MultiCallResults is guaranteed to be non-empty"); + let mut inconsistent_results: Vec<_> = results + .filter(|(_provider, result)| result != &base_result) + .collect(); + if !inconsistent_results.is_empty() { + inconsistent_results.push((base_node_provider, base_result)); + let error = MultiCallError::InconsistentResults(MultiCallResults::from_non_empty_iter( + inconsistent_results + .into_iter() + .map(|(provider, result)| (provider, Ok(result))), + )); + log!( + INFO, + "[reduce_with_equality]: inconsistent results {error:?}" + ); + return Err(error); + } + Ok(base_result) + } + + pub fn reduce_with_min_by_key K, K: Ord>( + self, + extractor: F, + ) -> Result> { + let min = self + .all_ok()? + .into_values() + .min_by_key(extractor) + .expect("BUG: MultiCallResults is guaranteed to be non-empty"); + Ok(min) + } + + pub fn reduce_with_strict_majority_by_key K, K: Ord>( + self, + extractor: F, + ) -> Result> { + let mut votes_by_key: BTreeMap> = BTreeMap::new(); + for (provider, result) in self.all_ok()?.into_iter() { + let key = extractor(&result); + match votes_by_key.remove(&key) { + Some(mut votes_for_same_key) => { + let (_other_provider, other_result) = votes_for_same_key + .last_key_value() + .expect("BUG: results_with_same_key is non-empty"); + if &result != other_result { + let error = MultiCallError::InconsistentResults( + MultiCallResults::from_non_empty_iter( + votes_for_same_key + .into_iter() + .chain(std::iter::once((provider, result))) + .map(|(provider, result)| (provider, Ok(result))), + ), + ); + log!( + INFO, + "[reduce_with_strict_majority_by_key]: inconsistent results {error:?}" + ); + return Err(error); + } + votes_for_same_key.insert(provider, result); + votes_by_key.insert(key, votes_for_same_key); + } + None => { + let _ = votes_by_key.insert(key, BTreeMap::from([(provider, result)])); + } + } + } + + let mut tally: Vec<(K, BTreeMap)> = Vec::from_iter(votes_by_key); + tally.sort_unstable_by(|(_left_key, left_ballot), (_right_key, right_ballot)| { + left_ballot.len().cmp(&right_ballot.len()) + }); + match tally.len() { + 0 => panic!("BUG: tally should be non-empty"), + 1 => Ok(tally + .pop() + .and_then(|(_key, mut ballot)| ballot.pop_last()) + .expect("BUG: tally is non-empty") + .1), + _ => { + let mut first = tally.pop().expect("BUG: tally has at least 2 elements"); + let second = tally.pop().expect("BUG: tally has at least 2 elements"); + if first.1.len() > second.1.len() { + Ok(first + .1 + .pop_last() + .expect("BUG: tally should be non-empty") + .1) + } else { + let error = + MultiCallError::InconsistentResults(MultiCallResults::from_non_empty_iter( + first + .1 + .into_iter() + .chain(second.1) + .map(|(provider, result)| (provider, Ok(result))), + )); + log!( + INFO, + "[reduce_with_strict_majority_by_key]: no strict majority {error:?}" + ); + Err(error) + } + } + } } } From ab382a047948ac20c4281912baa863ec3f72ba14 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 11:32:04 +0200 Subject: [PATCH 03/42] 243: copied providers --- src/rpc_client/providers.rs | 41 +++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/rpc_client/providers.rs diff --git a/src/rpc_client/providers.rs b/src/rpc_client/providers.rs new file mode 100644 index 00000000..7a05114c --- /dev/null +++ b/src/rpc_client/providers.rs @@ -0,0 +1,41 @@ +use evm_rpc_types::{EthMainnetService, EthSepoliaService, L2MainnetService, RpcService}; + +pub(crate) const MAINNET_PROVIDERS: &[RpcService] = &[ + RpcService::EthMainnet(EthMainnetService::Alchemy), + RpcService::EthMainnet(EthMainnetService::Ankr), + RpcService::EthMainnet(EthMainnetService::PublicNode), + RpcService::EthMainnet(EthMainnetService::Cloudflare), + RpcService::EthMainnet(EthMainnetService::Llama), +]; + +pub(crate) const SEPOLIA_PROVIDERS: &[RpcService] = &[ + RpcService::EthSepolia(EthSepoliaService::Alchemy), + RpcService::EthSepolia(EthSepoliaService::Ankr), + RpcService::EthSepolia(EthSepoliaService::BlockPi), + RpcService::EthSepolia(EthSepoliaService::PublicNode), + RpcService::EthSepolia(EthSepoliaService::Sepolia), +]; + +pub(crate) const ARBITRUM_PROVIDERS: &[RpcService] = &[ + RpcService::ArbitrumOne(L2MainnetService::Alchemy), + RpcService::ArbitrumOne(L2MainnetService::Ankr), + RpcService::ArbitrumOne(L2MainnetService::PublicNode), + RpcService::ArbitrumOne(L2MainnetService::Llama), +]; + +pub(crate) const BASE_PROVIDERS: &[RpcService] = &[ + RpcService::BaseMainnet(L2MainnetService::Alchemy), + RpcService::BaseMainnet(L2MainnetService::Ankr), + RpcService::BaseMainnet(L2MainnetService::PublicNode), + RpcService::BaseMainnet(L2MainnetService::Llama), +]; + +pub(crate) const OPTIMISM_PROVIDERS: &[RpcService] = &[ + RpcService::OptimismMainnet(L2MainnetService::Alchemy), + RpcService::OptimismMainnet(L2MainnetService::Ankr), + RpcService::OptimismMainnet(L2MainnetService::PublicNode), + RpcService::OptimismMainnet(L2MainnetService::Llama), +]; + +// Default RPC services for unknown EVM network +pub(crate) const UNKNOWN_PROVIDERS: &[RpcService] = &[]; From cca8be7a46f84f306aee7eaa0e8b6e53d3ed679f Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 11:37:41 +0200 Subject: [PATCH 04/42] 243: copied eth_rpc --- src/rpc_client/eth_rpc.rs | 1009 +++++++++++++++++++++++++++++++++++++ 1 file changed, 1009 insertions(+) create mode 100644 src/rpc_client/eth_rpc.rs diff --git a/src/rpc_client/eth_rpc.rs b/src/rpc_client/eth_rpc.rs new file mode 100644 index 00000000..a225de72 --- /dev/null +++ b/src/rpc_client/eth_rpc.rs @@ -0,0 +1,1009 @@ +//! 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::address::Address; +use crate::checked_amount::CheckedAmountOf; +use crate::endpoints::CandidBlockTag; +use crate::eth_rpc_client::providers::RpcService; +use crate::eth_rpc_client::responses::TransactionReceipt; +use crate::eth_rpc_client::RpcTransport; +use crate::eth_rpc_error::{sanitize_send_raw_transaction_result, Parser}; +use crate::logs::{DEBUG, TRACE_HTTP}; +use crate::numeric::{BlockNumber, LogIndex, TransactionCount, Wei, WeiPerGas}; +use crate::state::{mutate_state, State}; +use candid::{candid_method, CandidType}; +use ethnum; +use ic_canister_log::log; +use ic_cdk::api::call::RejectionCode; +use ic_cdk::api::management_canister::http_request::{ + CanisterHttpRequestArgument, HttpHeader, HttpMethod, HttpResponse, TransformArgs, + TransformContext, +}; +use ic_cdk_macros::query; +pub use metrics::encode as encode_metrics; +use minicbor::{Decode, Encode}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::fmt; +use std::fmt::{Debug, Display, Formatter, LowerHex, UpperHex}; +use thiserror::Error; + +#[cfg(test)] +mod tests; + +// This constant is our approximation of the expected header size. +// The HTTP standard doesn't define any limit, and many implementations limit +// the headers size to 8 KiB. We chose a lower limit because headers observed on most providers +// fit in the constant defined below, and if there is spike, then the payload size adjustment +// should take care of that. +pub const HEADER_SIZE_LIMIT: u64 = 2 * 1024; + +// This constant comes from the IC specification: +// > If provided, the value must not exceed 2MB +const HTTP_MAX_SIZE: u64 = 2_000_000; + +pub const MAX_PAYLOAD_SIZE: u64 = HTTP_MAX_SIZE - HEADER_SIZE_LIMIT; + +pub type Quantity = ethnum::u256; + +pub fn into_nat(quantity: Quantity) -> candid::Nat { + use num_bigint::BigUint; + candid::Nat::from(BigUint::from_bytes_be(&quantity.to_be_bytes())) +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(transparent)] +pub struct Data(#[serde(with = "crate::serde_data")] pub Vec); + +impl CandidType for Data { + fn _ty() -> candid::types::Type { + String::_ty() + } + + fn idl_serialize(&self, serializer: S) -> Result<(), S::Error> + where + S: candid::types::Serializer, + { + serializer.serialize_text(&format!("0x{}", hex::encode(self))) + } +} + +impl AsRef<[u8]> for Data { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +#[derive(Clone, Deserialize, Serialize, PartialEq, Eq, Hash)] +#[serde(transparent)] +pub struct FixedSizeData(#[serde(with = "crate::serde_data")] pub [u8; 32]); + +impl CandidType for FixedSizeData { + fn _ty() -> candid::types::Type { + String::_ty() + } + + fn idl_serialize(&self, serializer: S) -> Result<(), S::Error> + where + S: candid::types::Serializer, + { + serializer.serialize_text(&format!("{:#x}", self)) + } +} + +impl AsRef<[u8]> for FixedSizeData { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl std::str::FromStr for FixedSizeData { + type Err = String; + + fn from_str(s: &str) -> Result { + if !s.starts_with("0x") { + return Err("Ethereum hex string doesn't start with 0x".to_string()); + } + let mut bytes = [0u8; 32]; + hex::decode_to_slice(&s[2..], &mut bytes) + .map_err(|e| format!("failed to decode hash from hex: {}", e))?; + Ok(Self(bytes)) + } +} + +impl Debug for FixedSizeData { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:x}", self) + } +} + +impl Display for FixedSizeData { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:x}", self) + } +} + +impl LowerHex for FixedSizeData { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "0x{}", hex::encode(self.0)) + } +} + +impl UpperHex for FixedSizeData { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "0x{}", hex::encode_upper(self.0)) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, CandidType)] +pub enum SendRawTransactionResult { + Ok, + InsufficientFunds, + NonceTooLow, + NonceTooHigh, +} + +impl HttpResponsePayload for SendRawTransactionResult { + fn response_transform() -> Option { + Some(ResponseTransform::SendRawTransaction) + } +} + +#[derive( + Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, Ord, PartialOrd, Encode, Decode, +)] +#[serde(transparent)] +#[cbor(transparent)] +pub struct Hash( + #[serde(with = "crate::serde_data")] + #[cbor(n(0), with = "minicbor::bytes")] + pub [u8; 32], +); + +impl CandidType for Hash { + fn _ty() -> candid::types::Type { + String::_ty() + } + + fn idl_serialize(&self, serializer: S) -> Result<(), S::Error> + where + S: candid::types::Serializer, + { + serializer.serialize_text(&format!("{:#x}", self)) + } +} + +impl Debug for Hash { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:x}", self) + } +} + +impl Display for Hash { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:x}", self) + } +} + +impl LowerHex for Hash { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "0x{}", hex::encode(self.0)) + } +} + +impl UpperHex for Hash { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "0x{}", hex::encode_upper(self.0)) + } +} + +impl std::str::FromStr for Hash { + type Err = String; + + fn from_str(s: &str) -> Result { + if !s.starts_with("0x") { + return Err("Ethereum hash doesn't start with 0x".to_string()); + } + let mut bytes = [0u8; 32]; + hex::decode_to_slice(&s[2..], &mut bytes) + .map_err(|e| format!("failed to decode hash from hex: {}", e))?; + Ok(Self(bytes)) + } +} + +impl HttpResponsePayload for Hash {} + +/// Block tags. +/// See +#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, CandidType)] +pub enum BlockTag { + /// The latest mined block. + #[default] + #[serde(rename = "latest")] + Latest, + /// The latest safe head block. + /// See + /// + #[serde(rename = "safe")] + Safe, + /// The latest finalized block. + /// See + /// + #[serde(rename = "finalized")] + Finalized, + #[serde(rename = "earliest")] + Earliest, + #[serde(rename = "pending")] + Pending, +} + +impl From for BlockTag { + fn from(block_tag: CandidBlockTag) -> BlockTag { + match block_tag { + CandidBlockTag::Latest => BlockTag::Latest, + CandidBlockTag::Safe => BlockTag::Safe, + CandidBlockTag::Finalized => BlockTag::Finalized, + } + } +} + +impl Display for BlockTag { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Latest => write!(f, "latest"), + Self::Safe => write!(f, "safe"), + Self::Finalized => write!(f, "finalized"), + Self::Earliest => write!(f, "earliest"), + Self::Pending => write!(f, "pending"), + } + } +} + +/// The block specification indicating which block to query. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, CandidType)] +#[serde(untagged)] +pub enum BlockSpec { + /// Query the block with the specified index. + Number(BlockNumber), + /// Query the block with the specified tag. + Tag(BlockTag), +} + +impl Default for BlockSpec { + fn default() -> Self { + Self::Tag(BlockTag::default()) + } +} + +impl std::str::FromStr for BlockSpec { + type Err = String; + + fn from_str(s: &str) -> Result { + if s.starts_with("0x") { + let block_number = BlockNumber::from_str_hex(s) + .map_err(|e| format!("failed to parse block number '{s}': {e}"))?; + return Ok(BlockSpec::Number(block_number)); + } + Ok(BlockSpec::Tag(match s { + "latest" => BlockTag::Latest, + "safe" => BlockTag::Safe, + "finalized" => BlockTag::Finalized, + _ => return Err(format!("unknown block tag '{s}'")), + })) + } +} + +/// Parameters of the [`eth_getLogs`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getlogs) call. +#[derive(Debug, Clone, Serialize, Deserialize, CandidType)] +pub struct GetLogsParam { + /// Integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. + #[serde(rename = "fromBlock")] + pub from_block: BlockSpec, + /// Integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. + #[serde(rename = "toBlock")] + pub to_block: BlockSpec, + /// Contract address or a list of addresses from which logs should originate. + pub address: Vec
, + /// Array of 32 Bytes DATA topics. + /// Topics are order-dependent. + /// Each topic can also be an array of DATA with "or" options. + #[serde(skip_serializing_if = "Vec::is_empty")] + pub topics: Vec>, +} + +/// An entry of the [`eth_getLogs`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getlogs) call reply. +// Example: +// ```json +// { +// "address": "0x7e41257f7b5c3dd3313ef02b1f4c864fe95bec2b", +// "topics": [ +// "0x2a2607d40f4a6feb97c36e0efd57e0aa3e42e0332af4fceb78f21b7dffcbd657" +// ], +// "data": "0x00000000000000000000000055654e7405fcb336386ea8f36954a211b2cda764000000000000000000000000000000000000000000000000002386f26fc100000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000003f62327071372d71677a7a692d74623564622d72357363692d637736736c2d6e646f756c2d666f7435742d347a7732702d657a6677692d74616a32792d76716500", +// "blockNumber": "0x3aa4f4", +// "transactionHash": "0x5618f72c485bd98a3df58d900eabe9e24bfaa972a6fe5227e02233fad2db1154", +// "transactionIndex": "0x6", +// "blockHash": "0x908e6b84d26d71421bfaa08e7966e0afcef3883a28a53a0a7a31104caf1e94c2", +// "logIndex": "0x8", +// "removed": false +// } +// ``` +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, CandidType)] +pub struct LogEntry { + /// The address from which this log originated. + pub address: Address, + /// Array of 0 to 4 32 Bytes DATA of indexed log arguments. + /// In solidity: The first topic is the event signature hash (e.g. Deposit(address,bytes32,uint256)), + /// unless you declared the event with the anonymous specifier. + pub topics: Vec, + /// Contains one or more 32-byte non-indexed log arguments. + pub data: Data, + /// The block number in which this log appeared. + /// None if the block is pending. + #[serde(rename = "blockNumber")] + pub block_number: Option, + // 32 Bytes - hash of the transactions from which this log was created. + // None when its pending log. + #[serde(rename = "transactionHash")] + pub transaction_hash: Option, + // Integer of the transactions position within the block the log was created from. + // None if the log is pending. + #[serde(rename = "transactionIndex")] + pub transaction_index: Option>, + /// 32 Bytes - hash of the block in which this log appeared. + /// None if the block is pending. + #[serde(rename = "blockHash")] + pub block_hash: Option, + /// Integer of the log index position in the block. + /// None if the log is pending. + #[serde(rename = "logIndex")] + pub log_index: Option, + /// "true" when the log was removed due to a chain reorganization. + /// "false" if it's a valid log. + #[serde(default)] + pub removed: bool, +} + +impl HttpResponsePayload for Vec { + fn response_transform() -> Option { + Some(ResponseTransform::LogEntries) + } +} + +/// Parameters of the [`eth_getBlockByNumber`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbynumber) call. +#[derive(Debug, Serialize, Clone)] +#[serde(into = "(BlockSpec, bool)")] +pub struct GetBlockByNumberParams { + /// Integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. + pub block: BlockSpec, + /// If true, returns the full transaction objects. If false, returns only the hashes of the transactions. + pub include_full_transactions: bool, +} + +impl From for (BlockSpec, bool) { + fn from(value: GetBlockByNumberParams) -> Self { + (value.block, value.include_full_transactions) + } +} + +/// Parameters of the [`eth_feeHistory`](https://ethereum.github.io/execution-apis/api-documentation/) call. +#[derive(Debug, Serialize, Clone)] +#[serde(into = "(Quantity, BlockSpec, Vec)")] +pub struct FeeHistoryParams { + /// Number of blocks in the requested range. + /// Typically providers request this to be between 1 and 1024. + pub block_count: Quantity, + /// Highest block of the requested range. + /// Integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. + pub highest_block: BlockSpec, + /// A monotonically increasing list of percentile values between 0 and 100. + /// For each block in the requested range, the transactions will be sorted in ascending order + /// by effective tip per gas and the corresponding effective tip for the percentile + /// will be determined, accounting for gas consumed. + pub reward_percentiles: Vec, +} + +impl From for (Quantity, BlockSpec, Vec) { + fn from(value: FeeHistoryParams) -> Self { + ( + value.block_count, + value.highest_block, + value.reward_percentiles, + ) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CandidType)] +pub struct FeeHistory { + /// Lowest number block of the returned range. + #[serde(rename = "oldestBlock")] + pub oldest_block: BlockNumber, + /// An array of block base fees per gas. + /// This includes the next block after the newest of the returned range, + /// because this value can be derived from the newest block. + /// Zeroes are returned for pre-EIP-1559 blocks. + #[serde(rename = "baseFeePerGas")] + pub base_fee_per_gas: Vec, + /// An array of block gas used ratios (gasUsed / gasLimit). + #[serde(default)] + #[serde(rename = "gasUsedRatio")] + pub gas_used_ratio: Vec, + /// A two-dimensional array of effective priority fees per gas at the requested block percentiles. + #[serde(default)] + #[serde(rename = "reward")] + pub reward: Vec>, +} + +impl HttpResponsePayload for FeeHistory { + fn response_transform() -> Option { + Some(ResponseTransform::FeeHistory) + } +} + +impl HttpResponsePayload for Wei {} + +impl From for BlockSpec { + fn from(value: BlockNumber) -> Self { + BlockSpec::Number(value) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, CandidType)] +pub struct Block { + #[serde(rename = "baseFeePerGas")] + pub base_fee_per_gas: Option, + pub number: BlockNumber, + pub difficulty: Option>, + #[serde(rename = "extraData")] + pub extra_data: String, + #[serde(rename = "gasLimit")] + pub gas_limit: CheckedAmountOf<()>, + #[serde(rename = "gasUsed")] + pub gas_used: CheckedAmountOf<()>, + pub hash: String, + #[serde(rename = "logsBloom")] + pub logs_bloom: String, + pub miner: String, + #[serde(rename = "mixHash")] + pub mix_hash: String, + pub nonce: CheckedAmountOf<()>, + #[serde(rename = "parentHash")] + pub parent_hash: String, + #[serde(rename = "receiptsRoot")] + pub receipts_root: String, + #[serde(rename = "sha3Uncles")] + pub sha3_uncles: String, + pub size: CheckedAmountOf<()>, + #[serde(rename = "stateRoot")] + pub state_root: String, + #[serde(rename = "timestamp")] + pub timestamp: CheckedAmountOf<()>, + #[serde(rename = "totalDifficulty")] + pub total_difficulty: Option>, + #[serde(default)] + pub transactions: Vec, + #[serde(rename = "transactionsRoot")] + pub transactions_root: Option, + #[serde(default)] + pub uncles: Vec, +} + +impl HttpResponsePayload for Block { + fn response_transform() -> Option { + Some(ResponseTransform::Block) + } +} + +/// An envelope for all JSON-RPC requests. +#[derive(Clone, Serialize, Deserialize)] +pub struct JsonRpcRequest { + jsonrpc: String, + method: String, + id: u64, + pub params: T, +} + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct JsonRpcReply { + pub id: u64, + pub jsonrpc: String, + #[serde(flatten)] + pub result: JsonRpcResult, +} + +/// An envelope for all JSON-RPC replies. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub enum JsonRpcResult { + #[serde(rename = "result")] + Result(T), + #[serde(rename = "error")] + Error { code: i64, message: String }, +} + +impl JsonRpcResult { + pub fn unwrap(self) -> T { + match self { + Self::Result(t) => t, + Self::Error { code, message } => panic!( + "expected JSON RPC call to succeed, got an error: error_code = {code}, message = {message}" + ), + } + } +} + +impl From> for Result { + fn from(result: JsonRpcResult) -> Self { + match result { + JsonRpcResult::Result(r) => Ok(r), + JsonRpcResult::Error { code, message } => Err(JsonRpcError { code, message }.into()), + } + } +} + +/// Describes a payload transformation to execute before passing the HTTP response to consensus. +/// The purpose of these transformations is to ensure that the response encoding is deterministic +/// (the field order is the same). +#[derive(Encode, Decode, Debug)] +pub enum ResponseTransform { + #[n(0)] + Block, + #[n(1)] + LogEntries, + #[n(2)] + TransactionReceipt, + #[n(3)] + FeeHistory, + #[n(4)] + SendRawTransaction, +} + +impl ResponseTransform { + fn apply(&self, body_bytes: &mut Vec) { + fn redact_response(body: &mut Vec) + where + T: Serialize + DeserializeOwned, + { + let response: JsonRpcReply = match serde_json::from_slice(body) { + Ok(response) => response, + Err(_) => return, + }; + *body = serde_json::to_string(&response) + .expect("BUG: failed to serialize response") + .into_bytes(); + } + + fn redact_collection_response(body: &mut Vec) + where + T: Serialize + DeserializeOwned, + { + let mut response: JsonRpcReply> = match serde_json::from_slice(body) { + Ok(response) => response, + Err(_) => return, + }; + + if let JsonRpcResult::Result(ref mut result) = response.result { + sort_by_hash(result); + } + + *body = serde_json::to_string(&response) + .expect("BUG: failed to serialize response") + .into_bytes(); + } + + match self { + Self::Block => redact_response::(body_bytes), + Self::LogEntries => redact_collection_response::(body_bytes), + Self::TransactionReceipt => redact_response::(body_bytes), + Self::FeeHistory => redact_response::(body_bytes), + Self::SendRawTransaction => { + sanitize_send_raw_transaction_result(body_bytes, Parser::new()) + } + } + } +} + +#[query] +#[candid_method(query)] +fn cleanup_response(mut args: TransformArgs) -> HttpResponse { + args.response.headers.clear(); + ic_cdk::println!( + "RAW RESPONSE BEFORE TRANSFORM:\nstatus: {:?}\nbody:{:?}", + args.response.status, + String::from_utf8_lossy(&args.response.body).to_string() + ); + let status_ok = args.response.status >= 200u16 && args.response.status < 300u16; + if status_ok && !args.context.is_empty() { + let maybe_transform: Result = minicbor::decode(&args.context[..]); + if let Ok(transform) = maybe_transform { + transform.apply(&mut args.response.body); + } + } + ic_cdk::println!( + "RAW RESPONSE AFTER TRANSFORM:\nstatus: {:?}\nbody:{:?}", + args.response.status, + String::from_utf8_lossy(&args.response.body).to_string() + ); + args.response +} + +#[derive(Clone, Debug, PartialEq, Eq, CandidType, Deserialize)] +pub enum RpcError { + // #[error("RPC provider error")] + ProviderError(/* #[source] */ ProviderError), + // #[error("HTTPS outcall error")] + HttpOutcallError(/* #[source] */ HttpOutcallError), + // #[error("JSON-RPC error")] + JsonRpcError(/* #[source] */ JsonRpcError), + // #[error("data format error")] + ValidationError(/* #[source] */ ValidationError), +} + +impl From for RpcError { + fn from(err: ProviderError) -> Self { + RpcError::ProviderError(err) + } +} + +impl From for RpcError { + fn from(err: HttpOutcallError) -> Self { + RpcError::HttpOutcallError(err) + } +} + +impl From for RpcError { + fn from(err: JsonRpcError) -> Self { + RpcError::JsonRpcError(err) + } +} + +impl From for RpcError { + fn from(err: ValidationError) -> Self { + RpcError::ValidationError(err) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, CandidType, Deserialize)] +pub enum ProviderError { + // #[error("no permission")] + NoPermission, + // #[error("too few cycles (expected {expected}, received {received})")] + TooFewCycles { expected: u128, received: u128 }, + // #[error("provider not found")] + ProviderNotFound, + // #[error("missing required provider")] + MissingRequiredProvider, +} + +#[derive(Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, CandidType, Deserialize)] +pub enum HttpOutcallError { + /// Error from the IC system API. + // #[error("IC system error code {}: {message}", *.code as i32)] + IcError { + code: RejectionCode, + message: String, + }, + /// Response is not a valid JSON-RPC response, + /// which means that the response was not successful (status other than 2xx) + /// or that the response body could not be deserialized into a JSON-RPC response. + // #[error("invalid JSON-RPC response {status}: {})", .parsing_error.as_deref().unwrap_or(.body))] + InvalidHttpJsonRpcResponse { + status: u16, + body: String, + #[serde(rename = "parsingError")] + parsing_error: Option, + }, +} + +impl HttpOutcallError { + pub fn is_response_too_large(&self) -> bool { + match self { + Self::IcError { code, message } => is_response_too_large(code, message), + _ => false, + } + } +} + +pub fn is_response_too_large(code: &RejectionCode, message: &str) -> bool { + code == &RejectionCode::SysFatal && message.contains("size limit") +} + +pub type HttpOutcallResult = Result; + +#[derive(Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, CandidType, Deserialize)] +pub enum ValidationError { + // #[error("{0}")] + Custom(String), + // #[error("invalid hex data: {0}")] + InvalidHex(String), +} + +pub fn are_errors_consistent( + left: &Result, + right: &Result, +) -> bool { + match (left, right) { + (Ok(_), _) | (_, Ok(_)) => true, + _ => left == right, + } +} + +#[derive( + Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, CandidType, Serialize, Deserialize, Error, +)] +#[error("code {code}: {message}")] +pub struct JsonRpcError { + pub code: i64, + pub message: String, +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct ResponseSizeEstimate(u64); + +impl ResponseSizeEstimate { + pub fn new(num_bytes: u64) -> Self { + assert!(num_bytes > 0); + assert!(num_bytes <= MAX_PAYLOAD_SIZE); + Self(num_bytes) + } + + /// Describes the expected (90th percentile) number of bytes in the HTTP response body. + /// This number should be less than `MAX_PAYLOAD_SIZE`. + pub fn get(self) -> u64 { + self.0 + } + + /// Returns a higher estimate for the payload size. + pub fn adjust(self) -> Self { + Self(self.0.max(1024).saturating_mul(2).min(MAX_PAYLOAD_SIZE)) + } +} + +impl fmt::Display for ResponseSizeEstimate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +pub trait HttpResponsePayload { + fn response_transform() -> Option { + None + } +} + +impl HttpResponsePayload for Option {} + +impl HttpResponsePayload for TransactionCount {} + +/// Calls a JSON-RPC method on an Ethereum node at the specified URL. +pub async fn call( + provider: &RpcService, + method: impl Into, + params: I, + mut response_size_estimate: ResponseSizeEstimate, +) -> Result +where + T: RpcTransport, + I: Serialize, + O: DeserializeOwned + HttpResponsePayload, +{ + let eth_method = method.into(); + let mut rpc_request = JsonRpcRequest { + jsonrpc: "2.0".to_string(), + params, + method: eth_method.clone(), + id: 1, + }; + let api = T::resolve_api(provider)?; + let url = &api.url; + let mut headers = vec![HttpHeader { + name: "Content-Type".to_string(), + value: "application/json".to_string(), + }]; + if let Some(vec) = api.headers { + headers.extend(vec); + } + let mut retries = 0; + loop { + rpc_request.id = mutate_state(State::next_request_id); + let payload = serde_json::to_string(&rpc_request).unwrap(); + log!(TRACE_HTTP, "Calling url: {}, with payload: {payload}", url); + + let effective_size_estimate = response_size_estimate.get(); + let transform_op = O::response_transform() + .as_ref() + .map(|t| { + let mut buf = vec![]; + minicbor::encode(t, &mut buf).unwrap(); + buf + }) + .unwrap_or_default(); + + let request = CanisterHttpRequestArgument { + url: url.clone(), + max_response_bytes: Some(effective_size_estimate), + method: HttpMethod::POST, + headers: headers.clone(), + body: Some(payload.as_bytes().to_vec()), + transform: Some(TransformContext::from_name( + "cleanup_response".to_owned(), + transform_op, + )), + }; + + let response = match T::http_request( + provider, + ð_method, + request, + effective_size_estimate, + ) + .await + { + Err(RpcError::HttpOutcallError(HttpOutcallError::IcError { code, message })) + if is_response_too_large(&code, &message) => + { + let new_estimate = response_size_estimate.adjust(); + if response_size_estimate == new_estimate { + return Err(HttpOutcallError::IcError { code, message }.into()); + } + log!(DEBUG, "The {eth_method} response didn't fit into {response_size_estimate} bytes, retrying with {new_estimate}"); + response_size_estimate = new_estimate; + retries += 1; + continue; + } + result => result?, + }; + + log!( + TRACE_HTTP, + "Got response (with {} bytes): {} from url: {} with status: {}", + response.body.len(), + String::from_utf8_lossy(&response.body), + url, + response.status + ); + + metrics::observe_retry_count(eth_method.clone(), retries); + + // JSON-RPC responses over HTTP should have a 2xx status code, + // even if the contained JsonRpcResult is an error. + // If the server is not available, it will sometimes (wrongly) return HTML that will fail parsing as JSON. + let http_status_code = http_status_code(&response); + if !is_successful_http_code(&http_status_code) { + return Err(HttpOutcallError::InvalidHttpJsonRpcResponse { + status: http_status_code, + body: String::from_utf8_lossy(&response.body).to_string(), + parsing_error: None, + } + .into()); + } + + let reply: JsonRpcReply = serde_json::from_slice(&response.body).map_err(|e| { + HttpOutcallError::InvalidHttpJsonRpcResponse { + status: http_status_code, + body: String::from_utf8_lossy(&response.body).to_string(), + parsing_error: Some(e.to_string()), + } + })?; + + return reply.result.into(); + } +} + +fn http_status_code(response: &HttpResponse) -> u16 { + use num_traits::cast::ToPrimitive; + // HTTP status code are always 3 decimal digits, hence at most 999. + // See https://httpwg.org/specs/rfc9110.html#status.code.extensibility + response.status.0.to_u16().expect("valid HTTP status code") +} + +fn is_successful_http_code(status: &u16) -> bool { + const OK: u16 = 200; + const REDIRECTION: u16 = 300; + (OK..REDIRECTION).contains(status) +} + +fn sort_by_hash(to_sort: &mut [T]) { + use ic_crypto_sha3::Keccak256; + to_sort.sort_by(|a, b| { + let a_hash = Keccak256::hash(serde_json::to_vec(a).expect("BUG: failed to serialize")); + let b_hash = Keccak256::hash(serde_json::to_vec(b).expect("BUG: failed to serialize")); + a_hash.cmp(&b_hash) + }); +} + +pub(super) mod metrics { + use ic_metrics_encoder::MetricsEncoder; + use std::cell::RefCell; + use std::collections::BTreeMap; + + /// The max number of RPC call retries we expect to see (plus one). + const MAX_EXPECTED_RETRIES: usize = 20; + + #[derive(Default)] + struct RetryHistogram { + /// The histogram of HTTP call retry counts. + /// The last bucket corresponds to the "infinite" value that exceeds the maximum number we + /// expect to see in practice. + retry_buckets: [u64; MAX_EXPECTED_RETRIES + 1], + retry_count: u64, + } + + impl RetryHistogram { + fn observe_retry_count(&mut self, count: usize) { + self.retry_buckets[count.min(MAX_EXPECTED_RETRIES)] += 1; + self.retry_count += count as u64; + } + + /// Returns a iterator over the histrogram buckets in the format that ic-metrics-encoder + /// expects. + fn iter(&self) -> impl Iterator + '_ { + (0..MAX_EXPECTED_RETRIES) + .zip(self.retry_buckets[0..MAX_EXPECTED_RETRIES].iter().cloned()) + .map(|(k, v)| (k as f64, v as f64)) + .chain(std::iter::once(( + f64::INFINITY, + self.retry_buckets[MAX_EXPECTED_RETRIES] as f64, + ))) + } + } + + #[derive(Default)] + pub struct HttpMetrics { + /// Retry counts histograms indexed by the ETH RCP method name. + retry_histogram_per_method: BTreeMap, + } + + impl HttpMetrics { + pub fn observe_retry_count(&mut self, method: String, count: usize) { + self.retry_histogram_per_method + .entry(method) + .or_default() + .observe_retry_count(count); + } + + #[cfg(test)] + pub fn count_retries_in_bucket(&self, method: &str, count: usize) -> u64 { + match self.retry_histogram_per_method.get(method) { + Some(histogram) => histogram.retry_buckets[count.min(MAX_EXPECTED_RETRIES)], + None => 0, + } + } + + pub fn encode( + &self, + encoder: &mut MetricsEncoder, + ) -> std::io::Result<()> { + if self.retry_histogram_per_method.is_empty() { + return Ok(()); + } + + let mut histogram_vec = encoder.histogram_vec( + "cketh_eth_rpc_call_retry_count", + "The number of ETH RPC call retries by method.", + )?; + + for (method, histogram) in &self.retry_histogram_per_method { + histogram_vec = histogram_vec.histogram( + &[("method", method.as_str())], + histogram.iter(), + histogram.retry_count as f64, + )?; + } + + Ok(()) + } + } + + thread_local! { + static METRICS: RefCell = RefCell::default(); + } + + /// Record the retry count for the specified ETH RPC method. + pub fn observe_retry_count(method: String, count: usize) { + METRICS.with(|metrics| metrics.borrow_mut().observe_retry_count(method, count)); + } + + /// Encodes the metrics related to ETH RPC method calls. + pub fn encode(encoder: &mut MetricsEncoder) -> std::io::Result<()> { + METRICS.with(|metrics| metrics.borrow().encode(encoder)) + } +} From b3ab56f3be7333a3fb1fcd58bcc92df4d541b44f Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 11:37:52 +0200 Subject: [PATCH 05/42] 243: copied requests --- src/rpc_client/requests.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/rpc_client/requests.rs diff --git a/src/rpc_client/requests.rs b/src/rpc_client/requests.rs new file mode 100644 index 00000000..6777a3fb --- /dev/null +++ b/src/rpc_client/requests.rs @@ -0,0 +1,19 @@ +use crate::address::Address; +use crate::eth_rpc::BlockSpec; +use serde::Serialize; + +/// Parameters of the [`eth_getTransactionCount`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactioncount) call. +#[derive(Debug, Serialize, Clone)] +#[serde(into = "(Address, BlockSpec)")] +pub struct GetTransactionCountParams { + /// The address for which the transaction count is requested. + pub address: Address, + /// Integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. + pub block: BlockSpec, +} + +impl From for (Address, BlockSpec) { + fn from(params: GetTransactionCountParams) -> Self { + (params.address, params.block) + } +} From 3476f9bcec6f22ff06b5c8c97d0fddaa023a6723 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 11:38:00 +0200 Subject: [PATCH 06/42] 243: copied responses --- Cargo.toml | 2 + src/rpc_client/mod.rs | 7 +-- src/rpc_client/responses.rs | 104 ++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 src/rpc_client/responses.rs diff --git a/Cargo.toml b/Cargo.toml index 4f9e6cd0..d94e3a1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ inherits = "release" [dependencies] candid = { workspace = true } +ethnum = { workspace = true } evm_rpc_types = { path = "evm_rpc_types" } futures = { workspace = true } getrandom = { workspace = true } @@ -52,6 +53,7 @@ assert_matches = "1.5" [workspace.dependencies] candid = { version = "0.9" } +ethnum = "1.5.0" futures = "0.3.30" getrandom = { version = "0.2", features = ["custom"] } hex = "0.4.3" diff --git a/src/rpc_client/mod.rs b/src/rpc_client/mod.rs index e6d397ad..30792441 100644 --- a/src/rpc_client/mod.rs +++ b/src/rpc_client/mod.rs @@ -25,9 +25,10 @@ use std::marker::PhantomData; use self::providers::RpcApi; -pub mod providers; -pub mod requests; -pub mod responses; +mod eth_rpc; +mod providers; +mod requests; +mod responses; #[cfg(test)] mod tests; diff --git a/src/rpc_client/responses.rs b/src/rpc_client/responses.rs new file mode 100644 index 00000000..406f5239 --- /dev/null +++ b/src/rpc_client/responses.rs @@ -0,0 +1,104 @@ +use crate::checked_amount::CheckedAmountOf; +use crate::eth_rpc::{Hash, HttpResponsePayload, LogEntry, ResponseTransform}; +use crate::numeric::{BlockNumber, GasAmount, Wei, WeiPerGas}; +use minicbor::{Decode, Encode}; +use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct TransactionReceipt { + /// The hash of the block containing the transaction. + #[serde(rename = "blockHash")] + pub block_hash: Hash, + + /// The number of the block containing the transaction. + #[serde(rename = "blockNumber")] + pub block_number: BlockNumber, + + /// The total base charge plus tip paid for each unit of gas + #[serde(rename = "effectiveGasPrice")] + pub effective_gas_price: WeiPerGas, + + /// The amount of gas used by this specific transaction alone + #[serde(rename = "gasUsed")] + pub gas_used: GasAmount, + + /// Status of the transaction. + pub status: TransactionStatus, + + /// The hash of the transaction + #[serde(rename = "transactionHash")] + pub transaction_hash: Hash, + + #[serde(rename = "contractAddress")] + pub contract_address: Option, + + pub from: String, + pub logs: Vec, + #[serde(rename = "logsBloom")] + pub logs_bloom: String, + pub to: String, + #[serde(rename = "transactionIndex")] + pub transaction_index: CheckedAmountOf<()>, + pub r#type: String, +} + +impl TransactionReceipt { + pub fn effective_transaction_fee(&self) -> Wei { + self.effective_gas_price + .transaction_cost(self.gas_used) + .expect("ERROR: overflow during transaction fee calculation") + } +} + +impl HttpResponsePayload for TransactionReceipt { + fn response_transform() -> Option { + Some(ResponseTransform::TransactionReceipt) + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, )] +#[serde(try_from = "ethnum::u256", into = "ethnum::u256")] +pub enum TransactionStatus { + /// Transaction was mined and executed successfully. + #[n(0)] + Success, + + /// Transaction was mined but execution failed (e.g., out-of-gas error). + /// The amount of the transaction is returned to the sender but gas is consumed. + /// Note that this is different from a transaction that is not mined at all: a failed transaction + /// is part of the blockchain and the next transaction from the same sender should have an incremented + /// transaction nonce. + #[n(1)] + Failure, +} + +impl From for ethnum::u256 { + fn from(value: TransactionStatus) -> Self { + match value { + TransactionStatus::Success => ethnum::u256::ONE, + TransactionStatus::Failure => ethnum::u256::ZERO, + } + } +} + +impl TryFrom for TransactionStatus { + type Error = String; + + fn try_from(value: ethnum::u256) -> Result { + match value { + ethnum::u256::ZERO => Ok(TransactionStatus::Failure), + ethnum::u256::ONE => Ok(TransactionStatus::Success), + _ => Err(format!("invalid transaction status: {}", value)), + } + } +} + +impl Display for TransactionStatus { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + TransactionStatus::Success => write!(f, "Success"), + TransactionStatus::Failure => write!(f, "Failure"), + } + } +} From 709517438ae219fc0f326d02f0a3f6c8e738c089 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 11:48:46 +0200 Subject: [PATCH 07/42] 243: copied tests --- src/rpc_client/tests.rs | 613 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 613 insertions(+) create mode 100644 src/rpc_client/tests.rs diff --git a/src/rpc_client/tests.rs b/src/rpc_client/tests.rs new file mode 100644 index 00000000..36bebe87 --- /dev/null +++ b/src/rpc_client/tests.rs @@ -0,0 +1,613 @@ +mod eth_rpc_client { + use crate::eth_rpc_client::providers::{EthMainnetService, EthSepoliaService, RpcService}; + use crate::eth_rpc_client::EthRpcClient; + use crate::lifecycle::EthereumNetwork; + + #[test] + fn should_retrieve_sepolia_providers_in_stable_order() { + let client = EthRpcClient::new(EthereumNetwork::SEPOLIA, None); + + let providers = client.providers(); + + assert_eq!( + providers, + &[ + RpcService::EthSepolia(EthSepoliaService::Ankr), + RpcService::EthSepolia(EthSepoliaService::PublicNode) + ] + ); + } + + #[test] + fn should_retrieve_mainnet_providers_in_stable_order() { + let client = EthRpcClient::new(EthereumNetwork::MAINNET, None); + + let providers = client.providers(); + + assert_eq!( + providers, + &[ + RpcService::EthMainnet(EthMainnetService::Ankr), + RpcService::EthMainnet(EthMainnetService::PublicNode), + RpcService::EthMainnet(EthMainnetService::Cloudflare) + ] + ); + } +} + +mod multi_call_results { + use crate::eth_rpc_client::providers::{EthMainnetService, RpcService}; + + const ANKR: RpcService = RpcService::EthMainnet(EthMainnetService::Ankr); + const PUBLIC_NODE: RpcService = RpcService::EthMainnet(EthMainnetService::PublicNode); + const CLOUDFLARE: RpcService = RpcService::EthMainnet(EthMainnetService::Cloudflare); + + mod reduce_with_equality { + use crate::eth_rpc::{HttpOutcallError, JsonRpcResult}; + use crate::eth_rpc_client::tests::multi_call_results::{ANKR, PUBLIC_NODE}; + use crate::eth_rpc_client::{MultiCallError, MultiCallResults}; + use ic_cdk::api::call::RejectionCode; + + #[test] + #[should_panic(expected = "MultiCallResults cannot be empty")] + fn should_panic_when_empty() { + let _panic = MultiCallResults::::from_non_empty_iter(vec![]); + } + + #[test] + fn should_be_inconsistent_when_different_call_errors() { + let results: MultiCallResults = MultiCallResults::from_non_empty_iter(vec![ + ( + ANKR, + Err(HttpOutcallError::IcError { + code: RejectionCode::CanisterReject, + message: "reject".to_string(), + }), + ), + ( + PUBLIC_NODE, + Err(HttpOutcallError::IcError { + code: RejectionCode::SysTransient, + message: "transient".to_string(), + }), + ), + ]); + + let reduced = results.clone().reduce_with_equality(); + + assert_eq!(reduced, Err(MultiCallError::InconsistentResults(results))) + } + + #[test] + fn should_be_inconsistent_when_different_rpc_errors() { + let results: MultiCallResults = MultiCallResults::from_non_empty_iter(vec![ + ( + ANKR, + Ok(JsonRpcResult::Error { + code: -32700, + message: "insufficient funds for gas * price + value".to_string(), + }), + ), + ( + PUBLIC_NODE, + Ok(JsonRpcResult::Error { + code: -32000, + message: "nonce too low".to_string(), + }), + ), + ]); + + let reduced = results.clone().reduce_with_equality(); + + assert_eq!(reduced, Err(MultiCallError::InconsistentResults(results))) + } + + #[test] + fn should_be_inconsistent_when_different_ok_results() { + let results: MultiCallResults = MultiCallResults::from_non_empty_iter(vec![ + (ANKR, Ok(JsonRpcResult::Result("hello".to_string()))), + (PUBLIC_NODE, Ok(JsonRpcResult::Result("world".to_string()))), + ]); + + let reduced = results.clone().reduce_with_equality(); + + assert_eq!(reduced, Err(MultiCallError::InconsistentResults(results))) + } + + #[test] + fn should_be_consistent_http_outcall_error() { + let results: MultiCallResults = MultiCallResults::from_non_empty_iter(vec![ + ( + ANKR, + Err(HttpOutcallError::IcError { + code: RejectionCode::CanisterReject, + message: "reject".to_string(), + }), + ), + ( + PUBLIC_NODE, + Err(HttpOutcallError::IcError { + code: RejectionCode::CanisterReject, + message: "reject".to_string(), + }), + ), + ]); + + let reduced = results.clone().reduce_with_equality(); + + assert_eq!( + reduced, + Err(MultiCallError::ConsistentHttpOutcallError( + HttpOutcallError::IcError { + code: RejectionCode::CanisterReject, + message: "reject".to_string(), + } + )) + ); + } + + #[test] + fn should_be_consistent_rpc_error() { + let results: MultiCallResults = MultiCallResults::from_non_empty_iter(vec![ + ( + ANKR, + Ok(JsonRpcResult::Error { + code: -32700, + message: "insufficient funds for gas * price + value".to_string(), + }), + ), + ( + PUBLIC_NODE, + Ok(JsonRpcResult::Error { + code: -32700, + message: "insufficient funds for gas * price + value".to_string(), + }), + ), + ]); + + let reduced = results.clone().reduce_with_equality(); + + assert_eq!( + reduced, + Err(MultiCallError::ConsistentJsonRpcError { + code: -32700, + message: "insufficient funds for gas * price + value".to_string(), + }) + ); + } + + #[test] + fn should_be_consistent_ok_result() { + let results: MultiCallResults = MultiCallResults::from_non_empty_iter(vec![ + (ANKR, Ok(JsonRpcResult::Result("0x01".to_string()))), + (PUBLIC_NODE, Ok(JsonRpcResult::Result("0x01".to_string()))), + ]); + + let reduced = results.clone().reduce_with_equality(); + + assert_eq!(reduced, Ok("0x01".to_string())); + } + } + + mod reduce_with_min_by_key { + use crate::eth_rpc::{Block, JsonRpcResult}; + use crate::eth_rpc_client::tests::multi_call_results::{ANKR, PUBLIC_NODE}; + use crate::eth_rpc_client::MultiCallResults; + use crate::numeric::{BlockNumber, Wei}; + + #[test] + fn should_get_minimum_block_number() { + let results: MultiCallResults = MultiCallResults::from_non_empty_iter(vec![ + ( + ANKR, + Ok(JsonRpcResult::Result(Block { + number: BlockNumber::new(0x411cda), + base_fee_per_gas: Wei::new(0x10), + })), + ), + ( + PUBLIC_NODE, + Ok(JsonRpcResult::Result(Block { + number: BlockNumber::new(0x411cd9), + base_fee_per_gas: Wei::new(0x10), + })), + ), + ]); + + let reduced = results.reduce_with_min_by_key(|block| block.number); + + assert_eq!( + reduced, + Ok(Block { + number: BlockNumber::new(0x411cd9), + base_fee_per_gas: Wei::new(0x10), + }) + ); + } + } + + mod reduce_with_stable_majority_by_key { + use crate::eth_rpc::{FeeHistory, JsonRpcResult}; + use crate::eth_rpc_client::tests::multi_call_results::{ANKR, CLOUDFLARE, PUBLIC_NODE}; + use crate::eth_rpc_client::MultiCallError::ConsistentJsonRpcError; + use crate::eth_rpc_client::{MultiCallError, MultiCallResults}; + use crate::numeric::{BlockNumber, WeiPerGas}; + + #[test] + fn should_get_unanimous_fee_history() { + let results: MultiCallResults = + MultiCallResults::from_non_empty_iter(vec![ + (ANKR, Ok(JsonRpcResult::Result(fee_history()))), + (PUBLIC_NODE, Ok(JsonRpcResult::Result(fee_history()))), + (CLOUDFLARE, Ok(JsonRpcResult::Result(fee_history()))), + ]); + + let reduced = + results.reduce_with_strict_majority_by_key(|fee_history| fee_history.oldest_block); + + assert_eq!(reduced, Ok(fee_history())); + } + + #[test] + fn should_get_fee_history_with_2_out_of_3() { + for index_non_majority in 0..3_usize { + let index_majority = (index_non_majority + 1) % 3; + let mut fees = [fee_history(), fee_history(), fee_history()]; + fees[index_non_majority].oldest_block = BlockNumber::new(0x10f73fd); + assert_ne!( + fees[index_non_majority].oldest_block, + fees[index_majority].oldest_block + ); + let majority_fee = fees[index_majority].clone(); + let [ankr_fee_history, cloudflare_fee_history, public_node_fee_history] = fees; + let results: MultiCallResults = + MultiCallResults::from_non_empty_iter(vec![ + (ANKR, Ok(JsonRpcResult::Result(ankr_fee_history))), + ( + CLOUDFLARE, + Ok(JsonRpcResult::Result(cloudflare_fee_history)), + ), + ( + PUBLIC_NODE, + Ok(JsonRpcResult::Result(public_node_fee_history)), + ), + ]); + + let reduced = results + .reduce_with_strict_majority_by_key(|fee_history| fee_history.oldest_block); + + assert_eq!(reduced, Ok(majority_fee)); + } + } + + #[test] + fn should_fail_when_no_strict_majority() { + let ankr_fee_history = FeeHistory { + oldest_block: BlockNumber::new(0x10f73fd), + ..fee_history() + }; + let cloudflare_fee_history = FeeHistory { + oldest_block: BlockNumber::new(0x10f73fc), + ..fee_history() + }; + let public_node_fee_history = FeeHistory { + oldest_block: BlockNumber::new(0x10f73fe), + ..fee_history() + }; + let three_distinct_results: MultiCallResults = + MultiCallResults::from_non_empty_iter(vec![ + (ANKR, Ok(JsonRpcResult::Result(ankr_fee_history.clone()))), + ( + PUBLIC_NODE, + Ok(JsonRpcResult::Result(public_node_fee_history.clone())), + ), + ]); + + let reduced = three_distinct_results + .clone() + .reduce_with_strict_majority_by_key(|fee_history| fee_history.oldest_block); + + assert_eq!( + reduced, + Err(MultiCallError::InconsistentResults( + MultiCallResults::from_non_empty_iter(vec![ + (ANKR, Ok(JsonRpcResult::Result(ankr_fee_history.clone()))), + ( + PUBLIC_NODE, + Ok(JsonRpcResult::Result(public_node_fee_history)) + ), + ]) + )) + ); + + let two_distinct_results: MultiCallResults = + MultiCallResults::from_non_empty_iter(vec![ + (ANKR, Ok(JsonRpcResult::Result(ankr_fee_history.clone()))), + ( + PUBLIC_NODE, + Ok(JsonRpcResult::Result(cloudflare_fee_history.clone())), + ), + ]); + + let reduced = two_distinct_results + .clone() + .reduce_with_strict_majority_by_key(|fee_history| fee_history.oldest_block); + + assert_eq!( + reduced, + Err(MultiCallError::InconsistentResults( + MultiCallResults::from_non_empty_iter(vec![ + (ANKR, Ok(JsonRpcResult::Result(ankr_fee_history))), + ( + PUBLIC_NODE, + Ok(JsonRpcResult::Result(cloudflare_fee_history)) + ), + ]) + )) + ); + } + + #[test] + fn should_fail_when_fee_history_inconsistent_for_same_oldest_block() { + let (fee, inconsistent_fee) = { + let fee = fee_history(); + let mut inconsistent_fee = fee.clone(); + inconsistent_fee.base_fee_per_gas[0] = WeiPerGas::new(0x729d3f3b4); + assert_ne!(fee, inconsistent_fee); + (fee, inconsistent_fee) + }; + + let results: MultiCallResults = + MultiCallResults::from_non_empty_iter(vec![ + (ANKR, Ok(JsonRpcResult::Result(fee.clone()))), + ( + PUBLIC_NODE, + Ok(JsonRpcResult::Result(inconsistent_fee.clone())), + ), + ]); + + let reduced = + results.reduce_with_strict_majority_by_key(|fee_history| fee_history.oldest_block); + + assert_eq!( + reduced, + Err(MultiCallError::InconsistentResults( + MultiCallResults::from_non_empty_iter(vec![ + (ANKR, Ok(JsonRpcResult::Result(fee.clone()))), + (PUBLIC_NODE, Ok(JsonRpcResult::Result(inconsistent_fee))), + ]) + )) + ); + } + + #[test] + fn should_fail_upon_any_error() { + let results: MultiCallResults = + MultiCallResults::from_non_empty_iter(vec![ + (ANKR, Ok(JsonRpcResult::Result(fee_history()))), + ( + PUBLIC_NODE, + Ok(JsonRpcResult::Error { + code: -32700, + message: "error".to_string(), + }), + ), + ]); + + let reduced = results + .clone() + .reduce_with_strict_majority_by_key(|fee_history| fee_history.oldest_block); + + assert_eq!( + reduced, + Err(ConsistentJsonRpcError { + code: -32700, + message: "error".to_string() + }) + ); + } + + fn fee_history() -> FeeHistory { + FeeHistory { + oldest_block: BlockNumber::new(0x10f73fc), + base_fee_per_gas: vec![ + WeiPerGas::new(0x729d3f3b3), + WeiPerGas::new(0x766e503ea), + WeiPerGas::new(0x75b51b620), + WeiPerGas::new(0x74094f2b4), + WeiPerGas::new(0x716724f03), + WeiPerGas::new(0x73b467f76), + ], + reward: vec![ + vec![WeiPerGas::new(0x5f5e100)], + vec![WeiPerGas::new(0x55d4a80)], + vec![WeiPerGas::new(0x5f5e100)], + vec![WeiPerGas::new(0x5f5e100)], + vec![WeiPerGas::new(0x5f5e100)], + ], + } + } + } + + mod has_http_outcall_error_matching { + use super::*; + use crate::eth_rpc::{HttpOutcallError, JsonRpcResult}; + use crate::eth_rpc_client::{MultiCallError, MultiCallResults}; + use ic_cdk::api::call::RejectionCode; + use proptest::prelude::any; + use proptest::proptest; + + proptest! { + #[test] + fn should_not_match_when_consistent_json_rpc_error(code in any::(), message in ".*") { + let error: MultiCallError = MultiCallError::ConsistentJsonRpcError { + code, + message, + }; + let always_true = |_outcall_error: &HttpOutcallError| true; + + assert!(!error.has_http_outcall_error_matching(always_true)); + } + } + + #[test] + fn should_match_when_consistent_http_outcall_error() { + let error: MultiCallError = + MultiCallError::ConsistentHttpOutcallError(HttpOutcallError::IcError { + code: RejectionCode::SysTransient, + message: "message".to_string(), + }); + let always_true = |_outcall_error: &HttpOutcallError| true; + let always_false = |_outcall_error: &HttpOutcallError| false; + + assert!(error.has_http_outcall_error_matching(always_true)); + assert!(!error.has_http_outcall_error_matching(always_false)); + } + + #[test] + fn should_match_on_single_inconsistent_result_with_outcall_error() { + let always_true = |_outcall_error: &HttpOutcallError| true; + let error_with_no_outcall_error = + MultiCallError::InconsistentResults(MultiCallResults::from_non_empty_iter(vec![ + (ANKR, Ok(JsonRpcResult::Result(1))), + ( + CLOUDFLARE, + Ok(JsonRpcResult::Error { + code: -32700, + message: "error".to_string(), + }), + ), + (PUBLIC_NODE, Ok(JsonRpcResult::Result(1))), + ])); + assert!(!error_with_no_outcall_error.has_http_outcall_error_matching(always_true)); + + let error_with_outcall_error = + MultiCallError::InconsistentResults(MultiCallResults::from_non_empty_iter(vec![ + (ANKR, Ok(JsonRpcResult::Result(1))), + ( + CLOUDFLARE, + Err(HttpOutcallError::IcError { + code: RejectionCode::SysTransient, + message: "message".to_string(), + }), + ), + (PUBLIC_NODE, Ok(JsonRpcResult::Result(1))), + ])); + assert!(error_with_outcall_error.has_http_outcall_error_matching(always_true)); + } + } +} + +mod eth_get_transaction_receipt { + use crate::eth_rpc::Hash; + use crate::eth_rpc_client::responses::{TransactionReceipt, TransactionStatus}; + use crate::numeric::{BlockNumber, GasAmount, WeiPerGas}; + use assert_matches::assert_matches; + use proptest::proptest; + use std::str::FromStr; + + #[test] + fn should_deserialize_transaction_receipt() { + const RECEIPT: &str = r#"{ + "transactionHash": "0x0e59bd032b9b22aca5e2784e4cf114783512db00988c716cf17a1cc755a0a93d", + "blockHash": "0x82005d2f17b251900968f01b0ed482cb49b7e1d797342bc504904d442b64dbe4", + "blockNumber": "0x4132ec", + "logs": [], + "contractAddress": null, + "effectiveGasPrice": "0xfefbee3e", + "cumulativeGasUsed": "0x8b2e10", + "from": "0x1789f79e95324a47c5fd6693071188e82e9a3558", + "gasUsed": "0x5208", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x01", + "to": "0xdd2851cdd40ae6536831558dd46db62fac7a844d", + "transactionIndex": "0x32", + "type": "0x2" + }"#; + + let receipt: TransactionReceipt = serde_json::from_str(RECEIPT).unwrap(); + + assert_eq!( + receipt, + TransactionReceipt { + block_hash: Hash::from_str( + "0x82005d2f17b251900968f01b0ed482cb49b7e1d797342bc504904d442b64dbe4" + ) + .unwrap(), + block_number: BlockNumber::new(0x4132ec), + effective_gas_price: WeiPerGas::new(0xfefbee3e), + gas_used: GasAmount::new(0x5208), + status: TransactionStatus::Success, + transaction_hash: Hash::from_str( + "0x0e59bd032b9b22aca5e2784e4cf114783512db00988c716cf17a1cc755a0a93d" + ) + .unwrap(), + } + ) + } + + #[test] + fn should_deserialize_transaction_status() { + let status: TransactionStatus = serde_json::from_str("\"0x01\"").unwrap(); + assert_eq!(status, TransactionStatus::Success); + + // some providers do not return a full byte (2 hex digits) for the status + let status: TransactionStatus = serde_json::from_str("\"0x1\"").unwrap(); + assert_eq!(status, TransactionStatus::Success); + + let status: TransactionStatus = serde_json::from_str("\"0x0\"").unwrap(); + assert_eq!(status, TransactionStatus::Failure); + + let status: TransactionStatus = serde_json::from_str("\"0x00\"").unwrap(); + assert_eq!(status, TransactionStatus::Failure); + } + + #[test] + fn should_deserialize_serialized_transaction_status() { + let status: TransactionStatus = + serde_json::from_str(&serde_json::to_string(&TransactionStatus::Success).unwrap()) + .unwrap(); + assert_eq!(status, TransactionStatus::Success); + + let status: TransactionStatus = + serde_json::from_str(&serde_json::to_string(&TransactionStatus::Failure).unwrap()) + .unwrap(); + assert_eq!(status, TransactionStatus::Failure); + } + + proptest! { + #[test] + fn should_fail_deserializing_wrong_transaction_status(wrong_status in 2_u32..u32::MAX) { + let status = format!("\"0x{:x}\"", wrong_status); + let error = serde_json::from_str::(&status); + assert_matches!(error, Err(e) if e.to_string().contains("invalid transaction status")); + } + } +} + +mod eth_get_transaction_count { + use crate::address::Address; + use crate::eth_rpc::{BlockSpec, BlockTag}; + use crate::eth_rpc_client::requests::GetTransactionCountParams; + use crate::numeric::TransactionCount; + use std::str::FromStr; + + #[test] + fn should_serialize_get_transaction_count_params_as_tuple() { + let params = GetTransactionCountParams { + address: Address::from_str("0x407d73d8a49eeb85d32cf465507dd71d507100c1").unwrap(), + block: BlockSpec::Tag(BlockTag::Finalized), + }; + let serialized_params = serde_json::to_string(¶ms).unwrap(); + assert_eq!( + serialized_params, + r#"["0x407d73d8a49eeb85d32cf465507dd71d507100c1","finalized"]"# + ); + } + + #[test] + fn should_deserialize_transaction_count() { + let count: TransactionCount = serde_json::from_str("\"0x3d8\"").unwrap(); + assert_eq!(count, TransactionCount::from(0x3d8_u32)); + } +} From e8190205468f89a23a8a146d2aaf81a7918db509 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 12:02:48 +0200 Subject: [PATCH 08/42] 243: fixing rpc_client --- src/rpc_client/mod.rs | 71 +++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/src/rpc_client/mod.rs b/src/rpc_client/mod.rs index 30792441..d2d95d5e 100644 --- a/src/rpc_client/mod.rs +++ b/src/rpc_client/mod.rs @@ -1,30 +1,23 @@ -use crate::eth_rpc::{ - self, are_errors_consistent, Block, BlockSpec, FeeHistory, FeeHistoryParams, GetLogsParam, - Hash, HttpOutcallError, HttpResponsePayload, JsonRpcError, LogEntry, ProviderError, - ResponseSizeEstimate, RpcError, SendRawTransactionResult, +use crate::rpc_client::eth_rpc::{ + are_errors_consistent, Block, BlockSpec, FeeHistory, FeeHistoryParams, GetBlockByNumberParams, + GetLogsParam, Hash, HttpResponsePayload, JsonRpcError, LogEntry, ResponseSizeEstimate, + SendRawTransactionResult, HEADER_SIZE_LIMIT, }; -use crate::eth_rpc_client::eth_rpc::HEADER_SIZE_LIMIT; -use crate::eth_rpc_client::providers::{ - RpcService, MAINNET_PROVIDERS, SEPOLIA_PROVIDERS, UNKNOWN_PROVIDERS, - ARBITRUM_PROVIDERS, BASE_PROVIDERS, OPTIMISM_PROVIDERS, +use crate::rpc_client::providers::{ + ARBITRUM_PROVIDERS, BASE_PROVIDERS, MAINNET_PROVIDERS, OPTIMISM_PROVIDERS, SEPOLIA_PROVIDERS, + UNKNOWN_PROVIDERS, }; -use crate::eth_rpc_client::requests::GetTransactionCountParams; -use crate::eth_rpc_client::responses::TransactionReceipt; -use crate::lifecycle::EthereumNetwork; -use crate::logs::{DEBUG, INFO}; -use crate::numeric::TransactionCount; -use crate::state::State; +use crate::rpc_client::requests::GetTransactionCountParams; +use crate::rpc_client::responses::TransactionReceipt; use async_trait::async_trait; use candid::CandidType; -use ic_canister_log::log; +use evm_rpc_types::{HttpOutcallError, ProviderError, RpcApi, RpcConfig, RpcError, RpcService}; use ic_cdk::api::management_canister::http_request::{CanisterHttpRequestArgument, HttpResponse}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::collections::BTreeMap; use std::fmt::Debug; use std::marker::PhantomData; -use self::providers::RpcApi; - mod eth_rpc; mod providers; mod requests; @@ -33,6 +26,15 @@ mod responses; #[cfg(test)] mod tests; +//TODO: Dummy log. use ic_canister_log::log +macro_rules! log { + ($sink:expr, $message:expr $(,$args:expr)* $(,)*) => {{ + let message = std::format!($message $(,$args)*); + // Print the message for convenience for local development (e.g. integration tests) + println!("{}", &message); + }} +} + #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait RpcTransport: Debug { @@ -67,10 +69,19 @@ impl RpcTransport for DefaultTransport { } } -#[derive(Clone, Debug, PartialEq, Eq, Default, CandidType, Deserialize)] -pub struct RpcConfig { - #[serde(rename = "responseSizeEstimate")] - pub response_size_estimate: Option, +#[derive(Clone, Copy, Default, Debug, Eq, PartialEq)] +pub struct EthereumNetwork(#[n(0)] u64); + +impl EthereumNetwork { + pub const MAINNET: EthereumNetwork = EthereumNetwork(1); + pub const SEPOLIA: EthereumNetwork = EthereumNetwork(11155111); + pub const ARBITRUM: EthereumNetwork = EthereumNetwork(42161); + pub const BASE: EthereumNetwork = EthereumNetwork(8453); + pub const OPTIMISM: EthereumNetwork = EthereumNetwork(10); + + pub fn chain_id(&self) -> u64 { + self.0 + } } #[derive(Clone, Debug, PartialEq, Eq)] @@ -95,10 +106,6 @@ impl EthRpcClient { } } - pub fn from_state(state: &State) -> Self { - Self::new(state.ethereum_network(), None, RpcConfig::default()) - } - fn providers(&self) -> &[RpcService] { match self.providers { Some(ref providers) => providers, @@ -145,7 +152,7 @@ impl EthRpcClient { params.clone(), response_size_estimate, ) - .await; + .await; match result { Ok(value) => return Ok(value), Err(RpcError::JsonRpcError(json_rpc_error @ JsonRpcError { .. })) => { @@ -191,7 +198,7 @@ impl EthRpcClient { params.clone(), response_size_estimate, ) - .await + .await }); } futures::future::join_all(fut).await @@ -217,8 +224,6 @@ impl EthRpcClient { &self, block: BlockSpec, ) -> Result> { - use crate::eth_rpc::GetBlockByNumberParams; - let expected_block_size = match self.chain { EthereumNetwork::SEPOLIA => 12 * 1024, EthereumNetwork::MAINNET => 24 * 1024, @@ -278,7 +283,7 @@ impl EthRpcClient { vec![raw_signed_transaction_hex], self.response_size_estimate(256 + HEADER_SIZE_LIMIT), ) - .await + .await } pub async fn multi_eth_send_raw_transaction( @@ -290,8 +295,8 @@ impl EthRpcClient { vec![raw_signed_transaction_hex], self.response_size_estimate(256 + HEADER_SIZE_LIMIT), ) - .await - .reduce_with_equality() + .await + .reduce_with_equality() } pub async fn eth_get_transaction_count( @@ -303,7 +308,7 @@ impl EthRpcClient { params, self.response_size_estimate(50 + HEADER_SIZE_LIMIT), ) - .await + .await } } From 82798b7c7be04aabc85895847cb909e1c9fa12c7 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 13:26:45 +0200 Subject: [PATCH 09/42] 243: copy numeric --- src/rpc_client/numeric/mod.rs | 57 +++++++++++++++++++++++++ src/rpc_client/numeric/tests.rs | 76 +++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 src/rpc_client/numeric/mod.rs create mode 100644 src/rpc_client/numeric/tests.rs diff --git a/src/rpc_client/numeric/mod.rs b/src/rpc_client/numeric/mod.rs new file mode 100644 index 00000000..641f31ec --- /dev/null +++ b/src/rpc_client/numeric/mod.rs @@ -0,0 +1,57 @@ +//! Numeric types for Ethereum. + +#[cfg(test)] +mod tests; + +use crate::checked_amount::CheckedAmountOf; +use phantom_newtype::Id; + +pub enum WeiTag {} +pub type Wei = CheckedAmountOf; + +pub enum WeiPerGasUnit {} +pub type WeiPerGas = CheckedAmountOf; + +pub fn wei_from_milli_ether(value: u128) -> Wei { + const MILLI_ETHER: u64 = 1_000_000_000_000_000_000; + Wei::new(value) + .checked_mul(MILLI_ETHER) + .expect("any u128 multiplied by 10^15 always fits in a u256") +} + +pub enum TransactionNonceTag {} +/// Number of transactions sent by the sender. +/// Ethereum expects nonce to increase by 1 for each transaction. +/// If that's not the case, the transaction is rejected +/// (if the nonce was already seen in another transaction from the same sender) +/// or kept in the node's transaction pool while waiting for the missing nonce. +pub type TransactionNonce = CheckedAmountOf; + +pub enum TransactionCountTag {} + +/// Number of transactions emitted by an address at a given block height (`finalized`, `safe` or `latest`). +/// This should closely follow [`TransactionNonce`] in case the address is the minter's one, +/// but depending on the block height the two may differ. +pub type TransactionCount = CheckedAmountOf; + +pub enum BlockNumberTag {} +pub type BlockNumber = CheckedAmountOf; + +pub enum GasUnit {} +/// The number of gas units attached to a transaction for execution. +pub type GasAmount = CheckedAmountOf; + +pub enum EthLogIndexTag {} +pub type LogIndex = CheckedAmountOf; +pub enum BurnIndexTag {} +pub type LedgerBurnIndex = Id; + +pub enum MintIndexTag {} +pub type LedgerMintIndex = Id; + +impl WeiPerGas { + pub fn transaction_cost(self, gas: GasAmount) -> Option { + self.checked_mul(gas.into_inner()) + .map(|value| value.change_units()) + } +} diff --git a/src/rpc_client/numeric/tests.rs b/src/rpc_client/numeric/tests.rs new file mode 100644 index 00000000..a380c3d5 --- /dev/null +++ b/src/rpc_client/numeric/tests.rs @@ -0,0 +1,76 @@ +mod transaction_nonce { + use crate::numeric::TransactionNonce; + use assert_matches::assert_matches; + use candid::Nat; + use num_bigint::BigUint; + use proptest::{array::uniform32, prelude::any, prop_assert_eq, proptest}; + + #[test] + fn should_overflow() { + let nonce = TransactionNonce::MAX; + assert_eq!(nonce.checked_increment(), None); + } + + #[test] + fn should_not_overflow() { + let nonce = TransactionNonce::MAX + .checked_sub(TransactionNonce::ONE) + .unwrap(); + assert_eq!(nonce.checked_increment(), Some(TransactionNonce::MAX)); + } + + proptest! { + #[test] + fn should_convert_from_nat(u256_bytes in uniform32(any::())) { + let u256 = Nat(BigUint::from_bytes_be(&u256_bytes)); + + assert_eq!( + TransactionNonce::try_from(u256), + Ok(TransactionNonce::from_be_bytes(u256_bytes)) + ); + } + + #[test] + fn biguint_to_u256_conversion(value in any::()) { + use crate::numeric::Wei; + + let nat_value: Nat = value.into(); + let expected_wei_value = Wei::from(value); + + let converted_wei: Wei = nat_value.try_into().unwrap(); + prop_assert_eq!(converted_wei, expected_wei_value); + } + } + + #[test] + fn should_fail_when_nat_too_big() { + const U256_MAX: &[u8; 64] = + b"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; + + assert_eq!( + TransactionNonce::try_from(Nat( + BigUint::parse_bytes(U256_MAX, 16).expect("Failed to parse u256 max") + )), + Ok(TransactionNonce::MAX) + ); + + let u256_max_plus_one: Nat = + Nat(BigUint::parse_bytes(U256_MAX, 16).expect("Failed to parse u256 max")) + 1; + assert_matches!( + TransactionNonce::try_from(u256_max_plus_one), + Err(e) if e.contains("Nat does not fit in a U256") + ); + } +} + +mod wei { + use crate::numeric::{wei_from_milli_ether, Wei}; + + #[test] + fn should_not_overflow_when_converting_from_milli_ether() { + assert_eq!( + wei_from_milli_ether(u128::MAX), + Wei::from_str_hex("0xDE0B6B3A763FFFFFFFFFFFFFFFFFFFFF21F494C589C0000").unwrap() + ); + } +} From 52269480c470c1bd0288e5ffe4c3ee11b437da32 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 13:28:28 +0200 Subject: [PATCH 10/42] 243: copy checked_amount --- src/rpc_client/checked_amount/mod.rs | 330 +++++++++++++++++++++++++ src/rpc_client/checked_amount/tests.rs | 51 ++++ 2 files changed, 381 insertions(+) create mode 100644 src/rpc_client/checked_amount/mod.rs create mode 100644 src/rpc_client/checked_amount/tests.rs diff --git a/src/rpc_client/checked_amount/mod.rs b/src/rpc_client/checked_amount/mod.rs new file mode 100644 index 00000000..5306449c --- /dev/null +++ b/src/rpc_client/checked_amount/mod.rs @@ -0,0 +1,330 @@ +#[cfg(test)] +mod tests; + +use candid::CandidType; +use minicbor; +use rlp::RlpStream; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::cmp::Ordering; +use std::fmt; +use std::marker::PhantomData; +use std::num::ParseIntError; +use std::ops::Rem; + +use crate::eth_rpc::into_nat; + +/// `CheckedAmountOf` provides a type-safe way to keep an amount of some `Unit`. +/// In contrast to `AmountOf`, all operations are checked and do not overflow. +/// +/// # Arithmetic +/// ``` +/// use ic_cketh_minter::checked_amount::CheckedAmountOf; +/// +/// enum MetricApple {} +/// type Apples = CheckedAmountOf; +/// +/// enum MetricOrange {} +/// type Oranges = CheckedAmountOf; +/// +/// enum OrangesPerAppleUnit {} +/// type OrangesPerApple = CheckedAmountOf; +/// +/// let three_apples = Apples::from(3_u8); +/// +/// // Checked addition +/// assert_eq!(three_apples.checked_add(Apples::TWO), Some(Apples::from(5_u8))); +/// assert_eq!(Apples::MAX.checked_add(Apples::ONE), None); +/// +/// // Checked subtraction +/// assert_eq!(three_apples.checked_sub(Apples::TWO), Some(Apples::ONE)); +/// assert_eq!(Apples::TWO.checked_sub(three_apples), None); +/// +/// // Checked multiplication by scalar +/// assert_eq!(three_apples.checked_mul(2_u8), Some(Apples::from(6_u8))); +/// assert_eq!(Apples::MAX.checked_mul(2_u8), None); +/// +/// // Ceiling checked division by scalar +/// assert_eq!(three_apples.checked_div_ceil(0_u8), None); +/// assert_eq!(three_apples.checked_div_ceil(2_u8), Some(Apples::TWO)); +/// +/// // (Floor) division by two +/// assert_eq!(Apples::ONE.div_by_two(), Apples::ZERO); +/// assert_eq!(Apples::TWO.div_by_two(), Apples::ONE); +/// assert_eq!(three_apples.div_by_two(), Apples::ONE); +/// ``` +pub struct CheckedAmountOf(ethnum::u256, PhantomData); + +impl CandidType for CheckedAmountOf { + fn _ty() -> candid::types::Type { + candid::Nat::_ty() + } + + fn idl_serialize(&self, serializer: S) -> Result<(), S::Error> + where + S: candid::types::Serializer, + { + serializer.serialize_nat(&into_nat(self.0)) + } +} + +impl CheckedAmountOf { + pub const ZERO: Self = Self(ethnum::u256::ZERO, PhantomData); + pub const ONE: Self = Self(ethnum::u256::ONE, PhantomData); + pub const TWO: Self = Self(ethnum::u256::new(2), PhantomData); + pub const MAX: Self = Self(ethnum::u256::MAX, PhantomData); + + /// `new` is a synonym for `from` that can be evaluated in + /// compile time. The main use-case of this functions is defining + /// constants. + #[inline] + pub const fn new(value: u128) -> CheckedAmountOf { + Self(ethnum::u256::new(value), PhantomData) + } + + #[inline] + const fn from_inner(value: ethnum::u256) -> Self { + Self(value, PhantomData) + } + + pub const fn into_inner(self) -> ethnum::u256 { + self.0 + } + + #[inline] + pub const fn from_words(hi: u128, lo: u128) -> Self { + Self::from_inner(ethnum::u256::from_words(hi, lo)) + } + + pub fn from_str_hex(src: &str) -> Result { + ethnum::u256::from_str_hex(src).map(Self::from_inner) + } + + pub fn from_be_bytes(bytes: [u8; 32]) -> Self { + Self::from_inner(ethnum::u256::from_be_bytes(bytes)) + } + + pub fn to_be_bytes(self) -> [u8; 32] { + self.0.to_be_bytes() + } + + pub fn checked_add(self, other: Self) -> Option { + self.0.checked_add(other.0).map(Self::from_inner) + } + + pub fn checked_increment(&self) -> Option { + self.checked_add(Self::ONE) + } + + pub fn checked_decrement(&self) -> Option { + self.checked_sub(Self::ONE) + } + + pub fn checked_sub(self, other: Self) -> Option { + self.0.checked_sub(other.0).map(Self::from_inner) + } + + pub fn change_units(self) -> CheckedAmountOf { + CheckedAmountOf::::from_inner(self.0) + } + + pub fn checked_mul>(self, factor: T) -> Option { + self.0.checked_mul(factor.into()).map(Self::from_inner) + } + + pub fn checked_div_ceil>(self, rhs: T) -> Option { + let rhs = rhs.into(); + if rhs == ethnum::u256::ZERO { + return None; + } + let (quotient, remainder) = (self.0.div_euclid(rhs), self.0.rem(&rhs)); + if remainder == ethnum::u256::ZERO { + Some(Self::from_inner(quotient)) + } else { + Self::from_inner(quotient).checked_increment() + } + } + + pub fn div_by_two(self) -> Self { + Self::from_inner(self.0 >> 1) + } + + pub fn as_f64(&self) -> f64 { + self.0.as_f64() + } + + /// Returns the display implementation of the inner value. + /// Useful to avoid thousands separators if value is used for example in URLs. + /// ``` + /// use ic_cketh_minter::checked_amount::CheckedAmountOf; + /// + /// enum MetricApple{} + /// type Apples = CheckedAmountOf; + /// let many_apples = Apples::from(4_332_415_u32); + /// + /// assert_eq!(many_apples.to_string_inner(), "4332415".to_string()); + /// ``` + pub fn to_string_inner(&self) -> String { + self.0.to_string() + } +} + +macro_rules! impl_from { + ($($t:ty),* $(,)?) => {$( + impl From<$t> for CheckedAmountOf { + #[inline] + fn from(value: $t) -> Self { + Self(ethnum::u256::from(value), PhantomData) + } + } + )*}; +} + +impl_from! { u8, u16, u32, u64, u128 } + +impl TryFrom for CheckedAmountOf { + type Error = String; + + fn try_from(value: candid::Nat) -> Result { + let value_bytes = value.0.to_bytes_be(); + let mut value_u256 = [0u8; 32]; + if value_bytes.len() <= 32 { + value_u256[32 - value_bytes.len()..].copy_from_slice(&value_bytes); + } else { + return Err(format!("Nat does not fit in a U256: {}", value)); + } + Ok(Self::from_inner(ethnum::u256::from_be_bytes(value_u256))) + } +} + +impl From> for candid::Nat { + fn from(value: CheckedAmountOf) -> Self { + use num_bigint::BigUint; + candid::Nat::from(BigUint::from_bytes_be(&value.0.to_be_bytes())) + } +} + +impl fmt::Debug for CheckedAmountOf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use thousands::Separable; + write!(f, "{}", self.0.separate_with_underscores()) + } +} + +impl fmt::Display for CheckedAmountOf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use thousands::Separable; + write!(f, "{}", self.0.separate_with_underscores()) + } +} + +impl fmt::LowerHex for CheckedAmountOf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:x}", self.0) + } +} + +impl fmt::UpperHex for CheckedAmountOf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:X}", self.0) + } +} + +impl Clone for CheckedAmountOf { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for CheckedAmountOf {} + +impl PartialEq for CheckedAmountOf { + fn eq(&self, rhs: &Self) -> bool { + self.0.eq(&rhs.0) + } +} + +impl Eq for CheckedAmountOf {} + +impl PartialOrd for CheckedAmountOf { + fn partial_cmp(&self, rhs: &Self) -> Option { + Some(self.cmp(rhs)) + } +} + +impl Ord for CheckedAmountOf { + fn cmp(&self, rhs: &Self) -> Ordering { + self.0.cmp(&rhs.0) + } +} + +// Derived serde `impl Serialize` produces an extra `unit` value for +// phantom data, e.g. `AmountOf::::from(10)` is serialized +// into json as `[10, null]` by default. +// +// We want serialization format of `Repr` and the `AmountOf` to match +// exactly, that's why we have to provide custom instances. +impl Serialize for CheckedAmountOf { + fn serialize(&self, serializer: S) -> Result { + self.0.serialize(serializer) + } +} + +impl<'de, Unit> Deserialize<'de> for CheckedAmountOf { + fn deserialize>(deserializer: D) -> Result { + use num_bigint::BigUint; + use serde::de::{self, Visitor}; + struct CheckedAmountVisitor { + phantom: PhantomData, + } + impl Default for CheckedAmountVisitor { + fn default() -> Self { + Self { + phantom: PhantomData::default(), + } + } + } + impl<'de, Unit> Visitor<'de> for CheckedAmountVisitor { + type Value = CheckedAmountOf; + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "hex-encoded string or Candid nat") + } + fn visit_str(self, v: &str) -> Result { + CheckedAmountOf::from_str_hex(v) + .map_err(|_| de::Error::custom("invalid hex string")) + } + fn visit_byte_buf(self, v: Vec) -> Result { + if v[0] == 1 { + candid::Nat(BigUint::from_bytes_le(&v[1..])) + .try_into() + .map_err(|_| de::Error::custom("invalid BigUint")) + } else { + Err(de::Error::custom("not nat")) + } + } + } + deserializer.deserialize_any(CheckedAmountVisitor::default()) + } +} + +impl minicbor::Encode for CheckedAmountOf { + fn encode( + &self, + e: &mut minicbor::Encoder, + ctx: &mut C, + ) -> Result<(), minicbor::encode::Error> { + crate::cbor::u256::encode(&self.0, e, ctx) + } +} + +impl<'b, C, Unit> minicbor::Decode<'b, C> for CheckedAmountOf { + fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { + crate::cbor::u256::decode(d, ctx).map(Self::from_inner) + } +} + +impl rlp::Encodable for CheckedAmountOf { + fn rlp_append(&self, s: &mut RlpStream) { + let leading_empty_bytes: usize = self.0.leading_zeros() as usize / 8; + s.append(&self.0.to_be_bytes()[leading_empty_bytes..].as_ref()); + } +} diff --git a/src/rpc_client/checked_amount/tests.rs b/src/rpc_client/checked_amount/tests.rs new file mode 100644 index 00000000..571d5be3 --- /dev/null +++ b/src/rpc_client/checked_amount/tests.rs @@ -0,0 +1,51 @@ +use crate::checked_amount::CheckedAmountOf; + +mod checked_div_ceil { + use super::Amount; + use proptest::prelude::any; + use proptest::proptest; + + proptest! { + #[test] + fn should_be_zero_when_dividend_is_zero(divisor in 1_u128..=u128::MAX) { + assert_eq!(Amount::ZERO, Amount::ZERO.checked_div_ceil(divisor).unwrap()); + } + } + + proptest! { + #[test] + fn should_be_none_when_divisor_is_zero(amount in any::()) { + assert_eq!(None, Amount::from(amount).checked_div_ceil(0_u8)); + } + } + + proptest! { + #[test] + fn should_be_like_floor_division_for_multiple_of_divisors(quotient in any::(), divisor in 1_u128..=u128::MAX) { + let expected_quotient = Amount::from(quotient); + let amount = expected_quotient.checked_mul(divisor).expect("multiplication of two u128 fits in a u256"); + + let actual_quotient = amount.checked_div_ceil(divisor).unwrap(); + + assert_eq!(expected_quotient, actual_quotient); + } + } + + proptest! { + #[test] + fn should_increment_quotient_of_floor_division_when_not_multiple_of_divisor(divisor in 1_u128..=u128::MAX) { + let large_prime_number = Amount::from_str_hex( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", + ) + .expect("valid u256 since this is the p parameter of ECDSA Secp256k1 curve"); + + let actual_quotient = large_prime_number.checked_div_ceil(divisor).unwrap(); + + let expected_quotient = large_prime_number.0 / divisor + 1; + assert_eq!(expected_quotient, actual_quotient.0); + } + } +} + +enum Unit {} +type Amount = CheckedAmountOf; From 4511a343ab89f72bef8c18872ed16930e06e03f3 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 13:34:58 +0200 Subject: [PATCH 11/42] 243: fix checked_amount --- Cargo.lock | 4 +++ Cargo.toml | 5 ++- src/rpc_client/checked_amount/mod.rs | 44 ++------------------------ src/rpc_client/checked_amount/tests.rs | 2 +- 4 files changed, 11 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7be20b42..14d26394 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1590,6 +1590,7 @@ dependencies = [ "async-trait", "candid", "ethers-core", + "ethnum", "evm_rpc_types", "futures", "getrandom 0.2.15", @@ -1610,10 +1611,13 @@ dependencies = [ "ic-test-utilities-load-wasm", "maplit", "num", + "num-bigint 0.4.6", "num-derive", "num-traits", + "proptest", "serde", "serde_json", + "thousands", "url", "zeroize", ] diff --git a/Cargo.toml b/Cargo.toml index d94e3a1a..d0a98597 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,10 +33,12 @@ ic-certified-map = { workspace = true } cketh-common = { git = "https://github.com/dfinity/ic", rev = "1f551bea63354370b6e7a5037e96d464bdab3b41", package = "ic-cketh-minter" } maplit = "1.0" num = "0.4" +num-bigint = { workspace = true } num-traits = "0.2" num-derive = "0.4" serde = { workspace = true } serde_json = { workspace = true } +thousands = "0.2" url = "2.5" async-trait = "0.1" hex = "0.4" @@ -44,12 +46,13 @@ ethers-core = "2.0" zeroize = "1.8" [dev-dependencies] +assert_matches = "1.5" ic-ic00-types = { git = "https://github.com/dfinity/ic", rev = "release-2023-09-27_23-01" } ic-base-types = { git = "https://github.com/dfinity/ic", rev = "release-2023-09-27_23-01" } ic-config = { git = "https://github.com/dfinity/ic", rev = "release-2023-09-27_23-01" } ic-state-machine-tests = { git = "https://github.com/dfinity/ic", rev = "release-2023-09-27_23-01" } ic-test-utilities-load-wasm = { git = "https://github.com/dfinity/ic", rev = "release-2023-09-27_23-01" } -assert_matches = "1.5" +proptest = { workspace = true } [workspace.dependencies] candid = { version = "0.9" } diff --git a/src/rpc_client/checked_amount/mod.rs b/src/rpc_client/checked_amount/mod.rs index 5306449c..94290dfb 100644 --- a/src/rpc_client/checked_amount/mod.rs +++ b/src/rpc_client/checked_amount/mod.rs @@ -2,8 +2,6 @@ mod tests; use candid::CandidType; -use minicbor; -use rlp::RlpStream; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::cmp::Ordering; use std::fmt; @@ -11,14 +9,12 @@ use std::marker::PhantomData; use std::num::ParseIntError; use std::ops::Rem; -use crate::eth_rpc::into_nat; - /// `CheckedAmountOf` provides a type-safe way to keep an amount of some `Unit`. /// In contrast to `AmountOf`, all operations are checked and do not overflow. /// /// # Arithmetic /// ``` -/// use ic_cketh_minter::checked_amount::CheckedAmountOf; +/// use evm_rpc::rpc_client::checked_amount::CheckedAmountOf; /// /// enum MetricApple {} /// type Apples = CheckedAmountOf; @@ -54,19 +50,6 @@ use crate::eth_rpc::into_nat; /// ``` pub struct CheckedAmountOf(ethnum::u256, PhantomData); -impl CandidType for CheckedAmountOf { - fn _ty() -> candid::types::Type { - candid::Nat::_ty() - } - - fn idl_serialize(&self, serializer: S) -> Result<(), S::Error> - where - S: candid::types::Serializer, - { - serializer.serialize_nat(&into_nat(self.0)) - } -} - impl CheckedAmountOf { pub const ZERO: Self = Self(ethnum::u256::ZERO, PhantomData); pub const ONE: Self = Self(ethnum::u256::ONE, PhantomData); @@ -155,7 +138,7 @@ impl CheckedAmountOf { /// Returns the display implementation of the inner value. /// Useful to avoid thousands separators if value is used for example in URLs. /// ``` - /// use ic_cketh_minter::checked_amount::CheckedAmountOf; + /// use evm_rpc::rpc_client::checked_amount::CheckedAmountOf; /// /// enum MetricApple{} /// type Apples = CheckedAmountOf; @@ -305,26 +288,3 @@ impl<'de, Unit> Deserialize<'de> for CheckedAmountOf { deserializer.deserialize_any(CheckedAmountVisitor::default()) } } - -impl minicbor::Encode for CheckedAmountOf { - fn encode( - &self, - e: &mut minicbor::Encoder, - ctx: &mut C, - ) -> Result<(), minicbor::encode::Error> { - crate::cbor::u256::encode(&self.0, e, ctx) - } -} - -impl<'b, C, Unit> minicbor::Decode<'b, C> for CheckedAmountOf { - fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { - crate::cbor::u256::decode(d, ctx).map(Self::from_inner) - } -} - -impl rlp::Encodable for CheckedAmountOf { - fn rlp_append(&self, s: &mut RlpStream) { - let leading_empty_bytes: usize = self.0.leading_zeros() as usize / 8; - s.append(&self.0.to_be_bytes()[leading_empty_bytes..].as_ref()); - } -} diff --git a/src/rpc_client/checked_amount/tests.rs b/src/rpc_client/checked_amount/tests.rs index 571d5be3..f2da17d1 100644 --- a/src/rpc_client/checked_amount/tests.rs +++ b/src/rpc_client/checked_amount/tests.rs @@ -1,4 +1,4 @@ -use crate::checked_amount::CheckedAmountOf; +use crate::rpc_client::checked_amount::CheckedAmountOf; mod checked_div_ceil { use super::Amount; From 671b94f8a86131e82941a412e2c3c7d893d8c456 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 13:37:07 +0200 Subject: [PATCH 12/42] 243: fix numeric --- src/rpc_client/numeric/mod.rs | 8 +------- src/rpc_client/numeric/tests.rs | 6 +++--- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/rpc_client/numeric/mod.rs b/src/rpc_client/numeric/mod.rs index 641f31ec..850ff587 100644 --- a/src/rpc_client/numeric/mod.rs +++ b/src/rpc_client/numeric/mod.rs @@ -3,8 +3,7 @@ #[cfg(test)] mod tests; -use crate::checked_amount::CheckedAmountOf; -use phantom_newtype::Id; +use crate::rpc_client::checked_amount::CheckedAmountOf; pub enum WeiTag {} pub type Wei = CheckedAmountOf; @@ -43,11 +42,6 @@ pub type GasAmount = CheckedAmountOf; pub enum EthLogIndexTag {} pub type LogIndex = CheckedAmountOf; -pub enum BurnIndexTag {} -pub type LedgerBurnIndex = Id; - -pub enum MintIndexTag {} -pub type LedgerMintIndex = Id; impl WeiPerGas { pub fn transaction_cost(self, gas: GasAmount) -> Option { diff --git a/src/rpc_client/numeric/tests.rs b/src/rpc_client/numeric/tests.rs index a380c3d5..64434f1a 100644 --- a/src/rpc_client/numeric/tests.rs +++ b/src/rpc_client/numeric/tests.rs @@ -1,5 +1,5 @@ mod transaction_nonce { - use crate::numeric::TransactionNonce; + use crate::rpc_client::numeric::TransactionNonce; use assert_matches::assert_matches; use candid::Nat; use num_bigint::BigUint; @@ -32,7 +32,7 @@ mod transaction_nonce { #[test] fn biguint_to_u256_conversion(value in any::()) { - use crate::numeric::Wei; + use crate::rpc_client::numeric::{Wei}; let nat_value: Nat = value.into(); let expected_wei_value = Wei::from(value); @@ -64,7 +64,7 @@ mod transaction_nonce { } mod wei { - use crate::numeric::{wei_from_milli_ether, Wei}; + use crate::rpc_client::numeric::{wei_from_milli_ether, Wei}; #[test] fn should_not_overflow_when_converting_from_milli_ether() { From e1753fb026c20ae080f0bbcc0f7dacc774d160e8 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 13:40:12 +0200 Subject: [PATCH 13/42] 243: copy eth_rpc tests --- src/rpc_client/{eth_rpc.rs => eth_rpc/mod.rs} | 69 --- src/rpc_client/eth_rpc/tests.rs | 502 ++++++++++++++++++ 2 files changed, 502 insertions(+), 69 deletions(-) rename src/rpc_client/{eth_rpc.rs => eth_rpc/mod.rs} (93%) create mode 100644 src/rpc_client/eth_rpc/tests.rs diff --git a/src/rpc_client/eth_rpc.rs b/src/rpc_client/eth_rpc/mod.rs similarity index 93% rename from src/rpc_client/eth_rpc.rs rename to src/rpc_client/eth_rpc/mod.rs index a225de72..d361db6c 100644 --- a/src/rpc_client/eth_rpc.rs +++ b/src/rpc_client/eth_rpc/mod.rs @@ -21,7 +21,6 @@ use ic_cdk::api::management_canister::http_request::{ }; use ic_cdk_macros::query; pub use metrics::encode as encode_metrics; -use minicbor::{Decode, Encode}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::fmt; use std::fmt::{Debug, Display, Formatter, LowerHex, UpperHex}; @@ -625,74 +624,6 @@ fn cleanup_response(mut args: TransformArgs) -> HttpResponse { args.response } -#[derive(Clone, Debug, PartialEq, Eq, CandidType, Deserialize)] -pub enum RpcError { - // #[error("RPC provider error")] - ProviderError(/* #[source] */ ProviderError), - // #[error("HTTPS outcall error")] - HttpOutcallError(/* #[source] */ HttpOutcallError), - // #[error("JSON-RPC error")] - JsonRpcError(/* #[source] */ JsonRpcError), - // #[error("data format error")] - ValidationError(/* #[source] */ ValidationError), -} - -impl From for RpcError { - fn from(err: ProviderError) -> Self { - RpcError::ProviderError(err) - } -} - -impl From for RpcError { - fn from(err: HttpOutcallError) -> Self { - RpcError::HttpOutcallError(err) - } -} - -impl From for RpcError { - fn from(err: JsonRpcError) -> Self { - RpcError::JsonRpcError(err) - } -} - -impl From for RpcError { - fn from(err: ValidationError) -> Self { - RpcError::ValidationError(err) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, CandidType, Deserialize)] -pub enum ProviderError { - // #[error("no permission")] - NoPermission, - // #[error("too few cycles (expected {expected}, received {received})")] - TooFewCycles { expected: u128, received: u128 }, - // #[error("provider not found")] - ProviderNotFound, - // #[error("missing required provider")] - MissingRequiredProvider, -} - -#[derive(Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, CandidType, Deserialize)] -pub enum HttpOutcallError { - /// Error from the IC system API. - // #[error("IC system error code {}: {message}", *.code as i32)] - IcError { - code: RejectionCode, - message: String, - }, - /// Response is not a valid JSON-RPC response, - /// which means that the response was not successful (status other than 2xx) - /// or that the response body could not be deserialized into a JSON-RPC response. - // #[error("invalid JSON-RPC response {status}: {})", .parsing_error.as_deref().unwrap_or(.body))] - InvalidHttpJsonRpcResponse { - status: u16, - body: String, - #[serde(rename = "parsingError")] - parsing_error: Option, - }, -} - impl HttpOutcallError { pub fn is_response_too_large(&self) -> bool { match self { diff --git a/src/rpc_client/eth_rpc/tests.rs b/src/rpc_client/eth_rpc/tests.rs new file mode 100644 index 00000000..771763c3 --- /dev/null +++ b/src/rpc_client/eth_rpc/tests.rs @@ -0,0 +1,502 @@ +use super::*; + +fn check_response_normalization(left: &str, right: &str) { + fn add_envelope(reply: &str) -> Vec { + format!("{{\"jsonrpc\": \"2.0\", \"id\": 1, \"result\": {}}}", reply).into_bytes() + } + + let mut left = add_envelope(left); + let mut right = add_envelope(right); + let maybe_transform = O::response_transform(); + if let Some(transform) = maybe_transform { + transform.apply(&mut left); + transform.apply(&mut right); + } + let left_string = String::from_utf8(left).unwrap(); + let right_string = String::from_utf8(right).unwrap(); + assert_eq!(left_string, right_string); +} + +#[test] +fn fee_history_normalization() { + check_response_normalization::( + r#"{ + "baseFeePerGas": [ + "0x729d3f3b3", + "0x766e503ea", + "0x75b51b620", + "0x74094f2b4", + "0x716724f03", + "0x73b467f76" + ], + "gasUsedRatio": [ + 0.6332004, + 0.47556506666666665, + 0.4432122666666667, + 0.4092196, + 0.5811903 + ], + "oldestBlock": "0x10f73fc", + "reward": [ + [ + "0x5f5e100", + "0x5f5e100", + "0x68e7780" + ], + [ + "0x55d4a80", + "0x5f5e100", + "0x5f5e100" + ], + [ + "0x5f5e100", + "0x5f5e100", + "0x5f5e100" + ], + [ + "0x5f5e100", + "0x5f5e100", + "0x5f5e100" + ], + [ + "0x5f5e100", + "0x5f5e100", + "0x180789e0" + ] + ] + }"#, + r#"{ + "gasUsedRatio": [ + 0.6332004, + 0.47556506666666665, + 0.4432122666666667, + 0.4092196, + 0.5811903 + ], + "baseFeePerGas": [ + "0x729d3f3b3", + "0x766e503ea", + "0x75b51b620", + "0x74094f2b4", + "0x716724f03", + "0x73b467f76" + ], + "reward": [ + [ + "0x5f5e100", + "0x5f5e100", + "0x68e7780" + ], + [ + "0x55d4a80", + "0x5f5e100", + "0x5f5e100" + ], + [ + "0x5f5e100", + "0x5f5e100", + "0x5f5e100" + ], + [ + "0x5f5e100", + "0x5f5e100", + "0x5f5e100" + ], + [ + "0x5f5e100", + "0x5f5e100", + "0x180789e0" + ] + ], + "oldestBlock": "0x10f73fc" + }"#, + ) +} + +#[test] +fn block_normalization() { + check_response_normalization::( + r#"{ + "number": "0x10eb3c6", + "hash": "0x85db6d6ad071d127795df4c5f1b04863629d7c2832c89550aa2771bf81c40c85", + "transactions": [ + "0x3829ea8f4312fc3c69fea37003cbe43f7745c616bc3fd5bff8fef99e35bad75b" + ], + "difficulty": "0x0", + "extraData": "0x6275696c64657230783639", + "gasLimit": "0x1c9c380", + "gasUsed": "0xd447a0", + "logsBloom": "0xcdb111024104125e7188052bbd09fb21d8b08419130094a16401d7a6b605df8060b5f29682d5e7b072303f06c3299750de01e29aea01e9b75e70c4cd752f6d60381244097518a92c5974c28b8389202aa12a738008641e05ed45d5f498668eb47a12ed8a2a62dd03a75d39f938e17c4fa3f7066c30001d45f20a3cdd008854222a3cff6e860cf993c26d9521834e77aea0c5209109435088ec85fd4703107cacfee407e909b1b1a72a1957d19b9e440484061401a11260ea906b9326ae5a92e8591e74b6008062532f8c842037b0ac8480e51222268d72d68efac0226815e0cc3f58600c3be8a0f80e853eefa3216baa850f779a99fc87d60421384150a3a483", + "miner": "0x690b9a9e9aa1c9db991c7721a92d351db4fac990", + "mixHash": "0x4dd122a99169327413ec6533fd70a9a9a9cbfad627d356d9b1dc67a47f61b936", + "nonce": "0x0000000000000000", + "parentHash": "0xeb080e615e8d1583a5e5cbe3daaed23cf408ae64da2c7352691e00b6e1ffdf89", + "receiptsRoot": "0xb07ebab433f52fd6dc24297a7804a40578ae0201060aa5938a5a57f4a3a05e03", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x29ade", + "stateRoot": "0x675aa943df0011c3b47038b8365db65ce2d41fcdf3e4bcfb2076f1dfd2dabca4", + "timestamp": "0x64ba5557", + "totalDifficulty": "0xc70d815d562d3cfa955", + "transactionsRoot": "0x42bdb666db19f89d6b6d16e125c49bd15143e062665e00287da5fda10e0d95c0", + "uncles": [], + "baseFeePerGas": "0x4b85a0fcd", + "withdrawalsRoot": "0xedaa8043cdce8101ef827863eb0d808277d200a7a0ee77961934bd235dcb82c6", + "withdrawals": [ + { + "address": "0x80b2886b8ef418cce2564ad16ffec4bfbff13787", + "amount": "0xdbdc02", + "index": "0xac512e", + "validatorIndex": "0x932ef" + } + ] + }"#, + r#"{ + "hash": "0x85db6d6ad071d127795df4c5f1b04863629d7c2832c89550aa2771bf81c40c85", + "number": "0x10eb3c6", + "transactions": [ + "0x3829ea8f4312fc3c69fea37003cbe43f7745c616bc3fd5bff8fef99e35bad75b" + ], + "extraData": "0x6275696c64657230783639", + "difficulty": "0x0", + "gasUsed": "0xd447a0", + "gasLimit": "0x1c9c380", + "logsBloom": "0xcdb111024104125e7188052bbd09fb21d8b08419130094a16401d7a6b605df8060b5f29682d5e7b072303f06c3299750de01e29aea01e9b75e70c4cd752f6d60381244097518a92c5974c28b8389202aa12a738008641e05ed45d5f498668eb47a12ed8a2a62dd03a75d39f938e17c4fa3f7066c30001d45f20a3cdd008854222a3cff6e860cf993c26d9521834e77aea0c5209109435088ec85fd4703107cacfee407e909b1b1a72a1957d19b9e440484061401a11260ea906b9326ae5a92e8591e74b6008062532f8c842037b0ac8480e51222268d72d68efac0226815e0cc3f58600c3be8a0f80e853eefa3216baa850f779a99fc87d60421384150a3a483", + "mixHash": "0x4dd122a99169327413ec6533fd70a9a9a9cbfad627d356d9b1dc67a47f61b936", + "miner": "0x690b9a9e9aa1c9db991c7721a92d351db4fac990", + "parentHash": "0xeb080e615e8d1583a5e5cbe3daaed23cf408ae64da2c7352691e00b6e1ffdf89", + "nonce": "0x0000000000000000", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "receiptsRoot": "0xb07ebab433f52fd6dc24297a7804a40578ae0201060aa5938a5a57f4a3a05e03", + "stateRoot": "0x675aa943df0011c3b47038b8365db65ce2d41fcdf3e4bcfb2076f1dfd2dabca4", + "size": "0x29ade", + "timestamp": "0x64ba5557", + "transactionsRoot": "0x42bdb666db19f89d6b6d16e125c49bd15143e062665e00287da5fda10e0d95c0", + "totalDifficulty": "0xc70d815d562d3cfa955", + "baseFeePerGas": "0x4b85a0fcd", + "uncles": [], + "withdrawals": [ + { + "address": "0x80b2886b8ef418cce2564ad16ffec4bfbff13787", + "amount": "0xdbdc02", + "index": "0xac512e", + "validatorIndex": "0x932ef" + } + ], + "withdrawalsRoot": "0xedaa8043cdce8101ef827863eb0d808277d200a7a0ee77961934bd235dcb82c6" + }"#, + ) +} + +#[test] +fn eth_get_logs_normalization() { + check_response_normalization::>( + r#"[ + { + "removed": false, + "blockHash": "0x8436209a391f7bc076123616ecb229602124eb6c1007f5eae84df8e098885d3c", + "blockNumber": "0x3ca487", + "data": "0x000000000000000000000000000000000000000000000000002386f26fc10000", + "logIndex": "0x27", + "address": "0xb44b5e756a894775fc32eddf3314bb1b1944dc34", + "topics": [ + "0x257e057bb61920d8d0ed2cb7b720ac7f9c513cd1110bc9fa543079154f45f435", + "0x000000000000000000000000dd2851cdd40ae6536831558dd46db62fac7a844d", + "0x09efcdab00000000000100000000000000000000000000000000000000000000" + ], + "transactionHash": "0x705f826861c802b407843e99af986cfde8749b669e5e0a5a150f4350bcaa9bc3", + "transactionIndex": "0x22" + }, + { + "transactionHash": "0xf1ac37d920fa57d9caeebc7136fea591191250309ffca95ae0e8a7739de89cc2", + "logIndex": "0x1d", + "blockHash": "0x4205f2436ee7a90aa87a88ae6914ec6860971360995f463602a40803bff98f4d", + "blockNumber": "0x3c6f2f", + "data": "0x000000000000000000000000000000000000000000000000002386f26fc10000", + "address": "0xb44b5e756a894775fc32eddf3314bb1b1944dc34", + "removed": false, + "topics": [ + "0x257e057bb61920d8d0ed2cb7b720ac7f9c513cd1110bc9fa543079154f45f435", + "0x000000000000000000000000dd2851cdd40ae6536831558dd46db62fac7a844d", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "transactionIndex": "0x1f" + } + ]"#, + r#"[ + { + "address": "0xb44b5e756a894775fc32eddf3314bb1b1944dc34", + "blockHash": "0x4205f2436ee7a90aa87a88ae6914ec6860971360995f463602a40803bff98f4d", + "blockNumber": "0x3c6f2f", + "data": "0x000000000000000000000000000000000000000000000000002386f26fc10000", + "logIndex": "0x1d", + "removed": false, + "topics": [ + "0x257e057bb61920d8d0ed2cb7b720ac7f9c513cd1110bc9fa543079154f45f435", + "0x000000000000000000000000dd2851cdd40ae6536831558dd46db62fac7a844d", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "transactionHash": "0xf1ac37d920fa57d9caeebc7136fea591191250309ffca95ae0e8a7739de89cc2", + "transactionIndex": "0x1f" + }, + { + "address": "0xb44b5e756a894775fc32eddf3314bb1b1944dc34", + "blockHash": "0x8436209a391f7bc076123616ecb229602124eb6c1007f5eae84df8e098885d3c", + "blockNumber": "0x3ca487", + "data": "0x000000000000000000000000000000000000000000000000002386f26fc10000", + "logIndex": "0x27", + "removed": false, + "topics": [ + "0x257e057bb61920d8d0ed2cb7b720ac7f9c513cd1110bc9fa543079154f45f435", + "0x000000000000000000000000dd2851cdd40ae6536831558dd46db62fac7a844d", + "0x09efcdab00000000000100000000000000000000000000000000000000000000" + ], + "transactionHash": "0x705f826861c802b407843e99af986cfde8749b669e5e0a5a150f4350bcaa9bc3", + "transactionIndex": "0x22" + } + ]"#, + ); +} + +#[test] +fn eth_get_logs_order_normalization() { + use ic_crypto_test_utils_reproducible_rng::reproducible_rng; + use rand::prelude::SliceRandom; + const LOGS: &str = r#"[ + { + "address": "0xb44b5e756a894775fc32eddf3314bb1b1944dc34", + "topics": [ + "0x257e057bb61920d8d0ed2cb7b720ac7f9c513cd1110bc9fa543079154f45f435", + "0x000000000000000000000000dd4f158536c7f8c07736d04b7cc805f8d59b241a", + "0x1d5ad0eae83b042ac243598bde6c4eea3e5dff125e2e2057476a3010e4020000" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x3f50c8", + "transactionHash": "0xcf36dcf9e4b42954b35e9b7deb20f3c1c481c3ec9d66b538a58b6838d5fdc0cc", + "transactionIndex": "0x5", + "blockHash": "0x9ee5966f424dcae471daa0d669354a5fc1b7387303b5a623e615d1bf1540e6ad", + "logIndex": "0x10", + "removed": false + }, + { + "address": "0xb44b5e756a894775fc32eddf3314bb1b1944dc34", + "topics": [ + "0x257e057bb61920d8d0ed2cb7b720ac7f9c513cd1110bc9fa543079154f45f435", + "0x000000000000000000000000dd4f158536c7f8c07736d04b7cc805f8d59b241a", + "0x1d882d15b09f8e81e29606305f5fefc5eff3e2309620a3557ecae39d62020000" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x3f50c8", + "transactionHash": "0xcf36dcf9e4b42954b35e9b7deb20f3c1c481c3ec9d66b538a58b6838d5fdc0cc", + "transactionIndex": "0x5", + "blockHash": "0x9ee5966f424dcae471daa0d669354a5fc1b7387303b5a623e615d1bf1540e6ad", + "logIndex": "0x11", + "removed": false + }, + { + "address": "0xb44b5e756a894775fc32eddf3314bb1b1944dc34", + "topics": [ + "0x257e057bb61920d8d0ed2cb7b720ac7f9c513cd1110bc9fa543079154f45f435", + "0x000000000000000000000000dd4f158536c7f8c07736d04b7cc805f8d59b241a", + "0x1d5ad0eae83b042ac243598bde6c4eea3e5dff125e2e2057476a3010e4020000" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x3f50f5", + "transactionHash": "0x17a1b0272352607d36cddf6ad8e5883ddef5ba8235a4cf6ed2b4924b8d702756", + "transactionIndex": "0xa", + "blockHash": "0xd9daa1bea7a56809fd88a6d14bdd21820a5e9565bf75f307c413bf47b86e649f", + "logIndex": "0x10", + "removed": false + }, + { + "address": "0xb44b5e756a894775fc32eddf3314bb1b1944dc34", + "topics": [ + "0x257e057bb61920d8d0ed2cb7b720ac7f9c513cd1110bc9fa543079154f45f435", + "0x000000000000000000000000dd4f158536c7f8c07736d04b7cc805f8d59b241a", + "0x1d882d15b09f8e81e29606305f5fefc5eff3e2309620a3557ecae39d62020000" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x3f50f5", + "transactionHash": "0x17a1b0272352607d36cddf6ad8e5883ddef5ba8235a4cf6ed2b4924b8d702756", + "transactionIndex": "0xa", + "blockHash": "0xd9daa1bea7a56809fd88a6d14bdd21820a5e9565bf75f307c413bf47b86e649f", + "logIndex": "0x11", + "removed": false + }, + { + "address": "0xb44b5e756a894775fc32eddf3314bb1b1944dc34", + "topics": [ + "0x257e057bb61920d8d0ed2cb7b720ac7f9c513cd1110bc9fa543079154f45f435", + "0x000000000000000000000000dd4f158536c7f8c07736d04b7cc805f8d59b241a", + "0x1d5ad0eae83b042ac243598bde6c4eea3e5dff125e2e2057476a3010e4020000" + ], + "data": "0x000000000000000000000000000000000000000000000000002386f26fc10000", + "blockNumber": "0x3f50f5", + "transactionHash": "0x19a258899d3943d114f62d749521d1a3d3d88f0c9b3e2b45b9cd0c4c66fcda68", + "transactionIndex": "0xc", + "blockHash": "0xd9daa1bea7a56809fd88a6d14bdd21820a5e9565bf75f307c413bf47b86e649f", + "logIndex": "0x13", + "removed": false + }, + { + "address": "0xb44b5e756a894775fc32eddf3314bb1b1944dc34", + "topics": [ + "0x257e057bb61920d8d0ed2cb7b720ac7f9c513cd1110bc9fa543079154f45f435", + "0x000000000000000000000000dd4f158536c7f8c07736d04b7cc805f8d59b241a", + "0x1d882d15b09f8e81e29606305f5fefc5eff3e2309620a3557ecae39d62020000" + ], + "data": "0x000000000000000000000000000000000000000000000000002386f26fc10000", + "blockNumber": "0x3f50f5", + "transactionHash": "0x19a258899d3943d114f62d749521d1a3d3d88f0c9b3e2b45b9cd0c4c66fcda68", + "transactionIndex": "0xc", + "blockHash": "0xd9daa1bea7a56809fd88a6d14bdd21820a5e9565bf75f307c413bf47b86e649f", + "logIndex": "0x14", + "removed": false + } + ]"#; + let rng = &mut reproducible_rng(); + let original_logs: Vec = serde_json::from_str(LOGS).unwrap(); + assert!(original_logs.len() > 1); + let suffled_logs = { + let mut logs = original_logs.clone(); + logs.shuffle(rng); + logs + }; + + check_response_normalization::>( + &serde_json::to_string(&original_logs).unwrap(), + &serde_json::to_string(&suffled_logs).unwrap(), + ) +} + +#[test] +fn transaction_receipt_normalization() { + check_response_normalization::( + r#"{ + "type": "0x2", + "blockHash": "0x82005d2f17b251900968f01b0ed482cb49b7e1d797342bc504904d442b64dbe4", + "transactionHash": "0x0e59bd032b9b22aca5e2784e4cf114783512db00988c716cf17a1cc755a0a93d", + "logs": [], + "contractAddress": null, + "effectiveGasPrice": "0xfefbee3e", + "cumulativeGasUsed": "0x8b2e10", + "from": "0x1789f79e95324a47c5fd6693071188e82e9a3558", + "gasUsed": "0x5208", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "to": "0xdd2851cdd40ae6536831558dd46db62fac7a844d", + "transactionIndex": "0x32", + "blockNumber": "0x4132ec" + }"#, + r#"{ + "transactionHash": "0x0e59bd032b9b22aca5e2784e4cf114783512db00988c716cf17a1cc755a0a93d", + "blockHash": "0x82005d2f17b251900968f01b0ed482cb49b7e1d797342bc504904d442b64dbe4", + "blockNumber": "0x4132ec", + "logs": [], + "contractAddress": null, + "effectiveGasPrice": "0xfefbee3e", + "cumulativeGasUsed": "0x8b2e10", + "from": "0x1789f79e95324a47c5fd6693071188e82e9a3558", + "gasUsed": "0x5208", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "to": "0xdd2851cdd40ae6536831558dd46db62fac7a844d", + "transactionIndex": "0x32", + "type": "0x2" + }"#, + ); +} + +#[test] +fn http_metrics_should_aggregate_retry_counts() { + use super::metrics::HttpMetrics; + + let mut metrics = HttpMetrics::default(); + + for count in [0, 1, 2, 3, 0, 0, 2, 5, 100, 200, 300] { + metrics.observe_retry_count("eth_test".to_string(), count); + } + + for count in [0, 1, 2, 3] { + metrics.observe_retry_count("eth_test2".to_string(), count); + } + + assert_eq!(3, metrics.count_retries_in_bucket("eth_test", 0)); + assert_eq!(1, metrics.count_retries_in_bucket("eth_test", 1)); + assert_eq!(2, metrics.count_retries_in_bucket("eth_test", 2)); + assert_eq!(1, metrics.count_retries_in_bucket("eth_test", 3)); + assert_eq!(0, metrics.count_retries_in_bucket("eth_test", 4)); + assert_eq!(1, metrics.count_retries_in_bucket("eth_test", 5)); + assert_eq!(1, metrics.count_retries_in_bucket("eth_test", 5)); + assert_eq!(3, metrics.count_retries_in_bucket("eth_test", 100)); + assert_eq!(3, metrics.count_retries_in_bucket("eth_test", 200)); + assert_eq!(3, metrics.count_retries_in_bucket("eth_test", 300)); + assert_eq!(3, metrics.count_retries_in_bucket("eth_test", usize::MAX)); + + assert_eq!(1, metrics.count_retries_in_bucket("eth_test2", 0)); + assert_eq!(1, metrics.count_retries_in_bucket("eth_test2", 1)); + assert_eq!(1, metrics.count_retries_in_bucket("eth_test2", 2)); + assert_eq!(1, metrics.count_retries_in_bucket("eth_test2", 3)); + assert_eq!(0, metrics.count_retries_in_bucket("eth_test2", 100)); + + assert_eq!(0, metrics.count_retries_in_bucket("eth_unknown", 0)); + + let mut encoder = ic_metrics_encoder::MetricsEncoder::new(Vec::new(), 12346789); + metrics.encode(&mut encoder).unwrap(); + let bytes = encoder.into_inner(); + let metrics_text = String::from_utf8(bytes).unwrap(); + + assert_eq!( + metrics_text.trim(), + r#" +# HELP cketh_eth_rpc_call_retry_count The number of ETH RPC call retries by method. +# TYPE cketh_eth_rpc_call_retry_count histogram +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="0"} 3 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="1"} 4 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="2"} 6 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="3"} 7 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="4"} 7 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="5"} 8 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="6"} 8 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="7"} 8 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="8"} 8 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="9"} 8 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="10"} 8 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="11"} 8 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="12"} 8 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="13"} 8 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="14"} 8 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="15"} 8 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="16"} 8 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="17"} 8 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="18"} 8 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="19"} 8 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test",le="+Inf"} 11 12346789 +cketh_eth_rpc_call_retry_count_sum{method="eth_test"} 613 12346789 +cketh_eth_rpc_call_retry_count_count{method="eth_test"} 11 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="0"} 1 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="1"} 2 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="2"} 3 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="3"} 4 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="4"} 4 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="5"} 4 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="6"} 4 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="7"} 4 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="8"} 4 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="9"} 4 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="10"} 4 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="11"} 4 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="12"} 4 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="13"} 4 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="14"} 4 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="15"} 4 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="16"} 4 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="17"} 4 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="18"} 4 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="19"} 4 12346789 +cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="+Inf"} 4 12346789 +cketh_eth_rpc_call_retry_count_sum{method="eth_test2"} 6 12346789 +cketh_eth_rpc_call_retry_count_count{method="eth_test2"} 4 12346789 +"# + .trim() + ); +} From 7f69fe6b17ee4ee7a9eaac5493c01a7d0fdb3b55 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 14:08:09 +0200 Subject: [PATCH 14/42] 243: fix requests --- Cargo.toml | 2 ++ src/rpc_client/requests.rs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d0a98597..3e4db6bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,8 @@ evm_rpc_types = { path = "evm_rpc_types" } futures = { workspace = true } getrandom = { workspace = true } ic-canisters-http-types = { workspace = true } +ic-crypto-sha3 = { git = "https://github.com/dfinity/ic", rev = "release-2024-09-12_01-30-base" } +ic-ethereum-types = { git = "https://github.com/dfinity/ic", rev = "release-2024-09-12_01-30-base" } ic-nervous-system-common = { workspace = true } ic-metrics-encoder = { workspace = true } ic-stable-structures = { workspace = true } diff --git a/src/rpc_client/requests.rs b/src/rpc_client/requests.rs index 6777a3fb..2d8ea399 100644 --- a/src/rpc_client/requests.rs +++ b/src/rpc_client/requests.rs @@ -1,5 +1,5 @@ -use crate::address::Address; -use crate::eth_rpc::BlockSpec; +use crate::rpc_client::eth_rpc::BlockSpec; +use ic_ethereum_types::Address; use serde::Serialize; /// Parameters of the [`eth_getTransactionCount`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactioncount) call. From 317915c2557a29cb3965f7db34a78edd1a901b60 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 14:09:56 +0200 Subject: [PATCH 15/42] 243: fix response --- src/rpc_client/responses.rs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/rpc_client/responses.rs b/src/rpc_client/responses.rs index 406f5239..8fbde629 100644 --- a/src/rpc_client/responses.rs +++ b/src/rpc_client/responses.rs @@ -1,7 +1,6 @@ -use crate::checked_amount::CheckedAmountOf; -use crate::eth_rpc::{Hash, HttpResponsePayload, LogEntry, ResponseTransform}; -use crate::numeric::{BlockNumber, GasAmount, Wei, WeiPerGas}; -use minicbor::{Decode, Encode}; +use crate::rpc_client::checked_amount::CheckedAmountOf; +use crate::rpc_client::eth_rpc::{Hash, HttpResponsePayload, LogEntry, ResponseTransform}; +use crate::rpc_client::numeric::{BlockNumber, GasAmount, WeiPerGas}; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; @@ -43,21 +42,13 @@ pub struct TransactionReceipt { pub r#type: String, } -impl TransactionReceipt { - pub fn effective_transaction_fee(&self) -> Wei { - self.effective_gas_price - .transaction_cost(self.gas_used) - .expect("ERROR: overflow during transaction fee calculation") - } -} - impl HttpResponsePayload for TransactionReceipt { fn response_transform() -> Option { Some(ResponseTransform::TransactionReceipt) } } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, )] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)] #[serde(try_from = "ethnum::u256", into = "ethnum::u256")] pub enum TransactionStatus { /// Transaction was mined and executed successfully. From 821599b43b60196b6db5e6ccdb5d9d9153c1c252 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 14:16:56 +0200 Subject: [PATCH 16/42] 243: remove reduce_with_min_by_key --- src/rpc_client/mod.rs | 22 ++++++++-------------- src/rpc_client/tests.rs | 37 ------------------------------------- 2 files changed, 8 insertions(+), 51 deletions(-) diff --git a/src/rpc_client/mod.rs b/src/rpc_client/mod.rs index d2d95d5e..e06df5fb 100644 --- a/src/rpc_client/mod.rs +++ b/src/rpc_client/mod.rs @@ -1,8 +1,9 @@ use crate::rpc_client::eth_rpc::{ are_errors_consistent, Block, BlockSpec, FeeHistory, FeeHistoryParams, GetBlockByNumberParams, - GetLogsParam, Hash, HttpResponsePayload, JsonRpcError, LogEntry, ResponseSizeEstimate, + GetLogsParam, Hash, HttpResponsePayload, LogEntry, ResponseSizeEstimate, SendRawTransactionResult, HEADER_SIZE_LIMIT, }; +use crate::rpc_client::numeric::TransactionCount; use crate::rpc_client::providers::{ ARBITRUM_PROVIDERS, BASE_PROVIDERS, MAINNET_PROVIDERS, OPTIMISM_PROVIDERS, SEPOLIA_PROVIDERS, UNKNOWN_PROVIDERS, @@ -11,14 +12,18 @@ use crate::rpc_client::requests::GetTransactionCountParams; use crate::rpc_client::responses::TransactionReceipt; use async_trait::async_trait; use candid::CandidType; -use evm_rpc_types::{HttpOutcallError, ProviderError, RpcApi, RpcConfig, RpcError, RpcService}; +use evm_rpc_types::{ + HttpOutcallError, JsonRpcError, ProviderError, RpcApi, RpcConfig, RpcError, RpcService, +}; use ic_cdk::api::management_canister::http_request::{CanisterHttpRequestArgument, HttpResponse}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::collections::BTreeMap; use std::fmt::Debug; use std::marker::PhantomData; +pub mod checked_amount; mod eth_rpc; +mod numeric; mod providers; mod requests; mod responses; @@ -27,6 +32,7 @@ mod responses; mod tests; //TODO: Dummy log. use ic_canister_log::log +#[macro_export] macro_rules! log { ($sink:expr, $message:expr $(,$args:expr)* $(,)*) => {{ let message = std::format!($message $(,$args)*); @@ -433,18 +439,6 @@ impl MultiCallResults { Ok(base_result) } - pub fn reduce_with_min_by_key K, K: Ord>( - self, - extractor: F, - ) -> Result> { - let min = self - .all_ok()? - .into_values() - .min_by_key(extractor) - .expect("BUG: MultiCallResults is guaranteed to be non-empty"); - Ok(min) - } - pub fn reduce_with_strict_majority_by_key K, K: Ord>( self, extractor: F, diff --git a/src/rpc_client/tests.rs b/src/rpc_client/tests.rs index 36bebe87..80fc2cca 100644 --- a/src/rpc_client/tests.rs +++ b/src/rpc_client/tests.rs @@ -189,43 +189,6 @@ mod multi_call_results { } } - mod reduce_with_min_by_key { - use crate::eth_rpc::{Block, JsonRpcResult}; - use crate::eth_rpc_client::tests::multi_call_results::{ANKR, PUBLIC_NODE}; - use crate::eth_rpc_client::MultiCallResults; - use crate::numeric::{BlockNumber, Wei}; - - #[test] - fn should_get_minimum_block_number() { - let results: MultiCallResults = MultiCallResults::from_non_empty_iter(vec![ - ( - ANKR, - Ok(JsonRpcResult::Result(Block { - number: BlockNumber::new(0x411cda), - base_fee_per_gas: Wei::new(0x10), - })), - ), - ( - PUBLIC_NODE, - Ok(JsonRpcResult::Result(Block { - number: BlockNumber::new(0x411cd9), - base_fee_per_gas: Wei::new(0x10), - })), - ), - ]); - - let reduced = results.reduce_with_min_by_key(|block| block.number); - - assert_eq!( - reduced, - Ok(Block { - number: BlockNumber::new(0x411cd9), - base_fee_per_gas: Wei::new(0x10), - }) - ); - } - } - mod reduce_with_stable_majority_by_key { use crate::eth_rpc::{FeeHistory, JsonRpcResult}; use crate::eth_rpc_client::tests::multi_call_results::{ANKR, CLOUDFLARE, PUBLIC_NODE}; From 8a2fac86d7df5743b55d7a6bddd26a3d6103e726 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 14:22:28 +0200 Subject: [PATCH 17/42] 243: copy eth_rpc_error --- src/rpc_client/eth_rpc_error/mod.rs | 247 ++++++++++++++++++++++++++ src/rpc_client/eth_rpc_error/tests.rs | 65 +++++++ 2 files changed, 312 insertions(+) create mode 100644 src/rpc_client/eth_rpc_error/mod.rs create mode 100644 src/rpc_client/eth_rpc_error/tests.rs diff --git a/src/rpc_client/eth_rpc_error/mod.rs b/src/rpc_client/eth_rpc_error/mod.rs new file mode 100644 index 00000000..d035a019 --- /dev/null +++ b/src/rpc_client/eth_rpc_error/mod.rs @@ -0,0 +1,247 @@ +use crate::eth_rpc::{Hash, JsonRpcReply, JsonRpcResult, SendRawTransactionResult}; +use crate::logs::DEBUG; +use ic_canister_log::log; + +#[cfg(test)] +mod tests; + +/// Possible errors returned by calling `eth_sendRawTransaction` endpoint. +/// Unfortunately, error codes and error messages are not standardized in +/// [Ethereum JSON-RPC specification](https://ethereum.github.io/execution-apis/api-documentation/). +/// +/// Note that `eth_sendRawTransaction` endpoint is not idempotent, +/// meaning that when called via HTTP outcalls it's expected that one node will receive +/// a successful answer and other nodes will receive an error but we still need the consensus +/// result to indicate whether the transaction was sent to the network or not. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SendRawTransactionError { + /// The transaction is known to the mempool and may indicate + /// that the transaction was correctly sent to the network. + AlreadyKnown, + /// The total cost of executing a transaction is higher than the balance of the user's account + /// (determined by retrieving the public key from the signed transaction). + InsufficientFunds, + /// If the nonce of a transaction is lower than the one present in the local chain. + NonceTooLow, + /// if the nonce of a transaction is higher than the next one expected based on the local chain. + NonceTooHigh, +} + +impl From for JsonRpcResult { + fn from(value: SendRawTransactionError) -> Self { + match value { + SendRawTransactionError::AlreadyKnown => JsonRpcResult::Error { + code: -32_000, + message: "Transaction already known".to_string(), + }, + SendRawTransactionError::InsufficientFunds => JsonRpcResult::Error { + code: -32_000, + message: "insufficient funds for gas * price + value".to_string(), + }, + SendRawTransactionError::NonceTooLow => JsonRpcResult::Error { + code: -32_000, + message: "nonce too low".to_string(), + }, + SendRawTransactionError::NonceTooHigh => JsonRpcResult::Error { + code: -32_000, + message: "nonce too high".to_string(), + }, + } + } +} + +pub trait ErrorParser { + fn try_parse_send_raw_transaction_error( + &self, + code: i64, + message: String, + ) -> Option; +} + +struct GoEthereumParser; +// https://github.com/ethereum/go-ethereum/blob/5976e58415a633c24a0d903e8a60a3780abdfe59/core/txpool/errors.go#L24 +impl ErrorParser for GoEthereumParser { + fn try_parse_send_raw_transaction_error( + &self, + code: i64, + message: String, + ) -> Option { + match (code, message.to_lowercase()) { + (-32_000, msg) if msg.contains("already known") => { + Some(SendRawTransactionError::AlreadyKnown) + } + (-32_000, msg) if msg.contains("insufficient funds") => { + Some(SendRawTransactionError::InsufficientFunds) + } + (-32_000, msg) if msg.contains("nonce too low") => { + Some(SendRawTransactionError::NonceTooLow) + } + (-32_000, msg) if msg.contains("nonce too high") => { + Some(SendRawTransactionError::NonceTooHigh) + } + _ => None, + } + } +} + +struct NethermindParser; +//https://github.com/NethermindEth/nethermind/blob/ac86855116c652a68443b52c6377b3a55e9b8af5/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs#L21 +//https://github.com/NethermindEth/nethermind/blob/09bd1aebee402c682a3ce46ae7137cb0e2988a5e/src/Nethermind/Nethermind.JsonRpc/ErrorType.cs#L53 +impl ErrorParser for NethermindParser { + fn try_parse_send_raw_transaction_error( + &self, + code: i64, + message: String, + ) -> Option { + match (code, message.to_lowercase()) { + (-32_010, msg) if msg.contains("AlreadyKnown") => { + Some(SendRawTransactionError::AlreadyKnown) + } + (-32_010, msg) if msg.contains("InsufficientFunds") => { + Some(SendRawTransactionError::InsufficientFunds) + } + (-32_010, msg) if msg.contains("OldNonce") => { + Some(SendRawTransactionError::NonceTooLow) + } + (-32_010, msg) if msg.contains("NonceGap") => { + Some(SendRawTransactionError::NonceTooHigh) + } + _ => None, + } + } +} + +struct ErigonParser; +//https://github.com/ledgerwatch/erigon-lib/blob/3aa5249d48c1dacd95462c2653fd48179898db6f/types/txn.go#L123 +//https://github.com/ledgerwatch/erigon-lib/blob/3aa5249d48c1dacd95462c2653fd48179898db6f/txpool/txpoolcfg/txpoolcfg.go#L96 +impl ErrorParser for ErigonParser { + fn try_parse_send_raw_transaction_error( + &self, + code: i64, + message: String, + ) -> Option { + match (code, message.to_lowercase()) { + (-32_000, msg) if msg.contains("already known") => { + Some(SendRawTransactionError::AlreadyKnown) + } + (-32_000, msg) if msg.contains("insufficient funds") => { + Some(SendRawTransactionError::InsufficientFunds) + } + (-32_000, msg) if msg.contains("nonce too low") => { + Some(SendRawTransactionError::NonceTooLow) + } + //no NonceTooHigh in Erigon + _ => None, + } + } +} + +struct BesuParser; +//https://github.com/hyperledger/besu/blob/92a3c5b139bf57d1521c8f8ec623934c50430353/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/RpcErrorType.java +//https://github.com/hyperledger/besu/blob/92a3c5b139bf57d1521c8f8ec623934c50430353/evm/src/main/java/org/hyperledger/besu/evm/frame/ExceptionalHaltReason.java#L77 +impl ErrorParser for BesuParser { + fn try_parse_send_raw_transaction_error( + &self, + code: i64, + message: String, + ) -> Option { + match (code, message.to_lowercase()) { + (-32_000, msg) if msg.contains("known transaction") => { + Some(SendRawTransactionError::AlreadyKnown) + } + (-32_000, msg) if msg.contains("out of gas") => { + Some(SendRawTransactionError::InsufficientFunds) + } + (-32_001, msg) if msg.contains("nonce too low") => { + Some(SendRawTransactionError::NonceTooLow) + } + (-32_006, msg) if msg.contains("nonce too high") => { + Some(SendRawTransactionError::NonceTooHigh) + } + _ => None, + } + } +} + +pub struct Parser { + parsers: Vec>, +} + +impl Parser { + pub fn new() -> Self { + Self { + parsers: vec![ + Box::new(GoEthereumParser), + Box::new(NethermindParser), + Box::new(ErigonParser), + Box::new(BesuParser), + ], + } + } +} + +impl Default for Parser { + fn default() -> Self { + Self::new() + } +} + +impl ErrorParser for Parser { + fn try_parse_send_raw_transaction_error( + &self, + code: i64, + message: String, + ) -> Option { + self.parsers + .iter() + .find_map(|parser| parser.try_parse_send_raw_transaction_error(code, message.clone())) + } +} + +/// Sanitizes the response of `eth_sendRawTransaction` to hide implementation details of the various Ethereum clients +/// queried by HTTP outcalls and the fact that `eth_sendRawTransaction` is not idempotent. +/// The type `JsonRpcReply` of the original response is transformed into `JsonRpcReply`. +pub fn sanitize_send_raw_transaction_result(body_bytes: &mut Vec, parser: T) { + let response: JsonRpcReply = match serde_json::from_slice(body_bytes) { + Ok(response) => response, + Err(e) => { + log!(DEBUG, "Error deserializing: {:?}", e); + return; + } + }; + + let sanitized_result = match response.result { + JsonRpcResult::Result(_) => JsonRpcResult::Result(SendRawTransactionResult::Ok), + JsonRpcResult::Error { code, message } => { + if let Some(error) = parser.try_parse_send_raw_transaction_error(code, message.clone()) + { + match error { + //transaction already in the mempool, so it was sent successfully + SendRawTransactionError::AlreadyKnown => { + JsonRpcResult::Result(SendRawTransactionResult::Ok) + } + SendRawTransactionError::InsufficientFunds => { + JsonRpcResult::Result(SendRawTransactionResult::InsufficientFunds) + } + SendRawTransactionError::NonceTooLow => { + JsonRpcResult::Result(SendRawTransactionResult::NonceTooLow) + } + SendRawTransactionError::NonceTooHigh => { + JsonRpcResult::Result(SendRawTransactionResult::NonceTooHigh) + } + } + } else { + JsonRpcResult::Error { code, message } + } + } + }; + let sanitized_reply: JsonRpcReply = JsonRpcReply { + id: response.id, + jsonrpc: response.jsonrpc, + result: sanitized_result, + }; + + *body_bytes = serde_json::to_string(&sanitized_reply) + .expect("BUG: failed to serialize error response") + .into_bytes(); +} diff --git a/src/rpc_client/eth_rpc_error/tests.rs b/src/rpc_client/eth_rpc_error/tests.rs new file mode 100644 index 00000000..665c685d --- /dev/null +++ b/src/rpc_client/eth_rpc_error/tests.rs @@ -0,0 +1,65 @@ +use crate::eth_rpc_error::{sanitize_send_raw_transaction_result, Parser}; + +#[test] +fn should_sanitize_ok_response() { + let mut raw_response = + br#"{"id":1,"jsonrpc":"2.0","result":"0xcfa48c44dc89d18a898a42b4a5b02b6847a3c2019507d5571a481751c7a2f353"}"#.to_vec(); + check_sanitize_send_raw_transaction_result(&mut raw_response, sanitized_ok_response()); +} + +#[test] +fn should_ignore_already_known_error() { + let mut raw_response = + br#"{"jsonrpc": "2.0", "error": {"code": -32000, "message": "ALREADY_EXISTS: already known"}, "id": 1}"#.to_vec(); + check_sanitize_send_raw_transaction_result(&mut raw_response, sanitized_ok_response()); +} + +#[test] +fn should_sanitize_insufficient_funds_error() { + let mut raw_response = + br#"{"jsonrpc": "2.0", "error": {"code": -32000, "message": "out of gas"}, "id": 1}"# + .to_vec(); + let sanitized_error = br#"{"id":1,"jsonrpc":"2.0","result":"InsufficientFunds"}"#.to_vec(); + check_sanitize_send_raw_transaction_result(&mut raw_response, sanitized_error); +} + +#[test] +fn should_keep_unknown_error_and_normalize_response() { + let mut raw_response = + br#"{"jsonrpc": "2.0", "error": {"code": -32000, "message": "weird unknown error"}, "id": 1}"# + .to_vec(); + let mut other_raw_response = br#"{"error": {"code": -32000, "message": "weird unknown error"}, "jsonrpc": "2.0", "id": 1}"# + .to_vec(); + let expected_response = + br#"{"id":1,"jsonrpc":"2.0","error":{"code":-32000,"message":"weird unknown error"}}"# + .to_vec(); + + check_sanitize_send_raw_transaction_result(&mut raw_response, &expected_response); + check_sanitize_send_raw_transaction_result(&mut other_raw_response, &expected_response); +} + +#[test] +fn should_not_modify_response_when_deserialization_fails() { + let mut raw_response = br#"{"jsonrpc": "2.0", "unexpected": "value", "id": 1}"#.to_vec(); + let unmodified_response = raw_response.clone(); + check_sanitize_send_raw_transaction_result(&mut raw_response, unmodified_response); +} + +fn check_sanitize_send_raw_transaction_result>( + raw_response: &mut Vec, + expected: T, +) { + sanitize_send_raw_transaction_result(raw_response, Parser::new()); + let expected_bytes = expected.as_ref(); + assert_eq!( + raw_response, + expected_bytes, + "{:?} != {:?}", + String::from_utf8_lossy(raw_response), + String::from_utf8_lossy(expected_bytes) + ); +} + +fn sanitized_ok_response() -> Vec { + br#"{"id":1,"jsonrpc":"2.0","result":"Ok"}"#.to_vec() +} From 5b97b19a7a4d94a8d6857595627b329102d0b660 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 14:23:41 +0200 Subject: [PATCH 18/42] 243: fix eth_rpc_error --- src/rpc_client/eth_rpc_error/mod.rs | 5 ++--- src/rpc_client/eth_rpc_error/tests.rs | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/rpc_client/eth_rpc_error/mod.rs b/src/rpc_client/eth_rpc_error/mod.rs index d035a019..18168cbe 100644 --- a/src/rpc_client/eth_rpc_error/mod.rs +++ b/src/rpc_client/eth_rpc_error/mod.rs @@ -1,6 +1,5 @@ -use crate::eth_rpc::{Hash, JsonRpcReply, JsonRpcResult, SendRawTransactionResult}; -use crate::logs::DEBUG; -use ic_canister_log::log; +use crate::log; +use crate::rpc_client::eth_rpc::{Hash, JsonRpcReply, JsonRpcResult, SendRawTransactionResult}; #[cfg(test)] mod tests; diff --git a/src/rpc_client/eth_rpc_error/tests.rs b/src/rpc_client/eth_rpc_error/tests.rs index 665c685d..e6e8fa4e 100644 --- a/src/rpc_client/eth_rpc_error/tests.rs +++ b/src/rpc_client/eth_rpc_error/tests.rs @@ -1,4 +1,4 @@ -use crate::eth_rpc_error::{sanitize_send_raw_transaction_result, Parser}; +use crate::rpc_client::eth_rpc_error::{sanitize_send_raw_transaction_result, Parser}; #[test] fn should_sanitize_ok_response() { From cb3484c4e24e5319b1a1bd2abf5abbf1fa9797eb Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 14:44:52 +0200 Subject: [PATCH 19/42] 243: fix eth_rpc --- Cargo.toml | 4 + src/memory.rs | 11 +++ src/rpc_client/eth_rpc/mod.rs | 152 ++++++++++++---------------------- 3 files changed, 67 insertions(+), 100 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3e4db6bf..da075687 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,8 @@ ic-cdk-macros = { workspace = true } ic-certified-map = { workspace = true } cketh-common = { git = "https://github.com/dfinity/ic", rev = "1f551bea63354370b6e7a5037e96d464bdab3b41", package = "ic-cketh-minter" } maplit = "1.0" +minicbor = { workspace = true } +minicbor-derive = { workspace = true } num = "0.4" num-bigint = { workspace = true } num-traits = "0.2" @@ -70,6 +72,8 @@ ic-certified-map = "0.4" ic-nervous-system-common = { git = "https://github.com/dfinity/ic", rev = "release-2023-09-27_23-01" } ic-metrics-encoder = "1.1" ic-stable-structures = "0.5" +minicbor = { version = "0.19.1", features = ["alloc", "derive"] } +minicbor-derive = "0.13.0" num-bigint = "0.4.6" proptest = "1.5.0" serde = "1.0" diff --git a/src/memory.rs b/src/memory.rs index 457d07ab..96472dc1 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -18,6 +18,7 @@ type StableMemory = VirtualMemory; thread_local! { // Unstable static data: these are reset when the canister is upgraded. pub static UNSTABLE_METRICS: RefCell = RefCell::new(Metrics::default()); + static UNSTABLE_HTTP_REQUEST_COUNTER: RefCell = RefCell::new(0); // Stable static data: these are preserved when the canister is upgraded. static MEMORY_MANAGER: RefCell> = @@ -74,6 +75,16 @@ pub fn set_demo_active(is_active: bool) { }); } +pub fn next_request_id() -> u64 { + UNSTABLE_HTTP_REQUEST_COUNTER.with_borrow_mut(|counter| { + let current_request_id = *counter; + // overflow is not an issue here because we only use `next_request_id` to correlate + // requests and responses in logs. + *counter = counter.wrapping_add(1); + current_request_id + }) +} + #[cfg(test)] mod test { use candid::Principal; diff --git a/src/rpc_client/eth_rpc/mod.rs b/src/rpc_client/eth_rpc/mod.rs index d361db6c..173a1c4e 100644 --- a/src/rpc_client/eth_rpc/mod.rs +++ b/src/rpc_client/eth_rpc/mod.rs @@ -1,30 +1,28 @@ //! 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::address::Address; -use crate::checked_amount::CheckedAmountOf; -use crate::endpoints::CandidBlockTag; -use crate::eth_rpc_client::providers::RpcService; -use crate::eth_rpc_client::responses::TransactionReceipt; -use crate::eth_rpc_client::RpcTransport; -use crate::eth_rpc_error::{sanitize_send_raw_transaction_result, Parser}; -use crate::logs::{DEBUG, TRACE_HTTP}; -use crate::numeric::{BlockNumber, LogIndex, TransactionCount, Wei, WeiPerGas}; -use crate::state::{mutate_state, State}; +use crate::log; +use crate::memory::next_request_id; +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 candid::{candid_method, CandidType}; use ethnum; -use ic_canister_log::log; +use evm_rpc_types::{HttpOutcallError, JsonRpcError, RpcError, RpcService}; use ic_cdk::api::call::RejectionCode; use ic_cdk::api::management_canister::http_request::{ CanisterHttpRequestArgument, HttpHeader, HttpMethod, HttpResponse, TransformArgs, TransformContext, }; use ic_cdk_macros::query; +use ic_ethereum_types::Address; pub use metrics::encode as encode_metrics; +use minicbor::{Decode, Encode}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::fmt; use std::fmt::{Debug, Display, Formatter, LowerHex, UpperHex}; -use thiserror::Error; #[cfg(test)] mod tests; @@ -53,19 +51,6 @@ pub fn into_nat(quantity: Quantity) -> candid::Nat { #[serde(transparent)] pub struct Data(#[serde(with = "crate::serde_data")] pub Vec); -impl CandidType for Data { - fn _ty() -> candid::types::Type { - String::_ty() - } - - fn idl_serialize(&self, serializer: S) -> Result<(), S::Error> - where - S: candid::types::Serializer, - { - serializer.serialize_text(&format!("0x{}", hex::encode(self))) - } -} - impl AsRef<[u8]> for Data { fn as_ref(&self) -> &[u8] { &self.0 @@ -76,19 +61,6 @@ impl AsRef<[u8]> for Data { #[serde(transparent)] pub struct FixedSizeData(#[serde(with = "crate::serde_data")] pub [u8; 32]); -impl CandidType for FixedSizeData { - fn _ty() -> candid::types::Type { - String::_ty() - } - - fn idl_serialize(&self, serializer: S) -> Result<(), S::Error> - where - S: candid::types::Serializer, - { - serializer.serialize_text(&format!("{:#x}", self)) - } -} - impl AsRef<[u8]> for FixedSizeData { fn as_ref(&self) -> &[u8] { &self.0 @@ -133,7 +105,7 @@ impl UpperHex for FixedSizeData { } } -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, CandidType)] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] pub enum SendRawTransactionResult { Ok, InsufficientFunds, @@ -147,11 +119,7 @@ impl HttpResponsePayload for SendRawTransactionResult { } } -#[derive( - Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, Ord, PartialOrd, Encode, Decode, -)] -#[serde(transparent)] -#[cbor(transparent)] +#[derive(Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, Ord, PartialOrd)] pub struct Hash( #[serde(with = "crate::serde_data")] #[cbor(n(0), with = "minicbor::bytes")] @@ -235,16 +203,6 @@ pub enum BlockTag { Pending, } -impl From for BlockTag { - fn from(block_tag: CandidBlockTag) -> BlockTag { - match block_tag { - CandidBlockTag::Latest => BlockTag::Latest, - CandidBlockTag::Safe => BlockTag::Safe, - CandidBlockTag::Finalized => BlockTag::Finalized, - } - } -} - impl Display for BlockTag { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { @@ -310,22 +268,23 @@ pub struct GetLogsParam { } /// An entry of the [`eth_getLogs`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getlogs) call reply. -// Example: -// ```json -// { -// "address": "0x7e41257f7b5c3dd3313ef02b1f4c864fe95bec2b", -// "topics": [ -// "0x2a2607d40f4a6feb97c36e0efd57e0aa3e42e0332af4fceb78f21b7dffcbd657" -// ], -// "data": "0x00000000000000000000000055654e7405fcb336386ea8f36954a211b2cda764000000000000000000000000000000000000000000000000002386f26fc100000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000003f62327071372d71677a7a692d74623564622d72357363692d637736736c2d6e646f756c2d666f7435742d347a7732702d657a6677692d74616a32792d76716500", -// "blockNumber": "0x3aa4f4", -// "transactionHash": "0x5618f72c485bd98a3df58d900eabe9e24bfaa972a6fe5227e02233fad2db1154", -// "transactionIndex": "0x6", -// "blockHash": "0x908e6b84d26d71421bfaa08e7966e0afcef3883a28a53a0a7a31104caf1e94c2", -// "logIndex": "0x8", -// "removed": false -// } -// ``` +/// +/// Example: +/// ```json +/// { +/// "address": "0x7e41257f7b5c3dd3313ef02b1f4c864fe95bec2b", +/// "topics": [ +/// "0x2a2607d40f4a6feb97c36e0efd57e0aa3e42e0332af4fceb78f21b7dffcbd657" +/// ], +/// "data": "0x00000000000000000000000055654e7405fcb336386ea8f36954a211b2cda764000000000000000000000000000000000000000000000000002386f26fc100000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000003f62327071372d71677a7a692d74623564622d72357363692d637736736c2d6e646f756c2d666f7435742d347a7732702d657a6677692d74616a32792d76716500", +/// "blockNumber": "0x3aa4f4", +/// "transactionHash": "0x5618f72c485bd98a3df58d900eabe9e24bfaa972a6fe5227e02233fad2db1154", +/// "transactionIndex": "0x6", +/// "blockHash": "0x908e6b84d26d71421bfaa08e7966e0afcef3883a28a53a0a7a31104caf1e94c2", +/// "logIndex": "0x8", +/// "removed": false +/// } +/// ``` #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, CandidType)] pub struct LogEntry { /// The address from which this log originated. @@ -510,7 +469,7 @@ pub struct JsonRpcReply { } /// An envelope for all JSON-RPC replies. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, CandidType)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum JsonRpcResult { #[serde(rename = "result")] Result(T), @@ -541,7 +500,7 @@ impl From> for Result { /// Describes a payload transformation to execute before passing the HTTP response to consensus. /// The purpose of these transformations is to ensure that the response encoding is deterministic /// (the field order is the same). -#[derive(Encode, Decode, Debug)] +#[derive(Debug, Decode, Encode)] pub enum ResponseTransform { #[n(0)] Block, @@ -624,17 +583,19 @@ fn cleanup_response(mut args: TransformArgs) -> HttpResponse { args.response } -impl HttpOutcallError { - pub fn is_response_too_large(&self) -> bool { - match self { - Self::IcError { code, message } => is_response_too_large(code, message), - _ => false, +pub fn is_http_outcall_error_response_too_large(error: &HttpOutcallError) -> bool { + match error { + HttpOutcallError::IcError { code, message } => { + code == &RejectionCode::SysFatal + && (message.contains("size limit") || message.contains("length limit")) } + _ => false, } } pub fn is_response_too_large(code: &RejectionCode, message: &str) -> bool { - code == &RejectionCode::SysFatal && message.contains("size limit") + code == &RejectionCode::SysFatal + && (message.contains("size limit") || message.contains("length limit")) } pub type HttpOutcallResult = Result; @@ -657,15 +618,6 @@ pub fn are_errors_consistent( } } -#[derive( - Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, CandidType, Serialize, Deserialize, Error, -)] -#[error("code {code}: {message}")] -pub struct JsonRpcError { - pub code: i64, - pub message: String, -} - #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct ResponseSizeEstimate(u64); @@ -734,7 +686,7 @@ where } let mut retries = 0; loop { - rpc_request.id = mutate_state(State::next_request_id); + rpc_request.id = next_request_id(); let payload = serde_json::to_string(&rpc_request).unwrap(); log!(TRACE_HTTP, "Calling url: {}, with payload: {payload}", url); @@ -766,20 +718,20 @@ where request, effective_size_estimate, ) - .await + .await { Err(RpcError::HttpOutcallError(HttpOutcallError::IcError { code, message })) - if is_response_too_large(&code, &message) => - { - let new_estimate = response_size_estimate.adjust(); - if response_size_estimate == new_estimate { - return Err(HttpOutcallError::IcError { code, message }.into()); - } - log!(DEBUG, "The {eth_method} response didn't fit into {response_size_estimate} bytes, retrying with {new_estimate}"); - response_size_estimate = new_estimate; - retries += 1; - continue; + if is_response_too_large(&code, &message) => + { + let new_estimate = response_size_estimate.adjust(); + if response_size_estimate == new_estimate { + return Err(HttpOutcallError::IcError { code, message }.into()); } + log!(DEBUG, "The {eth_method} response didn't fit into {response_size_estimate} bytes, retrying with {new_estimate}"); + response_size_estimate = new_estimate; + retries += 1; + continue; + } result => result?, }; @@ -804,7 +756,7 @@ where body: String::from_utf8_lossy(&response.body).to_string(), parsing_error: None, } - .into()); + .into()); } let reply: JsonRpcReply = serde_json::from_slice(&response.body).map_err(|e| { From 8bcbf18890cadef7217205f2047a1e3b94874da2 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 14:52:25 +0200 Subject: [PATCH 20/42] 243: clean-up --- Cargo.lock | 26 +++++++++++++++- src/candid_rpc.rs | 4 +-- src/lib.rs | 2 +- src/rpc_client/checked_amount/mod.rs | 1 - src/rpc_client/eth_rpc/mod.rs | 46 +++++++--------------------- src/rpc_client/mod.rs | 5 +-- src/rpc_client/responses.rs | 2 -- 7 files changed, 41 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 14d26394..3a155981 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1603,6 +1603,8 @@ dependencies = [ "ic-certified-map", "ic-cketh-minter", "ic-config", + "ic-crypto-sha3 0.9.0 (git+https://github.com/dfinity/ic?rev=release-2024-09-12_01-30-base)", + "ic-ethereum-types", "ic-ic00-types 0.8.0", "ic-metrics-encoder", "ic-nervous-system-common", @@ -1610,6 +1612,8 @@ dependencies = [ "ic-state-machine-tests", "ic-test-utilities-load-wasm", "maplit", + "minicbor", + "minicbor-derive", "num", "num-bigint 0.4.6", "num-derive", @@ -2589,7 +2593,7 @@ dependencies = [ "ic-cdk-macros", "ic-cdk-timers", "ic-crypto-ecdsa-secp256k1 0.9.0", - "ic-crypto-sha3", + "ic-crypto-sha3 0.9.0 (git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41)", "ic-ic00-types 0.9.0", "ic-metrics-encoder", "ic-stable-structures", @@ -3207,6 +3211,14 @@ dependencies = [ "sha3 0.9.1", ] +[[package]] +name = "ic-crypto-sha3" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?rev=release-2024-09-12_01-30-base#afe1a18291987667fdb52dac3ca44b1aebf7176e" +dependencies = [ + "sha3 0.10.8", +] + [[package]] name = "ic-crypto-standalone-sig-verifier" version = "0.8.0" @@ -3424,6 +3436,18 @@ dependencies = [ "strum_macros 0.25.3", ] +[[package]] +name = "ic-ethereum-types" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?rev=release-2024-09-12_01-30-base#afe1a18291987667fdb52dac3ca44b1aebf7176e" +dependencies = [ + "hex", + "ic-crypto-sha3 0.9.0 (git+https://github.com/dfinity/ic?rev=release-2024-09-12_01-30-base)", + "minicbor", + "minicbor-derive", + "serde", +] + [[package]] name = "ic-execution-environment" version = "0.8.0" diff --git a/src/candid_rpc.rs b/src/candid_rpc.rs index ae400a98..0e7c1253 100644 --- a/src/candid_rpc.rs +++ b/src/candid_rpc.rs @@ -5,17 +5,15 @@ use candid::Nat; use ethers_core::{types::Transaction, utils::rlp}; use evm_rpc_types::{ Hex, Hex32, MultiRpcResult, ProviderError, RpcApi, RpcError, RpcResult, RpcService, - RpcServices, ValidationError, + ValidationError, }; use ic_cdk::api::management_canister::http_request::{CanisterHttpRequestArgument, HttpResponse}; -use crate::candid_rpc::cketh_conversion::into_rpc_error; use crate::rpc_client::{EthRpcClient, MultiCallError, RpcTransport}; use crate::{ accounting::get_http_request_cost, add_metric_entry, constants::{ - DEFAULT_ETH_MAINNET_SERVICES, DEFAULT_ETH_SEPOLIA_SERVICES, DEFAULT_L2_MAINNET_SERVICES, ETH_GET_LOGS_MAX_BLOCKS, }, http::http_request, diff --git a/src/lib.rs b/src/lib.rs index e5392bf5..1a0dc08f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ pub mod http; pub mod memory; pub mod metrics; pub mod providers; -mod rpc_client; +pub mod rpc_client; pub mod types; pub mod util; pub mod validate; diff --git a/src/rpc_client/checked_amount/mod.rs b/src/rpc_client/checked_amount/mod.rs index 94290dfb..228382e2 100644 --- a/src/rpc_client/checked_amount/mod.rs +++ b/src/rpc_client/checked_amount/mod.rs @@ -1,7 +1,6 @@ #[cfg(test)] mod tests; -use candid::CandidType; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::cmp::Ordering; use std::fmt; diff --git a/src/rpc_client/eth_rpc/mod.rs b/src/rpc_client/eth_rpc/mod.rs index 173a1c4e..606b8ea9 100644 --- a/src/rpc_client/eth_rpc/mod.rs +++ b/src/rpc_client/eth_rpc/mod.rs @@ -8,7 +8,7 @@ use crate::rpc_client::eth_rpc_error::{sanitize_send_raw_transaction_result, Par use crate::rpc_client::numeric::{BlockNumber, LogIndex, TransactionCount, Wei, WeiPerGas}; use crate::rpc_client::responses::TransactionReceipt; use crate::rpc_client::RpcTransport; -use candid::{candid_method, CandidType}; +use candid::candid_method; use ethnum; use evm_rpc_types::{HttpOutcallError, JsonRpcError, RpcError, RpcService}; use ic_cdk::api::call::RejectionCode; @@ -49,7 +49,7 @@ pub fn into_nat(quantity: Quantity) -> candid::Nat { #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] #[serde(transparent)] -pub struct Data(#[serde(with = "crate::serde_data")] pub Vec); +pub struct Data(#[serde(with = "ic_ethereum_types::serde_data")] pub Vec); impl AsRef<[u8]> for Data { fn as_ref(&self) -> &[u8] { @@ -59,7 +59,7 @@ impl AsRef<[u8]> for Data { #[derive(Clone, Deserialize, Serialize, PartialEq, Eq, Hash)] #[serde(transparent)] -pub struct FixedSizeData(#[serde(with = "crate::serde_data")] pub [u8; 32]); +pub struct FixedSizeData(#[serde(with = "ic_ethereum_types::serde_data")] pub [u8; 32]); impl AsRef<[u8]> for FixedSizeData { fn as_ref(&self) -> &[u8] { @@ -120,24 +120,7 @@ impl HttpResponsePayload for SendRawTransactionResult { } #[derive(Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct Hash( - #[serde(with = "crate::serde_data")] - #[cbor(n(0), with = "minicbor::bytes")] - pub [u8; 32], -); - -impl CandidType for Hash { - fn _ty() -> candid::types::Type { - String::_ty() - } - - fn idl_serialize(&self, serializer: S) -> Result<(), S::Error> - where - S: candid::types::Serializer, - { - serializer.serialize_text(&format!("{:#x}", self)) - } -} +pub struct Hash(#[serde(with = "ic_ethereum_types::serde_data")] pub [u8; 32]); impl Debug for Hash { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { @@ -181,7 +164,7 @@ impl HttpResponsePayload for Hash {} /// Block tags. /// See -#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, CandidType)] +#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum BlockTag { /// The latest mined block. #[default] @@ -216,7 +199,7 @@ impl Display for BlockTag { } /// The block specification indicating which block to query. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, CandidType)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(untagged)] pub enum BlockSpec { /// Query the block with the specified index. @@ -250,7 +233,7 @@ impl std::str::FromStr for BlockSpec { } /// Parameters of the [`eth_getLogs`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getlogs) call. -#[derive(Debug, Clone, Serialize, Deserialize, CandidType)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct GetLogsParam { /// Integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. #[serde(rename = "fromBlock")] @@ -285,7 +268,7 @@ pub struct GetLogsParam { /// "removed": false /// } /// ``` -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, CandidType)] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] pub struct LogEntry { /// The address from which this log originated. pub address: Address, @@ -370,7 +353,7 @@ impl From for (Quantity, BlockSpec, Vec) { } } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CandidType)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct FeeHistory { /// Lowest number block of the returned range. #[serde(rename = "oldestBlock")] @@ -405,7 +388,7 @@ impl From for BlockSpec { } } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, CandidType)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Block { #[serde(rename = "baseFeePerGas")] pub base_fee_per_gas: Option, @@ -600,14 +583,6 @@ pub fn is_response_too_large(code: &RejectionCode, message: &str) -> bool { pub type HttpOutcallResult = Result; -#[derive(Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, CandidType, Deserialize)] -pub enum ValidationError { - // #[error("{0}")] - Custom(String), - // #[error("invalid hex data: {0}")] - InvalidHex(String), -} - pub fn are_errors_consistent( left: &Result, right: &Result, @@ -876,6 +851,7 @@ pub(super) mod metrics { } } + //TODO XC-243: use existing METRICS declared in memory.rs thread_local! { static METRICS: RefCell = RefCell::default(); } diff --git a/src/rpc_client/mod.rs b/src/rpc_client/mod.rs index e06df5fb..9f2b883d 100644 --- a/src/rpc_client/mod.rs +++ b/src/rpc_client/mod.rs @@ -16,13 +16,14 @@ use evm_rpc_types::{ HttpOutcallError, JsonRpcError, ProviderError, RpcApi, RpcConfig, RpcError, RpcService, }; use ic_cdk::api::management_canister::http_request::{CanisterHttpRequestArgument, HttpResponse}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Serialize}; use std::collections::BTreeMap; use std::fmt::Debug; use std::marker::PhantomData; pub mod checked_amount; mod eth_rpc; +mod eth_rpc_error; mod numeric; mod providers; mod requests; @@ -76,7 +77,7 @@ impl RpcTransport for DefaultTransport { } #[derive(Clone, Copy, Default, Debug, Eq, PartialEq)] -pub struct EthereumNetwork(#[n(0)] u64); +pub struct EthereumNetwork(u64); impl EthereumNetwork { pub const MAINNET: EthereumNetwork = EthereumNetwork(1); diff --git a/src/rpc_client/responses.rs b/src/rpc_client/responses.rs index 8fbde629..1917c070 100644 --- a/src/rpc_client/responses.rs +++ b/src/rpc_client/responses.rs @@ -52,7 +52,6 @@ impl HttpResponsePayload for TransactionReceipt { #[serde(try_from = "ethnum::u256", into = "ethnum::u256")] pub enum TransactionStatus { /// Transaction was mined and executed successfully. - #[n(0)] Success, /// Transaction was mined but execution failed (e.g., out-of-gas error). @@ -60,7 +59,6 @@ pub enum TransactionStatus { /// Note that this is different from a transaction that is not mined at all: a failed transaction /// is part of the blockchain and the next transaction from the same sender should have an incremented /// transaction nonce. - #[n(1)] Failure, } From 6c93dc53c16c6ee34ed6dac0133c90951839b40d Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 15:21:18 +0200 Subject: [PATCH 21/42] 243: fix client instantiation and eth_get_logs --- src/candid_rpc.rs | 44 ++++----- src/candid_rpc/cketh_conversion.rs | 151 +++++------------------------ src/rpc_client/mod.rs | 8 +- src/rpc_client/tests.rs | 18 ++-- 4 files changed, 59 insertions(+), 162 deletions(-) diff --git a/src/candid_rpc.rs b/src/candid_rpc.rs index 0e7c1253..3a2db904 100644 --- a/src/candid_rpc.rs +++ b/src/candid_rpc.rs @@ -5,17 +5,18 @@ use candid::Nat; use ethers_core::{types::Transaction, utils::rlp}; use evm_rpc_types::{ Hex, Hex32, MultiRpcResult, ProviderError, RpcApi, RpcError, RpcResult, RpcService, - ValidationError, + 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::{ accounting::get_http_request_cost, add_metric_entry, - constants::{ - ETH_GET_LOGS_MAX_BLOCKS, - }, + constants::ETH_GET_LOGS_MAX_BLOCKS, http::http_request, providers::resolve_rpc_service, types::{MetricRpcHost, MetricRpcMethod, ResolvedRpcService, RpcMethod}, @@ -58,24 +59,21 @@ fn check_services(services: Vec) -> RpcResult> { Ok(services) } -// fn get_rpc_client( -// source: RpcServices, -// config: evm_rpc_types::RpcConfig, -// ) -> RpcResult> { -// 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 get_rpc_client( + source: RpcServices, + config: evm_rpc_types::RpcConfig, +) -> RpcResult> { + 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)) +} fn process_result(method: RpcMethod, result: Result>) -> MultiRpcResult { match result { @@ -123,7 +121,7 @@ impl CandidRpcClient { config: Option, ) -> RpcResult { Ok(Self { - client: EthRpcClient::new(source, config.unwrap_or_default())?, + client: get_rpc_client(source, config.unwrap_or_default())?, }) } diff --git a/src/candid_rpc/cketh_conversion.rs b/src/candid_rpc/cketh_conversion.rs index 75b105af..84931b7f 100644 --- a/src/candid_rpc/cketh_conversion.rs +++ b/src/candid_rpc/cketh_conversion.rs @@ -6,7 +6,7 @@ use cketh_common::checked_amount::CheckedAmountOf; use cketh_common::eth_rpc::{Hash, Quantity}; use evm_rpc_types::{BlockTag, Hex, Hex20, Hex256, Hex32, HexByte, Nat256}; -pub(super) fn into_block_spec(value: BlockTag) -> cketh_common::eth_rpc::BlockSpec { +pub(super) fn into_block_spec(value: BlockTag) -> crate::rpc_client::eth_rpc::BlockSpec { use cketh_common::eth_rpc::{self, BlockSpec}; match value { BlockTag::Number(n) => BlockSpec::Number(into_checked_amount_of(n)), @@ -20,14 +20,14 @@ pub(super) fn into_block_spec(value: BlockTag) -> cketh_common::eth_rpc::BlockSp pub(super) fn into_get_logs_param( value: evm_rpc_types::GetLogsArgs, -) -> cketh_common::eth_rpc::GetLogsParam { - cketh_common::eth_rpc::GetLogsParam { +) -> crate::rpc_client::eth_rpc::GetLogsParam { + crate::rpc_client::eth_rpc::GetLogsParam { from_block: value.from_block.map(into_block_spec).unwrap_or_default(), to_block: value.to_block.map(into_block_spec).unwrap_or_default(), address: value .addresses .into_iter() - .map(|address| cketh_common::address::Address::new(address.into())) + .map(|address| ic_ethereum_types::Address::new(address.into())) .collect(), topics: value .topics @@ -36,7 +36,7 @@ pub(super) fn into_get_logs_param( .map(|topic| { topic .into_iter() - .map(|t| cketh_common::eth_rpc::FixedSizeData(t.into())) + .map(|t| crate::rpc_client::eth_rpc::FixedSizeData(t.into())) .collect() }) .collect(), @@ -44,12 +44,12 @@ pub(super) fn into_get_logs_param( } pub(super) fn from_log_entries( - value: Vec, + value: Vec, ) -> Vec { value.into_iter().map(from_log_entry).collect() } -fn from_log_entry(value: cketh_common::eth_rpc::LogEntry) -> evm_rpc_types::LogEntry { +fn from_log_entry(value: crate::rpc_client::eth_rpc::LogEntry) -> evm_rpc_types::LogEntry { evm_rpc_types::LogEntry { address: from_address(value.address), topics: value.topics.into_iter().map(|t| t.0.into()).collect(), @@ -186,35 +186,19 @@ pub(super) fn from_send_raw_transaction_result( } } -pub(super) fn into_rpc_config( - value: evm_rpc_types::RpcConfig, -) -> cketh_common::eth_rpc_client::RpcConfig { - cketh_common::eth_rpc_client::RpcConfig { - response_size_estimate: value.response_size_estimate, - } -} - pub(super) fn into_ethereum_network( source: &evm_rpc_types::RpcServices, -) -> cketh_common::lifecycle::EthereumNetwork { +) -> crate::rpc_client::EthereumNetwork { match &source { evm_rpc_types::RpcServices::Custom { chain_id, .. } => { - cketh_common::lifecycle::EthereumNetwork(*chain_id) - } - evm_rpc_types::RpcServices::EthMainnet(_) => { - cketh_common::lifecycle::EthereumNetwork::MAINNET - } - evm_rpc_types::RpcServices::EthSepolia(_) => { - cketh_common::lifecycle::EthereumNetwork::SEPOLIA - } - evm_rpc_types::RpcServices::ArbitrumOne(_) => { - cketh_common::lifecycle::EthereumNetwork::ARBITRUM - } - evm_rpc_types::RpcServices::BaseMainnet(_) => { - cketh_common::lifecycle::EthereumNetwork::BASE + 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(_) => { - cketh_common::lifecycle::EthereumNetwork::OPTIMISM + crate::rpc_client::EthereumNetwork::OPTIMISM } } } @@ -349,130 +333,39 @@ pub(super) fn into_rpc_services( 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 { - fn map_eth_mainnet_service( - service: evm_rpc_types::EthMainnetService, - ) -> cketh_common::eth_rpc_client::providers::EthMainnetService { - match service { - evm_rpc_types::EthMainnetService::Alchemy => { - cketh_common::eth_rpc_client::providers::EthMainnetService::Alchemy - } - evm_rpc_types::EthMainnetService::Ankr => { - cketh_common::eth_rpc_client::providers::EthMainnetService::Ankr - } - evm_rpc_types::EthMainnetService::BlockPi => { - cketh_common::eth_rpc_client::providers::EthMainnetService::BlockPi - } - evm_rpc_types::EthMainnetService::PublicNode => { - cketh_common::eth_rpc_client::providers::EthMainnetService::PublicNode - } - evm_rpc_types::EthMainnetService::Cloudflare => { - cketh_common::eth_rpc_client::providers::EthMainnetService::Cloudflare - } - evm_rpc_types::EthMainnetService::Llama => { - cketh_common::eth_rpc_client::providers::EthMainnetService::Llama - } - } - } - - fn map_eth_sepolia_service( - service: evm_rpc_types::EthSepoliaService, - ) -> cketh_common::eth_rpc_client::providers::EthSepoliaService { - match service { - evm_rpc_types::EthSepoliaService::Alchemy => { - cketh_common::eth_rpc_client::providers::EthSepoliaService::Alchemy - } - evm_rpc_types::EthSepoliaService::Ankr => { - cketh_common::eth_rpc_client::providers::EthSepoliaService::Ankr - } - evm_rpc_types::EthSepoliaService::BlockPi => { - cketh_common::eth_rpc_client::providers::EthSepoliaService::BlockPi - } - evm_rpc_types::EthSepoliaService::PublicNode => { - cketh_common::eth_rpc_client::providers::EthSepoliaService::PublicNode - } - evm_rpc_types::EthSepoliaService::Sepolia => { - cketh_common::eth_rpc_client::providers::EthSepoliaService::Sepolia - } - } - } - - fn map_l2_mainnet_service( - service: evm_rpc_types::L2MainnetService, - ) -> cketh_common::eth_rpc_client::providers::L2MainnetService { - match service { - evm_rpc_types::L2MainnetService::Alchemy => { - cketh_common::eth_rpc_client::providers::L2MainnetService::Alchemy - } - evm_rpc_types::L2MainnetService::Ankr => { - cketh_common::eth_rpc_client::providers::L2MainnetService::Ankr - } - evm_rpc_types::L2MainnetService::BlockPi => { - cketh_common::eth_rpc_client::providers::L2MainnetService::BlockPi - } - evm_rpc_types::L2MainnetService::PublicNode => { - cketh_common::eth_rpc_client::providers::L2MainnetService::PublicNode - } - evm_rpc_types::L2MainnetService::Llama => { - cketh_common::eth_rpc_client::providers::L2MainnetService::Llama - } - } - } - +) -> Vec { match source { evm_rpc_types::RpcServices::Custom { chain_id: _, services, } => services .into_iter() - .map(|service| { - cketh_common::eth_rpc_client::providers::RpcService::Custom(into_rpc_api(service)) - }) + .map(|service| evm_rpc_types::RpcService::Custom(service)) .collect(), evm_rpc_types::RpcServices::EthMainnet(services) => services .unwrap_or_else(|| default_eth_mainnet_services.to_vec()) .into_iter() - .map(|service| { - cketh_common::eth_rpc_client::providers::RpcService::EthMainnet( - map_eth_mainnet_service(service), - ) - }) + .map(|service| evm_rpc_types::RpcService::EthMainnet(service)) .collect(), evm_rpc_types::RpcServices::EthSepolia(services) => services .unwrap_or_else(|| default_eth_sepolia_services.to_vec()) .into_iter() - .map(|service| { - cketh_common::eth_rpc_client::providers::RpcService::EthSepolia( - map_eth_sepolia_service(service), - ) - }) + .map(|service| evm_rpc_types::RpcService::EthSepolia(service)) .collect(), evm_rpc_types::RpcServices::ArbitrumOne(services) => services .unwrap_or_else(|| default_l2_mainnet_services.to_vec()) .into_iter() - .map(|service| { - cketh_common::eth_rpc_client::providers::RpcService::ArbitrumOne( - map_l2_mainnet_service(service), - ) - }) + .map(|service| evm_rpc_types::RpcService::ArbitrumOne(service)) .collect(), evm_rpc_types::RpcServices::BaseMainnet(services) => services .unwrap_or_else(|| default_l2_mainnet_services.to_vec()) .into_iter() - .map(|service| { - cketh_common::eth_rpc_client::providers::RpcService::BaseMainnet( - map_l2_mainnet_service(service), - ) - }) + .map(|service| evm_rpc_types::RpcService::BaseMainnet(service)) .collect(), evm_rpc_types::RpcServices::OptimismMainnet(services) => services .unwrap_or_else(|| default_l2_mainnet_services.to_vec()) .into_iter() - .map(|service| { - cketh_common::eth_rpc_client::providers::RpcService::OptimismMainnet( - map_l2_mainnet_service(service), - ) - }) + .map(|service| evm_rpc_types::RpcService::OptimismMainnet(service)) .collect(), } } @@ -746,7 +639,7 @@ fn into_quantity(value: Nat256) -> Quantity { Quantity::from_be_bytes(value.into_be_bytes()) } -fn from_address(value: cketh_common::address::Address) -> evm_rpc_types::Hex20 { +fn from_address(value: ic_ethereum_types::Address) -> evm_rpc_types::Hex20 { // TODO 243: cketh_common::address::Address should expose the underlying [u8; 20] // so that there is no artificial error handling here. value diff --git a/src/rpc_client/mod.rs b/src/rpc_client/mod.rs index 9f2b883d..66749ed0 100644 --- a/src/rpc_client/mod.rs +++ b/src/rpc_client/mod.rs @@ -22,7 +22,7 @@ use std::fmt::Debug; use std::marker::PhantomData; pub mod checked_amount; -mod eth_rpc; +pub(crate) mod eth_rpc; mod eth_rpc_error; mod numeric; mod providers; @@ -79,6 +79,12 @@ impl RpcTransport for DefaultTransport { #[derive(Clone, Copy, Default, Debug, Eq, PartialEq)] pub struct EthereumNetwork(u64); +impl From for EthereumNetwork { + fn from(value: u64) -> Self { + Self(value) + } +} + impl EthereumNetwork { pub const MAINNET: EthereumNetwork = EthereumNetwork(1); pub const SEPOLIA: EthereumNetwork = EthereumNetwork(11155111); diff --git a/src/rpc_client/tests.rs b/src/rpc_client/tests.rs index 80fc2cca..2641ef0e 100644 --- a/src/rpc_client/tests.rs +++ b/src/rpc_client/tests.rs @@ -1,7 +1,6 @@ mod eth_rpc_client { - use crate::eth_rpc_client::providers::{EthMainnetService, EthSepoliaService, RpcService}; - use crate::eth_rpc_client::EthRpcClient; - use crate::lifecycle::EthereumNetwork; + use crate::rpc_client::{EthRpcClient, EthereumNetwork}; + use evm_rpc_types::{EthMainnetService, EthSepoliaService, RpcService}; #[test] fn should_retrieve_sepolia_providers_in_stable_order() { @@ -36,16 +35,17 @@ mod eth_rpc_client { } mod multi_call_results { - use crate::eth_rpc_client::providers::{EthMainnetService, RpcService}; + use evm_rpc_types::{EthMainnetService, RpcService}; const ANKR: RpcService = RpcService::EthMainnet(EthMainnetService::Ankr); const PUBLIC_NODE: RpcService = RpcService::EthMainnet(EthMainnetService::PublicNode); const CLOUDFLARE: RpcService = RpcService::EthMainnet(EthMainnetService::Cloudflare); mod reduce_with_equality { - use crate::eth_rpc::{HttpOutcallError, JsonRpcResult}; - use crate::eth_rpc_client::tests::multi_call_results::{ANKR, PUBLIC_NODE}; - use crate::eth_rpc_client::{MultiCallError, MultiCallResults}; + use crate::rpc_client::eth_rpc::JsonRpcResult; + use crate::rpc_client::tests::multi_call_results::{ANKR, PUBLIC_NODE}; + use crate::rpc_client::{MultiCallError, MultiCallResults}; + use evm_rpc_types::HttpOutcallError; use ic_cdk::api::call::RejectionCode; #[test] @@ -496,7 +496,7 @@ mod eth_get_transaction_receipt { block_hash: Hash::from_str( "0x82005d2f17b251900968f01b0ed482cb49b7e1d797342bc504904d442b64dbe4" ) - .unwrap(), + .unwrap(), block_number: BlockNumber::new(0x4132ec), effective_gas_price: WeiPerGas::new(0xfefbee3e), gas_used: GasAmount::new(0x5208), @@ -504,7 +504,7 @@ mod eth_get_transaction_receipt { transaction_hash: Hash::from_str( "0x0e59bd032b9b22aca5e2784e4cf114783512db00988c716cf17a1cc755a0a93d" ) - .unwrap(), + .unwrap(), } ) } From e081cacaebf73460e7272c032702d69920f2b6dd Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 15:22:48 +0200 Subject: [PATCH 22/42] 243: fix eth_get_block_by_number --- src/candid_rpc/cketh_conversion.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/candid_rpc/cketh_conversion.rs b/src/candid_rpc/cketh_conversion.rs index 84931b7f..72c9a81d 100644 --- a/src/candid_rpc/cketh_conversion.rs +++ b/src/candid_rpc/cketh_conversion.rs @@ -2,12 +2,12 @@ //! This module is meant to be temporary and should be removed once the dependency on ckETH is removed, //! see -use cketh_common::checked_amount::CheckedAmountOf; -use cketh_common::eth_rpc::{Hash, Quantity}; +use crate::rpc_client::checked_amount::CheckedAmountOf; +use crate::rpc_client::eth_rpc::{Hash, Quantity}; use evm_rpc_types::{BlockTag, Hex, Hex20, Hex256, Hex32, HexByte, Nat256}; pub(super) fn into_block_spec(value: BlockTag) -> crate::rpc_client::eth_rpc::BlockSpec { - use cketh_common::eth_rpc::{self, BlockSpec}; + use crate::rpc_client::eth_rpc::{self, BlockSpec}; match value { BlockTag::Number(n) => BlockSpec::Number(into_checked_amount_of(n)), BlockTag::Latest => BlockSpec::Tag(eth_rpc::BlockTag::Latest), @@ -132,7 +132,7 @@ pub(super) fn from_transaction_receipt( } } -pub(super) fn from_block(value: cketh_common::eth_rpc::Block) -> evm_rpc_types::Block { +pub(super) fn from_block(value: crate::rpc_client::eth_rpc::Block) -> evm_rpc_types::Block { evm_rpc_types::Block { base_fee_per_gas: value.base_fee_per_gas.map(from_checked_amount_of), number: from_checked_amount_of(value.number), From 6b4e493c1e5ca3249b0999f30d2161f0e5cf64bd Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 15:24:38 +0200 Subject: [PATCH 23/42] 243: fix eth_get_transaction_receipt --- src/candid_rpc/cketh_conversion.rs | 10 +++------- src/rpc_client/mod.rs | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/candid_rpc/cketh_conversion.rs b/src/candid_rpc/cketh_conversion.rs index 72c9a81d..dc5200ee 100644 --- a/src/candid_rpc/cketh_conversion.rs +++ b/src/candid_rpc/cketh_conversion.rs @@ -102,7 +102,7 @@ pub(super) fn into_get_transaction_count_params( } pub(super) fn from_transaction_receipt( - value: cketh_common::eth_rpc_client::responses::TransactionReceipt, + value: crate::rpc_client::responses::TransactionReceipt, ) -> evm_rpc_types::TransactionReceipt { evm_rpc_types::TransactionReceipt { block_hash: Hex32::from(value.block_hash.0), @@ -110,12 +110,8 @@ pub(super) fn from_transaction_receipt( effective_gas_price: from_checked_amount_of(value.effective_gas_price), gas_used: from_checked_amount_of(value.gas_used), status: match value.status { - cketh_common::eth_rpc_client::responses::TransactionStatus::Success => { - Nat256::from(1_u8) - } - cketh_common::eth_rpc_client::responses::TransactionStatus::Failure => { - Nat256::from(0_u8) - } + crate::rpc_client::responses::TransactionStatus::Success => Nat256::from(1_u8), + crate::rpc_client::responses::TransactionStatus::Failure => Nat256::from(0_u8), }, transaction_hash: Hex32::from(value.transaction_hash.0), // TODO 243: responses types from querying JSON-RPC providers should be strongly typed diff --git a/src/rpc_client/mod.rs b/src/rpc_client/mod.rs index 66749ed0..56c5a621 100644 --- a/src/rpc_client/mod.rs +++ b/src/rpc_client/mod.rs @@ -27,7 +27,7 @@ mod eth_rpc_error; mod numeric; mod providers; mod requests; -mod responses; +pub(crate) mod responses; #[cfg(test)] mod tests; From 344d59bb70805b5ca6379e439834797f66859ebe Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 15:25:42 +0200 Subject: [PATCH 24/42] 243: fix eth_get_transaction_count --- src/candid_rpc/cketh_conversion.rs | 6 +++--- src/rpc_client/mod.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/candid_rpc/cketh_conversion.rs b/src/candid_rpc/cketh_conversion.rs index dc5200ee..60df97f2 100644 --- a/src/candid_rpc/cketh_conversion.rs +++ b/src/candid_rpc/cketh_conversion.rs @@ -94,9 +94,9 @@ pub(super) fn from_fee_history( pub(super) fn into_get_transaction_count_params( value: evm_rpc_types::GetTransactionCountArgs, -) -> cketh_common::eth_rpc_client::requests::GetTransactionCountParams { - cketh_common::eth_rpc_client::requests::GetTransactionCountParams { - address: cketh_common::address::Address::new(value.address.into()), +) -> crate::rpc_client::requests::GetTransactionCountParams { + crate::rpc_client::requests::GetTransactionCountParams { + address: ic_ethereum_types::Address::new(value.address.into()), block: into_block_spec(value.block), } } diff --git a/src/rpc_client/mod.rs b/src/rpc_client/mod.rs index 56c5a621..af9449b5 100644 --- a/src/rpc_client/mod.rs +++ b/src/rpc_client/mod.rs @@ -26,7 +26,7 @@ pub(crate) mod eth_rpc; mod eth_rpc_error; mod numeric; mod providers; -mod requests; +pub(crate) mod requests; pub(crate) mod responses; #[cfg(test)] From daf4e9f5c40e366f9c1fc3c70f3ea7599555def1 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 15:26:20 +0200 Subject: [PATCH 25/42] 243: fix eth_fee_history --- src/candid_rpc/cketh_conversion.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/candid_rpc/cketh_conversion.rs b/src/candid_rpc/cketh_conversion.rs index 60df97f2..906e7d2f 100644 --- a/src/candid_rpc/cketh_conversion.rs +++ b/src/candid_rpc/cketh_conversion.rs @@ -65,8 +65,8 @@ fn from_log_entry(value: crate::rpc_client::eth_rpc::LogEntry) -> evm_rpc_types: pub(super) fn into_fee_history_params( value: evm_rpc_types::FeeHistoryArgs, -) -> cketh_common::eth_rpc::FeeHistoryParams { - cketh_common::eth_rpc::FeeHistoryParams { +) -> crate::rpc_client::eth_rpc::FeeHistoryParams { + crate::rpc_client::eth_rpc::FeeHistoryParams { block_count: into_quantity(value.block_count), highest_block: into_block_spec(value.newest_block), reward_percentiles: value.reward_percentiles.unwrap_or_default(), @@ -74,7 +74,7 @@ pub(super) fn into_fee_history_params( } pub(super) fn from_fee_history( - value: cketh_common::eth_rpc::FeeHistory, + value: crate::rpc_client::eth_rpc::FeeHistory, ) -> evm_rpc_types::FeeHistory { evm_rpc_types::FeeHistory { oldest_block: from_checked_amount_of(value.oldest_block), From 7f44a2b591c21b8048a5269537b6df668f905fd9 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 15:27:00 +0200 Subject: [PATCH 26/42] 243: fix eth_send_raw_transaction --- src/candid_rpc/cketh_conversion.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/candid_rpc/cketh_conversion.rs b/src/candid_rpc/cketh_conversion.rs index 906e7d2f..c34d4e77 100644 --- a/src/candid_rpc/cketh_conversion.rs +++ b/src/candid_rpc/cketh_conversion.rs @@ -164,19 +164,19 @@ pub(super) fn from_block(value: crate::rpc_client::eth_rpc::Block) -> evm_rpc_ty pub(super) fn from_send_raw_transaction_result( transaction_hash: Option, - value: cketh_common::eth_rpc::SendRawTransactionResult, + value: crate::rpc_client::eth_rpc::SendRawTransactionResult, ) -> evm_rpc_types::SendRawTransactionStatus { match value { - cketh_common::eth_rpc::SendRawTransactionResult::Ok => { + crate::rpc_client::eth_rpc::SendRawTransactionResult::Ok => { evm_rpc_types::SendRawTransactionStatus::Ok(transaction_hash) } - cketh_common::eth_rpc::SendRawTransactionResult::InsufficientFunds => { + crate::rpc_client::eth_rpc::SendRawTransactionResult::InsufficientFunds => { evm_rpc_types::SendRawTransactionStatus::InsufficientFunds } - cketh_common::eth_rpc::SendRawTransactionResult::NonceTooLow => { + crate::rpc_client::eth_rpc::SendRawTransactionResult::NonceTooLow => { evm_rpc_types::SendRawTransactionStatus::NonceTooLow } - cketh_common::eth_rpc::SendRawTransactionResult::NonceTooHigh => { + crate::rpc_client::eth_rpc::SendRawTransactionResult::NonceTooHigh => { evm_rpc_types::SendRawTransactionStatus::NonceTooHigh } } From e036b22158f72760f41f990fa1ccc549bc984c9e Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 15:29:54 +0200 Subject: [PATCH 27/42] 243: it compiles! --- src/candid_rpc.rs | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/src/candid_rpc.rs b/src/candid_rpc.rs index 3a2db904..2860a7d2 100644 --- a/src/candid_rpc.rs +++ b/src/candid_rpc.rs @@ -237,13 +237,11 @@ fn get_transaction_hash(raw_signed_transaction_hex: &Hex) -> Option { #[cfg(test)] mod test { use super::*; - use crate::candid_rpc::cketh_conversion::into_rpc_service; - use crate::rpc_client::MultiCallError; + use crate::rpc_client::{MultiCallError, MultiCallResults}; use evm_rpc_types::RpcError; #[test] fn test_process_result_mapping() { - use cketh_common::eth_rpc_client::MultiCallResults; use evm_rpc_types::{EthMainnetService, RpcService}; let method = RpcMethod::EthGetTransactionCount; @@ -256,9 +254,7 @@ mod test { process_result( method, Err(MultiCallError::<()>::ConsistentError( - cketh_common::eth_rpc::RpcError::ProviderError( - cketh_common::eth_rpc::ProviderError::MissingRequiredProvider - ) + RpcError::ProviderError(ProviderError::MissingRequiredProvider) )) ), MultiRpcResult::Consistent(Err(RpcError::ProviderError( @@ -280,12 +276,9 @@ mod test { process_result( method, Err(MultiCallError::InconsistentResults(MultiCallResults { - results: vec![( - into_rpc_service(RpcService::EthMainnet(EthMainnetService::Ankr)), - Ok(5) - )] - .into_iter() - .collect(), + results: vec![(RpcService::EthMainnet(EthMainnetService::Ankr), Ok(5))] + .into_iter() + .collect(), })) ), MultiRpcResult::Inconsistent(vec![( @@ -298,15 +291,10 @@ mod test { method, Err(MultiCallError::InconsistentResults(MultiCallResults { results: vec![ + (RpcService::EthMainnet(EthMainnetService::Ankr), Ok(5)), ( - into_rpc_service(RpcService::EthMainnet(EthMainnetService::Ankr)), - Ok(5) - ), - ( - into_rpc_service(RpcService::EthMainnet(EthMainnetService::Cloudflare)), - Err(cketh_common::eth_rpc::RpcError::ProviderError( - cketh_common::eth_rpc::ProviderError::NoPermission - )) + RpcService::EthMainnet(EthMainnetService::Cloudflare), + Err(RpcError::ProviderError(ProviderError::NoPermission)) ) ] .into_iter() From c552b938ded16086baa6e6dc219387265a4bad35 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 15:49:20 +0200 Subject: [PATCH 28/42] 243: remove has_http_outcall_error_matching --- src/rpc_client/mod.rs | 16 ---------- src/rpc_client/tests.rs | 68 ----------------------------------------- 2 files changed, 84 deletions(-) diff --git a/src/rpc_client/mod.rs b/src/rpc_client/mod.rs index af9449b5..e048e25d 100644 --- a/src/rpc_client/mod.rs +++ b/src/rpc_client/mod.rs @@ -404,22 +404,6 @@ pub enum MultiCallError { } impl MultiCallError { - pub fn has_http_outcall_error_matching bool>( - &self, - predicate: P, - ) -> bool { - match self { - MultiCallError::ConsistentError(RpcError::HttpOutcallError(error)) => predicate(error), - MultiCallError::ConsistentError(_) => false, - MultiCallError::InconsistentResults(results) => { - results.results.values().any(|result| match result { - Err(RpcError::HttpOutcallError(error)) => predicate(error), - _ => false, - }) - } - } - } -} impl MultiCallResults { pub fn reduce_with_equality(self) -> Result> { diff --git a/src/rpc_client/tests.rs b/src/rpc_client/tests.rs index 2641ef0e..1ecc54ef 100644 --- a/src/rpc_client/tests.rs +++ b/src/rpc_client/tests.rs @@ -391,74 +391,6 @@ mod multi_call_results { } } } - - mod has_http_outcall_error_matching { - use super::*; - use crate::eth_rpc::{HttpOutcallError, JsonRpcResult}; - use crate::eth_rpc_client::{MultiCallError, MultiCallResults}; - use ic_cdk::api::call::RejectionCode; - use proptest::prelude::any; - use proptest::proptest; - - proptest! { - #[test] - fn should_not_match_when_consistent_json_rpc_error(code in any::(), message in ".*") { - let error: MultiCallError = MultiCallError::ConsistentJsonRpcError { - code, - message, - }; - let always_true = |_outcall_error: &HttpOutcallError| true; - - assert!(!error.has_http_outcall_error_matching(always_true)); - } - } - - #[test] - fn should_match_when_consistent_http_outcall_error() { - let error: MultiCallError = - MultiCallError::ConsistentHttpOutcallError(HttpOutcallError::IcError { - code: RejectionCode::SysTransient, - message: "message".to_string(), - }); - let always_true = |_outcall_error: &HttpOutcallError| true; - let always_false = |_outcall_error: &HttpOutcallError| false; - - assert!(error.has_http_outcall_error_matching(always_true)); - assert!(!error.has_http_outcall_error_matching(always_false)); - } - - #[test] - fn should_match_on_single_inconsistent_result_with_outcall_error() { - let always_true = |_outcall_error: &HttpOutcallError| true; - let error_with_no_outcall_error = - MultiCallError::InconsistentResults(MultiCallResults::from_non_empty_iter(vec![ - (ANKR, Ok(JsonRpcResult::Result(1))), - ( - CLOUDFLARE, - Ok(JsonRpcResult::Error { - code: -32700, - message: "error".to_string(), - }), - ), - (PUBLIC_NODE, Ok(JsonRpcResult::Result(1))), - ])); - assert!(!error_with_no_outcall_error.has_http_outcall_error_matching(always_true)); - - let error_with_outcall_error = - MultiCallError::InconsistentResults(MultiCallResults::from_non_empty_iter(vec![ - (ANKR, Ok(JsonRpcResult::Result(1))), - ( - CLOUDFLARE, - Err(HttpOutcallError::IcError { - code: RejectionCode::SysTransient, - message: "message".to_string(), - }), - ), - (PUBLIC_NODE, Ok(JsonRpcResult::Result(1))), - ])); - assert!(error_with_outcall_error.has_http_outcall_error_matching(always_true)); - } - } } mod eth_get_transaction_receipt { From ee10dd239b717350c6c59bb91b5bdcd4bafffff1 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 16:22:47 +0200 Subject: [PATCH 29/42] 243: tests compile! --- Cargo.lock | 11 ++ Cargo.toml | 2 + src/candid_rpc/cketh_conversion.rs | 236 ----------------------------- src/rpc_client/mod.rs | 32 +++- src/rpc_client/tests.rs | 116 ++++++++------ 5 files changed, 107 insertions(+), 290 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a155981..9c3d1295 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1604,6 +1604,7 @@ dependencies = [ "ic-cketh-minter", "ic-config", "ic-crypto-sha3 0.9.0 (git+https://github.com/dfinity/ic?rev=release-2024-09-12_01-30-base)", + "ic-crypto-test-utils-reproducible-rng", "ic-ethereum-types", "ic-ic00-types 0.8.0", "ic-metrics-encoder", @@ -1619,6 +1620,7 @@ dependencies = [ "num-derive", "num-traits", "proptest", + "rand", "serde", "serde_json", "thousands", @@ -3256,6 +3258,15 @@ dependencies = [ "ic-types", ] +[[package]] +name = "ic-crypto-test-utils-reproducible-rng" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?rev=release-2024-09-12_01-30-base#afe1a18291987667fdb52dac3ca44b1aebf7176e" +dependencies = [ + "rand", + "rand_chacha", +] + [[package]] name = "ic-crypto-tls-cert-validation" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index da075687..be06b553 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,9 +54,11 @@ assert_matches = "1.5" ic-ic00-types = { git = "https://github.com/dfinity/ic", rev = "release-2023-09-27_23-01" } ic-base-types = { git = "https://github.com/dfinity/ic", rev = "release-2023-09-27_23-01" } ic-config = { git = "https://github.com/dfinity/ic", rev = "release-2023-09-27_23-01" } +ic-crypto-test-utils-reproducible-rng = { git = "https://github.com/dfinity/ic", rev = "release-2024-09-12_01-30-base" } ic-state-machine-tests = { git = "https://github.com/dfinity/ic", rev = "release-2023-09-27_23-01" } ic-test-utilities-load-wasm = { git = "https://github.com/dfinity/ic", rev = "release-2023-09-27_23-01" } proptest = { workspace = true } +rand = "0.8" [workspace.dependencies] candid = { version = "0.9" } diff --git a/src/candid_rpc/cketh_conversion.rs b/src/candid_rpc/cketh_conversion.rs index c34d4e77..afed31cf 100644 --- a/src/candid_rpc/cketh_conversion.rs +++ b/src/candid_rpc/cketh_conversion.rs @@ -199,131 +199,6 @@ pub(super) fn into_ethereum_network( } } -#[cfg(test)] -pub(super) fn into_rpc_service( - source: evm_rpc_types::RpcService, -) -> cketh_common::eth_rpc_client::providers::RpcService { - fn map_eth_mainnet_service( - service: evm_rpc_types::EthMainnetService, - ) -> cketh_common::eth_rpc_client::providers::EthMainnetService { - match service { - evm_rpc_types::EthMainnetService::Alchemy => { - cketh_common::eth_rpc_client::providers::EthMainnetService::Alchemy - } - evm_rpc_types::EthMainnetService::Ankr => { - cketh_common::eth_rpc_client::providers::EthMainnetService::Ankr - } - evm_rpc_types::EthMainnetService::BlockPi => { - cketh_common::eth_rpc_client::providers::EthMainnetService::BlockPi - } - evm_rpc_types::EthMainnetService::PublicNode => { - cketh_common::eth_rpc_client::providers::EthMainnetService::PublicNode - } - evm_rpc_types::EthMainnetService::Cloudflare => { - cketh_common::eth_rpc_client::providers::EthMainnetService::Cloudflare - } - evm_rpc_types::EthMainnetService::Llama => { - cketh_common::eth_rpc_client::providers::EthMainnetService::Llama - } - } - } - - fn map_eth_sepolia_service( - service: evm_rpc_types::EthSepoliaService, - ) -> cketh_common::eth_rpc_client::providers::EthSepoliaService { - match service { - evm_rpc_types::EthSepoliaService::Alchemy => { - cketh_common::eth_rpc_client::providers::EthSepoliaService::Alchemy - } - evm_rpc_types::EthSepoliaService::Ankr => { - cketh_common::eth_rpc_client::providers::EthSepoliaService::Ankr - } - evm_rpc_types::EthSepoliaService::BlockPi => { - cketh_common::eth_rpc_client::providers::EthSepoliaService::BlockPi - } - evm_rpc_types::EthSepoliaService::PublicNode => { - cketh_common::eth_rpc_client::providers::EthSepoliaService::PublicNode - } - evm_rpc_types::EthSepoliaService::Sepolia => { - cketh_common::eth_rpc_client::providers::EthSepoliaService::Sepolia - } - } - } - - fn map_l2_mainnet_service( - service: evm_rpc_types::L2MainnetService, - ) -> cketh_common::eth_rpc_client::providers::L2MainnetService { - match service { - evm_rpc_types::L2MainnetService::Alchemy => { - cketh_common::eth_rpc_client::providers::L2MainnetService::Alchemy - } - evm_rpc_types::L2MainnetService::Ankr => { - cketh_common::eth_rpc_client::providers::L2MainnetService::Ankr - } - evm_rpc_types::L2MainnetService::BlockPi => { - cketh_common::eth_rpc_client::providers::L2MainnetService::BlockPi - } - evm_rpc_types::L2MainnetService::PublicNode => { - cketh_common::eth_rpc_client::providers::L2MainnetService::PublicNode - } - evm_rpc_types::L2MainnetService::Llama => { - cketh_common::eth_rpc_client::providers::L2MainnetService::Llama - } - } - } - - match source { - evm_rpc_types::RpcService::Chain(id) => { - cketh_common::eth_rpc_client::providers::RpcService::Chain(id) - } - evm_rpc_types::RpcService::Provider(id) => { - cketh_common::eth_rpc_client::providers::RpcService::Provider(id) - } - evm_rpc_types::RpcService::Custom(rpc) => { - cketh_common::eth_rpc_client::providers::RpcService::Custom( - cketh_common::eth_rpc_client::providers::RpcApi { - url: rpc.url, - headers: rpc.headers, - }, - ) - } - evm_rpc_types::RpcService::EthMainnet(service) => { - cketh_common::eth_rpc_client::providers::RpcService::EthMainnet( - map_eth_mainnet_service(service), - ) - } - evm_rpc_types::RpcService::EthSepolia(service) => { - cketh_common::eth_rpc_client::providers::RpcService::EthSepolia( - map_eth_sepolia_service(service), - ) - } - evm_rpc_types::RpcService::ArbitrumOne(service) => { - cketh_common::eth_rpc_client::providers::RpcService::ArbitrumOne( - map_l2_mainnet_service(service), - ) - } - evm_rpc_types::RpcService::BaseMainnet(service) => { - cketh_common::eth_rpc_client::providers::RpcService::BaseMainnet( - map_l2_mainnet_service(service), - ) - } - evm_rpc_types::RpcService::OptimismMainnet(service) => { - cketh_common::eth_rpc_client::providers::RpcService::OptimismMainnet( - map_l2_mainnet_service(service), - ) - } - } -} - -pub(super) fn into_rpc_api( - rpc: evm_rpc_types::RpcApi, -) -> cketh_common::eth_rpc_client::providers::RpcApi { - cketh_common::eth_rpc_client::providers::RpcApi { - url: rpc.url, - headers: rpc.headers, - } -} - pub(super) fn into_rpc_services( source: evm_rpc_types::RpcServices, default_eth_mainnet_services: &[evm_rpc_types::EthMainnetService], @@ -366,117 +241,6 @@ pub(super) fn into_rpc_services( } } -pub(super) fn from_rpc_service( - service: cketh_common::eth_rpc_client::providers::RpcService, -) -> evm_rpc_types::RpcService { - fn map_eth_mainnet_service( - service: cketh_common::eth_rpc_client::providers::EthMainnetService, - ) -> evm_rpc_types::EthMainnetService { - match service { - cketh_common::eth_rpc_client::providers::EthMainnetService::Alchemy => { - evm_rpc_types::EthMainnetService::Alchemy - } - cketh_common::eth_rpc_client::providers::EthMainnetService::Ankr => { - evm_rpc_types::EthMainnetService::Ankr - } - cketh_common::eth_rpc_client::providers::EthMainnetService::BlockPi => { - evm_rpc_types::EthMainnetService::BlockPi - } - cketh_common::eth_rpc_client::providers::EthMainnetService::PublicNode => { - evm_rpc_types::EthMainnetService::PublicNode - } - cketh_common::eth_rpc_client::providers::EthMainnetService::Cloudflare => { - evm_rpc_types::EthMainnetService::Cloudflare - } - cketh_common::eth_rpc_client::providers::EthMainnetService::Llama => { - evm_rpc_types::EthMainnetService::Llama - } - } - } - - fn map_eth_sepolia_service( - service: cketh_common::eth_rpc_client::providers::EthSepoliaService, - ) -> evm_rpc_types::EthSepoliaService { - match service { - cketh_common::eth_rpc_client::providers::EthSepoliaService::Alchemy => { - evm_rpc_types::EthSepoliaService::Alchemy - } - cketh_common::eth_rpc_client::providers::EthSepoliaService::Ankr => { - evm_rpc_types::EthSepoliaService::Ankr - } - cketh_common::eth_rpc_client::providers::EthSepoliaService::BlockPi => { - evm_rpc_types::EthSepoliaService::BlockPi - } - cketh_common::eth_rpc_client::providers::EthSepoliaService::PublicNode => { - evm_rpc_types::EthSepoliaService::PublicNode - } - cketh_common::eth_rpc_client::providers::EthSepoliaService::Sepolia => { - evm_rpc_types::EthSepoliaService::Sepolia - } - } - } - - fn map_l2_mainnet_service( - service: cketh_common::eth_rpc_client::providers::L2MainnetService, - ) -> evm_rpc_types::L2MainnetService { - match service { - cketh_common::eth_rpc_client::providers::L2MainnetService::Alchemy => { - evm_rpc_types::L2MainnetService::Alchemy - } - cketh_common::eth_rpc_client::providers::L2MainnetService::Ankr => { - evm_rpc_types::L2MainnetService::Ankr - } - cketh_common::eth_rpc_client::providers::L2MainnetService::BlockPi => { - evm_rpc_types::L2MainnetService::BlockPi - } - cketh_common::eth_rpc_client::providers::L2MainnetService::PublicNode => { - evm_rpc_types::L2MainnetService::PublicNode - } - cketh_common::eth_rpc_client::providers::L2MainnetService::Llama => { - evm_rpc_types::L2MainnetService::Llama - } - } - } - - match service { - cketh_common::eth_rpc_client::providers::RpcService::Chain(id) => { - evm_rpc_types::RpcService::Chain(id) - } - cketh_common::eth_rpc_client::providers::RpcService::Provider(id) => { - evm_rpc_types::RpcService::Provider(id) - } - cketh_common::eth_rpc_client::providers::RpcService::Custom(rpc) => { - evm_rpc_types::RpcService::Custom(evm_rpc_types::RpcApi { - url: rpc.url, - headers: rpc.headers.map(|headers| { - headers - .into_iter() - .map(|header| evm_rpc_types::HttpHeader { - name: header.name, - value: header.value, - }) - .collect() - }), - }) - } - cketh_common::eth_rpc_client::providers::RpcService::EthMainnet(service) => { - evm_rpc_types::RpcService::EthMainnet(map_eth_mainnet_service(service)) - } - cketh_common::eth_rpc_client::providers::RpcService::EthSepolia(service) => { - evm_rpc_types::RpcService::EthSepolia(map_eth_sepolia_service(service)) - } - cketh_common::eth_rpc_client::providers::RpcService::ArbitrumOne(service) => { - evm_rpc_types::RpcService::ArbitrumOne(map_l2_mainnet_service(service)) - } - cketh_common::eth_rpc_client::providers::RpcService::BaseMainnet(service) => { - evm_rpc_types::RpcService::BaseMainnet(map_l2_mainnet_service(service)) - } - cketh_common::eth_rpc_client::providers::RpcService::OptimismMainnet(service) => { - evm_rpc_types::RpcService::OptimismMainnet(map_l2_mainnet_service(service)) - } - } -} - pub(super) fn into_provider_error( error: evm_rpc_types::ProviderError, ) -> cketh_common::eth_rpc::ProviderError { diff --git a/src/rpc_client/mod.rs b/src/rpc_client/mod.rs index e048e25d..cbf2cdb4 100644 --- a/src/rpc_client/mod.rs +++ b/src/rpc_client/mod.rs @@ -1,6 +1,6 @@ use crate::rpc_client::eth_rpc::{ are_errors_consistent, Block, BlockSpec, FeeHistory, FeeHistoryParams, GetBlockByNumberParams, - GetLogsParam, Hash, HttpResponsePayload, LogEntry, ResponseSizeEstimate, + GetLogsParam, Hash, HttpResponsePayload, JsonRpcResult, LogEntry, ResponseSizeEstimate, SendRawTransactionResult, HEADER_SIZE_LIMIT, }; use crate::rpc_client::numeric::TransactionCount; @@ -327,7 +327,7 @@ impl EthRpcClient { /// Aggregates responses of different providers to the same query. /// Guaranteed to be non-empty. -#[derive(Debug, Clone, PartialEq, Eq, CandidType)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct MultiCallResults { pub results: BTreeMap>, } @@ -342,6 +342,27 @@ impl MultiCallResults { } Self { results } } + + fn from_json_rpc_result< + I: IntoIterator, RpcError>)>, + >( + iter: I, + ) -> Self { + Self::from_non_empty_iter(iter.into_iter().map(|(provider, result)| { + ( + provider, + match result { + Ok(json_rpc_result) => match json_rpc_result { + JsonRpcResult::Result(value) => Ok(value), + JsonRpcResult::Error { code, message } => { + Err(RpcError::JsonRpcError(JsonRpcError { code, message })) + } + }, + Err(e) => Err(e), + }, + ) + })) + } } impl MultiCallResults { @@ -392,19 +413,18 @@ impl MultiCallResults { } } -#[derive(Debug, PartialEq, Eq, CandidType)] +#[derive(Debug, PartialEq, Eq)] pub enum SingleCallError { HttpOutcallError(HttpOutcallError), JsonRpcError { code: i64, message: String }, } -#[derive(Debug, PartialEq, Eq, CandidType)] + +#[derive(Debug, PartialEq, Eq)] pub enum MultiCallError { ConsistentError(RpcError), InconsistentResults(MultiCallResults), } -impl MultiCallError { - impl MultiCallResults { pub fn reduce_with_equality(self) -> Result> { let mut results = self.all_ok()?.into_iter(); diff --git a/src/rpc_client/tests.rs b/src/rpc_client/tests.rs index 1ecc54ef..77df3cbc 100644 --- a/src/rpc_client/tests.rs +++ b/src/rpc_client/tests.rs @@ -1,10 +1,11 @@ mod eth_rpc_client { - use crate::rpc_client::{EthRpcClient, EthereumNetwork}; - use evm_rpc_types::{EthMainnetService, EthSepoliaService, RpcService}; + use crate::rpc_client::{DefaultTransport, EthRpcClient, EthereumNetwork}; + use evm_rpc_types::{EthMainnetService, EthSepoliaService, RpcConfig, RpcService}; #[test] fn should_retrieve_sepolia_providers_in_stable_order() { - let client = EthRpcClient::new(EthereumNetwork::SEPOLIA, None); + let client: EthRpcClient = + EthRpcClient::new(EthereumNetwork::SEPOLIA, None, RpcConfig::default()); let providers = client.providers(); @@ -19,7 +20,8 @@ mod eth_rpc_client { #[test] fn should_retrieve_mainnet_providers_in_stable_order() { - let client = EthRpcClient::new(EthereumNetwork::MAINNET, None); + let client: EthRpcClient = + EthRpcClient::new(EthereumNetwork::MAINNET, None, RpcConfig::default()); let providers = client.providers(); @@ -45,7 +47,7 @@ mod multi_call_results { use crate::rpc_client::eth_rpc::JsonRpcResult; use crate::rpc_client::tests::multi_call_results::{ANKR, PUBLIC_NODE}; use crate::rpc_client::{MultiCallError, MultiCallResults}; - use evm_rpc_types::HttpOutcallError; + use evm_rpc_types::{HttpOutcallError, JsonRpcError, RpcError}; use ic_cdk::api::call::RejectionCode; #[test] @@ -59,17 +61,17 @@ mod multi_call_results { let results: MultiCallResults = MultiCallResults::from_non_empty_iter(vec![ ( ANKR, - Err(HttpOutcallError::IcError { + Err(RpcError::HttpOutcallError(HttpOutcallError::IcError { code: RejectionCode::CanisterReject, message: "reject".to_string(), - }), + })), ), ( PUBLIC_NODE, - Err(HttpOutcallError::IcError { + Err(RpcError::HttpOutcallError(HttpOutcallError::IcError { code: RejectionCode::SysTransient, message: "transient".to_string(), - }), + })), ), ]); @@ -80,7 +82,7 @@ mod multi_call_results { #[test] fn should_be_inconsistent_when_different_rpc_errors() { - let results: MultiCallResults = MultiCallResults::from_non_empty_iter(vec![ + let results: MultiCallResults = MultiCallResults::from_json_rpc_result(vec![ ( ANKR, Ok(JsonRpcResult::Error { @@ -104,7 +106,7 @@ mod multi_call_results { #[test] fn should_be_inconsistent_when_different_ok_results() { - let results: MultiCallResults = MultiCallResults::from_non_empty_iter(vec![ + let results: MultiCallResults = MultiCallResults::from_json_rpc_result(vec![ (ANKR, Ok(JsonRpcResult::Result("hello".to_string()))), (PUBLIC_NODE, Ok(JsonRpcResult::Result("world".to_string()))), ]); @@ -119,17 +121,17 @@ mod multi_call_results { let results: MultiCallResults = MultiCallResults::from_non_empty_iter(vec![ ( ANKR, - Err(HttpOutcallError::IcError { + Err(RpcError::HttpOutcallError(HttpOutcallError::IcError { code: RejectionCode::CanisterReject, message: "reject".to_string(), - }), + })), ), ( PUBLIC_NODE, - Err(HttpOutcallError::IcError { + Err(RpcError::HttpOutcallError(HttpOutcallError::IcError { code: RejectionCode::CanisterReject, message: "reject".to_string(), - }), + })), ), ]); @@ -137,18 +139,18 @@ mod multi_call_results { assert_eq!( reduced, - Err(MultiCallError::ConsistentHttpOutcallError( + Err(MultiCallError::ConsistentError(RpcError::HttpOutcallError( HttpOutcallError::IcError { code: RejectionCode::CanisterReject, message: "reject".to_string(), } - )) + ))) ); } #[test] fn should_be_consistent_rpc_error() { - let results: MultiCallResults = MultiCallResults::from_non_empty_iter(vec![ + let results: MultiCallResults = MultiCallResults::from_json_rpc_result(vec![ ( ANKR, Ok(JsonRpcResult::Error { @@ -169,16 +171,18 @@ mod multi_call_results { assert_eq!( reduced, - Err(MultiCallError::ConsistentJsonRpcError { - code: -32700, - message: "insufficient funds for gas * price + value".to_string(), - }) + Err(MultiCallError::ConsistentError(RpcError::JsonRpcError( + JsonRpcError { + code: -32700, + message: "insufficient funds for gas * price + value".to_string(), + } + ))) ); } #[test] fn should_be_consistent_ok_result() { - let results: MultiCallResults = MultiCallResults::from_non_empty_iter(vec![ + let results: MultiCallResults = MultiCallResults::from_json_rpc_result(vec![ (ANKR, Ok(JsonRpcResult::Result("0x01".to_string()))), (PUBLIC_NODE, Ok(JsonRpcResult::Result("0x01".to_string()))), ]); @@ -190,16 +194,16 @@ mod multi_call_results { } mod reduce_with_stable_majority_by_key { - use crate::eth_rpc::{FeeHistory, JsonRpcResult}; - use crate::eth_rpc_client::tests::multi_call_results::{ANKR, CLOUDFLARE, PUBLIC_NODE}; - use crate::eth_rpc_client::MultiCallError::ConsistentJsonRpcError; - use crate::eth_rpc_client::{MultiCallError, MultiCallResults}; - use crate::numeric::{BlockNumber, WeiPerGas}; + use crate::rpc_client::eth_rpc::{FeeHistory, JsonRpcResult}; + use crate::rpc_client::numeric::{BlockNumber, WeiPerGas}; + use crate::rpc_client::tests::multi_call_results::{ANKR, CLOUDFLARE, PUBLIC_NODE}; + use crate::rpc_client::{MultiCallError, MultiCallResults}; + use evm_rpc_types::{JsonRpcError, RpcError}; #[test] fn should_get_unanimous_fee_history() { let results: MultiCallResults = - MultiCallResults::from_non_empty_iter(vec![ + MultiCallResults::from_json_rpc_result(vec![ (ANKR, Ok(JsonRpcResult::Result(fee_history()))), (PUBLIC_NODE, Ok(JsonRpcResult::Result(fee_history()))), (CLOUDFLARE, Ok(JsonRpcResult::Result(fee_history()))), @@ -224,7 +228,7 @@ mod multi_call_results { let majority_fee = fees[index_majority].clone(); let [ankr_fee_history, cloudflare_fee_history, public_node_fee_history] = fees; let results: MultiCallResults = - MultiCallResults::from_non_empty_iter(vec![ + MultiCallResults::from_json_rpc_result(vec![ (ANKR, Ok(JsonRpcResult::Result(ankr_fee_history))), ( CLOUDFLARE, @@ -258,7 +262,7 @@ mod multi_call_results { ..fee_history() }; let three_distinct_results: MultiCallResults = - MultiCallResults::from_non_empty_iter(vec![ + MultiCallResults::from_json_rpc_result(vec![ (ANKR, Ok(JsonRpcResult::Result(ankr_fee_history.clone()))), ( PUBLIC_NODE, @@ -273,7 +277,7 @@ mod multi_call_results { assert_eq!( reduced, Err(MultiCallError::InconsistentResults( - MultiCallResults::from_non_empty_iter(vec![ + MultiCallResults::from_json_rpc_result(vec![ (ANKR, Ok(JsonRpcResult::Result(ankr_fee_history.clone()))), ( PUBLIC_NODE, @@ -284,7 +288,7 @@ mod multi_call_results { ); let two_distinct_results: MultiCallResults = - MultiCallResults::from_non_empty_iter(vec![ + MultiCallResults::from_json_rpc_result(vec![ (ANKR, Ok(JsonRpcResult::Result(ankr_fee_history.clone()))), ( PUBLIC_NODE, @@ -299,7 +303,7 @@ mod multi_call_results { assert_eq!( reduced, Err(MultiCallError::InconsistentResults( - MultiCallResults::from_non_empty_iter(vec![ + MultiCallResults::from_json_rpc_result(vec![ (ANKR, Ok(JsonRpcResult::Result(ankr_fee_history))), ( PUBLIC_NODE, @@ -321,7 +325,7 @@ mod multi_call_results { }; let results: MultiCallResults = - MultiCallResults::from_non_empty_iter(vec![ + MultiCallResults::from_json_rpc_result(vec![ (ANKR, Ok(JsonRpcResult::Result(fee.clone()))), ( PUBLIC_NODE, @@ -335,7 +339,7 @@ mod multi_call_results { assert_eq!( reduced, Err(MultiCallError::InconsistentResults( - MultiCallResults::from_non_empty_iter(vec![ + MultiCallResults::from_json_rpc_result(vec![ (ANKR, Ok(JsonRpcResult::Result(fee.clone()))), (PUBLIC_NODE, Ok(JsonRpcResult::Result(inconsistent_fee))), ]) @@ -346,7 +350,7 @@ mod multi_call_results { #[test] fn should_fail_upon_any_error() { let results: MultiCallResults = - MultiCallResults::from_non_empty_iter(vec![ + MultiCallResults::from_json_rpc_result(vec![ (ANKR, Ok(JsonRpcResult::Result(fee_history()))), ( PUBLIC_NODE, @@ -363,10 +367,12 @@ mod multi_call_results { assert_eq!( reduced, - Err(ConsistentJsonRpcError { - code: -32700, - message: "error".to_string() - }) + Err(MultiCallError::ConsistentError(RpcError::JsonRpcError( + JsonRpcError { + code: -32700, + message: "error".to_string() + } + ))) ); } @@ -381,6 +387,13 @@ mod multi_call_results { WeiPerGas::new(0x716724f03), WeiPerGas::new(0x73b467f76), ], + gas_used_ratio: vec![ + 0.6332004, + 0.47556506666666665, + 0.4432122666666667, + 0.4092196, + 0.5811903, + ], reward: vec![ vec![WeiPerGas::new(0x5f5e100)], vec![WeiPerGas::new(0x55d4a80)], @@ -394,9 +407,9 @@ mod multi_call_results { } mod eth_get_transaction_receipt { - use crate::eth_rpc::Hash; - use crate::eth_rpc_client::responses::{TransactionReceipt, TransactionStatus}; - use crate::numeric::{BlockNumber, GasAmount, WeiPerGas}; + use crate::rpc_client::eth_rpc::Hash; + use crate::rpc_client::numeric::{BlockNumber, GasAmount, WeiPerGas}; + use crate::rpc_client::responses::{TransactionReceipt, TransactionStatus}; use assert_matches::assert_matches; use proptest::proptest; use std::str::FromStr; @@ -437,6 +450,13 @@ mod eth_get_transaction_receipt { "0x0e59bd032b9b22aca5e2784e4cf114783512db00988c716cf17a1cc755a0a93d" ) .unwrap(), + contract_address: None, + from: "0x1789f79e95324a47c5fd6693071188e82e9a3558".to_string(), + logs: vec![], + logs_bloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".to_string(), + to: "0xdd2851cdd40ae6536831558dd46db62fac7a844d".to_string(), + transaction_index: 0x32_u32.into(), + r#type: "0x2".to_string(), } ) } @@ -481,10 +501,10 @@ mod eth_get_transaction_receipt { } mod eth_get_transaction_count { - use crate::address::Address; - use crate::eth_rpc::{BlockSpec, BlockTag}; - use crate::eth_rpc_client::requests::GetTransactionCountParams; - use crate::numeric::TransactionCount; + use crate::rpc_client::eth_rpc::{BlockSpec, BlockTag}; + use crate::rpc_client::numeric::TransactionCount; + use crate::rpc_client::requests::GetTransactionCountParams; + use ic_ethereum_types::Address; use std::str::FromStr; #[test] From 4e5ef50d3cbfd88639c576162c3ee17ff02631a2 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Fri, 13 Sep 2024 16:31:08 +0200 Subject: [PATCH 30/42] 243: clean-up --- src/candid_rpc/cketh_conversion.rs | 146 +---------------------------- src/rpc_client/eth_rpc/mod.rs | 17 ---- src/rpc_client/mod.rs | 15 ++- src/rpc_client/numeric/mod.rs | 15 --- src/rpc_client/numeric/tests.rs | 32 ++----- 5 files changed, 22 insertions(+), 203 deletions(-) diff --git a/src/candid_rpc/cketh_conversion.rs b/src/candid_rpc/cketh_conversion.rs index afed31cf..610e093e 100644 --- a/src/candid_rpc/cketh_conversion.rs +++ b/src/candid_rpc/cketh_conversion.rs @@ -5,7 +5,7 @@ use crate::rpc_client::checked_amount::CheckedAmountOf; use crate::rpc_client::eth_rpc::{Hash, Quantity}; use evm_rpc_types::{BlockTag, Hex, Hex20, Hex256, Hex32, HexByte, Nat256}; - +/**/ pub(super) fn into_block_spec(value: BlockTag) -> crate::rpc_client::eth_rpc::BlockSpec { use crate::rpc_client::eth_rpc::{self, BlockSpec}; match value { @@ -241,148 +241,6 @@ pub(super) fn into_rpc_services( } } -pub(super) fn into_provider_error( - error: evm_rpc_types::ProviderError, -) -> cketh_common::eth_rpc::ProviderError { - match error { - evm_rpc_types::ProviderError::NoPermission => { - cketh_common::eth_rpc::ProviderError::NoPermission - } - evm_rpc_types::ProviderError::TooFewCycles { expected, received } => { - cketh_common::eth_rpc::ProviderError::TooFewCycles { expected, received } - } - evm_rpc_types::ProviderError::ProviderNotFound => { - cketh_common::eth_rpc::ProviderError::ProviderNotFound - } - evm_rpc_types::ProviderError::MissingRequiredProvider => { - cketh_common::eth_rpc::ProviderError::MissingRequiredProvider - } - } -} - -pub(super) fn into_rpc_error(value: evm_rpc_types::RpcError) -> cketh_common::eth_rpc::RpcError { - fn map_http_outcall_error( - error: evm_rpc_types::HttpOutcallError, - ) -> cketh_common::eth_rpc::HttpOutcallError { - match error { - evm_rpc_types::HttpOutcallError::IcError { code, message } => { - cketh_common::eth_rpc::HttpOutcallError::IcError { code, message } - } - evm_rpc_types::HttpOutcallError::InvalidHttpJsonRpcResponse { - status, - body, - parsing_error, - } => cketh_common::eth_rpc::HttpOutcallError::InvalidHttpJsonRpcResponse { - status, - body, - parsing_error, - }, - } - } - - fn map_json_rpc_error( - error: evm_rpc_types::JsonRpcError, - ) -> cketh_common::eth_rpc::JsonRpcError { - cketh_common::eth_rpc::JsonRpcError { - code: error.code, - message: error.message, - } - } - - fn map_validation_error( - error: evm_rpc_types::ValidationError, - ) -> cketh_common::eth_rpc::ValidationError { - match error { - evm_rpc_types::ValidationError::Custom(message) => { - cketh_common::eth_rpc::ValidationError::Custom(message) - } - evm_rpc_types::ValidationError::InvalidHex(message) => { - cketh_common::eth_rpc::ValidationError::InvalidHex(message) - } - } - } - - match value { - evm_rpc_types::RpcError::ProviderError(error) => into_provider_error(error).into(), - evm_rpc_types::RpcError::HttpOutcallError(error) => map_http_outcall_error(error).into(), - evm_rpc_types::RpcError::JsonRpcError(error) => map_json_rpc_error(error).into(), - evm_rpc_types::RpcError::ValidationError(error) => map_validation_error(error).into(), - } -} - -fn from_provider_error( - error: cketh_common::eth_rpc::ProviderError, -) -> evm_rpc_types::ProviderError { - match error { - cketh_common::eth_rpc::ProviderError::NoPermission => { - evm_rpc_types::ProviderError::NoPermission - } - cketh_common::eth_rpc::ProviderError::TooFewCycles { expected, received } => { - evm_rpc_types::ProviderError::TooFewCycles { expected, received } - } - cketh_common::eth_rpc::ProviderError::ProviderNotFound => { - evm_rpc_types::ProviderError::ProviderNotFound - } - cketh_common::eth_rpc::ProviderError::MissingRequiredProvider => { - evm_rpc_types::ProviderError::MissingRequiredProvider - } - } -} - -pub(super) fn from_rpc_error(value: cketh_common::eth_rpc::RpcError) -> evm_rpc_types::RpcError { - fn map_http_outcall_error( - error: cketh_common::eth_rpc::HttpOutcallError, - ) -> evm_rpc_types::HttpOutcallError { - match error { - cketh_common::eth_rpc::HttpOutcallError::IcError { code, message } => { - evm_rpc_types::HttpOutcallError::IcError { code, message } - } - cketh_common::eth_rpc::HttpOutcallError::InvalidHttpJsonRpcResponse { - status, - body, - parsing_error, - } => evm_rpc_types::HttpOutcallError::InvalidHttpJsonRpcResponse { - status, - body, - parsing_error, - }, - } - } - - fn map_json_rpc_error( - error: cketh_common::eth_rpc::JsonRpcError, - ) -> evm_rpc_types::JsonRpcError { - evm_rpc_types::JsonRpcError { - code: error.code, - message: error.message, - } - } - - fn map_validation_error( - error: cketh_common::eth_rpc::ValidationError, - ) -> evm_rpc_types::ValidationError { - match error { - cketh_common::eth_rpc::ValidationError::Custom(message) => { - evm_rpc_types::ValidationError::Custom(message) - } - cketh_common::eth_rpc::ValidationError::InvalidHex(message) => { - evm_rpc_types::ValidationError::InvalidHex(message) - } - } - } - - match value { - cketh_common::eth_rpc::RpcError::ProviderError(error) => from_provider_error(error).into(), - cketh_common::eth_rpc::RpcError::HttpOutcallError(error) => { - map_http_outcall_error(error).into() - } - cketh_common::eth_rpc::RpcError::JsonRpcError(error) => map_json_rpc_error(error).into(), - cketh_common::eth_rpc::RpcError::ValidationError(error) => { - map_validation_error(error).into() - } - } -} - pub(super) fn into_hash(value: Hex32) -> Hash { Hash(value.into()) } @@ -400,7 +258,7 @@ fn into_quantity(value: Nat256) -> Quantity { } fn from_address(value: ic_ethereum_types::Address) -> evm_rpc_types::Hex20 { - // TODO 243: cketh_common::address::Address should expose the underlying [u8; 20] + // TODO 243: ic_ethereum_types::Address should expose the underlying [u8; 20] // so that there is no artificial error handling here. value .to_string() diff --git a/src/rpc_client/eth_rpc/mod.rs b/src/rpc_client/eth_rpc/mod.rs index 606b8ea9..0af31ef4 100644 --- a/src/rpc_client/eth_rpc/mod.rs +++ b/src/rpc_client/eth_rpc/mod.rs @@ -42,11 +42,6 @@ pub const MAX_PAYLOAD_SIZE: u64 = HTTP_MAX_SIZE - HEADER_SIZE_LIMIT; pub type Quantity = ethnum::u256; -pub fn into_nat(quantity: Quantity) -> candid::Nat { - use num_bigint::BigUint; - candid::Nat::from(BigUint::from_bytes_be(&quantity.to_be_bytes())) -} - #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] #[serde(transparent)] pub struct Data(#[serde(with = "ic_ethereum_types::serde_data")] pub Vec); @@ -566,23 +561,11 @@ fn cleanup_response(mut args: TransformArgs) -> HttpResponse { args.response } -pub fn is_http_outcall_error_response_too_large(error: &HttpOutcallError) -> bool { - match error { - HttpOutcallError::IcError { code, message } => { - code == &RejectionCode::SysFatal - && (message.contains("size limit") || message.contains("length limit")) - } - _ => false, - } -} - pub fn is_response_too_large(code: &RejectionCode, message: &str) -> bool { code == &RejectionCode::SysFatal && (message.contains("size limit") || message.contains("length limit")) } -pub type HttpOutcallResult = Result; - pub fn are_errors_consistent( left: &Result, right: &Result, diff --git a/src/rpc_client/mod.rs b/src/rpc_client/mod.rs index cbf2cdb4..c68807f4 100644 --- a/src/rpc_client/mod.rs +++ b/src/rpc_client/mod.rs @@ -1,6 +1,6 @@ use crate::rpc_client::eth_rpc::{ are_errors_consistent, Block, BlockSpec, FeeHistory, FeeHistoryParams, GetBlockByNumberParams, - GetLogsParam, Hash, HttpResponsePayload, JsonRpcResult, LogEntry, ResponseSizeEstimate, + GetLogsParam, Hash, HttpResponsePayload, LogEntry, ResponseSizeEstimate, SendRawTransactionResult, HEADER_SIZE_LIMIT, }; use crate::rpc_client::numeric::TransactionCount; @@ -11,7 +11,6 @@ use crate::rpc_client::providers::{ use crate::rpc_client::requests::GetTransactionCountParams; use crate::rpc_client::responses::TransactionReceipt; use async_trait::async_trait; -use candid::CandidType; use evm_rpc_types::{ HttpOutcallError, JsonRpcError, ProviderError, RpcApi, RpcConfig, RpcError, RpcService, }; @@ -343,8 +342,14 @@ impl MultiCallResults { Self { results } } + #[cfg(test)] fn from_json_rpc_result< - I: IntoIterator, RpcError>)>, + I: IntoIterator< + Item = ( + RpcService, + Result, RpcError>, + ), + >, >( iter: I, ) -> Self { @@ -353,8 +358,8 @@ impl MultiCallResults { provider, match result { Ok(json_rpc_result) => match json_rpc_result { - JsonRpcResult::Result(value) => Ok(value), - JsonRpcResult::Error { code, message } => { + crate::rpc_client::eth_rpc::JsonRpcResult::Result(value) => Ok(value), + crate::rpc_client::eth_rpc::JsonRpcResult::Error { code, message } => { Err(RpcError::JsonRpcError(JsonRpcError { code, message })) } }, diff --git a/src/rpc_client/numeric/mod.rs b/src/rpc_client/numeric/mod.rs index 850ff587..16b42129 100644 --- a/src/rpc_client/numeric/mod.rs +++ b/src/rpc_client/numeric/mod.rs @@ -11,21 +11,6 @@ pub type Wei = CheckedAmountOf; pub enum WeiPerGasUnit {} pub type WeiPerGas = CheckedAmountOf; -pub fn wei_from_milli_ether(value: u128) -> Wei { - const MILLI_ETHER: u64 = 1_000_000_000_000_000_000; - Wei::new(value) - .checked_mul(MILLI_ETHER) - .expect("any u128 multiplied by 10^15 always fits in a u256") -} - -pub enum TransactionNonceTag {} -/// Number of transactions sent by the sender. -/// Ethereum expects nonce to increase by 1 for each transaction. -/// If that's not the case, the transaction is rejected -/// (if the nonce was already seen in another transaction from the same sender) -/// or kept in the node's transaction pool while waiting for the missing nonce. -pub type TransactionNonce = CheckedAmountOf; - pub enum TransactionCountTag {} /// Number of transactions emitted by an address at a given block height (`finalized`, `safe` or `latest`). diff --git a/src/rpc_client/numeric/tests.rs b/src/rpc_client/numeric/tests.rs index 64434f1a..c33bfe65 100644 --- a/src/rpc_client/numeric/tests.rs +++ b/src/rpc_client/numeric/tests.rs @@ -1,5 +1,5 @@ mod transaction_nonce { - use crate::rpc_client::numeric::TransactionNonce; + use crate::rpc_client::numeric::TransactionCount; use assert_matches::assert_matches; use candid::Nat; use num_bigint::BigUint; @@ -7,16 +7,16 @@ mod transaction_nonce { #[test] fn should_overflow() { - let nonce = TransactionNonce::MAX; + let nonce = TransactionCount::MAX; assert_eq!(nonce.checked_increment(), None); } #[test] fn should_not_overflow() { - let nonce = TransactionNonce::MAX - .checked_sub(TransactionNonce::ONE) + let nonce = TransactionCount::MAX + .checked_sub(TransactionCount::ONE) .unwrap(); - assert_eq!(nonce.checked_increment(), Some(TransactionNonce::MAX)); + assert_eq!(nonce.checked_increment(), Some(TransactionCount::MAX)); } proptest! { @@ -25,8 +25,8 @@ mod transaction_nonce { let u256 = Nat(BigUint::from_bytes_be(&u256_bytes)); assert_eq!( - TransactionNonce::try_from(u256), - Ok(TransactionNonce::from_be_bytes(u256_bytes)) + TransactionCount::try_from(u256), + Ok(TransactionCount::from_be_bytes(u256_bytes)) ); } @@ -48,29 +48,17 @@ mod transaction_nonce { b"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; assert_eq!( - TransactionNonce::try_from(Nat( + TransactionCount::try_from(Nat( BigUint::parse_bytes(U256_MAX, 16).expect("Failed to parse u256 max") )), - Ok(TransactionNonce::MAX) + Ok(TransactionCount::MAX) ); let u256_max_plus_one: Nat = Nat(BigUint::parse_bytes(U256_MAX, 16).expect("Failed to parse u256 max")) + 1; assert_matches!( - TransactionNonce::try_from(u256_max_plus_one), + TransactionCount::try_from(u256_max_plus_one), Err(e) if e.contains("Nat does not fit in a U256") ); } } - -mod wei { - use crate::rpc_client::numeric::{wei_from_milli_ether, Wei}; - - #[test] - fn should_not_overflow_when_converting_from_milli_ether() { - assert_eq!( - wei_from_milli_ether(u128::MAX), - Wei::from_str_hex("0xDE0B6B3A763FFFFFFFFFFFFFFFFFFFFF21F494C589C0000").unwrap() - ); - } -} From 43004989dc70987278f374bb81780634ef704a6c Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Mon, 16 Sep 2024 10:00:49 +0200 Subject: [PATCH 31/42] 243: fix unit tests --- src/rpc_client/tests.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/rpc_client/tests.rs b/src/rpc_client/tests.rs index 77df3cbc..8e2e1f26 100644 --- a/src/rpc_client/tests.rs +++ b/src/rpc_client/tests.rs @@ -12,8 +12,11 @@ mod eth_rpc_client { assert_eq!( providers, &[ + RpcService::EthSepolia(EthSepoliaService::Alchemy), RpcService::EthSepolia(EthSepoliaService::Ankr), - RpcService::EthSepolia(EthSepoliaService::PublicNode) + RpcService::EthSepolia(EthSepoliaService::BlockPi), + RpcService::EthSepolia(EthSepoliaService::PublicNode), + RpcService::EthSepolia(EthSepoliaService::Sepolia) ] ); } @@ -28,9 +31,11 @@ mod eth_rpc_client { assert_eq!( providers, &[ + RpcService::EthMainnet(EthMainnetService::Alchemy), RpcService::EthMainnet(EthMainnetService::Ankr), RpcService::EthMainnet(EthMainnetService::PublicNode), - RpcService::EthMainnet(EthMainnetService::Cloudflare) + RpcService::EthMainnet(EthMainnetService::Cloudflare), + RpcService::EthMainnet(EthMainnetService::Llama) ] ); } @@ -199,6 +204,7 @@ mod multi_call_results { use crate::rpc_client::tests::multi_call_results::{ANKR, CLOUDFLARE, PUBLIC_NODE}; use crate::rpc_client::{MultiCallError, MultiCallResults}; use evm_rpc_types::{JsonRpcError, RpcError}; + use proptest::collection::vec; #[test] fn should_get_unanimous_fee_history() { @@ -365,15 +371,7 @@ mod multi_call_results { .clone() .reduce_with_strict_majority_by_key(|fee_history| fee_history.oldest_block); - assert_eq!( - reduced, - Err(MultiCallError::ConsistentError(RpcError::JsonRpcError( - JsonRpcError { - code: -32700, - message: "error".to_string() - } - ))) - ); + assert_eq!(reduced, Err(MultiCallError::InconsistentResults(results))); } fn fee_history() -> FeeHistory { From dfebc55847d99779771c155e461306df10b37740 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Mon, 16 Sep 2024 11:16:56 +0200 Subject: [PATCH 32/42] 243: remove cketh_common --- Cargo.lock | 842 ++++++++++++---------------------------------------- Cargo.toml | 5 +- src/main.rs | 140 +++++---- 3 files changed, 265 insertions(+), 722 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c3d1295..ea65a6d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,52 +118,6 @@ dependencies = [ "term", ] -[[package]] -name = "askama" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" -dependencies = [ - "askama_derive", - "askama_escape", - "humansize", - "num-traits", - "percent-encoding", - "serde", - "serde_json", -] - -[[package]] -name = "askama_derive" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" -dependencies = [ - "askama_parser", - "basic-toml", - "mime", - "mime_guess", - "proc-macro2", - "quote", - "serde", - "syn 2.0.77", -] - -[[package]] -name = "askama_escape" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" - -[[package]] -name = "askama_parser" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" -dependencies = [ - "nom", -] - [[package]] name = "asn1-rs" version = "0.5.2" @@ -372,15 +326,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "basic-toml" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" -dependencies = [ - "serde", -] - [[package]] name = "beef" version = "0.5.2" @@ -470,7 +415,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding", "generic-array", ] @@ -483,12 +427,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-padding" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" - [[package]] name = "borsh" version = "1.5.1" @@ -1263,7 +1201,7 @@ source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e505 dependencies = [ "candid", "dfn_core", - "ic-base-types 0.8.0", + "ic-base-types", "on_wire", "serde", ] @@ -1273,7 +1211,7 @@ name = "dfn_core" version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ - "ic-base-types 0.8.0", + "ic-base-types", "on_wire", ] @@ -1283,9 +1221,9 @@ version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ "dfn_core", - "ic-base-types 0.8.0", + "ic-base-types", "on_wire", - "prost 0.11.9", + "prost", ] [[package]] @@ -1510,7 +1448,7 @@ dependencies = [ "regex", "serde", "serde_json", - "sha3 0.10.8", + "sha3", "thiserror", "uint", ] @@ -1595,18 +1533,17 @@ dependencies = [ "futures", "getrandom 0.2.15", "hex", - "ic-base-types 0.8.0", + "ic-base-types", "ic-canister-log 0.2.0 (git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41)", - "ic-canisters-http-types 0.8.0", + "ic-canisters-http-types", "ic-cdk", "ic-cdk-macros", "ic-certified-map", - "ic-cketh-minter", "ic-config", - "ic-crypto-sha3 0.9.0 (git+https://github.com/dfinity/ic?rev=release-2024-09-12_01-30-base)", + "ic-crypto-sha3", "ic-crypto-test-utils-reproducible-rng", "ic-ethereum-types", - "ic-ic00-types 0.8.0", + "ic-ic00-types", "ic-metrics-encoder", "ic-nervous-system-common", "ic-stable-structures", @@ -2070,12 +2007,6 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" -[[package]] -name = "hex-literal" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" - [[package]] name = "hmac" version = "0.12.1" @@ -2128,15 +2059,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "humansize" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" -dependencies = [ - "libm", -] - [[package]] name = "humantime" version = "2.1.0" @@ -2240,7 +2162,7 @@ name = "ic-adapter-metrics-service" version = "0.1.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ - "prost 0.11.9", + "prost", "prost-build", "tonic", "tonic-build", @@ -2275,37 +2197,16 @@ dependencies = [ "candid", "comparable", "crc32fast", - "ic-crypto-sha2 0.8.0", - "ic-protobuf 0.8.0", + "ic-crypto-sha2", + "ic-protobuf", "ic-stable-structures", - "phantom_newtype 0.8.0", - "prost 0.11.9", + "phantom_newtype", + "prost", "serde", "strum 0.23.0", "strum_macros 0.23.1", ] -[[package]] -name = "ic-base-types" -version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41#1f551bea63354370b6e7a5037e96d464bdab3b41" -dependencies = [ - "base32", - "byte-unit", - "bytes", - "candid", - "comparable", - "crc32fast", - "ic-crypto-sha2 0.9.0", - "ic-protobuf 0.9.0", - "ic-stable-structures", - "phantom_newtype 0.9.0", - "prost 0.12.6", - "serde", - "strum 0.25.0", - "strum_macros 0.25.3", -] - [[package]] name = "ic-btc-interface" version = "0.1.0" @@ -2323,20 +2224,7 @@ source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e505 dependencies = [ "candid", "ic-btc-interface", - "ic-protobuf 0.8.0", - "serde", - "serde_bytes", -] - -[[package]] -name = "ic-btc-types-internal" -version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41#1f551bea63354370b6e7a5037e96d464bdab3b41" -dependencies = [ - "candid", - "ic-btc-interface", - "ic-error-types 0.9.0", - "ic-protobuf 0.9.0", + "ic-protobuf", "serde", "serde_bytes", ] @@ -2362,7 +2250,7 @@ name = "ic-canister-sandbox-backend-lib" version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ - "ic-base-types 0.8.0", + "ic-base-types", "ic-canister-sandbox-common", "ic-config", "ic-constants", @@ -2371,15 +2259,15 @@ dependencies = [ "ic-interfaces", "ic-logger", "ic-replicated-state", - "ic-sys 0.8.0", + "ic-sys", "ic-system-api", "ic-types", - "ic-utils 0.8.0", + "ic-utils", "ic-wasm-types", "libc", "libflate", "memory_tracker", - "nix 0.23.2", + "nix", "rayon", "serde_json", "slog", @@ -2397,12 +2285,12 @@ dependencies = [ "ic-interfaces", "ic-registry-subnet-type", "ic-replicated-state", - "ic-sys 0.8.0", + "ic-sys", "ic-system-api", "ic-types", - "ic-utils 0.8.0", + "ic-utils", "libc", - "nix 0.23.2", + "nix", "serde", "serde_bytes", ] @@ -2420,13 +2308,13 @@ dependencies = [ "ic-logger", "ic-metrics", "ic-replicated-state", - "ic-sys 0.8.0", + "ic-sys", "ic-system-api", "ic-types", "ic-wasm-types", "lazy_static", "libc", - "nix 0.23.2", + "nix", "once_cell", "prometheus", "regex", @@ -2445,34 +2333,24 @@ dependencies = [ "serde_bytes", ] -[[package]] -name = "ic-canisters-http-types" -version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41#1f551bea63354370b6e7a5037e96d464bdab3b41" -dependencies = [ - "candid", - "serde", - "serde_bytes", -] - [[package]] name = "ic-canonical-state" version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ - "ic-base-types 0.8.0", + "ic-base-types", "ic-canonical-state-tree-hash", "ic-certification-version", "ic-crypto-tree-hash", - "ic-error-types 0.8.0", - "ic-protobuf 0.8.0", + "ic-error-types", + "ic-protobuf", "ic-registry-routing-table", "ic-registry-subnet-type", "ic-replicated-state", "ic-types", "itertools 0.10.5", "leb128", - "phantom_newtype 0.8.0", + "phantom_newtype", "scoped_threadpool", "serde", "serde_bytes", @@ -2528,20 +2406,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "ic-cdk-timers" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198e55e4d9e069903fbea1dceae6fd28f7e0b38d5a4e1026ed2c772e1d55f5e0" -dependencies = [ - "futures", - "ic-cdk", - "ic0", - "serde", - "serde_bytes", - "slotmap", -] - [[package]] name = "ic-certification" version = "0.8.0" @@ -2577,57 +2441,16 @@ dependencies = [ "sha2 0.10.8", ] -[[package]] -name = "ic-cketh-minter" -version = "0.1.0" -source = "git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41#1f551bea63354370b6e7a5037e96d464bdab3b41" -dependencies = [ - "askama", - "async-trait", - "candid", - "ethnum", - "futures", - "hex", - "hex-literal 0.4.1", - "ic-canister-log 0.2.0 (git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41)", - "ic-canisters-http-types 0.9.0", - "ic-cdk", - "ic-cdk-macros", - "ic-cdk-timers", - "ic-crypto-ecdsa-secp256k1 0.9.0", - "ic-crypto-sha3 0.9.0 (git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41)", - "ic-ic00-types 0.9.0", - "ic-metrics-encoder", - "ic-stable-structures", - "ic-utils-ensure", - "icrc-ledger-client-cdk", - "icrc-ledger-types 0.1.4", - "minicbor", - "minicbor-derive", - "num-bigint 0.4.6", - "num-traits", - "phantom_newtype 0.9.0", - "rlp", - "serde", - "serde_bytes", - "serde_json", - "strum 0.25.0", - "strum_macros 0.25.3", - "thiserror", - "thousands", - "time", -] - [[package]] name = "ic-config" version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ "base64 0.11.0", - "ic-base-types 0.8.0", - "ic-protobuf 0.8.0", + "ic-base-types", + "ic-protobuf", "ic-registry-subnet-type", - "ic-sys 0.8.0", + "ic-sys", "ic-types", "json5", "serde", @@ -2661,7 +2484,7 @@ dependencies = [ "hex", "ic-adapter-metrics-server", "ic-async-utils", - "ic-base-types 0.8.0", + "ic-base-types", "ic-config", "ic-crypto-interfaces-sig-verification", "ic-crypto-internal-basic-sig-ed25519", @@ -2682,7 +2505,7 @@ dependencies = [ "ic-interfaces-registry", "ic-logger", "ic-metrics", - "ic-protobuf 0.8.0", + "ic-protobuf", "ic-registry-client-fake", "ic-registry-client-helpers", "ic-registry-keys", @@ -2712,20 +2535,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ic-crypto-ecdsa-secp256k1" -version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41#1f551bea63354370b6e7a5037e96d464bdab3b41" -dependencies = [ - "k256", - "lazy_static", - "num-bigint 0.4.6", - "pem 1.1.1", - "rand", - "simple_asn1", - "zeroize", -] - [[package]] name = "ic-crypto-ecdsa-secp256r1" version = "0.1.0" @@ -2805,7 +2614,7 @@ version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ "base64 0.11.0", - "ic-crypto-ecdsa-secp256k1 0.1.0", + "ic-crypto-ecdsa-secp256k1", "ic-crypto-internal-basic-sig-der-utils", "ic-crypto-internal-types", "ic-crypto-secrets-containers", @@ -2848,7 +2657,7 @@ dependencies = [ "ic-crypto-internal-seed", "ic-crypto-internal-types", "ic-crypto-secrets-containers", - "ic-protobuf 0.8.0", + "ic-protobuf", "ic-types", "rand", "rand_chacha", @@ -2867,7 +2676,7 @@ dependencies = [ "ic-certification", "ic-crypto-internal-basic-sig-der-utils", "ic-crypto-internal-types", - "ic-crypto-sha2 0.8.0", + "ic-crypto-sha2", "ic-crypto-tree-hash", "ic-types", "serde", @@ -2883,7 +2692,7 @@ source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e505 dependencies = [ "ic-crypto-getrandom-for-wasm", "ic-crypto-internal-basic-sig-der-utils", - "ic-crypto-sha2 0.8.0", + "ic-crypto-sha2", "ic-types", "num-bigint 0.4.6", "num-traits", @@ -2937,18 +2746,18 @@ dependencies = [ "ic-crypto-internal-types", "ic-crypto-node-key-validation", "ic-crypto-secrets-containers", - "ic-crypto-sha2 0.8.0", + "ic-crypto-sha2", "ic-crypto-standalone-sig-verifier", "ic-crypto-tls-interfaces", "ic-crypto-utils-time", "ic-interfaces", "ic-logger", "ic-metrics", - "ic-protobuf 0.8.0", + "ic-protobuf", "ic-types", - "ic-utils 0.8.0", + "ic-utils", "parking_lot 0.12.3", - "prost 0.11.9", + "prost", "rand", "rand_chacha", "rcgen 0.10.0", @@ -2975,7 +2784,7 @@ name = "ic-crypto-internal-hmac" version = "0.1.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ - "ic-crypto-internal-sha2 0.8.0", + "ic-crypto-internal-sha2", ] [[package]] @@ -3000,8 +2809,8 @@ dependencies = [ "ic-crypto-internal-bls12-381-type", "ic-crypto-internal-types", "ic-crypto-secrets-containers", - "ic-crypto-sha2 0.8.0", - "ic-protobuf 0.8.0", + "ic-crypto-sha2", + "ic-protobuf", "ic-types", "rand", "rand_chacha", @@ -3015,7 +2824,7 @@ version = "0.1.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ "hex", - "ic-crypto-sha2 0.8.0", + "ic-crypto-sha2", "ic-types", "rand", "rand_chacha", @@ -3031,14 +2840,6 @@ dependencies = [ "sha2 0.10.8", ] -[[package]] -name = "ic-crypto-internal-sha2" -version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41#1f551bea63354370b6e7a5037e96d464bdab3b41" -dependencies = [ - "sha2 0.10.8", -] - [[package]] name = "ic-crypto-internal-test-vectors" version = "0.8.0" @@ -3063,7 +2864,7 @@ dependencies = [ "ic-crypto-internal-threshold-sig-bls12381-der", "ic-crypto-internal-types", "ic-crypto-secrets-containers", - "ic-crypto-sha2 0.8.0", + "ic-crypto-sha2", "ic-types", "lazy_static", "parking_lot 0.12.3", @@ -3092,12 +2893,12 @@ source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e505 dependencies = [ "fe-derive", "hex", - "hex-literal 0.3.4", + "hex-literal", "ic-crypto-internal-hmac", "ic-crypto-internal-seed", "ic-crypto-internal-types", "ic-crypto-secrets-containers", - "ic-crypto-sha2 0.8.0", + "ic-crypto-sha2", "ic-types", "k256", "lazy_static", @@ -3137,8 +2938,8 @@ dependencies = [ "arrayvec 0.5.2", "base64 0.11.0", "hex", - "ic-protobuf 0.8.0", - "phantom_newtype 0.8.0", + "ic-protobuf", + "phantom_newtype", "serde", "serde_cbor", "strum 0.23.0", @@ -3153,7 +2954,7 @@ version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ "hex", - "ic-base-types 0.8.0", + "ic-base-types", "ic-crypto-internal-basic-sig-ed25519", "ic-crypto-internal-multi-sig-bls12381", "ic-crypto-internal-threshold-sig-bls12381", @@ -3161,7 +2962,7 @@ dependencies = [ "ic-crypto-internal-types", "ic-crypto-tls-cert-validation", "ic-interfaces", - "ic-protobuf 0.8.0", + "ic-protobuf", "ic-types", "serde", ] @@ -3171,7 +2972,7 @@ name = "ic-crypto-prng" version = "0.1.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ - "ic-crypto-sha2 0.8.0", + "ic-crypto-sha2", "ic-interfaces", "ic-types", "rand", @@ -3194,23 +2995,7 @@ name = "ic-crypto-sha2" version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ - "ic-crypto-internal-sha2 0.8.0", -] - -[[package]] -name = "ic-crypto-sha2" -version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41#1f551bea63354370b6e7a5037e96d464bdab3b41" -dependencies = [ - "ic-crypto-internal-sha2 0.9.0", -] - -[[package]] -name = "ic-crypto-sha3" -version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41#1f551bea63354370b6e7a5037e96d464bdab3b41" -dependencies = [ - "sha3 0.9.1", + "ic-crypto-internal-sha2", ] [[package]] @@ -3218,7 +3003,7 @@ name = "ic-crypto-sha3" version = "0.9.0" source = "git+https://github.com/dfinity/ic?rev=release-2024-09-12_01-30-base#afe1a18291987667fdb52dac3ca44b1aebf7176e" dependencies = [ - "sha3 0.10.8", + "sha3", ] [[package]] @@ -3235,7 +3020,7 @@ dependencies = [ "ic-crypto-internal-basic-sig-iccsa", "ic-crypto-internal-basic-sig-rsa-pkcs1", "ic-crypto-internal-types", - "ic-crypto-sha2 0.8.0", + "ic-crypto-sha2", "ic-types", ] @@ -3254,7 +3039,7 @@ version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ "hex", - "ic-protobuf 0.8.0", + "ic-protobuf", "ic-types", ] @@ -3273,10 +3058,10 @@ version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ "hex", - "ic-base-types 0.8.0", + "ic-base-types", "ic-crypto-internal-basic-sig-ed25519", "ic-crypto-internal-types", - "ic-protobuf 0.8.0", + "ic-protobuf", "ic-types", "serde", "x509-parser 0.14.0", @@ -3288,8 +3073,8 @@ version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ "async-trait", - "ic-crypto-sha2 0.8.0", - "ic-protobuf 0.8.0", + "ic-crypto-sha2", + "ic-protobuf", "ic-types", "serde", "tokio", @@ -3304,8 +3089,8 @@ source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e505 dependencies = [ "assert_matches", "ic-crypto-internal-types", - "ic-crypto-sha2 0.8.0", - "ic-protobuf 0.8.0", + "ic-crypto-sha2", + "ic-protobuf", "serde", "serde_bytes", "thiserror", @@ -3318,11 +3103,11 @@ source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e505 dependencies = [ "base64 0.11.0", "ed25519-consensus", - "ic-base-types 0.8.0", + "ic-base-types", "ic-crypto-internal-basic-sig-der-utils", "ic-crypto-internal-basic-sig-ed25519", "ic-crypto-internal-types", - "ic-protobuf 0.8.0", + "ic-protobuf", "simple_asn1", ] @@ -3364,7 +3149,7 @@ name = "ic-crypto-utils-tls" version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ - "ic-base-types 0.8.0", + "ic-base-types", "ic-crypto-tls-interfaces", "tokio-rustls", "x509-parser 0.15.1", @@ -3375,9 +3160,9 @@ name = "ic-cycles-account-manager" version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ - "ic-base-types 0.8.0", + "ic-base-types", "ic-config", - "ic-ic00-types 0.8.0", + "ic-ic00-types", "ic-interfaces", "ic-logger", "ic-nns-constants", @@ -3402,16 +3187,16 @@ dependencies = [ "ic-metrics", "ic-registry-subnet-type", "ic-replicated-state", - "ic-sys 0.8.0", + "ic-sys", "ic-system-api", "ic-types", - "ic-utils 0.8.0", + "ic-utils", "ic-utils-lru-cache", "ic-wasm-types", "libc", "libflate", "memory_tracker", - "nix 0.23.2", + "nix", "prometheus", "rayon", "serde", @@ -3430,30 +3215,19 @@ name = "ic-error-types" version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ - "ic-utils 0.8.0", + "ic-utils", "serde", "strum 0.23.0", "strum_macros 0.23.1", ] -[[package]] -name = "ic-error-types" -version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41#1f551bea63354370b6e7a5037e96d464bdab3b41" -dependencies = [ - "ic-utils 0.9.0", - "serde", - "strum 0.25.0", - "strum_macros 0.25.3", -] - [[package]] name = "ic-ethereum-types" version = "0.9.0" source = "git+https://github.com/dfinity/ic?rev=release-2024-09-12_01-30-base#afe1a18291987667fdb52dac3ca44b1aebf7176e" dependencies = [ "hex", - "ic-crypto-sha3 0.9.0 (git+https://github.com/dfinity/ic?rev=release-2024-09-12_01-30-base)", + "ic-crypto-sha3", "minicbor", "minicbor-derive", "serde", @@ -3468,7 +3242,7 @@ dependencies = [ "crossbeam-channel", "escargot", "hex", - "ic-base-types 0.8.0", + "ic-base-types", "ic-btc-interface", "ic-canister-sandbox-replica-controller", "ic-config", @@ -3478,8 +3252,8 @@ dependencies = [ "ic-crypto-tree-hash", "ic-cycles-account-manager", "ic-embedders", - "ic-error-types 0.8.0", - "ic-ic00-types 0.8.0", + "ic-error-types", + "ic-ic00-types", "ic-interfaces", "ic-interfaces-state-manager", "ic-logger", @@ -3491,18 +3265,18 @@ dependencies = [ "ic-registry-subnet-type", "ic-replicated-state", "ic-state-layout", - "ic-sys 0.8.0", + "ic-sys", "ic-system-api", "ic-types", - "ic-utils 0.8.0", + "ic-utils", "ic-utils-lru-cache", "ic-wasm-types", "lazy_static", "memory_tracker", - "nix 0.23.2", + "nix", "num-rational 0.2.4", "num-traits", - "phantom_newtype 0.8.0", + "phantom_newtype", "prometheus", "rand", "scoped_threadpool", @@ -3522,11 +3296,11 @@ version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ "candid", - "ic-base-types 0.8.0", + "ic-base-types", "ic-btc-interface", - "ic-btc-types-internal 0.1.0", - "ic-error-types 0.8.0", - "ic-protobuf 0.8.0", + "ic-btc-types-internal", + "ic-error-types", + "ic-protobuf", "num-traits", "serde", "serde_bytes", @@ -3535,25 +3309,6 @@ dependencies = [ "strum_macros 0.23.1", ] -[[package]] -name = "ic-ic00-types" -version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41#1f551bea63354370b6e7a5037e96d464bdab3b41" -dependencies = [ - "candid", - "ic-base-types 0.9.0", - "ic-btc-interface", - "ic-btc-types-internal 0.9.0", - "ic-error-types 0.9.0", - "ic-protobuf 0.9.0", - "num-traits", - "serde", - "serde_bytes", - "serde_cbor", - "strum 0.25.0", - "strum_macros 0.25.3", -] - [[package]] name = "ic-icrc1" version = "0.8.0" @@ -3562,12 +3317,12 @@ dependencies = [ "candid", "ciborium", "hex", - "ic-base-types 0.8.0", - "ic-crypto-sha2 0.8.0", + "ic-base-types", + "ic-crypto-sha2", "ic-ledger-canister-core", "ic-ledger-core", "ic-ledger-hash-of", - "icrc-ledger-types 0.1.2", + "icrc-ledger-types", "num-bigint 0.4.6", "num-traits", "serde", @@ -3583,20 +3338,20 @@ source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e505 dependencies = [ "async-trait", "derive_more 0.99.8-alpha.0", - "ic-base-types 0.8.0", + "ic-base-types", "ic-crypto-interfaces-sig-verification", "ic-crypto-tree-hash", - "ic-error-types 0.8.0", - "ic-ic00-types 0.8.0", + "ic-error-types", + "ic-ic00-types", "ic-interfaces-state-manager", - "ic-protobuf 0.8.0", + "ic-protobuf", "ic-registry-provisional-whitelist", "ic-registry-subnet-type", - "ic-sys 0.8.0", + "ic-sys", "ic-types", - "ic-utils 0.8.0", + "ic-utils", "ic-wasm-types", - "prost 0.11.9", + "prost", "rand", "serde", "serde_bytes", @@ -3618,7 +3373,7 @@ version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ "ic-types", - "prost 0.11.9", + "prost", "serde", ] @@ -3629,7 +3384,7 @@ source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e505 dependencies = [ "ic-crypto-tree-hash", "ic-types", - "phantom_newtype 0.8.0", + "phantom_newtype", "thiserror", ] @@ -3640,13 +3395,13 @@ source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e505 dependencies = [ "async-trait", "candid", - "ic-base-types 0.8.0", + "ic-base-types", "ic-canister-log 0.2.0 (git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01)", "ic-constants", - "ic-ic00-types 0.8.0", + "ic-ic00-types", "ic-ledger-core", "ic-ledger-hash-of", - "ic-utils 0.8.0", + "ic-utils", "num-traits", "serde", ] @@ -3681,7 +3436,7 @@ dependencies = [ "chrono", "ic-config", "ic-context-logger", - "ic-protobuf 0.8.0", + "ic-protobuf", "serde", "slog", "slog-async", @@ -3695,7 +3450,7 @@ name = "ic-messaging" version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ - "ic-base-types 0.8.0", + "ic-base-types", "ic-certification-version", "ic-config", "ic-constants", @@ -3703,15 +3458,15 @@ dependencies = [ "ic-crypto-tree-hash", "ic-crypto-utils-threshold-sig-der", "ic-cycles-account-manager", - "ic-error-types 0.8.0", - "ic-ic00-types 0.8.0", + "ic-error-types", + "ic-ic00-types", "ic-interfaces", "ic-interfaces-certified-stream-store", "ic-interfaces-registry", "ic-interfaces-state-manager", "ic-logger", "ic-metrics", - "ic-protobuf 0.8.0", + "ic-protobuf", "ic-registry-client-fake", "ic-registry-client-helpers", "ic-registry-keys", @@ -3722,7 +3477,7 @@ dependencies = [ "ic-registry-subnet-type", "ic-replicated-state", "ic-types", - "ic-utils 0.8.0", + "ic-utils", "prometheus", "slog", ] @@ -3765,11 +3520,11 @@ dependencies = [ "dfn_candid", "dfn_core", "dfn_protobuf", - "ic-base-types 0.8.0", + "ic-base-types", "ic-canister-log 0.2.0 (git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01)", - "ic-canisters-http-types 0.8.0", - "ic-crypto-sha2 0.8.0", - "ic-ic00-types 0.8.0", + "ic-canisters-http-types", + "ic-crypto-sha2", + "ic-ic00-types", "ic-icrc1", "ic-ledger-core", "ic-metrics-encoder", @@ -3777,12 +3532,12 @@ dependencies = [ "ic-nns-constants", "ic-stable-structures", "icp-ledger", - "icrc-ledger-types 0.1.2", + "icrc-ledger-types", "json5", "maplit", "mockall", "priority-queue", - "prost 0.11.9", + "prost", "rust_decimal", "serde", "serde_json", @@ -3797,7 +3552,7 @@ dependencies = [ "candid", "dfn_candid", "dfn_core", - "ic-base-types 0.8.0", + "ic-base-types", "ic-cdk", ] @@ -3806,7 +3561,7 @@ name = "ic-nns-constants" version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ - "ic-base-types 0.8.0", + "ic-base-types", "lazy_static", ] @@ -3819,22 +3574,7 @@ dependencies = [ "candid", "erased-serde", "maplit", - "prost 0.11.9", - "serde", - "serde_json", - "slog", -] - -[[package]] -name = "ic-protobuf" -version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41#1f551bea63354370b6e7a5037e96d464bdab3b41" -dependencies = [ - "bincode", - "candid", - "erased-serde", - "maplit", - "prost 0.12.6", + "prost", "serde", "serde_json", "slog", @@ -3854,10 +3594,10 @@ name = "ic-registry-client-helpers" version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ - "ic-base-types 0.8.0", - "ic-ic00-types 0.8.0", + "ic-base-types", + "ic-ic00-types", "ic-interfaces-registry", - "ic-protobuf 0.8.0", + "ic-protobuf", "ic-registry-common-proto", "ic-registry-keys", "ic-registry-provisional-whitelist", @@ -3873,7 +3613,7 @@ name = "ic-registry-common-proto" version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ - "prost 0.11.9", + "prost", ] [[package]] @@ -3882,8 +3622,8 @@ version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ "candid", - "ic-base-types 0.8.0", - "ic-ic00-types 0.8.0", + "ic-base-types", + "ic-ic00-types", "ic-types", "serde", ] @@ -3898,7 +3638,7 @@ dependencies = [ "ic-registry-common-proto", "ic-registry-transport", "ic-types", - "ic-utils 0.8.0", + "ic-utils", "thiserror", ] @@ -3907,8 +3647,8 @@ name = "ic-registry-provisional-whitelist" version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ - "ic-base-types 0.8.0", - "ic-protobuf 0.8.0", + "ic-base-types", + "ic-protobuf", ] [[package]] @@ -3917,8 +3657,8 @@ version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ "candid", - "ic-base-types 0.8.0", - "ic-protobuf 0.8.0", + "ic-base-types", + "ic-protobuf", "serde", ] @@ -3928,8 +3668,8 @@ version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ "candid", - "ic-ic00-types 0.8.0", - "ic-protobuf 0.8.0", + "ic-ic00-types", + "ic-protobuf", "serde", ] @@ -3939,7 +3679,7 @@ version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ "candid", - "ic-protobuf 0.8.0", + "ic-protobuf", "serde", "strum 0.23.0", "strum_macros 0.23.1", @@ -3952,9 +3692,9 @@ source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e505 dependencies = [ "bytes", "candid", - "ic-base-types 0.8.0", - "ic-protobuf 0.8.0", - "prost 0.11.9", + "ic-base-types", + "ic-protobuf", + "prost", "serde", ] @@ -3964,32 +3704,32 @@ version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ "cvt", - "ic-base-types 0.8.0", + "ic-base-types", "ic-btc-interface", - "ic-btc-types-internal 0.1.0", + "ic-btc-types-internal", "ic-certification-version", "ic-config", "ic-constants", "ic-crypto-internal-basic-sig-ed25519", - "ic-crypto-sha2 0.8.0", + "ic-crypto-sha2", "ic-crypto-test-utils-keys", - "ic-error-types 0.8.0", - "ic-ic00-types 0.8.0", + "ic-error-types", + "ic-ic00-types", "ic-interfaces", "ic-logger", - "ic-protobuf 0.8.0", + "ic-protobuf", "ic-registry-routing-table", "ic-registry-subnet-features", "ic-registry-subnet-type", - "ic-sys 0.8.0", + "ic-sys", "ic-types", - "ic-utils 0.8.0", + "ic-utils", "ic-wasm-types", "lazy_static", "libc", "maplit", - "nix 0.23.2", - "phantom_newtype 0.8.0", + "nix", + "phantom_newtype", "rand", "rand_chacha", "serde", @@ -4010,19 +3750,19 @@ version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ "hex", - "ic-base-types 0.8.0", - "ic-ic00-types 0.8.0", + "ic-base-types", + "ic-ic00-types", "ic-logger", "ic-metrics", - "ic-protobuf 0.8.0", + "ic-protobuf", "ic-replicated-state", - "ic-sys 0.8.0", + "ic-sys", "ic-types", - "ic-utils 0.8.0", + "ic-utils", "ic-wasm-types", "libc", "prometheus", - "prost 0.11.9", + "prost", "scoped_threadpool", "serde", "serde_bytes", @@ -4043,7 +3783,7 @@ dependencies = [ "ic-config", "ic-constants", "ic-crypto", - "ic-crypto-ecdsa-secp256k1 0.1.0", + "ic-crypto-ecdsa-secp256k1", "ic-crypto-extended-bip32", "ic-crypto-iccsa", "ic-crypto-internal-seed", @@ -4053,9 +3793,9 @@ dependencies = [ "ic-crypto-tree-hash", "ic-crypto-utils-threshold-sig-der", "ic-cycles-account-manager", - "ic-error-types 0.8.0", + "ic-error-types", "ic-execution-environment", - "ic-ic00-types 0.8.0", + "ic-ic00-types", "ic-interfaces", "ic-interfaces-certified-stream-store", "ic-interfaces-registry", @@ -4063,7 +3803,7 @@ dependencies = [ "ic-logger", "ic-messaging", "ic-metrics", - "ic-protobuf 0.8.0", + "ic-protobuf", "ic-registry-client-fake", "ic-registry-client-helpers", "ic-registry-keys", @@ -4098,30 +3838,30 @@ dependencies = [ "bit-vec 0.6.3", "crossbeam-channel", "hex", - "ic-base-types 0.8.0", + "ic-base-types", "ic-canonical-state", "ic-canonical-state-tree-hash", "ic-config", - "ic-crypto-sha2 0.8.0", + "ic-crypto-sha2", "ic-crypto-tree-hash", - "ic-error-types 0.8.0", + "ic-error-types", "ic-interfaces", "ic-interfaces-certified-stream-store", "ic-interfaces-state-manager", "ic-logger", "ic-metrics", - "ic-protobuf 0.8.0", + "ic-protobuf", "ic-registry-routing-table", "ic-registry-subnet-type", "ic-replicated-state", "ic-state-layout", - "ic-sys 0.8.0", + "ic-sys", "ic-types", - "ic-utils 0.8.0", - "nix 0.23.2", + "ic-utils", + "nix", "parking_lot 0.12.3", "prometheus", - "prost 0.11.9", + "prost", "rand", "rand_chacha", "scoped_threadpool", @@ -4139,25 +3879,11 @@ version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ "hex", - "ic-crypto-sha2 0.8.0", + "ic-crypto-sha2", "lazy_static", "libc", - "nix 0.23.2", - "phantom_newtype 0.8.0", - "wsl", -] - -[[package]] -name = "ic-sys" -version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41#1f551bea63354370b6e7a5037e96d464bdab3b41" -dependencies = [ - "hex", - "ic-crypto-sha2 0.9.0", - "lazy_static", - "libc", - "nix 0.24.3", - "phantom_newtype 0.9.0", + "nix", + "phantom_newtype", "wsl", ] @@ -4167,22 +3893,22 @@ version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ "candid", - "ic-base-types 0.8.0", + "ic-base-types", "ic-btc-interface", "ic-config", "ic-constants", "ic-cycles-account-manager", - "ic-error-types 0.8.0", - "ic-ic00-types 0.8.0", + "ic-error-types", + "ic-ic00-types", "ic-interfaces", "ic-logger", "ic-nns-constants", "ic-registry-routing-table", "ic-registry-subnet-type", "ic-replicated-state", - "ic-sys 0.8.0", + "ic-sys", "ic-types", - "ic-utils 0.8.0", + "ic-utils", "ic-wasm-types", "prometheus", "serde", @@ -4227,7 +3953,7 @@ source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e505 dependencies = [ "ic-interfaces", "ic-interfaces-registry", - "ic-protobuf 0.8.0", + "ic-protobuf", "ic-registry-client-fake", "ic-registry-keys", "ic-registry-proto-data-provider", @@ -4247,20 +3973,20 @@ dependencies = [ "chrono", "derive_more 0.99.8-alpha.0", "hex", - "ic-base-types 0.8.0", - "ic-btc-types-internal 0.1.0", + "ic-base-types", + "ic-btc-types-internal", "ic-constants", "ic-crypto-internal-types", - "ic-crypto-sha2 0.8.0", + "ic-crypto-sha2", "ic-crypto-tree-hash", - "ic-error-types 0.8.0", - "ic-ic00-types 0.8.0", - "ic-protobuf 0.8.0", - "ic-utils 0.8.0", + "ic-error-types", + "ic-ic00-types", + "ic-protobuf", + "ic-utils", "maplit", "once_cell", - "phantom_newtype 0.8.0", - "prost 0.11.9", + "phantom_newtype", + "prost", "serde", "serde_bytes", "serde_cbor", @@ -4279,38 +4005,16 @@ source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e505 dependencies = [ "cvt", "hex", - "ic-sys 0.8.0", - "libc", - "nix 0.23.2", - "prost 0.11.9", - "rand", - "scoped_threadpool", - "serde", - "thiserror", -] - -[[package]] -name = "ic-utils" -version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41#1f551bea63354370b6e7a5037e96d464bdab3b41" -dependencies = [ - "cvt", - "hex", - "ic-sys 0.9.0", + "ic-sys", "libc", - "nix 0.24.3", - "prost 0.12.6", + "nix", + "prost", "rand", "scoped_threadpool", "serde", "thiserror", ] -[[package]] -name = "ic-utils-ensure" -version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41#1f551bea63354370b6e7a5037e96d464bdab3b41" - [[package]] name = "ic-utils-lru-cache" version = "0.1.0" @@ -4325,11 +4029,11 @@ name = "ic-wasm-types" version = "0.8.0" source = "git+https://github.com/dfinity/ic?rev=release-2023-09-27_23-01#ca5e5052886de781021506814d2c6502e375da48" dependencies = [ - "ic-crypto-sha2 0.8.0", - "ic-protobuf 0.8.0", - "ic-sys 0.8.0", + "ic-crypto-sha2", + "ic-protobuf", + "ic-sys", "ic-types", - "ic-utils 0.8.0", + "ic-utils", "serde", ] @@ -4366,17 +4070,17 @@ dependencies = [ "dfn_core", "dfn_protobuf", "hex", - "ic-base-types 0.8.0", - "ic-crypto-sha2 0.8.0", + "ic-base-types", + "ic-crypto-sha2", "ic-ledger-canister-core", "ic-ledger-core", "ic-ledger-hash-of", - "icrc-ledger-types 0.1.2", + "icrc-ledger-types", "lazy_static", "num-traits", "on_wire", - "prost 0.11.9", - "prost-derive 0.11.9", + "prost", + "prost-derive", "serde", "serde_bytes", "serde_cbor", @@ -4384,28 +4088,6 @@ dependencies = [ "strum_macros 0.24.3", ] -[[package]] -name = "icrc-ledger-client" -version = "0.1.2" -source = "git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41#1f551bea63354370b6e7a5037e96d464bdab3b41" -dependencies = [ - "async-trait", - "candid", - "icrc-ledger-types 0.1.4", - "serde", -] - -[[package]] -name = "icrc-ledger-client-cdk" -version = "0.1.2" -source = "git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41#1f551bea63354370b6e7a5037e96d464bdab3b41" -dependencies = [ - "async-trait", - "candid", - "ic-cdk", - "icrc-ledger-client", -] - [[package]] name = "icrc-ledger-types" version = "0.1.2" @@ -4421,22 +4103,6 @@ dependencies = [ "sha2 0.10.8", ] -[[package]] -name = "icrc-ledger-types" -version = "0.1.4" -source = "git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41#1f551bea63354370b6e7a5037e96d464bdab3b41" -dependencies = [ - "base32", - "candid", - "crc32fast", - "hex", - "num-bigint 0.4.6", - "num-traits", - "serde", - "serde_bytes", - "sha2 0.10.8", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -4561,15 +4227,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.11" @@ -4846,11 +4503,11 @@ dependencies = [ "ic-config", "ic-logger", "ic-replicated-state", - "ic-sys 0.8.0", - "ic-utils 0.8.0", + "ic-sys", + "ic-utils", "lazy_static", "libc", - "nix 0.23.2", + "nix", "slog", ] @@ -4860,16 +4517,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - [[package]] name = "minicbor" version = "0.19.1" @@ -4969,18 +4616,6 @@ dependencies = [ "memoffset 0.6.5", ] -[[package]] -name = "nix" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.6.5", -] - [[package]] name = "nom" version = "7.1.3" @@ -5479,16 +5114,6 @@ dependencies = [ "slog", ] -[[package]] -name = "phantom_newtype" -version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=1f551bea63354370b6e7a5037e96d464bdab3b41#1f551bea63354370b6e7a5037e96d464bdab3b41" -dependencies = [ - "candid", - "serde", - "slog", -] - [[package]] name = "phf_shared" version = "0.10.0" @@ -5794,17 +5419,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" dependencies = [ "bytes", - "prost-derive 0.11.9", -] - -[[package]] -name = "prost" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" -dependencies = [ - "bytes", - "prost-derive 0.12.6", + "prost-derive", ] [[package]] @@ -5821,7 +5436,7 @@ dependencies = [ "multimap", "petgraph", "prettyplease", - "prost 0.11.9", + "prost", "prost-types", "regex", "syn 1.0.109", @@ -5842,26 +5457,13 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "prost-derive" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" -dependencies = [ - "anyhow", - "itertools 0.12.1", - "proc-macro2", - "quote", - "syn 2.0.77", -] - [[package]] name = "prost-types" version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" dependencies = [ - "prost 0.11.9", + "prost", ] [[package]] @@ -6534,18 +6136,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "sha3" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "keccak", - "opaque-debug", -] - [[package]] name = "sha3" version = "0.10.8" @@ -6687,15 +6277,6 @@ dependencies = [ "time", ] -[[package]] -name = "slotmap" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" -dependencies = [ - "version_check", -] - [[package]] name = "smallvec" version = "1.13.2" @@ -6796,15 +6377,6 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" -[[package]] -name = "strum" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" -dependencies = [ - "strum_macros 0.25.3", -] - [[package]] name = "strum" version = "0.26.3" @@ -6840,19 +6412,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "strum_macros" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.77", -] - [[package]] name = "strum_macros" version = "0.26.4" @@ -7288,7 +6847,7 @@ dependencies = [ "hyper-timeout", "percent-encoding", "pin-project", - "prost 0.11.9", + "prost", "tokio", "tokio-stream", "tower", @@ -7452,15 +7011,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" -[[package]] -name = "unicase" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - [[package]] name = "unicode-bidi" version = "0.3.15" diff --git a/Cargo.toml b/Cargo.toml index be06b553..a3cf3953 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,6 @@ ic-canister-log = { git = "https://github.com/dfinity/ic", rev = "1f551bea633543 ic-cdk = { workspace = true } ic-cdk-macros = { workspace = true } ic-certified-map = { workspace = true } -cketh-common = { git = "https://github.com/dfinity/ic", rev = "1f551bea63354370b6e7a5037e96d464bdab3b41", package = "ic-cketh-minter" } maplit = "1.0" minicbor = { workspace = true } minicbor-derive = { workspace = true } @@ -47,7 +46,7 @@ url = "2.5" async-trait = "0.1" hex = "0.4" ethers-core = "2.0" -zeroize = "1.8" +zeroize = {version = "1.8", features = ["zeroize_derive"]} [dev-dependencies] assert_matches = "1.5" @@ -62,7 +61,7 @@ rand = "0.8" [workspace.dependencies] candid = { version = "0.9" } -ethnum = "1.5.0" +ethnum = { version = "1.5.0", features = ["serde"] } futures = "0.3.30" getrandom = { version = "0.2", features = ["custom"] } hex = "0.4.3" diff --git a/src/main.rs b/src/main.rs index e750dd15..193be6d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,4 @@ use candid::candid_method; -use cketh_common::logs::INFO; use evm_rpc::accounting::{get_cost_with_collateral, get_http_request_cost}; use evm_rpc::candid_rpc::CandidRpcClient; use evm_rpc::constants::NODES_IN_SUBNET; @@ -11,13 +10,8 @@ use evm_rpc::memory::{ use evm_rpc::metrics::encode_metrics; use evm_rpc::providers::{find_provider, resolve_rpc_service, PROVIDERS, SERVICE_PROVIDER_MAP}; use evm_rpc::types::{Provider, ProviderId, RpcAccess}; -use evm_rpc::{ - http::{json_rpc_request, transform_http_request}, - memory::UNSTABLE_METRICS, - types::{InitArgs, MetricRpcMethod, Metrics}, -}; +use evm_rpc::{http::{json_rpc_request, transform_http_request}, log, memory::UNSTABLE_METRICS, types::{InitArgs, MetricRpcMethod, Metrics}}; use evm_rpc_types::{Hex32, MultiRpcResult, RpcResult}; -use ic_canister_log::log; use ic_canisters_http_types::{ HttpRequest as AssetHttpRequest, HttpResponse as AssetHttpResponse, HttpResponseBuilder, }; @@ -234,72 +228,72 @@ fn post_upgrade(args: InitArgs) { fn http_request(request: AssetHttpRequest) -> AssetHttpResponse { match request.path() { "/metrics" => serve_metrics(encode_metrics), - "/logs" => { - use cketh_common::logs::{Log, Priority, Sort}; - use std::str::FromStr; - - let max_skip_timestamp = match request.raw_query_param("time") { - Some(arg) => match u64::from_str(arg) { - Ok(value) => value, - Err(_) => { - return HttpResponseBuilder::bad_request() - .with_body_and_content_length("failed to parse the 'time' parameter") - .build() - } - }, - None => 0, - }; - - let mut log: Log = Default::default(); - - match request.raw_query_param("priority").map(Priority::from_str) { - Some(Ok(priority)) => match priority { - Priority::Info => log.push_logs(Priority::Info), - Priority::Debug => log.push_logs(Priority::Debug), - Priority::TraceHttp => {} - }, - _ => { - log.push_logs(Priority::Info); - log.push_logs(Priority::Debug); - } - } - - log.entries - .retain(|entry| entry.timestamp >= max_skip_timestamp); - - fn ordering_from_query_params(sort: Option<&str>, max_skip_timestamp: u64) -> Sort { - match sort { - Some(ord_str) => match Sort::from_str(ord_str) { - Ok(order) => order, - Err(_) => { - if max_skip_timestamp == 0 { - Sort::Ascending - } else { - Sort::Descending - } - } - }, - None => { - if max_skip_timestamp == 0 { - Sort::Ascending - } else { - Sort::Descending - } - } - } - } - - log.sort_logs(ordering_from_query_params( - request.raw_query_param("sort"), - max_skip_timestamp, - )); - - const MAX_BODY_SIZE: usize = 3_000_000; - HttpResponseBuilder::ok() - .header("Content-Type", "application/json; charset=utf-8") - .with_body_and_content_length(log.serialize_logs(MAX_BODY_SIZE)) - .build() - } + // TODO 243: re-enable logs + // "/logs" => { + // use std::str::FromStr; + // + // let max_skip_timestamp = match request.raw_query_param("time") { + // Some(arg) => match u64::from_str(arg) { + // Ok(value) => value, + // Err(_) => { + // return HttpResponseBuilder::bad_request() + // .with_body_and_content_length("failed to parse the 'time' parameter") + // .build() + // } + // }, + // None => 0, + // }; + // + // let mut log: Log = Default::default(); + // + // match request.raw_query_param("priority").map(Priority::from_str) { + // Some(Ok(priority)) => match priority { + // Priority::Info => log.push_logs(Priority::Info), + // Priority::Debug => log.push_logs(Priority::Debug), + // Priority::TraceHttp => {} + // }, + // _ => { + // log.push_logs(Priority::Info); + // log.push_logs(Priority::Debug); + // } + // } + // + // log.entries + // .retain(|entry| entry.timestamp >= max_skip_timestamp); + // + // fn ordering_from_query_params(sort: Option<&str>, max_skip_timestamp: u64) -> Sort { + // match sort { + // Some(ord_str) => match Sort::from_str(ord_str) { + // Ok(order) => order, + // Err(_) => { + // if max_skip_timestamp == 0 { + // Sort::Ascending + // } else { + // Sort::Descending + // } + // } + // }, + // None => { + // if max_skip_timestamp == 0 { + // Sort::Ascending + // } else { + // Sort::Descending + // } + // } + // } + // } + // + // log.sort_logs(ordering_from_query_params( + // request.raw_query_param("sort"), + // max_skip_timestamp, + // )); + // + // const MAX_BODY_SIZE: usize = 3_000_000; + // HttpResponseBuilder::ok() + // .header("Content-Type", "application/json; charset=utf-8") + // .with_body_and_content_length(log.serialize_logs(MAX_BODY_SIZE)) + // .build() + // } _ => HttpResponseBuilder::not_found().build(), } } From d0c80940f5c18c57476009b76fa9fb3805f97367 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Mon, 16 Sep 2024 11:28:29 +0200 Subject: [PATCH 33/42] 243: Clippy --- src/candid_rpc.rs | 8 +------- src/candid_rpc/cketh_conversion.rs | 12 ++++++------ src/rpc_client/checked_amount/mod.rs | 2 +- src/rpc_client/eth_rpc/mod.rs | 2 +- src/rpc_client/mod.rs | 2 +- src/rpc_client/tests.rs | 2 -- 6 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/candid_rpc.rs b/src/candid_rpc.rs index 2860a7d2..c5d42c1b 100644 --- a/src/candid_rpc.rs +++ b/src/candid_rpc.rs @@ -99,13 +99,7 @@ fn process_result(method: RpcMethod, result: Result>) -> ) } }); - MultiRpcResult::Inconsistent( - multi_call_results - .results - .into_iter() - .map(|(service, result)| (service, result)) - .collect(), - ) + MultiRpcResult::Inconsistent(multi_call_results.results.into_iter().collect()) } }, } diff --git a/src/candid_rpc/cketh_conversion.rs b/src/candid_rpc/cketh_conversion.rs index 610e093e..777de11e 100644 --- a/src/candid_rpc/cketh_conversion.rs +++ b/src/candid_rpc/cketh_conversion.rs @@ -211,32 +211,32 @@ pub(super) fn into_rpc_services( services, } => services .into_iter() - .map(|service| evm_rpc_types::RpcService::Custom(service)) + .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(|service| evm_rpc_types::RpcService::EthMainnet(service)) + .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(|service| evm_rpc_types::RpcService::EthSepolia(service)) + .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(|service| evm_rpc_types::RpcService::ArbitrumOne(service)) + .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(|service| evm_rpc_types::RpcService::BaseMainnet(service)) + .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(|service| evm_rpc_types::RpcService::OptimismMainnet(service)) + .map(evm_rpc_types::RpcService::OptimismMainnet) .collect(), } } diff --git a/src/rpc_client/checked_amount/mod.rs b/src/rpc_client/checked_amount/mod.rs index 228382e2..b8b9c755 100644 --- a/src/rpc_client/checked_amount/mod.rs +++ b/src/rpc_client/checked_amount/mod.rs @@ -261,7 +261,7 @@ impl<'de, Unit> Deserialize<'de> for CheckedAmountOf { impl Default for CheckedAmountVisitor { fn default() -> Self { Self { - phantom: PhantomData::default(), + phantom: PhantomData, } } } diff --git a/src/rpc_client/eth_rpc/mod.rs b/src/rpc_client/eth_rpc/mod.rs index 0af31ef4..61c489d8 100644 --- a/src/rpc_client/eth_rpc/mod.rs +++ b/src/rpc_client/eth_rpc/mod.rs @@ -18,7 +18,6 @@ use ic_cdk::api::management_canister::http_request::{ }; use ic_cdk_macros::query; use ic_ethereum_types::Address; -pub use metrics::encode as encode_metrics; use minicbor::{Decode, Encode}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::fmt; @@ -751,6 +750,7 @@ fn sort_by_hash(to_sort: &mut [T]) { }); } +#[allow(dead_code)] //TODO 243: hook-up to existing metrics pub(super) mod metrics { use ic_metrics_encoder::MetricsEncoder; use std::cell::RefCell; diff --git a/src/rpc_client/mod.rs b/src/rpc_client/mod.rs index c68807f4..219b01c8 100644 --- a/src/rpc_client/mod.rs +++ b/src/rpc_client/mod.rs @@ -388,7 +388,7 @@ impl MultiCallResults { first_error = Some((provider.clone(), result)); } Some((first_error_provider, error)) => { - if !are_errors_consistent(&error, result) { + if !are_errors_consistent(error, result) { return Err(MultiCallError::InconsistentResults(self)); } first_error = Some((first_error_provider, error)); diff --git a/src/rpc_client/tests.rs b/src/rpc_client/tests.rs index 8e2e1f26..27008169 100644 --- a/src/rpc_client/tests.rs +++ b/src/rpc_client/tests.rs @@ -203,8 +203,6 @@ mod multi_call_results { use crate::rpc_client::numeric::{BlockNumber, WeiPerGas}; use crate::rpc_client::tests::multi_call_results::{ANKR, CLOUDFLARE, PUBLIC_NODE}; use crate::rpc_client::{MultiCallError, MultiCallResults}; - use evm_rpc_types::{JsonRpcError, RpcError}; - use proptest::collection::vec; #[test] fn should_get_unanimous_fee_history() { From 79946f54ae3d53871c8e7fbbd7d6117fff048f3e Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Mon, 16 Sep 2024 11:57:07 +0200 Subject: [PATCH 34/42] 243: format --- src/rpc_client/eth_rpc/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc_client/eth_rpc/tests.rs b/src/rpc_client/eth_rpc/tests.rs index 771763c3..856a5399 100644 --- a/src/rpc_client/eth_rpc/tests.rs +++ b/src/rpc_client/eth_rpc/tests.rs @@ -497,6 +497,6 @@ cketh_eth_rpc_call_retry_count_bucket{method="eth_test2",le="+Inf"} 4 12346789 cketh_eth_rpc_call_retry_count_sum{method="eth_test2"} 6 12346789 cketh_eth_rpc_call_retry_count_count{method="eth_test2"} 4 12346789 "# - .trim() + .trim() ); } From 714a12a5b8203c87ed463b0925fcd9ed52efa45d Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Mon, 16 Sep 2024 12:53:18 +0200 Subject: [PATCH 35/42] 243: format --- src/main.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 193be6d5..47d9ef5f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,12 @@ use evm_rpc::memory::{ use evm_rpc::metrics::encode_metrics; use evm_rpc::providers::{find_provider, resolve_rpc_service, PROVIDERS, SERVICE_PROVIDER_MAP}; use evm_rpc::types::{Provider, ProviderId, RpcAccess}; -use evm_rpc::{http::{json_rpc_request, transform_http_request}, log, memory::UNSTABLE_METRICS, types::{InitArgs, MetricRpcMethod, Metrics}}; +use evm_rpc::{ + http::{json_rpc_request, transform_http_request}, + log, + memory::UNSTABLE_METRICS, + types::{InitArgs, MetricRpcMethod, Metrics}, +}; use evm_rpc_types::{Hex32, MultiRpcResult, RpcResult}; use ic_canisters_http_types::{ HttpRequest as AssetHttpRequest, HttpResponse as AssetHttpResponse, HttpResponseBuilder, From 3c6b98b9daf0192895ff3b0cbe780566307f31c2 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Mon, 16 Sep 2024 15:37:58 +0200 Subject: [PATCH 36/42] 243: copied logs --- src/lib.rs | 1 + src/logs/mod.rs | 306 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 307 insertions(+) create mode 100644 src/logs/mod.rs diff --git a/src/lib.rs b/src/lib.rs index 1a0dc08f..f58df96a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ pub mod accounting; pub mod candid_rpc; pub mod constants; pub mod http; +pub mod logs; pub mod memory; pub mod metrics; pub mod providers; diff --git a/src/logs/mod.rs b/src/logs/mod.rs new file mode 100644 index 00000000..f56f7fe8 --- /dev/null +++ b/src/logs/mod.rs @@ -0,0 +1,306 @@ +#[cfg(test)] +mod tests; + +use ic_canister_log::{declare_log_buffer, export as export_logs, GlobalBuffer, Sink}; +use serde::Deserialize; +use std::str::FromStr; + +// High-priority messages. +declare_log_buffer!(name = INFO_BUF, capacity = 1000); + +// Low-priority info messages. +declare_log_buffer!(name = DEBUG_BUF, capacity = 1000); + +// Trace of HTTP requests and responses. +declare_log_buffer!(name = TRACE_HTTP_BUF, capacity = 1000); + +pub const INFO: PrintProxySink = PrintProxySink("INFO", &INFO_BUF); +pub const DEBUG: PrintProxySink = PrintProxySink("DEBUG", &DEBUG_BUF); +pub const TRACE_HTTP: PrintProxySink = PrintProxySink("TRACE_HTTP", &TRACE_HTTP_BUF); + +#[derive(Debug)] +pub struct PrintProxySink(&'static str, &'static GlobalBuffer); + +impl Sink for PrintProxySink { + fn append(&self, entry: ic_canister_log::LogEntry) { + ic_cdk::println!("{} {}:{} {}", self.0, entry.file, entry.line, entry.message); + self.1.append(entry) + } +} + +#[derive(Copy, Clone, Debug, Deserialize, serde::Serialize)] +pub enum Priority { + Info, + TraceHttp, + Debug, +} + +impl FromStr for Priority { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "info" => Ok(Priority::Info), + "trace_http" => Ok(Priority::TraceHttp), + "debug" => Ok(Priority::Debug), + _ => Err("could not recognize priority".to_string()), + } + } +} + +#[derive(Copy, Clone, Debug, Deserialize, serde::Serialize)] +pub enum Sort { + Ascending, + Descending, +} + +impl FromStr for Sort { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "asc" => Ok(Sort::Ascending), + "desc" => Ok(Sort::Descending), + _ => Err("could not recognize sort order".to_string()), + } + } +} + +#[derive(Clone, Debug, Deserialize, serde::Serialize)] +pub struct LogEntry { + pub timestamp: u64, + pub priority: Priority, + pub file: String, + pub line: u32, + pub message: String, + pub counter: u64, +} + +#[derive(Clone, Debug, Default, Deserialize, serde::Serialize)] +pub struct Log { + pub entries: Vec, +} + +impl Log { + pub fn push_logs(&mut self, priority: Priority) { + let logs = match priority { + Priority::Info => export_logs(&INFO_BUF), + Priority::TraceHttp => export_logs(&TRACE_HTTP_BUF), + Priority::Debug => export_logs(&DEBUG_BUF), + }; + for entry in logs { + self.entries.push(LogEntry { + timestamp: entry.timestamp, + counter: entry.counter, + priority, + file: entry.file.to_string(), + line: entry.line, + message: entry.message, + }); + } + } + + pub fn push_all(&mut self) { + self.push_logs(Priority::Info); + self.push_logs(Priority::TraceHttp); + self.push_logs(Priority::Debug); + } + + pub fn serialize_logs(&self, max_body_size: usize) -> String { + let mut entries_json: String = serde_json::to_string(&self).unwrap_or_default(); + + if entries_json.len() > max_body_size { + let mut left = 0; + let mut right = self.entries.len(); + + while left < right { + let mid = left + (right - left) / 2; + let mut temp_log = self.clone(); + temp_log.entries.truncate(mid); + let temp_entries_json = serde_json::to_string(&temp_log).unwrap_or_default(); + + if temp_entries_json.len() <= max_body_size { + entries_json = temp_entries_json; + left = mid + 1; + } else { + right = mid; + } + } + } + entries_json + } + + pub fn sort_logs(&mut self, sort_order: Sort) { + match sort_order { + Sort::Ascending => self.sort_asc(), + Sort::Descending => self.sort_desc(), + } + } + + pub fn sort_asc(&mut self) { + self.entries.sort_by(|a, b| a.timestamp.cmp(&b.timestamp)); + } + + pub fn sort_desc(&mut self) { + self.entries.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); + } +} + +#[cfg(test)] +mod tests { + use crate::logs::{Log, LogEntry, Priority, Sort}; + use proptest::{prop_assert, proptest}; + + fn info_log_entry_with_timestamp(timestamp: u64) -> LogEntry { + LogEntry { + timestamp, + priority: Priority::Info, + file: String::default(), + line: 0, + message: String::default(), + counter: 0, + } + } + + fn is_ascending(log: &Log) -> bool { + for i in 0..log.entries.len() - 1 { + if log.entries[i].timestamp > log.entries[i + 1].timestamp { + return false; + } + } + true + } + + fn is_descending(log: &Log) -> bool { + for i in 0..log.entries.len() - 1 { + if log.entries[i].timestamp < log.entries[i + 1].timestamp { + return false; + } + } + true + } + + proptest! { + #[test] + fn logs_always_fit_in_message( + number_of_entries in (1..100_usize), + entry_size in (1..10000_usize), + max_body_size in (100..10000_usize) + ) { + let mut entries: Vec = vec![]; + for _ in 0..number_of_entries { + entries.push(LogEntry { + timestamp: 0, + priority: Priority::Info, + file: String::default(), + line: 0, + message: "1".repeat(entry_size), + counter: 0, + }); + } + let log = Log { entries }; + let truncated_logs_json_len = log.serialize_logs(max_body_size).len(); + prop_assert!(truncated_logs_json_len <= max_body_size); + } + } + + #[test] + fn sorting_order() { + let mut log = Log { entries: vec![] }; + log.entries.push(info_log_entry_with_timestamp(2)); + log.entries.push(info_log_entry_with_timestamp(0)); + log.entries.push(info_log_entry_with_timestamp(1)); + log.sort_asc(); + assert!(is_ascending(&log)); + + log.sort_desc(); + assert!(is_descending(&log)); + + log.sort_logs(Sort::Ascending); + assert!(is_ascending(&log)); + + log.sort_logs(Sort::Descending); + assert!(is_descending(&log)); + } + + #[test] + fn simple_logs_truncation() { + let mut entries: Vec = vec![]; + const MAX_BODY_SIZE: usize = 3_000_000; + + for _ in 0..10 { + entries.push(LogEntry { + timestamp: 0, + priority: Priority::Info, + file: String::default(), + line: 0, + message: String::default(), + counter: 0, + }); + } + let log = Log { + entries: entries.clone(), + }; + let small_len = serde_json::to_string(&log).unwrap_or_default().len(); + + entries.push(LogEntry { + timestamp: 0, + priority: Priority::Info, + file: String::default(), + line: 0, + message: "1".repeat(MAX_BODY_SIZE), + counter: 0, + }); + let log = Log { entries }; + let entries_json = serde_json::to_string(&log).unwrap_or_default(); + assert!(entries_json.len() > MAX_BODY_SIZE); + + let truncated_logs_json = log.serialize_logs(MAX_BODY_SIZE); + + assert_eq!(small_len, truncated_logs_json.len()); + } + + #[test] + fn one_entry_too_big() { + let mut entries: Vec = vec![]; + const MAX_BODY_SIZE: usize = 3_000_000; + + entries.push(LogEntry { + timestamp: 0, + priority: Priority::Info, + file: String::default(), + line: 0, + message: "1".repeat(MAX_BODY_SIZE), + counter: 0, + }); + let log = Log { entries }; + let truncated_logs_json_len = log.serialize_logs(MAX_BODY_SIZE).len(); + assert!(truncated_logs_json_len < MAX_BODY_SIZE); + assert_eq!("{\"entries\":[]}", log.serialize_logs(MAX_BODY_SIZE)); + } + + #[test] + fn should_truncate_last_entry() { + let log_entries = vec![ + info_log_entry_with_timestamp(0), + info_log_entry_with_timestamp(1), + info_log_entry_with_timestamp(2), + ]; + let log_with_2_entries = Log { + entries: { + let mut entries = log_entries.clone(); + entries.pop(); + entries + }, + }; + let log_with_3_entries = Log { + entries: log_entries, + }; + + let serialized_log_with_2_entries = log_with_2_entries.serialize_logs(usize::MAX); + let serialized_log_with_3_entries = + log_with_3_entries.serialize_logs(serialized_log_with_2_entries.len()); + + assert_eq!(serialized_log_with_3_entries, serialized_log_with_2_entries); + } +} From 8696a2b833297f61fe986d177acd74b941dc7aa2 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Mon, 16 Sep 2024 15:38:31 +0200 Subject: [PATCH 37/42] 243: move tests --- src/logs/mod.rs | 158 ---------------------------------------------- src/logs/tests.rs | 155 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+), 158 deletions(-) create mode 100644 src/logs/tests.rs diff --git a/src/logs/mod.rs b/src/logs/mod.rs index f56f7fe8..31fd20cb 100644 --- a/src/logs/mod.rs +++ b/src/logs/mod.rs @@ -146,161 +146,3 @@ impl Log { } } -#[cfg(test)] -mod tests { - use crate::logs::{Log, LogEntry, Priority, Sort}; - use proptest::{prop_assert, proptest}; - - fn info_log_entry_with_timestamp(timestamp: u64) -> LogEntry { - LogEntry { - timestamp, - priority: Priority::Info, - file: String::default(), - line: 0, - message: String::default(), - counter: 0, - } - } - - fn is_ascending(log: &Log) -> bool { - for i in 0..log.entries.len() - 1 { - if log.entries[i].timestamp > log.entries[i + 1].timestamp { - return false; - } - } - true - } - - fn is_descending(log: &Log) -> bool { - for i in 0..log.entries.len() - 1 { - if log.entries[i].timestamp < log.entries[i + 1].timestamp { - return false; - } - } - true - } - - proptest! { - #[test] - fn logs_always_fit_in_message( - number_of_entries in (1..100_usize), - entry_size in (1..10000_usize), - max_body_size in (100..10000_usize) - ) { - let mut entries: Vec = vec![]; - for _ in 0..number_of_entries { - entries.push(LogEntry { - timestamp: 0, - priority: Priority::Info, - file: String::default(), - line: 0, - message: "1".repeat(entry_size), - counter: 0, - }); - } - let log = Log { entries }; - let truncated_logs_json_len = log.serialize_logs(max_body_size).len(); - prop_assert!(truncated_logs_json_len <= max_body_size); - } - } - - #[test] - fn sorting_order() { - let mut log = Log { entries: vec![] }; - log.entries.push(info_log_entry_with_timestamp(2)); - log.entries.push(info_log_entry_with_timestamp(0)); - log.entries.push(info_log_entry_with_timestamp(1)); - log.sort_asc(); - assert!(is_ascending(&log)); - - log.sort_desc(); - assert!(is_descending(&log)); - - log.sort_logs(Sort::Ascending); - assert!(is_ascending(&log)); - - log.sort_logs(Sort::Descending); - assert!(is_descending(&log)); - } - - #[test] - fn simple_logs_truncation() { - let mut entries: Vec = vec![]; - const MAX_BODY_SIZE: usize = 3_000_000; - - for _ in 0..10 { - entries.push(LogEntry { - timestamp: 0, - priority: Priority::Info, - file: String::default(), - line: 0, - message: String::default(), - counter: 0, - }); - } - let log = Log { - entries: entries.clone(), - }; - let small_len = serde_json::to_string(&log).unwrap_or_default().len(); - - entries.push(LogEntry { - timestamp: 0, - priority: Priority::Info, - file: String::default(), - line: 0, - message: "1".repeat(MAX_BODY_SIZE), - counter: 0, - }); - let log = Log { entries }; - let entries_json = serde_json::to_string(&log).unwrap_or_default(); - assert!(entries_json.len() > MAX_BODY_SIZE); - - let truncated_logs_json = log.serialize_logs(MAX_BODY_SIZE); - - assert_eq!(small_len, truncated_logs_json.len()); - } - - #[test] - fn one_entry_too_big() { - let mut entries: Vec = vec![]; - const MAX_BODY_SIZE: usize = 3_000_000; - - entries.push(LogEntry { - timestamp: 0, - priority: Priority::Info, - file: String::default(), - line: 0, - message: "1".repeat(MAX_BODY_SIZE), - counter: 0, - }); - let log = Log { entries }; - let truncated_logs_json_len = log.serialize_logs(MAX_BODY_SIZE).len(); - assert!(truncated_logs_json_len < MAX_BODY_SIZE); - assert_eq!("{\"entries\":[]}", log.serialize_logs(MAX_BODY_SIZE)); - } - - #[test] - fn should_truncate_last_entry() { - let log_entries = vec![ - info_log_entry_with_timestamp(0), - info_log_entry_with_timestamp(1), - info_log_entry_with_timestamp(2), - ]; - let log_with_2_entries = Log { - entries: { - let mut entries = log_entries.clone(); - entries.pop(); - entries - }, - }; - let log_with_3_entries = Log { - entries: log_entries, - }; - - let serialized_log_with_2_entries = log_with_2_entries.serialize_logs(usize::MAX); - let serialized_log_with_3_entries = - log_with_3_entries.serialize_logs(serialized_log_with_2_entries.len()); - - assert_eq!(serialized_log_with_3_entries, serialized_log_with_2_entries); - } -} diff --git a/src/logs/tests.rs b/src/logs/tests.rs new file mode 100644 index 00000000..e360f65a --- /dev/null +++ b/src/logs/tests.rs @@ -0,0 +1,155 @@ +use crate::logs::{Log, LogEntry, Priority, Sort}; +use proptest::{prop_assert, proptest}; + +fn info_log_entry_with_timestamp(timestamp: u64) -> LogEntry { + LogEntry { + timestamp, + priority: Priority::Info, + file: String::default(), + line: 0, + message: String::default(), + counter: 0, + } +} + +fn is_ascending(log: &Log) -> bool { + for i in 0..log.entries.len() - 1 { + if log.entries[i].timestamp > log.entries[i + 1].timestamp { + return false; + } + } + true +} + +fn is_descending(log: &Log) -> bool { + for i in 0..log.entries.len() - 1 { + if log.entries[i].timestamp < log.entries[i + 1].timestamp { + return false; + } + } + true +} + +proptest! { + #[test] + fn logs_always_fit_in_message( + number_of_entries in (1..100_usize), + entry_size in (1..10000_usize), + max_body_size in (100..10000_usize) + ) { + let mut entries: Vec = vec![]; + for _ in 0..number_of_entries { + entries.push(LogEntry { + timestamp: 0, + priority: Priority::Info, + file: String::default(), + line: 0, + message: "1".repeat(entry_size), + counter: 0, + }); + } + let log = Log { entries }; + let truncated_logs_json_len = log.serialize_logs(max_body_size).len(); + prop_assert!(truncated_logs_json_len <= max_body_size); + } +} + +#[test] +fn sorting_order() { + let mut log = Log { entries: vec![] }; + log.entries.push(info_log_entry_with_timestamp(2)); + log.entries.push(info_log_entry_with_timestamp(0)); + log.entries.push(info_log_entry_with_timestamp(1)); + log.sort_asc(); + assert!(is_ascending(&log)); + + log.sort_desc(); + assert!(is_descending(&log)); + + log.sort_logs(Sort::Ascending); + assert!(is_ascending(&log)); + + log.sort_logs(Sort::Descending); + assert!(is_descending(&log)); +} + +#[test] +fn simple_logs_truncation() { + let mut entries: Vec = vec![]; + const MAX_BODY_SIZE: usize = 3_000_000; + + for _ in 0..10 { + entries.push(LogEntry { + timestamp: 0, + priority: Priority::Info, + file: String::default(), + line: 0, + message: String::default(), + counter: 0, + }); + } + let log = Log { + entries: entries.clone(), + }; + let small_len = serde_json::to_string(&log).unwrap_or_default().len(); + + entries.push(LogEntry { + timestamp: 0, + priority: Priority::Info, + file: String::default(), + line: 0, + message: "1".repeat(MAX_BODY_SIZE), + counter: 0, + }); + let log = Log { entries }; + let entries_json = serde_json::to_string(&log).unwrap_or_default(); + assert!(entries_json.len() > MAX_BODY_SIZE); + + let truncated_logs_json = log.serialize_logs(MAX_BODY_SIZE); + + assert_eq!(small_len, truncated_logs_json.len()); +} + +#[test] +fn one_entry_too_big() { + let mut entries: Vec = vec![]; + const MAX_BODY_SIZE: usize = 3_000_000; + + entries.push(LogEntry { + timestamp: 0, + priority: Priority::Info, + file: String::default(), + line: 0, + message: "1".repeat(MAX_BODY_SIZE), + counter: 0, + }); + let log = Log { entries }; + let truncated_logs_json_len = log.serialize_logs(MAX_BODY_SIZE).len(); + assert!(truncated_logs_json_len < MAX_BODY_SIZE); + assert_eq!("{\"entries\":[]}", log.serialize_logs(MAX_BODY_SIZE)); +} + +#[test] +fn should_truncate_last_entry() { + let log_entries = vec![ + info_log_entry_with_timestamp(0), + info_log_entry_with_timestamp(1), + info_log_entry_with_timestamp(2), + ]; + let log_with_2_entries = Log { + entries: { + let mut entries = log_entries.clone(); + entries.pop(); + entries + }, + }; + let log_with_3_entries = Log { + entries: log_entries, + }; + + let serialized_log_with_2_entries = log_with_2_entries.serialize_logs(usize::MAX); + let serialized_log_with_3_entries = + log_with_3_entries.serialize_logs(serialized_log_with_2_entries.len()); + + assert_eq!(serialized_log_with_3_entries, serialized_log_with_2_entries); +} From d21600f1b86a40f1ebe79f2a914b54f8c079664e Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Mon, 16 Sep 2024 15:42:59 +0200 Subject: [PATCH 38/42] 243: remove dummy macro to log --- src/main.rs | 3 ++- src/rpc_client/eth_rpc/mod.rs | 3 ++- src/rpc_client/eth_rpc_error/mod.rs | 3 ++- src/rpc_client/mod.rs | 12 ++---------- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/main.rs b/src/main.rs index 47d9ef5f..ac2c4714 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ use evm_rpc::accounting::{get_cost_with_collateral, get_http_request_cost}; use evm_rpc::candid_rpc::CandidRpcClient; use evm_rpc::constants::NODES_IN_SUBNET; use evm_rpc::http::get_http_response_body; +use evm_rpc::logs::INFO; use evm_rpc::memory::{ insert_api_key, is_api_key_principal, is_demo_active, remove_api_key, set_api_key_principals, set_demo_active, @@ -12,11 +13,11 @@ use evm_rpc::providers::{find_provider, resolve_rpc_service, PROVIDERS, SERVICE_ use evm_rpc::types::{Provider, ProviderId, RpcAccess}; use evm_rpc::{ http::{json_rpc_request, transform_http_request}, - log, memory::UNSTABLE_METRICS, types::{InitArgs, MetricRpcMethod, Metrics}, }; use evm_rpc_types::{Hex32, MultiRpcResult, RpcResult}; +use ic_canister_log::log; use ic_canisters_http_types::{ HttpRequest as AssetHttpRequest, HttpResponse as AssetHttpResponse, HttpResponseBuilder, }; diff --git a/src/rpc_client/eth_rpc/mod.rs b/src/rpc_client/eth_rpc/mod.rs index 61c489d8..d89ef435 100644 --- a/src/rpc_client/eth_rpc/mod.rs +++ b/src/rpc_client/eth_rpc/mod.rs @@ -1,7 +1,7 @@ //! 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::log; +use crate::logs::{DEBUG, TRACE_HTTP}; use crate::memory::next_request_id; use crate::rpc_client::checked_amount::CheckedAmountOf; use crate::rpc_client::eth_rpc_error::{sanitize_send_raw_transaction_result, Parser}; @@ -11,6 +11,7 @@ use crate::rpc_client::RpcTransport; use candid::candid_method; use ethnum; use evm_rpc_types::{HttpOutcallError, JsonRpcError, RpcError, RpcService}; +use ic_canister_log::log; use ic_cdk::api::call::RejectionCode; use ic_cdk::api::management_canister::http_request::{ CanisterHttpRequestArgument, HttpHeader, HttpMethod, HttpResponse, TransformArgs, diff --git a/src/rpc_client/eth_rpc_error/mod.rs b/src/rpc_client/eth_rpc_error/mod.rs index 18168cbe..8a7338e0 100644 --- a/src/rpc_client/eth_rpc_error/mod.rs +++ b/src/rpc_client/eth_rpc_error/mod.rs @@ -1,5 +1,6 @@ -use crate::log; +use crate::logs::DEBUG; use crate::rpc_client::eth_rpc::{Hash, JsonRpcReply, JsonRpcResult, SendRawTransactionResult}; +use ic_canister_log::log; #[cfg(test)] mod tests; diff --git a/src/rpc_client/mod.rs b/src/rpc_client/mod.rs index 219b01c8..1d49cd67 100644 --- a/src/rpc_client/mod.rs +++ b/src/rpc_client/mod.rs @@ -19,6 +19,8 @@ use serde::{de::DeserializeOwned, Serialize}; use std::collections::BTreeMap; use std::fmt::Debug; use std::marker::PhantomData; +use ic_canister_log::log; +use crate::logs::{DEBUG, INFO}; pub mod checked_amount; pub(crate) mod eth_rpc; @@ -31,16 +33,6 @@ pub(crate) mod responses; #[cfg(test)] mod tests; -//TODO: Dummy log. use ic_canister_log::log -#[macro_export] -macro_rules! log { - ($sink:expr, $message:expr $(,$args:expr)* $(,)*) => {{ - let message = std::format!($message $(,$args)*); - // Print the message for convenience for local development (e.g. integration tests) - println!("{}", &message); - }} -} - #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait RpcTransport: Debug { From dab306b4ac01334bb09e916121ab0d5e6b6f120a Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Mon, 16 Sep 2024 15:47:44 +0200 Subject: [PATCH 39/42] 243: re-enable logging --- src/main.rs | 132 ++++++++++++++++++++++++++-------------------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/src/main.rs b/src/main.rs index ac2c4714..6b0b82ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -234,72 +234,72 @@ fn post_upgrade(args: InitArgs) { fn http_request(request: AssetHttpRequest) -> AssetHttpResponse { match request.path() { "/metrics" => serve_metrics(encode_metrics), - // TODO 243: re-enable logs - // "/logs" => { - // use std::str::FromStr; - // - // let max_skip_timestamp = match request.raw_query_param("time") { - // Some(arg) => match u64::from_str(arg) { - // Ok(value) => value, - // Err(_) => { - // return HttpResponseBuilder::bad_request() - // .with_body_and_content_length("failed to parse the 'time' parameter") - // .build() - // } - // }, - // None => 0, - // }; - // - // let mut log: Log = Default::default(); - // - // match request.raw_query_param("priority").map(Priority::from_str) { - // Some(Ok(priority)) => match priority { - // Priority::Info => log.push_logs(Priority::Info), - // Priority::Debug => log.push_logs(Priority::Debug), - // Priority::TraceHttp => {} - // }, - // _ => { - // log.push_logs(Priority::Info); - // log.push_logs(Priority::Debug); - // } - // } - // - // log.entries - // .retain(|entry| entry.timestamp >= max_skip_timestamp); - // - // fn ordering_from_query_params(sort: Option<&str>, max_skip_timestamp: u64) -> Sort { - // match sort { - // Some(ord_str) => match Sort::from_str(ord_str) { - // Ok(order) => order, - // Err(_) => { - // if max_skip_timestamp == 0 { - // Sort::Ascending - // } else { - // Sort::Descending - // } - // } - // }, - // None => { - // if max_skip_timestamp == 0 { - // Sort::Ascending - // } else { - // Sort::Descending - // } - // } - // } - // } - // - // log.sort_logs(ordering_from_query_params( - // request.raw_query_param("sort"), - // max_skip_timestamp, - // )); - // - // const MAX_BODY_SIZE: usize = 3_000_000; - // HttpResponseBuilder::ok() - // .header("Content-Type", "application/json; charset=utf-8") - // .with_body_and_content_length(log.serialize_logs(MAX_BODY_SIZE)) - // .build() - // } + "/logs" => { + use evm_rpc::logs::{Log, Priority, Sort}; + use std::str::FromStr; + + let max_skip_timestamp = match request.raw_query_param("time") { + Some(arg) => match u64::from_str(arg) { + Ok(value) => value, + Err(_) => { + return HttpResponseBuilder::bad_request() + .with_body_and_content_length("failed to parse the 'time' parameter") + .build() + } + }, + None => 0, + }; + + let mut log: Log = Default::default(); + + match request.raw_query_param("priority").map(Priority::from_str) { + Some(Ok(priority)) => match priority { + Priority::Info => log.push_logs(Priority::Info), + Priority::Debug => log.push_logs(Priority::Debug), + Priority::TraceHttp => {} + }, + _ => { + log.push_logs(Priority::Info); + log.push_logs(Priority::Debug); + } + } + + log.entries + .retain(|entry| entry.timestamp >= max_skip_timestamp); + + fn ordering_from_query_params(sort: Option<&str>, max_skip_timestamp: u64) -> Sort { + match sort { + Some(ord_str) => match Sort::from_str(ord_str) { + Ok(order) => order, + Err(_) => { + if max_skip_timestamp == 0 { + Sort::Ascending + } else { + Sort::Descending + } + } + }, + None => { + if max_skip_timestamp == 0 { + Sort::Ascending + } else { + Sort::Descending + } + } + } + } + + log.sort_logs(ordering_from_query_params( + request.raw_query_param("sort"), + max_skip_timestamp, + )); + + const MAX_BODY_SIZE: usize = 3_000_000; + HttpResponseBuilder::ok() + .header("Content-Type", "application/json; charset=utf-8") + .with_body_and_content_length(log.serialize_logs(MAX_BODY_SIZE)) + .build() + } _ => HttpResponseBuilder::not_found().build(), } } From 334539e1843410cff6f4028054ddcfee0db8e7a5 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Mon, 16 Sep 2024 15:48:14 +0200 Subject: [PATCH 40/42] 243: change MAX_BODY_SIZE to 2MB --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 6b0b82ca..bf7efddf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -294,7 +294,7 @@ fn http_request(request: AssetHttpRequest) -> AssetHttpResponse { max_skip_timestamp, )); - const MAX_BODY_SIZE: usize = 3_000_000; + const MAX_BODY_SIZE: usize = 2_000_000; HttpResponseBuilder::ok() .header("Content-Type", "application/json; charset=utf-8") .with_body_and_content_length(log.serialize_logs(MAX_BODY_SIZE)) From ef9f25e02804d34ca308b5580d31e764843c4eab Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Mon, 16 Sep 2024 16:52:36 +0200 Subject: [PATCH 41/42] 243: test for logs --- Cargo.lock | 1 + Cargo.toml | 2 ++ src/logs/mod.rs | 5 ++--- tests/tests.rs | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea65a6d6..a47c6a80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1559,6 +1559,7 @@ dependencies = [ "proptest", "rand", "serde", + "serde_bytes", "serde_json", "thousands", "url", diff --git a/Cargo.toml b/Cargo.toml index a3cf3953..e5ab5eea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ ic-crypto-test-utils-reproducible-rng = { git = "https://github.com/dfinity/ic", ic-state-machine-tests = { git = "https://github.com/dfinity/ic", rev = "release-2023-09-27_23-01" } ic-test-utilities-load-wasm = { git = "https://github.com/dfinity/ic", rev = "release-2023-09-27_23-01" } proptest = { workspace = true } +serde_bytes = { workspace = true } rand = "0.8" [workspace.dependencies] @@ -79,6 +80,7 @@ num-bigint = "0.4.6" proptest = "1.5.0" serde = "1.0" serde_json = "1.0" +serde_bytes = "0.11.15" [workspace] members = ["e2e/rust", "evm_rpc_types"] diff --git a/src/logs/mod.rs b/src/logs/mod.rs index 31fd20cb..3afb6c8f 100644 --- a/src/logs/mod.rs +++ b/src/logs/mod.rs @@ -28,7 +28,7 @@ impl Sink for PrintProxySink { } } -#[derive(Copy, Clone, Debug, Deserialize, serde::Serialize)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, serde::Serialize)] pub enum Priority { Info, TraceHttp, @@ -66,7 +66,7 @@ impl FromStr for Sort { } } -#[derive(Clone, Debug, Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, serde::Serialize)] pub struct LogEntry { pub timestamp: u64, pub priority: Priority, @@ -145,4 +145,3 @@ impl Log { self.entries.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); } } - diff --git a/tests/tests.rs b/tests/tests.rs index c68aff5a..6fc2dea4 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -2,6 +2,7 @@ mod mock; use assert_matches::assert_matches; use candid::{CandidType, Decode, Encode, Nat}; +use evm_rpc::logs::{Log, LogEntry}; use evm_rpc::{ constants::{CONTENT_TYPE_HEADER_LOWERCASE, CONTENT_TYPE_VALUE}, providers::PROVIDERS, @@ -12,6 +13,7 @@ use evm_rpc_types::{ MultiRpcResult, Nat256, ProviderError, RpcApi, RpcError, RpcResult, RpcService, RpcServices, }; use ic_base_types::{CanisterId, PrincipalId}; +use ic_canisters_http_types::{HttpRequest, HttpResponse}; use ic_cdk::api::management_canister::http_request::{ CanisterHttpRequestArgument, HttpHeader, HttpMethod, HttpResponse as OutCallHttpResponse, TransformArgs, TransformContext, TransformFunc, @@ -302,6 +304,26 @@ impl EvmRpcSetup { ); self } + pub fn http_get_logs(&self, priority: &str) -> Vec { + let request = HttpRequest { + method: "".to_string(), + url: format!("/logs?priority={priority}"), + headers: vec![], + body: serde_bytes::ByteBuf::new(), + }; + let response = Decode!( + &assert_reply( + self.env + .query(self.canister_id, "http_request", Encode!(&request).unwrap(),) + .expect("failed to get minter info") + ), + HttpResponse + ) + .unwrap(); + serde_json::from_slice::(&response.body) + .expect("failed to parse EVM_RPC minter log") + .entries + } } pub struct CallFlow { @@ -1651,3 +1673,20 @@ fn upgrade_should_change_manage_api_key_principals() { .as_caller(deauthorized_caller) .update_api_keys(&[(0, Some("unauthorized-api-key".to_string()))]); } + +#[test] +fn should_retrieve_logs() { + let setup = EvmRpcSetup::with_args(InitArgs { + demo: None, + manage_api_keys: None, + }); + assert_eq!(setup.http_get_logs("DEBUG"), vec![]); + assert_eq!(setup.http_get_logs("INFO"), vec![]); + + let setup = setup.mock_api_keys(); + + assert_eq!(setup.http_get_logs("DEBUG"), vec![]); + assert!(setup.http_get_logs("INFO")[0] + .message + .contains("Updating API keys")); +} From bd2c675200e6642b8e978dbca2914c90c738edb2 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Mon, 16 Sep 2024 17:13:43 +0200 Subject: [PATCH 42/42] 243: formatting --- src/rpc_client/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rpc_client/mod.rs b/src/rpc_client/mod.rs index 1d49cd67..9bb7e564 100644 --- a/src/rpc_client/mod.rs +++ b/src/rpc_client/mod.rs @@ -1,3 +1,4 @@ +use crate::logs::{DEBUG, INFO}; use crate::rpc_client::eth_rpc::{ are_errors_consistent, Block, BlockSpec, FeeHistory, FeeHistoryParams, GetBlockByNumberParams, GetLogsParam, Hash, HttpResponsePayload, LogEntry, ResponseSizeEstimate, @@ -14,13 +15,12 @@ use async_trait::async_trait; use evm_rpc_types::{ HttpOutcallError, JsonRpcError, ProviderError, RpcApi, RpcConfig, RpcError, RpcService, }; +use ic_canister_log::log; use ic_cdk::api::management_canister::http_request::{CanisterHttpRequestArgument, HttpResponse}; use serde::{de::DeserializeOwned, Serialize}; use std::collections::BTreeMap; use std::fmt::Debug; use std::marker::PhantomData; -use ic_canister_log::log; -use crate::logs::{DEBUG, INFO}; pub mod checked_amount; pub(crate) mod eth_rpc;