The Graph: Sửa lỗi truy vấn dữ liệu Web3
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;23contract Game {4 uint256 totalGamesPlayerWon = 0;5 uint256 totalGamesPlayerLost = 0;6 event BetPlaced(address player, uint256 value, bool hasWon);78 function placeBet() external payable {9 bool hasWon = evaluateBetForPlayer(msg.sender);1011 if (hasWon) {12 (bool success, ) = msg.sender.call{ value: msg.value * 2 }('');13 require(success, "Transfer failed");14 totalGamesPlayerWon++;15 } else {16 totalGamesPlayerLost++;17 }1819 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à:
- Tìm nạp
totalGamesPlayerWon. - Tìm nạp
totalGamesPlayerLost. - Đă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: 03}, function(error, event) { console.log(event); })4.on('data', function(event) {5 // sự kiện được kích hoạt6})7.on('changed', function(event) {8 // sự kiện đã bị xóa một lần nữa9})10.on('error', function(error, receipt) {11 // tx bị từ chối12});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.
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.
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:
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.
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:
- Tệp kê khai (
subgraph.yaml) - Lược đồ (
schema.graphql) - Á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.tsbê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.12description: Placing Bets on Ethereum3repository: - GitHub link -4schema:5 file: ./schema.graphql6dataSources:7 - kind: ethereum/contract8 name: GameContract9 network: mainnet10 source:11 address: '0x2E6454...cf77eC'12 abi: GameContract13 startBlock: 617524414 mapping:15 kind: ethereum/events16 apiVersion: 0.0.117 language: wasm/assemblyscript18 entities:19 - GameContract20 abis:21 - name: GameContract22 file: ../build/contracts/GameContract.json23 eventHandlers:24 - event: PlacedBet(address,uint256,bool)25 handler: handleNewBet26 file: ./src/mapping.tsHiệ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}78type Player @entity {9 id: ID!10 totalPlayedCount: Int11 hasWonCount: Int12 hasLostCount: Int13 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"34export function handleNewBet(event: PlacedBet): void {5 let player = Player.load(event.transaction.from.toHex())67 if (player == null) {8 // tạo nếu chưa tồn tại9 player = new Player(event.transaction.from.toHex())10 player.bets = new Array<string>(0)11 player.totalPlayedCount = 012 player.hasWonCount = 013 player.hasLostCount = 014 }1516 let bet = new Bet(17 event.transaction.hash.toHex() + "-" + event.logIndex.toString()18 )19 bet.player = player.id20 bet.playerHasWon = event.params.hasWon21 bet.time = event.block.timestamp22 bet.save()2324 player.totalPlayedCount++25 if (event.params.hasWon) {26 player.hasWonCount++27 } else {28 player.hasLostCount++29 }3031 // cập nhật mảng như thế này32 let bets = player.bets33 bets.push(bet.id)34 player.bets = bets3536 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})56ReactDOM.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 totalPlayedCount4 hasWonCount5 hasLostCount6 bets {7 time8 }9 }10`1112const { loading, error, data } = useQuery(myGraphQlQuery)1314React.useEffect(() => {15 if (!loading && !error && data) {16 console.log({ data })17 }18}, [loading, error, data])Hiện tất cả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.
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à:
- Người dùng trả tiền cho người lập chỉ mục cho các truy vấn.
- 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






