from:
https://medium.com/taipei-ethereum-meetup/solidity%E6%92%B0%E5%AF%AB%E6%99%BA%E8%83%BD%E5%90%88%E7%B4%84%E8%88%87%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85-%E4%BA%8C-dd915bdeafa0
這篇將介紹以太坊的log資料及event的使用
類似於比特幣的OP_RETURN,以太坊也提供一個把資料永久寫入區塊鏈裡的方法 — event,event所寫入的資料會被記錄在一個Receipt資料裡。
每一筆transaction都會有一個對應的Receipt,用來記錄這筆transaction的執行結果
eth.getTransaction()
getTransaction可以得到這筆transaction的相關資料,這些資料在transaction被製作出來的時候就有了,而Receipt則是直到被執行完(也就是transaction被放進鏈裡)後才會有。
註:(1)gas是指這次提供的gas總量
(2)input是合約的code(這是一個部署合約的transaction,所以input就是整份合約的code)。
用getTransacitonReceipt("hash")來取得transaction對應的Receipt:
eth.getTransactionReceipt()
註:(1)gasUsed是這筆transaction所花費的gas(我們提供了4700000,只花了118615,剩下的會退還給我們)
(2)logs則是我們這篇介紹的log,如果有event被觸發,資料就會被入在這
那什麼時候會需要用到event呢?
1. 當作一個額外的儲存空間,而且很便宜。event寫入的成本和用合約變數來儲存的成本相比之下少了很多,如果你開發的dapp需要將使用者的使用紀錄(如付款紀錄)等記錄下來當作證明,與其用一個陣列儲存,不如在每次使用時用event寫進log裡。
但要注意的是,這些寫進log裡的資料是沒辦法被合約所存取的。
2. 當成return value來使用。合約裡函式的回傳值並非總是可以使用,假設一個有回傳值的函式:
contract f00{
function foo(int _value) returns (int) {
return _value * _value;
}
}
什麼時候你可以拿到這個回傳值?只有在你使用call在本地進行模擬的時候才會有回傳值,如下:
var ret = f00.foo.call(25);
console.log(ret); //625
當你使用sendTransaction,真的做出一筆交易的時候,他會回傳你這筆transaction的hash值,所以這時候你可以使用event來將回傳值記錄起來:
contract f00{
event retValue(int _value);
function foo(int _value) returns(int) {
return _value * _value;
}
}
這時候在前端便可以利用像javascript那樣監聽的功能:
var retValueEvent = f00.retValue();
retValueEvent.watch(function(err, result){
if(err){
console.log(err);
return;
}
console.log(result.args._value);
});
當transaction被收入區塊鏈裡後,就會觸發監聽器然後按照你設定的callback函式執行對應的動作。如果要結束監聽,執行 retValueEvent.stopWatching()。
3. 最後便是當你開發dapp的時候,藉由觸發event寫入log,再觸發監聽器執行對應動作,如此完成從 外界->鏈->鏈->外界 一個完整的執行過程。對使用者來說就像一個是對資料庫操作的動作,只是這個資料庫變成了區塊鏈。
接下來以簡單的例子來介紹監聽器更多的功能
contract depositAccount {
event Deposit(addressindexed_owner, uint _amount, uint _time);
function deposit() payable {
Deposit(msg.sender, msg.value, now);
}
}
首先先介紹第二行的indexed。
在Receipt裡如果有log,會寫在logs欄位裡,每筆log其中有兩部分:data和topics。一般event寫入的資料都會寫在data裡,但如果在event的變數加入一個indexed屬性,到時候觸發時這個變數對應寫入的值就會寫在topics裡,在topcis裡的值可以用來當作監聽器的篩選條件。
註:一份Receipt裡面可以有很多筆log(表示一次transaction可以觸發很多次event),一筆log最多只能儲存四個topics,而第一個topic必須是這個event的識別值identifier,代表一個event最多只能有三個變數可以有indexed屬性。
transaction的logs裡的其中一筆log
上面這張圖是其中某一筆log,這個event沒有加indexed的變數,值都會寫在data欄位裡(32byte為一單位接在一起,圖中為十六進位的9和19),topcis裡唯一一個值 0x35bd26...是該event的識別值。
下面這張圖是變數都加上indexed的event的log記錄:
event有indexed
因為所有變數都加上indexed,所以data裡沒有值,值都寫在topics裡(十六進位的102和abcde)。
接下來在前端加入監聽器:
var depositEvent = depositAccount.Deposit({_owner:null},{fromBlock: 5000, toBlock: ‘latest’});
depositEvent.watch(function(err, results) {
if (err) {
console.log(err);
return;
}
console.log(results.blockNumber);
});
第一行的{fromBlock: 5000, toBlock: ‘latest’}是加入的篩選條件,表示監聽從第5000個區塊開始到最新的區塊。如果event被觸發,那callback函式就會印出是發生在第幾個區塊( result.blockNumber )。
如果我們要用有加上indexed的變數(_owner變數)來當篩選條件的話,就指定_owner應該要是多少,如果給null(像上面的例子),那就是任何address都可以的意思:
var depositEvent = depositaccount.Deposit({_owner:'0xbd7255b64eeb594ca57652c94249da6a9b37cd2f'});
depositEvent.watch(function(err, results) {
if (err) {
console.log(err);
return;
}
if(results.args._amount > web3.toWei(0.05,"ether")){
console.log("User: " + results.args._owner +
" deposits: " + results.args._amount +
" on: " +
JSON.stringify(new Date((parseInt(results.args._time)+28800)*1000))
);
}
});
這個callback會在"0xbd7255b64eeb594ca57652c94249da6a9b37cd2f"這個address存錢的時候被觸發,存入超過0.05 ether的時候會印出存入的金額和存入時間。
References:
[1]https://media.consensys.net/technical-introduction-to-events-and-logs-in-ethereum-a074d65dd61e#.u7l9ejta3
[2]https://github.com/ethereum/wiki/wiki/JavaScript-API#contract-events