Nhảy đến nội dung chính

The Graph: Sửa lỗi truy vấn dữ liệu Web3

Solidity
hợp đồng thông minh
truy vấn
the graph
react
Trung gian
Markus Waas
6 tháng 9, 2020
11 số phút đọc

Lần này, chúng ta sẽ xem xét kỹ hơn về The Graph, vốn đã trở thành một phần của ngăn xếp tiêu chuẩn để phát triển các ứng dụng phi tập trung trong năm ngoái. Trước tiên, hãy xem cách chúng ta thực hiện theo cách truyền thống...

Nếu không có The Graph...

Vậy hãy bắt đầu với một ví dụ đơn giản cho mục đích minh họa. Tất cả chúng ta đều thích trò chơi, vì vậy hãy tưởng tượng một trò chơi đơn giản với những người dùng đặt cược:

1pragma solidity 0.7.1;
2
3contract Game {
4 uint256 totalGamesPlayerWon = 0;
5 uint256 totalGamesPlayerLost = 0;
6 event BetPlaced(address player, uint256 value, bool hasWon);
7
8 function placeBet() external payable {
9 bool hasWon = evaluateBetForPlayer(msg.sender);
10
11 if (hasWon) {
12 (bool success, ) = msg.sender.call{ value: msg.value * 2 }('');
13 require(success, "Transfer failed");
14 totalGamesPlayerWon++;
15 } else {
16 totalGamesPlayerLost++;
17 }
18
19 emit BetPlaced(msg.sender, msg.value, hasWon);
20 }
21}
Hiện tất cả

Bây giờ, giả sử trong ứng dụng phi tập trung của chúng ta, chúng ta muốn hiển thị tổng số cược, tổng số ván thua/thắng và cũng cập nhật nó bất cứ khi nào ai đó chơi lại. Cách tiếp cận sẽ là:

  1. Tìm nạp totalGamesPlayerWon.
  2. Tìm nạp totalGamesPlayerLost.
  3. Đăng ký các sự kiện BetPlaced.

Chúng ta có thể nghe sự kiện trong Web3opens in a new tab như hiển thị ở bên phải, nhưng nó đòi hỏi phải xử lý một vài trường hợp.

1GameContract.events.BetPlaced({
2 fromBlock: 0
3}, function(error, event) { console.log(event); })
4.on('data', function(event) {
5 // sự kiện được kích hoạt
6})
7.on('changed', function(event) {
8 // sự kiện đã bị xóa một lần nữa
9})
10.on('error', function(error, receipt) {
11 // tx bị từ chối
12});
Hiện tất cả

Hiện tại, điều này vẫn ổn đối với ví dụ đơn giản của chúng ta. Nhưng giả sử bây giờ chúng ta muốn hiển thị số tiền cược thua/thắng chỉ cho người chơi hiện tại. Chà, chúng ta không gặp may rồi, tốt hơn hết bạn nên triển khai một hợp đồng mới lưu trữ các giá trị đó và tìm nạp chúng. Và bây giờ hãy tưởng tượng một hợp đồng thông minh và một ứng dụng phi tập trung phức tạp hơn nhiều, mọi thứ có thể trở nên lộn xộn nhanh chóng.

Không thể truy vấn đơn giản như vậy

Bạn có thể thấy điều này không tối ưu như thế nào:

  • Không hoạt động đối với các hợp đồng đã được triển khai.
  • Tăng chi phí gas để lưu trữ các giá trị đó.
  • Yêu cầu một lệnh gọi khác để tìm nạp dữ liệu cho một nút Ethereum.

Như vậy là chưa đủ tốt

Bây giờ chúng ta hãy xem một giải pháp tốt hơn.

Để tôi giới thiệu cho bạn về GraphQL

Đầu tiên, hãy nói về GraphQL, được Facebook thiết kế và triển khai ban đầu. Bạn có thể đã quen thuộc với mô hình REST API truyền thống. Bây giờ hãy tưởng tượng thay vào đó bạn có thể viết một truy vấn cho chính xác dữ liệu bạn muốn:

GraphQL API so với REST API

Hai hình ảnh này thể hiện khá rõ bản chất của GraphQL. Với truy vấn ở bên phải, chúng ta có thể xác định chính xác dữ liệu mình muốn, vì vậy chúng ta nhận được mọi thứ trong một yêu cầu và không có gì hơn ngoài những gì chúng ta cần. Một máy chủ GraphQL xử lý việc tìm nạp tất cả dữ liệu được yêu cầu, vì vậy phía người tiêu dùng giao diện người dùng cực kỳ dễ sử dụng. Đây là một lời giải thích hayopens in a new tab về cách máy chủ xử lý chính xác một truy vấn nếu bạn quan tâm.

Bây giờ với kiến thức đó, cuối cùng hãy cùng tìm hiểu về không gian chuỗi khối và The Graph.

The Graph là gì?

Chuỗi khối là một cơ sở dữ liệu phi tập trung, nhưng trái ngược với trường hợp thông thường, chúng ta không có ngôn ngữ truy vấn cho cơ sở dữ liệu này. Các giải pháp để truy xuất dữ liệu rất khó khăn hoặc hoàn toàn không thể. The Graph là một giao thức phi tập trung để lập chỉ mục và truy vấn dữ liệu chuỗi khối. Và bạn có thể đã đoán ra, nó đang sử dụng GraphQL làm ngôn ngữ truy vấn.

The Graph

Ví dụ luôn là cách tốt nhất để hiểu điều gì đó, vì vậy hãy sử dụng The Graph cho ví dụ GameContract của chúng ta.

Cách tạo Subgraph

Định nghĩa về cách lập chỉ mục dữ liệu được gọi là subgraph. Nó yêu cầu ba thành phần:

  1. Tệp kê khai (subgraph.yaml)
  2. Lược đồ (schema.graphql)
  3. Ánh xạ (mapping.ts)

Tệp kê khai (subgraph.yaml)

Tệp kê khai là tệp cấu hình của chúng ta và xác định:

  • hợp đồng thông minh nào sẽ được lập chỉ mục (địa chỉ, mạng, ABI...)
  • sự kiện nào cần lắng nghe
  • những thứ khác cần lắng nghe như lệnh gọi hàm hoặc khối
  • các hàm ánh xạ được gọi (xem mapping.ts bên dưới)

Bạn có thể xác định nhiều hợp đồng và trình xử lý tại đây. Một thiết lập điển hình sẽ có một thư mục subgraph bên trong dự án Hardhat với kho lưu trữ riêng. Sau đó, bạn có thể dễ dàng tham chiếu ABI.

Vì lý do tiện lợi, bạn cũng có thể muốn sử dụng một công cụ tạo mẫu như mustache. Sau đó, bạn tạo một subgraph.template.yaml và chèn các địa chỉ dựa trên các lần triển khai mới nhất. Để có một ví dụ thiết lập nâng cao hơn, hãy xem ví dụ kho lưu trữ subgraph Aave tại đâyopens in a new tab.

Và tài liệu đầy đủ có thể được xem tại đâyopens in a new tab.

1specVersion: 0.0.1
2description: Placing Bets on Ethereum
3repository: - GitHub link -
4schema:
5 file: ./schema.graphql
6dataSources:
7 - kind: ethereum/contract
8 name: GameContract
9 network: mainnet
10 source:
11 address: '0x2E6454...cf77eC'
12 abi: GameContract
13 startBlock: 6175244
14 mapping:
15 kind: ethereum/events
16 apiVersion: 0.0.1
17 language: wasm/assemblyscript
18 entities:
19 - GameContract
20 abis:
21 - name: GameContract
22 file: ../build/contracts/GameContract.json
23 eventHandlers:
24 - event: PlacedBet(address,uint256,bool)
25 handler: handleNewBet
26 file: ./src/mapping.ts
Hiện tất cả

Lược đồ (schema.graphql)

Lược đồ là định nghĩa dữ liệu GraphQL. Nó sẽ cho phép bạn xác định thực thể nào tồn tại và các loại của chúng. Các loại được The Graph hỗ trợ là

  • Byte
  • ID
  • Chuỗi
  • Boolean
  • Int
  • BigInt
  • BigDecimal

Bạn cũng có thể sử dụng các thực thể làm loại để xác định mối quan hệ. Trong ví dụ của chúng ta, chúng ta xác định mối quan hệ 1-nhiều từ người chơi đến các lần đặt cược. Dấu ! có nghĩa là giá trị không được để trống. Tài liệu đầy đủ có thể được xem tại đâyopens in a new tab.

1type Bet @entity {
2 id: ID!
3 player: Player!
4 playerHasWon: Boolean!
5 time: Int!
6}
7
8type Player @entity {
9 id: ID!
10 totalPlayedCount: Int
11 hasWonCount: Int
12 hasLostCount: Int
13 bets: [Bet]!
14}
Hiện tất cả

Ánh xạ (mapping.ts)

Tệp ánh xạ trong The Graph xác định các hàm của chúng ta để chuyển đổi các sự kiện đến thành các thực thể. Nó được viết bằng AssemblyScript, một tập hợp con của Typescript. Điều này có nghĩa là nó có thể được biên dịch thành WASM (WebAssembly) để thực thi ánh xạ hiệu quả và di động hơn.

Bạn sẽ cần xác định từng hàm được đặt tên trong tệp subgraph.yaml, vì vậy trong trường hợp của chúng ta, chúng ta chỉ cần một hàm: handleNewBet. Đầu tiên, chúng ta cố gắng tải thực thể Player từ địa chỉ người gửi dưới dạng id. Nếu nó không tồn tại, chúng ta tạo một thực thể mới và điền các giá trị ban đầu cho nó.

Sau đó, chúng ta tạo một thực thể Bet mới. Id cho mục này sẽ là event.transaction.hash.toHex() + "-" + event.logIndex.toString() để đảm bảo giá trị luôn là duy nhất. Chỉ sử dụng hàm băm là không đủ vì ai đó có thể gọi hàm placeBet nhiều lần trong một giao dịch thông qua một hợp đồng thông minh.

Cuối cùng, chúng ta có thể cập nhật thực thể Player với tất cả dữ liệu. Các mảng không thể được đẩy trực tiếp vào mà cần được cập nhật như được hiển thị ở đây. Chúng ta sử dụng id để tham chiếu đến lần đặt cược. Và .save() được yêu cầu ở cuối để lưu trữ một thực thể.

Bạn có thể xem tài liệu đầy đủ tại đây: https://thegraph.com/docs/en/developing/creating-a-subgraph/#writing-mappingsopens in a new tab. Bạn cũng có thể thêm đầu ra ghi nhật ký vào tệp ánh xạ, xem tại đâyopens in a new tab.

1import { Bet, Player } from "../generated/schema"
2import { PlacedBet } from "../generated/GameContract/GameContract"
3
4export function handleNewBet(event: PlacedBet): void {
5 let player = Player.load(event.transaction.from.toHex())
6
7 if (player == null) {
8 // tạo nếu chưa tồn tại
9 player = new Player(event.transaction.from.toHex())
10 player.bets = new Array<string>(0)
11 player.totalPlayedCount = 0
12 player.hasWonCount = 0
13 player.hasLostCount = 0
14 }
15
16 let bet = new Bet(
17 event.transaction.hash.toHex() + "-" + event.logIndex.toString()
18 )
19 bet.player = player.id
20 bet.playerHasWon = event.params.hasWon
21 bet.time = event.block.timestamp
22 bet.save()
23
24 player.totalPlayedCount++
25 if (event.params.hasWon) {
26 player.hasWonCount++
27 } else {
28 player.hasLostCount++
29 }
30
31 // cập nhật mảng như thế này
32 let bets = player.bets
33 bets.push(bet.id)
34 player.bets = bets
35
36 player.save()
37}
Hiện tất cả

Sử dụng trong Giao diện người dùng

Sử dụng một thứ gì đó như Apollo Boost, bạn có thể dễ dàng tích hợp The Graph vào ứng dụng phi tập trung React của mình (hoặc Apollo-Vue). Đặc biệt khi sử dụng các hook React và Apollo, việc tìm nạp dữ liệu cũng đơn giản như viết một truy vấn GraphQL duy nhất trong thành phần của bạn. Một thiết lập điển hình có thể trông như thế này:

1// Xem tất cả các subgraph: https://thegraph.com/explorer/
2const client = new ApolloClient({
3 uri: "{{ subgraphUrl }}",
4})
5
6ReactDOM.render(
7 <ApolloProvider client={client}>
8 <App />
9 </ApolloProvider>,
10 document.getElementById("root")
11)
Hiện tất cả

Và bây giờ chúng ta có thể viết ví dụ một truy vấn như thế này. Thao tác này sẽ tìm nạp cho chúng ta

  • số lần người dùng hiện tại đã thắng
  • số lần người dùng hiện tại đã thua
  • một danh sách các dấu thời gian với tất cả các lần đặt cược trước đó của họ

Tất cả trong một yêu cầu duy nhất đến máy chủ GraphQL.

1const myGraphQlQuery = gql`
2 players(where: { id: $currentUser }) {
3 totalPlayedCount
4 hasWonCount
5 hasLostCount
6 bets {
7 time
8 }
9 }
10`
11
12const { loading, error, data } = useQuery(myGraphQlQuery)
13
14React.useEffect(() => {
15 if (!loading && !error && data) {
16 console.log({ data })
17 }
18}, [loading, error, data])
Hiện tất cả

Phép màu

Nhưng chúng ta đang thiếu mảnh ghép cuối cùng và đó là máy chủ. Bạn có thể tự chạy nó hoặc sử dụng dịch vụ được lưu trữ.

Máy chủ The Graph

Graph Explorer: Dịch vụ được lưu trữ

Cách dễ nhất là sử dụng dịch vụ được lưu trữ. Làm theo hướng dẫn tại đâyopens in a new tab để triển khai một subgraph. Đối với nhiều dự án, bạn thực sự có thể tìm thấy các subgraph hiện có trong exploreropens in a new tab.

The Graph-Explorer

Chạy nút của riêng bạn

Ngoài ra, bạn có thể chạy nút của riêng mình. Tài liệu tại đâyopens in a new tab. Một lý do để làm điều này có thể là sử dụng một mạng không được hỗ trợ bởi dịch vụ được lưu trữ. Các mạng hiện được hỗ trợ có thể được tìm thấy ở đâyopens in a new tab.

Tương lai phi tập trung

GraphQL cũng hỗ trợ các luồng cho các sự kiện mới đến. Những tính năng này được hỗ trợ trên The Graph thông qua Substreamsopens in a new tab, hiện đang ở phiên bản beta mở.

Vào năm 2021opens in a new tab, The Graph đã bắt đầu quá trình chuyển đổi sang một mạng lập chỉ mục phi tập trung. Bạn có thể đọc thêm về kiến trúc của mạng lập chỉ mục phi tập trung này tại đâyopens in a new tab.

Hai khía cạnh chính là:

  1. Người dùng trả tiền cho người lập chỉ mục cho các truy vấn.
  2. Người lập chỉ mục đặt cọc Graph Token (GRT).

Lần cập nhật trang lần cuối: 24 tháng 6, 2025

Hướng dẫn này có hữu ích không?