WEB3DEV

Cover image for Avançando no desenvolvimento de dApps com indexação Hardhat: um divisor de águas para desenvolvedores Ethereum - Com Subsquid
Diogo Jorge
Diogo Jorge

Posted on

Avançando no desenvolvimento de dApps com indexação Hardhat: um divisor de águas para desenvolvedores Ethereum - Com Subsquid

Resumo

Image description

A indexação de dados da blockchain ajuda os desenvolvedores dApp a criar aplicativos descentralizados melhores e com mais desempenho, melhorando a experiência do usuário.

Uma das maneiras mais importantes de fazer isso é desacoplar o desenvolvimento de contrato inteligente do front-end e, quando necessário, do desenvolvimento do back-end.

Mas, tradicionalmente, faltam soluções que ajudem o desenvolvimento local nesse sentido, ninguém pensou em indexar um nó EVM local…

Mas o Subsquid pensou nisso

E este artigo explica como.

Ter um middleware de indexação desde o início de um projeto, quando nenhum contrato foi implantado na cadeia ainda, significa que os desenvolvedores podem criar o contrato inteligente localmente (assim como fazem agora) e indexar o nó local também. Isso também permite o desenvolvimento inicial do front-end, com acoplamento reduzido ao contrato inteligente ou sua blockchain.

Uma experiência de desenvolvimento melhor e mais suave leva a melhores dApps, que podem ser testados mais cedo e melhor, antes de implantar o contrato inteligente na cadeia (ou mesmo na testnet!).

Introdução

Há pouco mais de um mês, escrevi um artigo sobre como indexar um nó de desenvolvimento Ethereum, usando Subsquid e o pacote Ganache/Truffle.

Video: https://youtu.be/KDPPJYOfgfc

Fiquei surpreso positivamente com a apreciação da comunidade de desenvolvedores sobre este recurso muito útil oferecido pela estrutura do Subsquid e sobre o próprio artigo.

A inspiração do artigo veio de um projeto que construí há pouco mais de um mês, para o qual escrevi um artigo diferente. Neste projeto inicial, eu estava usando um nó local do Hardhat para o desenvolvimento do meu contrato inteligente. Eu decidi mudar para Truffle para o artigo final, porque tinha um contrato de demonstração mais intuitivo e porque a ferramenta de linha de comando facilitava a criação de transações manuais.

Em uma busca para fornecer aos desenvolvedores o máximo de opções possíveis, o máximo de material possível e o máximo de exemplos, decidi revisitar esse material e escrever um guia sobre como indexar um nó Hardhat.

O projeto discutido neste artigo está disponível neste repositório:

GitHub - RaekwonIII/local-evm-indexing

O projeto

Se ainda não o fez, você precisa instalar o CLI do Subsquid primeiro:

npm i -g @subsquid/cli@latest
Enter fullscreen mode Exit fullscreen mode

Para criar um novo projeto squid, basta abrir um terminal e executar o comando:

sqd init local-evm-indexing -t evm
Enter fullscreen mode Exit fullscreen mode

Onde local-evm-indexing é o nome que decidimos dar ao nosso projeto.Você pode mudar para o que quiser. E -t evm especifica qual modelo deve ser usado e garante que a configuração criará um projeto a partir do modelo de indexação da EVM.

1. Crie um projeto Hardhat

Para começar a trabalhar com Hardhat, é necessário instalar o pacote.

Em uma janela de console, na pasta raiz do projeto squid, execute:

npx hardhat
Enter fullscreen mode Exit fullscreen mode

Observação: Se você receber um erro referenciando seu README.md ou outros arquivos, exclua-os e execute npx hardhat de novo. Alternativamente, escolha uma pasta diferente, mas isso irá divergir um pouco deste tutorial.

E siga o processo de inicialização guiado. Para o propósito deste tutorial, eu escolhi um Projeto TypeScript e deixei outras opções como padrão.

Configurar a automineração do hardhat (opcional)

Em seguida, abra o hardhat.config.ts (a extensão do arquivo será .js se você selecionou um arquivo JavaScript na etapa anterior) e adicione-o no objeto HardhatUserConfig (ou omodule.exports para um arquivo JavaScript):

const config: HardhatUserConfig = {
 defaultNetwork: "hardhat",
 networks: {
   hardhat: {
     chainId: 1337,
     mining: {
       auto: false,
       interval: [4800, 5200]
     }
   },
 },
 solidity: "0.8.17",
};
Enter fullscreen mode Exit fullscreen mode

A configuração mining vai minerar blocos continuamente, mesmo que nenhum evento ou transação seja executado, pode ser útil para depuração. Você pode ler mais sobre isso na documentação oficial do hardhat.

Por fim, inicie o nó com este comando:

npx hardhat node
Enter fullscreen mode Exit fullscreen mode

2. Implantar o contrato (e gerar transações)

O hardhat é enviado com seu próprio contrato de demonstração, chamado,lock mas me senti mais à vontade com o contrato MetaCoin do Truffle, que parte do motivo pelo qual o primeiro artigo usa Ganache e Trufle como exemplo.

Para comparar melhor a configuração entre os dois artigos e as duas opções (Ganache e Hardhat), vou continuar usando o contrato MetaCoin neste artigo.

Minha sugestão é apenas copiar o Código-fonte do Solidity do repositório e cole os dois arquivos na pasta contratos do projeto Hardhat.

A próxima coisa a fazer é mudar o deploy.ts (ou.js, dependendo de como você inicializou o projeto), localizado na pasta scripts. Ele precisa ser editado para implantar nossa biblioteca ConvertLib, bem como o nosso novo contrato MetaCoin.

O plug-in hardhat-ethers adiciona a biblioteca ethers.js para o nó do Hardhat, possibilitando o uso de suas funcionalidades. É possível usá-lo para implantar o contrato, mas como nosso objetivo é criar transações, também podemos adicionar algum código para fazer isso.

Deve ficar assim:

import { ethers } from "hardhat";

async function main() {

 const ConvertLib = await ethers.getContractFactory("ConvertLib");
 const convertLib = await ConvertLib.deploy();

 await convertLib.deployed();

 const MetaCoin = await ethers.getContractFactory("MetaCoin", {
   libraries: {
     ConvertLib: convertLib.address,
   },
 });
 const metaCoin = await MetaCoin.deploy();

 let instance = await metaCoin.deployed();

 console.log(`MetaCoin  deployed to ${metaCoin.address}`);
 let accounts = await ethers.getSigners()
 console.log(`There are a total of ${accounts.length} accounts`)
 await instance.sendCoin(accounts[1].address, 10, {from: accounts[0].address})
 console.log(`Sent 10 coin from ${accounts[0].address} to ${accounts[1].address}`)
 await instance.sendCoin(accounts[2].address, 10, {from: accounts[0].address})
 console.log(`Sent 10 coin from ${accounts[1].address} to ${accounts[2].address}`)
 await instance.sendCoin(accounts[3].address, 10, {from: accounts[0].address})
 console.log(`Sent 10 coin from ${accounts[1].address} to ${accounts[3].address}`)
 await instance.sendCoin(accounts[4].address, 10, {from: accounts[0].address})
 console.log(`Sent 10 coin from ${accounts[1].address} to ${accounts[3].address}`)
}
Recomendamos este padrão para poder usar async/await em qualquer lugar
// e lidar adequadamente com os erros.
main().catch((error) => {
 console.error(error);
 process.exitCode = 1;
});
Enter fullscreen mode Exit fullscreen mode

Em seguida, abra um terminal diferente (o primeiro é ocupado pelo nó em execução) e execute este comando para implantar o contrato:

npx hardhat run scripts/deploy.ts --network localhost
Enter fullscreen mode Exit fullscreen mode

Observação: por favor, preste atenção ao endereço do contrato inteligente MetaCoin implantado, pois será usado na seção de lógica do indexador abaixo e pode variar daquele mostrado lá.

As próximas seções sobre o Arquivo Subsquid e o desenvolvimento do indexador Squid estão, na verdade, inalterados no artigo original, e isso não é apenas de propósito, mas é realmente o ponto:

Mudamos a ferramenta que executa o nó Ethereum local, mas a parte de indexação permanece a mesma

Hardhat oferece a capacidade de interagir com o nó da blockchain em execução por meio de um console. Se você não deseja gerar transações na implantação, pode abrir o console e executar o código necessário posteriormente.

Usá-lo é tão fácil quanto executar o comando:

npx hardhat console
Enter fullscreen mode Exit fullscreen mode

Arquivar imagem do Docker

Para indexar o nó Ethereum local, precisamos usar uma imagem Docker pré-construída do Arquivo Ethereum do Subsquid. Você pode fazer isso, criando um arquivo docker-compose.archive.yml (eu adicionei um na pasta arquivo) e cole isto:

version: "3"

services:
 worker:
   image: subsquid/eth-archive-worker:latest
   environment:
     RUST_LOG: "info"
   ports:
     - 8080:8080
   command: [
           "/eth/eth-archive-worker",
           "--server-addr", "0.0.0.0:8080",
           "--db-path", "/data/db",
           "--data-path", "/data/parquet/files",
           "--request-timeout-secs", "300",
           "--connect-timeout-ms", "1000",
           "--block-batch-size", "10",
           "--http-req-concurrency", "10",
           "--best-block-offset", "10",
           "--rpc-urls", "http://host.docker.internal:8545",
           "--max-resp-body-size", "30",
           "--resp-time-limit", "5000",
   ]
   # Descomente esta seção em máquinas Linux.
   # A conexão com o nó RPC local não funcionará de outra forma.
   # extra_hosts:
   # - "host.docker.internal:host-gateway"
   volumes:
     - database:/data/db
volumes:
 database:
Enter fullscreen mode Exit fullscreen mode

Agora, vamos abrir o arquivo comandos.json e adicionar archive-up e archive-down abaixo à lista de comandos. Não é obrigatório, mas esses atalhos são mais rápidos de digitar e fáceis de lembrar! 😅

{
   "$schema": "https://cdn.subsquid.io/schemas/commands.json",
   "commands": {
     // ...
     "archive-up": {
       "description": "Iniciar um Arquivo EVM local",
       "cmd": ["docker-compose", "-f", "archive/docker-compose.archive.yml", "up", "-d"]
     },
     "archive-down": {
       "description": "Parar um arquivo EVM local",
       "cmd": ["docker-compose", "-f", "archive/docker-compose.archive.yml", "down"]
     },
     // ...
   }
 }
Enter fullscreen mode Exit fullscreen mode

Então inicie o serviço abrindo o terminal e lançando o seguinte comando:

sqd archive-up
Enter fullscreen mode Exit fullscreen mode

Desenvolvimento do Squid

Finalmente chegou a hora de indexar os eventos do contrato inteligente que geramos anteriormente, com o SDK do Subsquid. Usando a ABI do contrato e o endereço de contrato das etapas anteriores.

Se você quiser saber mais sobre como desenvolver seu ETL squid, acesse o tutorial dedicado a isso na documentação oficial do Subsquid.

Esquema

Já que estamos usando o contrato MetaCoin como referência, então vamos construir um esquema que faça sentido em relação aos dados que ele produz.

type Transfer @entity {
 id: ID!
 block: Int!
 from: String! @index
 to: String! @index
 value: BigInt!
 txHash: String!
 timestamp: BigInt!
}
Enter fullscreen mode Exit fullscreen mode

E agora vamos gerar as classes de modelo TypeScript para este esquema. Em uma janela do console, na pasta raiz do projeto, digite este comando:

sqd codegen
Enter fullscreen mode Exit fullscreen mode

Facade (Fachada) da ABI

Também precisamos gerar o código TypeScript para interagir com o contrato e, para isso, precisamos da ABI do contrato.

Da mesma forma que o Truffle, o Hardhat gera isso quando compila o código de Solidity e coloca a ABI sob artifacts/contracts(enquanto Truffle faz o mesmo sob build/contracts/).

Então vamos usar as ferramentas CLI do Subsquid e em uma janela de console, na pasta raiz do projeto, digite este comando:

sqd typegen artifacts/contracts/MetaCoin.sol/MetaCoin.json
Enter fullscreen mode Exit fullscreen mode

Configuração do processador

Vamos abrir o arquivo processador.ts e certifique-se de importar o novo modelo Transfer, assim como a fachada da ABI. A partir disso, vamos apenas importar o objeto events.

import { Transfer } from './model';
import { events } from './abi/MetaCoin';
Enter fullscreen mode Exit fullscreen mode

Como queremos indexar nosso Arquivo local, a fonte de dados da classe do processador precisa ser definida para o ambiente local.

Também criei uma constante para o endereço do contrato que implantamos e lembre-se sempre de toLowerCase() (colocar em minúsculas) seus endereços de contrato, para evitar problemas.

Observação: É aqui que você precisa usar o endereço do contrato que foi registrado pelo roteiro do deploy.ts.

E, finalmente, queremos configurar o processador para solicitar dados sobre o evento Transfer:

// ...
const contractAddress = "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0".toLowerCase()
const processor = new EvmBatchProcessor()
 .setDataSource({
   chain: "http://localhost:8545",
   archive: "http://localhost:8080",
 })
 .addLog(contractAddress, {
   filter: [[events.Transfer.topic]],
   data: {
     evmLog: {
       topics: true,
       data: true,
     },
     transaction: {
       hash: true,
     },
   },
 });
// …
Enter fullscreen mode Exit fullscreen mode

Você também pode usar variáveis ​​de ambiente, como mostrado neste exemplo de projeto de ponta a ponta completo. Isso garantirá que o projeto permaneça o mesmo e apenas os arquivos de configuração contendo variáveis ​​de ambiente sejam alterados, dependendo da implantação.

Processar eventos

Precisamos agora mudar a lógica da função processador.run(), para aplicar nossa lógica personalizada.

Para processar eventos Transfer, precisamos "separar" todos os eventos que nosso processador recebe do Arquivo em um único lote e decodificar cada um deles.

A melhor prática neste cenário é usar um array como armazenamento temporário de dados e salvar todas as transferências processadas no final, em outra operação em lote. Isso renderá um melhor desempenho, pois os armazenamentos de dados, como o banco de dados Postgres que estamos usando no momento, são otimizados para isso. O código deve ficar assim:

processor.run(new TypeormDatabase(), async (ctx) => {
 const transfers: Transfer[] = []
 for (let c of ctx.blocks) {
   for (let i of c.items) {
     if (i.address === contractAddress && i.kind === "evmLog"){
       if (i.evmLog.topics[0] === events.Transfer.topic) {
         const { _from, _to, _value } = events.Transfer.decode(i.evmLog)
         transfers.push(new Transfer({
           id: `${String(c.header.height).padStart(10, '0')}-${i.transaction.hash.slice(3,8)}`,
           block: c.header.height,
           from: _from,
           to: _to,
           value: _value.toBigInt(),
           timestamp: BigInt(c.header.timestamp),
           txHash: i.transaction.hash
         }))
       }
     }
   }
  }
  await ctx.store.save(transfers)
});
Enter fullscreen mode Exit fullscreen mode

Executar e testar

Finalmente chegou a hora de realizar um teste completo. Vamos construir o projeto primeiro:

construção sqd
Enter fullscreen mode Exit fullscreen mode

Em seguida, precisamos iniciar o contêiner Docker do banco de dados, executando o comando:

sqd up
Enter fullscreen mode Exit fullscreen mode

Em seguida, precisamos remover o arquivo de migração de banco de dados que acompanha o modelo e gerar um novo para nosso novo esquema de banco de dados:

sqd migration: clean
sqd migration: generate
Enter fullscreen mode Exit fullscreen mode

E podemos finalmente lançar o processador:

sqd process
Enter fullscreen mode Exit fullscreen mode

Para testar nossos dados, também podemos iniciar o servidor GraphQL. Em uma janela de console diferente, execute:

sqd serve
Enter fullscreen mode Exit fullscreen mode

E execute um teste de query (consulta):

query MyQuery {
 transfers {
   block
   from
   id
   timestamp
   to
   txHash
   value
 }
}
Enter fullscreen mode Exit fullscreen mode

Image description

Conclusões

O que eu queria mostrar com este artigo é que, graças ao Subsquid, é possível ter um middleware de indexação ao mesmo tempo em que se desenvolve contrato(s) inteligente(s) para um projeto.

Os desenvolvedores podem criar seu dApp criando seu contrato inteligente localmente, implantando-o em um nó local e indexando esse nó local com um squid. Isso leva a um ciclo de desenvolvimento e teste com iterações muito mais rígidas.

Além disso, como o middleware de indexação está presente desde o início, menos alterações precisam ser feitas (apenas alterando o chain, address e o próprio endereço do contrato).

Se você achou este artigo interessante e deseja ler mais, por favor siga-me e o mais importante, siga o Subsquid.

Subsquid socials: \
Website | Twitter | Discord | LinkedIn | Telegram | GitHub | YouTube

Este artigo foi escrito por Maximo Luraschi e traduzido por Diogo Jorge. O artigo original pode ser encontrado aqui.

Oldest comments (0)