solidity学习笔记(10)—— 事件、日志与交互(含实例)

事件是以太坊EVM提供的一种日志基础设施。事件可以用来做操作记录,存储为日志。也可以用来实现一些交互功能,比如通知UI,返回函数调用结果等。

总的来说:事件就是当区块链某个函数被调用或执行的时候,被触发从而被前端获取或者记录到日志中的对象。

一、事件的实现

事件的实现是在合约对象中,分两步:
1、定义事件类型
2、实例化事件对象

代码:

pragma solidity ^0.4.19;
contract ZombieFactory {
    // 定义事件类型
    event NewZombie(uint zombieId, string name, uint dna);
​
    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;
​
    struct Zombie {
        string name;
        uint dna;
    }
​
    Zombie[] public zombies;
​
    function _createZombie(string _name, uint _dna) private {
        uint id = zombies.push(Zombie(_name, _dna)) - 1;
        // 实例化事件对象
        NewZombie(id, _name, _dna);
    }
}

二、事件与交互

我们在前端使用web3.js来与区块链进行交互。当智能合约中的函数被调用而更改了区块链中的数据后,前端如何实时进行相应的行为?

1、调用合约,生成一个可以访问公共函数和事件的合约对象;
2、监听事件,调用事件方法,异步获取事件返回的值,error或者result;
3、判断并执行相应的前端函数;
4、注意:在操作执行完成后,我们要记得调用event.stopWatching();来终止监听。

代码:

// 下面是调用合约的方式:
var abi = /* abi是由编译器生成的 */
var ZombieFactoryContract = web3.eth.contract(abi)
var contractAddress = /* 发布之后在以太坊上生成的合约地址 */
var ZombieFactory = ZombieFactoryContract.at(contractAddress)
// `ZombieFactory` 能访问公共的函数以及事件
​
// 监听 `NewZombie` 事件, 并且更新UI
var event = ZombieFactory.NewZombie(function(error, result) {
  if (error) return
  generateZombie(result.zombieId, result.name, result.dna)
})
​
// 获取 Zombie 的 dna, 更新图像
function generateZombie(id, name, dna) {
  // 新建一个Zombie的图像
  ...
}

三、事件与日志

如上所说,事件是以太坊EVM提供的一种日志基础设施,日志是区块链中的一种特殊数据结构。

当定义的事件触发时,我们可以将事件存储到EVM的交易日志中,日志与合约关联,与合约的存储合并存入区块链中。只要某个区块可以访问,其相关的日志就可以访问。但在合约中,我们不能直接访问日志和事件数据(即便是创建日志的合约)。

web3.js监听事件,实际上是对EVM的交易日志的监听。

所以,当我们需要对事件日志进行条件性的过滤,即只在满足某些条件的情况下才执行前端的函数,要如何进行?

检索日志:indexed属性的使用

可以在事件参数上增加indexed属性,最多可以对三个参数增加这样的属性。加上这个属性,可以允许你在web3.js中通过对加了这个属性的参数进行值过滤,方式如下:

var event = myContract.transfer({value: "100"});

上面实现的是对value值为100的日志,过滤后的返回。

如果你想同时匹配多个值,还可以传入一个要匹配的数组。

var event = myContract.transfer({value: ["99","100","101"]});

增加了indexed的参数值会存到日志结构的Topic部分,便于快速查找。
未加indexed的参数值会存在data部分,成为原始日志。

需要注意的是,如果增加indexed属性的是数组类型(包括string和bytes),那么只会在Topic存储对应的数据的web3.sha3哈希值,将不会再存原始数据。因为Topic是用于快速查找的,不能存任意长度的数据,所以通过Topic实际存的是数组这种非固定长度数据哈希结果。要查找时,是将要查找内容哈希后与Topic内容进行匹配,但我们不能反推哈希结果,从而得不到原始值。

所以如果你要实现过滤还要获得原始值,那就不要把indexed加到string和bytes类型参数前面。

使用web3.js读取事件的完整例子

下面是一个使用以太坊提供的工具包web3.js访问事件的完整例子:

let Web3 = require('web3');
let web3;
​
if (typeof web3 !== 'undefined') {
    web3 = new Web3(web3.currentProvider);
} else {
    // set the provider you want from Web3.providers
    web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
}
​
let from = web3.eth.accounts[0];
​
//编译合约
let source = "pragma solidity ^0.4.0;contract Transfer{ event transfer(address indexed _from, address indexed _to, uint indexed value); function deposit() payable { address current = this; uint value = msg.value; transfer(msg.sender, current, value); } function getBanlance() constant returns(uint) { return this.balance; } /* fallback function */ function(){}}";
​
let transferCompiled = web3.eth.compile.solidity(source);
console.log(transferCompiled);
console.log("ABI definition:");
console.log(transferCompiled["info"]["abiDefinition"]);
​
//得到合约对象
let abiDefinition = transferCompiled["info"]["abiDefinition"];
let transferContract = web3.eth.contract(abiDefinition);

//2. 部署合约
//2.1 获取合约的代码,部署时传递的就是合约编译后的二进制码
let deployCode = transferCompiled["code"];
//2.2 部署者的地址,当前取默认账户的第一个地址。
let deployeAddr = web3.eth.accounts[0];
​
//2.3 异步方式,部署合约
//警告,你不应该每次都部署合约,这里只是为了提供一个可以完全跑通的例子!!!
transferContract.new({
    data: deployCode,
    from: deployeAddr,
    gas: 1000000
}, function(err, myContract) {
    if (!err) {
        // 注意:这个回调会触发两次
        //一次是合约的交易哈希属性完成
        //另一次是在某个地址上完成部署
        // 通过判断是否有地址,来确认是第一次调用,还是第二次调用。
        if (!myContract.address) {
            console.log("contract deploy transaction hash: " + myContract.transactionHash) //部署合约的交易哈希值
            // 合约发布成功后,才能调用后续的方法
        } else {
            console.log("contract deploy address: " + myContract.address) // 合约的部署地址
            console.log("Current balance: " + myContract.getBanlance());
            var event = myContract.transfer();
            // 监听
            event.watch(function(error, result){
              console.log("Event are as following:-------");
              for(let key in result){
                console.log(key + " : " + result[key]);
              }
              console.log("Event ending-------");
            });
            //使用transaction方式调用,写入到区块链上
            myContract.deposit.sendTransaction({
                from: deployeAddr,
                value: 100,
                gas: 1000000
            }, function(err, result){
              console.log("Deposit status: " + err + " result: " + result);
              console.log("After deposit balance: " + myContract.getBanlance());
              //终止监听,注意这里要在回调里面,因为是异步执行的。
              event.stopWatching();
            });
        }
    }
});

 

你可能感兴趣的:(Solidity)