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 urgentes a resolver. La 'capa de ejecución' de la blockchain es la parte clave que maneja cada transacción y la agrega a la cadena. Para acelerar la capacidad de procesamiento, mejorar la capa de ejecución se convierte en una de las estrategias centrales, y la ejecución paralela es un importante avance en este aspecto.

Las blockchains tradicionales generalmente procesan transacciones una por una de manera secuencial, lo que limita significativamente la velocidad de las transacciones, especialmente en redes con alta demanda de transacciones que pueden causar congestión. Sin embargo, mediante la ejecución paralela, múltiples transacciones pueden ser procesadas simultáneamente, mejorando así la eficiencia de ejecución y aliviando la presión sobre la cadena.

Para entender mejor qué es la paralelización, comenzaremos introduciendo la ejecución y usaremos Ethereum en el modo PBS posterior a la fusión como ejemplo 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.

Detalles específicos de la ejecución de transacciones

  1. Las transacciones entran en el pool de memoria y son filtradas y ordenadas: esta es la fase de preprocesamiento después de que las transacciones son enviadas, que incluye la interacción entre Mempool, Searcher y Builder, completando la filtración y ordenación de transacciones.

  2. Builder construye el bloque (pero no lo ejecuta): Builder organiza las transacciones rentables en un bloque para completar el empaquetado y ordenación de transacciones.

  3. Proposer verifica y envía el bloque: una vez que el bloque está construido, 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 el bloque es enviado, los nodos ejecutan cada transacción dentro del bloque una por una. Esta es la fase clave de actualización del estado, donde cada transacción desencadena llamadas a contratos inteligentes, cambios en los saldos de cuentas o cambios en el estado.

  5. Testigos testifican: los validadores testifican el resultado de la ejecución del bloque y la raíz del estado, y lo certifican como la confirmación final. Esto asegura la autenticidad y validez del bloque en la capa de ejecución y previene inconsistencias.

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

Por supuesto, esto es solo la sincronización de estado de las transacciones a nivel de bloque; para mantener el estado más reciente en la cadena, normalmente, los nodos sincronizan datos bloque por bloque y continúan validando bloques y estados. Pero para alcanzar la finalización bajo el mecanismo POS, los agregadores también necesitan agregar las firmas de los testigos de cada Slot en una firma completa y transmitirla al proponente del siguiente Slot, y los validadores necesitan confirmar el estado de todos los bloques dentro de ese Epoch basado en la cantidad de votos después de un Epoch, creando un punto de verificación de consenso temporal. Solo cuando dos Epoch consecutivos obtienen el apoyo de la mayoría de los validadores, los bloques y las transacciones 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 requiere procesar las transacciones una por una y actualizar el estado correspondiente de cuentas o contratos. Una vez que todas las transacciones se han ejecutado, Proposer calculará una nueva raíz de estado (raíz de Merkle), que es un resumen de los resultados de la ejecución de todas las transacciones actuales del bloque y el estado global final. En términos simples, el proceso completo de ejecución del bloque incluye una serie de cálculos que deben completarse para transformar Ethereum de un estado anterior a uno siguiente, desde la ejecución de cada transacción hasta el cálculo de la raíz de Merkle.

Ejecución secuencial

Lo opuesto a la paralelización es la ejecución secuencial, que es el método de ejecución más común en las blockchains en este momento. Normalmente, las transacciones se ejecutan secuencialmente una por una. Cuando una transacción se completa, Ethereum actualiza el estado de la cuenta y la información relevante (como saldo, datos almacenados en contratos) en el árbol de estado de cuentas, generándose un nuevo hash de estado de cuenta. Una vez que todos los árboles de estado de cuentas se han actualizado, se genera el hash de la raíz del árbol de Merkle conocido como raíz Merkle de estado. Tras completar la raíz Merkle de estado, la raíz Merkle de transacción y la raíz Merkle de recibo, se realiza el cálculo hash del encabezado del bloque, generando el hash del bloque.

Y en este contexto, el orden de ejecución de las transacciones es crucial. Dado que el árbol de Merkle es un árbol binario de hashes, los valores de raíz de 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 una por una en orden, sino que se distribuyen a diferentes 'caminos de ejecución' para que puedan ejecutarse simultáneamente. A través de la ejecución paralela, el sistema puede manejar de manera más eficiente las transacciones en el bloque, aumentando la capacidad de procesamiento.

Después de que todas las transacciones se han completado, los nodos resumen los resultados de la ejecución (es decir, las actualizaciones de estado afectadas por las transacciones) para formar un nuevo estado de bloque. Este estado se añadirá a la cadena de bloques, representando el estado global más reciente en la cadena.

Conflictos de estado

Dado que el paralelismo procesa transacciones en diferentes rutas simultáneamente, una gran dificultad del paralelismo es el conflicto de estado. Es decir, puede haber múltiples transacciones que intenten leer o escribir datos (estado) en la misma parte de la blockchain en el mismo intervalo de tiempo. Si no se maneja adecuadamente, esto puede llevar a resultados de ejecución inciertos. Debido a que el orden de actualización de estado es diferente, el resultado final del cálculo también puede ser diferente. 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 la transacción A primero, y luego la transacción B:

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

  • Aumenta nuevamente en 20, alcanzando finalmente 130.

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

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

  • Aumentar 10, terminando en 130.

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

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

  1. La transacción A lee un saldo de 100, y después de calcular, actualiza el saldo a 110.

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

En este caso, dado que las transacciones se ejecutan simultáneamente, el saldo final solo se actualiza a 120 en lugar de 130, ya que las operaciones de las transacciones A y B 'cubrieron' los resultados de cada una, causando un conflicto de estado.

Este tipo de problemas de conflicto de estado suelen denominarse 'cobertura de datos', es decir, cuando las transacciones intentan modificar datos idénticos simultáneamente, pueden sobrescribir los resultados de cálculo de cada una, resultando en un estado final incorrecto. Otro tipo de conflicto de estado que puede surgir es que no se puede garantizar el orden de ejecución. Dado que múltiples transacciones completan operaciones en diferentes momentos, esto puede provocar diferentes órdenes de ejecución. Un orden diferente puede llevar a diferentes resultados de cálculo, lo que hace que los resultados sean inciertos.

Para evitar esta incertidumbre, los sistemas de ejecución paralela de blockchain suelen introducir mecanismos de detección de conflictos y retroceso, o realizan análisis de dependencia de transacciones por adelantado para asegurarse de que se ejecuten en paralelo sin afectar la consistencia del estado final.

Paralelismo optimista y paralelismo determinista

Hay dos métodos para abordar los posibles problemas de conflictos de estado: paralelismo determinista y paralelismo optimista. Estos dos modos tienen sus compensaciones en eficiencia y complejidad de diseño.

El paralelismo determinista requiere declarar el acceso al estado por adelantado; los validadores o secuenciadores verificarán el acceso declarado al ordenar las transacciones. Si múltiples transacciones intentan escribir al mismo estado, estas transacciones se marcarán como conflictos y se evitará su ejecución simultánea. Las distintas cadenas implementan de diferentes maneras la declaración anticipada de acceso al estado, pero en general incluyen las siguientes formas:

  • A través de restricciones de especificación de contrato: los desarrolladores especifican directamente el rango de acceso al estado en el contrato inteligente. Por ejemplo, una transferencia de token ERC-20 necesita acceder a los campos de saldo del remitente y del receptor.

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

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

  • A través de declaraciones impuestas por el marco: algunos marcos requieren que los desarrolladores especifiquen explícitamente el estado que necesita ser accedido 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 la ocurrencia de conflictos, el núcleo del diseño de paralelismo optimista es hacer rápidas predicciones y suposiciones sobre 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, evitando esperar todos los procesos de verificación, mejorando así el rendimiento y la capacidad de procesamiento.

Aunque el paralelismo optimista puede evitar los conflictos mediante algunas predicciones rápidas y suposiciones sobre el estado, todavía hay 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 computacionales.

El paralelismo determinista evita conflictos potenciales de paralelismo optimista mediante el chequeo de dependencia de estado antes de la transacción, pero requiere que las dependencias de estado sean declaradas con precisión antes del envío de la transacción, lo que plantea mayores exigencias a los desarrolladores y aumenta la complejidad de la implementación.

Dilema de EVM Paralelo

No solo hay distinciones entre determinismo y optimismo en el tratamiento de conflictos de estado; en la implementación concreta de la paralelización, también hay que considerar desde la perspectiva de la arquitectura de la base de datos de la cadena. Los problemas de conflictos de estado en la EVM bajo la arquitectura de árbol de Merkle son especialmente difíciles. Un árbol de Merkle es una estructura de hash jerárquica, y cada vez que una transacción modifica ciertos datos de estado, la raíz de hash del árbol de Merkle también necesita ser actualizada. Este proceso de actualización es recursivo, calculándose desde las hojas hacia arriba hasta la raíz. Debido a que el hash es irreversible, solo se puede calcular la capa superior después de que se complete el cambio de datos en la capa inferior, lo que hace que sea muy difícil actualizar en paralelo.

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

Soluciones de paralelismo no EVM

Solana

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

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

Debido a que cada transacción ha declarado 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 de lectura/escritura compartidas entre las transacciones, el sistema puede asignarlas a diferentes procesadores para que se ejecuten en paralelo.

Aptos

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

Ethereum necesita actualizar frecuentemente 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 cualquier transacción necesita acceder y actualizar una parte de este árbol de estado. Aptos, en cambio, divide las cuentas en unidades de estado independientes, donde cada objeto es un par de clave-valor independiente, existiendo de manera independiente y sin influenciarse mutuamente, asociándose solo cuando hay relaciones de referencia explícitas. No hay rutas de árbol comunes entre objetos, evitando la competencia por bloqueos y permitiendo una ejecución completamente paralela.

La estructura de datos subyacente de Aptos es Jellyfish Merkle Tree. El estado de cada objeto se almacena finalmente en JMT como pares de clave-valor independientes. A diferencia del MPT de Ethereum, Jellyfish Merkle Tree tiene una forma de árbol binario completo, lo que simplifica las rutas de almacenamiento y consulta de 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 manera independiente, lo que permite que las actualizaciones y búsquedas de múltiples cuentas se realicen en paralelo.

Aptos es paralelo optimista, no necesita proporcionar por adelantado todas las dependencias de las cuentas declaradas. Para ello, Aptos utiliza Block-STM, que utiliza un orden de transacciones preestablecido para estimar las dependencias, reduciendo así la cantidad de abortos.

EVM paralelo

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

Sui

Sui, al igual que Aptos, también utiliza un modelo de objetos para manejar el estado, usando cada objeto (por ejemplo, cuentas, estado de contratos inteligentes) como recursos independientes, diferenciados por identificadores únicos de objetos. Cuando las transacciones involucran diferentes objetos, estas transacciones pueden procesarse en paralelo, ya que operan sobre diferentes estados 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 conecta el modelo de objetos y el modelo de cuentas de EVM a través de capas de adaptación o mecanismos de abstracción adicionales.

En Sui, la programación de transacciones utiliza una estrategia de paralelismo optimista, asumiendo 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 los problemas de dependencia de estado. Cada objeto actúa como un recurso independiente; diferentes transacciones pueden ejecutarse en paralelo, aumentando así el rendimiento y la eficiencia. Pero la compensación de este método es la complejidad del modelo de objetos y el costo del mecanismo de retroceso. Si ocurren conflictos entre transacciones, se necesita retroceder parte del estado, lo que aumentará 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 computacionales y de almacenamiento para mantener la alta eficiencia en la paralelización.

Monad

Al igual que Sui, Monad también utiliza el paralelismo optimista. Sin embargo, el paralelismo optimista de Monad, antes de la ejecución de la transacción, predecirá algunas transacciones con dependencias, 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 Ethereum almacena el estado en su base de datos hace que sea muy difícil acceder al estado. Para hacer que la paralelización sea más eficiente en el proceso de lectura del estado, Monad también ha reestructurado su base de datos.

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

Resumen

El núcleo de la paralelización es aumentar la eficiencia de la capa de ejecución mediante la ejecución de múltiples caminos, y para lograr la ejecución de múltiples caminos, la cadena necesita realizar una serie de cosas como detección de conflictos y mecanismos de retroceso para asegurarse de que se ejecuten en paralelo sin afectar la consistencia del estado final, y hacer 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 de la fase de ejecución también puede lograrse reduciendo las operaciones de lectura/escritura que una transacción requiere de la base de datos. Además, la mejora de la velocidad de toda la cadena implica un alcance más amplio, incluyendo también la mejora de la eficiencia de la capa de consenso.

Cada tecnología tiene sus condiciones específicas de limitación. La paralelización es solo una forma de mejorar la eficiencia; la decisión final sobre si utilizar esta tecnología también debe considerar si es amigable para los desarrolladores, si puede completarse sin sacrificar la descentralización, etc. La pila tecnológica no siempre debe ser más, al menos en el caso de Ethereum, la paralelización no parece tan atractiva; si se considera solo desde la perspectiva de mejorar la eficiencia, agregar paralelización no es la solución óptima para Ethereum, ya sea desde la simplicidad o considerando la hoja de ruta centrada en Rollup de Ethereum.