DWH 2: Hệ thống phân tích dữ liệu blockchain

- 13 mins

Trong bài viết trước mình đã giới thiệu với các bạn một thiết kế và cài đặt Data Warehouse dựa trên nền tảng Hadoop và một số công nghệ Opensource, bạn có thể xem lại tại đây. Trong bài viết này mình sẽ sử dụng DWH này để áp dụng cho một bài toán cụ thể là phân tích dữ liệu Blockchain. Do nội dung bài viết sẽ tập trung vào hệ thống phân tích dữ liệu nên mình sẽ không đi quá sâu vào công nghệ Blockchain mà sẽ chỉ trình bày các vấn đề liên quan đến dữ liệu EVM Blockchain để giải thích cho thiết kế của hệ thống.

Nội dung

  1. Sơ lược về EVM Blockchain
  2. Kiến trúc thiết kế
  3. Scanner
  4. Decoder
  5. Spellbook
  6. Visualization
  7. Automation
  8. Kết luận

Sơ lược về EVM Blockchain

Bitcoin cho chúng ta thấy phiên bản đầu tiên của Blockchain là một sổ cái phi tập trung (bạn có thể xem lại bài viết tại đây), nơi mà toàn bộ các giao dịch chuyển tiền được ghi lại. EVM Blockchain là thế hệ tiếp theo của Blockchain, nó bao gồm một lớp các chain ra đời sau này kể từ khi Ethereum được giới thiệu bao gồm rất nhiều chain phổ biến như: Ethereum, Binance Smart Chain, Polygon… (bạn có thể xem thêm trong danh sách này). Ethereum đã giúp cho Blockchain trở nên linh hoạt và đa dụng hơn thay vì chỉ có thể thực hiện các giao dịch chuyển tiền, mình sẽ tóm tắt lại một số điểm mới của EVM Blockchain như sau:

Smart Contract: là một chương trình đang nằm trên EVM Blockchain, tương tự như phần mềm nằm trên máy tính. Smart Contract sẽ được kích hoạt khi người dùng gọi đến một chức năng (function) của nó, lúc này Smart Contract sẽ thực thi chức năng đó theo đúng những gì mà nó được lập trình. Smart Contract thường được viết bằng ngôn ngữ Sodility, dưới đây là một ví dụ đơn giản:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract SimpleStorage {
    uint storedData;

    function set(uint x) public {
        storedData = x;
    }

    function get() public view returns (uint) {
        return storedData;
    }
}

Ethereum Virtual Machine (EVM): Là máy ảo chạy trên các node của EVM Blockchain dùng để thực thi các chức năng của Smart Contract.

Native cryptocurrency: Là đồng tiền chính trên mỗi chain, ví dụ trên Ethereum là Ether (ETH), Binance Smart Chain là Binance (BNB). Native cryptocurrency được tạo ra tuỳ theo cơ chế của mỗi chain khác nhau, và được sử dụng để trả phí giao dịch trên chain đó.

Transaction: Khi người dùng thực hiện việc chuyển tiền, deploy contract (đưa một contract mới lên blockchain) hay thực hiện một chức năng của Smart Contract thì sẽ tạo ra một giao dịch.

Gas: Là đơn vị để đo lường khối lượng tính toán của EVM khi thực hiện một giao dịch. Để thực hiện được giao dịch thì người dùng phải trả phí gas cho mạng lưới.

Token Standards: Là các chuẩn cho Smart Contract cho Token, nó bao gồm các chức năng mà một Smart Contract cần phải có để đạt chuẩn, dưới dây là 3 Torken Standards phổ biến trên EVM Blockchain:

Blockchain Explorer Để có thể hiểu được những gì đã, đang diễn ra trên EVM Blockchain chúng ta cần sử dụng Blockchain Explorer là một trang web cho phép người dùng tra cứu thông tin, xem dữ liệu trên blockchain một cách thân thiện hơn. VD: etherescan.io, bscscan.com, polygonscan.com

Kiến trúc thiết kế

Để thiết kế hệ thống này mình có tham khảo những mô tả của Dune Analyst một startup cung cấp hạ tầng phân tích dữ liệu Blockchain, được định giá tới 1 tỷ đô vào thời điểm tháng 2/2022.

BI Architecture

Scanner

Trước khi nói về Scanner mình sẽ mô tả qua một chút về dữ liệu trên EVM Blockchain. Mình sẽ lấy một ví dụ là một giao dịch Transfer của USDT, một stable coin chuẩn ERC20 trên mạng Ethereum. Ở tab overiew bạn có thể nhìn thấy các thông tin sau (click vào more detail để xem toàn bộ thông tin):

Chuyển qua tab logs bạn sẽ nhìn thấy danh sách các log được phát ra khi thực hiện giao dịch này, mỗi event log bao gồm các thông tin sau:

Scanner sẽ thực hiện việc lấy dữ liệu raw transaction và event log từ Node Service và lưu xuống DWH, mình sử dụng thư viện Web3j để lấy lấy dữ liệu từ Node Service. Dưới đây là một đoạn code mẫu bằng ngôn ngữ Scala để các bạn tham khảo:

import org.web3j.protocol.Web3j
import com.google.gson.Gson

val rpcUrl = <URL_YOUR_RPC_SERVICE>
val web3 = Web3j.build(new HttpService(rpcUrl))
val gson = new Gson()

val blockNumber = new DefaultBlockParameterNumber(1234)
val query = new EthFilter(blockNumber, blockNumber, java.util.Collections.emptyList[String]())
val eventlogs =  gson.toJson(web3.ethGetLogs(query).sendAsync().get().getLogs)
println(eventlogs)

val blockData = web3.ethGetBlockByNumber(blockNumber, true).sendAsync().get().getBlock
val transactions = gson.toJson(blockData)
println(transactions)

Với số lượng block cần lấy dữ liệu là rất lớn: 16,6 triệu block trên Ethereum và 25,6 triệu block trên BSC nên để có thể sử dụng được tối đa số lượng request đến Node Service, mình sử dụng Spark để thực hiện việc scan nhiều block song song với nhau.

Kết quả chạy đến thời điểm hiện tại như sau:

Decoder

Dữ liệu sau khi được lấy về là dữ liệu raw đã được mã hoá, để giải mã được nó ta cần có ABI (Application Binary Interface) của contract. ABI giống như một bản hướng dẫn kỹ thuật cho phép chúng ta có thể decode được dữ liệu raw thành dữ liệu có cấu trúc và mang ý nghĩa rõ ràng phù hợp cho việc phân tích. Chi tiết về ABI các bạn có thể xem tại đây. Để lấy được ABI của contract chúng ta có thể sử dụng Blockchain Explorer, ví dụ với contract token USDT bạn có thể vào đây, trong trang này bạn có thể nhìn thấy cả mã nguồn và ABI của contract. Decoder được thiết kế bao gồm CMS cho phép người dùng upload file ABI của contract (do không phải contract nào cũng có ABI đầy đủ trên Explorer), decoder sẽ giải mã dữ liệu raw và đẩy vào decoded table, bạn có thể xem mô tả về decoded table tại đây. Dưới đây là một đoạn code mẫu decode dữ liệu event log và function call data để các bạn tham khảo:

import com.esaulpaugh.headlong.abi.{Event, Function}
import com.esaulpaugh.headlong.util.Strings
import org.web3j.abi.EventEncoder
import java.util

val eventABIJson = <String abi json of one event>
val e = Event.fromJson(eventABIJson)
val eventSignature = EventEncoder.buildEventSignature(e.getCanonicalSignature)
println(eventSignature)

val topic1, topic2, topic3, topic4: String // Topics of one Event
val data: String // Data of one Event

val topics = new util.ArrayList[Array[Byte]]
topics.add(Strings.decode(topic1.replace("0x", "")))
    if (topic2 != null && topic2 != "") {
        topics.add(Strings.decode(topic2.replace("0x", "")))
}
if (topic3 != null && topic3 != "") {
    topics.add(Strings.decode(topic3.replace("0x", "")))
}
if (topic4 != null && topic4 != "") {
    topics.add(Strings.decode(topic4.replace("0x", "")))
}

val extractedData = e.decodeArgs(topics.toArray(new Array[Array[Byte]](topics.size())), Strings.decode(data.replace("0x", "")))
println(extractedData)

val functionABIJson = <String abi json of one Function>
val f = Function.fromJson(functionABIJson)
val methodId = Strings.encode(f.selector())
println(methodId)

val data: String // Input of one Transaction
val extractedData = f.decodeCall(Strings.decode(data.replace("0x", "")))
println(extractedData)

Spellbook

Sau khi lấy source code của Spellbook về bạn tiến hành cài đặt theo hướng dẫn tại đây, kiểm tra doc của Spellbook trên giao diện:

DBT Screen

Để chạy riêng từng bảng trên Spellbook bạn dùng command sau:

$ dbt run --select +<model_name>

Trước khi chạy một model bạn cần phải kiểm tra các bảng dữ liệu mà nó phụ thuộc vào đã có đủ hay chưa bằng cách kiểm tra DAG Graph của model đó. Ví dụ mình muốn chạy model: tofu_bnb_trades có DAG Graph như sau:

Spellbook DAG

Ta thấy rằng model này đang phụ thuộc vào các datasource (green) vì vậy ta cần chuẩn bị các bảng dữ liệu datasource (bằng cách sử dụng Decoder hoặc lấy thêm dữ liệu từ nguồn khác) trước khi có thể chạy được model này.

Kết luận

Bài viết hôm nay đến đây thôi, mình sẽ tiếp tục viết thêm nội dung cho cho bài viết này trong các lần cập nhật tiếp theo nhé. Hẹn gặp lại các bạn