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);
}

关键点说明

  1. API定义: 使用decl_runtime_apis宏定义你的运行时API trait
  2. 实现API: 为你的Runtime结构体实现这些trait
  3. 暴露API: 使用impl_runtime_apis宏将API暴露给外部
  4. 链下调用: 通过client的runtime_api()方法调用这些API

注意事项

  • 确保runtime和链下代码使用相同的API版本
  • 所有API方法必须是可序列化的
  • 考虑API调用的性能和gas成本
  • 错误处理要清晰明确

这个示例展示了如何定义、实现和使用frame-system-rpc-runtime-api来创建自定义运行时API并与链下代码交互。你可以根据需要扩展这个基础示例,添加更多自定义API方法。


1 回复

Substrate核心组件frame-system-rpc-runtime-api使用指南

概述

frame-system-rpc-runtime-api是Substrate区块链开发框架中的一个关键组件,它允许运行时(Runtime)与链下(off-chain)环境通过RPC进行交互。这个组件为开发者提供了在运行时实现自定义API的能力,使得链下应用能够查询运行时状态和执行特定操作。

核心功能

  1. 定义运行时API接口
  2. 实现运行时与链下的通信桥梁
  3. 提供类型安全的API调用方式
  4. 支持版本控制和兼容性管理

使用方法

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);
}

最佳实践

  1. 保持API简洁:运行时API调用有性能开销,应设计简洁的接口
  2. 合理使用版本控制:当需要修改API时,使用版本控制保持向后兼容
  3. 错误处理:在API实现中提供清晰的错误信息
  4. 文档注释:为所有API方法添加详细文档注释
  5. 测试:为运行时API编写全面的测试用例

注意事项

  1. 运行时API调用会消耗区块链资源,应避免设计过于复杂的API
  2. API方法应该是只读的或具有明确且有限的副作用
  3. 考虑API调用的gas成本
  4. 注意数据序列化/反序列化的开销

通过合理使用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。

回到顶部