智能合约不是数据库 - 事件应该这么用!

  • 影无双
  • 更新于 2020-04-16 14:48
  • 阅读 8354

如果合约中没有使用该变量,请用事件存储数据

任何把智能合约当成数据库使用的工程师其实都搞错了,其实我也是直到不久前才意识到这一点。

开发区块链解决方案需要以不同的思维考虑数据、控制和隐私问题。其中有些技术细节比较容易掌握,例如数字货币基本上可以理解为一个账户余额表再加上一些操作余额的方法。但有些技术细节则更复杂一些,例如对自己构建的解决方案几乎没有控制的能力。

公链中的数据都是公开的, 如何兼顾数据隐私,而又准确提供历史交易记录,我发现了一个非常优雅解决方案: 状态变量中只应该保存Solidity智能合约需要使用的数据, 而其他数据都应该利用智能合约的事件机制转储到外部

在这篇文章,我将涉及到一些以太坊底层架构,一些区块链开发准则以及一些代码。 借助这些工具,我将向您展示如何跟踪和报告区块链状态变化,如何简化智能合约以及在不引起注意的情况下将数据公开。

需要一个更好的方式跟踪数据

在去中心化金融平台上,我们需要提供完整的交易历史。

像MiFID II这样的规定要求金融平台必须在监管机构要求时提供用户完整的金融交易历史, 这不仅包含那些代币转账交易,还包括任何引发区块链状态变化的用户动作。

最初的研究让我产生了这样的思考:每一个状态变化都记录在区块链上,因此一定有办法通过遍历区块来提取交易历史。这个解决方案听起来比将交易相关的数据保存在智能合约里要优雅一些,因为经验告诉我智能合约的功能越少越好。

基于交易历史构造的数据库可以随时随地按需重建,因为原始的数据始终都在区块链上,这样智能合约就不需要保存那些以存档为目的的数据。于是我请一个好朋友 Bernardo Vieira帮我搜罗一下可以构造交易历史数据库的工具(他知道大家都在做什么,常常找到我们可以使用的东西)。

我一直都没有想到用 Solidity事件来解决这个问题,直到有一天Bernardo跟我说:如果用区块链触发事件的方式来记录,会不会解决你的问题?

的确是的,这一下子打开了我的思路。

使用合约事件(Event)

在此之前我一直没有仔细思考以太坊的事件机制。我知道在合约代码中,当区块链状态变化时,可能会触发一个事件而不是返回一个值。我也学过如何在前端代码中捕捉这些事件以检索结果,不过对于以太坊Solidity合约中事件的具体运作,我实际上并不太清楚。

当你从智能合约中触发事件时,这个事件就以半结构化的格式记录在区块链上,其中有些字段是保持不变的,而你为事件定义的参数值将出现在附加字段中。事件是以太坊中gas成本最低的操作之一,一次触发大约消耗4000 gas。

一开始我们使用了TheGraph,它实现了我们期望的功能: 捕捉区块链事件并提供一个查询事件的GraphQL接口。不过对于我们的应用而言,利用 theGraph最终会让整个解决方案太复杂,但是TheGraph的这些查询让我意识到区块链上的事件实际上变成了一个只写的数据库(只添加数据)。有些字段在所有事件中都存在,你也可以定义自己的字段。

如何改进软件开发

Soldity合约的事件听起来很容易理解,但是这对于我们如何实现解决方案有着深远的影响。第一个变化就是采纳了这个开发准则:

所有的状态变化必须触发一个事件

通过确保所有的状态变化都产生事件,我们就不需要担心交易的可追溯性问题。所有的状态变化都以事件的形式转储,我们只需要在前端按需提取信息。

但是隐私数据怎么办?实际上,数据从交易中始终是可以恢复出来的,我们只不过让其利用更简单而已。

记住,公链上的所有数据都是公开的

如果你之前读过我的其他文章,你可能会注意到我总是尽力找到解决问题的最简单的办法。下面这个简单的文档登记代码也是在不久前我在对它满意:

pragma solidity ^0.5.10;

contract DocumentRegistry {
  event Registered(uint256 hash);
  mapping (uint256 => uint256) documents;

  function register(uint256 hash) public {
    documents[hash] = msg.sender;
    emit Registered(hash);
  }

  function verify(uint256 hash) public view returns (uint256) {
    return documents[hash];
  }
}

很简单,其实还可以更简单更高效:

pragma solidity ^0.5.10;

contract DocumentRegistry {
  event Registered(uint256 hash, address sender);

  function register(uint256 hash) public {
    emit Registered(hash, msg.sender);
  }
}

注意着第二个合约实际上没有任何变量存储内容,也没有提供函数来获取数据。但是它的确实现了同样的功能

我们通常认为事件是转瞬即逝的,而区块链上的内容都是永久性的,包括事件。如果你希望知道上面合约里一个文档是否注册,只需要计算其哈希,然后在缓存上查询。

唯一要做的是需要维护一个外部基础设施来跟踪事件并在链下模仿区块链的状态。这不仅相对简单,而且在性能上也可能是最好的方案。

在这个starter-kit中我们 实现了一个简单的事件缓存,请知晓,因为这是我们现在在所有项目中使用的工具,并且应该会迅速改进。

结论

对事件的重新思考带来的变化是深远的,突然之间需要保存在智能合约中的信息与临时性触发的信息之间,出现了一个清晰的界限。

仅在状态变量中保存智能合约需要使用的数据,否则就利用事件处理。

我过去说在任何区块链解决方案中只有10%的代码是智能合约。新的开发准则让我们有了更简洁的代码。

在接下来的几个月中,我们将继续使用本文中开发准则去完善我们使用的工具来编写智能合约代码。 请关注我们的Repo

All that data, neatly stored.

原文链接:Smart Contracts are not Databases

点赞 3
收藏 2
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

1 条评论

请先 登录 后评论
影无双
影无双
江湖只有他的大名,没有他的介绍。