Substrate 官方教程增强版

这篇文章主要介绍了Substrate 官方教程增强版 ,文中通过代码以及文档配合进行讲解,很详细,它对在座的每个人的研究和工作具有很经典的参考价值。 如果需要,让我们与区块链资料网一起学习。

https://www.interchains.cc/16839.html

Substrate 官方教程增强版是很好的区块链资料,他说明了区块链当中的经典原理,可以给我们提供资料,Substrate 官方教程增强版学习起来其实是很简单的,

不多的几个较为抽象的概念也很容易理解,之所以很多人感觉Substrate 官方教程增强版比较复杂,一方面是因为大多数的文档没有做到由浅入深地讲解,概念上没有注意先后顺序,给读者的理解带来困难

Substrate 官方教程增强版

  • Substrate

Substrate 开发没那么神秘。

经过前两篇(第一篇、第二篇)漫长的铺垫,按照剧情发展,作为第三篇怎么滴也得开始写写代码了。这也确实是本篇的目的。

按照原定计划,你将在文中看到一个端到端的示例。但查过官方教程之后,我打算略微调整一下写作计划:在官方的教程上进行增强,不再凭空写一个。

官方教程的第一个编程示例:Build a PoE Decentralized Application 提供了一个非常好的示范,一个完整的端到端例子。透过这篇教程,你应该很快能够了解 Substrate 的开发,以及如何开发一个前端应用。不过它依旧还有改进的空间,而本文则针对这些地方给出补充:

  1. 缺少单元测试的示例。
  2. 虽然有前端示例代码,但跟 UI 混杂在一起反而没有办法突出重点。

现在,请先去查看并练习官方教程,之后再来阅读本文。

单元测试

首先,让我们先看看单元测试。这里假设你已经了解 rust 的测试编写过程,若不清楚,请先去查阅相关资料。或者,先跳过此节,回头再看不迟。

Substrate 的模板工程已经为测试提供了一个很好的基础:有 mock 也有 test。就官方教程而言,写测试基本上就是把我们的方法调用一下,然后检验结果即可,与平时的测试开发没有什么不同。而且,很大程度上还省掉了 mock 的时间。

那么,让我们先完成一个测试,它用来测试官方教程的完整业务逻辑:既可以创建 claim ,也能移除 claim 。在这一步,只需要更改 tests.rs 文件:

#[test] fn should_work() {     new_test_ext().execute_with(|| {         assert_ok!(TemplateModule::create_claim(             Origin::signed(1),             vec![1, 2, 3, 4]         ));         assert_ok!(TemplateModule::revoke_claim(             Origin::signed(1),             vec![1, 2, 3, 4]         ));     }); }

就这么简单,传入合适的参数,验证是否调用成功即可,这里用到了 assert_ok 这个宏。其中的 Origin 等都是在 mock 中定义的。同时,也注意这些 assert 方法其实运行在一个闭包之中。不妨简单理解成,这个闭包其实提供了一个净室环境,准备了这些方法运行所需要的上下文。

程序显然不是只有理想情况,还有异常,比如典型的:移除一个不存在的 claim 。此时,当然要报错啦。验证这种情况很简单:

#[test] fn should_not_revoke_calim_with_non_existing_proof() {     new_test_ext().execute_with(|| {         assert_noop!(             TemplateModule::revoke_claim(Origin::signed(1), vec![1, 2, 3, 5]),             Error::<Test>::NoSuchProof         );     }); }

请注意这里用了另一个宏:assert_noop,验证方法失败同时验证跑出的错误符合我们的预期。

看到以上两个示例,相信聪明的你应该已经知道其他测试该如何书写了。这里就将此作为练习,供大家自行解决。

但是,在看下一节之前,我们还需要解决一件事情,这也是 mock 中并没有完成,需要我们花点时间去准备的:关于事件的测试。

细心的同学应该会发现第一个示例是不完整的:它虽然测试了完整的流程,但却没有验证正确的事件被触发。这个安排是有意的,因为 mock 中并没有为 event 测试做好模拟,如果一上来就摆出来,可能会显得过程太复杂。

现在,到了讲解验证事件的时候了。让我们先看看要测试事件,mock.rs 需要进行哪些修改:

  • 引入:impl_outer_event
  • 为测试运行时添加事件支持。
mod template {     pub use crate::Event; }  impl_outer_event! {     pub enum TestEvent for Test {         system<T>,         template<T>,     } }
  • 将原来代码中的:type Event = () 改为 type Event = TestEvent
  • 同时添加:pub type System = system::Module<Test>;
    • 其中的 system 为:use frame_system as system;

对于 tests.rs,需要引入:use super::RawEvent;

这样,你的工程就为事件的测试做好了准备。让我们将上面第一个测试完善一下,增加对于事件的测试:

#[test] fn should_work() {     new_test_ext().execute_with(|| {         System::set_block_number(1);          let sender = ensure_signed(Origin::signed(1)).unwrap();          assert_ok!(TemplateModule::create_claim(             Origin::signed(1),             vec![1, 2, 3, 4]         ));          assert!(System::events().iter().any(|a| {             a.event == TestEvent::template(RawEvent::ClaimCreated(sender, vec![1, 2, 3, 4]))         }));          assert_ok!(TemplateModule::revoke_claim(             Origin::signed(1),             vec![1, 2, 3, 4]         ));          assert!(System::events().iter().any(|a| {             a.event == TestEvent::template(RawEvent::ClaimRevoked(sender, vec![1, 2, 3, 4]))         }));     }); }

请注意:这里有一个小 trick 。注意上面的第一行,它显式的设定了区块号。这一点对于事件测试很关键,缺少这一行,整个测试会失败。检查之后,你会发现: System::events() 的长度为 0,即没有任何事件被激发。这是因为,对于区块 0,不会发出事件!

运行测试很简单:在 pallet/template 运行 cargo test

最后,再补充一个技巧:适当的使用 assert_eq 宏,因为我发现单单用 assert 宏并不利于调试:它在失败时不会给出类似:expect xxx but got yyy 的信息,只会给出一个单调的失败报错,让你郁闷无比。

Polkadot API

本来,我打算给出 js 和 java 两种示例,但在检查 java git 仓库时发现太久没有更新,且其 README 中有以下这句话:

The working substrate version is 1.0.0-41ccb19c-x86_64-macos. Newer substrate may be not supported.

再加上本质上,作为 client 调用机制和套路应该都差不多,因此也就打消了这个念头,只给出 js 的示例。

可能有同学会疑惑:官方教程上已经有前端示例了,这里再给出一个有何意义?这里我来解释一下:

  1. 官方教程的例子是基于 react ui 的范例,很多细节都隐藏了(不信就去对比一下 api 文档里的代码和官方教程中的前端代码),并不利于理解 API。
  2. 对于非 react 团队(比如我们团队一直用 angular),官方教程的代码不具备参考价值,还得直接去使用 api。

关于 API,官方文档非常详细且具体,非常值得一读。这里只给出值得注意之处:

  • 整个调用采用的是 promise 风格,熟悉前端开发的同学应该不陌生。
  • api 基于元数据自动生成,整个模式:api.<type>.<module>.<section>
  • 有过以太坊eth开发经验的同学会知道,任何发往后端的交易基本上都需要经过签名,这里也不例外。

那么,我们看一下完整的调用官方教程的前端 api 例子:

import program from "commander";  import * as fs from "fs"; import { ApiPromise, WsProvider, Keyring } from "@polkadot/api"; import { blake2AsHex } from "@polkadot/util-crypto";  const wsProvider = new WsProvider("ws://127.0.0.1:9944");  module.exports = async (argv: string[]) => {   program.version("1.0.0").usage("<command> [options]");   const api = await ApiPromise.create({     provider: wsProvider,     types: {       Address: "AccountId",       LookupSource: "AccountId",     },   });    api.isReady.then((api) => {     program       .command("server-info")       .description("Show the information about a local chain.")       .action(async () => {         const [chain, nodeName, nodeVersion] = await Promise.all([           api.rpc.system.chain(),           api.rpc.system.name(),           api.rpc.system.version(),         ]);          console.log(`You are connected to chain ${chain} using ${nodeName} v${nodeVersion}`);         api.disconnect();       });      program       .command("create-claim [name]")       .description("Create a claim from a file.")       .action(async (name) => {         const content = Array.from(new Uint8Array(fs.readFileSync(name)))           .map((b) => b.toString(16).padStart(2, "0"))           .join("");          const hash = blake2AsHex(content, 256);         console.log(hash);          const keyring = new Keyring({ type: "sr25519" });         const alice = keyring.addFromUri("//Alice", { name: "Alice default" });          await api.tx["templateModule"]           ["createClaim"](hash)           .signAndSend(alice)           .catch((e) => {             console.log(e.toString());             api.disconnect();           });          api.disconnect();       });      program       .command("revoke-claim [hash]")       .description("Revoke a claim by a hash code.")       .action(async (hash) => {         const keyring = new Keyring({ type: "sr25519" });         const alice = keyring.addFromUri("//Alice", { name: "Alice default" });          await api.tx["templateModule"]           ["revokeClaim"](hash)           .signAndSend(alice)           .catch((e) => {             console.log(e.toString());             api.disconnect();           });          api.disconnect();       });      program.parse(argv);   }); };

其中:

  • 这里是一个 cli 示例,用的是 commander。
  • 创建 api 实例时请注意里面给出了类型映射(在官方教程的前端示例工程中的 development.json 文件中可看到类似的内容),这一点很关键,因为官方文档的 getting started 中没有明确给出(后面有提到,但不明显)。缺少这步,你很可能在发起请求时得到下面的错误:

    Verification Error: Execution(ApiError("Could not convert parameter ‘tx’ between node and runtime

  • 之所以放在 isReady 中,这是为了保证 ws 链接已经建立成功。
  • keyring 可简单理解为账户,用它来完成交易的签名。

除此之外,没有什么特别的了。

关于 Substrate 应用的设计

最后,简单聊一下 Substrate 应用的设计:

  • 采用 Substrate 并不意味着你就要舍弃其他后端存储,如数据库等。它们之间的关系应该是互补而非排它关系。
  • 不要把 Substrate 当垃圾场,它保存的内容应该尽可能的少。这或许有点反直觉,但细细品味一下确实是这样。这里有几个原因:
    • Substrate 的存储不是免费的,存储越多意味着成本越高。
    • 区块链blockchain上保存的内容应该是大家达成共识的内容,这种形式有很多,最典型的就是教程中的 claim,它也没有保存实际的源文件。这样可以鱼和熊掌兼得:
    • claim 是哈希值,本身就是抗修改;
    • 保存于区块链blockchain上可利用区块链blockchain存储本身的特质,不允许修改;
    • 不保存源文件,存储成本低。
  • 关于链上存储结构的设计,本质上跟一般 nosql 数据库设计没有差别。由于不像 sql 数据库天然提供了类似外键和 count 等聚合函数的支持,如果你有类似查询需求,你就得自行去用相应的辅助结构去完成。

写在最后

总的来讲,只要习惯了 rust 语法,熟悉了 Substrate 的概念和 API,它的开发其实并没有什么难度。

至于其他,没什么秘诀,一个字:练。再就是,熟读文档,善用搜索,尤其是英文原文搜索。

原文链接

部分转自网络,侵权联系删除www.interchains.cchttps://www.interchains.cc/16839.html

区块链毕设网(www.interchains.cc)全网最靠谱的原创区块链毕设代做网站 部分资料来自网络,侵权联系删除! 最全最大的区块链源码站 !
区块链知识分享网, 以太坊dapp资源网, 区块链教程, fabric教程下载, 区块链书籍下载, 区块链资料下载, 区块链视频教程下载, 区块链基础教程, 区块链入门教程, 区块链资源 » Substrate 官方教程增强版

提供最优质的资源集合

立即查看 了解详情