Skip to content

Lắng nghe event trong Ethereum với Eventeum

Posted on:May 26, 2020

Trong vài viết Xây dựng Backend cho Decentralized Application (ĐApp) chúng ta cũng đã đề cập tới việc event trong Ethereum là thiếu ổn định. Vì thế ta cần xây dựng một cơ chế có thể lắng nghe, lưu trữ và xử lý event một cách hiệu quả hơn.

Eventeum chính là một giải pháp.

Eventeum là gì

Eventeum là một Ethereum event listener, đóng vai trò cầu nối giữa smart contract event và backend.

Mỗi khi có event xảy ra, thì một message bao gồm toàn bộ detail của event đó sẽ được lưu trữ tại message bus (Kafka hoặc RabbitMQ), sau đó sẽ được xử lý bởi các service phía backend.

Eventeum có mã nguồn mở, được phát triển bởi kauri.io team.

Why Eventeum

Nếu so sánh với việc trực tiếp lắng nghe và trực tiếp xử lý event luôn tại backend thì Eventeum có rất nhiều điểm vượt trội, có thể kể đến như:

Deploying Eventeum

Eventeum support các phương pháp broadcast message sau:

với RabbitMQ ta có thể config thêm các trường sau:

Prerequisites

Trông có vẻ khá rắc rối khi nhiều thứ phải cài cắm, tuy nhiên ta có thể đơn giản hoá việc cài đặt này bằng cách sử dụng Docker như sau:

git clone https://github.com/ConsenSys/eventeum.git
cd eventeum
mvn clean package
cd server
docker-compose -f docker-compose.yml build
docker-compose -f docker-compose.yml up

Ok như vậy là ta đã có Eventeum chạy trong máy của chúng ta. Eventeum sẽ mặc định chạy ở cổng 8060.

Deploy smart contract

Trong bài này ta sẽ sử dụng Remix IDE để viết và deploy contract một cách nhanh chóng.

Blockchain sử dụng là ganache, được chạy bởi ganache-cli tại cổng 8545. Nếu chưa có ganache-cli ta có thể install bằng npm như sau:

npm install -g ganache-cli

và tiến hành chạy chain lên:

ganache-cli

Khi này ta đã có một blockchain chạy tại địa chỉ http://127.0.0.1:8545.

Gas Price
==================
20000000000

Gas Limit
==================
6721975

Call Gas Limit
==================
9007199254740991

Listening on 127.0.0.1:8545

Tiếp theo, ta sẽ chuẩn bị một contract đơn giản như sau:

pragma solidity ^0.5.0;

contract Counter {

    uint256 counter;

    function get() external view returns (uint256) {
        return counter;
    }

    function set(uint256 newValue) external {
        counter = newValue;
        emit CounterUpdated(newValue);
    }

    event CounterUpdated(uint256 newCounter);
}

Trên Remix, tiến hành compile contract, hãy đảm bảo rằng không có lỗi nào xảy ra:

Trước khi deploy, hãy nhớ chọn network là Web3 Provider và chọn địa chỉ http://127.0.0.1:8545 là địa chỉ blockchain chúng ta mới chạy bên trên.

và tiến hành Deploy:

Vậy là giờ ta đã có contract đã được deploy lên blockchain.

Register Event

Tiếp theo chúng ta sẽ đăng ký event với Eventeum, để mỗi khi event xảy ra ra sẽ bắt được event đó, ở đây là event CounterUpdated ta đã định nghĩa trong contract.

để đơn giản, ở đây ta gọi bằng curl, hãy thay đoạn CONTRACT_ADDRESS bằng địa chỉ contract mà ta đã deploy tại Remix ở trên:

curl -X POST \
http://localhost:8060/api/rest/v1/event-filter \
-H 'Cache-Control: no-cache' \
-H 'Content-Type: application/json' \
-H 'Postman-Token: 616712a3-bf11-bbf5-b4ac-b82835779d51' \
-d '{
"id": "CounterUpdatedEvent",
"contractAddress": "CONTRACT_ADDRESS",
"eventSpecification": {
  "eventName": "CounterUpdated",
  "nonIndexedParameterDefinitions": [{"position": 0, "type": "UINT256"}]
  }
}'

nếu thành công ta sẽ thấy message như thế này trong docker log mesages:

registerContractEventFilter - Registered filters: {"CounterUpdatedEvent":{"filter":{"id":"CounterUpdatedEvent","contractAddress":"0x1F5DAf1B8aE9fE2C28Bb8206dF13962906A98db0","node":"default","eventSpecification":{"eventName":"CounterUpdated","indexedParameterDefinitions":[],"nonIndexedParameterDefinitions":[{"position":0,"type":"UINT256"}]}},"subscription":BufferAsyncEmitter{9223372036854775807},"startBlock":0}}

Ta sẽ kiểm tra xem sau khi đã đăng kí với Eventeum rồi thì khi tương tác với contract, event có được bắn ra không bằng cách lên Remix và thực hiện transaction như sau:

Trên docker logs của eventeum, ta nhận được log như sau:

broadcastContractEvent - Sending contract event message: {"id":"0x00d319cdc9cc80de18642f7f2bb1e3701433f2417225d61791266e055fe07541-0x3f2b9dfc3e9dc86aea43dd11ec6482dfdc05cc454c9de81b19e3b5344515e69a-0","type":"CONTRACT_EVENT","details":{"name":"CounterUpdated","filterId":"CounterUpdatedEvent","nodeName":"default","indexedParameters":[],"nonIndexedParameters":[{"type":"uint256","value":123}],"transactionHash":"0x00d319cdc9cc80de18642f7f2bb1e3701433f2417225d61791266e055fe07541","logIndex":0,"blockNumber":12,"blockHash":"0x3f2b9dfc3e9dc86aea43dd11ec6482dfdc05cc454c9de81b19e3b5344515e69a","address":"0x492934308E98b590A626666B703A6dDf2120e85e","status":"UNCONFIRMED","eventSpecificationSignature":"0x4785d80d2593e2cb7a3331d31eb5106408bdde2aab0db9e9b616b036a1b6039d","networkName":"default","id":"0x00d319cdc9cc80de18642f7f2bb1e3701433f2417225d61791266e055fe07541-0x3f2b9dfc3e9dc86aea43dd11ec6482dfdc05cc454c9de81b19e3b5344515e69a-0"},"retries":0}

có nghĩa là việc lắng nghe event của chúng ta đa được cài đặt thành công!

Eventeum có rất nhiều API khác nữa, ta có thể tham khảo thêm tại đây.

Subscribing Eventeum events trong ứng dụng NodeJs

Ta sẽ xây dựng một nodejs ứng dụng nho nhỏ để có thể hình dung ra việc tích hợp Eventeum vào trong app thực tế thế nào.

mkdir watcher
cd watcher
npm init
npm install
npm i kafka-node  (Kafka-nodejs client)
touch index.js

nội dung của index.js rất đơn giản, lắng nghe được gì thì log ra cái đó. Ở đây ta sử dụng topic contract-events, đây là một builtin của Eventeum, nó sẽ check tất cả những topic đã được define bên trong Eventeum.

var kafka = require("kafka-node");
const client = new kafka.KafkaClient({ kafkaHost: "localhost:9092" });
var kafka = require("kafka-node"),
  Consumer = kafka.Consumer,
  consumer = new Consumer(
    client,
    [{ topic: "contract-events", partition: 0 }],
    {
      autoCommit: false,
    }
  );
consumer.on("message", function (message) {
  console.log(message);
});

Tiến hành chạy thêm một transaction nữa trên Remix:

ta sẽ thấy được log của nodejs sẽ như sau:

{ topic: 'contract-events',
  value:
   '{"id":"0xdf064648f5923f3f015f53d464f2d95fce2540b0ab6a1e851e3603ff781b894b-0x204c9b64582e5154fa63f3e32c9b3960ea30a88c6d1f97abe7985a16b2762805-0","type":"CONTRACT_EVENT","details":{"name":"CounterUpdated","filterId":"CounterUpdatedEvent","nodeName":"default","indexedParameters":[],"nonIndexedParameters":[{"type":"uint256","value":123}],"transactionHash":"0xdf064648f5923f3f015f53d464f2d95fce2540b0ab6a1e851e3603ff781b894b","logIndex":0,"blockNumber":11,"blockHash":"0x204c9b64582e5154fa63f3e32c9b3960ea30a88c6d1f97abe7985a16b2762805","address":"0x492934308E98b590A626666B703A6dDf2120e85e","status":"UNCONFIRMED","eventSpecificationSignature":"0x4785d80d2593e2cb7a3331d31eb5106408bdde2aab0db9e9b616b036a1b6039d","networkName":"default","id":"0xdf064648f5923f3f015f53d464f2d95fce2540b0ab6a1e851e3603ff781b894b-0x204c9b64582e5154fa63f3e32c9b3960ea30a88c6d1f97abe7985a16b2762805-0"},"retries":0}',
  offset: 0,
  partition: 0,
  highWaterOffset: 2,
  key:
   '0xdf064648f5923f3f015f53d464f2d95fce2540b0ab6a1e851e3603ff781b894b-0x204c9b64582e5154fa63f3e32c9b3960ea30a88c6d1f97abe7985a16b2762805-0' }
{ topic: 'contract-events',
  value:
   '{"id":"0x00d319cdc9cc80de18642f7f2bb1e3701433f2417225d61791266e055fe07541-0x3f2b9dfc3e9dc86aea43dd11ec6482dfdc05cc454c9de81b19e3b5344515e69a-0","type":"CONTRACT_EVENT","details":{"name":"CounterUpdated","filterId":"CounterUpdatedEvent","nodeName":"default","indexedParameters":[],"nonIndexedParameters":[{"type":"uint256","value":234567}],"transactionHash":"0x00d319cdc9cc80de18642f7f2bb1e3701433f2417225d61791266e055fe07541","logIndex":0,"blockNumber":12,"blockHash":"0x3f2b9dfc3e9dc86aea43dd11ec6482dfdc05cc454c9de81b19e3b5344515e69a","address":"0x492934308E98b590A626666B703A6dDf2120e85e","status":"UNCONFIRMED","eventSpecificationSignature":"0x4785d80d2593e2cb7a3331d31eb5106408bdde2aab0db9e9b616b036a1b6039d","networkName":"default","id":"0x00d319cdc9cc80de18642f7f2bb1e3701433f2417225d61791266e055fe07541-0x3f2b9dfc3e9dc86aea43dd11ec6482dfdc05cc454c9de81b19e3b5344515e69a-0"},"retries":0}',
  offset: 1,
  partition: 0,
  highWaterOffset: 2,
  key:
   '0x00d319cdc9cc80de18642f7f2bb1e3701433f2417225d61791266e055fe07541-0x3f2b9dfc3e9dc86aea43dd11ec6482dfdc05cc454c9de81b19e3b5344515e69a-0' }

như vậy ta đã tích hợp thành công Eventume vào một ứng dụng nodejs.

Kết luận

Với những hệ thống nhỏ, việc log event không quá nhiều thì ta hoàn toàn có thể sử dụng phương án lắng nghe trực tiếp từ network và xử lý ngay khi lắng nghe được.

Tuy nhiên khi hệ thống của chúng ta lớn lên, đòi hỏi lượng event nhiều và cấu trúc phức tạp, thì Eventum sẽ là một lựa chọn hiệu quả.

Tham khảo