sync

Agradecimentos especiais a Gary pelo feedback e avaliação!

Sincronização instantânea (snap/1) melhorou drasticamente a sincronização do nó Ethereum quando foi lançado no Geth v1.10.0. Mas tem um calcanhar de Aquiles bem conhecido: o tente fase de curaum processo iterativo em que os nós de sincronização descobrem e corrigem inconsistências de estado, um nó de teste por vez. Esta fase fez com que os nós travassem a cura por dias ou semanas e foi identificada como algo que a comunidade deseja eliminar.

Com EIP-7928 (listas de acesso em nível de bloco), uma nova abordagem se torna possível: substitua a tentativa de cura inteiramente pela aplicação sequencial de BAL. Esta postagem explica como o snap sync funciona hoje, o que torna a tentativa de cura problemática e como uma proposta snap/2 a atualização do protocolo resolveria isso.


Parte 1: Como o Snap Sync funciona hoje

O problema que a sincronização instantânea resolve

Um novo nó Ethereum precisa do estado atual: cada saldo de conta, slot de armazenamento e bytecode de contrato. Este estado vive em um Merkle Patrícia Trie com a tentativa de conta saturada a uma profundidade de cerca de 7 níveis (blog EF: Snapshot Acceleration), contendo centenas de milhões de nós.

A abordagem antiga (“sincronização rápida”, eth/6366) baixou esta tentativa nó por nó da raiz. No bloco ~11.177.000, o estado continha 617 milhões de nós trie, e sincronizá-los exigia o download de 43,8 GB de dados distribuídos em 1.607 milhões de pacotes, resultando em um total de ~10h 50m de tempo de sincronização.

Principais insights do Snap Sync: pule totalmente os nós de teste intermediários e baixe as folhas (contas, armazenamento) como intervalos contíguose reconstrua o teste localmente. Isso requer que os nós de serviço mantenham um instantâneo dinâmico, um armazenamento de valor-chave simples que pode iterar contas em aproximadamente 7 minutos versus aproximadamente 9,5 horas para iteração de teste bruto (consulte snap.md).

Comparando a sincronização rápida com a sincronização instantânea, obtivemos as seguintes melhorias:

Métrica Sincronização rápida Sincronização instantânea Melhoria
Download 43,8GB 20,44GB -53%
Carregar 20,38GB 0,15GB -99,3%
Pacotes 1.607 milhões 0,099 milhões -99,99%
Servindo leituras de disco 15,68 TB 0,096TB -99,4%
Tempo 10h 50m 2h 6m -80,6%

Observação: Esses benchmarks são do bloco ~11,2M (final de 2020). O estado cresceu desde então, mas as melhorias relativas permanecem representativas. A sincronização instantânea moderna normalmente leva de 2 a 3 horas no total em um bom hardware.

As três fases

A sincronização instantânea ocorre em três fases:

Fase 1 – Download do cabeçalho: Usa o eth protocolo para baixar todos os cabeçalhos de bloco, construindo uma cadeia verificada. O CL aciona o EL, o que significa que o primeiro HEAD é recebido do CL e o EL baixa todos os cabeçalhos pais, começando pelo cabeçalho mais recente e retrocedendo.

Fase 2 – Download do estado: O nó escolhe um bloco pivô P (normalmente HEAD-64) e baixa o estado completo em P:

  • GetAccountRange (0x00): Baixe contas em intervalos de hash contíguos, cada resposta comprovada por Merkle nos limites para evitar ataques de lacuna
  • GetStorageRanges (0x02): Baixe slots de armazenamento para contratos, com vários contratos pequenos agrupados em uma única solicitação
  • GetByteCodes (0x04): Baixe o código do contrato, verificado pela comparação de codehash

Cada resposta é limitada pelo tamanho de bytes (não pela contagem) para largura de banda previsível, e diferentes peers podem servir diferentes intervalos simultaneamente. Os nós de serviço mantêm instantâneos dos 128 blocos mais recentes (aproximadamente 25,6 minutos a 12 segundos por slot).

O bloco pivô é um bloco distante o suficiente da ponta da cadeia para garantir que não baixemos o estado que é o resultado de um bloco que é posteriormente reorganizado. Reorganizações profundas de 64 blocos são praticamente impossíveis. Mesmo que tal reorganização ocorra, o estado já baixado não precisa ser descartado e pode ser reparado buscando iterativamente os nós de teste necessários.

Crucialmente, à medida que cada intervalo de estado é recebido, o nó reconstrói e persiste localmente os nós de teste intermediários para aquele segmento, em vez de buscá-los pela rede. Ao final da Fase 2, a maior parte do teste já está construída corretamente, reduzindo significativamente a carga de trabalho de reparo para apenas corrigir nós que se tornaram inconsistentes devido a alterações de estado que ocorreram durante a janela de download.

Fase 3 – Cura: Enquanto a Fase 2 está em execução, a cadeia avança de P para P+K, tornando o estado baixado obsoleto. A cura corrige isso, mas é também onde começa o problema.

Como funciona a tentativa de cura

A fase de cura usa GetTrieNodes (0x06) / TrieNodes (0x07) para descobrir e buscar iterativamente nós de teste alterados:

Por que tentar a cura é o gargalo

  1. Descoberta iterativa. Os nós de sincronização não sabem o que mudou até olharem. Cada rodada de GetTrieNodes revela o próximo conjunto de diferenças, exigindo outra viagem de ida e volta. Isto é fundamentalmente sequencial.
  2. Cargas pequenas, muitas viagens de ida e volta. Os nós de teste individuais têm de 100 a 500 bytes. Mesmo em lote, os dados por viagem de ida e volta são minúsculos em relação à latência da rede.
  3. Alvo em movimento. Com slots de 12 segundos, cerca de 1.000 nós de teste são excluídos e 2.000 adicionados por bloco. A cura deve ultrapassar isso ou nunca convergirá.
  4. Acesso aleatório ao disco. Servindo GetTrieNodes requer leituras aleatórias do banco de dados. Isso é caro em comparação com as leituras sequenciais usadas por GetAccountRange.
  5. O progresso é incognoscível. Como observa a documentação do Geth: “Não é possível monitorar o progresso da recuperação do estado porque a extensão dos erros não pode ser conhecida até que o estado atual já tenha sido regenerado.”

O impacto no mundo real pode ser grave. Os exemplos incluem nós que ficaram presos por mais de 2 semanas durante a recuperação (43 milhões de nós de teste, 11,7 GiB baixados; taxa de transferência degradada para aproximadamente 2 nós de teste/segundo), ficando presos por 4 ou 6 dias durante a recuperação.

O benchmark no lançamento mostrou a cura adicionando ~541.260 nós trie (~160 MiB) no bloco ~11,2M, mas com o estado maior de hoje e o limite de gás do bloco mais alto, a carga de cura já é substancialmente mais pesada e piorará com novos aumentos no limite de gás.


Parte 2: Listas de acesso em nível de bloco (BALs)

EIP-7928 apresenta Listas de acesso em nível de bloco (BALs): estruturas de dados que registram cada conta e local de armazenamento acessado durante a execução do bloco, juntamente com valores pós-execução. Cada cabeçalho de bloco é confirmado em seu BAL por meio de um novo block_access_list_hash campo colocado no cabeçalho do bloco:

block_access_list_hash = keccak256(rlp.encode(block_access_list))

Um BAL contém, para cada conta acessada:

  • Mudanças de armazenamento: pós-valores por slot, indexados por qual transação causou a alteração
  • Leituras de armazenamento: slots lidos, mas não modificados
  • Alterações de saldo/nonce/código: valores pós-transação

BALs são codificados em RLP, ordenados deterministicamente (contas lexicograficamente por endereço, alterações por índice de transação) e completos. As diferenças de estado são um subconjunto do BAL e podem, portanto, ser usadas para auxiliar durante a sincronização.

Tamanhos BAL

A análise empírica de 1.000 blocos da rede principal com um limite de gás de bloco de 60M mostrou que os BALs têm aproximadamente 72,4 KiB em média.

Os nós devem reter BALs pelo menos durante o período de subjetividade fraca (até 3.533 épocas, aproximadamente 15,7 dias no tamanho atual do conjunto de validadores).


Parte 3: snap/2: Cura do Estado Baseada em BAL

Em vez de descobrir e buscar iterativamente os nós de teste, snap/2 inverte o snap/1 padrão. Em snap/1o teste é construído de forma incremental durante o download e o estado simples é derivado dele. Em snap/2, apenas o estado plano (folhas) é sincronizadoas diferenças BAL são aplicadas diretamente a ele, e o trie é reconstruído uma vez a partir do estado completoeliminando a construção incremental de testes e a cura complexa que ela requer.

Concretamente, em vez de descobrir e buscar iterativamente os nós de teste, os nós baixam os BALs para cada bloco que avançou durante a sincronização e aplicam as diferenças de estado sequencialmente. O conjunto de blocos é conhecido antecipadamente. Cada BAL é verificado em relação ao seu compromisso de cabeçalho. Isso elimina a necessidade de descoberta iterativa.

snap/2 remove as mensagens de tentativa de cura e as substitui por BALs, reutilizando os mesmos IDs de mensagens:

EU IA estalo/1 estalo/2
0x00–0x05 Download de conta/armazenamento/bytecode Inalterado
0x06 GetTrieNodes GetBlockAccessLists
0x07 TrieNodes BlockAccessLists

Observe que reutilizar os IDs das mensagens é seguro porque snap/2 é uma nova versão do protocolo negociada durante o handshake RLPx. snap/1 colegas nunca veem snap/2 mensagens.

As novas mensagens

GetBlockAccessLists (0x06):

(request-id: P, (blockhash₁: B_32, blockhash₂: B_32, ...))

Listas de acesso de bloqueio (0x07):

(request-id: P, (block-access-list₁, block-access-list₂, ...))
  • Os nós devem sempre responder
  • Entradas vazias (bytes de comprimento zero) para BALs indisponíveis
  • As respostas preservam a ordem da solicitação e podem ser truncadas no final
  • O limite flexível recomendado é definido como 2 MiB por resposta, o que é consistente com as mensagens existentes, por exemplo, blocos, cabeçalhos ou recibos.

O novo algoritmo de sincronização

Notavelmente, uma vez que os BALs são garantidos como corretos por consenso (verificação de hash do BAL contra blocos canônicos), é garantido que as raízes do estado correspondam; assim, os clientes podem até pular a etapa final de comparação da raiz do estado.

Por que isso funciona

Com snap/2a janela de cura é limitada e conhecida. Para um pivô em HEAD−64:

  • 64 blocos × ~72,4 KiB (gás 60M projetado) ≈ 4,5 MiB dados totais de BAL
  • Se encaixa 2–3 respostas no limite flexível de 2 MiB
  • Servir BALs requer apenas algumas pesquisas de disco em vez de uma pesquisa para cada nó de teste alterado
  • 1–3 viagens de ida e volta no total (incluindo quaisquer blocos “caudais” que cheguem durante a aplicação)
  • Extrair a diferença de estado do BAL é computação puramente local. Não há necessidade de tentativa de travessia.

Comparado com snap/1, snap/2a recuperação do é mais eficiente, exigindo menos leituras de disco e viagens de ida e volta. Com snap/2pelo menos em teoria, deveria ser impossível para a cadeia ultrapassar a sincronização.

Relacionamento com eth/71

EIP-8159 adiciona troca BAL ao eth protocolo como mensagens 0x12/0x13. Ambos existem por razões diferentes:

eth/71 estalo/2
Propósito BALs recentes para execução paralela, manipulação de reorganização Sincronização: download em massa do BAL durante a cicatrização
Volume 1–3 BALs por vez Vários BALs ao mesmo tempo
Protocolo Obrigatório para todos os nós Protocolo de satélite opcional

As mensagens são duplicadas em eth/71 e snap/2 para garantir snap permanece um protocolo de satélite independente e permite que o snap evolua de forma independente, por exemplo, servindo apenas diferenças de estado em vez de BALs completos em uma versão futura, sem exigir alterações na eth.


Parte 4: Comparação

Fase de cura: snap/1 vs snap/2

Propriedade snap/1 (tentar cura) snap/2 (cura BAL)
Descoberta Iterativo: os nós não sabem o que mudou até olharem Determinístico: os blocos P+1..P+K são conhecidos antecipadamente
Viagens de ida e volta Centenas+ (problemas relatam milhões de nós de teste) Número final a ser definido, mas estimado em apenas alguns
Verificação Reconstrução de tentativa complexa + comparação de raiz keccak256(rlp(bal)) == header.block_access_list_hash
Alvo móvel Cada rodada de cura + avanço da cadeia → mais cura A aplicação do BAL é local e rápida; cauda é minúscula
Garantia de convergência Fraco: a cura deve superar o crescimento da cadeia Forte: trabalho determinístico e limitado

O fluxo completo, comparando snap/1 com snap/2tem a seguinte aparência:

Modos de falha

Falha estalo/1 estalo/2
A cura não pode convergir Risco real: a tentativa de cura é lenta o suficiente para que a corrente a ultrapasse Quase eliminado: apenas o download do BAL precisa de rede
Dados indisponíveis Nenhum: snap/1 requer apenas cura para ultrapassar a cadeia O período de subjetividade fraca (~15,7 dias) é generoso
Dados incorretos As provas Merkle detectam nós de teste ruins Comparações de hash detectam BALs ruins
Reorganizar o pivô anterior Recuperável: tentativa de cura resolve estado contra a nova cadeia canônica Recuperável se os BALs órfãos forem retidos; caso contrário, requer reinicialização da sincronização

Um exemplo concreto

Considere um pivô no bloco 22.000.000 com a cadeia 200 blocos à frente quando o download do estado for concluído:

snap/1: Comece a tentativa de travessia a partir da raiz do estado do bloco 22.000.200. Cada rodada descobre mais diferenças, vai mais fundo. Enquanto isso, vários novos blocos chegam durante a cura. Na melhor das hipóteses, isso leva alguns minutos; em casos patológicos (disco lento, rede lenta), demorou dias.

pressão/2: Solicite BALs para vários blocos. Em um limite de gás de bloco de 60M, isso é ~4–5 MiB, o que cabe em algumas respostas. Aplique os BALs localmente e, opcionalmente, verifique as correspondências da raiz do estado. Chegaram mais alguns blocos durante a aplicação? Busque mais 2–3 BALs. Total: 2–3 viagens de ida e volta, segundos para concluir.

Leitura adicional

Fontesethresear

By victor

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *