Solidity的event的支持指引(二十二)|入门系列

2017/5/28 posted in  Solidity入门系列

这篇文章是来自ConsenSys1。事件和日志在以太坊中异常重要。因为它是合约之间,合约与终端间的沟通桥梁。在传统的Web开发中,服务器通过前端回调函数返回结果(这里更多的想说明的是结果同步返回)。在以太坊中,当交易打包后,某个事件才真正发生,合约将这个事件写入区块链后,前端才能进行对应的响应。有各种方式来跟踪事件和日志。这个技术指引将会解释一些关于事件的困惑,以及对应的解决方案示例。

因为事件可以在不同的方式中使用,所以带来了一些困惑。不同的事件使用上有很大不同,但主要有三种主要的日志和事件方式。

  • 智能合约返回给用户界面的值
  • 异步的数据触发行为
  • 事件是一种低gas消耗的storage

术语事件和日志是另一种引发混乱的源头,我们将在第三个用户场景中解释它们。

1)智能合约返回给用户界面的值

事件最简单的用法是传递合约的返回值给前端。下面是一个简单的说明代码:

contract ExampleContract {
  // some state variables ...
  function foo(int256 _value) returns (int256) {
    // manipulate state ...
    return _value;
  }
}

假设在使用web3.js的前端中,exampleContractExampleContract的一个实例,我们可以通过下述合约执行得到返回值:

var returnValue = exampleContract.foo.call(2);
console.log(returnValue) // 2

然而,当我们用web3.jstransaction的方式发起调用,我们发现我们不能立即得到返回值2

var returnValue = exampleContract.foo.sendTransaction(2, {from: web3.eth.coinbase});
console.log(returnValue) // transaction hash

sendTransaction()方法的返回值是创建的交易哈希值。因为交易不是立即打包进区块链中的,所以交易不能立即返回一个合约值给前端。

推荐的解决方案是使用事件。这也是事件的设计目标之一。对于下述合约:

contract ExampleContract {
  event ReturnValue(address indexed _from, int256 _value);
function foo(int256 _value) returns (int256) {
    ReturnValue(msg.sender, _value);
    return _value;
  }
}

前端可以通过下述代码得到返回值:

var exampleEvent = exampleContract.ReturnValue({_from: web3.eth.coinbase});
exampleEvent.watch(function(err, result) {
  if (err) {
    console.log(err)
    return;
  }
  console.log(result.args._value)
  // check that result.args._from is web3.eth.coinbase then
  // display result.args._value in the UI and call    
  // exampleEvent.stopWatching()
})
exampleContract.foo.sendTransaction(2, {from: web3.eth.coinbase})

当调用foo()函数的交易被打包了,回调中的watch将会触发。这将允许前端有效的得到foo()函数的返回值。

2)异步的数据触发行为

返回值是事件的最小用例,事件通常被认为是一个异步的数据事件触发器。当合约希望与前端交互,合约可以发起一个事件。比如,前端监听某个事件发生后,进行某个行为,显示消息等。下一节中将提供一个这样的例子(当用户存款后,UI进行对应的更新)。

3)事件是一种低gas消耗的storage

第三种使用场景与前两种完全不同。事件是一种低gas消耗的storage(强调它作为日志数据库的这层属性)。在EVM和以太坊的黄皮书中3,事件被认为是日志(对应的的是LOG操作码)。当说到存储时,将数据存到日志中比将数据存到事件这种说法,技术上更加准确。然而,当我们在协议这个层面上时,合约发起,或触发一个前端可以响应时,使用事件则更为确切。无论何时事件发生,对应的日志将被写入区块链。事件和日志由于上下文不同,带来了一些混淆,大家可以取决实际情况决定用哪个词。

日志是一种相比合约存储的一种低成本存储。日志每字节花费8gas4,然而合约存储每32字节花费20000gas。虽然日志非常省gas,但在任何合约中均不可直接被访问5

然而,有些情形下,我们可以使用日志做为一种省钱的存储手段,而不仅仅用来触发前端行为。一种是用存储用于前端渲染的历史数据。

一个加密币的交易所,可以用来展示某个用户的所有存款记录。使用日志的方式相比直接存储到合约中,gas花费低很多。上述实现是可行的,因为交易所往往只需要在合约中存储用户当前的余额(这个比较重要),但不一定需要历史存款的详情。

contract CryptoExchange {
  event Deposit(uint256 indexed _market, address indexed _sender, uint256 _amount, uint256 _time);
function deposit(uint256 _amount, uint256 _market) returns (int256) {
    // perform deposit, update user’s balance, etc
    Deposit(_market, msg.sender, _amount, now);
}

假设我们要在用户存款后更新UI界面上的展示。下面是一个使用事件做为异步数据触发器的例子,数据有_marketmsg.sender_amountnow。其中cryptoExContractCryptoExContract的一个实例:

var depositEvent = cryptoExContract.Deposit({_sender: userAddress});
depositEvent.watch(function(err, result) {
  if (err) {
    console.log(err)
    return;
  }
  // append details of result.args to UI
})

事件中,我们对_sender标记为了indexed。原因是因为为了提高查找事件的性能。

默认情况下,当事件发生时,我们才开始监听。但当UI首次加载时,由于没有事件发生,没有对应的存款记录需要展示。所以为了展示用户的历史存款记录,我们需要从0号区块开始搜索用户的事件,这可以通过增加一个fromBlock参数来实现。

var depositEventAll = cryptoExContract.Deposit({_sender: userAddress}, {fromBlock: 0, toBlock: 'latest'});
depositEventAll.watch(function(err, result) {
  if (err) {
    console.log(err)
    return;
  }
  // append details of result.args to UI
})

UI加载完成后,切记调用depositEventAll.stopWatching()来去掉监听。

其它-索引参数

最多可以有三个参数是索引的。举例来说,基本的代币行为有event Transfer(address indexed _from, address indexed _to, uint256 _value)。所以为了有效的监听转帐行为,你可以:

  • 监听发送帐号tokenContract.Transfer({_from: senderAddress})
  • 监听收款帐号tokenContract.Transfer({_to: receiverAddress})
  • 同时监听发送,收款帐号tokenContract.Transfer({_from: senderAddress, _to: receiverAddress})

结语

上面说明了三种使用事件的场景。一,通过事件获取以sendTransaction()方式进行调用的返回值。二,使用事件作为一个异步的数据触发器,来通知观察者,比如UI。三,将事件记录为日志,以一种低成本的存储的形式使用。这个技术指引也使用了一些与事件相关的API6,不过还有其它处理事件,日志和收据7的方法,这将在未来的文章中提及。

-Joseph Chow. Thanks to Aaron Davis, Vincent Gariepy, and Joseph Lubin for feedback on this article.

关于作者

专注基于以太坊(Ethereum)的相关区块链(Blockchain)技术,了解以太坊,Solidity,Truffle,web3.js。

个人博客: http://tryblockchain.org
版权所有,转载注明出处

参考资料


  1. 原文翻译自这里。https://media.consensys.net/technical-introduction-to-events-and-logs-in-ethereum-a074d65dd61e 

  2. web3.js可以监视该交易是否被打包在区块链中,然后在某个EVM实例中重放该交易,以获取返回值,但这会在web3.js中增加大量逻辑。 

  3. https://github.com/ethereum/yellowpaper 

  4. 单次LOG指令操作,gas为375,单个topic为375gas,但由于可存比较多的信息,这些成本平均下来就显得微不足道了。 

  5. 日志的Merkle证明是可能的,所以在区块链外,如果能提供这样一种证明,可以证明日志确实存在于区块链上。 

  6. https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethfilter 

  7. http://ethereum.stackexchange.com/questions/1381/how-do-i-parse-the-transaction-receipt-log-with-web3-js 

友情链接: 区块链技术中文社区