Lanjut ke konten utama

The Graph: Memperbaiki pembuatan kueri data Web3

soliditykontrak pintarmembuat kuerithe graphcreate-eth-appbereaksi
Tingkat menengah
Markus Waas
soliditydeveloper.com(opens in a new tab)
6 September 2020
7 bacaan singkat minute read

Kali ini kita akan melihat lebih dekat The Graph yang pada dasarnya menjadi bagian dari tumpukan standar untuk mengembangkan Dapp pada tahun lalu. Mari lihat bagaimana kita akan melakukan prosesnya secara tradisional...

Tanpa The Graph...

Mari kita memulai dengan contoh sederhana untuk tujuan ilustrasi. Kita semua menyukai game, jadi bayangkan game sederhana di mana pengguna memasang taruhan:

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}
Tampilkan semua
Salin

Now let's say in our Dapp, we want to display total bets, the total games lost/won and also update it whenever someone plays again. Pendekatannya akan seperti:

  1. Ambilkan totalGamesPlayerWon.
  2. Ambilkan totalGamesPlayerLost.
  3. Berlangganan dengan aksi BetPlaced.

Kita bisa mendengarkan aksi di Web3(opens in a new tab) seperti yang ditunjukkan di sebelah kanan, tapi ini memerlukan beberapa kasus.

1GameContract.events.BetPlaced({
2 fromBlock: 0
3}, function(error, event) { console.log(event); })
4.on('data', function(event) {
5 // event fired
6})
7.on('changed', function(event) {
8 // event was removed again
9})
10.on('error', function(error, receipt) {
11 // tx rejected
12});
Tampilkan semua
Salin

Sekarang, ini masih tampak cukup baik untuk contoh sederhana kita. Tapi mari anggaplah sekarang kita mau menampilkan jumlah taruhan kalah/menang hanya untuk pemain saat ini. Sayangnya kita tidak beruntung, Anda sebaiknya menggunakan kontrak baru yang menyimpan nilai tersebut dan mengambilnya. Dan sekarang bayangkan kontrak pintar dan Dapp yang lebih rumit, semua hal bisa menjadi kacau dengan cepat.

Seseorang Tidak Hanya Membuat Kueri

Anda bisa melihat bagaimana ini tidak optimal:

  • Tidak berfungsi untuk kontrak yang sudah digunakan.
  • Memerlukan biaya gas tambahan untuk menyimpan nilai tersebut.
  • Memerlukan pemanggilan lainnya untuk mengambil data dari node Ethereum.

Itu belum cukup baik

Sekarang, mari kita lihat solusi yang lebih baik.

Perkenalkan, ini GraphQL

Pertama, mari kita bicara tentang GraphQL, yang semula dirancang dan diimplementasikan oleh Facebook. Anda mungkin sudah mengenal model API Rest tradisional. Sekarang bayangkan sebaliknya, Anda bisa menulis kueri untuk data yang persis Anda inginkan:

API GraphQL vs. API REST

(opens in a new tab)

Kedua gambar cukup menangkap inti GraphQL. Dengan kueri di sebelah kanan, kita bisa secara persis menentukan data apa yang kita inginkan, sehingga di sana kita mendapatkan semua hal dalam satu permintaan dan tidak lebih dari yang benar-benar kita butuhkan. Server GraphQL menangani pengambilan semua data yang diperlukan, sehingga itu sangat mudah digunakan dari sisi pengguna frontend. Ini adalah penjelasan baik(opens in a new tab) tentang bagaimana sebenarnya server menangani kueri jika Anda tertarik.

Sekarang dengan pengetahuan itu, mari akhirnya masuk ke dalam ruang blockchain dan The Graph.

Apa itu The Graph?

Sebuah blockchain adalah basis data terdesentralisasi, tapi berbeda dari basis data umumnya, kita tidak memiliki bahasa kueri untuk basis data ini. Solusi untuk mengambil data sulit atau benar-benar mustahil. The Graph adalah protokol terdesentralisasi untuk mengindeks dan membuat kueri data blockchain. Dan Anda mungkin telah menebaknya, blockchain menggunakan GraphQL sebagai bahasa kuerinya.

The Graph

Contoh penggunaan adalah cara paling baik untuk memahami sesuatu, jadi mari kita gunakan The Graph untuk contoh GameContract kita.

Bagaimana membuat Subgraph

Definisi bagaimana mengindeks data disebut subgraph. Subgraph memerlukan tiga komponen:

  1. Manifest (subgraph.yaml)
  2. Schema (schema.graphql)
  3. Mapping (mapping.ts)

Manifest (subgraph.yaml)

Manifestasi adalah file konfigurasi kita dan menentukan:

  • kontrak pintar mana yang diindeks (alamat, jaringan, ABI...)
  • aksi mana yang didengarkan
  • hal lain untuk didengarkan seperti fungsi pemanggilan atau blok
  • the mapping functions being called (see mapping.ts below)

Anda bisa menentukan kontrak dan handler beragam di sini. Pengaturan umumnya akan memiliki folder subgraph di dalam proyek Truffle/Hardhat dengan repositorinya sendiri. Lalu Anda bisa dengan mudah merujuk ABI-nya.

Untuk alasan kenyamanan, Anda mungkin juga mau menggunakan peralatan templat seperti mustache. Then you create a subgraph.template.yaml and insert the addresses based on the latest deployments. Untuk pengaturan percontohan yang lebih canggih, lihat contoh repo subgraph Aave(opens in a new tab).

Dan dokumentasi lengkapnya bisa dilihat di sini: https://thegraph.com/docs/define-a-subgraph#the-subgraph-manifest(opens 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
Tampilkan semua

Schema (schema.graphql)

Skema adalah definisi data GraphQL. Skema akan memungkinkan Anda menentukan entitas mana yang ada dan jenisnya. Jenis yang didukung The Graph adalah

  • Bita
  • ID
  • String
  • Boolean
  • Int
  • BigInt
  • BigDecimal

Anda bisa juga menggunakan entitas sebagai jenis untuk menentukan hubungan. Dalam contoh kita, kita menentukan 1 untuk banyak hubungan dari pemain ke taruhan. Tanda ! berarti nilai tidak boleh kosong. Dokumentasi lengkapnya bisa dilihat di sini: https://thegraph.com/docs/define-a-subgraph#the-graphql-schema(opens 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}
Tampilkan semua

Mapping (mapping.ts)

File pemetaan dalam The Graph menentukan fungsi kita yang mengubah aksi selanjutnya ke dalam entitas. File ini ditulis dalam AssemblyScript, subset dari Typescript. Ini berarti bisa dikompilasi ke dalam WASM (WebAssembly) untuk eksekusi pemetaan yang lebih efisien dan portabel.

You will need to define each function named in the subgraph.yaml file, so in our case we need only one: handleNewBet. Pertama kita mencoba memuat entitas Pemain dari alamat pengirim sebagai id. Jika itu tidak ada, kita membuat entitas baru dan mengisinya dengan nilai awal.

Lalu kita membuat entitas Bet baru. The id for this will be event.transaction.hash.toHex() + "-" + event.logIndex.toString() ensuring always a unique value. Hanya menggunakan hash tidak cukup karena seseorang mungkin memanggil fungsi placeBet beberapa kali dalam satu transaksi lewat kontrak pintar.

Lastly we can update the Player entity with all the data. Array tidak boleh didorong secara langsung, tapi perlu diperbarui seperti yang ditunjukkan di sini. Kita menggunakan id untuk merujuk pada taruhannya. And .save() is required at the end to store an entity.

Dokumentasi lengkapnya bisa dilihat di sini: https://thegraph.com/docs/define-a-subgraph#writing-mappings(opens in a new tab). Anda juga bisa menambahkan output yang membuat log ke file pemetaan, lihat di sini(opens 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 // create if doesn't exist yet
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 // perbarui array seperti ini
32 let bets = player.bets
33 bets.push(bet.id)
34 player.bets = bets
35
36 player.save()
37}
Tampilkan semua

Menggunakannya pada Frontend

Dengan menggunakan sesuatu seperti Apollo Boost, Anda bisa dengan mudah mengintegrasikan The Graph dalam Dapp React Anda (atau Apollo-Vue). Khususnya ketika menggunakan kail React dan Apollo, mengambil data sesederhana menulis kueri GraphQl tunggal dalam komponen Anda. Pengaturan umumnya mungkin tampak seperti ini:

1// See all subgraphs: 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)
Tampilkan semua

Dan sekarang, kita bisa menulis contoh kueri seperti ini. Ini akan mengambilkan kita

  • berapa kali pengguna saat ini telah menang
  • berapa kali pengguna saat ini telah kalah
  • daftar stempel waktu dengan semua taruhan sebelumnya

Permintaan semua informasinya ke server 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])
Tampilkan semua

Sihir

Tapi kita kehilangan satu keping teka-teki terakhirnya dan itu adalah server. Anda bisa entah menjalankannya sendiri atau menggunakan layanan berhost.

Server The Graph

Penjelajah Graph: Layanan berhost

Cara termudahnya adalah menggunakan layanan berhost. Ikuti instruksinya di sini(opens in a new tab) untuk menggunakan subgraph. Untuk penggunaan pada banyak proyek, Anda bisa menemukan subgraph yang ada dalam penjelajah di https://thegraph.com/explorer/(opens in a new tab).

Penjelajah Graph

Menjalankan node milik Anda sendiri

Sebagai alternatif, Anda bisa menjalankan node Anda sendiri: https://github.com/graphprotocol/graph-node#quick-start(opens in a new tab). Satu alasan untuk melakukan ini adalah mungkin karena menggunakan jaringan yang tidak didukung oleh layanan berhost. Saat ini jaringan yang didukung adalah Jaringan Utama, Kovan, Rinkeby, Ropsten, Goerli, PoA-Core, xDAI, dan Sokol.

Masa depan terdesentralisasi

GraphQL mendukung penyiaran maupun aksi berikutnya yang baru dibuat. Ini belum sepenuhnya didukung oleh The Graph, tapi itu akan dirilis segera.

Satu aspek yang masih hilang adalah desentralisasi. The Graph memiliki rencana masa depan untuk pada akhirnya menjadi protokol terdesentralisasi penuh. Berikut adalah dua artikel hebat yang menjelaskan rencana ini dalam lebih banyak detail:

Dua aspek kuncinya adalah:

  1. Pengguna akan membayar pengindeks untuk pembuatan kueri.
  2. Pengindeks akan mempertaruhkan Token Graph (GRT).

Terakhir diedit: @yeremiaryangunadi(opens in a new tab), 15 Agustus 2023

Apakah tutorial ini membantu?