主页 > 下载imtoken被盗 > 浅谈以太坊智能合约的设计模式和升级方式

浅谈以太坊智能合约的设计模式和升级方式

下载imtoken被盗 2023-07-27 05:13:56

本文约6500字+,阅读(观看)需38分钟

以太坊 EVM 是目前区块链行业使用最广泛的虚拟机。 它支持的智能合约语言是图灵完备的。 该语言支持各种基本类型(布尔、整数、地址、字符串、枚举、地址等)、复杂类型(结构、映射、数组等)、复杂表达式和控制结构、接口等面向对象的特性遗产。

正是因为强大的智能合约语言,才能在区块链上轻松实现现实世界中复杂的业务逻辑和应用。 但需要注意的是,虽然公链可以实现合理的GAS机制进行自我保护,联盟链可以用其他机制代替GAS的计算和代币化来保证EVM沙箱的安全,但由于运营区块链机制,智能合约即使运行异常,也会在所有区块链节点上独立重复运行。 因此,无论是在公链上还是在联盟链上运行智能合约都是一个非常昂贵的操作(计算资源、存储资源)。

此外,智能合约与传统应用的区别在于,智能合约一旦发布在区块链上,就无法被篡改。 即使智能合约中存在需要修复的错误或业务逻辑发生变化,也无法直接在原始合约上进行更新。 修改并重新发布。 因此,在设计之初,需要结合业务场景考虑合理的升级机制。

总而言之,智能合约实现要达到的目标是:业务功能完备、代码逻辑简洁、模块抽象良好、合约结构清晰、安全检查合理、升级方案完备。

智能合约的生命周期主要包括设计、开发、部署、运行、升级和销毁。 下面主要是根据设计阶段和升级阶段的目标进行一些梳理和总结。

1. 最佳实践

从业务角度看,智能合约只需要做两件事,一是如何定义数据的结构,如何读写,二是如何处理数据以太坊智能合约的应用,对外提供服务接口。

为了更好的做好模块抽象和合约结构分层,把这两个东西分开,也就是把业务控制逻辑和数据从合约代码层面分开。 这种处理目前在复杂的业务逻辑场景中实践。 被认为是最好的模式。

此模式称为 CD(控制器数据)模式。 将合约分为两类:Controller Contract和Data Contract。

以太坊智能合约转不出去币_siteqq.com 以太坊智能合约_以太坊智能合约的应用

控制器合约通过访问数据合约获取数据,对数据进行逻辑处理,然后写回数据合约。 它侧重于数据的逻辑处理和对外提供服务。 根据处理逻辑的不同,有namespace controller contracts、proxy controller contracts、business controller contracts、factory controller contracts等。一般情况下,controller contract不需要存储任何数据,完全依赖外部输入来决定访问到数据合同。 在特殊情况下,controller合约可以存储固定数据合约的地址或命名空间(通过命名空间获取运行时的合约地址)。

数据契约专注于数据结构定义和读写存储数据的原始接口。 为了达到统一数据访问管理和数据访问权限控制的目的,最好只将数据读写接口暴露给相应的controller合约。 禁止通过其他方式进行读写访问。

基于这个模型,按照自上而下的分析方法,从对外提供的服务接口开始设计各种controller契约,然后逐步过渡到服务接口需要的数据模型和存储方式,再设计各种数据契约,你可以比较快速的完成合约结构的设计。

2. 实用设计案例

在CD模式下,根据controller合约和data合约之间的操作关系,逻辑上可以分为四类:

控制器合约和数据合约1->1

控制器合约和数据合约1->N

控制器合约和数据合约 N->1

控制器合约和数据合约 N->N

假设一个业务场景:把全国所有银行的业务和信息都上链。

2.1 控制器合约和数据合约:1->1

假设全国只有两家银行,A银行和B银行,A银行只有存款业务,B银行只有取款业务。 一种可能的设计是这样的:

以太坊智能合约转不出去币_siteqq.com 以太坊智能合约_以太坊智能合约的应用

siteqq.com 以太坊智能合约_以太坊智能合约的应用_以太坊智能合约转不出去币

代理控制器合约:面向Dapp,是所有业务合约的入口,提供命名空间服务,提供命名空间到合约地址的映射。 让 Dapp 感知不到链上合约升级导致的地址变化。 比如Dapp对于银行A的入金请求只需要("BankA", deposit, args)。对于银行B的提款请求只需要("BankB", withdraw, args)。 代理控制合约的实现应该内置固化在区块链底层,或者在业务上很少改变。 Dapp在业务运行之前就已经明确知道agent controller合约的地址。

命名控制器合约:为链上合约提供命名空间服务,提供命名空间到合约地址的映射。 这使得链上合约在运行时可以根据命名获取实际的合约地址。 例如,银行A控制器合约请求命名控制器合约(“BankA-Data”)获取银行A数据合约的地址,以便银行A控制器合约可以在运行时访问银行A数据合约。 它与代理控制器合约的主要区别在于服务对象。 代理控制器合约面向Dapp,命名控制器合约面向链上合约。 此外,命名控制器合约包含版本控制设计(见下文3.2节),可以根据需要配合灰度策略的实施。

银行控制器合约:提供存款服务接口存款。 当部署初始化时,它已经清楚地知道它的身份“BankA”。 “BankA-Data”的合约地址是在运行时通过命名控制合约获得的。

Bank A数据合约:保存了Bank A的当前余额。为A bank controller合约提供add和sub接口,更新余额信息。

Bank B controller contract:提供存款服务接口取款。 当部署初始化时,它已经清楚地知道它的身份“BankB”。 “BankB”的合约地址是在运行时通过命名控制合约获得的。

Bank B data contract:保存Bank B的当前余额。为B bank controller contract提供add和sub接口,更新余额信息。

向银行A提出入金请求的流程如下:

Dapp 指定代理控制器合约地址并请求存款交易(“BankA”,存款,货币)

代理控制器合约在运行时获取“BankA”对应的银行A控制器合约的地址,向银行A控制器合约请求存款交易(deposit, money)

银行A控制器合约的存款接口向命名控制器合约请求“BankA-Data”获取银行A数据数据合约的地址,并访问银行A数据合约的数据,然后执行一些存款业务逻辑. 返回结果。

将结果依次返回给 Dapp。

2.2 控制器合约和数据合约:1->N

假设全国有N家银行,所有银行都有存款业务和取款业务,业务流程相同。 一种可能的设计是这样的:

siteqq.com 以太坊智能合约_以太坊智能合约转不出去币_以太坊智能合约的应用

本次设计与上面2.1的不同之处在于,存款服务接口和取款接口都集成到了银行业务控制器合约中。 这意味着任何一家银行的存取款业务都由银行业务控制器合约统一处理,处理逻辑不再区分是银行A还是银行B,而是需要根据输入的不同来决定接入访问数据时的参数。 银行数据合同。

还有,相对于2.1,对于Dapp来说,在发送请求的时候,只需要将请求发送到一个固定的“Bank”即可,不需要关心具体的银行。

另外,由于银行数量众多,存储结构相同,可以设计一个银行数据合约的工厂控制器合约,负责生成模块化的新数据合约。

向银行A提出入金请求的流程如下:

Dapp 指定代理控制器合约地址并请求存款交易("Bank", deposit, "BankA", money)

代理控制器合约在运行时获取“Bank”对应的银行控制器合约的地址,并向银行控制器合约请求一笔存款交易(deposit, “BankA”, money)

siteqq.com 以太坊智能合约_以太坊智能合约转不出去币_以太坊智能合约的应用

银行业务控制器合约的存款接口向命名控制器合约请求“BankA-Data”以获取银行A数据数据合约的地址,访问银行A数据合约的数据,然后执行一些存款业务逻辑。 返回结果。

将结果依次返回给 Dapp。

2.3 控制器合约和数据合约:N->1

假设全国有N家银行,所有银行都有存款业务和取款业务,业务流程相同,但由于业务逻辑复杂,需要将存款业务和取款业务分开为了模块化维护。 一种可能的设计是这样的:

siteqq.com 以太坊智能合约_以太坊智能合约的应用_以太坊智能合约转不出去币

这个设计和上面2.2的区别在于,存款服务接口和取款接口拆分到了不同的业务控制器合约中。 这意味着不同的业务逻辑与模块明确分开。 对于Dapp来说,在发送请求的时候,需要明确指向对应的业务接口。

向银行A提出入金请求的流程如下:

Dapp 指定代理控制器合约地址并请求存款交易(“deposit”、“BankA”、money)

代理控制器合约在运行时获取“存款”对应的存款业务控制器合约地址,向存款业务控制器合约(“BankA”,money)请求一笔存款交易

存款业务控制器合约的存款接口向named controller合约请求“BankA-Data”获取Bank A数据数据合约的地址,并访问Bank A数据合约的数据,然后执行一些存款业务逻辑. 返回结果。

将结果依次返回给 Dapp。

2.4 控制器合约和数据合约:N->N

这样的情况可以分解为以上三种情况的组合。 不再。

2.5 总结

从Dapp的角度,可以概括为:

CD 模式功能

1->1

面向业务对象

1->N

面向业务流程

N->1

以太坊智能合约的应用_以太坊智能合约转不出去币_siteqq.com 以太坊智能合约

面向业务的接口

N->N

/

3.升级

在CD模式下,当业务逻辑变更需要升级合约时,根据controller合约和data合约的升级关系分为以下三种情况:

控制器合约数据合约

升级

不要升级

不要升级

升级

升级

升级

在升级过程中,还需要考虑是全量升级还是灰度升级? 如果是灰度升级,灰度策略是怎样的? 另外,多链场景、单链场景、跨链场景在升级过程上有什么区别吗? 多链场景的灰度策略如何考虑? 新旧版本数据能否共存? 如果需要进行数据迁移,如何实现无缝迁移?

下面以最常见的1->N场景来介绍不同的升级情况。

3.1 controller合约升级,data合约没有升级

以太坊智能合约的应用_以太坊智能合约转不出去币_siteqq.com 以太坊智能合约

如上图所示,银行业务控制器合约从V1升级到V2,其他合约和接口不需要更新。 假设V2版本相对于V1版本只升级了withdraw接口。

此时,银行控制器合约V2版本需要做的是:

继承银行控制器合约的 V1 版本。

添加一个成员变量,指向V1版本的链上合约地址。

新增提现开关接口,允许外部账户通过普通交易操作V2合约的启停灰度策略。

以太坊智能合约的应用_以太坊智能合约转不出去币_siteqq.com 以太坊智能合约

重载取款接口。 升级相应的接口逻辑。 并且在业务逻辑真正开始执行之前,自定义灰度策略(比如灰度特定用户,或者一定比例的用户或者其他策略)。 并且需要注意的是,开启灰度开关后,如果请求没有命中灰度策略,会直接通过透传参数调用V1版本的合约接口,不会调用V2版本的withdraw接口做任何额外的工作。

完成V2版本的合约工作后,即可发布正常交易。 交易中的逻辑是先部署v2版本的银行业务controller合约,然后更新其地址到agent controller合约,让“Bank”映射到V2版本的合约地址。 这样控制器合约就升级了。

如果需要回滚版本,只需要发布一个正常的交易,将proxy controller合约的“Bank”映射到V1版本的合约地址即可。

以上是单链场景的升级方法。 如果是多链场景,只需要根据业务需要判断链间的灰度策略,重复单链场景的升级即可。 如果是跨链场景,需要根据跨链两端的具体情况制定升级方式。

对于业务发起端的Dapp,它是没有感知的。 其向银行A的存款请求与2.2完全相同。 请求仍然是通过 ("Bank", deposit, "BankA", money) 发出的。

综上所述,新版controller合约中定义了灰度策略,数据不需要迁移,业务无感知,无需停止服务。 无缝升级。

3.2 controller contract没有升级,data contract升级了

以太坊智能合约转不出去币_siteqq.com 以太坊智能合约_以太坊智能合约的应用

如上图所示,Bank A数据合约从V1升级到V2。 其他合约和接口不需要更新,假设V2版本相比V1版本只增加了一个新的数据字段loan,假设银行业务控制器合约本来可以支持V2版本的A银行数据合约(如果是银行业务,controller合约也需要升级,即3.3节的场景,这里不再赘述)。

此时以太坊智能合约的应用,A银行数据合约V2版本需要做的是:

继承银行数据合约V1版本。

添加新的外地贷款。 并实现贷款相关的数据接口。

需要注意的是命名控制器合约有如下重要设计:

named controller contract通过访问named data contract来存储和访问数据(为了描述方便,图中没有画出来),所以command controller contract升级可以参考3.1节的方法。

命名数据契约保存了name=>mapping(version=>address)的映射表。

命名数据契约保存了名称=>当前有效版本的映射表。

命名控制器契约提供了一个接口,用于遍历命名数据契约的名称。

命名控制器合约为命名数据合约的映射表提供更改接口。

因此,V2版本的数据合约完成后,就可以发出正常的交易了。 交易中的逻辑是先部署A银行数据合约V2版本,完成从V1版本数据合约到V2版本数据合约的数据迁移(数据迁移方法将在章节中介绍) 4)、然后将V2版本数据合约地址注册到named controller合约中,并更新BankA-Data映射的当前有效verison=V2。 至此,A银行数据合约V2版本升级完成。

如果需要回滚版本,只需要发布一个正常的交易,并命名controller合约的BankA-Data映射的当前有效的verison=V1即可。

对于业务发起端的Dapp,它是没有感知的。 其向银行A的存款请求与2.2完全相同。 请求仍然是通过 ("Bank", deposit, "BankA", money) 发出的。

以太坊智能合约转不出去币_以太坊智能合约的应用_siteqq.com 以太坊智能合约

对于B银行,由于B银行数据合约还没有升级,与其相关的业务请求仍然是访问的B银行数据合约的V1版本。 因此,对于老版本的数据合约,可以根据业务的需要判断是否需要升级老版本。 在某些特殊场景下,需要升级所有历史旧版本的数据合约。 这时候就可以使用named controller合约的遍历功能,对所有的数据合约进行类似的升级。 对于新加入的银行C,可以直接使用最新版本的V2数据合约,按照正常流程完成部署和注册,无需额外操作。

正是由于命名控制器合约的版本控制逻辑,即使数据合约存在新旧版本,业务控制器合约仍然可以正常运行。 但由于业务发展和版本不断升级,命名数据合约的存储容量会扩大,从而可能导致性能下降。 本节介绍的数据迁移和升级方法仍然可以解决问题。

以上是单链场景的升级方法。 如果是多链场景,只需要根据业务需要判断链间的灰度策略,重复单链场景的升级即可。 如果是跨链场景,需要根据跨链两端的具体情况制定升级方式。

综上所述,得益于命名控制器合约的版本控制设计,灰度策略可以非常自由的交给业务方选择,而且业务是不知情的,也不需要停止服务。 无缝升级。

3.3 controller合约升级,data合约升级

在这种情况下,它本质上是3.1和3.2两种情况的混搭。

所以,根据具体情况,可以拆开参考3.1、3.2场景方法执行。

4.数据迁移

如3.2节所述,在数据合约升级的场景下,某些情况下需要处理新旧合约之间历史数据的迁移。 迁移的方法有以下三种,各有特点。

4.1 硬编码迁移方式

硬编码迁移方式是指新版本数据合约保存一个指向旧版本数据合约的合约地址,新版本数据合约保存增量数据内容。

这相当于新版本的合约保留了指向旧版本数据的指针。 当新版本需要使用旧数据时,可以直接调用旧数据合约地址对应的数据接口。 这样,新旧版本的数据合约就可以共存了。 即使在异常情况下,数据被误写入旧版本合约,新版本仍然可以访问。

这种方式的优点是:新旧合约可以同时共存,不会增加区块链的存储压力,简单灵活,升级容错性强。 缺点:不断的版本升级会导致形成长链式逻辑关系,维护成本高。

4.2 硬拷贝迁移方法

硬拷贝迁移方式是指切断新旧版本之间的逻辑关系,利用外部迁移工具将旧版本数据逐步复制到链下,然后从链下重新存储的过程-链到新版本的合约。

以太坊智能合约转不出去币_siteqq.com 以太坊智能合约_以太坊智能合约的应用

这种方法的优点是:没有历史包袱。 缺点是:大大增加了区块链的存储压力; 数据迁移工具需要适应不同的数据契约,开发成本高; 迁移过程中需要停止服务,否则容易出现脏数据; 当数据量大时,时间长,操作复杂,容易出错,基本无法操作。

4.3 Merkle树迁移方法

默克尔号码迁移方法的要点如下:

利用智能合约语言面向对象的继承特性,新版合约存储结构完全兼容旧版合约存储结构。

利用区块链上智能合约的存储树原理,新版本合约的存储树直接来源于旧版本合约。 不需要明确的迁移过程。

利用区块链交易的原子性,可以原子地完成新版本合约的部署、数据迁移和升级。

该方法具有前两种方法的所有优点,简单、高效、安全、实用。 缺点:需要区块链底层功能特性的支持。