The Graph: Consertando a consulta de dados da Web3
Dessa vez, daremos uma olhada mais de perto no The Graph que essencialmente se tornou parte do stack padrão para o desenvolvimento de Dapps no último ano. Primeiro, vamos ver como faríamos as coisas da maneira tradicional...
Sem The Graph...
Então vamos começar com um exemplo simples para propósitos ilustrativos. Todos nós gostamos de jogos, então imagine um jogo simples com os usuários fazendo apostas:
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}Exibir tudoCopiar
Agora, digamos em nosso dapp, queremos exibir as apostas totais, os jogos perdidos/ganhos e também atualizá-lo sempre que alguém jogar novamente. A abordagem seria:
- Busca
totalGamesPlayerWon
. - Busca
totalGamesPlayerWon
. - Inscreva-se nos eventos
BetPlaced
.
Nós podemos escutar evento na Web3(opens in a new tab) como mostrado à direita, mas isso requer manipular alguns casos.
1AmeContract.events.BetPlaced({2 fromBlock: 03}, function(error, event) { console.log(event); })4.on('data', function(event) {5 // event fired6})7.on('changed', function(event) {8 // event was removed again9})10.on('error', function(error, receipt) {11 // tx rejected12});Exibir tudoCopiar
Por hora está de bom tamanho nosso simples exemplo. Mas digamos que queremos exibir as quantidades das apostas perdidas/ganhas apenas para o jogador atual. Se estivermos sem sorte, você pode implantar um novo contrato que armazena esses valores e busca. E agora imagine um contrato inteligente e um Dapp muito mais complicados. As coisas podem ficar confusas rapidamente.
Você pode ver que isso não é ideal:
- Não funciona para contratos já implementados.
- Custos de gas adicionais para armazenar esses valores.
- Requer outra chamada para obter os dados para um nó Ethereum.
Agora vamos ver uma solução melhor.
Deixe-me apresentá-lo ao GraphQL
Primeiro, vamos falar sobre GraphQL, originalmente projetado e implementado pelo Facebook. Você deve estar familiarizado com o modelo tradicional da Rest API. Agora imagine que você poderia escrever uma consulta para exatamente os dados que você queria:
As duas imagens capturam praticamente a essência do GraphQL. Com a consulta à direita, podemos definir exactamente quais os dados que queremos, assim aí temos tudo ao alcance e nada mais do que aquilo de que precisamos. Um servidor GraphQL lida com a busca de todos os dados necessários, então é incrivelmente fácil para o lado frontend do consumidor. Esta é uma bela explicação(opens in a new tab) de como exatamente o servidor lida com uma consulta se estiver interessado.
Agora com esse conhecimento, vamos finalmente adentrar o espaço da blockchain e The Graph.
O que é The Graph?
Um blockchain é um banco de dados descentralizado, mas em contraste com o que é geralmente o caso, nós não temos uma linguagem de consulta para esse banco de dados. Soluções para a obtenção de dados são dolorosas ou completamente impossíveis. The Graph é um protocolo descentralizado para indexação e consulta de dados da blockchain. E você pode ter adivinhado, ele está usando GraphQL como idioma de consulta.
Os exemplos são sempre os melhores para entender algo, então vamos usar The Graph para o nosso exemplo de GameContract.
Como criar um Subgraph
A definição de como indexar dados é chamada de subgráfico. Requer três componentes:
- Manifesto (
subgraph.yaml
) - Esquema (
schema.graphql
) - Mapping (
mapping.ts
)
Manifesto (subgraph.yaml
)
O manifesto é nosso arquivo de configuração e define:
- que contratos inteligentes indexar (endereço, rede, ABI...)
- quais eventos ouvir
- outras coisas para ouvir como chamadas de função ou blocos
- as fnções de mapping sendo chamadas (conferir
mapping.ts
below)
Aqui você pode definir vários contratos e manipuladores. Uma configuração típica teria uma pasta de subgráfico dentro do projeto Hardhat com seu próprio repositório. Então você pode facilmente se referir ao ABI.
Por conveniência você também pode querer usar uma ferramenta modelo tipo um bigode. Em seguida, você cria um subgraph.template.yaml
e insere os endereços com base nas mais recentes implantações. Para uma configuração de exemplo mais avançada, veja, por exemplo, o repositório de subgráfico Aave(opens in a new tab).
E a documentação completa pode ser vista aqui(opens in a new tab).
1specVersion: 0.0.12description: Placing Bets on Ethereum3repository: - GitHub link -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.tsExibir tudo
Esquema (schema.graphql
)
O esquema é a definição de dados do GraphQL. Permitirá que você defina quais entidades existem e seus tipos. Tipos suportados do The Graph são
- Bytes
- ID
- String
- Booleano
- Int
- BigInt
- BigDecimal
Você também pode usar entidades como tipo para definir relações. No nosso exemplo, definimos uma relação entre 1 e muitos entre jogadores e apostas. O ! significa que o valor não pode ser vazio. A documentação completa pode ser vista aqui(opens 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}Exibir tudo
Mapping (mapping.ts
)
O arquivo de mapeamento no The Graph define nossas funções que transformam eventos recebidos em entidades. É escrito em AssemblyScript, um subconjunto de Typescript. Isto significa que pode ser compilado em WASM (WebAssembly) para uma execução mais eficiente e portátil do mapeamento.
Você precisará definir cada função nomeada no arquivo subgraph.yaml
, portanto, no nosso caso, precisamos apenas uma: handleNewBet
. Primeiro, tentamos carregar a entidade Jogador a partir do endereço do remetente como id. Se não existir, nós criamos uma nova entidade e a preenchemos com os valores iniciais.
Em seguida, criamos uma nova entidade Bet. O ID para isso sempre event.transaction.hash.toHex() + "-" + event.logIndex.toString()
garantirá um valor exclusivo. Usar somente o hash não é o suficiente porque alguém pode chamar a função placeBet várias vezes em uma transação através de um contrato inteligente.
Finalmente, nós podemos atualizar a entidade "Player" com todos os dados. Arrays não podem ser empurrados diretamente, mas precisam ser atualizados como mostrado aqui. Usamos o id para fazer referência à aposta. E .save()
é necessário no final para armazenar uma entidade.
A documentação completa pode ser vista aqui: https://thegraph.com/docs/en/developing/creating-a-subgraph/#writing-mappings(opens in a new tab). Você também pode adicionar a saída do log ao arquivo de mapeamento, consultando aqui(opens 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 // create if doesn't exist yet9 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 // update array like this32 let bets = player.bets33 bets.push(bet.id)34 player.bets = bets3536 player.save()37}Exibir tudo
Usando isso no Frontend
Usando algo como Apollo Boost, você pode facilmente integrar o The Graph em seu React Dapp (ou Apollo-Vue). Especialmente ao usar React hooks e Apollo, buscar dados é tão simples quanto escrever uma única consulta GraphQl no seu componente. Uma típica configuração pode se parecer com isso:
1// See all subgraphs: 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)Exibir tudo
E agora podemos escrever, por exemplo, uma consulta como esta. Isso vai nos ajudar
- quantas vezes o usuário atual ganhou
- quantas vezes o usuário atual perdeu
- uma lista de horários com todas as suas apostas anteriores
Tudo em um único pedido para o servidor do 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])Exibir tudo
Mas precisamos de uma última peça do quebra-cabeças: o servidor. Você pode também executá-lo por conta própria ou usar o serviço hospedado.
Servidor The Graph
Graph Explorer: o serviço hospedado
O jeito mais fácil é usar o serviço hospedado. Siga as instruções aqui(opens in a new tab) para publicar um subgráfico. Para muitos projetos, você pode encontrar subgrafos existentes no explorer(opens in a new tab).
Executando seu próprio nó
Como alternativa, você pode executar seu próprio nó. Documentação aqui(opens in a new tab). Uma das razões para isso: você pode estar usando uma rede não suportada pelo serviço hospedado. As redes que contam atualmente com suporte podem ser encontradas aqui(opens in a new tab).
O futuro descentralizado
GraphQL também suporta streams para os próximos eventos. Elas tem suporte no grafo por meio de Subfluxos(opens in a new tab), que se encontram atualmente em versão beta de código aberto.
Em 2021(opens in a new tab) O Grafo iniciou sua transição para uma rede de indexação descentralizada. Leia mais sobre a arquitetura dessa rede de indexação descentralizada aqui(opens in a new tab).
Dois aspectos fundamentais são:
- Os usuários pagam aos indexadores pelas perguntas.
- Os indexadores fazem stake dos Graph Tokens (GRT).
Última edição: @sumitvekariya(opens in a new tab), 29 de agosto de 2024