Quorum IndexerIndexador EVM resistente a reorganizações
Arquitetura de referência para indexar eventos de contratos EVM em PostgreSQL com tratamento de reorganizações de cadeia, escrita idempotente e consenso entre provedores RPC.
- Disciplina
- Blockchain & Web3
- Tecnologias
- Go · PostgreSQL · EVM · Solidity ABI · Outbox
- Demonstra
- Tratamento de reorganizações
- Escrita idempotente
- Consenso entre provedores
- Confirmação explícita
O desafio
- 01
Blocos recentes podem ser substituídos. Um indexador ingênuo grava eventos que depois deixam de existir na cadeia canônica.
- 02
Provedores RPC divergem, atrasam ou falham. Confiar em um só deixa o sistema cego sem perceber.
- 03
Reprocessar um intervalo de blocos não pode duplicar saldos, transferências ou notificações.
Restrições
- Várias redes EVM
- Exatamente uma vez no efeito
- Rastreabilidade até o bloco
Arquitetura
Componentes
- RPC providers
- Três ou mais provedores independentes por rede.
- Head quorum
- Só avança quando a maioria concorda sobre o bloco mais recente.
- Block fetcher
- Busca blocos e logs em lotes, com retentativas por provedor.
- Reorg detector
- Compara o parentHash com os últimos N hashes armazenados.
- ABI decoder
- Transforma logs em eventos tipados por contrato.
- PostgreSQL
- Upsert por (chain_id, tx_hash, log_index); rollback por hash de bloco.
- Query API
- Consultas com o nível de confirmação explícito na resposta.
- Webhooks
- Notificações só após a profundidade de confirmação configurada.
Decisões de engenharia
D1Hash de bloco em toda linha
- Escolha
- Cada evento grava block_number e block_hash; uma reorganização remove as linhas pelos hashes órfãos.
- Alternativa descartada
- Gravar apenas o número do bloco.
- Por quê
- O número do bloco não muda em uma reorg, o hash sim. Sem ele, não há como saber quais linhas ficaram inválidas.
D2Chave natural idempotente
- Escolha
- Upsert por (chain_id, tx_hash, log_index).
- Alternativa descartada
- IDs gerados no momento da inserção.
- Por quê
- Reprocessar um intervalo vira uma operação segura, sem duplicatas, em vez de uma operação de risco.
D3Confirmação como dado, não como suposição
- Escolha
- A API expõe o estado de cada evento (pendente, seguro, finalizado) e os webhooks esperam a profundidade configurada.
- Alternativa descartada
- Tratar qualquer evento indexado como definitivo.
- Por quê
- Cada consumidor tem uma tolerância diferente. Um painel aceita dados recentes; uma liquidação não.
D4Quórum entre provedores
- Escolha
- O avanço da cabeça da cadeia exige concordância da maioria dos provedores.
- Alternativa descartada
- Um provedor principal com fallback.
- Por quê
- O fallback só age quando o provedor cai. O quórum também detecta o provedor que responde, mas está atrasado ou divergente.
Detalhes de implementação
O fetcher mantém em memória e no banco os hashes dos últimos 128 blocos processados. Ao receber um novo bloco, compara o parentHash com o hash armazenado da altura anterior; se divergirem, recua até encontrar o ancestral comum.
O rollback e a reaplicação acontecem na mesma transação, então consumidores nunca observam um estado intermediário. Webhooks são emitidos via outbox, só depois do commit e da profundidade de confirmação.
Cada rede roda como um worker independente. Uma rede lenta ou instável não atrasa as demais.
// findCommonAncestor walks back from a new block until its parent matches what we stored.
func (ix *Indexer) findCommonAncestor(ctx context.Context, b *types.Header) (uint64, error) {
for height := b.Number.Uint64() - 1; ; height-- {
stored, ok := ix.recent.Hash(height)
if !ok {
return 0, ErrReorgDeeperThanWindow // alert: needs a manual resync
}
canonical, err := ix.quorum.HeaderByNumber(ctx, height)
if err != nil {
return 0, err
}
if canonical.Hash() == stored {
return height, nil // rows above this height are orphaned
}
}
}Resultados
Não aplicável. Por ser uma demonstração interna, não há métricas de produção verificadas a relatar, e não publicamos números estimados como se fossem resultados.
Tem um problema parecido?
Conte o contexto. Discutimos a arquitetura antes de qualquer proposta comercial.
Próximo estudoTessera RiskGateway de risco pré-negociação