风险提示:请理性看待区块链,树立正确的货币观念和投资理念,不要盲目跟风投资,本站内容不构成投资建议,请谨慎对待。 免责声明:本站所发布文章仅代表个人观点,与CoinVoice官方立场无关

Substrate 的 RPC 客户端之 Subxt 进阶篇​

PolkaWorld
2020年04月20日

本文为 PolkaWorld 特约技术专栏「Substrate 的 RPC 客户端」,作者为波卡技术大使 John。

距离 上一篇入门篇 已经过去一段时间了,Substrate 和 Subxt 也有了很多的进展。上一次,我们用 Subxt 成功给 Substrate 提交了一个交易。但是大家知道,基于 Substrate 开发的项目很多情况下是从 node-template 中创建的,而这次我们就给未经修改的 node-template 节点尝试提交一个交易。我们使用的所有代码都基于 alpha3 版本(substrate,node-template 和 subxt)

复习

首先,结合目前 Subxt 给出的例子,我们来复习一下最新的能给 Substrate 提交交易的代码,注意 tokio 已经被 async_std 取代了。

    use sp_keyring::AccountKeyring;  
    use substrate_subxt::{  
        balances,  
        system::System,  
        DefaultNodeRuntime as Runtime,  
        ExtrinsicSuccess,  
    };  

    type AccountId = as System>::AccountId;  
    type Balance = as balances::Balances>::Balance;  

    fn main() {  
        let result: Result, Box<dyn std::error::Error + 'static>> =  
            async_std::task::block_on(async move {  
                env_logger::init();  

                let signer = AccountKeyring::Alice.pair();  

                let dest = AccountKeyring::Bob.to_account_id();  

                let cli = substrate_subxt::ClientBuilder::::new()  
                    .build()  
                    .await?;  
                let xt = cli.xt(signer, None).await?;  
                let xt_result = xt  
                    .watch()  
                    .submit(balances::transfer::(dest.clone().into(), 10_000))  
                    .await?;  
                Ok(xt_result)  
            });  
        match result {  
            Ok(extrinsic_success) => {  
                match extrinsic_success  
                    .find_event::<(AccountId, AccountId, Balance)>(  
                        "Balances", "Transfer",  
                    ) {  
                    Some(Ok((_from,_to, value))) => {  
                        println!("Balance transfer success: value: {:?}", value)  
                    }  
                    Some(Err(err)) => println!("Failed to decode code hash: {}", err),  
                    None => println!("Failed to find Balances::Transfer Event"),  
                }  
            }  
            Err(err) => println!("Error: {:?}", err),  
        }  
    }  

前半部分我们看到新出现了一个 watch 函数,它可以监视并返回提交交易的上链后的结果 ExtrinsicSuccess。match result 之后的后半部分则是在 ExtrinsicSuccess 中寻找 Transfer 这一 Event,并将转账金额打出。

注意,使用的 Subxt (https://github.com/paritytech/substrate-subxt/blob/bee03e25df78bdb6d4a572e33846c4458bc529e4) 支持的是 alpha3 版的 Substrate

转向 node-template

那么用这段程序向 node-template 节点发送转账会发生什么呢?我们会得到这样一个错误:

    Err(Rpc(Request(Error { code: MethodError(1002), message: "Verification Error: Execution(ApiError(\"Could not convert parameter `tx` between node and runtime: No such variant in enum MultiSignature\"))", data: Some(String("RuntimeApi(\"Execution(ApiError(\\\"Could not convert parameter `tx` between node and runtime: No such variant in enum MultiSignature\\\"))\")")) })))  

实际上,由于 Substrate 的可配置性,我们也需要在 Subxt 中配置正确的 Runtime。比如说这里我们只需要转账的情况,那么需要为这个 Runtime 实现 system::System 和 balances::Balances。对于未经修改的 node-template,我们可以观察它的 lib.rs,然后如下实现一个定制的 Runtime: MyNodeRuntime

    use substrate_subxt::{  
        balances::{  
            AccountData,  
            Balances,  
        },  
        system::System,  
    };  

    use sp_runtime::{  
        generic::Header,  
        traits::{  
            BlakeTwo256,  
            IdentifyAccount,  
            Verify,  
        },  
        MultiSignature,  
        OpaqueExtrinsic,  
    };  

    #[derive(Debug, Clone, Eq, PartialEq)]  
    pub struct MyNodeRuntime;  

    impl System for MyNodeRuntime {  
        type Index = u32;  
        type BlockNumber = u32;  
        type Hash = sp_core::H256;  
        type Hashing = BlakeTwo256;  
        type AccountId = <as Verify>::Signer as IdentifyAccount>::AccountId;  
        type Address = Self::AccountId;  
        type Header = Header;  
        type Extrinsic = OpaqueExtrinsic;  
        type AccountData = AccountData<<Self as Balances>::Balance>;  
    }  

    impl Balances for MyNodeRuntime {  
        type Balance = u128;  
    }  

通过比较可以得知,原来 Substrate 和 node-template 的默认 Address 类型并不相同。这样只要把之前代码中的 DefaultNodeRuntime 换成 MyNodeRuntime,程序就可以成功给 node-template 使用了。

偷懒指引我们前进

但是这样做有一个很大的劣势,就是每次 node-template 做了修改,这里也需要做相应的修改,非常麻烦。从工程开发的角度来说,如果依赖能够自动帮我们搞定这些类型,那无论对于开发还是维护都是一个好消息。一个直观的想法就是直接把 node_template_runtime::Runtime 中的类型照搬过来。

    use subxt::{  
        balances::Balances,  
        system::System,  
        ExtrinsicSuccess,  
    };  
    use node_template_runtime::Runtime;  
    use sp_runtime::OpaqueExtrinsic;  

    #[derive(Debug, Clone, Eq, PartialEq)]  
    pub struct MyNodeRuntime;  

    impl System for MyNodeRuntime {  
        type Index = as frame_system::Trait>::Index;  
        type BlockNumber = as frame_system::Trait>::BlockNumber;  
        type Hash = as frame_system::Trait>::Hash;  
        type Hashing = as frame_system::Trait>::Hashing;  
        type AccountId = as frame_system::Trait>::AccountId;  
        type Address = Self::AccountId;  
        type Header = as frame_system::Trait>::Header;  
        type Extrinsic = OpaqueExtrinsic;  
        type AccountData = as frame_system::Trait>::AccountData;  
    }  

    impl Balances for MyNodeRuntime {  
        type Balance = as pallet_balances::Trait>::Balance;  
    }  

这样一来,我们就可以再也不用每次 node-template 升级的时候都要一堆复制粘贴啦。

小结

这一讲带大家使用 Subxt 给 node-template 节点实现了一个可以发起转账的 RPC 客户端,也针对开发和运维的问题提出了一个可能的优化方案,希望大家自己试试。再次提醒大家要注意版本问题,关于依赖,这里面还是有些坑的,祝大家蹚坑愉快~

Substrate 的 RPC 客户端之 Subxt 进阶篇​

  • 欢迎学习 Substrate:

https://substrate.dev/

  • 关注 Substrate 进展 :

https://github.com/paritytech/substrate

  • 关注 Polkadot 进展 :

https://github.com/paritytech/polkadot

  • 申请 Bootcamp:

https://bootcamp.web3.foundation/

相关内容:

Substrate 的 RPC 客户端之 Subxt 入门篇

开发了两年 Dapp 二层网络后,我转投了 Substrate 阵营

话题|开发者们最早都是怎么注意到 Substrate 的?

扫码关注公众号,回复 “1” 加入波卡群

Substrate 的 RPC 客户端之 Subxt 进阶篇​

关注 PolkaWorld

发现 Web 3.0 时代新机遇

点个 “在看” 再走吧!


声明:本内容为作者独立观点,不代表 CoinVoice 立场,且不构成投资建议,请谨慎对待,如需报道或加入交流群,请联系微信:VOICE-V。

评论0条