Vai al contenuto principale

The Graph: query di dati in Web3

Soliditycontratto intelligentequerythe graphcreate-eth-appreact
Intermedio
✍️Markus Waas
📚soliditydeveloper.com(opens in a new tab)
📆6 settembre 2020
⏱️8 minuti letti

Questa volta daremo un'occhiata più da vicino a The Graph, che è essenzialmente diventato parte dello stack standard per sviluppare le dapp nell'ultimo anno. Prima però vediamo come ci comporteremmo tradizionalmente...

Senza The Graph...

Procediamo con un semplice esempio a scopo illustrativo. A chi non piacciono i giochi? Immaginiamo quindi un gioco semplice, dove gli utenti fanno scommesse:

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}
22
Mostra tutto
📋 Copia

Ora, diciamo che nella nostra dapp, vogliamo mostrare le scommesse totali, le partite perse/vinte totali e, inoltre, aggiornarle ogni volta che qualcuno gioca di nuovo. L'approccio sarebbe:

  1. Recuperare totalGamesPlayerWon.
  2. Recuperare totalGamesPlayerLost.
  3. Iscriversi agli eventi BetPlaced.

Possiamo ascoltare l'evento in Web3(opens in a new tab) come mostrato sulla destra, ma richiederebbe la gestione di alcuni casi.

1GameContract.events.BetPlaced({
2 fromBlock: 0
3}, function(error, event) { console.log(event); })
4.on('data', function(event) {
5 // evento generato
6})
7.on('changed', function(event) {
8 // evento rimosso nuovamente
9})
10.on('error', function(error, receipt) {
11 // tx rifiutata
12});
13
Mostra tutto
📋 Copia

Questo va comunque bene per il nostro esempio semplice. Diciamo però che adesso vogliamo mostrare le quantità di scommesse perse/vinte solo per il giocatore corrente. In questo caso siamo sfortunati, è meglio distribuire un nuovo contratto che memorizzi questi valori e li recuperi. E, ora, immaginiamo un contratto intelligente e una dapp molto più complicati; le cose si complicano in fretta.

Non basta eseguire Query (opens in a new tab)

È facile capire perché questo non sia ottimale:

  • Non funziona per i contratti già distribuiti.
  • Costi aggiuntivi del gas per memorizzare tali valori.
  • Serve un'altra chiamata per recuperare i dati per un nodo Ethereum.

Non è sufficiente (opens in a new tab)

Cerchiamo allora una soluzione migliore.

Ti presento GraphQL

Parliamo prima di GraphQL, originariamente progettato e implementato da Facebook. Potresti conoscere il modello API Rest tradizionale. Ora immagina di poter scrivere invece una query proprio per i dati che volevi:

API GraphQL API e API REST (opens in a new tab)

Le due immagini catturano quasi perfettamente l'essenza di GraphQL. Con la query sulla destra possiamo definire esattamente i dati che vogliamo, così otteniamo tutto in un'unica richiesta e niente di più di quanto necessario. Un server GraphQL gestisce il recupero di tutti i dati necessari, quindi è incredibilmente facile da usare dal lato frontend client. Questa è una spiegazione efficace(opens in a new tab) e accurata di come il server gestisce una query.

Con queste informazioni, passiamo finalmente allo spazio della blockchain e a The Graph.

Cos'è The Graph?

Una blockchain è un database decentralizzato, ma a differenza di quanto avviene normalmente, in questo caso non abbiamo un linguaggio per interrogare il database. Le soluzioni per recuperare i dati sono complicate o assolutamente impraticabili. The Graph è un protocollo decentralizzato per indicizzare e interrogare i dati della blockchain. E, come forse avrai capito, usa GraphQL come linguaggio di query.

The Graph (opens in a new tab)

Gli esempi sono sempre la strategia migliore per comprendere qualcosa, quindi usiamo The Graph per il nostro esempio GameContract.

Come creare un Subgraph

La definizione di come indicizzare i dati è detta subgraph. Richiede tre componenti:

  1. Manifesto (subgraph.yaml)
  2. Schema (schema.graphql)
  3. Mappatura (mapping.ts)

Manifesto (subgraph.yaml)

Il manifest è il nostro file di configurazione e definisce:

  • quali Smart Contract indicizzare (indirizzo, rete, ABI...)
  • quali eventi attendere
  • altri elementi da attendere, come chiamate a funzioni o blocchi
  • le funzioni di mapping chiamate (vedi mapping.ts sotto)

Qui puoi definire più contratti e gestori. Una configurazione tipica avrebbe una cartella subgraph nel progetto Truffle/Hardhat con un proprio repository. A questo punto puoi facilmente fare riferimento all'ABI.

Per motivi di comodità potresti anche usare uno strumento di modelli come mustache. Poi crei un subgraph.template.yaml e inserisci gli indirizzi in base alle distribuzioni più recenti. Per una configurazione più avanzata, vedi ad esempio il repo del subgraph Aave(opens in a new tab).

E la documentazione completa può essere consultata qui(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
27
Mostra tutto

Schema (schema.graphql)

Lo schema è la definizione dei dati di GraphQL. Ti consentirà di definire quali entità esistono e i loro tipi. I tipi supportati da The Graph sono

  • Byte
  • ID
  • Stringa
  • Booleano
  • Int
  • BigInt
  • BigDecimal

Puoi anche usare le entità come tipo per definire le relazioni. Nel nostro esempio definiamo una relazione 1 a tanti dal giocatore alle scommesse. Il punto esclamativo ! significa che il valore non può essere vuoto. La documentazione completa può essere consultata qui(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}
15
Mostra tutto

Mappatura (mapping.ts)

Il file di mapping in The Graph definisce le nostre funzioni che trasformano gli eventi in ingresso in entità. È scritto in AssemblyScript, un subset di Typescript. Significa che è compilabile in WASM (WebAssembly) per un'esecuzione più portatile ed efficace del mapping.

Devi definire ogni funzione nominata nel file subgraph-yaml, quindi nel nostro caso ne occorrerà una sola: handleNewBet. Prima proviamo a caricare l'entità Player dall'indirizzo del mittente come id. Se non esiste, creiamo una nuova entità e la compiliamo con i valori iniziali.

Poi creiamo una nuova entità Bet. L'id sarà event.transaction.hash.toHex() + "-" + event.logIndex.toString() che assicura sempre un valore unico. Usare solo l'hash non è abbastanza poiché qualcuno potrebbe chiamare la funzione placeBet diverse volte in una transazione tramite uno smart contract.

Infine possiamo aggiornare l'entità Player con tutti i dati. Non è possibile eseguire direttamente il push degli array, bensì devono essere aggiornati come indicato qui. Usiamo l'id per fare riferimento alla scommessa. E occorre aggiungere .save() alla fine per memorizzare un'entità.

La documentazione completa è consultabile qui: https://thegraph.com/docs/define-a-subgraph#writing-mappings(opens in a new tab). Puoi anche aggiungere l'output di registrazione al file di mapping, vedi qui(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 // update array like this
32 let bets = player.bets
33 bets.push(bet.id)
34 player.bets = bets
35
36 player.save()
37}
38
Mostra tutto

Uso nel frontend

Usando qualcosa come Apollo Boost, puoi facilmente integrare The Graph nella tua dapp di React (o di Apollo-Vue). Specialmente se si utilizzano hook React e Apollo, per recuperare i dati basta scrivere una sola query GraphQI nel componente. Una configurazione tipica potrebbe somigliare a:

1// Vedi tutti i sotto-grafici: 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)
12
Mostra tutto

E ora possiamo scrivere per esempio una query come questa. Otterremo una serie di informazioni:

  • quante volte l'utente corrente ha vinto
  • quante volte l'utente corrente ha perso
  • un elenco di indicatori data/ora con tutte le scommesse precedenti dell'utente corrente

Tutto con una sola richiesta al 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])
19
Mostra tutto

Magic (opens in a new tab)

Ma ci manca l'ultimo pezzo del puzzle: il server. Puoi eseguirlo personalmente o tramite un servizio di hosting.

Il server The Graph

Graph Explorer: il servizio ospitato

Il modo più semplice è usare il servizio di hosting. Segui le istruzioni qui(opens in a new tab) per distribuire un subgraph. Per molti progetti è possibile trovare i sottografi esistenti nell'esploratore (opens in a new tab).

Explorer di The Graph (opens in a new tab)

Esecuzione di un nodo personalizzato

In alternativa, può eseguire il suo nodo personale. Documenti qui(opens in a new tab). Un motivo per farlo potrebbe essere l'uso di una rete non supportata dal servizio di hosting. Sono correntemente supportate Mainnet, Kovan, Rinkeby, Ropsten, Goerli, PoA-Core, xDAI e Sokol.

Il futuro decentralizzato

GraphQL supporta i flussi e anche nuovi eventi in ingresso che non sono ancora supportati completamente da The Graph, ma saranno presto rilasciati.

Un aspetto ancora mancante è la decentralizzazione. The Graph ha piani futuri per diventare un protocollo completamente decentralizzato. Di seguito sono elencati due ottimi articoli che spiegano il piano in modo più dettagliato:

Due aspetti chiave sono:

  1. Gli utenti pagheranno gli indicizzatori per le query.
  2. Gli indicizzatori saranno token di Graph (GRT) in staking.

Ultima modifica: , Invalid DateTime

Questa pagina è stata utile?