Artículo reimpreso de: Techub News

Autor: Tia, Techub News

La blockchain sacrifica eficiencia debido a su diseño descentralizado, por lo que mejorar la velocidad de ejecución ha sido uno de los problemas que necesita solución. La "capa de ejecución" de la blockchain es la parte clave que maneja cada transacción y la añade a la cadena. Para acelerar la capacidad de procesamiento, mejorar la capa de ejecución se ha convertido en una de las estrategias centrales, y la ejecución paralela es un importante avance en este aspecto.

Las blockchains tradicionales suelen procesar transacciones una por una de forma secuencial, lo que limita en gran medida la velocidad de las transacciones, especialmente en redes con alta densidad de transacciones que generan congestión. Sin embargo, a través de la ejecución paralela, múltiples transacciones se pueden procesar simultáneamente, lo que mejora significativamente la eficiencia de ejecución y alivia la presión sobre la cadena.

Para entender mejor qué es la paralelización, comenzaremos con la ejecución y tomaremos como ejemplo Ethereum bajo el modelo PBS después de Merge para explicar qué es la ejecución, al mismo tiempo que mostramos la posición de la ejecución en todo el ciclo de vida de la transacción.

Etapas específicas de ejecución de transacciones

  1. Las transacciones ingresan al grupo de memoria y son filtradas y ordenadas: Esta es la etapa de preprocesamiento después de que se envían las transacciones, que incluye la interacción entre Mempool, Searcher y Builder, completando el filtrado y ordenamiento de las transacciones.

  2. Builder construye bloques (pero no ejecuta): Builder organiza las transacciones rentables en un bloque para completar el empaquetado y ordenamiento de las transacciones.

  3. Proposer verifica y envía el bloque: Una vez completada la construcción del bloque, Builder envía la propuesta del bloque a Proposer. Proposer verifica la estructura del bloque y el contenido de las transacciones, y luego envía oficialmente el bloque a la red para comenzar la ejecución.

  4. Ejecutar transacciones: Después de que se envía el bloque, los nodos ejecutan cada transacción dentro del bloque una por una. Esta es la etapa clave de actualización del estado, donde cada transacción desencadena llamadas a contratos inteligentes, cambios en saldos de cuentas o cambios de estado.

  5. Testigos dan fe: Los validadores dan fe de los resultados de la ejecución del bloque y de la raíz del estado, y lo consideran como una confirmación final. Esto asegura la autenticidad y validez del bloque en la capa de ejecución y evita inconsistencias.

  6. Sincronización de estado: Cada nodo sincroniza los resultados de la ejecución del bloque (como saldos de cuentas, actualizaciones de estado de contratos, etc.) a su estado local, calculando y almacenando una nueva raíz de estado después de ejecutar cada transacción, para usarla como estado inicial en el siguiente bloque.

Por supuesto, esta es solo la sincronización de estados de transacciones a nivel de bloque. Para mantener el estado más reciente en la cadena, generalmente los nodos sincronizan los datos bloque por bloque y continúan verificando bloques y estados. Pero para alcanzar la finalización bajo el mecanismo POS, los agregadores deben agregar las firmas de los testigos de cada Slot en una firma completa y pasarla al Proposer del siguiente Slot, y los validadores deben confirmar el estado de todos los bloques dentro de un Epoch basado en la cantidad de votos, formando un punto de control de estado de consenso temporal. Solo después de que dos Epoch consecutivos obtengan el apoyo de la mayoría de los validadores, el bloque y la transacción alcanzarán la finalización.

Desde la perspectiva de todo el ciclo de vida de la transacción, la ejecución ocurre después de que Proposer verifica la estructura del bloque y el contenido de las transacciones enviados por Builder. El proceso de ejecución real necesita procesar las transacciones una por una y actualizar el estado de las cuentas o contratos correspondientes. Una vez que se completan todas las ejecuciones de transacciones, Proposer calculará una nueva raíz de estado (raíz Merkle), que resume los resultados de la ejecución de todas las transacciones en el bloque actual y el estado global final. En términos sencillos, el proceso completo de ejecución del bloque incluye una serie de cálculos que deben completarse para llevar a Ethereum de un estado anterior a uno siguiente, desde la ejecución de cada transacción hasta el cálculo de la raíz Merkle.

Ejecución secuencial

Lo opuesto a la paralelización es la ejecución secuencial, que es la forma de ejecución más común en blockchain. Normalmente, las transacciones se ejecutan secuencialmente. Cuando una transacción finaliza su ejecución, Ethereum actualizará el estado de la cuenta y la información relacionada (como saldo, datos de almacenamiento del contrato) en el árbol de estado de la cuenta, generando un nuevo hash de estado de cuenta. Después de que todos los árboles de estado de cuentas se actualicen, se generará un hash del nodo raíz del árbol de estado conocido como raíz Merkle de estado. Después de completar la raíz Merkle del estado, la raíz Merkle de la transacción y la raíz Merkle de los recibos, se calculará el hash del encabezado del bloque, generando el hash del bloque correspondiente.

Y en esto, el orden de ejecución de las transacciones es crucial. Dado que el árbol Merkle es un árbol binario de hashes, los valores de raíz Merkle formados en diferentes órdenes serán diferentes.

Ejecución paralela

En un entorno de ejecución paralela, los nodos intentan procesar las transacciones en el bloque de manera paralela. No se ejecutan secuencialmente transacción por transacción, sino que se distribuyen a diferentes "rutas de ejecución" para que puedan ejecutarse simultáneamente. A través de la ejecución paralela, el sistema puede procesar las transacciones en el bloque de manera más eficiente, aumentando el rendimiento.

Una vez que se completan todas las ejecuciones de transacciones, los nodos consolidan los resultados de la ejecución (es decir, las actualizaciones de estado afectadas por las transacciones) para formar un nuevo estado del bloque. Este estado se añade a la blockchain, representando el estado global más reciente en la cadena.

Conflicto de estado

Dado que la paralelización procesa transacciones en diferentes rutas simultáneamente, una de las grandes dificultades del paralelismo es el conflicto de estado. Es posible que múltiples transacciones realicen operaciones de lectura o escritura en la misma parte de datos (estado) de la blockchain al mismo tiempo. Si no se manejan adecuadamente, esto puede llevar a resultados de ejecución indeterminados. Debido a que el orden de actualización del estado es diferente, el resultado final del cálculo también variará. Por ejemplo,

Supongamos que hay dos transacciones, transacción A y transacción B, que intentan actualizar el saldo de la misma cuenta:

  • Transacción A: Aumentar el saldo de la cuenta en 10.

  • Transacción B: Aumentar el saldo de la cuenta en 20.

El saldo inicial de la cuenta es 100.

Si ejecutamos en serie, el resultado del orden de ejecución es determinista:

1. Ejecutar primero la transacción A, luego la transacción B:

  • El saldo de la cuenta aumenta primero en 10, alcanzando 110.

  • Luego aumenta en 20, alcanzando 130.

2. Ejecutar primero la transacción B, luego la transacción A:

  • El saldo de la cuenta aumenta primero en 20, alcanzando 120.

  • Luego aumenta en 10, alcanzando 130.

En ambos órdenes, el saldo final es 130, porque el sistema asegura la consistencia en el orden de ejecución de las transacciones.

Pero en un entorno de ejecución paralela, la transacción A y la transacción B pueden leer simultáneamente el saldo inicial de 100 y realizar sus respectivos cálculos:

  1. La transacción A lee un saldo de 100, calcula y actualiza el saldo a 110.

  2. La transacción B también lee un saldo de 100, calcula y actualiza el saldo a 120.

En este caso, debido a que las transacciones se ejecutan simultáneamente, el saldo final solo se actualiza a 120 en lugar de 130, porque las operaciones de la transacción A y la transacción B "anulan" los resultados de la otra, provocando un conflicto de estado.

Este tipo de problemas de conflicto de estado se denominan comúnmente "sobrescritura de datos", es decir, cuando las transacciones intentan modificar los mismos datos simultáneamente, pueden sobrescribir los resultados de cálculo de la otra, lo que lleva a un estado final incorrecto. Otro problema potencial del conflicto de estado es la incapacidad de garantizar el orden de ejecución. Dado que múltiples transacciones completan operaciones en diferentes momentos, esto puede resultar en diferentes órdenes de ejecución. Un orden diferente puede llevar a diferentes resultados de cálculo, haciendo que el resultado sea indeterminado.

Para evitar esta incertidumbre, los sistemas de ejecución paralela de blockchain suelen introducir mecanismos de detección de conflictos y retroceso, o analizar las dependencias de las transacciones con anticipación, para asegurarse de que se ejecuten en paralelo sin afectar la consistencia del estado final.

Paralelismo optimista y determinista

Hay dos enfoques para abordar los posibles problemas de conflicto de estado: paralelismo determinista y paralelismo optimista. Ambos modos tienen sus compensaciones en términos de eficiencia y complejidad de diseño.

El paralelismo determinista requiere declarar el acceso al estado de antemano; los validadores o secuenciadores revisan el acceso al estado declarado al ordenar las transacciones. Si varias transacciones intentan escribir en el mismo estado, se marcarán como conflictos para evitar la ejecución simultánea. Las diferentes cadenas implementan de manera diferente la declaración anticipada del acceso al estado, pero generalmente incluyen las siguientes formas:

  • A través de restricciones normativas de contratos: Los desarrolladores establecen directamente el alcance de acceso al estado dentro de los contratos inteligentes. Por ejemplo, las transferencias de tokens ERC-20 requieren acceder a los campos de saldo del remitente y del receptor.

  • A través de la declaración de datos estructurados en transacciones: Se añaden campos específicos a las transacciones para marcar el acceso al estado.

  • A través del análisis de compiladores: Los compiladores de lenguajes de alto nivel pueden analizar estáticamente el código del contrato y generar automáticamente un conjunto de accesos al estado.

  • A través de declaraciones forzadas por marcos: Algunos marcos requieren que los desarrolladores especifiquen explícitamente el estado que necesitan acceder al llamar a funciones.

El paralelismo optimista procesa las transacciones optimistamente primero y, cuando ocurre un conflicto, vuelve a ejecutar las transacciones afectadas en orden. Para evitar conflictos en la medida de lo posible, el núcleo del diseño de paralelismo optimista es predecir y suponer rápidamente el estado a través de datos históricos, análisis estático, etc. Es decir, el sistema asume que ciertas operaciones o actualizaciones de estado son válidas sin una verificación completa para evitar esperar todos los procesos de verificación, mejorando así el rendimiento y la capacidad de procesamiento.

Aunque el paralelismo optimista puede evitar conflictos en la medida de lo posible mediante algunas rápidas predicciones y suposiciones sobre el estado, todavía enfrenta algunos desafíos inevitables, especialmente en la ejecución de contratos o transacciones entre cadenas; si los conflictos ocurren con frecuencia, la reejecución puede ralentizar significativamente el rendimiento del sistema y aumentar el consumo de recursos de cálculo.

El paralelismo determinista evita posibles conflictos de paralelismo optimista al realizar verificaciones de dependencia de estado antes de las transacciones, pero debido a que necesita declarar con precisión las dependencias de estado antes de enviar transacciones, esto plantea mayores exigencias para los desarrolladores, aumentando así la complejidad de implementación.

Dilema de paralelismo en EVM

No solo hay diferencias entre el paralelismo determinista y el optimista en el tratamiento de conflictos de estado, sino que también es necesario considerar desde la perspectiva de la arquitectura de la base de datos de la cadena durante el proceso de implementación del paralelismo. El problema de conflictos de estado en el EVM bajo la arquitectura de árbol Merkle es particularmente complicado. Un árbol Merkle es una estructura hash jerárquica, y cada vez que una transacción modifica algún dato de estado, el hash raíz del árbol Merkle también necesita actualizarse. Este proceso de actualización es recursivo, calculándose capa por capa desde las hojas hasta la raíz. Debido a que el hash es irreversible, es decir, solo se puede calcular la capa superior una vez que se han completado los cambios en los datos de la capa inferior, esta característica dificulta las actualizaciones paralelas.

Si dos transacciones se ejecutan en paralelo y acceden al mismo estado (como el saldo de la cuenta), puede resultar en un conflicto de nodos en el árbol Merkle. Resolver este tipo de conflictos generalmente requiere mecanismos de gestión de transacciones adicionales para asegurar que se obtenga un hash raíz consistente en múltiples ramas. Esto no es fácil de implementar para el EVM, ya que necesita encontrar un equilibrio entre la paralelización y la consistencia del estado.

Soluciones paralelas no EVM

Solana

A diferencia del árbol de estado global de Ethereum, Solana utiliza un modelo de cuentas. Cada cuenta es un espacio de almacenamiento independiente, almacenado en un libro de contabilidad, evitando así el problema de conflictos de rutas.

Solana es paralelismo determinista. En Solana, cada transacción debe declarar explícitamente las cuentas que se accederán y los permisos de acceso requeridos (solo lectura o lectura/escritura) al momento de enviarse. Este diseño permite que los nodos de la blockchain analicen anticipadamente los recursos que cada transacción necesita acceder antes de su ejecución. Debido a que las transacciones ya han declarado todas las dependencias de cuentas antes de comenzar a ejecutarse, los nodos pueden determinar qué transacciones accederán a la misma cuenta y cuáles pueden ejecutarse en paralelo de manera segura, logrando así una programación inteligente y evitando conflictos, estableciendo así la base para la programación paralela.

Dado que cada transacción declara las cuentas y permisos que necesita acceder antes de ejecutarse, Solana puede verificar si hay dependencias de cuentas entre las transacciones (modelo Sealevel). Si no hay cuentas compartidas para lectura y escritura entre las transacciones, el sistema puede asignarlas a diferentes procesadores para ejecutarse en paralelo.

Aptos

El diseño de ejecución paralela de Aptos es muy diferente al de Ethereum, realizando algunas innovaciones clave en su arquitectura y mecanismo, que se reflejan principalmente en el modelo de cuentas y el almacenamiento de estado.

Ethereum necesita actualizar con frecuencia el árbol de estado global (MPT) al ejecutar transacciones. Todos los estados de cuentas y contratos se almacenan en un árbol de estado compartido, y cada transacción necesita acceder y actualizar una parte de este árbol de estado. En cambio, Aptos divide las cuentas en unidades de estado independientes, donde cada objeto es un par clave-valor independiente, y los objetos pueden existir de manera independiente entre sí, conectándose solo cuando hay una relación de referencia explícita. Los objetos no comparten rutas en el árbol, por lo que no habrá competencia de bloqueo y pueden ejecutarse completamente en paralelo.

La estructura de datos subyacente de Aptos es el Árbol Merkle Jellyfish. El estado de cada objeto se almacena finalmente en el JMT como un par clave-valor independiente. A diferencia del MPT de Ethereum, el Jellyfish Merkle Tree tiene una estructura de árbol binario completo, lo que simplifica las rutas de almacenamiento y consulta de los nodos, reduciendo significativamente el tiempo de verificación. Además, la posición de cada cuenta en el árbol es fija, y los nodos en el árbol se almacenan de forma independiente, lo que permite que las actualizaciones y búsquedas de múltiples cuentas se realicen en paralelo.

Aptos es paralelismo optimista, no necesita proporcionar previamente todas las dependencias de las cuentas declaradas. Para ello, Aptos utiliza Block-STM, que utiliza el orden de transacciones preestablecido para estimar dependencias y así reducir la cantidad de abortos.

EVM paralela

En comparación con la paralelización no EVM, la EVM paralela enfrenta mayores dificultades técnicas al abordar problemas de dependencia de estado, detección de conflictos, gestión de Gas y mecanismos de retroceso. Para comprender mejor esto, podemos referirnos a cómo algunos proyectos de EVM paralela (como Sui, Monad, Canto) abordan estos problemas.

Sui

Al igual que Aptos, Sui también utiliza un modelo de objetos para manejar el estado, tratando cada objeto (por ejemplo, cuentas, estado de contrato inteligente) como un recurso independiente, que se distingue por un identificador único. Cuando las transacciones involucran diferentes objetos, estas pueden procesarse en paralelo, ya que operan sobre estados diferentes y no generan conflictos directos.

Aunque Sui utiliza un modelo de objetos para gestionar el estado, para ser compatible con EVM, la arquitectura de Sui cuenta con una capa de adaptación adicional o un mecanismo de abstracción que conecta el modelo de objetos con el modelo de cuentas de EVM.

En Sui, la programación de transacciones utiliza una estrategia de paralelismo optimista, suponiendo que no hay conflictos entre las transacciones. Si ocurre un conflicto, el sistema utiliza un mecanismo de retroceso para restaurar el estado.

Sui utiliza un modelo de objetos y tecnología de aislamiento de estado, lo que puede evitar eficazmente problemas de dependencia de estado. Cada objeto es un recurso independiente, y diferentes transacciones pueden ejecutarse en paralelo, aumentando así el rendimiento y la eficiencia. Sin embargo, el trade-off de este enfoque es la complejidad del modelo de objetos y el costo del mecanismo de retroceso. Si ocurre un conflicto entre transacciones, es necesario retroceder parte del estado, lo que aumenta la carga del sistema y puede afectar la eficiencia del procesamiento paralelo. En comparación con sistemas paralelos no EVM (como Solana), Sui requiere más recursos de cálculo y almacenamiento para mantener una alta eficiencia de paralelismo.

Monad

Al igual que Sui, Monad también adopta el paralelismo optimista. Pero el paralelismo optimista de Monad predice algunas transacciones con relaciones de dependencia antes de la ejecución específica, y esta predicción se realiza principalmente a través del analizador de código estático de Monad. La predicción requiere acceso al estado, y la forma en que la base de datos de Ethereum almacena el estado dificulta mucho el acceso. Para hacer que la paralelización sea más eficiente en el proceso de lectura de estado, Monad también ha reestructurado la base de datos.

El árbol de estado Monad se divide por particiones, cada partición mantiene su propio subárbol de estado. Al actualizar, solo se necesita modificar los fragmentos correspondientes sin reconstruir todo el árbol de estado. Se utiliza una tabla de índices de estado para localizar rápidamente el estado dentro de la partición, reduciendo la interacción entre particiones.

Resumen

El núcleo de la paralelización es mejorar la eficiencia de ejecución en la capa de ejecución a través de la ejecución en múltiples rutas. Para lograr la ejecución en múltiples rutas, la cadena debe llevar a cabo una serie de procedimientos como la detección de conflictos y mecanismos de retroceso para asegurar que se ejecuten en paralelo sin afectar la consistencia del estado final, y realizar ciertas mejoras en la base de datos.

Por supuesto, la mejora de la eficiencia en la capa de ejecución no se limita a la paralelización; la optimización en esta etapa también se puede lograr reduciendo las operaciones de lectura y escritura que una transacción requiere en la base de datos. Y la mejora de la velocidad de toda la cadena abarca un rango más amplio, incluyendo también mejoras en la eficiencia de la capa de consenso.

Cada tecnología tiene sus condiciones específicas de limitación. La paralelización es solo una de las formas de mejorar la eficiencia; la decisión de usar esta tecnología también debe considerar si es amigable para los desarrolladores y si se puede lograr sin sacrificar la descentralización, entre otros. La acumulación de tecnología no siempre es mejor, al menos para Ethereum, la paralelización no es tan atractiva; si se considera solo desde la perspectiva de mejorar la eficiencia, agregar paralelización no es la mejor solución para Ethereum, tanto en términos de simplicidad como en relación con la hoja de ruta centrada en Rollup de Ethereum en este momento.