半年的工作回顾

2023-05-06

回顾一下最近半年的工作。从半年前开始,我就迷茫了,因为知道 Filecoin 在做 EVM 的适配,存储赛道已经倒向计算赛道的一边。我们又无法超越 Filecoin。

不过我还是有收获的,主要集中在对文件证明的进一步理解,以及对项目中代码细节的进一步熟悉,这一块是我之前没了解的。

扇区证明

扇区证明一直存在个问题,就是证明会失败。一开始认为是合约进行证明检查的时候,根本没有带上文件列表的信息,导致过期文件没有被删掉。后来发现不止这一个问题,因为在修复代码后,如果文件只存到一个存储节点,是正常的,如果有多个存储节点,证明仍然会失败。

最后查到的原因是,当一个文件被存到多个节点,其他节点提交证明到合约,如果发现文件过期了,就会在合约里删掉节点对应的文件信息,同时广播给其他节点,要删掉这个文件。而其他节点收到通知后,只删除了本地的问题,没有删除合约里的文件信息,就导致本地的文件经常会少于合约里记录的文件,证明就失败了。

这个问题解决的过程中,相当于把证明相关的代码看了一遍。

EVM 合约

在去年 一年的工作回顾 中提到,我在做一件把 Go 语言合约用 Solidity 重写的事情,也就是把原有的合约移植到以太坊上运行。

其中让项目支持以太坊账户的事情早就完成了,存储节点可以顺利地使用以太坊节点,通过以太坊的智能合约实现文件的上传下载。但是合约对文件的证明一直空着,因为项目使用了 Bulletproof 的零知识证明,用 Solidity 写一遍 Bulletproof 的证明太费力了。

现在的做法是 POA 的方式,在合约里本该进行证明的地方,换成提交证明参数,把证明参数记录到合约中。然后验证节点从合约里把证明参数拿下来,在本地进行零知识证明的验证,再把证明结果提交回合约里。

因为把证明相关的代码看了一遍,所以现在能够完成 EVM 合约中,证明相关的这一部分。

为什么忽略 Layer2

之前有一个想法是,把证明放到 Layer2 的节点上做。我在这个方向上没什么进度,有两个原因:

  1. Layer2 的定位是 EVM 完备,本身不具备额外的功能
  2. Layer2 也很难进行验证

第一点的意思是,对于用户或者开发者来说,Layer2 存在的意义是降低手续费,如果能在使用效果上和 Layer1 一模一样,那就了不得了。给 Layer2 增加一些额外的功能,是不是 Layer2 这种项目的本意呢?

第二点的意思是,Layer2 的项目也是拿着 Layer1 的节点改了改区块的头信息之类。想要实现文件的验证,需要在虚拟机的字节码层面,识别出一笔交易是调用了某个合约的某个函数,然后把这个函数要操作的变量拿出来,对参数进行一次零知识证明的验证,再把验证结果在字节码层面写入回变量中。这样做的难度很大。不过要真的能搞成,相当于搞了一个业务专用的 EVM 虚拟机,技术门槛挺高,挺能用来炫耀的。

与之相比,相对简单的做法就是,用一个客户端对证明参数做验证,再把结果提交回去。如果是这样,那就无所谓客户端在 Layer2 的节点上还是其他什么节点上了。 我就选择了这种简单的方式去做,虽然不够酷,但是有生之年能完成,能看到结果,现在确实完成了。

Solidity 合约

稍微总结一下 Go 合约迁移到 Solidity 合约遇到的问题。

合约大小不能超过 24 KB,否则会编译失败。对于业务型的合约,24 KB 是不够用的,需要拆分为两个甚至多个合约,合约之间再通过调用完成相应的功能。拆起来也不难,就是注意在功能上拆明白,哪些是对外的,哪些是内部的。

storage 的结构体不能包含列表,因为列表不是固定长度的。合约不好计算占用了多少空间。这就需要把原先结构体里面的列表拆出来,用的时候单独用。当然如果对业务逻辑熟悉,这一点不是什么问题,如果对业务逻辑不熟悉,看到一堆陌生的变量名称,还是挺烦的。我就因为当时偷懒,合约里没把逻辑写全,查了两天才查明白问题。

mapping 不能迭代。原先简单的写法,需要用 library 改为挺复杂的写法。

mapping 元素不存在会返回默认值。无法判断 mapping 是否包含某个 key,如果业务中类型的默认值有含义,可能会出现意想不到的情况。

这几点不算什么大问题,但可能会给写代码的过程带来一点麻烦,因为需要用不一样的写法,写出完全一样的逻辑。