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();
});
}
}
});