Vai al contenuto principale

Aiuta ad aggiornare questa pagina

🌏

C'è una nuova versione di questa pagina, ma al momento è solo in inglese. Aiutaci a tradurre l'ultima versione.

Traduci la pagina
Visualizza in inglese

Nessun bug qui!🐛

Questa pagina non è stata tradotta. Per il momento, è stata intenzionalmente lasciata in inglese.

The Graph: query di dati in Web3

Solidity
smart contract
interrogando
the graph
create-eth-app
react
Intermedio
✍Markus Waas
📚soliditydeveloper.com
📆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 attendere l'evento in Web3 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

È 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

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

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 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

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.

Al seguente link puoi trovare la documentazione completa: https://thegraph.com/docs/define-a-subgraph#the-subgraph-manifest.

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 è consultabile qui: https://thegraph.com/docs/define-a-subgraph#the-graphql-schema.

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. Puoi anche aggiungere l'output di registrazione al file di mapping, vedi qui.

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

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 per distribuire un subgraph. Per molti progetti, puoi trovare i subgraph esistenti nell'esploratore all'indirizzo https://thegraph.com/explorer/.

Explorer di The Graph

Esecuzione di un nodo personalizzato

In alternativa puoi eseguire un nodo personalizzato: https://github.com/graphprotocol/graph-node#quick-start. 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
Modifica la pagina

Questa pagina è stata utile?