Skip to content

Commit 76637a6

Browse files
committed
XC-412: Add json_request to EVM RPC canister
1 parent 48f5a14 commit 76637a6

File tree

12 files changed

+272
-50
lines changed

12 files changed

+272
-50
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

candid/evm_rpc.did

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,11 @@ type ValidationError = variant {
294294
Custom : text;
295295
InvalidHex : text;
296296
};
297+
type JsonRequestResult = variant { Ok : text; Err : RpcError };
298+
type MultiJsonRequestResult = variant {
299+
Consistent : JsonRequestResult;
300+
Inconsistent : vec record { RpcService; JsonRequestResult };
301+
};
297302
service : (InstallArgs) -> {
298303
eth_feeHistory : (RpcServices, opt RpcConfig, FeeHistoryArgs) -> (MultiFeeHistoryResult);
299304
eth_getBlockByNumber : (RpcServices, opt RpcConfig, BlockTag) -> (MultiGetBlockByNumberResult);
@@ -302,6 +307,8 @@ service : (InstallArgs) -> {
302307
eth_getTransactionReceipt : (RpcServices, opt RpcConfig, hash : text) -> (MultiGetTransactionReceiptResult);
303308
eth_sendRawTransaction : (RpcServices, opt RpcConfig, rawSignedTransactionHex : text) -> (MultiSendRawTransactionResult);
304309
eth_call : (RpcServices, opt RpcConfig, CallArgs) -> (MultiCallResult);
310+
json_request : (RpcServices, opt RpcConfig, text) -> (MultiJsonRequestResult);
311+
// DEPRECATED: Use `json_request` instead.
305312
request : (RpcService, json : text, maxResponseBytes : nat64) -> (RequestResult);
306313
requestCost : (RpcService, json : text, maxResponseBytes : nat64) -> (RequestCostResult) query;
307314

evm_rpc_client/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ evm_rpc_types = { path = "../evm_rpc_types", features = ["alloy"] }
1919
ic-cdk = { workspace = true }
2020
ic-error-types = { workspace = true }
2121
serde = { workspace = true }
22+
serde_json = { workspace = true }
2223
strum = { workspace = true }
2324

2425
[dev-dependencies]

evm_rpc_client/src/lib.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ use crate::request::{
111111
CallRequest, CallRequestBuilder, FeeHistoryRequest, FeeHistoryRequestBuilder,
112112
GetBlockByNumberRequest, GetBlockByNumberRequestBuilder, GetTransactionCountRequest,
113113
GetTransactionCountRequestBuilder, GetTransactionReceiptRequest,
114-
GetTransactionReceiptRequestBuilder, Request, RequestBuilder, SendRawTransactionRequest,
115-
SendRawTransactionRequestBuilder,
114+
GetTransactionReceiptRequestBuilder, JsonRequest, JsonRequestBuilder, Request, RequestBuilder,
115+
SendRawTransactionRequest, SendRawTransactionRequestBuilder,
116116
};
117117
use candid::{CandidType, Principal};
118118
use evm_rpc_types::{
@@ -611,6 +611,52 @@ impl<R> EvmRpcClient<R> {
611611
)
612612
}
613613

614+
/// Call `json_request` on the EVM RPC canister.
615+
///
616+
/// # Examples
617+
///
618+
/// ```rust
619+
/// use evm_rpc_client::EvmRpcClient;
620+
/// use serde_json::json;
621+
///
622+
/// # use evm_rpc_types::MultiRpcResult;
623+
/// # #[tokio::main]
624+
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
625+
/// let client = EvmRpcClient::builder_for_ic()
626+
/// # .with_default_stub_response(MultiRpcResult::Consistent(Ok(json!({"jsonrpc": "2.0","id": 73,"result": "0x24604d0d"}).to_string())))
627+
/// .build();
628+
///
629+
/// let result = client
630+
/// .json_request(json!({
631+
/// "jsonrpc": "2.0",
632+
/// "id": 73,
633+
/// "method": "eth_gasPrice",
634+
/// }))
635+
/// .send()
636+
/// .await
637+
/// .expect_consistent()
638+
/// .map(|s| serde_json::from_str(&s).unwrap())
639+
/// .unwrap();
640+
///
641+
/// assert_eq!(
642+
/// result,
643+
/// json!({
644+
/// "jsonrpc": "2.0",
645+
/// "id": 73,
646+
/// "result": "0x24604d0d"
647+
/// })
648+
/// );
649+
/// # Ok(())
650+
/// # }
651+
/// ```
652+
pub fn json_request(&self, params: serde_json::Value) -> JsonRequestBuilder<R> {
653+
RequestBuilder::new(
654+
self.clone(),
655+
JsonRequest::try_from(params).expect("Client error: invalid JSON request"),
656+
10_000_000_000,
657+
)
658+
}
659+
614660
/// Call `eth_sendRawTransaction` on the EVM RPC canister.
615661
///
616662
/// # Examples

evm_rpc_client/src/request/mod.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,37 @@ pub type GetTransactionReceiptRequestBuilder<R> = RequestBuilder<
274274
MultiRpcResult<Option<alloy_rpc_types::TransactionReceipt>>,
275275
>;
276276

277+
#[derive(Debug, Clone)]
278+
pub struct JsonRequest(String);
279+
280+
impl TryFrom<serde_json::Value> for JsonRequest {
281+
type Error = String;
282+
283+
fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
284+
serde_json::to_string(&value)
285+
.map(JsonRequest)
286+
.map_err(|e| e.to_string())
287+
}
288+
}
289+
290+
impl EvmRpcRequest for JsonRequest {
291+
type Config = RpcConfig;
292+
type Params = String;
293+
type CandidOutput = MultiRpcResult<String>;
294+
type Output = MultiRpcResult<String>;
295+
296+
fn endpoint(&self) -> EvmRpcEndpoint {
297+
EvmRpcEndpoint::JsonRequest
298+
}
299+
300+
fn params(self) -> Self::Params {
301+
self.0
302+
}
303+
}
304+
305+
pub type JsonRequestBuilder<R> =
306+
RequestBuilder<R, RpcConfig, String, MultiRpcResult<String>, MultiRpcResult<String>>;
307+
277308
#[derive(Debug, Clone)]
278309
pub struct SendRawTransactionRequest(Hex);
279310

@@ -339,6 +370,8 @@ pub enum EvmRpcEndpoint {
339370
GetTransactionCount,
340371
/// `eth_getTransactionReceipt` endpoint.
341372
GetTransactionReceipt,
373+
/// `json_request` endpoint.
374+
JsonRequest,
342375
/// `eth_sendRawTransaction` endpoint.
343376
SendRawTransaction,
344377
}
@@ -353,6 +386,7 @@ impl EvmRpcEndpoint {
353386
Self::GetLogs => "eth_getLogs",
354387
Self::GetTransactionCount => "eth_getTransactionCount",
355388
Self::GetTransactionReceipt => "eth_getTransactionReceipt",
389+
Self::JsonRequest => "json_request",
356390
Self::SendRawTransaction => "eth_sendRawTransaction",
357391
}
358392
}

src/candid_rpc/mod.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,22 @@ mod cketh_conversion;
33
mod tests;
44

55
use crate::rpc_client::{EthRpcClient, ReducedResult};
6+
use crate::types::MetricRpcMethod;
67
use crate::{
78
add_metric_entry,
89
providers::resolve_rpc_service,
910
types::{MetricRpcHost, ResolvedRpcService, RpcMethod},
1011
};
1112
use candid::Nat;
13+
use canhttp::http::json::JsonRpcRequest;
1214
use canhttp::multi::{ReductionError, Timestamp};
1315
use ethers_core::{types::Transaction, utils::rlp};
14-
use evm_rpc_types::{Hex, Hex32, MultiRpcResult, Nat256, RpcResult, ValidationError};
16+
use evm_rpc_types::{Hex, Hex32, MultiRpcResult, Nat256, RpcError, RpcResult, ValidationError};
1517

16-
fn process_result<T>(method: RpcMethod, result: ReducedResult<T>) -> MultiRpcResult<T> {
18+
fn process_result<T>(
19+
method: impl Into<MetricRpcMethod> + Clone,
20+
result: ReducedResult<T>,
21+
) -> MultiRpcResult<T> {
1722
match result {
1823
Ok(value) => MultiRpcResult::Consistent(Ok(value)),
1924
Err(err) => match err {
@@ -27,7 +32,7 @@ fn process_result<T>(method: RpcMethod, result: ReducedResult<T>) -> MultiRpcRes
2732
add_metric_entry!(
2833
inconsistent_responses,
2934
(
30-
method.into(),
35+
method.clone().into(),
3136
MetricRpcHost(
3237
provider
3338
.hostname()
@@ -172,6 +177,24 @@ impl CandidRpcClient {
172177
)
173178
.map(from_data)
174179
}
180+
181+
pub async fn json_request(&self, json_rpc_payload: String) -> MultiRpcResult<String> {
182+
let request: JsonRpcRequest<serde_json::Value> =
183+
match serde_json::from_str(&json_rpc_payload) {
184+
Ok(req) => req,
185+
Err(e) => {
186+
return MultiRpcResult::Consistent(Err(RpcError::ValidationError(
187+
ValidationError::Custom(format!("Invalid JSON RPC request: {e}")),
188+
)))
189+
}
190+
};
191+
process_result(
192+
MetricRpcMethod(request.method().to_string()),
193+
self.client
194+
.json_request(request.method(), request.params())
195+
.await,
196+
)
197+
}
175198
}
176199

177200
fn get_transaction_hash(raw_signed_transaction_hex: &Hex) -> Option<Hex32> {

src/http.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ use ic_cdk::api::management_canister::http_request::{
3737
};
3838
use serde::de::DeserializeOwned;
3939
use serde::Serialize;
40+
use std::cmp::PartialEq;
4041
use std::fmt::Debug;
4142
use thiserror::Error;
4243
use tower::layer::util::{Identity, Stack};

src/main.rs

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use evm_rpc::{
1717
providers::{find_provider, resolve_rpc_service, PROVIDERS, SERVICE_PROVIDER_MAP},
1818
types::{OverrideProvider, Provider, ProviderId, RpcAccess, RpcAuth},
1919
};
20-
use evm_rpc_types::{Hex32, HttpOutcallError, MultiRpcResult, RpcConfig, RpcResult};
20+
use evm_rpc_types::{Hex32, HttpOutcallError, MultiRpcResult, RpcConfig, RpcResult, RpcServices};
2121
use ic_canister_log::log;
2222
use ic_cdk::{
2323
api::{
@@ -46,7 +46,7 @@ pub fn require_api_key_principal_or_controller() -> Result<(), String> {
4646
#[update(name = "eth_getLogs")]
4747
#[candid_method(rename = "eth_getLogs")]
4848
pub async fn eth_get_logs(
49-
source: evm_rpc_types::RpcServices,
49+
source: RpcServices,
5050
config: Option<evm_rpc_types::GetLogsRpcConfig>,
5151
args: evm_rpc_types::GetLogsArgs,
5252
) -> MultiRpcResult<Vec<evm_rpc_types::LogEntry>> {
@@ -61,8 +61,8 @@ pub async fn eth_get_logs(
6161
#[update(name = "eth_getBlockByNumber")]
6262
#[candid_method(rename = "eth_getBlockByNumber")]
6363
pub async fn eth_get_block_by_number(
64-
source: evm_rpc_types::RpcServices,
65-
config: Option<evm_rpc_types::RpcConfig>,
64+
source: RpcServices,
65+
config: Option<RpcConfig>,
6666
block: evm_rpc_types::BlockTag,
6767
) -> MultiRpcResult<evm_rpc_types::Block> {
6868
match CandidRpcClient::new(source, config, now()) {
@@ -74,8 +74,8 @@ pub async fn eth_get_block_by_number(
7474
#[update(name = "eth_getTransactionReceipt")]
7575
#[candid_method(rename = "eth_getTransactionReceipt")]
7676
pub async fn eth_get_transaction_receipt(
77-
source: evm_rpc_types::RpcServices,
78-
config: Option<evm_rpc_types::RpcConfig>,
77+
source: RpcServices,
78+
config: Option<RpcConfig>,
7979
tx_hash: Hex32,
8080
) -> MultiRpcResult<Option<evm_rpc_types::TransactionReceipt>> {
8181
match CandidRpcClient::new(source, config, now()) {
@@ -87,8 +87,8 @@ pub async fn eth_get_transaction_receipt(
8787
#[update(name = "eth_getTransactionCount")]
8888
#[candid_method(rename = "eth_getTransactionCount")]
8989
pub async fn eth_get_transaction_count(
90-
source: evm_rpc_types::RpcServices,
91-
config: Option<evm_rpc_types::RpcConfig>,
90+
source: RpcServices,
91+
config: Option<RpcConfig>,
9292
args: evm_rpc_types::GetTransactionCountArgs,
9393
) -> MultiRpcResult<evm_rpc_types::Nat256> {
9494
match CandidRpcClient::new(source, config, now()) {
@@ -100,8 +100,8 @@ pub async fn eth_get_transaction_count(
100100
#[update(name = "eth_feeHistory")]
101101
#[candid_method(rename = "eth_feeHistory")]
102102
pub async fn eth_fee_history(
103-
source: evm_rpc_types::RpcServices,
104-
config: Option<evm_rpc_types::RpcConfig>,
103+
source: RpcServices,
104+
config: Option<RpcConfig>,
105105
args: evm_rpc_types::FeeHistoryArgs,
106106
) -> MultiRpcResult<evm_rpc_types::FeeHistory> {
107107
match CandidRpcClient::new(source, config, now()) {
@@ -113,8 +113,8 @@ pub async fn eth_fee_history(
113113
#[update(name = "eth_sendRawTransaction")]
114114
#[candid_method(rename = "eth_sendRawTransaction")]
115115
pub async fn eth_send_raw_transaction(
116-
source: evm_rpc_types::RpcServices,
117-
config: Option<evm_rpc_types::RpcConfig>,
116+
source: RpcServices,
117+
config: Option<RpcConfig>,
118118
raw_signed_transaction_hex: evm_rpc_types::Hex,
119119
) -> MultiRpcResult<evm_rpc_types::SendRawTransactionStatus> {
120120
match CandidRpcClient::new(source, config, now()) {
@@ -130,8 +130,8 @@ pub async fn eth_send_raw_transaction(
130130
#[update(name = "eth_call")]
131131
#[candid_method(rename = "eth_call")]
132132
pub async fn eth_call(
133-
source: evm_rpc_types::RpcServices,
134-
config: Option<evm_rpc_types::RpcConfig>,
133+
source: RpcServices,
134+
config: Option<RpcConfig>,
135135
args: evm_rpc_types::CallArgs,
136136
) -> MultiRpcResult<evm_rpc_types::Hex> {
137137
match CandidRpcClient::new(source, config, now()) {
@@ -140,6 +140,19 @@ pub async fn eth_call(
140140
}
141141
}
142142

143+
#[update(name = "json_request")]
144+
#[candid_method(rename = "json_request")]
145+
pub async fn json_request(
146+
source: RpcServices,
147+
config: Option<RpcConfig>,
148+
args: String,
149+
) -> MultiRpcResult<String> {
150+
match CandidRpcClient::new(source, config, now()) {
151+
Ok(source) => source.json_request(args).await,
152+
Err(err) => Err(err).into(),
153+
}
154+
}
155+
143156
#[update]
144157
#[candid_method]
145158
async fn request(

src/rpc_client/eth_rpc/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ pub enum ResponseTransform {
4444
FeeHistory,
4545
#[n(4)]
4646
SendRawTransaction,
47+
#[n(5)]
48+
Raw,
4749
}
4850

4951
impl ResponseTransform {
@@ -87,6 +89,7 @@ impl ResponseTransform {
8789
Self::SendRawTransaction => {
8890
sanitize_send_raw_transaction_result(body_bytes, Parser::new())
8991
}
92+
Self::Raw => redact_response::<Block>(body_bytes),
9093
}
9194
}
9295
}

src/rpc_client/json/responses.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,3 +323,9 @@ impl AsRef<[u8]> for Data {
323323
&self.0
324324
}
325325
}
326+
327+
impl HttpResponsePayload for String {
328+
fn response_transform() -> Option<ResponseTransform> {
329+
Some(ResponseTransform::Raw)
330+
}
331+
}

0 commit comments

Comments
 (0)