Przejdź do głównej zawartości

The Graph: zapytania o dane Web3

solidityinteligentne kontraktyzapytaniathe graphcreate-eth-appreakcja
Średnie
Markus Waas
soliditydeveloper.com(opens in a new tab)
6 września 2020
7 minuta czytania minute read

Tym razem przyjrzymy się bliżej protokołowi The Graph, który zasadniczo stał się częścią standardowego stosu do tworzenia aplikacji dapps w zeszłym roku. Zobaczmy najpierw, jak zrobilibyśmy rzeczy w tradycyjny sposób...

Bez The Graph...

Przejdźmy więc do prostego przykładu w celach ilustracyjnych. Wszyscy lubimy gry, więc wyobraźmy sobie prostą grę z użytkownikami zakładów:

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}
Pokaż wszystko
Kopiuj

Załóżmy teraz, że w naszej aplikacji dapp chcemy wyświetlić całkowitą liczbę przegranych/wygranych gier, a także aktualizować ją za każdym razem, gdy ktoś gra ponownie. Podejście byłoby następujące:

  1. Pobierz totalGamesPlayerWon.
  2. Pobierz totalGamesPlayerlost.
  3. Subskrybuj zdarzenia BetPlaceed.

Możemy nasłuchiwać zdarzeń w sieci Web3(opens in a new tab), jak widać po prawej stronie, ale wymaga to obsługi kilku przypadków.

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});
Pokaż wszystko
Kopiuj

W naszym prostym przykładzie jest to nadal poniekąd w porządku. Ale powiedzmy, że chcemy teraz wyświetlać kwoty przegranych/wygranych zakładów tylko dla aktualnego gracza. Cóż, nie mamy szczęścia, lepiej wdrożyć nowy kontrakt, który przechowuje te wartości i je pobiera. A teraz wyobraź sobie znacznie bardziej skomplikowany inteligentny kontrakt i dapp, rzeczy mogą szybko się popsuć.

Nie wystarczy po prostu zapytać

Możesz zobaczyć, że to nieoptymalne:

  • Nie działa dla już wdrożonych kontraktów.
  • Dodatkowe koszty gazu za przechowywanie tych wartości.
  • Wymaga kolejnego wywołania w celu pobrania danych dla węzła Ethereum.

To nie jest wystarczająco dobre

Spójrzmy teraz na lepsze rozwiązanie.

Pozwól, że przedstawię Ci GraphQL

Najpierw porozmawiajmy o GraphQL, pierwotnie zaprojektowanym i zaimplementowanym przez Facebooka. Być może znasz tradycyjny model Rest API. Teraz wyobraź sobie, że zamiast tego możesz napisać zapytanie dla dokładnie tych danych, które chciałeś:

GraphQL API vs. REST API

(opens in a new tab)

Te dwa obrazy w dużym stopniu oddają istotę GraphQL. Za pomocą zapytania po prawej stronie możemy zdefiniować dokładnie, jakich danych chcemy, dzięki czemu otrzymujemy wszystko w jednym żądaniu i nic więcej niż dokładnie to, czego potrzebujemy. Serwer GraphQL obsługuje pobieranie wszystkich wymaganych danych, dzięki czemu jest niezwykle łatwy w użyciu dla użytkownika frontendu. To dobre wyjaśnienie(opens in a new tab), jak dokładnie serwer obsługuje zapytanie, jeśli jesteś zainteresowany.

Teraz, mając tę ​​wiedzę, w końcu wskoczmy w przestrzeń blockchain i The Graph.

Co to jest The Graph?

Blockchain to zdecentralizowana baza danych, ale w przeciwieństwie do tego, co zwykle ma miejsce, nie mamy języka zapytań dla tej bazy danych. Rozwiązania do odzyskiwania danych są bolesne lub całkowicie niemożliwe. The Graph to zdecentralizowany protokół do indeksowania i odpytywania danych blockchain. I mogłeś się domyślić, że używa GraphQL jako języka zapytań.

The Graph

Przykłady są zawsze najlepsze, aby coś zrozumieć, więc użyjmy The Graph dla naszego przykładu GameContract.

Jak stworzyć Subgraph

Definicja sposobu indeksowania danych nazywana jest subgraph. Wymaga trzech komponentów:

  1. Manifest (subgraf.yaml)
  2. Schemat (schema.graphql)
  3. Mapping (mapping.ts)

Manifest (subgraf.yaml)

Manifest jest naszym plikiem konfiguracyjnym i definiuje:

  • które inteligentne kontrakty indeksować (adres, sieć, ABI...)
  • jakich zdarzeń nasłuchiwać
  • inne rzeczy do słuchania, takie jak wywołania funkcji lub bloki
  • wywoływane funkcje mapujące (zobacz mapping.ts poniżej)

Tutaj możesz zdefiniować wiele kontraktów i programów obsługi. Typowa konfiguracja miałaby folder podrzędny wewnątrz projektu Truffle/Hardhat z własnym repozytorium. Wtedy możesz łatwo odwołać się do ABI.

Dla wygody możesz również użyć narzędzia szablonu, takiego jak wąsy. Następnie tworzysz subgraph.template.yaml i wstawiasz adresy oparte na najnowszych wdrożeniach. Aby zapoznać się z bardziej zaawansowaną przykładową konfiguracją, zobacz na przykład Repozytorium subgrafów Aave(opens in a new tab).

A pełną dokumentację można zobaczyć tutaj: 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
Pokaż wszystko

Schemat (schema.graphql)

Schematem jest definicja danych GraphQL. Pozwoli to na zdefiniowanie istniejących obiektów i ich typów. Obsługiwane typy z wykresu to

  • Bajty
  • ID
  • Tekst
  • Boolean
  • Wewnątrz
  • BigInt
  • BigDecimal

Możesz również używać obiektów jako typu do definiowania relacji. W naszym przykładzie definiujemy relacje od gracza do zakładów. ! oznacza, że ​​wartość nie może być pusta. Pełną dokumentację można zobaczyć tutaj: 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}
Pokaż wszystko

Mapping (mapping.ts)

Plik mapowania na wykresie definiuje nasze funkcje, które przekształcają przychodzące zdarzenia w podmioty. Jest napisany w AssemblyScript, podzbiorze Typescript. Oznacza to, że może być skompilowany w WASM (WebAssembly) dla bardziej wydajnego i przenośnego wykonywania mapowania.

Musisz zdefiniować każdą funkcję nazwaną w pliku subgraph.yaml, więc w naszym przypadku potrzebujemy tylko jednego: handleNewBet. Najpierw próbujemy załadować obiekt Gracza z adresu nadawcy w postaci identyfikatora. Jeśli nie istnieje, tworzymy nową jednostkę i wypełniamy ją wartościami początkowymi.

Następnie tworzymy nową jednostkę zakładu. Identyfikatorem dla tego będzie event.transaction.hash.toHex() + "-" + event.logIndex.toString() zapewniający zawsze unikalną wartość. Używanie samego skrótu nie wystarczy, ponieważ ktoś może kilkakrotnie wywoływać funkcję placeBet w jednej transakcji za pośrednictwem inteligentnej umowy.

Na koniec możemy zaktualizować podmiot Player, który będzie zawierał wszystkie dane. Tablice nie mogą być wypychane bezpośrednio, ale muszą zostać zaktualizowane, jak pokazano tutaj. Używamy identyfikatora, aby odnieść się do zakładu. A .save() jest wymagane na końcu do przechowywania obiektu.

Pełną dokumentację można zobaczyć tutaj: https://thegraph.com/docs/define-a-subgraph#writing-mappings(opens in a new tab). Możesz także dodać dane wyjściowe rejestrowania do pliku mapowania, zobacz tutaj(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}
Pokaż wszystko

Używanie go w frontendzie

Używając czegoś takiego jak Apollo Boost, możesz łatwo zintegrować The Graph z React Dapp (lub Apollo-Vue). Zwłaszcza gdy używasz hooków React i Apollo, pobieranie danych jest tak proste, jak napisanie pojedynczego zapytania GraphQl w twoim komponencie. Typowa konfiguracja może wyglądać tak:

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)
Pokaż wszystko

A teraz możemy napisać na przykład takie zapytanie. Spowoduje to pobranie

  • ile razy obecny użytkownik wygrał
  • ile razy aktualny użytkownik przegrał
  • listę sygnatur czasowych ze wszystkimi jego poprzednimi zakładami

Wszystko w jednym żądaniu do serwera 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])
Pokaż wszystko

Magic

Ale brakuje nam ostatniego elementu układanki, a jest nim serwer. Możesz uruchomić go samodzielnie lub skorzystać z usługi hostowanej.

Serwer The Graph

Graph Explorer: usługa hostowana

Najprostszym sposobem jest skorzystanie z usługi hostowanej. Postępuj zgodnie z instrukcjami tutaj(opens in a new tab), aby wdrożyć subgraf. W przypadku wielu projektów można znaleźć istniejące podgrafy w eksploratorze pod adresem https://thegraph.com/explorer/(opens in a new tab).

The Graph-Explorer

Uruchamianie własnego węzła

Alternatywnie możesz uruchomić własny węzeł: https://github.com/graphprotocol/graph-node#quick-start(opens in a new tab). Jednym z powodów, aby to zrobić, może być korzystanie z sieci, która nie jest obsługiwana przez hostowaną usługę. Obecnie obsługiwane są sieć główna, Kovan, Rinkeby, Ropsten, Goerli, PoA-Core, xDAI i Sokol.

Zdecentralizowana przyszłość

GraphQL obsługuje również strumienie dla nowo przychodzących zdarzeń. Nie jest to jeszcze w pełni obsługiwane przez The Graph, ale zostanie wkrótce wydane.

Brakującym aspektem jest jednak wciąż decentralizacja. The Graph w przyszłości ma ostatecznie stać się w pełni zdecentralizowanym protokołem. Oto dwa świetne artykuły, które bardziej szczegółowo wyjaśniają plan:

Dwa kluczowe aspekty to:

  1. Użytkownicy będą płacić indeksatorom za zapytania.
  2. Indeksatorzy będą stakować tokeny Graph (GRT).

Ostatnia edycja: @Beas(opens in a new tab), 31 października 2023

Czy ten samouczek był pomocny?