1. 程式人生 > >solidity學習筆記(10)—— 事件、日誌與互動(含例項)

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