智能合约事件的正确用法【Solidity实战】

在开发区块链应用时,如果把智能合约当成数据库使用就错了 —— 不幸的是, 我刚刚意识到这一点,意识到在状态变量中只应该保存Solidity智能合约需要使用的数据, 而其他数据都应该利用智能合约的事件机制转储到外部处理。在我们开发的去中心化 金融平台上,就是利用事件实现了金融交易的历史追溯,因此相信这篇文章对你 开发以太坊区块链应用会有帮助。

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

有一个问题一直让我困惑,那就是在公链上数据到底意味着什么?如何平衡 数据的公开透明与隐私之间的关系?

我的结论是,在以太坊区块链上,只把Solidity智能合约要使用的数据保存在状态变量里, 任何你需要存档的数据,都应当利用事件转储出来。

在这篇文章中,我们将涉及以太坊的底层架构、区块链的开发准则以及一些Solidity代码, 学习如何跟踪报告区块链的状态变化,如何简化你的智能合约,以及如何正确地 公开数据。本文涉及的智能合约事件缓存源代码,可以从Github下载。

跟踪数据的正确姿势

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

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

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

基于交易历史构造的数据库可以随时随地按需重建,因为原始的数据始终都在 区块链上,这样智能合约就不需要保存那些以存档为目的的数据。于是我请一个好朋友 Bernardo帮我搜罗一下可以构造交易历史数据库的工具。

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

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

使用智能合约的事件

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

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

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

改进的智能合约开发模式

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

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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];
}
}

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

1
2
3
4
5
6
7
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%的代码是智能合约。现在实现 可追溯性需要的更少了。新的开发准则让我们有了更简洁的代码。


原文链接:Smart Contracts are not Databases

汇智网翻译整理,转载请标明出处