Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ evm_rpc_types = { path = "evm_rpc_types" }
getrandom = { workspace = true }
http = { workspace = true }
ic-ethereum-types = { workspace = true }
ic-error-types = { workspace = true }
ic-http-types = { workspace = true }
ic-metrics-encoder = { workspace = true }
ic-stable-structures = { workspace = true }
Expand Down
18 changes: 18 additions & 0 deletions src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use evm_rpc_types::{
HttpOutcallError, LegacyRejectionCode, ProviderError, RpcError, RpcResult, ValidationError,
};
use http::{header::CONTENT_TYPE, HeaderValue};
use ic_error_types::RejectCode;
use ic_management_canister_types::{
HttpRequestArgs as IcHttpRequest, HttpRequestResult as IcHttpResponse, TransformArgs,
TransformContext, TransformFunc,
Expand Down Expand Up @@ -149,6 +150,12 @@ where
(req_data.method, req_data.host),
1
);
} else if is_consensus_error(error) {
add_metric_entry!(
err_no_consensus,
(req_data.method, req_data.host),
1
);
} else {
log!(
Priority::TraceHttp,
Expand Down Expand Up @@ -407,3 +414,14 @@ pub fn transform_http_request(args: TransformArgs) -> IcHttpResponse {
headers: vec![],
}
}

fn is_consensus_error(error: &IcError) -> bool {
match error {
IcError::CallRejected { code, message } => {
code == &RejectCode::SysTransient
&& message
.contains("No consensus could be reached. Replicas had different responses.")
Comment thread
lpahlavi marked this conversation as resolved.
Outdated
}
_ => false,
}
}
5 changes: 5 additions & 0 deletions src/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ pub fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder<Vec<u8>>) -> st
&m.err_max_response_size_exceeded,
"Number of HTTP outcalls with max response size exceeded",
);
w.counter_entries(
"evmrpc_err_no_consensus",
&m.err_no_consensus,
"Number of HTTP outcalls with consensus errors",
);

Ok(())
})
Expand Down
2 changes: 2 additions & 0 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ pub struct Metrics {
pub err_http_outcall: HashMap<(MetricRpcMethod, MetricRpcHost, LegacyRejectionCode), u64>,
#[serde(rename = "errMaxResponseSizeExceeded")]
pub err_max_response_size_exceeded: HashMap<(MetricRpcMethod, MetricRpcHost), u64>,
#[serde(rename = "errNoConsensus")]
pub err_no_consensus: HashMap<(MetricRpcMethod, MetricRpcHost), u64>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
Expand Down
49 changes: 47 additions & 2 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1419,8 +1419,8 @@ async fn candid_rpc_should_return_inconsistent_results_with_consensus_error() {
setup
.check_metrics()
.await
.assert_contains_metric_matching(r#"evmrpc_err_http_outcall\{method="eth_getTransactionCount",host="rpc.ankr.com",code="SYS_TRANSIENT"\} 1 \d+"#)
.assert_contains_metric_matching(r#"evmrpc_err_http_outcall\{method="eth_getTransactionCount",host="ethereum-rpc.publicnode.com",code="SYS_TRANSIENT"\} 1 \d+"#);
.assert_contains_metric_matching(r#"evmrpc_err_no_consensus\{method="eth_getTransactionCount",host="rpc.ankr.com"\} 1 \d+"#)
.assert_contains_metric_matching(r#"evmrpc_err_no_consensus\{method="eth_getTransactionCount",host="ethereum-rpc.publicnode.com"\} 1 \d+"#, );
Comment thread
lpahlavi marked this conversation as resolved.
Outdated
}

#[tokio::test]
Expand Down Expand Up @@ -1461,6 +1461,51 @@ async fn should_have_metrics_for_request_endpoint() {
);
}

#[tokio::test]
async fn should_have_metrics_for_consensus_errors() {
let mocks = MockHttpOutcallsBuilder::new()
.given(get_transaction_count_request())
.respond_with(CanisterHttpReject::with_reject_code(RejectCode::SysTransient)
.with_message("No consensus could be reached. Replicas had different responses. Details: request_id: 21114231, timeout: 1761906996398580080, hashes: [2f66337c4e46bad3b26f3271d7def54b1b9632dee3146a993bf968ac9fb5bbd5: 15], [6ca1037eb29b619e387de330bc8e248a619b66b04cba26eab59723eddba12d1c: 14], [8ebeb0f2e2390b2e8c63f1ae24d416e6f90e4ddddc47c3df23c40ac03c7d3835: 2], [4fce8e9722ab59f92be2f4a65c5ae7d1f3b69f2b2993287c0795bbfe17d9ed51: 1]")
);

let setup = EvmRpcSetup::new().await.mock_api_keys().await;
let result = setup
.client(mocks)
.with_rpc_sources(RpcServices::EthMainnet(Some(vec![
EthMainnetService::Cloudflare,
])))
.build()
.get_transaction_count((
address!("0xdac17f958d2ee523a2206206994597c13d831ec7"),
BlockNumberOrTag::Latest,
))
.send()
.await
.expect_consistent();
assert_matches!(
result,
Err(RpcError::HttpOutcallError(HttpOutcallError::IcError {
code: LegacyRejectionCode::SysTransient,
..
}))
);

setup
.check_metrics()
.await
.assert_contains_metric_matching(
r#"evmrpc_requests\{method="eth_getTransactionCount",host="cloudflare-eth.com"\} 1 \d+"#,
)
.assert_contains_metric_matching(
r#"evmrpc_err_no_consensus\{method="eth_getTransactionCount",host="cloudflare-eth.com"\} 1 \d+"#,
).assert_does_not_contain_metric_matching(
r#"evmrpc_responses.*"#,
).assert_does_not_contain_metric_matching(
r#"evmrpc_err_http_outcall.*"#,
);
}

#[tokio::test]
async fn should_have_metrics_for_multi_request_endpoint() {
let mocks = MockHttpOutcallsBuilder::new()
Expand Down
Loading