Salt la conținutul principal

The Graph: Remedierea interogării datelor Web3

soliditycontracte inteligenteinterogareathe graphcreate-eth-appreact
Intermediar
Markus Waas
soliditydeveloper.com(opens in a new tab)
6 septembrie 2020
8 minute de citit minute read

De data aceasta vom arunca o privire mai atentă la „The Graph” care, în esență, a devenit parte din stiva standard pentru dezvoltarea aplicațiilor dapp în ultimul an. Să vedem mai întâi cum am face lucrurile în mod tradițional...

Fără „The Graph”...

Vom folosi un exemplu simplu în scopul de ilustrare. Tuturor ne plac jocurile, deci să ne imaginăm un joc simplu, cu utilizatori care plasează pariuri:

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, "Transferul nu a reușit");
14 totalGamesPlayerWon++;
15 } else {
16 totalGamesPlayerLost++;
17 }
18
19 emit BetPlaced(msg.sender, msg.value, hasWon);
20 }
21}
Afișează tot
Copiați

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. Abordarea ar fi:

  1. Preia totalGamesPlayerWon.
  2. Preia totalGamesPlayerLost.
  3. Abonează-te la evenimente BetPlaced.

Putem asculta evenimentul în Web3(opens in a new tab) așa cum se arată în dreapta, dar necesită manipularea destul de multor cazuri.

1GameContract.events.BetPlaced({
2 fromBlock: 0
3}, function(error, event) { console.log(event); })
4.on('data', function(event) {
5 // eveniment declanșat
6})
7.on('changed', function(event) {
8 // evenimentul a fost eliminat din nou
9})
10.on('error', function(error, receipt) {
11 // tx respins
12});
Afișează tot
Copiați

Acum, acest lucru este încă destul de bun pentru exemplul nostru simplu. Dar să presupunem că vrem să afișăm numai câte pariuri a pierdut/câștigat doar jucătorul actual. Ei bine, nu avem noroc, ar fi bine să implementezi un nou contract care stochează valorile respective și să le afișeze. Și acum să ne imaginăm un contract inteligent și o aplicație dapp mult mai complicate, lucrurile pot deveni repede foarte confuze.

Nu faci interogări pur și simplu

Putem vedea de ce acest lucru nu este optim:

  • Nu funcționează pentru contracte deja implementate.
  • Costuri suplimentare de gaz pentru stocarea acestor valori.
  • Necesită un alt apel pentru a prelua datele pentru un nod Ethereum.

Asta nu e suficient de bine

Acum să analizăm o soluție mai bună.

Permite-mi să-ți prezint GraphQL

În primul rând, să vorbim despre GraphQL, inițial proiectat și implementat de Facebook. Este posibil să fii familiarizat cu modelul tradițional API Rest. Acum imaginează-ți că ai putea scrie o interogare pentru exact datele pe care le-ai dorit:

GraphQL API față de REST API

(opens in a new tab)

Cele două imagini surprind destul de bine esența GraphQL. Cu ajutorul interogării din dreapta putem defini exact ce date vrem și astfel obținem totul printr-o singură cerere, exact ce avem nevoie și nimic mai mult. Un server GraphQL se ocupă de preluarea tuturor datelor necesare, utilizarea fiind deci incredibil de ușoară de către consumatorul din frontend. Se explică frumos prin aceasta(opens in a new tab) cum anume gestionează serverul o interogare, dacă sunteți interesat.

Cunoscând acum aceste lucruri, să ne avântăm în sfârșit în spațiul blockchain și în „The Graph”.

Ce este „The Graph”?

Un blockchain este o bază de date descentralizată, dar, spre deosebire de ceea ce se întâmplă de obicei, nu avem un limbaj de interogare pentru această bază de date. Soluțiile pentru obținerea datelor sunt chinuitoare sau absolut imposibile. „The Graph” este un protocol descentralizat pentru indexarea și interogarea datelor de pe blockchain. Și poate ați ghicit, utilizează GraphQL ca limbaj de interogare.

The Graph

Cel mai bine se înțeleg lucrurile din exemple, așa că haideți să folosim „The Graph” în exemplul nostru „GameContract”.

Cum să creăm un Subgraph

Definiția modului de indexare a datelor se numește „subgraph”. Acesta necesită trei componente:

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

Manifest (subgraph.yaml)

Manifestul este fișierul nostru de configurare și definește:

  • ce contracte inteligente să indexeze (adresa, rețea, ABI...)
  • ce evenimente să asculte
  • alte lucruri de ascultat, cum ar fi apeluri de funcții sau blocuri
  • the mapping functions being called (see mapping.ts below)

Aici puteți defini mai multe contracte și manipulatoare. O configurare tipică ar fi cu un folder subgraph în proiectul Truffle/Hardhat, cu propriul depozitar. Puteți apoi face referire la ABI cu ușurință.

Din comoditate, ați dori probabil să utilizați un instrument șablon, cum ar fi „mustache”. Then you create a subgraph.template.yaml and insert the addresses based on the latest deployments. Pentru a vedea un exemplu de configurare mai avansată, puteți consulta, de exemplu, depozitarul subgraph-ului Aave(opens in a new tab).

Iar aici puteți vedea documentația completă: 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
Afișează tot

Schema (schema.graphql)

Schema este definiția datelor GraphQL. Aceasta vă va permite să definiți ce entități există și tipurile acestora. Tipurile acceptate din „The Graph” sunt

  • Bytes
  • ID
  • String
  • Boolean
  • Int
  • BigInt
  • BigDecimal

De asemenea, puteți utiliza entitățile ca „tip” pentru a defini relațiile. În exemplul nostru definim o relație între 1-și-mai-mulți a unui jucător la pariuri. Semnul „!” ne spune că valoarea nu poate fi goală. Puteți vedea aici documentația completă: 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}
Afișează tot

Mapping (mapping.ts)

Fișierul de mapare din „The Graph” definește funcțiile noastre care transformă evenimentele primite în entități. Este scris în AssemblyScript, un subset al Typescript. Deci poate fi compilat în WASM (WebAssembly) pentru executarea mai eficientă și portabilă a mapării.

You will need to define each function named in the subgraph.yaml file, so in our case we need only one: handleNewBet. Mai întâi încercăm să încărcăm entitatea Player utilizând adresa expeditorului drept cod de identificare (id). Dacă nu există, creăm o entitate nouă și o completăm cu valorile inițiale.

Apoi vom crea o nouă entitate Bet. The id for this will be event.transaction.hash.toHex() + "-" + event.logIndex.toString() ensuring always a unique value. Dacă utilizăm numai hash-ul, acest lucru nu este suficient, pentru că cineva poate apela funcția „placeBet” de mai multe ori într-o singură tranzacție printr-un contract inteligent.

Lastly we can update the Player entity with all the data. Matricele nu pot fi împinse direct, dar trebuie actualizate așa cum se arată aici. Folosim id-ul pentru a face referire la pariu. And .save() is required at the end to store an entity.

Puteți vedea aici documentația completă: https://thegraph.com/docs/define-a-subgraph#writing-mappings(opens in a new tab). Puteți și adăuga rezultatele jurnalizării în fișierul de mapare; pentru aceasta, uitați-vă aici(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 // creare dacă nu există încă
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 // actualizează matrice ca aceasta
32 let bets = player.bets
33 bets.push(bet.id)
34 player.bets = bets
35
36 player.save()
37}
Afișează tot

Folosindu-l în Front-end

Folosind ceva de genul Apollo Boost, puteți integra cu ușurință „The Graph” în aplicația dvs. React Dapp (sau Apollo Vue). În special când utilizați hooks React și Apollo, este tot atât de simplu să preluați datele cât să scrieți o singură interogare GrafQl în componenta dvs. O configurare tipică ar putea arăta astfel:

1// Vezi toate subgraph-urile: 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)
Afișează tot

Și acum putem scrie, de exemplu, o interogare de genul. Prin aceasta vom obține ca rezultat

  • de câte ori a câștigat utilizatorul curent
  • de câte ori a pierdut utilizatorul curent
  • o listă a marcajelor temporale cu toate pariurile sale anterioare

Toate într-o singură cerere către serverul 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])
Afișează tot

Magic

Dar ne lipsește o ultimă piesă din puzzle, și anume serverul. Puteți fie să îl rulați singur, fie să utilizați serviciul găzduit.

Serverul „The Graph”

Exploratorul Graph: Serviciul găzduit

Cel mai simplu mod de a utiliza serviciul găzduit. Pentru a implementa un subgraf, urmați instrucțiunile de aici(opens in a new tab). Pentru multe proiecte, puteți găsi subgraph-urile existente în explorator la https://thegraph.com/explorer/(opens in a new tab).

Exploratorul - The Graph

Rularea propriului tău nod

Altfel, vă puteți rula propriul nod: https://github.com/graphprotocol/graph-node#quick-start(opens in a new tab). Poate faceți acest lucru pentru că utilizați o rețea care nu este acceptată de serviciul găzduit. Sunt actualmente acceptate Mainnet, Kovan, Rinkeby, Ropsten, Goerli, PoA-Core, xDAI și Sokol.

Viitorul descentralizat

GraphQL acceptă și fluxuri pentru evenimente nou primite. „The Graph” nu acceptă încă aceasta, dar compatibilitatea se va lansa în curând.

Rămâne însă un aspect lipsă, și anume descentralizarea. Pentru viitor se are în vedere ca „The Graph” să devină în cele din urmă un protocol complet descentralizat. Iată două articole excelente care explică în detaliu care este planul:

Iată două aspecte esențiale:

  1. Utilizatorii vor plăti indexurile pentru interogări.
  2. Indexurile vor miza tokenuri Graph (GRT).

Ultima modificare: @nicklcanada(opens in a new tab), 15 august 2023

A fost util acest tutorial?