Ethereum’da Event ve Loglara Giriş

Yazının orijinali için https://media.consensys.net/technical-introduction-to-events-and-logs-in-ethereum-a074d65dd61e

Eventler ve loglar Ethereum için önemlidir çünkü akıllı kontratlar ve kullanıcı arayüzleri arasındaki iletişimi kolaylaştırırlar. Geleneksel web geliştirmede, server cevabı önyüze bir callback ile iletilir. Ethereum’da bir transaction kazıldığında akıllı kontratlar event gönderip blokzincirine log yazabilir ve sonrasında önyüzde loglar işlenebilir. Event ve logları incelemenin farklı yolları vardır. Bu teknik giriş yazısı, eventlerle ilgili bazı kafa karışıklıklarını giderip örnek kodları inceleyecek.

Eventler farklı şekillerde kullanıldığından kafa karıştırıcı olabilir. Bir event bir diğerine benzemeyebilir. Genel itibariyle event ve logların üç ana kullanım alanı vardır:

1. Kullanıcı arayüzünün erişimi için akıllı kontrat return değerlerini elde etmek

2. Asenkron tetikleyici

3. Daha ucuz depolama alanı

Eventler ve loglar arasındaki terminoloji kafaları karıştıran bir diğer husus ve bunu depolama alanıyla ilgili kısımda açıklayacağız.

1) Kullanıcı arayüzü için akıllı kontrat return değerleri

Eventlerin en basit kullanım şekli kontratlardan return değerlerini uygulamanızın önyüzüne çekmektir. Problemi özetlemek gerekirse,

contract ExampleContract {
  // some state variables ...
  function foo(int256 _value) returns (int256) {
    // manipulate state ...
    return _value;
  }
}

“exampleContract”, “ExampleContract” ın bir instance’ı olmak üzere, web3.js kullanan bir önyüzde kontrat simüle edilerek return değeri elde edilebilir:

var returnValue = exampleContract.foo.call(2);
console.log(returnValue) // 2

Lakin kontrat çağrısı web3.js kullanılarak bir transaction olarak gönderildiğinde return değeri elde edilemez[1]:

var returnValue = exampleContract.foo.sendTransaction(2, {from: web3.eth.coinbase});
console.log(returnValue) // transaction hash

sendTransaction metodunun return değeri oluşan transaction’ın hash’idir. Transaction’lar önyüze kontrat değeri döndürmezler çünkü kazılıp blokzincire eklenmeleri için bir parça süre gerekir.

Bu problem için önerilen çözüm, event kullanmaktır ve aslında eventlerin kullanılmasının sebeplerinden birisi bu problemdir.

contract ExampleContract {
  event ReturnValue(address indexed _from, int256 _value);
function foo(int256 _value) returns (int256) {
    ReturnValue(msg.sender, _value);
    return _value;
  }
}

Ardından önyüz return değerini elde edebilir:

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” yu çağıran transaction kazıldığında “watch” içindeki callback tetiklenir. Bu şekilde önyüzde “foo” nun return değeri elde edilir.

2) Asenkron tetikleyici

Return değerleri eventleri kullanmanın en basit yolu demiştik. Aslında eventler genel olarak veri tutan asenkron tetikleyiciler olarak düşünülebilir. Bir kontrat, önyüzü tetiklemek istediğinde bir event yollar. Önyüz eventi aldığında birşeyler yapabilir, mesaj kutusu gösterebilir vs. Bir sonraki bölümde buna bir örnek verdik.

3) Ucuz depolama

Üçüncü kullanım alanı, yani eventleri oldukça ucuz bir çeşit depolama alanı olarak kullanmak, öncekilerden oldukça farklı bir yaklaşım. Ethereum Sanal Makinesi(EVM) ve Ethereum Yellow Paper[2]’ında eventler log olarak geçer (LOG opcode’ları vardır). Depolamadan bahsederken “eventlerde depolanan veri” demek yerine “loglarda depolanan veri” demek teknik açıdan daha uygundur. Buna karşın protokolde bir üst seviyeye çıkarsak, “kontratlar eventleri gönderir veya tetikler” demek, “kontratlar logları gönderir veya tetikler demekten” daha doğrudur. Bir event gönderildiğinde, ona karşılık gelen loglar da blokzincirine yazılır. Eventler ve logların terminolojisi kafa karıştırıcıdır çünkü hangi kavramı kullanmamız gerektiğini bağlam belirler.

Loglar, kontrat depolama alanlarına göre çok daha az gaz harcayan depolama alanları olarak dizayn edilmiştir. Loglarda depolanan byte başına 8 gaz ödenirken[3], kontrat depolaması 32 byte başına 20,000 gaz harcar. Her ne kadar loglar çok büyük miktarda gaz tasarrufu sağlasa da unutmamak gerekir ki loglara herhangi bir kontrattan erişmek mümkün değildir[4].

Yine de logları önyüz için tetikleyici olarak değil de ucuz depolama alanı olarak kullandığımız durumlar vardır. Örnek olarak önyüz tarafından işlenecek geçmiş verisini saklamak diyebiliriz.

Bir kriptopara borsası kullanıcısına borsadaki bütün mevduat geçmişini göstermek istiyor olabilir. Mevduat detaylarını bir kontratta tutmak yerine loglarda depolamak çok daha ucuzdur. Bunu yapmak mümkündür çünkü borsa mesela kullanıcısının bakiyesini bilmek zorundayken(ve bu yüzden bu bilgiyi kontratta depolarken) geçmiş mevduat ayrıntılarını bilmek zorunda değildir.

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

Kullanıcı para yatırdıkça kullanıcı arayüzünü güncellemek istiyoruz diyelim. İşte bir eventi(Deposit) asenkron tetikleyici olarak kullanan bir örnek (_market, msg.sender, _amount, now). “cryptoExContract”, “CryptoExchange” sınıfının bir instance’ı olmak üzere:

var depositEvent = cryptoExContract.Deposit({_sender: userAddress});
depositEvent.watch(function(err, result) {
  if (err) {
    console.log(err)
    return;
  }
  // append details of result.args to UI
})

Bir kullanıcıya has bütün eventleri almayı daha verimli kılmak için _sender parametresi indekslidir: ( uint256 indexed _market, address indexed _sender, uint256 _amount, uint256 _time)

Varsayılan olarak, eventleri dinleme işi ancak event instantiate edildiğinde başlar. Başlangıçta kullanıcı arayüzü yüklenirken kendisine hiç mevduat yoktur. Bu yüzden blok 0’dan itibaren eventleri getirmek isteriz ve bunu event’e bir “fromBlock” parametresi ekleyerek yaparız.

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

Kullanıcı arayüzü yüklendiğinde depositEventAll.stopWatching() çağırılmalıdır.

Netice

Eventler için üç farklı kullanım şekli anlattık. İlki, bir event kullanarak basitçe sendTransaction() ile çağırılmış bir kontrat fonksiyonunun return değerini elde etmek. İkincisi, eventleri, kullanıcı arayüzü gibi bir gözleyiciyi haberdar edebilecek asenkron tetikleyici olarak kullanma. Üçüncüsü, eventleri kullanarak ucuz bir depolama için blokzincirine log yazma. Bu giriş yazısı eventlerle çalışmaya yarayan bazı API’ları[5] gösterdi. Eventlerle, loglarla ve transaction receiptleriyle çalışmak için geliştirilmiş başka yaklaşımlar da mevcuttur ve bu konular ileride başka makalelerde ele alınabilir.

– Joseph Chow.

Makaleye verdikleri geribildirim ve destek için Aaron Davis, Vincent Gariepy ve Joseph Lubin’e teşekkürler.

Referanslar

[1] Alternatif olarak web3.js ile, transaction’ın blokzincire eklendiğini kontrol edip sonra da bir EVM instance’ı kullanarak lokalde transaction’ı tekrar edebilir ve return değerini böylece öğrenebiliriz. Lakin bu seçenek web3.js hazır fonksiyonları yanında koda ciddi miktarda logic eklemeyi gerektirir.

[2] https://github.com/ethereum/yellowpaper

[3] LOG operasyonu başına 375 gaz ve topic başına 375 gaz gibi harcamalar da mevcuttur ama çokça byte’ı depoladığımızda bu miktarlar hesaplamada ihmal edilebilir düzeye iner.

[4] Loglar için Merkle kanıtları kullanmak mümkündür. Yani mesela dışarıdan birisi kontrata böyle bir kanıt verirse, mevzubahis logun blokzincirdeki varlığını kontratın tahkik etmesi mümkündür.

[5] https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethfilter

[6] http://ethereum.stackexchange.com/questions/1381/how-do-i-parse-the-transaction-receipt-log-with-web3-js
  • Ahmet Bilal Uçan

One Comment

  • Taner Dursun

    Herkes blokzincirde depolanan transaction ve bunları içeren bloklara odaklanmışken az bilinen event ve log kavramları, özellikle blokzincir için GUI içeren istemci uygulamalarınca sıklıkla kullanılır. Faydalı bir yazı. Teşekkürler.

Leave a Comment

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir