查看原文
其他

Subdev 讨论 | 同学们设计的加密猫进阶到模块V4了

一块链习 一块Plus社区 2020-11-11

Parity 科技公司研发的 Substrate 框架,让我们拥有了可以几乎不受限制的快速的开发出一个完整、高性能、安全的区块链项目。
 
今年6月份,一块链习与 Polkadot 社区大使陈锡亮老师倾力打造了《Substrate快速入门与实战开发》课程。


目前第三期课程已经进行到第五周,同学们平常会在班级群将学习中遇到的难题或不解向老师、助教提问、讨论。

另外,每周六晚 8 点,都会进行《Substrate 快速入门与开发实战》开发课的内容知识拓展——作业点评会,助教们会从解题思路、以及本组学员的作业问题分析每一课的知识点。

现在将第四周班级群日常优质的讨论内容以及张静波、黄志光助教对第 4、5、6课的作业点评分享给大家。

Q&A 具体内容如下:


 01 

Q:KuoYeh@在读博士:有没有人知道作业test的Currency,Randomness怎么写啊?我Rust OOP还没时间细看。
 
 impl Trait for Test {
  type KittyIndex = u32;
  type Event = ();
 
  type Currency = ?;
  type Randomness = ?;
 
 }
 
A:张静波@助教:
https://github.com/paritytech/substrate/blob/master/frame/utility/src/lib.rs#L714
可以参考一下这个。
 
Q:村上香菜子@电商:
              
看不太明白呢。能解释一下吗?
 
A:张静波@助教:把这三部分,加进去,然后修改一下编译错误,就差不多了。
                
跟着编译器的报错,一步一步来,慢慢理解。
               
缺 `Event`, `Currency`, `Randomness` ,就先加到 impl Trait for Test 里。
 
Q:村上香菜子@电商:但是按照刚才说的步骤,还是不过。
              
像这种怎么办,全部变成()?那就是要继续往上加?
               
把这块也加上?
 
A:晓聪@程序员:对。
 
Q:村上香菜子@电商:
              
刚才粘上去了,剩下这两个,怎么办?
 
A:Ratentlan@程序员:版本问题。
               
这版本还不叫 pallet,是重命名了。
 
Q:村上香菜子@电商:
              
所以我要去改toml文件是吗?
 
A:Ratentlan@程序员:这时候, 他们还没从重构完成,你也可以改代码。就是你代码里面跟toml保持一致就行了。toml叫balances, 你就别写pallet-balances。
 
             

 
 02 
 
Q:alan poon@社群成员:怎么用offchain worker download 一链的block header?
 
A:陈锡亮@讲师:offchain 可以读链上storage,system模块里面应该有存最近几个区块的数据。
 
Q:alan poon@社群成员:但是我是要download all the block headers, 答案还是一样吗?
 
A:陈锡亮@讲师:offchain worker是每个block都会执行的,所以每个block存当前的header就好了。

 
 03 
 
Q:建怀@社群成员:offchain worker里面保存数据会不会所有节点也会同步?
 
A:陈锡亮@讲师:不会。
 
Q:建怀@社群成员:要维护全网状态一致,还要走链上的操作才行。一个节点通过offchain worker 喂一个状态给链上,比如价格,如何达成全网共识呢?
 
A:陈锡亮@讲师:和预言机原理一样,要么POA,要么POS,甚至有POW的。
 
Q:建怀@社群成员:需要有一个机制来确认链下数据真实性。
 
A:陈锡亮@讲师:主要就是经济模型,提高作恶成本。
 
Q:建怀@社群成员:授权出块节点跑offchain worker,他们能直接操作链上状态,然后直接就是共识,这样是否可行。当然只是有限的不是核心的链上状态。
 
A:陈锡亮@讲师:可以,那就是POS的方法,而且复用了出块的POS,会简单很多。
 
Q:建怀@社群成员:offchain worker用来做其他公链交易真实性的验证,但没必要跑太多重复的验证,这个逻辑在思考。

验证一个交易,希望能直接更改一下这个交易状态,其他的worker就不要做重复的工作了。但这个更改状态,要能快速达成共识,直接POA授权出块节点干好了。
 
A:陈锡亮@讲师:嗯,可以交给验证人节点就最简单了。

 
 04 

Q:村上香菜子@电商:   
           
老师在视频里提到,这里边真实的price是balance类型。那上面框的这话,是不是把Option封装的balance类型的价格取出来了,重新赋值给到price这个变量呢?

所以这个price变量就从原来的Option类型,现在变成Balance类型啦?是不是可以改写成下面的形式?
if let Some(balance_price) = price{......}
这样就把Option类型的price里边的值,取出来放在balance_price里?
              
这里的情况也是类似的,看着像同一个变量,从原来的Some类型变成了某个具体的类型。
 
A:黎倚杭@助教:不是类型变换,是从可空的类型中,取出非空的对象。
 
Q:村上香菜子@电商:但是变量前面等于Some(a),后面等于a,两种类型啊。
 
A:黎倚杭@助教:这是一个简写语法,if let
https://kaisery.github.io/trpl-zh-cn/ch06-03-if-let.html
               

 05 

Q:村上香菜子@电商:老师有提到T在某个地方,被限制了使用类型,一般是在哪里做限制的呢?是在写这个函数的Module声明里做的这个T:Trait这个限制吗?所以在这整个Module里,T都是指Trait对吗?
                        
前面这个Module限制了T: Trait, Trait的定义就是后面这张图,对吗?
                              
A:Ratentlan@程序员:对,T是个变量, 叫什么名字都可以看到T并不能认为是 T:Trait, 可能是 T:Index.  要进一步看。

 
 06 

Q:村上香菜子@电商:在Substrate里边,每一个crate文件,是不是必须定义一个名字叫做Trait的trait,一个名字叫做Module的struct,用来封装整个模版?

这就是它们的模版一样的东西,这样的话,在这个新建的crate里边,Trait就负责把要用到的其它crate里边要继承的东西都继承过来。Module就用来承接Trait定义的东西,然后把函数都写到Module里边,是这样吗?
 
A:陈锡亮@讲师:嗯没错。理论上这些名字可以改的只要保持一致就好,不过所有人都这么写。
 
 
 07 
 
Q:Ratentlan@程序员:有个问题,  是否收到kitties中定义的事件的时候, 就是这个事件的块已经finalized的时候?
 
A:陈锡亮@讲师:一般情况下出块确认了就会收到事件,finalize 是有可能一次 finalize多个区块的,但出块确认就是一个一个的。
 
Q:Ratentlan@程序员:是否最保险的方法就是扫描每个finalized的块, 然后看里面的自己需要的事件.   不然光订阅可能会因为网络问题漏掉事件。
 
想做的事情就是, 根据finalized的交易, 在中心化服务做点事情。在js中用 api.query.system.events((events) => { 监听的事件可能是在分叉上的.   有什么简单的办法达到只监听确定下的事件, 并且还不漏块吗?
 
A:陈锡亮@讲师: 可以存着最后扫描的区块,监听finalized的区块,扫描每个从最后扫描的区块到最新finalized区块到事件。没有直接简单的方法,不过按照上面的思路也不会太复杂。
 
 
 08 
 
Q:村上香菜子@电商:
                
这句话好复杂,能否麻烦帮忙拆解一下?等号前面的T跟等号后面的2个T是同一个T吗?这个能不能写成 
Self::Trait::Currency?
              
A:陈锡亮@讲师:是同一个T,这行里面没法有Self。
 
Q:村上香菜子@电商:那等号左边的T的trait bound是哪里定义的呢?这句话没有放在 Trait的定义里,是单独放出来的呀。

             
 
A:陈锡亮@讲师:就是右边的T as Trait。
 
Q:村上香菜子@电商:不能用Self的原因,是不是就是因为这句话没有定义在Trait的{}里?所以不会有实例实现?如果是的话,为什么不把它写进去Trait的定义里呢?这句话能直接写成下面的句子吗?
       
 
type BalanceOf<T> = <Trait::Currency as Currency<system::Trait::AccountId>>::Balance;
 
这个BalanceOf的功能,我可不可以直接用 Trait::Currency::Balance  ?
 
A:陈锡亮@讲师:可以,这个就是提供个缩写。

 
 09 

Q:村上香菜子@电商:
              
做测试的时候, exist()这里出了问题,请老师指点一下这个语法错在哪里?
 
A:陈锡亮@讲师:可以分开拆成几步再试试。不过这个直接用 
ownedkitties<t>::exists就好了。Storage是LinkedList的泛型参数,外部是不能调用的。
 
Q:村上香菜子@电商:改成ownedkitties,可以了。这相当于调用不了OwnedKittiesList来操作自己的map,直接用自己的map定义啊。
 
A:陈锡亮@讲师:嗯。不然的话也没太多意义,LinkedItem<OwnedKitties>::Storage明显比直接OwnedKitties麻烦。
 
Q:村上香菜子@电商:kitty_price()是decl_storage里边定义的KittyPrices的get方法。为什么调用它的时候,不用Self::KittyPrices::kitty_price(),而是直接Self::kitty_price()呢?
                             
A:陈锡亮@讲师:这边的Self是Module,decl_storage是把方法实现在Module里面的。KittyPrice已经有了get读数据。只是一个方便点的getter,不然的话是<KittyPrice<T>>::get()。
 
Q:村上香菜子@电商:所以这个 kitty_price是定义在Module里的单独的方法,不是KittyPrices的成员,所以不需要用KittyPrices作为前缀。
 
 
 10 

Q: 村上香菜子@电商:这个是什么语法呢?为什么泛型参数在:后面?不是应该紧跟名字的吗?
              
A:Ratentlan@程序员:好像叫  turbofish。
 
Q:村上香菜子@电商:另外,像这个测试的 Trait 实现的这些赋值方法,老师有没有什么参考文档或者帮助信息啊?我完全一头懵,全是按照助教给的文档复制粘贴的。感觉里边的关系错综复杂。
              
A:陈锡亮@讲师:这些都是kitties模块中的Trait里面限定的。
 
 
 11 

Q:KuoYeh@在读博士:请问再测试时若是用不到,Event,Currency,Randomness, 我该怎么写好?
不写的话又会报错没有implement
 impl Trait for Test {
  type KittyIndex = u32;
  type Event = ?;
  type Currency = ?;
  type Randomness = ?;
 
 }
 
A:陈锡亮@讲师:有两个方法,一个是自己建一个struct,impl 对应的trait,传进去,一个是引入实现了这个trait的模块,传进去。可以参考substrate 里面各种模块比如staking, treasury等的mock.rs文件。
 
Q:KuoYeh@在读博士:有没有substrate unit test的一些范例?
 
A:陈锡亮@讲师:
https://github.com/paritytech/substrate/blob/master/frame/staking/src/mock.rs
 
https://github.com/paritytech/substrate/blob/master/frame/treasury/src/lib.rs#L366
 
substrate里面每个模块都有测试,都可以参考。
 

作业
&
点评
    张静波助教

第四课作业点评


 作业要求:
1.重构create,使用新的帮助函数
2.完成combine_dna
3.设计加密猫模块V3
 transfer kitty 转移猫
 要求复杂度必须优于O(n)
 
01-佳爷
在重构create函数的时候,对kitty_id的溢出做了代码优化,但create和insert_kitty函数有重复代码,还有优化空间;
combine_dna写了伪代码的实现,但是有逻辑问题,可以在讲了测试方法后进行测试和修正;
设计加密猫模块V3,有较为详细的描述,作业认真。
 
03-朱强
create函数重构比较好,combine_dna功能实现,但还可以进行位运算优化。
 
04-郝明
作业质量高,优化了combine_dna,并代码实现了transfer功能。
  
这是郝明 同学的实现,大家可以学习参考一下,进行改进。
               
05-xvhehui
参考workshop对create函数做了优化,但功能不完整,希望能多看一下视频,参考一下其它同学的作业,继续加油。


第五课作业点评


 作业要求:
1.完成transfer
2.完成insert_owned_kitty
3.设计加密猫模块V4
 交易所
 给自己的小猫设定价钱
 购买小猫
 
01-佳爷
实现了do_transfer,判断了kitty_id合法性,但没有判断只有主人才有权限调用;
 
朱强和郝明的作业质量都比较高,郝明代码实现了交易功能功能,很赞。
               
这个是 朱强 同学的实现,可以参考一下,xvhehui同学参考了workshop,但代码不完整,希望多看看视频,继续加油。
 
交了作业的同学,作业完成度都比较高,并且有对下次作业的思考设计,这点很值得表扬。没及时交作业的同学,也希望能继续努力,跟上课程。其实可以发现,老师的作业代码量真的很少,希望大家能再接再厉,继续加油。








&
点评
   黄志光助教

第六课作业点评

 
 作业要求: 
1、完成linked_item
append
remove
2、修复测试
 
第六课我们把之前实现在Storage Item OwnedKitties上的链表方法抽象成独立的模块,让链表模块更加通用。
 
本组共有1位同学提交了第六课作业,完成质量很好,我们一起来看一下这位同学的作业,顺便复习一下链表数据结构。
  
首先是append方法,append方法用于插入一个元素到链表尾部,我们课程中的链表头元素有prev/next指向上一个和下一个元素,是一个双向链表。
 
复习一下双向链表的插入流程:
1) 找到要插入的位置,以及该位置的上一个和下一个元素
2) 把上一个元素的next指向自己,把下一个元素的prev指向自己
3) 把自己的prev指向上一个元素,把自己的next指向下一个元素
 
稍有不同:我们课程中实现的链表头的prev指向了链表尾(便于找到链表尾进行append操作),但链表尾的next并不需要指向链表头。
  
我们组这位同学的作业中,append方法如下,我做了点注释方便大家理解。
              
然后是remove方法,remove方法用于移除链表中任意位置(链表头除外)的元素。
 
复习一下双向链表的删除流程:
1) 找到要删除的位置,以及该位置的上一个和下一个元素。
2) 让上一个和下一个元素直接相连,跳过自己。
a) 把上一个元素的next改成下一个元素
b) 把下一个元素的prev改成上一个元素
3) 删除自己占用的存储空间。
 
我们组这位同学的作业中,remove方法如下,我做了点注释方便大家理解。
               
方法逻辑正确,代码中先读出数据,操作链表后,再调用StorageMap提供的remove方法删除数据,这里可以做一点小优化。
 
读取和删除确保都可以完成的情况下(中途不需要ensure!、不会panic、不会return等),可以使用StorageMap提供的take方法一步完成。
  
最后是修复测试,需要在保证测试用例正确性的前提下,用测试来驱动开发。
 
这位同学的作业中,正确修复了测试,并且执行cargo test相关命令也能通过,这里就不展开点评。

利用各种数据结构提升Substrate Storage item的存取能力是一个非常好的课题,课程中的linked_item是很典型的例子,值得深入了解。



更多阅读:

▎Subdev 分享 | 手把手:用RUST语言开发RSA算法模拟

▎Subdev 周记 | 10年后,Web3技术能带来的未来

▎Subdev Beijing 0.1 | 让一个区块链项目真正拥有商业模式



扫码关注公众号,回复“1”加入开发者社群


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存