Rust区块链开发框架Substrate核心组件frame-system-rpc-runtime-api的使用,实现运行时API与链下交互
Rust区块链开发框架Substrate核心组件frame-system-rpc-runtime-api的使用,实现运行时API与链下交互
概述
frame-system-rpc-runtime-api
是System RPC扩展所需的运行时API定义。这个API应该被想要使用自定义RPC扩展添加系统访问方法的节点运行时导入和实现。
许可证: Apache-2.0
安装
在项目目录中运行以下Cargo命令:
cargo add frame-system-rpc-runtime-api
或者在Cargo.toml中添加以下行:
frame-system-rpc-runtime-api = "38.0.0"
完整示例
以下是一个完整的示例,展示如何使用frame-system-rpc-runtime-api
实现运行时API与链下交互:
// 1. 在runtime中引入必要的trait和类型
use frame_support::{decl_module, decl_runtime_apis};
use frame_system_rpc_runtime_api::AccountNonceApi;
use sp_api::impl_runtime_apis;
use sp_runtime::traits::Block as BlockT;
// 2. 定义你的runtime API trait
#[sp_api::decl_runtime_apis]
pub trait MyRuntimeApi {
/// 获取账户nonce
fn get_account_nonce(account: sp_core::crypto::AccountId32) -> u32;
}
// 3. 在你的runtime模块中实现API
pub struct Runtime;
impl AccountNonceApi<Block> for Runtime {
fn account_nonce(account: sp_core::crypto::AccountId32) -> u32 {
// 实现获取账户nonce的逻辑
frame_system::Pallet::<Runtime>::account_nonce(account)
}
}
impl MyRuntimeApi<Block> for Runtime {
fn get_account_nonce(account: sp_core::crypto::AccountId32) -> u32 {
// 可以复用AccountNonceApi的实现
<Runtime as AccountNonceApi<Block>>::account_nonce(account)
}
}
// 4. 在runtime的lib.rs中实现runtime API
impl_runtime_apis! {
impl sp_api::Core<Block> for Runtime {
fn version() -> sp_version::RuntimeVersion {
// 返回runtime版本信息
unimplemented!()
}
fn execute_block(block: Block) {
// 执行区块逻辑
unimplemented!()
}
}
impl AccountNonceApi<Block> for Runtime {
fn account_nonce(account: sp_core::crypto::AccountId32) -> u32 {
// 实现获取账户nonce的逻辑
frame_system::Pallet::<Runtime>::account_nonce(account)
}
}
impl MyRuntimeApi<Block> for Runtime {
fn get_account_nonce(account: sp_core::crypto::AccountId32) -> u32 {
// 可以复用AccountNonceApi的实现
<Runtime as AccountNonceApi<Block>>::account_nonce(account)
}
}
}
// 5. 在链下代码中调用API
async fn call_runtime_api() {
use sp_core::crypto::AccountId32;
use sp_api::RuntimeApiInfo;
// 假设你已经创建了client和block_hash
let client: Arc<FullClient> = unimplemented!();
let block_hash = unimplemented!();
// 调用AccountNonceApi
let nonce = client
.runtime_api()
.account_nonce(&block_hash, AccountId32::new([0; 32]))
.unwrap();
println!("Account nonce: {}", nonce);
// 调用自定义的MyRuntimeApi
let custom_nonce = client
.runtime_api()
.get_account_nonce(&block_hash, AccountId32::new([0; 32]))
.unwrap();
println!("Custom API nonce: {}", custom_nonce);
}
关键点说明
- API定义: 使用
decl_runtime_apis
宏定义你的运行时API trait - 实现API: 为你的Runtime结构体实现这些trait
- 暴露API: 使用
impl_runtime_apis
宏将API暴露给外部 - 链下调用: 通过client的runtime_api()方法调用这些API
注意事项
- 确保runtime和链下代码使用相同的API版本
- 所有API方法必须是可序列化的
- 考虑API调用的性能和gas成本
- 错误处理要清晰明确
这个示例展示了如何定义、实现和使用frame-system-rpc-runtime-api
来创建自定义运行时API并与链下代码交互。你可以根据需要扩展这个基础示例,添加更多自定义API方法。
Substrate核心组件frame-system-rpc-runtime-api使用指南
概述
frame-system-rpc-runtime-api
是Substrate区块链开发框架中的一个关键组件,它允许运行时(Runtime)与链下(off-chain)环境通过RPC进行交互。这个组件为开发者提供了在运行时实现自定义API的能力,使得链下应用能够查询运行时状态和执行特定操作。
核心功能
- 定义运行时API接口
- 实现运行时与链下的通信桥梁
- 提供类型安全的API调用方式
- 支持版本控制和兼容性管理
使用方法
1. 定义运行时API trait
首先需要在runtime/src/lib.rs
中定义你的API trait:
// runtime/src/lib.rs
sp_api::decl_runtime_apis! {
/// 自定义运行时API
pub trait MyRuntimeApi {
/// 获取特定账户的定制信息
fn get_custom_account_info(account_id: u64) -> CustomAccountInfo;
/// 执行某些计算并返回结果
fn perform_computation(input: Vec<u8>) -> Result<Vec<u8>, sp_runtime::DispatchError>;
}
}
2. 实现运行时API
在runtime/src/lib.rs
中实现你定义的trait:
// runtime/src/lib.rs
sp_api::impl_runtime_apis! {
impl my_runtime_api::MyRuntimeApi for Runtime {
fn get_custom_account_info(account_id: u64) -> CustomAccountInfo {
// 实现逻辑
CustomAccountInfo::default()
}
fn perform_computation(input: Vec<u8>) -> Result<Vec<u8>, sp_runtime::DispatchError> {
// 实现计算逻辑
Ok(input)
}
}
}
3. 在链下代码中调用API
在客户端代码中调用运行时API:
// 客户端代码
use jsonrpsee::ws_client::WsClientBuilder;
use my_runtime_api::MyRuntimeApi;
async fn call_runtime_api() {
let client = WsClientBuilder::default()
.build("ws://localhost:9944")
.await
.unwrap();
// 调用get_custom_account_info
let account_info: CustomAccountInfo = client
.request("myruntimeapi_getCustomAccountInfo", (123u64,))
.await
.unwrap();
println!("Account info: {:?}", account_info);
// 调用perform_computation
let input = vec![1, 2, 3, 4];
let result: Vec<u8> = client
.request("myruntimeapi_performComputation", (input,))
.await
.unwrap();
println!("Computation result: {:?}", result);
}
4. 版本控制
运行时API支持版本控制,确保兼容性:
sp_api::decl_runtime_apis! {
#[api_version(2)]
pub trait MyRuntimeApi {
// v1方法
#[changed_in(2)]
fn old_method() -> u32;
// v2方法
fn new_method() -> u64;
}
}
实际应用示例
实现链下数据验证
// runtime/src/lib.rs
sp_api::decl_runtime_apis! {
pub trait DataVerificationApi {
/// 验证链下数据的有效性
fn verify_offchain_data(data: Vec<u8>, signature: Vec<u8>) -> bool;
}
}
sp_api::impl_runtime_apis! {
impl data_verification_api::DataVerificationApi for Runtime {
fn verify_offchain_data(data: Vec<u8>, signature: Vec<u8>) -> bool {
// 实现验证逻辑
true
}
}
}
客户端调用验证
async fn verify_data() {
let client = /* 创建客户端 */;
let data = b"important offchain data".to_vec();
let signature = b"signature".to_vec();
let is_valid: bool = client
.request("dataverificationapi_verifyOffchainData", (data, signature))
.await
.unwrap();
println!("Data is valid: {}", is_valid);
}
最佳实践
- 保持API简洁:运行时API调用有性能开销,应设计简洁的接口
- 合理使用版本控制:当需要修改API时,使用版本控制保持向后兼容
- 错误处理:在API实现中提供清晰的错误信息
- 文档注释:为所有API方法添加详细文档注释
- 测试:为运行时API编写全面的测试用例
注意事项
- 运行时API调用会消耗区块链资源,应避免设计过于复杂的API
- API方法应该是只读的或具有明确且有限的副作用
- 考虑API调用的gas成本
- 注意数据序列化/反序列化的开销
通过合理使用frame-system-rpc-runtime-api
,开发者可以构建强大的链上链下交互功能,为Substrate区块链应用提供更丰富的功能扩展。
完整示例Demo
下面是一个完整的示例,展示如何定义、实现和调用一个简单的运行时API:
1. 定义运行时API
// runtime/src/lib.rs
// 定义CustomAccountInfo结构体
#[derive(Clone, Debug, Default, Encode, Decode)]
pub struct CustomAccountInfo {
pub balance: u128,
pub nonce: u64,
pub is_active: bool,
}
sp_api::decl_runtime_apis! {
/// 账户信息服务API
pub trait AccountServiceApi {
/// 获取账户信息
fn get_account_info(account_id: u64) -> CustomAccountInfo;
/// 检查账户是否活跃
fn is_account_active(account_id: u64) -> bool;
}
}
2. 实现运行时API
// runtime/src/lib.rs
sp_api::impl_runtime_apis! {
impl account_service_api::AccountServiceApi for Runtime {
fn get_account_info(account_id: u64) -> CustomAccountInfo {
// 模拟从存储中获取账户信息
CustomAccountInfo {
balance: 1000u128,
nonce: account_id,
is_active: account_id % 2 == 0,
}
}
fn is_account_active(account_id: u64) -> bool {
// 简单的活跃检查逻辑
account_id % 2 == 0
}
}
}
3. 客户端调用代码
// client/src/main.rs
use jsonrpsee::ws_client::WsClientBuilder;
use runtime_api::account_service_api::AccountServiceApi;
#[tokio::main]
async fn main() {
// 创建RPC客户端
let client = WsClientBuilder::default()
.build("ws://localhost:9944")
.await
.expect("Failed to create client");
// 调用get_account_info
let account_id = 123u64;
let account_info: runtime_api::CustomAccountInfo = client
.request("accountserviceapi_getAccountInfo", (account_id,))
.await
.expect("Failed to get account info");
println!("Account {} info: {:?}", account_id, account_info);
// 调用is_account_active
let is_active: bool = client
.request("accountserviceapi_isAccountActive", (account_id,))
.await
.expect("Failed to check account status");
println!("Account {} is active: {}", account_id, is_active);
}
4. 版本控制示例
// runtime/src/lib.rs
sp_api::decl_runtime_apis! {
#[api_version(2)]
pub trait VersionedApi {
// v1方法 - 已弃用
#[changed_in(2)]
fn get_old_data() -> Vec<u8>;
// v2方法
fn get_new_data() -> String;
}
}
sp_api::impl_runtime_apis! {
impl versioned_api::VersionedApi for Runtime {
fn get_old_data() -> Vec<u8> {
b"old data".to_vec()
}
fn get_new_data() -> String {
"new data".to_string()
}
}
}
这个完整示例展示了从定义API到客户端调用的完整流程,包括数据结构定义、API实现、版本控制和实际调用。开发者可以根据这个模板构建自己的运行时API。