编写 Fabric 链码的一般准则
我相信智能合约(链码)是 Hyperledger Fabric 区块链网络的核心。正确开发链码可以真正发挥一个安全区块链的优势,反之则会带来灾难性的后果。在这篇文章里我不打算探讨 Hyperledger Fabric 链码设计的特定模式的好与坏,而是希望分享我在开发若干 Hyperledger Fabric 概念验证应用过程中 总结的一些基本准则。
1. 使用链码 DevMode(开发模式)使用开发模式开启你的 Hyperledger Fabric 链码开发流程。这一点无论怎么强调都不过分,这会节省你大量的时间和精力,因为你可以自由地修改代码而无需重新部署并激活链码,也无需一遍遍地重启网络。
参考文档:https://github.com/hyperledger/fabric-samples/tree/release/chaincode-docker-devmode
P.S. - 虽然这个教程是使用 Golang,除了构建链码部分,使用其他语言其实也差不多。
2. 使用链码 Logging(日志)这可能是能帮助你调试 Hyperledger Fabric 链码并快速找出链码 bug 的一个有用的技能。链码日志很简单易用,使用 Fabric 内建的 logger 即可。
参考文档:
Golang:shim ChaincodeLoggerNodeJS:shim newLoggerJava:可以使用任何标准的日志框架,例如 log4j3. 避免在 Fabric 链码中使用全局键(Global Keys)在开发 Hyperledger Fabric 链码时,我们经常会发现在搜索数据方面限制很多,因此要跟踪在键值库中注册的键,我们有时会尝试使用某些全局数据。
例如,当你再 Hyperledger Fabric 应用中跟踪注册的弹珠时,可能想创建一个全局的计数器以便生成弹珠的下一个 ID。但是这么做的时候, 你就引入了对这个变量的依赖。在开始的时候这看起来不是个问题,但是当你提交并发交易时就会出错。为什么?让我解释一下。
分析一下下面链码:
package mainimport ( //other imports "github.com/hyperledger/fabric/core/chaincode/shim" pb "github.com/hyperledger/fabric/protos/peer")//不要这么做! totalNumberOfMarbles := 0func (t *SimpleChaincode) initMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { var err error marbleId := fmt.Sprintf("MARBLE_%06d",totalNumberOfMarbles) marbleName := args[0] color := strings.ToLower(args[1]) owner := strings.ToLower(args[3]) size, err := strconv.Atoi(args[2]) //other code to initialize objectType := "marble" marble := &marble{objectType, marbleId, marbleName, color, size, owner} //--------------CODE SMELL---------------- //BIG source of Non-determinism as well as performance hit. totalNumberOfMarbles = totalNumberOfMarbles + 1 //--------------CODE SMELL---------------- //regular stuff... err = stub.PutState(marbleId, marbleJSONasBytes) if err != nil { return shim.Error(err.Error()) }}
那么,为什么我讨厌这样?
原因 1:假设你已经完成这个 Fabric 链码,一切都很正常,直到有一天, 某个运行这个链码的 peer 节点,崩溃了。虽然账本数据还在,但是内部有些可怕的事情已经发生了。你可能重新启动 peer 节点,起初一切看起来都正常。 但是突然,这个节点背书的所有交易都开始失败了。为什么?就是因为那个全局计数变量已经不能正确跟踪真实的值了。其他的 peer 节点都计数到比如 15K 了,而这个节点突然从零开始计数,你的弹珠的 ID 又从零开始了。因此,当你将这个交易发送给排序节点(Orderer)并到达提交节点(Peer)时,提交节点上的验证系统(Validation System Chaincode)会比较所有背书交易的提议响应, 同时检查是否有足够的签名存在,只要有一个提议响应不匹配,提交节点就会抛出一个 ENDORSEMENT_POLICY_FAILURE 异常。
原因 2:现在让我们尝试解决上面的问题,在 Fabric 链码的最后添加代码stub.PutState("marble_count", totalNumberOfMarbles)
这样会好一些吗? NO...
考虑一下有两个并发交易都试图插入新的弹珠。
例如,一个交易要将marble_count的值更新为34,此时 marble_count 的版本为 10 new_version(marble_count) = 10。 而另一个交易则要将 marble_count 的值更新为 35,也是 new_version(marble_count) = 10 。 记住,由于这两个交易是并发的,两个交易看到的都是current_version(marble_count) = 09。
现在其中一个交易将在另一个交易之前到达 Fabric 的排序节点,marble_count 键已经更新到新的值,这时 marble_count 的版本已经是 10,因此后到的交易将失败,因为 marble_count 的版本已经是 10 ,而后续交易还认为它读的是版本 09 并且将更新到版本 10。这是区块链中经典的双花问题(double spending)。
Hyperledger Fabric 在提交交易时使用一种优化的锁模型。正如我已经解释过的, 提议响应由客户端从背书节点采集,然后发送给排序节点并最终由排序节点将其分发给提交节点。在这个两步过程中,如果有些在背书阶段读取的键的版本发生了变化, 你就会得到MVCC_READ_CONFLICT错误。当存在并发交易同时更新相同的键时,就有可能出现这个问题。
关于这一点的详细说明,可以参考这篇文章。
PS. 即使您不执行并发交易,如果更新同一键的一个或多个交易打包到同一块中同样也会出现这个问题。因为,在提交块之前,不会提交 Hyperledger Fabric 中的交易。
4. 聪明地使用 CouchDB 查询Couch DB 查询(又称为 Mongo 查询)在搜索 Fabric 节点的键值库中的数据时非常有用, 但是有一些坑你需要注意。
** Couch DB 查询不会修改交易的 READ SET**
Mongo 查询仅用来查询节点的键值库也就是状态库。它不会修改交易的 read set。这可能会在交易中导致幻读(phantom reads)。
** 只能搜索到已经存入 CouchDB 的数据 **
不要试图用 Mongo 查询按键名搜索。虽然你可以访问 CouchDB 的 Fauxton 控制台, 但你无法按键查询。例如,不允许查询channelName\0000KeyName。更好的方法时将键作为你自己数据的属性保存。
5. 编写确定性的 Fabric 链码永远不要编写不确定的链码。意思是说如果我在多个不同的时间、不同的环境下执行链码,总应该得到相同的结果。例如,避免使用像rand.New(...)或 t := time.Now() 这样的代码,或者依赖于某个没有在账本中持久化的变量。
这是因为,如果生成的读写集不一样,Hyperledger Fabric 的验证系统链码(Validation System Chaincode) 会拒绝交易并抛出 ENDORSEMENT_POLICY_FAILURE 异常。
6. 小心调用其他通道的 Fabric 链码在链码中调用同一个通道中的另一个链码没问题,但是要了解的是,如果是要调用另一个通道的链码,你只能得到链码方法的返回结果,而不会在另一个通道账本中有任何提交。目前,跨通道的链码调用不会修改数据,因此,一个交易一次只能写入一个通道。
7. 记得设置 Fabric 链码的执行超时时间在高负载的情况下,你的 Hyperledger Fabric 链码可能不会在 30s 内完成。因此一个好的实践是 根据需求定制链码执行超时值。这是由 core.yaml 中的参数决定的。你可以 在 docker compose 文件中如下设置:
如: CORE_CHAINCODE_EXECUTETIMEOUT=60s
8. 避免从 Fabric 链码中访问外部资源访问外部资源可能会暴露系统漏洞并给你的 Hyperledger Fabric 链码引入安全威胁。无论如何你不会希望外部资源中的恶意代码影响你的链码逻辑。因此请尽可能的避免 再 Fabric 链码中访问区块链外部的资源。
- 免责声明
- 世链财经作为开放的信息发布平台,所有资讯仅代表作者个人观点,与世链财经无关。如文章、图片、音频或视频出现侵权、违规及其他不当言论,请提供相关材料,发送到:2785592653@qq.com。
- 风险提示:本站所提供的资讯不代表任何投资暗示。投资有风险,入市须谨慎。
- 世链粉丝群:提供最新热点新闻,空投糖果、红包等福利,微信:juu3644。