From d783ca9d9bfb42ae938f8d4ce9899b6aa3cc00c6 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Fri, 7 Jun 2024 19:26:52 +0800 Subject: [PATCH] New reference doc for Custom RPC V2 (#4654) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks for @xlc for the original seed info, I've just fixed it up a bit and added example links. I've moved the comparison between eth-rpc-api and frontier outside, as it is opinionation. I think the content there was good but should live in the README of the corresponding repos. No strong opinion, happy either way. --------- Co-authored-by: Bryan Chen <xlchen1291@gmail.com> Co-authored-by: Bastian Köcher <git@kchr.de> Co-authored-by: Gonçalo Pestana <g6pestana@gmail.com> Co-authored-by: command-bot <> --- Cargo.lock | 3 + .../reference_docs/custom_runtime_api_rpc.rs | 77 +++++++++++++++++++ docs/sdk/src/reference_docs/mod.rs | 3 + .../frame/system/rpc/runtime-api/Cargo.toml | 1 + .../frame/system/rpc/runtime-api/src/lib.rs | 1 + substrate/utils/frame/rpc/system/Cargo.toml | 9 ++- substrate/utils/frame/rpc/system/src/lib.rs | 1 + templates/minimal/node/Cargo.toml | 1 + templates/minimal/node/src/rpc.rs | 3 +- 9 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 docs/sdk/src/reference_docs/custom_runtime_api_rpc.rs diff --git a/Cargo.lock b/Cargo.lock index 9ef971b4be9..a01420bc3ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6055,6 +6055,7 @@ dependencies = [ name = "frame-system-rpc-runtime-api" version = "26.0.0" dependencies = [ + "docify", "parity-scale-codec", "sp-api", ] @@ -8372,6 +8373,7 @@ name = "minimal-template-node" version = "0.0.0" dependencies = [ "clap 4.5.3", + "docify", "futures", "futures-timer", "jsonrpsee", @@ -20961,6 +20963,7 @@ name = "substrate-frame-rpc-system" version = "28.0.0" dependencies = [ "assert_matches", + "docify", "frame-system-rpc-runtime-api", "futures", "jsonrpsee", diff --git a/docs/sdk/src/reference_docs/custom_runtime_api_rpc.rs b/docs/sdk/src/reference_docs/custom_runtime_api_rpc.rs new file mode 100644 index 00000000000..83a70606cb8 --- /dev/null +++ b/docs/sdk/src/reference_docs/custom_runtime_api_rpc.rs @@ -0,0 +1,77 @@ +//! # Custom RPC do's and don'ts +//! +//! **TLDR:** don't create new custom RPCs. Instead, rely on custom Runtime APIs, combined with +//! `state_call` +//! +//! ## Background +//! +//! Polkadot-SDK offers the ability to query and subscribe storages directly. However what it does +//! not have is [view functions](https://github.com/paritytech/polkadot-sdk/issues/216). This is an +//! essential feature to avoid duplicated logic between runtime and the client SDK. Custom RPC was +//! used as a solution. It allow the RPC node to expose new RPCs that clients can be used to query +//! computed properties. +//! +//! ## Problems with Custom RPC +//! +//! Unfortunately, custom RPC comes with many problems. To list a few: +//! +//! - It is offchain logic executed by the RPC node and therefore the client has to trust the RPC +//! node. +//! - To upgrade or add a new RPC logic, the RPC node has to be upgraded. This can cause significant +//! trouble when the RPC infrastructure is decentralized as we will need to coordinate multiple +//! parties to upgrade the RPC nodes. +//! - A lot of boilerplate code are required to add custom RPC. +//! - It prevents the dApp to use a light client or alternative client. +//! - It makes ecosystem tooling integration much more complicated. For example, the dApp will not +//! be able to use [Chopsticks](https://github.com/AcalaNetwork/chopsticks) for testing as +//! Chopsticks will not have the custom RPC implementation. +//! - Poorly implemented custom RPC can be a DoS vector. +//! +//! Hence, we should avoid custom RPC. +//! +//! ## Alternatives +//! +//! Generally, [`sc_rpc::state::StateBackend::call`] aka. `state_call` should be used instead of +//! custom RPC. +//! +//! Usually, each custom RPC comes with a corresponding runtime API which implements the business +//! logic. So instead of invoke the custom RPC, we can use `state_call` to invoke the runtime API +//! directly. This is a trivial change on the dApp and no change on the runtime side. We may remove +//! the custom RPC from the node side if wanted. +//! +//! There are some other cases that a simple runtime API is not enough. For example, implementation +//! of Ethereum RPC requires an additional offchain database to index transactions. In this +//! particular case, we can have the RPC implemented on another client. +//! +//! For example, the Acala EVM+ RPC are implemented by +//! [eth-rpc-adapter](https://github.com/AcalaNetwork/bodhi.js/tree/master/packages/eth-rpc-adapter). +//! Alternatively, the [Frontier](https://github.com/polkadot-evm/frontier) project also provided +//! Ethereum RPC compatibility directly in the node-side software. +//! +//! ## Create a new Runtime API +//! +//! For example, let's take a look a the process through which the account nonce can be queried +//! through an RPC. First, a new runtime-api needs to be declared: +#![doc = docify::embed!("../../substrate/frame/system/rpc/runtime-api/src/lib.rs", AccountNonceApi)] +//! +//! This API is implemented at the runtime level, always inside [`sp_api::impl_runtime_apis!`]. +//! +//! As noted, this is already enough to make this API usable via `state_call`. +//! +//! ## Create a new custom RPC (Legacy) +//! +//! Should you wish to implement the legacy approach of exposing this runtime-api as a custom +//! RPC-api, then a custom RPC server has to be defined. +#![doc = docify::embed!("../../substrate/utils/frame/rpc/system/src/lib.rs", SystemApi)] +//! +//! ## Add a new RPC to the node (Legacy) +//! +//! Finally, this custom RPC needs to be integrated into the node side. This is usually done in a +//! `rpc.rs` in a typical template, as follows: +#![doc = docify::embed!("../../templates/minimal/node/src/rpc.rs", create_full)] +//! +//! ## Future +//! +//! - [XCQ](https://forum.polkadot.network/t/cross-consensus-query-language-xcq/7583) will be a good +//! solution for most of the query needs. +//! - [New JSON-RPC Specification](https://github.com/paritytech/json-rpc-interface-spec) diff --git a/docs/sdk/src/reference_docs/mod.rs b/docs/sdk/src/reference_docs/mod.rs index e50690b5021..8e0431c48b6 100644 --- a/docs/sdk/src/reference_docs/mod.rs +++ b/docs/sdk/src/reference_docs/mod.rs @@ -108,3 +108,6 @@ pub mod frame_pallet_coupling; /// Learn about the Polkadot Umbrella crate that re-exports all other crates. pub mod umbrella_crate; + +/// Learn about how to create custom RPC endpoints and runtime APIs. +pub mod custom_runtime_api_rpc; diff --git a/substrate/frame/system/rpc/runtime-api/Cargo.toml b/substrate/frame/system/rpc/runtime-api/Cargo.toml index b134cc3b617..8b71ca2a139 100644 --- a/substrate/frame/system/rpc/runtime-api/Cargo.toml +++ b/substrate/frame/system/rpc/runtime-api/Cargo.toml @@ -18,6 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.12", default-features = false } sp-api = { path = "../../../../primitives/api", default-features = false } +docify = "0.2.0" [features] default = ["std"] diff --git a/substrate/frame/system/rpc/runtime-api/src/lib.rs b/substrate/frame/system/rpc/runtime-api/src/lib.rs index f59988d818f..67adeb5cb9d 100644 --- a/substrate/frame/system/rpc/runtime-api/src/lib.rs +++ b/substrate/frame/system/rpc/runtime-api/src/lib.rs @@ -23,6 +23,7 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[docify::export(AccountNonceApi)] sp_api::decl_runtime_apis! { /// The API to query account nonce. pub trait AccountNonceApi<AccountId, Nonce> where diff --git a/substrate/utils/frame/rpc/system/Cargo.toml b/substrate/utils/frame/rpc/system/Cargo.toml index 6829d753ed7..75d24e8e210 100644 --- a/substrate/utils/frame/rpc/system/Cargo.toml +++ b/substrate/utils/frame/rpc/system/Cargo.toml @@ -16,9 +16,14 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.6.12" } -jsonrpsee = { version = "0.22.5", features = ["client-core", "macros", "server-core"] } futures = "0.3.30" +codec = { package = "parity-scale-codec", version = "3.6.12" } +docify = "0.2.0" +jsonrpsee = { version = "0.22.5", features = [ + "client-core", + "macros", + "server-core", +] } log = { workspace = true, default-features = true } frame-system-rpc-runtime-api = { path = "../../../../frame/system/rpc/runtime-api" } sc-rpc-api = { path = "../../../../client/rpc-api" } diff --git a/substrate/utils/frame/rpc/system/src/lib.rs b/substrate/utils/frame/rpc/system/src/lib.rs index bb0592599b2..8cb7b785bc7 100644 --- a/substrate/utils/frame/rpc/system/src/lib.rs +++ b/substrate/utils/frame/rpc/system/src/lib.rs @@ -37,6 +37,7 @@ use sp_runtime::{legacy, traits}; pub use frame_system_rpc_runtime_api::AccountNonceApi; /// System RPC methods. +#[docify::export] #[rpc(client, server)] pub trait SystemApi<BlockHash, AccountId, Nonce> { /// Returns the next valid index (aka nonce) for given account. diff --git a/templates/minimal/node/Cargo.toml b/templates/minimal/node/Cargo.toml index d07c7b6dd9b..a10364a2854 100644 --- a/templates/minimal/node/Cargo.toml +++ b/templates/minimal/node/Cargo.toml @@ -14,6 +14,7 @@ build = "build.rs" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +docify = "0.2.0" clap = { version = "4.5.3", features = ["derive"] } futures = { version = "0.3.30", features = ["thread-pool"] } futures-timer = "3.0.1" diff --git a/templates/minimal/node/src/rpc.rs b/templates/minimal/node/src/rpc.rs index d0c417a93d7..4b283bb2a66 100644 --- a/templates/minimal/node/src/rpc.rs +++ b/templates/minimal/node/src/rpc.rs @@ -27,7 +27,6 @@ use runtime::interface::{AccountId, Nonce, OpaqueBlock}; use sc_transaction_pool_api::TransactionPool; use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; use std::sync::Arc; -use substrate_frame_rpc_system::{System, SystemApiServer}; pub use sc_rpc_api::DenyUnsafe; @@ -41,6 +40,7 @@ pub struct FullDeps<C, P> { pub deny_unsafe: DenyUnsafe, } +#[docify::export] /// Instantiate all full RPC extensions. pub fn create_full<C, P>( deps: FullDeps<C, P>, @@ -57,6 +57,7 @@ where C::Api: substrate_frame_rpc_system::AccountNonceApi<OpaqueBlock, AccountId, Nonce>, P: TransactionPool + 'static, { + use substrate_frame_rpc_system::{System, SystemApiServer}; let mut module = RpcModule::new(()); let FullDeps { client, pool, deny_unsafe } = deps; -- GitLab