The Graph: Oprava dotazování dat ve Web3
Tentokrát se podíváme blíže na The Graph, který se v podstatě stal součástí standardní sady nástrojů pro vývoj dapps v posledním roce. Nejprve se podívejme, jak bychom to dělali tradičním způsobem...
Bez The Graph...
Pro názornost si tedy uveďme jednoduchý příklad. Všichni máme rádi hry, takže si představte jednoduchou hru, ve které uživatelé sázejí:
1pragma solidity 0.7.1;23contract Game {4 uint256 totalGamesPlayerWon = 0;5 uint256 totalGamesPlayerLost = 0;6 event BetPlaced(address player, uint256 value, bool hasWon);78 function placeBet() external payable {9 bool hasWon = evaluateBetForPlayer(msg.sender);1011 if (hasWon) {12 (bool success, ) = msg.sender.call{ value: msg.value * 2 }('');13 require(success, "Transfer failed");14 totalGamesPlayerWon++;15 } else {16 totalGamesPlayerLost++;17 }1819 emit BetPlaced(msg.sender, msg.value, hasWon);20 }21}Zobrazit všeŘekněme, že v naší dapp chceme zobrazit celkový počet sázek, celkový počet prohraných/vyhraných her a také jej aktualizovat, kdykoli někdo hraje znovu. Přístup by byl následující:
- Načíst
totalGamesPlayerWon. - Načíst
totalGamesPlayerLost. - Přihlásit se k odběru událostí
BetPlaced.
Můžeme naslouchat události ve Web3opens in a new tab, jak je znázorněno vpravo, ale vyžaduje to řešení několika případů.
1GameContract.events.BetPlaced({2 fromBlock: 03}, function(error, event) { console.log(event); })4.on('data', function(event) {5 // událost spuštěna6})7.on('changed', function(event) {8 // událost byla znovu odstraněna9})10.on('error', function(error, receipt) {11 // tx zamítnuta12});Zobrazit všePro náš jednoduchý příklad je to stále jakž takž v pořádku. Ale řekněme, že nyní chceme zobrazit výši prohraných/vyhraných sázek pouze pro aktuálního hráče. No, máme smůlu, raději nasaďte nový kontrakt, který tyto hodnoty uloží a načte. A teď si představte mnohem složitější chytrý kontrakt a dapp, věci se mohou rychle zkomplikovat.
Vidíte, že to není optimální:
- Nefunguje pro již nasazené kontrakty.
- Dodatečné náklady na palivo pro uložení těchto hodnot.
- Vyžaduje další volání k načtení dat pro uzel Etherea.
Nyní se podívejme na lepší řešení.
Dovolte mi, abych vám představil GraphQL
Nejprve si povíme něco o GraphQL, původně navrženém a implementovaném společností Facebook. Možná jste obeznámeni s tradičním modelem REST API. Nyní si představte, že byste místo toho mohli napsat dotaz na přesně ta data, která jste chtěli:
Tyto dva obrázky v podstatě vystihují podstatu GraphQL. S dotazem vpravo můžeme definovat přesně, jaká data chceme, takže získáme vše v jednom požadavku a nic víc, než přesně to, co potřebujeme. GraphQL server se stará o načítání všech požadovaných dat, takže je pro spotřebitelskou stranu frontendu neuvěřitelně snadno použitelný. Zde je pěkné vysvětleníopens in a new tab, jak přesně server zpracovává dotaz, pokud vás to zajímá.
S těmito znalostmi se konečně vrhněme do světa blockchainu a The Graph.
Co je The Graph?
Blockchain je decentralizovaná databáze, ale na rozdíl od toho, co je obvyklé, pro tuto databázi nemáme dotazovací jazyk. Řešení pro získávání dat jsou bolestivá nebo zcela nemožná. The Graph je decentralizovaný protokol pro indexování a dotazování dat na blockchainu. A možná jste to uhodli, jako dotazovací jazyk používá GraphQL.
Příklady jsou vždy nejlepší pro pochopení, takže použijme The Graph pro náš příklad GameContract.
Jak vytvořit Subgraph
Definice způsobu indexování dat se nazývá subgraph. Vyžaduje tři komponenty:
- Manifest (
subgraph.yaml) - Schéma (
schema.graphql) - Mapování (
mapping.ts)
Manifest (subgraph.yaml)
Manifest je náš konfigurační soubor a definuje:
- které chytré kontrakty indexovat (adresa, síť, ABI...)
- kterým událostem naslouchat
- další věci, kterým naslouchat, jako jsou volání funkcí nebo bloky
- volané mapovací funkce (viz
mapping.tsníže)
Zde můžete definovat více kontraktů a handlerů. Typické nastavení by mělo složku subgraph uvnitř projektu Hardhat s vlastním repozitářem. Potom můžete snadno odkazovat na ABI.
Z důvodu pohodlí můžete také chtít použít nástroj pro šablony, jako je mustache. Potom vytvoříte subgraph.template.yaml a vložíte adresy na základě nejnovějších nasazení. Pro pokročilejší příklad nastavení se podívejte například na repozitář subgraphu Aaveopens in a new tab.
A kompletní dokumentaci si můžete prohlédnout zdeopens in a new tab.
1specVersion: 0.0.12description: Sázení na Ethereu3repository: - odkaz na GitHub -4schema:5 file: ./schema.graphql6dataSources:7 - kind: ethereum/contract8 name: GameContract9 network: mainnet10 source:11 address: '0x2E6454...cf77eC'12 abi: GameContract13 startBlock: 617524414 mapping:15 kind: ethereum/events16 apiVersion: 0.0.117 language: wasm/assemblyscript18 entities:19 - GameContract20 abis:21 - name: GameContract22 file: ../build/contracts/GameContract.json23 eventHandlers:24 - event: PlacedBet(address,uint256,bool)25 handler: handleNewBet26 file: ./src/mapping.tsZobrazit všeSchéma (schema.graphql)
Schéma je definice dat GraphQL. Umožní vám definovat, které entity existují a jaké jsou jejich typy. Podporované typy z The Graph jsou
- Bajty
- ID
- String
- Boolean
- Int
- BigInt
- BigDecimal
Můžete také použít entity jako typ k definování vztahů. V našem příkladu definujeme vztah 1 ku mnoha od hráče k sázkám. Znak ! znamená, že hodnota nemůže být prázdná. Kompletní dokumentaci si můžete prohlédnout zdeopens in a new tab.
1type Bet @entity {2 id: ID!3 player: Player!4 playerHasWon: Boolean!5 time: Int!6}78type Player @entity {9 id: ID!10 totalPlayedCount: Int11 hasWonCount: Int12 hasLostCount: Int13 bets: [Bet]!14}Zobrazit všeMapování (mapping.ts)
Mapovací soubor v The Graph definuje naše funkce, které transformují příchozí události na entity. Je napsán v AssemblyScript, což je podmnožina Typescriptu. To znamená, že jej lze zkompilovat do WASM (WebAssembly) pro efektivnější a přenositelnější provádění mapování.
Budete muset definovat každou funkci pojmenovanou v souboru subgraph.yaml, takže v našem případě potřebujeme pouze jednu: handleNewBet. Nejprve se pokusíme načíst entitu Player z adresy odesílatele jako id. Pokud neexistuje, vytvoříme novou entitu a naplníme ji počátečními hodnotami.
Potom vytvoříme novou entitu Bet. ID pro ni bude event.transaction.hash.toHex() + "-" + event.logIndex.toString(), což zajišťuje vždy jedinečnou hodnotu. Použití pouze haše nestačí, protože by někdo mohl volat funkci placeBet několikrát v jedné transakci prostřednictvím chytrého kontraktu.
Nakonec můžeme aktualizovat entitu Player se všemi daty. Pole nelze přímo doplňovat (push), ale je třeba je aktualizovat, jak je zde ukázáno. Používáme id k odkazování na sázku. A .save() je na konci vyžadováno k uložení entity.
Kompletní dokumentaci si můžete prohlédnout zde: https://thegraph.com/docs/en/developing/creating-a-subgraph/#writing-mappingsopens in a new tab. Můžete také přidat výstup protokolování do mapovacího souboru, viz zdeopens in a new tab.
1import { Bet, Player } from "../generated/schema"2import { PlacedBet } from "../generated/GameContract/GameContract"34export function handleNewBet(event: PlacedBet): void {5 let player = Player.load(event.transaction.from.toHex())67 if (player == null) {8 // vytvořit, pokud ještě neexistuje9 player = new Player(event.transaction.from.toHex())10 player.bets = new Array<string>(0)11 player.totalPlayedCount = 012 player.hasWonCount = 013 player.hasLostCount = 014 }1516 let bet = new Bet(17 event.transaction.hash.toHex() + "-" + event.logIndex.toString()18 )19 bet.player = player.id20 bet.playerHasWon = event.params.hasWon21 bet.time = event.block.timestamp22 bet.save()2324 player.totalPlayedCount++25 if (event.params.hasWon) {26 player.hasWonCount++27 } else {28 player.hasLostCount++29 }3031 // aktualizovat pole tímto způsobem32 let bets = player.bets33 bets.push(bet.id)34 player.bets = bets3536 player.save()37}Zobrazit všePoužití ve frontendu
Pomocí něčeho jako Apollo Boost můžete snadno integrovat The Graph do své React dapp (nebo Apollo-Vue). Zejména při použití React hooks a Apollo je načítání dat tak jednoduché jako napsání jediného GraphQL dotazu ve vaší komponentě. Typické nastavení může vypadat takto:
1// Zobrazit všechny subgraphy: https://thegraph.com/explorer/2const client = new ApolloClient({3 uri: "{{ subgraphUrl }}",4})56ReactDOM.render(7 <ApolloProvider client={client}>8 <App />9 </ApolloProvider>,10 document.getElementById("root")11)Zobrazit všeA nyní můžeme napsat například takovýto dotaz. Tímto se nám načte
- kolikrát aktuální uživatel vyhrál
- kolikrát aktuální uživatel prohrál
- seznam časových razítek se všemi jeho předchozími sázkami
Vše v jediném požadavku na server GraphQL.
1const myGraphQlQuery = gql`2 players(where: { id: $currentUser }) {3 totalPlayedCount4 hasWonCount5 hasLostCount6 bets {7 time8 }9 }10`1112const { loading, error, data } = useQuery(myGraphQlQuery)1314React.useEffect(() => {15 if (!loading && !error && data) {16 console.log({ data })17 }18}, [loading, error, data])Zobrazit všeAle chybí nám poslední dílek skládačky, a to je server. Můžete si ho buď spustit sami, nebo použít hostovanou službu.
Server The Graph
Graph Explorer: Hostovaná služba
Nejjednodušší způsob je použít hostovanou službu. Postupujte podle pokynů zdeopens in a new tab a nasaďte subgraph. Pro mnoho projektů můžete skutečně najít existující subgraphy v průzkumníkoviopens in a new tab.
Provozování vlastního uzlu
Alternativně si můžete spustit vlastní uzel. Dokumentace zdeopens in a new tab. Jedním z důvodů může být použití sítě, která není podporována hostovanou službou. Aktuálně podporované sítě naleznete zdeopens in a new tab.
Decentralizovaná budoucnost
GraphQL podporuje také streamy pro nově příchozí události. Ty jsou na grafu podporovány prostřednictvím Substreamsopens in a new tab, které jsou v současné době v otevřené beta verzi.
V roce 2021opens in a new tab začal The Graph přecházet na decentralizovanou indexovací síť. Více o architektuře této decentralizované indexovací sítě si můžete přečíst zdeopens in a new tab.
Dva klíčové aspekty jsou:
- Uživatelé platí indexerům za dotazy.
- Indexeři stakují tokeny Graph (GRT).
Stránka naposledy aktualizována: 24. června 2025






