Events和logs在ethereum中十分重要,因为他们租金了智能合约和其用户接口之间的沟通。在常规的web开发中,在前端的回调中提供服务器相应。在ethereum中,当一个交易被矿工添加,智能合约可以激活事件events并将日志log写入前端可以处理的区块链。有不同的方法可以处理events和logs。这篇简要介绍会解释有关events的一些概念和与他们一起使用的一些实例代码。
Events可能会令人困惑,因为它们可以用不同的方式使用。一个event可能和另一个event完全不同。可以归纳为有三种主要的events和logs的实例:
- 智能合约对用户接口返回值;
- 异步触发和数据相关的事件;
- 更廉价的存储形式。
为用户接口提供智能合约返回值
最简单的event的使用是将contract中的返回值传递给应用程序的前端。为了说明以下为一个例子:
contract ExampleContract {
// some state variables ...
function foo(int256 _value) returns (int256) {
// manipulate state ...
return _value;
}
}
假设exampleContract是ExampleContract的一个实例(instance),前端交互使用web3.js,可以使用以下代码获取函数返回值:
var returnValue = exampleContract.foo.call(2);
console.log(returnValue) // 2
但是,如果web3.js将这个函数提交为一笔交易(transaction),它就不能获得返回值:
var returnValue = exampleContract.foo.sendTransaction(2, {from: web3.eth.coinbase});
console.log(returnValue) // transaction hash
sendTransaction的返回值永远都是交易的hash值。交易不会将函数的返回值发送给前端因为交易不是立即被矿工加入到区块中(有一定延迟)。
这是我们建议用event来实现得到函数返回值的功能,这也是使用events的一个目的之一。
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的transaction被miner挖矿时,watch当中的回调函数将会被触发,这允许前端来从foo获得返回值。
异步数据触发器
event通常可以被认为是数据的异步触发器。当一个智能合约想要触发前端时,合约会发出一个event。前端正在使用watch函数观察时间,他可以采取行动,显示消息等。
更廉价的数据存储
第三个用例和上面提到的不同,使用events作为一个更便宜的存储方式。在Ethereum Virtual Machine he Ethereum 黄皮书中,events对应于logs(LOG 字节码)。logs被设计成一个存储方式,比contract storage存储成本低很多。Logs消耗8 gas 每byte,而contract storage消耗20000 gas 每 32 bytes。虽然logs消耗更少,但logs不可以被任何contract所访问。
尽管如此,还是有使用logs作为廉价存储的用例,而不是前端的触发器。一个更合适的logs示例是存储前端可以呈现的历史数据。一笔加密货币交易可能想要显示一个用户的所有交易存款。不是将这些存款的详细信息存储在智能合约中,而是将它们存储为logs,这样会便宜很多。
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);
}
假设我们想在用户进行存款的时候更新用户界面。下面是一个使用event作为数据的异步触发器的示例,假设cryptoExchange是一CryptoExchange的一个示例:
var depositEvent = cryptoExContract.Deposit({_sender: userAddress});
depositEvent.watch(function(err, result) {
if (err) {
console.log(err)
return;
}
// append details of result.args to UI
})
提高为用户获得所有event的效率是event的_sender参数被索引(indexed)的原因
event Deposit(uint256 indexed _market, address indexed _sender, uint256 _amount, uint256 _time)
默认情况下,监听event仅在event实例化时开始。当用户界面第一次加载时,没有追加的存款。所以我们希望从block 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()