Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: adjust Candid-RPC methods for state machine tests #95

Merged
merged 37 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
8d382af
Progress
rvanasa Dec 7, 2023
6ae1643
Fix formatting
rvanasa Dec 7, 2023
cf07d3b
Add specific error message when decoding Candid response from update …
rvanasa Dec 7, 2023
c64d3e9
Add equivalent error message for query call
rvanasa Dec 7, 2023
9f9711a
Derive 'PartialEq' and 'Eq' for Candid-RPC types
rvanasa Dec 7, 2023
76cdd8b
Use latest 'evm-rpc' branch
rvanasa Dec 7, 2023
1a2c1ff
Fix JSON
rvanasa Dec 7, 2023
756faa4
Update Candid interface
rvanasa Dec 7, 2023
56ab8f0
Test 'eth_get_transaction_count'
rvanasa Dec 8, 2023
1239df9
Misc
rvanasa Dec 8, 2023
a77742a
Add 'should_decode_checked_amount' test
rvanasa Dec 9, 2023
a86e11c
Reformat test JSON strings
rvanasa Dec 9, 2023
c14a96c
Reorganize tests
rvanasa Dec 9, 2023
2339733
Use camelCase for all fields in Candid interface
rvanasa Dec 9, 2023
da42bac
Use original Ethereum RPC method naming convention
rvanasa Dec 9, 2023
cff2565
Update 'examples' script
rvanasa Dec 9, 2023
e89d9b4
Merge branch 'main' of https://github.com/internet-computer-protocol/…
rvanasa Dec 9, 2023
602c36e
Update Motoko E2E test
rvanasa Dec 9, 2023
4d903bb
Update method names in state machine tests
rvanasa Dec 9, 2023
b33901f
Update canister method names to Motoko convention
rvanasa Dec 9, 2023
5fadbe2
Fix
rvanasa Dec 9, 2023
6d9f180
Rename Candid methods
rvanasa Dec 9, 2023
73c84b1
Regenerate Candid interface
rvanasa Dec 9, 2023
859e044
Update Rust E2E test
rvanasa Dec 9, 2023
465e54e
Update method names
rvanasa Dec 9, 2023
ce4f834
Update 'eth_getTransactionCount' test
rvanasa Dec 9, 2023
55944e5
Fix 'eth_get_logs' test
rvanasa Dec 11, 2023
003f14f
Refactor for repeated mock HTTP scenarios
rvanasa Dec 11, 2023
c526f92
Test 'eth_feeHistory' and 'eth_sendRawTransaction'
rvanasa Dec 11, 2023
fe5ef5f
Update 'examples' script
rvanasa Dec 11, 2023
83edfb3
Misc
rvanasa Dec 11, 2023
af7aae4
Fix Candid decoding for 'TransactionStatus'
rvanasa Dec 12, 2023
78ce8c9
Update Cargo.lock
rvanasa Dec 12, 2023
20d572c
Add all fields to 'eth_feeHistory' test
rvanasa Dec 12, 2023
6bb7493
Add all fields to 'eth_getLogs' test
rvanasa Dec 12, 2023
e88b65d
Rename param 'cycles' -> 'cycles_cost' for clarity
rvanasa Dec 12, 2023
fcb1398
Add fail case tests
rvanasa Dec 12, 2023
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
212 changes: 105 additions & 107 deletions Cargo.lock

Large diffs are not rendered by default.

112 changes: 56 additions & 56 deletions candid/evm_rpc.did
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
type Auth = variant { Rpc; RegisterProvider; FreeRpc; ManageService };
type Block = record { base_fee_per_gas : nat; number : nat };
type Block = record { baseFeePerGas : nat; number : nat };
type BlockSpec = variant { Tag : BlockTag; Number : nat };
type BlockTag = variant {
Earliest;
Expand All @@ -17,19 +17,19 @@ type EthMainnetService = variant { BlockPi; Cloudflare; PublicNode; Ankr };
type EthSepoliaService = variant { BlockPi; PublicNode; Ankr };
type FeeHistory = record {
reward : vec vec nat;
base_fee_per_gas : vec nat;
oldest_block : nat;
oldestBlock : nat;
baseFeePerGas : vec nat;
};
type FeeHistoryArgs = record {
block_count : nat;
newest_block : BlockSpec;
reward_percentiles : opt vec nat8;
blockCount : nat;
newestBlock : BlockSpec;
rewardPercentiles : opt vec nat8;
};
type GetLogsArgs = record {
fromBlock : opt BlockSpec;
toBlock : opt BlockSpec;
addresses : vec text;
topics : opt vec text;
to_block : opt BlockSpec;
from_block : opt BlockSpec;
};
type GetTransactionCountArgs = record { address : text; block : BlockSpec };
type HttpHeader = record { value : text; name : text };
Expand All @@ -38,19 +38,19 @@ type HttpOutcallError = variant {
InvalidHttpJsonRpcResponse : record {
status : nat16;
body : text;
parsing_error : opt text;
parsingError : opt text;
};
};
type JsonRpcError = record { code : int64; message : text };
type LogEntry = record {
transaction_hash : opt vec nat8;
block_hash : opt vec nat8;
log_index : opt nat;
data : vec nat8;
transaction_index : opt nat;
block_number : opt nat;
topics : vec vec nat8;
address : vec nat8;
transactionHash : opt text;
blockNumber : opt nat;
data : text;
blockHash : opt text;
transactionIndex : opt nat;
topics : vec text;
address : text;
logIndex : opt nat;
removed : bool;
};
type Message = variant { Data : vec nat8; Hash : vec nat8 };
Expand All @@ -60,21 +60,21 @@ type ProviderError = variant {
NoPermission;
};
type ProviderView = record {
cyclesPerCall : nat64;
owner : principal;
hostname : text;
provider_id : nat64;
cycles_per_message_byte : nat64;
primary : bool;
chain_id : nat64;
cycles_per_call : nat64;
chainId : nat64;
cyclesPerMessageByte : nat64;
providerId : nat64;
};
type RegisterProviderArgs = record {
credential_headers : opt vec HttpHeader;
cyclesPerCall : nat64;
credentialPath : text;
hostname : text;
cycles_per_message_byte : nat64;
chain_id : nat64;
cycles_per_call : nat64;
credential_path : text;
credentialHeaders : opt vec HttpHeader;
chainId : nat64;
cyclesPerMessageByte : nat64;
};
type RejectionCode = variant {
NoError;
Expand Down Expand Up @@ -112,27 +112,27 @@ type SignedMessage = record {
};
type Source = variant {
Custom : record { url : text; headers : opt vec HttpHeader };
Service : record { hostname : text; chain_id : opt nat64 };
Service : record { hostname : text; chainId : opt nat64 };
Chain : nat64;
Provider : nat64;
};
type TransactionReceipt = record {
effective_gas_price : nat;
status : TransactionStatus;
transaction_hash : vec nat8;
block_hash : vec nat8;
block_number : nat;
gas_used : nat;
transactionHash : text;
blockNumber : nat;
blockHash : text;
effectiveGasPrice : nat;
gasUsed : nat;
};
type TransactionStatus = variant { Success; Failure };
type UpdateProviderArgs = record {
credential_headers : opt vec HttpHeader;
cyclesPerCall : opt nat64;
credentialPath : opt text;
hostname : opt text;
provider_id : nat64;
cycles_per_message_byte : opt nat64;
credentialHeaders : opt vec HttpHeader;
primary : opt bool;
cycles_per_call : opt nat64;
credential_path : opt text;
cyclesPerMessageByte : opt nat64;
providerId : nat64;
};
type ValidationError = variant {
CredentialPathNotAllowed : text;
Expand All @@ -144,26 +144,26 @@ type ValidationError = variant {
service : {
authorize : (principal, Auth) -> ();
deauthorize : (principal, Auth) -> ();
eth_fee_history : (CandidRpcSource, FeeHistoryArgs) -> (Result);
eth_get_block_by_number : (CandidRpcSource, BlockSpec) -> (Result_1);
eth_get_logs : (CandidRpcSource, GetLogsArgs) -> (Result_2);
eth_get_transaction_count : (CandidRpcSource, GetTransactionCountArgs) -> (
eth_feeHistory : (CandidRpcSource, FeeHistoryArgs) -> (Result);
eth_getBlockByNumber : (CandidRpcSource, BlockSpec) -> (Result_1);
eth_getLogs : (CandidRpcSource, GetLogsArgs) -> (Result_2);
eth_getTransactionCount : (CandidRpcSource, GetTransactionCountArgs) -> (
Result_3,
);
eth_get_transaction_receipt : (CandidRpcSource, text) -> (Result_4);
eth_send_raw_transaction : (CandidRpcSource, text) -> (Result_5);
get_accumulated_cycle_count : (nat64) -> (nat) query;
get_authorized : (Auth) -> (vec text) query;
get_nodes_in_subnet : () -> (nat32) query;
get_open_rpc_access : () -> (bool) query;
get_providers : () -> (vec ProviderView) query;
register_provider : (RegisterProviderArgs) -> (nat64);
eth_getTransactionReceipt : (CandidRpcSource, text) -> (Result_4);
eth_sendRawTransaction : (CandidRpcSource, text) -> (Result_5);
getAccumulatedCycleCount : (nat64) -> (nat) query;
getAuthorized : (Auth) -> (vec text) query;
getNodesInSubnet : () -> (nat32) query;
getOpenRpcAccess : () -> (bool) query;
getProviders : () -> (vec ProviderView) query;
registerProvider : (RegisterProviderArgs) -> (nat64);
request : (Source, text, nat64) -> (Result_6);
request_cost : (Source, text, nat64) -> (Result_7) query;
set_nodes_in_subnet : (nat32) -> ();
set_open_rpc_access : (bool) -> ();
unregister_provider : (nat64) -> (bool);
update_provider : (UpdateProviderArgs) -> ();
verify_message_signature : (SignedMessage) -> (bool) query;
withdraw_accumulated_cycles : (nat64, principal) -> ();
requestCost : (Source, text, nat64) -> (Result_7) query;
setNodesInSubnet : (nat32) -> ();
setOpenRpcAccess : (bool) -> ();
unregisterProvider : (nat64) -> (bool);
updateProvider : (UpdateProviderArgs) -> ();
verifyMessageSignature : (SignedMessage) -> (bool) query;
withdrawAccumulatedCycles : (nat64, principal) -> ();
}
14 changes: 7 additions & 7 deletions e2e/motoko/Main.mo
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ actor class Main() {
public shared ({ caller }) func test() : async () {
let source = #Service {
hostname = "cloudflare-eth.com";
chain_id = ?(1 : Nat64); // Ethereum mainnet
chainId = ?(1 : Nat64); // Ethereum mainnet
};
let json = "{\"jsonrpc\":\"2.0\",\"method\":\"eth_gasPrice\",\"params\":null,\"id\":1}";
let maxResponseBytes : Nat64 = 1000;

let cyclesResult = await EvmRpcCanister.request_cost(source, json, maxResponseBytes);
let cyclesResult = await EvmRpcCanister.requestCost(source, json, maxResponseBytes);
let cycles = switch cyclesResult {
case (#Ok cycles) { cycles };
case (#Err err) {
Expand Down Expand Up @@ -67,7 +67,7 @@ actor class Main() {

// Invalid address
assert not (
await EvmRpcCanister.verify_message_signature({
await EvmRpcCanister.verifyMessageSignature({
address = a2;
message = m1;
signature = s1;
Expand All @@ -76,7 +76,7 @@ actor class Main() {

// Invalid message
assert not (
await EvmRpcCanister.verify_message_signature({
await EvmRpcCanister.verifyMessageSignature({
address = a1;
message = m2;
signature = s1;
Expand All @@ -85,7 +85,7 @@ actor class Main() {

// Invalid signature
assert not (
await EvmRpcCanister.verify_message_signature({
await EvmRpcCanister.verifyMessageSignature({
address = a1;
message = m1;
signature = s2;
Expand All @@ -94,14 +94,14 @@ actor class Main() {

// Valid signatures
assert (
await EvmRpcCanister.verify_message_signature({
await EvmRpcCanister.verifyMessageSignature({
address = a1;
message = m1;
signature = s1;
})
);
assert (
await EvmRpcCanister.verify_message_signature({
await EvmRpcCanister.verifyMessageSignature({
address = a1;
message = m2;
signature = s2;
Expand Down
4 changes: 2 additions & 2 deletions e2e/rust/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ pub async fn test() {
let params = (
&Source::Service {
hostname: "cloudflare-eth.com".to_string(),
chain_id: Some(1), // Ethereum mainnet
chainId: Some(1), // Ethereum mainnet
},
"{\"jsonrpc\":\"2.0\",\"method\":\"eth_gasPrice\",\"params\":null,\"id\":1}".to_string(),
1000 as u64,
);

// Get cycles cost
let (cycles_result,): (Result<u128, RpcError>,) =
ic_cdk::api::call::call(evm_rpc.0, "request_cost", params.clone())
ic_cdk::api::call::call(evm_rpc.0, "requestCost", params.clone())
.await
.unwrap();
let cycles = cycles_result
Expand Down
4 changes: 2 additions & 2 deletions lib/motoko/src/lib.mo
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ module {
case (#Service { hostname; network }) {
#Service {
hostname;
chain_id = switch network {
chainId = switch network {
case (?n) { ?wrapChainId(n) };
case null { null };
};
Expand Down Expand Up @@ -122,7 +122,7 @@ module {
#InvalidHttpJsonRpcResponse {
status = 0;
body = text;
parsing_error = ?("error while parsing JSON response");
parsingError = ?("error while parsing JSON response");
}
);
};
Expand Down
16 changes: 8 additions & 8 deletions scripts/examples
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# Run a variety of example RPC calls.

CANISTER_NAME=evm_rpc
CANISTER_ID=evm_rpc
CYCLES=100000000000
WALLET=$(dfx identity get-wallet)
JSON_SOURCE=Chain=1
Expand All @@ -10,11 +10,11 @@ CANDID_SOURCE=EthMainnet
FLAGS="--with-cycles=$CYCLES --wallet=$WALLET"


dfx canister call $CANISTER_NAME request "(variant {$JSON_SOURCE}, "'"{ \"jsonrpc\": \"2.0\", \"method\": \"eth_gasPrice\", \"params\": [], \"id\": 1 }"'", 1000)" $FLAGS || exit 1
dfx canister call $CANISTER_ID request "(variant {$JSON_SOURCE}, "'"{ \"jsonrpc\": \"2.0\", \"method\": \"eth_gasPrice\", \"params\": [], \"id\": 1 }"'", 1000)" $FLAGS || exit 1

dfx canister call $CANISTER_NAME eth_get_logs "(variant {$CANDID_SOURCE}, record {addresses = vec {\"0xdAC17F958D2ee523a2206206994597C13D831ec7\"}})" $FLAGS || exit 1
dfx canister call $CANISTER_NAME eth_get_block_by_number "(variant {$CANDID_SOURCE}, variant {Tag=variant {Latest}})" $FLAGS || exit 1
dfx canister call $CANISTER_NAME eth_get_transaction_receipt "(variant {$CANDID_SOURCE}, \"0xdd5d4b18923d7aae953c7996d791118102e889bea37b48a651157a4890e4746f\")" $FLAGS || exit 1
dfx canister call $CANISTER_NAME eth_get_transaction_count "(variant {$CANDID_SOURCE}, record {address = \"0xdAC17F958D2ee523a2206206994597C13D831ec7\"; block = variant {Tag = variant {Latest}}})" $FLAGS || exit 1
dfx canister call $CANISTER_NAME eth_fee_history "(variant {$CANDID_SOURCE}, record {block_count = 3; newest_block = variant {Tag = variant {Latest}}})" $FLAGS || exit 1
dfx canister call $CANISTER_NAME eth_send_raw_transaction "(variant {$CANDID_SOURCE}, \"0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83\")" $FLAGS || exit 1
dfx canister call $CANISTER_ID eth_getLogs "(variant {$CANDID_SOURCE}, record {addresses = vec {\"0xdAC17F958D2ee523a2206206994597C13D831ec7\"}})" $FLAGS || exit 1
dfx canister call $CANISTER_ID eth_getBlockByNumber "(variant {$CANDID_SOURCE}, variant {Tag=variant {Latest}})" $FLAGS || exit 1
dfx canister call $CANISTER_ID eth_getTransactionReceipt "(variant {$CANDID_SOURCE}, \"0xdd5d4b18923d7aae953c7996d791118102e889bea37b48a651157a4890e4746f\")" $FLAGS || exit 1
dfx canister call $CANISTER_ID eth_getTransactionCount "(variant {$CANDID_SOURCE}, record {address = \"0xdAC17F958D2ee523a2206206994597C13D831ec7\"; block = variant {Tag = variant {Latest}}})" $FLAGS || exit 1
dfx canister call $CANISTER_ID eth_feeHistory "(variant {$CANDID_SOURCE}, record {blockCount = 3; newestBlock = variant {Tag = variant {Latest}}})" $FLAGS || exit 1
dfx canister call $CANISTER_ID eth_sendRawTransaction "(variant {$CANDID_SOURCE}, \"0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83\")" $FLAGS || exit 1
17 changes: 9 additions & 8 deletions scripts/local
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
#!/usr/bin/env bash
# Redeploy the canister and set up permissions in your local environment.

CANISTER_ID=evm_rpc
PRINCIPAL=$(dfx identity get-principal)

dfx deploy evm_rpc --mode reinstall -y
# dfx canister call evm_rpc authorize "(principal \"$PRINCIPAL\", variant { Rpc })"
dfx canister call evm_rpc authorize "(principal \"$PRINCIPAL\", variant { RegisterProvider })"

dfx canister call evm_rpc register_provider '(record {hostname = "cloudflare-eth.com"; credential_path = "/v1/mainnet"; chain_id = 1; cycles_per_call = 1000; cycles_per_message_byte = 100})'
dfx deploy $CANISTER_ID --mode reinstall -y
dfx canister call $CANISTER_ID authorize "(principal \"$PRINCIPAL\", variant { RegisterProvider })"

dfx canister call evm_rpc request_cost '(variant {Chain=1}, "{ \"jsonrpc\": \"2.0\", \"method\": \"eth_getBlockByNumber\", \"params\": [\"0x2244\", true], \"id\": 1 }", 1000)'
dfx canister call evm_rpc request '(variant {Chain=1}, "{ \"jsonrpc\": \"2.0\", \"method\": \"eth_getBlockByNumber\", \"params\": [\"0x2244\", true], \"id\": 1 }", 1000)'
dfx canister call $CANISTER_ID registerProvider '(record {hostname = "cloudflare-eth.com"; credentialPath = "/v1/mainnet"; chainId = 1; cyclesPerCall = 1000; cyclesPerMessageByte = 100})'

dfx canister call evm_rpc request_cost '(variant {Provider=0}, "{ \"jsonrpc\": \"2.0\", \"method\": \"eth_getBlockByNumber\", \"params\": [\"0x2244\", true], \"id\": 1 }", 1000)'
dfx canister call evm_rpc request '(variant {Provider=0}, "{ \"jsonrpc\": \"2.0\", \"method\": \"eth_getBlockByNumber\", \"params\": [\"0x2244\", true], \"id\": 1 }", 1000)'
dfx canister call $CANISTER_ID requestCost '(variant {Chain=1}, "{ \"jsonrpc\": \"2.0\", \"method\": \"eth_getBlockByNumber\", \"params\": [\"0x2244\", true], \"id\": 1 }", 1000)'
dfx canister call $CANISTER_ID request '(variant {Chain=1}, "{ \"jsonrpc\": \"2.0\", \"method\": \"eth_getBlockByNumber\", \"params\": [\"0x2244\", true], \"id\": 1 }", 1000)'

dfx canister call $CANISTER_ID requestCost '(variant {Provider=0}, "{ \"jsonrpc\": \"2.0\", \"method\": \"eth_getBlockByNumber\", \"params\": [\"0x2244\", true], \"id\": 1 }", 1000)'
dfx canister call $CANISTER_ID request '(variant {Provider=0}, "{ \"jsonrpc\": \"2.0\", \"method\": \"eth_getBlockByNumber\", \"params\": [\"0x2244\", true], \"id\": 1 }", 1000)'
35 changes: 22 additions & 13 deletions src/candid_rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use std::str::FromStr;
use async_trait::async_trait;
use cketh_common::{
eth_rpc::{
into_nat, Block, FeeHistory, GetLogsParam, Hash, LogEntry, ProviderError, RpcError,
SendRawTransactionResult, ValidationError,
into_nat, Block, FeeHistory, GetLogsParam, Hash, HttpOutcallError, LogEntry, ProviderError,
RpcError, SendRawTransactionResult, ValidationError,
},
eth_rpc_client::{
providers::{EthMainnetService, EthSepoliaService, RpcApi, RpcService},
Expand All @@ -13,10 +13,7 @@ use cketh_common::{
},
lifecycle::EthereumNetwork,
};
use ic_cdk::api::{
call::CallResult,
management_canister::http_request::{CanisterHttpRequestArgument, HttpResponse},
};
use ic_cdk::api::management_canister::http_request::{CanisterHttpRequestArgument, HttpResponse};

use crate::*;

Expand Down Expand Up @@ -61,13 +58,25 @@ impl RpcTransport for CanisterTransport {
async fn http_request(
_provider: &RpcService,
request: CanisterHttpRequestArgument,
cost: u128,
) -> CallResult<HttpResponse> {
Ok(
ic_cdk::api::management_canister::http_request::http_request(request, cost)
.await?
.0,
)
cycles_cost: u128,
) -> RpcResult<HttpResponse> {
if !is_authorized(&ic_cdk::caller(), Auth::FreeRpc) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cycles payment logic was originally in the forked ckETH codebase; I moved it here to support the FreeRpc authorization for Candid-RPC methods. Everything else works the same as before.

let cycles_available = ic_cdk::api::call::msg_cycles_available128();
if cycles_available < cycles_cost {
return Err(ProviderError::TooFewCycles {
expected: cycles_cost,
received: cycles_available,
}
.into());
}
ic_cdk::api::call::msg_cycles_accept128(cycles_cost);
}
match ic_cdk::api::management_canister::http_request::http_request(request, cycles_cost)
.await
{
Ok((response,)) => Ok(response),
Err((code, message)) => Err(HttpOutcallError::IcError { code, message }.into()),
}
}
}

Expand Down
Loading