Resumo
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
Para criar um novo projeto squid, basta abrir um terminal e executar o comando:
sqd init local-evm-indexing -t evm
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
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",
};
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
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;
});
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
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
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:
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"]
},
// ...
}
}
Então inicie o serviço abrindo o terminal e lançando o seguinte comando:
sqd archive-up
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!
}
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
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
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';
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,
},
},
});
// …
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)
});
Executar e testar
Finalmente chegou a hora de realizar um teste completo. Vamos construir o projeto primeiro:
construção sqd
Em seguida, precisamos iniciar o contêiner Docker do banco de dados, executando o comando:
sqd up
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
E podemos finalmente lançar o processador:
sqd process
Para testar nossos dados, também podemos iniciar o servidor GraphQL. Em uma janela de console diferente, execute:
sqd serve
E execute um teste de query (consulta):
query MyQuery {
transfers {
block
from
id
timestamp
to
txHash
value
}
}
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.
Latest comments (0)