Skip to content

Commit

Permalink
feat!: NNS-managed RPC providers (#252)
Browse files Browse the repository at this point in the history
This PR makes significant changes to the EVM RPC canister to enable
using NNS proposals in place of a trusted principal to add, remove, and
update RPC providers.

**Progress:**
- [x] Refactor `Provider` struct for immutability
- [x] Remove per-provider cycles accounting in favor of decentralized
ownership
- [x] Update JSON-RPC API configuration for default RPC providers
- [x] Change URL and header config to use `{API_KEY}` placeholder
- [x] Adjust URL / header validation logic
- [x] Include more tests for URL / header validation
- [x] Refactor to use `ProviderId` type alias
- [x] ~~Implement post-upgrade actions to add, remove, and update one or
more providers~~
- [x] Add new API key system
- [x] Remove `stableSize` and `stableRead` canister methods
- [x] Simplify principal authorization system
- [x] Remove `authorize`, `deauthorize`, and `getAuthorized` canister
methods
- [x] Convert to using hard-coded RPC providers and service mappings
- [x] Add sanity checks for hard-coded providers and service-provider
mappings
- [x] Remove `FreeRpc` auth
- [x] Remove `PriorityRpc` auth as well as `getOpenRpcAccess` and
`setOpenRpcAccess`
- [x] Use a fixed number of subnet nodes (28) for all EVM RPC
deployments
- [x] Remove `PrincipalStorable` struct
- [x] Include changes from
[dfinity/ic#1027](dfinity/ic#1027)
- [x] Include changes from
[dfinity/ic#1113](dfinity/ic#1113)
- [x] ~~Add optional `public_url` to each provider~~
- [x] Refactor URLs and headers to use enum variants
- [x] Add `demo` flag to locally use EVM RPC canister without cycles
payments, e.g. through Candid UI
- [x] Remove header validation in favor of overwriting default
`Content-Type` header
- [x] Update state machine test runner logic to test new init/upgrade
args
- [x] Update existing tests
- [x] Add unit tests for URL validation
- [x] Test API key insertion
- [x] Test simplified permission system
- [x] Update Rust and Motoko E2E tests
- [x] Add public URLs for all RPC providers

---------

Co-authored-by: gregorydemay <[email protected]>
  • Loading branch information
rvanasa and gregorydemay authored Sep 5, 2024
1 parent 8ce8414 commit 1e5ba9a
Show file tree
Hide file tree
Showing 26 changed files with 1,622 additions and 2,581 deletions.
867 changes: 456 additions & 411 deletions Cargo.lock

Large diffs are not rendered by default.

19 changes: 8 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ ic-canisters-http-types = { workspace = true }
ic-nervous-system-common = { workspace = true }
ic-metrics-encoder = { workspace = true }
ic-stable-structures = { workspace = true }
ic-certified-map = { workspace = true }
ic-canister-log = { git = "https://github.com/dfinity/ic", rev = "1f551bea63354370b6e7a5037e96d464bdab3b41", package = "ic-canister-log" }
ic-cdk = { workspace = true }
ic-cdk-macros = { workspace = true }
ic-canister-log = { git = "https://github.com/dfinity/ic", rev = "5991b07d0e396b7885d5b4d4c917f7290dd70814", package = "ic-canister-log" }
cketh-common = { git = "https://github.com/dfinity/ic", rev = "5991b07d0e396b7885d5b4d4c917f7290dd70814", package = "ic-cketh-minter" }
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-traits = "0.2"
Expand All @@ -39,6 +39,7 @@ url = "2.5"
async-trait = "0.1"
hex = "0.4"
ethers-core = "2.0"
zeroize = "1.8"

[dev-dependencies]
ic-ic00-types = { git = "https://github.com/dfinity/ic", rev = "release-2023-09-27_23-01" }
Expand All @@ -53,21 +54,17 @@ candid = { version = "0.9" }
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" }
ic-cdk = "0.10"
ic-cdk-bindgen = "0.1"
ic-cdk-macros = "0.7"
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"
ic-certified-map = "0.4"
ic-cdk = "0.10"
ic-cdk-macros = "0.7"
ic-cdk-bindgen = "0.1"
num-bigint = "0.4.6"
proptest = "1.5.0"
serde = "1.0"
serde_json = "1.0"


[patch.crates-io]
wasm-bindgen = { git = "https://github.com/dfinity/wasm-bindgen", rev = "9cde9c9a88c1fad13a8663277650e01b5671843e" }

[workspace]
members = ["e2e/rust", "evm_rpc_types"]
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,11 @@ npm install
# Deploy to the local replica
dfx start --background
npm run generate
dfx deploy evm_rpc --argument "(record {nodesInSubnet = 28})"
dfx deploy evm_rpc

# Alternatively, deploy and run test suite
dfx start --background
scripts/e2e
```

Regenerate language bindings with the `generate` [npm script](https://docs.npmjs.com/cli/v10/using-npm/scripts):
Expand Down
79 changes: 29 additions & 50 deletions candid/evm_rpc.did
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
type Auth = variant { FreeRpc; PriorityRpc; RegisterProvider; Manage };
type Block = record {
miner : text;
totalDifficulty : opt nat;
Expand Down Expand Up @@ -43,6 +42,7 @@ type EthSepoliaService = variant {
Ankr;
BlockPi;
PublicNode;
Sepolia;
};
type L2MainnetService = variant {
Alchemy;
Expand Down Expand Up @@ -79,7 +79,8 @@ type HttpOutcallError = variant {
};
};
type InitArgs = record {
nodesInSubnet : nat32;
demo : opt bool;
manageApiKeys : opt vec principal;
};
type JsonRpcError = record { code : int64; message : text };
type LogEntry = record {
Expand All @@ -93,12 +94,6 @@ type LogEntry = record {
logIndex : opt nat;
removed : bool;
};
type ManageProviderArgs = record {
providerId : nat64;
chainId: opt nat64;
"service" : opt RpcService;
primary : opt bool;
};
type Metrics = record {
requests : vec record { record { text; text }; nat64 };
responses : vec record { record { text; text; text }; nat64 };
Expand Down Expand Up @@ -140,22 +135,25 @@ type ProviderError = variant {
NoPermission;
};
type ProviderId = nat64;
type ProviderView = record {
cyclesPerCall : nat64;
owner : principal;
hostname : text;
primary : bool;
chainId : nat64;
cyclesPerMessageByte : nat64;
providerId : nat64;
};
type RegisterProviderArgs = record {
cyclesPerCall : nat64;
credentialPath : text;
hostname : text;
credentialHeaders : opt vec HttpHeader;
chainId : nat64;
cyclesPerMessageByte : nat64;
type ChainId = nat64;
type Provider = record {
providerId : ProviderId;
chainId : ChainId;
access : RpcAccess;
alias : opt RpcService;
};
type RpcAccess = variant {
Authenticated : record {
auth : RpcAuth;
publicUrl : opt text;
};
Unauthenticated : record {
publicUrl : text;
};
};
type RpcAuth = variant {
BearerToken : record { url : text };
UrlParameter : record { urlPattern : text };
};
type RejectionCode = variant {
NoError;
Expand Down Expand Up @@ -200,7 +198,7 @@ type RpcService = variant {
};
type RpcServices = variant {
Custom : record {
chainId : nat64;
chainId : ChainId;
services : vec RpcApi;
};
EthSepolia : opt vec EthSepoliaService;
Expand Down Expand Up @@ -233,13 +231,6 @@ type TransactionReceipt = record {
contractAddress : opt text;
gasUsed : nat;
};
type UpdateProviderArgs = record {
providerId : nat64;
cyclesPerCall : opt nat64;
credentialPath : opt text;
credentialHeaders : opt vec HttpHeader;
cyclesPerMessageByte : opt nat64;
};
type ValidationError = variant {
Custom : text;
HostNotAllowed : text;
Expand All @@ -249,29 +240,17 @@ type ValidationError = variant {
CredentialHeaderNotAllowed;
};
service : (InitArgs) -> {
authorize : (principal, Auth) -> (success : bool);
deauthorize : (principal, Auth) -> (success : bool);
eth_feeHistory : (RpcServices, opt RpcConfig, FeeHistoryArgs) -> (MultiFeeHistoryResult);
eth_getBlockByNumber : (RpcServices, opt RpcConfig, BlockTag) -> (MultiGetBlockByNumberResult);
eth_getLogs : (RpcServices, opt RpcConfig, GetLogsArgs) -> (MultiGetLogsResult);
eth_getTransactionCount : (RpcServices, opt RpcConfig, GetTransactionCountArgs) -> (
MultiGetTransactionCountResult
);
eth_getTransactionCount : (RpcServices, opt RpcConfig, GetTransactionCountArgs) -> (MultiGetTransactionCountResult);
eth_getTransactionReceipt : (RpcServices, opt RpcConfig, hash : text) -> (MultiGetTransactionReceiptResult);
eth_sendRawTransaction : (RpcServices, opt RpcConfig, rawSignedTransactionHex : text) -> (MultiSendRawTransactionResult);
getAccumulatedCycleCount : (ProviderId) -> (cycles : nat) query;
getAuthorized : (Auth) -> (vec principal) query;
getMetrics : () -> (Metrics) query;
getNodesInSubnet : () -> (numberOfNodes : nat32) query;
getOpenRpcAccess : () -> (active : bool) query;
getProviders : () -> (vec ProviderView) query;
getServiceProviderMap : () -> (vec record { RpcService; nat64 }) query;
manageProvider : (ManageProviderArgs) -> ();
registerProvider : (RegisterProviderArgs) -> (nat64);
request : (RpcService, json : text, maxResponseBytes : nat64) -> (RequestResult);
requestCost : (RpcService, json : text, maxResponseBytes : nat64) -> (RequestCostResult) query;
setOpenRpcAccess : (active : bool) -> ();
unregisterProvider : (ProviderId) -> (bool);
updateProvider : (UpdateProviderArgs) -> ();
withdrawAccumulatedCycles : (ProviderId, recipient : principal) -> ();
getMetrics : () -> (Metrics) query;
getNodesInSubnet : () -> (numberOfNodes : nat32) query;
getProviders : () -> (vec Provider) query;
getServiceProviderMap : () -> (vec record { RpcService; ProviderId }) query;
updateApiKeys : (vec record { ProviderId; opt text }) -> ();
};
5 changes: 1 addition & 4 deletions canister_ids.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@
"evm_rpc": {
"ic": "7hfb6-caaaa-aaaar-qadga-cai"
},
"evm_rpc_staging_13_node": {
"ic": "a6d44-nyaaa-aaaap-abp7q-cai"
},
"evm_rpc_staging_fiduciary": {
"evm_rpc_staging": {
"ic": "xhcuo-6yaaa-aaaar-qacqq-cai"
}
}
18 changes: 9 additions & 9 deletions dfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,36 @@
"pullable": {
"dependencies": [],
"wasm_url": "https://github.com/internet-computer-protocol/evm-rpc-canister/releases/latest/download/evm_rpc.wasm.gz",
"init_guide": "Number of nodes in the subnet, e.g. '(record {nodesInSubnet = 28})'"
"init_guide": "Documentation: https://internetcomputer.org/docs/current/developer-docs/multi-chain/ethereum/evm-rpc/evm-rpc-canister"
},
"gzip": true,
"init_arg": "(record {nodesInSubnet = 28})"
"init_arg": "(record {})"
},
"evm_rpc_staging_13_node": {
"evm_rpc_demo": {
"candid": "candid/evm_rpc.did",
"type": "rust",
"package": "evm_rpc",
"gzip": true,
"init_arg": "(record {nodesInSubnet = 13})"
"init_arg": "(record {demo = opt true})"
},
"evm_rpc_staging_fiduciary": {
"evm_rpc_staging": {
"candid": "candid/evm_rpc.did",
"type": "rust",
"package": "evm_rpc",
"gzip": true,
"init_arg": "(record {nodesInSubnet = 28})"
"init_arg": "(record {})"
},
"e2e_rust": {
"dependencies": ["evm_rpc_staging_fiduciary"],
"dependencies": ["evm_rpc_staging"],
"candid": "e2e/rust/e2e_rust.did",
"type": "rust",
"package": "e2e"
},
"e2e_motoko": {
"dependencies": [
"evm_rpc",
"evm_rpc_staging_13_node",
"evm_rpc_staging_fiduciary"
"evm_rpc_demo",
"evm_rpc_staging"
],
"type": "motoko",
"main": "e2e/motoko/main.mo"
Expand Down
9 changes: 3 additions & 6 deletions e2e/motoko/main.mo
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import EvmRpc "canister:evm_rpc";
import EvmRpcStaging13Node "canister:evm_rpc_staging_13_node";
import EvmRpcStagingFidicuary "canister:evm_rpc_staging_fiduciary";
import EvmRpcStaging "canister:evm_rpc_staging";

import Buffer "mo:base/Buffer";
import Cycles "mo:base/ExperimentalCycles";
Expand All @@ -16,14 +15,12 @@ shared ({ caller = installer }) actor class Main() {
// (`subnet name`, `nodes in subnet`, `expected cycles for JSON-RPC call`)
type SubnetTarget = (Text, Nat32, Nat);
let collateralCycles = 10_000_000;
let defaultSubnet : SubnetTarget = ("13-node", 13, 99_330_400);
let fiduciarySubnet : SubnetTarget = ("fiduciary", 28, 239_142_400);

let testTargets = [
// (`canister module`, `canister type`, `subnet`)
(EvmRpc, #production, fiduciarySubnet),
(EvmRpcStaging13Node, #staging, defaultSubnet),
(EvmRpcStagingFidicuary, #staging, fiduciarySubnet),
(EvmRpcStaging, #staging, fiduciarySubnet),
];

// (`RPC service`, `method`)
Expand Down Expand Up @@ -155,7 +152,7 @@ shared ({ caller = installer }) actor class Main() {
};

// All RPC services suitable for E2E testing
let mainnetServices = [#Alchemy, #Ankr, #BlockPi, #PublicNode, #Llama];
let mainnetServices = [#Ankr, #BlockPi, #PublicNode, #Llama];
let l2Services = [#Ankr, #BlockPi, #PublicNode, #Llama];
let allServices : [(Text, EvmRpc.RpcServices)] = [
(
Expand Down
3 changes: 0 additions & 3 deletions e2e/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ description = "End-to-end testing with a Rust canister."
authors = ["DFINITY Foundation"]
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
candid = { workspace = true }
ic-certified-map = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion e2e/rust/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ fn main() {

builder.add({
// Uppercase canister name is a workaround for using `ic-cdk-bindgen` with `dfx` >= 0.18.0
let mut config = Config::new("EVM_RPC_STAGING_FIDUCIARY");
let mut config = Config::new("EVM_RPC_STAGING");
config.binding.type_attributes =
"#[derive(CandidType, Clone, Debug, Deserialize)]".to_string();
config
Expand Down
1 change: 0 additions & 1 deletion e2e/rust/src/lib.rs

This file was deleted.

6 changes: 4 additions & 2 deletions e2e/rust/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use candid::candid_method;
use ic_cdk_macros::update;

use e2e::declarations::EVM_RPC_STAGING_FIDUCIARY::{
mod declarations;

use declarations::EVM_RPC_STAGING::{
BlockTag, EthMainnetService, GetBlockByNumberResult, MultiGetBlockByNumberResult,
ProviderError, RpcError, RpcService, RpcServices, EVM_RPC_STAGING_FIDUCIARY as evm_rpc,
ProviderError, RpcError, RpcService, RpcServices, EVM_RPC_STAGING as evm_rpc,
};

fn main() {}
Expand Down
6 changes: 3 additions & 3 deletions scripts/e2e
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
(
dfx canister create --all &&
npm run generate &&
dfx deploy evm_rpc --argument "(record {nodesInSubnet = 28})" --mode reinstall -y &&
dfx deploy evm_rpc_staging_13_node --argument "(record {nodesInSubnet = 13})" --mode reinstall -y &&
dfx deploy evm_rpc_staging_fiduciary --argument "(record {nodesInSubnet = 28})" --mode reinstall -y &&
dfx deploy evm_rpc --mode reinstall -y &&
dfx deploy evm_rpc_demo --mode reinstall -y &&
dfx deploy evm_rpc_staging --mode reinstall -y &&
dfx deploy e2e_rust &&
dfx canister call e2e_rust test &&
dfx deploy e2e_motoko &&
Expand Down
Loading

0 comments on commit 1e5ba9a

Please sign in to comment.