From 9513ef516c498f47db4ccae3b37b367e2c59cafa Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Thu, 28 Nov 2024 10:30:03 +0100 Subject: [PATCH 1/6] XC-246: memory metrics to follow convention --- src/metrics.rs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/metrics.rs b/src/metrics.rs index 04a1d945..6232646b 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -52,6 +52,8 @@ impl EncoderExtensions for ic_metrics_encoder::MetricsEncoder> { } pub fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder>) -> std::io::Result<()> { + const WASM_PAGE_SIZE_IN_BYTES: f64 = 65536.0; + crate::memory::UNSTABLE_METRICS.with(|m| { let m = m.borrow(); @@ -66,10 +68,17 @@ pub fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder>) -> st "Canister version", )?; w.encode_gauge( - "evmrpc_stable_memory_pages", - ic_cdk::api::stable::stable_size().metric_value(), - "Size of the stable memory allocated by this canister measured in 64-bit Wasm pages", + "stable_memory_bytes", + ic_cdk::api::stable::stable_size() as f64 * WASM_PAGE_SIZE_IN_BYTES, + "Size of the stable memory allocated by this canister.", )?; + + w.encode_gauge( + "heap_memory_bytes", + heap_memory_size_bytes() as f64, + "Size of the heap memory allocated by this canister.", + )?; + w.counter_entries( "evmrpc_cycles_charged", &m.cycles_charged, @@ -114,3 +123,15 @@ pub fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder>) -> st Ok(()) }) } + +/// Returns the amount of heap memory in bytes that has been allocated. +#[cfg(target_arch = "wasm32")] +pub fn heap_memory_size_bytes() -> usize { + const WASM_PAGE_SIZE_BYTES: usize = 65536; + core::arch::wasm32::memory_size(0) * WASM_PAGE_SIZE_BYTES +} + +#[cfg(not(any(target_arch = "wasm32")))] +pub fn heap_memory_size_bytes() -> usize { + 0 +} From 2eccb256c426cebe4ccd68d3016c260e509dbe89 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Thu, 28 Nov 2024 10:38:42 +0100 Subject: [PATCH 2/6] XC-246: remove cyclesWithdrawn --- candid/evm_rpc.did | 4 +++- src/metrics.rs | 5 ----- src/types.rs | 2 -- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/candid/evm_rpc.did b/candid/evm_rpc.did index 4a496cc6..d6102d37 100644 --- a/candid/evm_rpc.did +++ b/candid/evm_rpc.did @@ -132,7 +132,6 @@ type Metrics = record { responses : vec record { record { text; text; text }; nat64 }; inconsistentResponses : vec record { record { text; text }; nat64 }; cyclesCharged : vec record { record { text; text }; nat }; - cyclesWithdrawn : nat; errNoPermission : nat64; errHttpOutcall : vec record { record { text; text }; nat64 }; errHostNotAllowed : vec record { text; nat64 }; @@ -292,6 +291,9 @@ service : (InstallArgs) -> { eth_call : (RpcServices, opt RpcConfig, CallArgs) -> (MultiCallResult); request : (RpcService, json : text, maxResponseBytes : nat64) -> (RequestResult); requestCost : (RpcService, json : text, maxResponseBytes : nat64) -> (RequestCostResult) query; + + // DEBUG endpoint to retrieve metrics accumulated by the EVM RPC canister. + // NOTE: this method exists for debugging purposes, backward compatibility is not guaranteed. getMetrics : () -> (Metrics) query; getNodesInSubnet : () -> (numberOfNodes : nat32) query; getProviders : () -> (vec Provider) query; diff --git a/src/metrics.rs b/src/metrics.rs index 6232646b..33c7823b 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -84,11 +84,6 @@ pub fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder>) -> st &m.cycles_charged, "Number of cycles charged for RPC calls", ); - w.encode_counter( - "evmrpc_cycles_withdrawn", - m.cycles_withdrawn.metric_value(), - "Number of accumulated cycles withdrawn by RPC providers", - )?; w.counter_entries( "evmrpc_requests", &m.requests, diff --git a/src/types.rs b/src/types.rs index 9e05b6b4..2a133bd5 100644 --- a/src/types.rs +++ b/src/types.rs @@ -123,8 +123,6 @@ pub struct Metrics { pub inconsistent_responses: HashMap<(MetricRpcMethod, MetricRpcHost), u64>, #[serde(rename = "cyclesCharged")] pub cycles_charged: HashMap<(MetricRpcMethod, MetricRpcHost), u128>, - #[serde(rename = "cyclesWithdrawn")] - pub cycles_withdrawn: u128, #[serde(rename = "errNoPermission")] pub err_no_permission: u64, #[serde(rename = "errHttpOutcall")] From 25448482789f7a6c68ed635e243cbb6ce9f8c1d5 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Thu, 28 Nov 2024 10:43:54 +0100 Subject: [PATCH 3/6] XC-246: remove errHostNotAllowed --- candid/evm_rpc.did | 1 - src/constants.rs | 2 -- src/http.rs | 10 +--------- src/metrics.rs | 5 ----- src/types.rs | 2 -- src/validate.rs | 39 +-------------------------------------- 6 files changed, 2 insertions(+), 57 deletions(-) diff --git a/candid/evm_rpc.did b/candid/evm_rpc.did index d6102d37..0cae6a0a 100644 --- a/candid/evm_rpc.did +++ b/candid/evm_rpc.did @@ -134,7 +134,6 @@ type Metrics = record { cyclesCharged : vec record { record { text; text }; nat }; errNoPermission : nat64; errHttpOutcall : vec record { record { text; text }; nat64 }; - errHostNotAllowed : vec record { text; nat64 }; }; type MultiFeeHistoryResult = variant { Consistent : FeeHistoryResult; diff --git a/src/constants.rs b/src/constants.rs index 53e5af8b..be854088 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -43,5 +43,3 @@ pub const ETH_SEPOLIA_CHAIN_ID: u64 = 11155111; pub const ARBITRUM_ONE_CHAIN_ID: u64 = 42161; pub const BASE_MAINNET_CHAIN_ID: u64 = 8453; pub const OPTIMISM_MAINNET_CHAIN_ID: u64 = 10; - -pub const SERVICE_HOSTS_BLOCKLIST: &[&str] = &[]; diff --git a/src/http.rs b/src/http.rs index 23f10a9f..abb4701e 100644 --- a/src/http.rs +++ b/src/http.rs @@ -1,7 +1,7 @@ use crate::{ accounting::{get_cost_with_collateral, get_http_request_cost}, add_metric_entry, - constants::{CONTENT_TYPE_HEADER_LOWERCASE, CONTENT_TYPE_VALUE, SERVICE_HOSTS_BLOCKLIST}, + constants::{CONTENT_TYPE_HEADER_LOWERCASE, CONTENT_TYPE_VALUE}, memory::is_demo_active, types::{MetricRpcHost, MetricRpcMethod, ResolvedRpcService}, util::canonicalize_json, @@ -69,14 +69,6 @@ pub async fn http_request( } }; let rpc_host = MetricRpcHost(host.to_string()); - if SERVICE_HOSTS_BLOCKLIST.contains(&rpc_host.0.as_str()) { - add_metric_entry!(err_host_not_allowed, rpc_host.clone(), 1); - return Err(ValidationError::Custom(format!( - "Disallowed RPC service host: {}", - rpc_host.0 - )) - .into()); - } if !is_demo_active() { let cycles_available = ic_cdk::api::call::msg_cycles_available128(); let cycles_cost_with_collateral = get_cost_with_collateral(cycles_cost); diff --git a/src/metrics.rs b/src/metrics.rs index 33c7823b..20e62ad4 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -104,11 +104,6 @@ pub fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder>) -> st &m.err_http_outcall, "Number of unsuccessful HTTP outcalls", ); - w.counter_entries( - "evmrpc_err_host_not_allowed", - &m.err_host_not_allowed, - "Number of HostNotAllowed errors", - ); w.encode_counter( "evmrpc_err_no_permission", m.err_no_permission.metric_value(), diff --git a/src/types.rs b/src/types.rs index 2a133bd5..e32e1324 100644 --- a/src/types.rs +++ b/src/types.rs @@ -127,8 +127,6 @@ pub struct Metrics { pub err_no_permission: u64, #[serde(rename = "errHttpOutcall")] pub err_http_outcall: HashMap<(MetricRpcMethod, MetricRpcHost), u64>, - #[serde(rename = "errHostNotAllowed")] - pub err_host_not_allowed: HashMap, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/src/validate.rs b/src/validate.rs index 846ff36a..1687358e 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -1,19 +1,4 @@ -use crate::{ - constants::{SERVICE_HOSTS_BLOCKLIST, VALID_API_KEY_CHARS}, - util::hostname_from_url, -}; - -pub fn validate_hostname(hostname: &str) -> Result<(), &'static str> { - if SERVICE_HOSTS_BLOCKLIST.contains(&hostname) { - Err("Hostname not allowed") - } else { - Ok(()) - } -} - -pub fn validate_url_pattern(url_pattern: &str) -> Result<(), &'static str> { - validate_hostname(&hostname_from_url(url_pattern).ok_or("Invalid hostname in URL")?) -} +use crate::constants::VALID_API_KEY_CHARS; pub fn validate_api_key(api_key: &str) -> Result<(), &'static str> { if api_key.is_empty() { @@ -34,28 +19,6 @@ pub fn validate_api_key(api_key: &str) -> Result<(), &'static str> { mod test { use super::*; - #[test] - pub fn test_validate_url_pattern() { - assert_eq!(validate_url_pattern("https://example.com"), Ok(())); - assert_eq!(validate_url_pattern("https://example.com/v1/rpc"), Ok(())); - assert_eq!( - validate_url_pattern("https://example.com/{API_KEY}"), - Ok(()) - ); - assert_eq!( - validate_url_pattern("https://{API_KEY}"), - Err("Invalid hostname in URL") - ); - assert_eq!( - validate_url_pattern("https://{API_KEY}/v1/rpc"), - Err("Invalid hostname in URL") - ); - assert_eq!( - validate_url_pattern("https://{API_KEY}/{API_KEY}"), - Err("Invalid hostname in URL") - ); - } - #[test] pub fn test_validate_api_key() { assert_eq!(validate_api_key("abc"), Ok(())); From 809c3ff74b45cd408d657abcc194e12546574f5b Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Thu, 28 Nov 2024 10:52:16 +0100 Subject: [PATCH 4/6] XC-246: remove errNoPermission --- candid/evm_rpc.did | 1 - src/metrics.rs | 5 ----- src/types.rs | 2 -- 3 files changed, 8 deletions(-) diff --git a/candid/evm_rpc.did b/candid/evm_rpc.did index 0cae6a0a..a5e9e309 100644 --- a/candid/evm_rpc.did +++ b/candid/evm_rpc.did @@ -132,7 +132,6 @@ type Metrics = record { responses : vec record { record { text; text; text }; nat64 }; inconsistentResponses : vec record { record { text; text }; nat64 }; cyclesCharged : vec record { record { text; text }; nat }; - errNoPermission : nat64; errHttpOutcall : vec record { record { text; text }; nat64 }; }; type MultiFeeHistoryResult = variant { diff --git a/src/metrics.rs b/src/metrics.rs index 20e62ad4..ec1728f7 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -104,11 +104,6 @@ pub fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder>) -> st &m.err_http_outcall, "Number of unsuccessful HTTP outcalls", ); - w.encode_counter( - "evmrpc_err_no_permission", - m.err_no_permission.metric_value(), - "Number of NoPermission errors", - )?; Ok(()) }) diff --git a/src/types.rs b/src/types.rs index e32e1324..dca2292f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -123,8 +123,6 @@ pub struct Metrics { pub inconsistent_responses: HashMap<(MetricRpcMethod, MetricRpcHost), u64>, #[serde(rename = "cyclesCharged")] pub cycles_charged: HashMap<(MetricRpcMethod, MetricRpcHost), u128>, - #[serde(rename = "errNoPermission")] - pub err_no_permission: u64, #[serde(rename = "errHttpOutcall")] pub err_http_outcall: HashMap<(MetricRpcMethod, MetricRpcHost), u64>, } From db42c7f8c7f49a86d021040c5c079d8c727e13bc Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Thu, 28 Nov 2024 11:25:34 +0100 Subject: [PATCH 5/6] XC-246: add RejectionCode to err_http_outcall --- candid/evm_rpc.did | 2 +- src/http.rs | 2 +- src/types.rs | 19 ++++++++++++++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/candid/evm_rpc.did b/candid/evm_rpc.did index a5e9e309..4f38aeee 100644 --- a/candid/evm_rpc.did +++ b/candid/evm_rpc.did @@ -132,7 +132,7 @@ type Metrics = record { responses : vec record { record { text; text; text }; nat64 }; inconsistentResponses : vec record { record { text; text }; nat64 }; cyclesCharged : vec record { record { text; text }; nat }; - errHttpOutcall : vec record { record { text; text }; nat64 }; + errHttpOutcall : vec record { record { text; text; RejectionCode }; nat64 }; }; type MultiFeeHistoryResult = variant { Consistent : FeeHistoryResult; diff --git a/src/http.rs b/src/http.rs index abb4701e..c89eb7c0 100644 --- a/src/http.rs +++ b/src/http.rs @@ -94,7 +94,7 @@ pub async fn http_request( Ok(response) } Err((code, message)) => { - add_metric_entry!(err_http_outcall, (rpc_method, rpc_host), 1); + add_metric_entry!(err_http_outcall, (rpc_method, rpc_host, code), 1); Err(HttpOutcallError::IcError { code, message }.into()) } } diff --git a/src/types.rs b/src/types.rs index dca2292f..11833fe2 100644 --- a/src/types.rs +++ b/src/types.rs @@ -3,6 +3,7 @@ use crate::memory::get_api_key; use crate::util::hostname_from_url; use crate::validate::validate_api_key; use candid::CandidType; +use ic_cdk::api::call::RejectionCode; use ic_cdk::api::management_canister::http_request::HttpHeader; use ic_stable_structures::storable::Bound; use ic_stable_structures::Storable; @@ -115,6 +116,22 @@ impl MetricLabels for MetricHttpStatusCode { } } +impl MetricLabels for RejectionCode { + fn metric_labels(&self) -> Vec<(&str, &str)> { + let code = match self { + RejectionCode::NoError => "NO_ERROR", + RejectionCode::SysFatal => "SYS_FATAL", + RejectionCode::SysTransient => "SYS_TRANSIENT", + RejectionCode::DestinationInvalid => "DESTINATION_INVALID", + RejectionCode::CanisterReject => "CANISTER_REJECT", + RejectionCode::CanisterError => "CANISTER_ERROR", + RejectionCode::Unknown => "UNKNOWN", + }; + + vec![("code", code)] + } +} + #[derive(Clone, Debug, Default, PartialEq, Eq, CandidType, Deserialize)] pub struct Metrics { pub requests: HashMap<(MetricRpcMethod, MetricRpcHost), u64>, @@ -124,7 +141,7 @@ pub struct Metrics { #[serde(rename = "cyclesCharged")] pub cycles_charged: HashMap<(MetricRpcMethod, MetricRpcHost), u128>, #[serde(rename = "errHttpOutcall")] - pub err_http_outcall: HashMap<(MetricRpcMethod, MetricRpcHost), u64>, + pub err_http_outcall: HashMap<(MetricRpcMethod, MetricRpcHost, RejectionCode), u64>, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] From 709d3853a3699d43044be6959564dfab112e1484 Mon Sep 17 00:00:00 2001 From: gregorydemay Date: Thu, 28 Nov 2024 13:51:32 +0100 Subject: [PATCH 6/6] XC-246: add test --- evm_rpc_types/src/result/mod.rs | 27 ++++++----- tests/mock.rs | 32 ++++++++----- tests/tests.rs | 80 +++++++++++++++++++++++++++++++-- 3 files changed, 109 insertions(+), 30 deletions(-) diff --git a/evm_rpc_types/src/result/mod.rs b/evm_rpc_types/src/result/mod.rs index 61223130..14f51902 100644 --- a/evm_rpc_types/src/result/mod.rs +++ b/evm_rpc_types/src/result/mod.rs @@ -4,6 +4,7 @@ mod tests; use crate::RpcService; use candid::{CandidType, Deserialize}; use ic_cdk::api::call::RejectionCode; +use std::fmt::Debug; use thiserror::Error; pub type RpcResult = Result; @@ -34,28 +35,26 @@ impl MultiRpcResult { ), } } +} - pub fn consistent(self) -> Option> { +impl MultiRpcResult { + pub fn expect_consistent(self) -> RpcResult { match self { - MultiRpcResult::Consistent(result) => Some(result), - MultiRpcResult::Inconsistent(_) => None, + MultiRpcResult::Consistent(result) => result, + MultiRpcResult::Inconsistent(inconsistent_result) => { + panic!("Expected consistent, but got: {:?}", inconsistent_result) + } } } - pub fn inconsistent(self) -> Option)>> { + pub fn expect_inconsistent(self) -> Vec<(RpcService, RpcResult)> { match self { - MultiRpcResult::Consistent(_) => None, - MultiRpcResult::Inconsistent(results) => Some(results), + MultiRpcResult::Consistent(consistent_result) => { + panic!("Expected inconsistent:, but got: {:?}", consistent_result) + } + MultiRpcResult::Inconsistent(results) => results, } } - - pub fn expect_consistent(self) -> RpcResult { - self.consistent().expect("expected consistent results") - } - - pub fn expect_inconsistent(self) -> Vec<(RpcService, RpcResult)> { - self.inconsistent().expect("expected inconsistent results") - } } impl From> for MultiRpcResult { diff --git a/tests/mock.rs b/tests/mock.rs index 65e71781..c1d3945b 100644 --- a/tests/mock.rs +++ b/tests/mock.rs @@ -1,5 +1,7 @@ +use ic_cdk::api::call::RejectionCode; use pocket_ic::common::rest::{ - CanisterHttpHeader, CanisterHttpMethod, CanisterHttpReply, CanisterHttpRequest, + CanisterHttpHeader, CanisterHttpMethod, CanisterHttpReject, CanisterHttpReply, + CanisterHttpRequest, CanisterHttpResponse, }; use std::collections::BTreeSet; @@ -32,11 +34,25 @@ impl MockOutcallBuilder { request_headers: None, request_body: None, max_response_bytes: None, - response: CanisterHttpReply { + response: CanisterHttpResponse::CanisterHttpReply(CanisterHttpReply { status, headers: vec![], body: body.into().0, - }, + }), + }) + } + + pub fn new_error(code: RejectionCode, message: impl ToString) -> Self { + Self(MockOutcall { + method: None, + url: None, + request_headers: None, + request_body: None, + max_response_bytes: None, + response: CanisterHttpResponse::CanisterHttpReject(CanisterHttpReject { + reject_code: code as u64, + message: message.to_string(), + }), }) } @@ -77,14 +93,6 @@ impl MockOutcallBuilder { self } - pub fn with_response_header(mut self, name: String, value: String) -> Self { - self.0 - .response - .headers - .push(CanisterHttpHeader { name, value }); - self - } - pub fn build(self) -> MockOutcall { self.0 } @@ -103,7 +111,7 @@ pub struct MockOutcall { pub request_headers: Option>, pub request_body: Option, pub max_response_bytes: Option, - pub response: CanisterHttpReply, + pub response: CanisterHttpResponse, } impl MockOutcall { diff --git a/tests/tests.rs b/tests/tests.rs index b3dd7e34..51e5783f 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -15,14 +15,13 @@ use evm_rpc_types::{ InstallArgs, JsonRpcError, MultiRpcResult, Nat256, Provider, ProviderError, RpcApi, RpcConfig, RpcError, RpcResult, RpcService, RpcServices, }; +use ic_cdk::api::call::RejectionCode; use ic_cdk::api::management_canister::http_request::HttpHeader; use ic_cdk::api::management_canister::main::CanisterId; use ic_test_utilities_load_wasm::load_wasm; use maplit::hashmap; use mock::{MockOutcall, MockOutcallBuilder}; -use pocket_ic::common::rest::{ - CanisterHttpMethod, CanisterHttpResponse, MockCanisterHttpResponse, RawMessageId, -}; +use pocket_ic::common::rest::{CanisterHttpMethod, MockCanisterHttpResponse, RawMessageId}; use pocket_ic::{CanisterSettings, PocketIc, WasmResult}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::json; @@ -416,7 +415,7 @@ impl CallFlow { let response = MockCanisterHttpResponse { subnet_id: request.subnet_id, request_id: request.request_id, - response: CanisterHttpResponse::CanisterHttpReply(mock.response.clone()), + response: mock.response.clone(), additional_responses: vec![], }; self.setup.env.mock_canister_http_response(response); @@ -1345,6 +1344,79 @@ fn candid_rpc_should_return_inconsistent_results_with_error() { ); } +#[test] +fn candid_rpc_should_return_inconsistent_results_with_consensus_error() { + const CONSENSUS_ERROR: &str = + "No consensus could be reached. Replicas had different responses."; + + let setup = EvmRpcSetup::new().mock_api_keys(); + let result = setup + .eth_get_transaction_count( + RpcServices::EthMainnet(None), + Some(RpcConfig { + response_consensus: Some(ConsensusStrategy::Threshold { + total: Some(3), + min: 2, + }), + ..Default::default() + }), + evm_rpc_types::GetTransactionCountArgs { + address: "0xdAC17F958D2ee523a2206206994597C13D831ec7" + .parse() + .unwrap(), + block: evm_rpc_types::BlockTag::Latest, + }, + ) + .mock_http_once(MockOutcallBuilder::new_error( + RejectionCode::SysTransient, + CONSENSUS_ERROR, + )) + .mock_http_once(MockOutcallBuilder::new( + 200, + r#"{"jsonrpc":"2.0","id":0,"result":"0x1"}"#, + )) + .mock_http_once(MockOutcallBuilder::new_error( + RejectionCode::SysTransient, + CONSENSUS_ERROR, + )) + .wait() + .expect_inconsistent(); + + assert_eq!( + result, + vec![ + ( + RpcService::EthMainnet(EthMainnetService::PublicNode), + Ok(1_u8.into()) + ), + ( + RpcService::EthMainnet(EthMainnetService::BlockPi), + Err(RpcError::HttpOutcallError(HttpOutcallError::IcError { + code: RejectionCode::SysTransient, + message: CONSENSUS_ERROR.to_string() + })) + ), + ( + RpcService::EthMainnet(EthMainnetService::Cloudflare), + Err(RpcError::HttpOutcallError(HttpOutcallError::IcError { + code: RejectionCode::SysTransient, + message: CONSENSUS_ERROR.to_string() + })) + ), + ] + ); + + let rpc_method = || RpcMethod::EthGetTransactionCount.into(); + let err_http_outcall = setup.get_metrics().err_http_outcall; + assert_eq!( + err_http_outcall, + hashmap! { + (rpc_method(), BLOCKPI_ETH_HOSTNAME.into(), RejectionCode::SysTransient) => 1, + (rpc_method(), CLOUDFLARE_HOSTNAME.into(), RejectionCode::SysTransient) => 1, + }, + ); +} + #[test] fn candid_rpc_should_return_inconsistent_results_with_unexpected_http_status() { let setup = EvmRpcSetup::new().mock_api_keys();