Substrate中的RPC或远程程序性调用是一种与Substrate节点互动的方式。它们可用于检查存储值、提交交易或查询当前的共识机构。Substrate是下一代的区块链创新框架。它配备了你构建区块链所需的一切。Substrate是一个完全免费和开源的项目。它是使用Rust和WebAssembly构建的。Rust是为创建快速和固有的安全软件而设计的。底层就像一个模板,你可以从中制作你自己的特定应用区块链,以添加特定于一个用例或一组用例的定制功能。基板带有许多默认的RPC调用。但我们可以添加我们自己的RPC来与节点通信。在这篇博客中,我们将看到我们如何对Runtime API进行RPC调用。
Runtime API
对于那些不知道什么是Substrate中的Runtime APIs的人来说。这里有一个关于运行时API的简短介绍。
每个Substrate节点都有一个运行时。该运行时包含链的业务逻辑。它定义了哪些交易是有效的或无效的,并决定了链的状态如何响应交易而变化。运行时被编译成Wasm,以允许运行时升级。除了运行时以外的一切都被称为外部节点。外层节点负责回答来自外部世界的RPC调用。
首先,让我们看看我们的RPC将调用什么 Runtime API 。我们假设有一个名为trial-pallet
的托盘,我们在其中定义了一个名为get-something
的 Runtime API。
定义get-something RPC
我们可以在不同的文件夹中定义我们的RPC,但是由于它与我们的托盘密切相关,所以在trial_pallet
中创建一个板条箱来存储RPC是合理的**。**让我们创建一个板条箱来存储我们的RPC,名称为rpc
。现在我们将把rpc
这个板条箱添加到托盘的Cargo.toml中。为了避免任何混淆,我们将把我们的RPC存储在pallets/trial_pallet/rpc中:
现在我们在Cargo.toml中加入这条路径,加入以下一行。
getter-rpc = { path = "../../pallets/trial_pallet/rpc" }
编写RPC
如果你知道如何编写Silly RPC,就会更容易理解下面发生的事情。但如果你不知道什么是愚蠢的RPC,那么不用担心,我会解释一切。首先,实现RPC的结构Get_RPC需要一个对client
的引用作为参数。这一点很重要,这样我们才能真正调用到运行时。另一件需要注意的事情是,该结构在BlockHash
类型上是通用的。这背后的原因是,这个RPC将调用一个运行时API,而运行时API总是在一个特定的区块被调用。
以下代码将包含在rpc crate的src文件夹中的lib.rs
文件中:
///pallets/trial_pallet/rpc/src/lib.rs
#![allow(unused)]
fn main() {
#[rpc]
pub trait GetSomethingAPI<BlockHash> {
#[rpc(name = "get_something_rpc")]
fn get_something(
&self,
at: Option<BlockHash>
) -> Result<u32>;
}
/// A struct that implements the `GetterApi`.
pub struct Get_RPC<C, M> {
client: Arc<C>,
_marker: std::marker::PhantomData<M>,
}
impl<C, M> Get_RPC<C, M> {
pub fn new(client: Arc<C>) -> Self {
Self { client, _marker: Default::default() }
}
}
}
在上面的代码中,我们定义了一个名为GetSomethingAPI的特质,它看起来与我们在定义 Runtime API时创建的特质相似。它包含了 get_something_rpc
RPC,它将调用Runtime API。
Get_RPC是一个结构,它将实现GetSomethingAPI特性,以定义get_something_rpc
RPC。但是在这之前,我们将定义新的方法来初始化API的 客户端。
现在我们将插入以下代码:
impl<C, Block> GetterApi<<Block as BlockT>::Hash>
for Get_RPC<C, Block>
where
Block: BlockT
C: Send + Sync + 'static,
C: ProvideRuntimeApi,
C: HeaderBackend<Block>,
C::Api: GetSomethingRuntimeApi<Block>,
{
fn get_something(
&self,
at: Option<<Block as BlockT>::Hash>
) -> Result<u32> {
let api = self.client.runtime_api();
let at = BlockId::hash(at.unwrap_or_else(||
// If the block hash is not supplied assume the best block.
self.client.info().best_hash
));
let runtime_api_result = api.get_something(&at);
runtime_api_result.map_err(|e| RpcError {
code: ErrorCode::ServerError(9876), // No real reason for this value
message: "Something wrong".into(),
data: Some(format!("{:?}", e).into()),
})
}
}
}
上面的代码什么也没做,只是定义了我们在GetSomethingAPI trait中声明的get_something
方法,最终调用我们的Runtime API。它使用客户端来访问前面定义的Runtime API。
安装RPC
为了安装这个RPC,我们在node/rpc-node/src/rpc.rs
的create_full
函数中添加以下代码:
pub fn create_full<C, P>(
deps: FullDeps<C, P>,
) -> jsonrpc_core::IoHandler<sc_rpc::Metadata> where
// --snip--
{
let mut io = jsonrpc_core::IoHandler::default();
#![allow(unused)]
fn main() {
io.extend_with(getter_rpc::GetSomethingApi::to_delegate(
getter_rpc::Get_RPC::new(client),
));
}
// --snip--
io
}
这将安装我们的自定义RPC ,准备执行对Runtime API的调用。这就是关于创建和添加一个RPC来调用Runtime API的全部内容。