O núcleo da paralelização é aumentar a eficiência da camada de execução através da execução em múltiplos caminhos. Para realizar a execução em múltiplos caminhos, a cadeia precisa passar por uma série de verificações de conflito e mecanismos de rollback para garantir que possam ser executados em paralelo sem afetar a consistência do estado final, além de realizar melhorias no banco de dados.

Escrito por: Tia, Techub News

As blockchains sacrificam a eficiência devido ao seu design descentralizado, portanto, melhorar a velocidade de execução sempre foi um dos problemas que precisam ser resolvidos. A "camada de execução" da blockchain é a parte chave que processa cada transação e a adiciona à cadeia. Para acelerar a capacidade de processamento, melhorar a camada de execução se tornou uma das estratégias centrais, e a execução paralela é um importante avanço nessa área.

Blockchains tradicionais normalmente tratam transações uma a uma de forma sequencial, o que limita muito a velocidade das transações, especialmente em redes com alta densidade de transações, onde isso pode causar congestionamento. No entanto, com a execução paralela, várias transações podem ser processadas simultaneamente, melhorando significativamente a eficiência da execução e aliviando a pressão na cadeia.

Para entender melhor o que é paralelização, começaremos com a execução, usando o Ethereum no modo PBS após a fusão como exemplo para explicar o que é execução e mostrar onde se encaixa na totalidade do ciclo de vida da transação.

Etapas específicas da execução da transação

  1. As transações entram no pool de memória e são filtradas e ordenadas: esta é a fase de pré-processamento após a submissão da transação, envolvendo a interação entre Mempool, Searcher e Builder, completando a filtragem e ordenação das transações.

  2. O Builder constrói o bloco (mas não executa): o Builder organiza as transações lucrativas em um bloco, completando o empacotamento e ordenação das transações.

  3. O Proposer verifica e submete o bloco: após a construção do bloco, o Builder envia a proposta do bloco ao Proposer. O Proposer verifica a estrutura e o conteúdo da transação do bloco e, em seguida, submete formalmente o bloco à rede para iniciar a execução.

  4. Executar transações: após a submissão do bloco, os nós executam as transações dentro do bloco uma a uma. Esta é a fase chave de atualização do estado, onde cada transação aciona chamadas de contratos inteligentes, mudanças de saldo de contas ou mudanças de estado.

  5. Os testemunhas atestam: os validadores atestam os resultados da execução do bloco e a raiz do estado, e isso serve como confirmação final. Isso garante a autenticidade e a validade do bloco na camada de execução e impede inconsistências.

  6. Sincronização de estado: cada nó sincroniza os resultados da execução do bloco (como saldo de contas, atualizações de estado de contratos, etc.) em seu estado local. Após a execução de cada transação, o nó calcula e armazena uma nova raiz de estado, para servir como estado inicial no próximo bloco.

Claro, isso é apenas a sincronização do estado da transação em unidades de bloco; para manter o estado mais recente na cadeia, normalmente, os nós sincronizam dados bloco por bloco e validam continuamente blocos e estados. No entanto, para alcançar a finalidade sob o mecanismo POS, os agregadores precisam agregar as assinaturas dos testemunhas de cada Slot em uma assinatura completa e passá-la ao proponente do próximo Slot, e os validadores precisam confirmar o estado de todos os blocos dentro de um Epoch com base na quantidade de votos após um Epoch, formando um ponto de verificação de estado de consenso temporário. Quando dois Epochs consecutivos obtêm o apoio da maioria dos validadores, os blocos e transações alcançam a finalidade.

Do ponto de vista de todo o ciclo de vida da transação, a execução ocorre após o Proposer verificar a estrutura do bloco e o conteúdo da transação enviados ao Builder. O processo de execução real requer o processamento de cada transação individualmente e a atualização do estado correspondente das contas ou contratos. Após todas as transações serem executadas, o Proposer calculará uma nova raiz de estado (raiz de Merkle), que resume os resultados da execução de todas as transações do bloco e o estado global final. Em termos simples, o processo completo de execução do bloco inclui uma série de cálculos necessários para transformar o Ethereum do estado anterior para o próximo estado, desde a execução de cada transação até o cálculo da raiz de Merkle.

Execução sequencial

Em contraste com a execução paralela, a execução sequencial é a forma de execução mais comum atualmente nas blockchains. Normalmente, as transações são executadas em uma ordem sequencial. Quando uma transação é executada, o Ethereum atualiza o estado da conta e as informações relacionadas (como saldo, dados de armazenamento de contratos) na árvore de estado da conta, gerando um novo hash de estado da conta. Após todas as árvores de estado da conta serem atualizadas, forma-se o que é chamado de raiz de Merkle de estado. Após a conclusão da raiz de Merkle de estado, raiz de Merkle de transação e raiz de Merkle de recibos, o cabeçalho do bloco passa pelo cálculo de hash, gerando o hash do bloco.

Dentro disso, a ordem de execução das transações é crucial. Como a árvore de Merkle é uma árvore binária de valores hash, os valores de raiz de Merkle formados em ordens diferentes serão diferentes.

Execução paralela

Em um ambiente de execução paralela, os nós tentarão processar as transações dentro do bloco em paralelo. As transações não são executadas uma a uma na ordem, mas alocadas em diferentes "caminhos de execução", permitindo que sejam executadas simultaneamente. Através da execução paralela, o sistema pode processar as transações no bloco de forma mais eficiente, aumentando o throughput.

Após a execução de todas as transações, os nós agregam os resultados da execução (ou seja, as atualizações de estado afetadas pelas transações) e formam um novo estado do bloco. Este estado será adicionado à blockchain, representando o estado global mais recente na cadeia.

Conflito de estado

Como a execução paralela processa transações em diferentes caminhos ao mesmo tempo, uma grande dificuldade da paralelização é o conflito de estado. Isso significa que pode haver várias transações tentando ler ou escrever na mesma parte dos dados (estado) na blockchain ao mesmo tempo. Se não for tratado corretamente, isso pode resultar em resultados de execução incertos. Como a ordem de atualização do estado é diferente, o resultado final da computação também será diferente. Por exemplo,

Suponha que haja duas transações, transação A e transação B, ambas tentando atualizar o saldo da mesma conta:

  • Transação A: Aumentar o saldo da conta em 10.

  • Transação B: Aumentar o saldo da conta em 20.

O saldo inicial da conta é 100.

Se executarmos sequencialmente, o resultado da ordem de execução é determinado:

1. Execute a transação A primeiro, depois a transação B:

  • O saldo da conta aumenta para 110.

  • Depois, aumenta para 130.

2. Execute a transação B primeiro, depois a transação A:

  • O saldo da conta aumenta para 120.

  • Em seguida, aumenta para 130.

Em ambas as ordens, o saldo final é 130, pois o sistema garante a consistência da ordem de execução das transações.

Mas em um ambiente de execução paralela, as transações A e B podem ler simultaneamente o saldo inicial de 100 e realizar seus cálculos:

  1. A transação A lê o saldo de 100, calcula e atualiza o saldo para 110.

  2. A transação B também lê o saldo de 100, calcula e atualiza o saldo para 120.

Nesse caso, devido à execução simultânea das transações, o saldo final é atualizado apenas para 120, em vez de 130, pois as operações das transações A e B "sobrepuseram" os resultados uma da outra, gerando um conflito de estado.

Esses problemas de conflito de estado são frequentemente chamados de "sobrescrição de dados", ou seja, quando as transações tentam modificar os mesmos dados simultaneamente, elas podem sobrescrever os resultados de cálculo uma da outra, levando a um estado final incorreto. Outro tipo de conflito de estado que pode ocorrer é a incapacidade de garantir a ordem de execução. Como várias transações concluem operações em diferentes momentos, isso pode resultar em diferentes ordens de execução. Ordens diferentes podem levar a diferentes resultados de cálculo, tornando o resultado incerto.

Para evitar essa incerteza, os sistemas de execução paralela de blockchain geralmente introduzem algumas verificações de conflito e mecanismos de rollback, ou realizam previamente uma análise de dependência das transações para garantir que possam ser executadas em paralelo sem afetar a consistência do estado final.

Paralelização otimista vs. Paralelização determinística

Existem duas maneiras de lidar com possíveis problemas de conflito de estado: paralelização determinística e paralelização otimista. Esses dois modos têm suas compensações em eficiência e complexidade de design.

A paralelização determinística exige que o acesso ao estado seja declarado previamente; validadores ou sequenciadores verificam o acesso ao estado declarado ao classificar as transações. Se várias transações tentarem escrever no mesmo estado, elas serão marcadas como conflitos, evitando a execução simultânea. Diferentes cadeias implementam a declaração de acesso ao estado de formas diferentes, mas geralmente incluem as seguintes maneiras:

  • Através de restrições de especificação de contrato: os desenvolvedores podem definir diretamente o escopo de acesso ao estado no contrato inteligente. Por exemplo, a transferência de tokens ERC-20 requer o acesso aos campos de saldo do remetente e do destinatário.

  • Através de declarações de dados estruturados na transação: adicionar campos especiais à transação para marcar o acesso ao estado.

  • Através da análise do compilador: compiladores de linguagens de alto nível podem analisar estaticamente o código do contrato e gerar automaticamente um conjunto de acessos ao estado.

  • Através de declarações forçadas por frameworks: alguns frameworks exigem que os desenvolvedores especifiquem explicitamente os estados que precisam acessar ao chamar funções

A paralelização otimista processa as transações de forma otimista, aguardando até que um conflito ocorra, momento em que as transações afetadas são reexecutadas em ordem. Para evitar o máximo possível a ocorrência de conflitos, o design da paralelização otimista se baseia em rápidas previsões e suposições sobre o estado, utilizando dados históricos e análise estática. O sistema assume que certas operações ou atualizações de estado são válidas sem validação completa, evitando assim esperar por todo o processo de validação, melhorando a performance e o throughput.

Embora a paralelização otimista possa evitar conflitos por meio de rápidas previsões e suposições sobre o estado, ainda existem desafios inevitáveis, especialmente em relação à execução de contratos ou transações interchain. Se ocorrerem conflitos frequentes, a reexecução pode reduzir significativamente o desempenho do sistema e aumentar o consumo de recursos computacionais.

A paralelização determinística evita as situações de conflito que a paralelização otimista pode apresentar, realizando verificações de dependência de estado antes da transação, mas isso exige que os desenvolvedores declarem com precisão as dependências de estado antes da submissão da transação, aumentando assim a complexidade da implementação.

Dilema da paralelização do EVM

Lidar com conflitos de estado não envolve apenas determinismo e otimismo; na prática da paralelização, também é necessário considerar a arquitetura do banco de dados da cadeia. O problema de conflito de estado na execução paralela é particularmente desafiador na arquitetura de árvores de Merkle do EVM. A árvore de Merkle é uma estrutura de hash hierárquica, e cada vez que uma transação modifica um determinado dado de estado, o hash da raiz da árvore de Merkle também precisa ser atualizado. Esse processo de atualização é recursivo, calculando de baixo para cima até a raiz. Como o hash é irreversível, só é possível calcular o nível superior após a conclusão das alterações no nível inferior, o que torna difícil a atualização paralela.

Se duas transações forem executadas em paralelo e acessarem o mesmo estado (por exemplo, saldo da conta), isso causará conflitos nos nós da árvore de Merkle. Resolver esse conflito geralmente requer mecanismos de gerenciamento de transações adicionais, garantindo que um valor de hash raiz consistente seja obtido em vários ramos. Isso não é fácil de implementar no EVM, pois exige escolhas entre paralelização e consistência de estado.

Soluções de paralelização não EVM

Solana

Diferente da árvore de estado global do Ethereum, o Solana adota um modelo de conta. Cada conta é um espaço de armazenamento independente, armazenado no livro razão, evitando assim problemas de conflito de caminho.

O Solana é paralelização determinística. No Solana, cada transação precisa declarar claramente as contas que acessará e as permissões necessárias (somente leitura ou leitura/escrita) ao ser submetida. Esse design permite que os nós da blockchain analisem previamente os recursos que cada transação precisará acessar antes da execução. Como todas as dependências de conta são claramente definidas antes do início da execução da transação, os nós podem determinar quais transações acessarão a mesma conta e quais podem ser executadas em paralelo de forma segura, permitindo agendamento inteligente e evitando conflitos, estabelecendo assim a base para agendamento paralelo.

Como cada transação declara previamente as contas e permissões que precisa acessar antes da execução, o Solana pode verificar se há dependências de contas entre as transações (modelo Sealevel). Se não houver contas lidas ou escritas compartilhadas entre transações, o sistema pode alocá-las em diferentes processadores para execução paralela.

Aptos

O design de execução paralela do Aptos é muito diferente do Ethereum, fazendo algumas inovações chave em sua arquitetura e mecanismos, principalmente em relação ao modelo de conta e ao armazenamento de estado.

O Ethereum precisa atualizar frequentemente a árvore de estado global (MPT) ao executar transações. Todos os estados de contas e contratos são armazenados em uma árvore de estado compartilhada, e qualquer transação precisa acessar e atualizar uma parte dessa árvore de estado. O Aptos, por outro lado, divide as contas em unidades de estado independentes, onde cada objeto é um par chave-valor independente que pode existir de forma independente, sem impacto mútuo, e só se relaciona quando há uma referência explícita. Não há caminhos de árvore públicos entre objetos, evitando competição por bloqueios, permitindo a execução totalmente paralela.

A estrutura de dados subjacente do Aptos é a Jellyfish Merkle Tree. O estado de cada objeto é finalmente armazenado no JMT, como pares chave-valor independentes. Diferente da MPT do Ethereum, a Jellyfish Merkle Tree tem uma estrutura de árvore binária completa, o que simplifica os caminhos de armazenamento e consulta dos nós, reduzindo significativamente o tempo de verificação. Além disso, a posição de cada conta na árvore é fixa, e os nós na árvore são armazenados de forma independente, permitindo que atualizações e buscas de várias contas sejam feitas em paralelo.

O Aptos é otimista em paralelização, não precisa fornecer previamente as dependências de todas as contas declaradas. Para isso, o Aptos utiliza o Block-STM, que utiliza a ordem de transação pré-definida para estimar as dependências, reduzindo assim o número de abortos.

EVM Paralela

Comparado à paralelização não EVM, a paralelização EVM enfrenta dificuldades técnicas maiores ao lidar com questões de dependência de estado, detecção de conflitos, gerenciamento de Gas e mecanismos de rollback. Para entender melhor isso, podemos nos referir a como alguns projetos de EVM paralela (como Sui, Monad, Canto) resolveram esses problemas.

Sui

Assim como o Aptos, o Sui também usa um modelo de objeto para lidar com o estado, adotando cada objeto (como contas e estado de contratos inteligentes) como um recurso independente, que são distinguidos por identificadores únicos de objetos. Quando uma transação envolve diferentes objetos, essas transações podem ser processadas em paralelo, pois operam em estados diferentes, sem gerar conflitos diretos.

Embora o Sui use um modelo de objeto para gerenciar o estado, para ser compatível com o EVM, a arquitetura do Sui usa uma camada adicional de adaptação ou um mecanismo abstrato para conectar o modelo de objeto e o modelo de conta do EVM.

No Sui, o agendamento de transações usa uma estratégia de paralelização otimista, assumindo que não há conflitos entre transações. Se um conflito ocorrer, o sistema utiliza um mecanismo de rollback para restaurar o estado.

O Sui utiliza um modelo de objeto e tecnologia de isolamento de estado, o que pode evitar efetivamente problemas de dependência de estado. Cada objeto é um recurso independente, e transações diferentes podem ser executadas em paralelo, aumentando assim o throughput e a eficiência. No entanto, essa abordagem tem o trade-off da complexidade do modelo de objeto e o custo do mecanismo de rollback. Se ocorrer um conflito entre transações, parte do estado precisará ser revertida, o que aumentará a carga do sistema e pode afetar a eficiência do processamento paralelo. Comparado a sistemas paralelos não EVM (como Solana), o Sui requer mais recursos computacionais e de armazenamento para manter uma paralelização eficiente.

Monad

Assim como o Sui, o Monad também adota a execução otimista em paralelo. No entanto, a execução otimista em paralelo do Monad prevê algumas transações com dependências antes da execução real, principalmente através do analisador de código estático do Monad. A previsão requer acesso ao estado, e a maneira como o Ethereum armazena o estado no banco de dados torna esse acesso muito difícil. Para tornar a leitura do estado mais eficiente durante a execução paralela, o Monad também reestruturou o banco de dados.

A árvore de estado do Monad é dividida em partições, cada partição mantém sua própria subárvore de estado. Durante a atualização, é necessário modificar apenas a partição relevante, sem precisar reconstruir toda a árvore de estado. Um índice de estado permite localizar rapidamente o estado dentro da partição, reduzindo a interação entre partições.

Resumo

O núcleo da paralelização é aumentar a eficiência da camada de execução através da execução em múltiplos caminhos. Para realizar a execução em múltiplos caminhos, a cadeia precisa passar por uma série de verificações de conflito e mecanismos de rollback para garantir que possam ser executados em paralelo sem afetar a consistência do estado final, além de realizar melhorias no banco de dados.

Claro, a melhoria da eficiência da camada de execução não se limita a apenas uma forma de paralelização; a otimização da etapa de execução também pode ser feita reduzindo as operações de leitura e escrita que uma transação requer no banco de dados. A melhoria da velocidade de toda a cadeia abrange um escopo mais amplo, incluindo também a melhoria da eficiência da camada de consenso.

Por trás de cada tecnologia, há condições específicas que limitam seu uso. A paralelização é apenas uma das maneiras de aumentar a eficiência; a decisão final de usar essa tecnologia deve considerar se é amigável para os desenvolvedores e se pode ser implementada sem sacrificar a descentralização, entre outros fatores. A pilha de tecnologias não precisa ser sempre mais complexa; pelo menos para o Ethereum, a paralelização não é tão atraente. Se olharmos apenas pela perspectiva de aumentar a eficiência, adicionar paralelização pode não ser a solução ideal para o Ethereum, seja em termos de simplicidade ou considerando o roteiro atual centrado em Rollup do Ethereum.