存证&积分场景合约设计技巧与实例代码解析

作者:毛嘉宇|FISCO BCOS 核心开发者

场景1:区块链+存证的权限合约编写

电子数据存证是记录“用户身份验证-数据创建-存储-传输”全过程的方式,应用一系列安全技术全方位确保电子数据的真实性、完整性、安全性,在司法上具备完整的法律效力。

区块链技术的下列特点有助于降低成本、提升效率,并且确保存证数据的安全。

存证场景简要业务流程

在存证场景中可以抽象出三类典型用户:存证方、审核方和取证方

存证场景权限合约实例讲解

我们以存证场景中权限合约进行讲解。

存证合约概要设计

首先将逻辑和数据层分离。因为Solidity智能合约语言没有独立数据层,为便于合约后续扩展、升级,需要将逻辑和数据层分离,体现在下图里就是将数据层和控制层区分开。

其次引入权限层。在一条联盟链上所有节点可以自由访问链上数据,智能合约提供了一种修饰器机制,可控制合约给指定授权用户访问,依据合约单一职责原理,将这一层抽象出来。

同时,我们需要控制数据层的权限,防止将写入数据层的接口权限开放给所有人,因此需要依赖并引入权限合约。

权限合约实例讲解

权限合约比较简单,不需要依赖其他合约,在很多合约开发中存在需求,可供复用。

Authentication合约里首先定义了两个成员变量,合约属主owner和权限控制映射列表acl(access control list)。

存证数据

下图展示的是证据数据层,存证数据合约的代码。

EvidenceRepository是存证数据仓库,它继承了权限合约,权限合约里的方法和修饰器可在存证仓库的合约被使用。

可以看出,所有存证数据都被保存到数据合约里。这样可以起到统一存储、统一管理的效果。当然,这不一定是最优方案。 在业务场景中,如果合约拥有海量存证数据,则可能成为性能瓶颈,采用分拆设计方案会更加合理。

请求数据

存证方开始提交存证数据并不会直接被写入到存证仓库中,而是经过审核方签名完成后才会真正提交,审核方可以为多方。

理解合约用途后,我们仔细看这个合约。

控制器

控制器引入了两个数据仓库合约,我们只需调用controller就可以完成所有用户接口的交互;它的构造函数参数变量包含了请求合约构造所需的参数:审核者列表及投票阈值,这个构造函数会自动构造和创建合约。

controller定义了两个方法,一个是创建存证请求,另一个是审核人根据请求进行投票。 创建请求函数较为简单,会直接调用请求数据仓库合约里的创建请求函数。 处理投票函数相对复杂。在验证hash数据非空后,会调用审核接口,如果审核成功,会触发检查当前请求审核通过数是否超过阈值,一旦超过,就自动保存到存证数据合约,同时,删除该请求。

此外,这个合约里还定义了三个event事件,有以下作用:

例如,createSaveRequest日志记录了hash和调用地址。如果配合SDK,我们可以实现对这个特定事件监听,并自动触发自定义回调函数。

存证合约示例小结

以上就是一个完整的存证场景权限合约demo。为了便于理解,我们并没有把例子设计得面面俱到,希望大家能更好地从中理解到demo的设计思想:

场景2:区块链+积分合约实例讲解

下面介绍另一个智能合约典型应用场景——积分场景。

区块链技术可以如何解决积分场景中的这些痛点呢?

图:典型积分业务场景示例

一种思路是基于区块链技术,多个商家组成积分联盟,实现积分通存通兑、客户资源相互引流等。我们抽象了一个管理者,管理者部署和管理合约,商家有发行积分、拉入其他商家、撤销发行者身份的权限;消费者有开户、销户、消费积分和积分转账的权限。

积分场景合约实例讲解

先来做积分合约的概要设计。在存证合约中,我们引入了数据和逻辑分离的思想;在积分合约中,我们将引入管理、数据和逻辑分离的思想。 为什么要增加一层管理合约呢?原有的两层结构中,控制合约会自动创建数据合约,而数据层合约中写死了属主是控制合约。 引入了管理合约后,就实现了类似控制反转的效果,控制合约和数据合约都由管理合约来创建;同时,管理合约还可以随时设置数据合约中控制合约的地址。这样,控制合约就可以随时实现平滑地业务逻辑升级;将管理合约分离出来,还有利于链上权限治理。 此外,我们还将常用的权限、角色功能抽象为合约,并抽象了权限mapping和数据计算的库。

下面我们将看下积分合约具体的代码实现。

合约库——安全计算

安全计算在Solidity中非常重要。涉及到数值计算部分,可优先考虑采用成熟开源的库。分享一个小技巧,由于链上资源非常宝贵,建议大家使用库的时候可以裁剪掉冗余的代码,节省资源。 安全计算的库,会在执行后重新检查数值,避免出现溢出,规避攻击。

库——角色管理

角色管理的库提供了创建角色、删除角色、查询角色的功能。这里有个基础的mapping bearer,是address到bool的映射,在mapping中维护role身份。

基础权限合约

在BasicAuth的基础权限合约中,我们提供了对属主的判断。

发行者合约

发行者合约依赖上面LibRole的合约。为了简化规则,易于理解,我们这样定义:发行者允许添加新的发行者,也可以撤销自己发行者身份。有了发行者角色后,我们就可以发行积分了。

积分数据合约

现在进入到积分合约主体——admin-controller-data三层架构。 首先介绍积分数据合约。它会将所有用户积分,以及角色信息都存到积分数据合约里。

管理合约

管理合约作用是创建所有的合约。constructor更新在数据合约中所持有的版本号,一旦controller需要升级,只需调用下这个方法即可。

控制合约

最后介绍控制合约。由于controller代码较长,这里只展示最典型的两个函数。 balance查余额调的就是数据查余额的接口。积分的消费通过transfer实现,其中会有很多修饰器来检查账户是否已经注册,是否有效等,此外采用了智能合约事件来输出和打印日志。

积分合约示例小结

小结一下积分场景demo中的一些设计思想:

如何写出高质量设计说明文档

文档编写之『术』

『术』主要就是编写文档时结构上所需要素,这里分为了三类:

文档编写之『道』

有了术,就能确保文档内容结构的完整性,文档就有了骨架和血肉;但是,『道』才是文档的灵魂和精髓所在,这里整理了5个关注点:

本文主要分享了存证和积分两个典型应用场景合约设计思路和实例代码解析,为大家总结了相关的开发技巧和文档写作技巧。 在智能合约开发过程中,开发者需根据实际业务需求选择适用的技巧与方案。正所谓『兵无常势,水无常形』,没有最优的设计,只有最合适的设计。


Q&A

Q :智能合约中如何获取之前的数据?

A :主要有两种方法:1.在智能合约中定义需要查询历史数据的函数,通过合约查询接口来查询。2.使用WeBASE-Collect-Bee数据导出组件将链上数据导出到链下数据库中,可以查询所有数据。

Q :合约中的变量定义了private 属性,也可以被链上公开获取到吗?

A :链上所有数据都是公开的,即便定义了private属性的变量,也可以通过技术手段获取。

Q :在智能合约内可不可以调用其他外部接口?

A :智能合约中可以调用其他合约接口,但是不能访问外部接口。

Q :如果积分在一定时间内会减少,这种积分场景怎么实现?

A :首先要定义一个积分减少的业务规则;其次要实现这种场景在技术上是可行的,例如可以设计一个积分销毁函数,调用这个函数后,可以减少指定账户积分并同时减少总积分数量。最后,具体的实现逻辑和方式取决于业务规则。另外,需要注意的是,智能合约不支持类似定时脚本机制,需要外部调用才能触发。

Q :在编写智能合约时一定要按照三层结构进行编写吗?

A :不一定,取决于具体的业务场景,需要从实际业务场景中分析利弊,选择合适的方案。一般场景下我们推荐分层,这样更加灵活,更利于合约升级和维护。

Q :怎么通过智能合约实现同一群组内不同用户数据读写权限管控?

A :数据一旦上链后,对链上所有参与者来说都是公开透明的,因此,在链上实现同一群组内不同用户数据读写权限的管控本身不可行。但我们可以通过在数据上链前对数据本身进行加密来实现类似效果。

Q :存证中的哈希值是合同或者发票的哈希值吗?

A :这个规则取决于具体应用需求,可以直接是文件的哈希或者是拼装了其他信息再算一个哈希值,需要看具体场景需求。

Q :如果上链的数据错误,怎么处理?

A :数据一旦上链后,就不能被篡改和被物理删除;但可以设计一种合约逻辑删除机制,例如,在特定的数据合约中添加一个状态字段来标记数据是否被删除。