Autor: Tia, Techub News
La blockchain sacrifica la eficiencia debido a su diseño descentralizado, por lo que mejorar la velocidad de ejecución siempre 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 ha convertido en una de las estrategias centrales, y la ejecución en paralelo es un avance importante en este aspecto.
Las blockchains tradicionales suelen procesar transacciones de manera secuencial, lo que limita significativamente la velocidad de transacción, especialmente en redes con alta densidad de transacciones, lo que puede provocar congestión. Sin embargo, mediante la ejecución en paralelo, múltiples transacciones pueden ser procesadas simultáneamente, aumentando significativamente la eficiencia de ejecución y aliviando la presión en la cadena.
Para entender mejor qué es el paralelismo, primero comenzaremos a introducir la ejecución, utilizando Ethereum bajo el modelo PBS después de la fusión como ejemplo para explicar qué es la ejecución y mostrar la posición de la ejecución en todo el ciclo de vida de las transacciones.
Los detalles específicos de la ejecución de transacciones
Las transacciones entran en el pool de memoria y son filtradas y ordenadas: esta es la fase de preprocesamiento después de que la transacción ha sido enviada, que incluye la interacción entre Mempool, Searcher y Builder, completando la filtración y el ordenamiento de las transacciones.
Builder construye el bloque (pero no lo ejecuta): Builder organiza las transacciones rentables en un bloque para completar el empaquetado y ordenamiento de las transacciones.
Proposer verifica y envía el bloque: después de que el bloque ha sido construido, Builder envía la propuesta del bloque a Proposer. Proposer verifica la estructura del bloque y el contenido de las transacciones, y luego formalmente envía el bloque a la red para iniciar la ejecución.
Ejecutando transacciones: después de que el bloque es enviado, los nodos ejecutan las transacciones en el bloque una por una. Esta es la etapa clave de actualización del estado, donde cada transacción desencadenará llamadas a contratos inteligentes, cambios en el saldo de la cuenta o cambios en el estado.
Testigos dan fe: los validadores dan fe de los resultados de la ejecución del bloque y de la raíz del estado, y los consideran como una confirmación final. Esto asegura la autenticidad y validez del bloque en la capa de ejecución y previene inconsistencias.
Sincronización del estado: cada nodo sincroniza los resultados de ejecución del bloque (como el saldo de la cuenta, actualizaciones del estado del contrato, 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 del estado de las transacciones a nivel de bloque. Para mantener el estado más reciente en la cadena, los nodos generalmente sincronizan los datos bloque por bloque y continúan verificando los bloques y el estado. Pero para alcanzar la finalización bajo el mecanismo POS, también se necesita que los agregadores agreguen las firmas de los testigos en cada Slot en una firma completa y las pasen al proponente del siguiente Slot, y los validadores deben, después de un Epoch, confirmar el estado de todos los bloques en ese Epoch basado en la cantidad de votos, formando un punto de control de estado de consenso temporal. Solo después de que dos Epochs consecutivos obtengan el apoyo de la mayoría de los testigos, 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 el Proposer valida la estructura del bloque y el contenido de las transacciones enviadas por el Builder. El proceso de ejecución real necesita procesar las transacciones una por una y actualizar el estado de la cuenta o del contrato correspondiente. Una vez que se completan todas las transacciones, el Proposer calculará una nueva raíz de estado (raíz Merkle), que es un resumen de los resultados de ejecución de todas las transacciones del bloque actual y del estado global final. En términos simples, el proceso completo de ejecución del bloque incluye una serie de cálculos necesarios para transformar Ethereum de un estado anterior a un siguiente, desde la ejecución de cada transacción hasta el cálculo de la raíz Merkle.
Ejecución secuencial
En contraste con la ejecución en paralelo, la ejecución secuencial es el método de ejecución más común en las blockchains actuales. Normalmente, las transacciones se ejecutan secuencialmente. Una vez que una transacción se completa, Ethereum actualiza el estado de la cuenta y la información relacionada (como el saldo, los datos de almacenamiento del contrato) en el árbol de estado de la cuenta, generando un nuevo hash de estado de cuenta. Una vez que todos los árboles de estado de cuenta se actualizan, se genera el hash del nodo raíz del árbol de estado, conocido como raíz Merkle de estado. Después de completar la raíz Merkle de estado, la raíz Merkle de transacción y la raíz Merkle de recibos, se realiza el cálculo de hash del encabezado del bloque, generando el hash del bloque.
Y en esto, el orden de ejecución de las transacciones es crucial. Dado que el árbol de Merkle es un árbol binario de hash, el valor de la raíz de Merkle formado en diferentes órdenes será diferente.
Ejecución en paralelo
En un entorno de ejecución en paralelo, los nodos intentarán procesar las transacciones en el bloque de manera paralela. No se ejecutan transacciones una por una en secuencia, sino que se distribuyen en diferentes 'rutas de ejecución' para que puedan ejecutarse simultáneamente. Al ejecutar en paralelo, el sistema puede procesar las transacciones en el bloque de manera más eficiente, aumentando el rendimiento.
Una vez que todas las transacciones se han ejecutado, el nodo resume los resultados de ejecución (es decir, las actualizaciones de estado afectadas por las transacciones) para formar un nuevo estado del bloque. Este estado se agrega a la blockchain, representando el estado global más reciente en la cadena.
Conflictos de estado
Dado que el paralelismo procesa transacciones en diferentes rutas simultáneamente, uno de los principales desafíos del paralelismo son los conflictos de estado. Es decir, puede haber múltiples transacciones que lean o escriban sobre la misma parte de los datos (estado) de la blockchain en el mismo período de tiempo. Si esta situación no se maneja adecuadamente, puede dar lugar a resultados de ejecución inciertos. Dado que el orden de las actualizaciones del estado es diferente, el resultado final de los cálculos también será diferente. Por ejemplo,
Supongamos que hay dos transacciones, la transacción A y la transacción B, ambas 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 a 10, convirtiéndose en 110.
Luego aumenta a 20, resultando en 130.
2. Ejecutar primero la transacción B, luego la transacción A:
El saldo de la cuenta aumenta primero a 20, convirtiéndose en 120.
Luego aumenta a 10, resultando en 130.
En ambas secuencias, 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 en paralelo, las transacciones A y B pueden leer simultáneamente el saldo inicial de 100 y realizar sus propios cálculos:
La transacción A lee un saldo de 100, calcula y actualiza el saldo a 110.
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 'cubrieron' el resultado de cada una, creando un conflicto de estado.
Este tipo de problema de conflicto de estado a menudo se denomina 'sobrescritura de datos', es decir, cuando las transacciones intentan modificar los mismos datos al mismo tiempo, pueden sobrescribir los resultados de cálculo de cada una, lo que lleva a un estado final incorrecto. Otro tipo de conflicto de estado que puede surgir es la incapacidad de garantizar el orden de ejecución. Dado que múltiples transacciones completan operaciones en diferentes momentos, esto puede dar lugar a diferentes órdenes de ejecución. Un orden diferente puede resultar en diferentes resultados de cálculo, lo que hace que el resultado sea incierto.
Para evitar esta incertidumbre, los sistemas de ejecución paralela de blockchain generalmente introducen algunos mecanismos de detección de conflictos y reversión, o analizan anticipadamente las dependencias de las transacciones para asegurar que se ejecuten en paralelo sin afectar la consistencia del estado final.
Paralelismo optimista vs. paralelismo determinista
Hay dos enfoques para abordar el posible problema de conflicto de estado: paralelismo determinista y paralelismo optimista. Ambas modalidades tienen sus compensaciones en términos de eficiencia y complejidad de diseño.
El paralelismo determinista requiere declarar con anticipación el acceso al estado, el validador o el secuenciador revisará el acceso declarado al estado al ordenar las transacciones. Si hay múltiples transacciones que intentan escribir en el mismo estado, estas se marcarán como conflictos para evitar su ejecución simultánea. Las diferentes cadenas tienen diversas implementaciones para declarar anticipadamente el acceso al estado, pero generalmente incluyen las siguientes formas:
A través de especificaciones contractuales: los desarrolladores especifican directamente el rango de acceso al estado en los contratos inteligentes. Por ejemplo, la transferencia de tokens ERC-20 requiere acceder a los campos de saldo del remitente y del receptor.
A través de la estructuración de datos de transacción: añadir campos específicos en la transacción para marcar el acceso al estado.
A través del análisis del compilador: 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.
Declaración forzada a través del marco: ciertos marcos requieren que los desarrolladores especifiquen explícitamente el estado que necesitan acceder al llamar a la función
El paralelismo optimista procesará las transacciones de manera optimista primero, y cuando se produzcan conflictos, volverá a ejecutar las transacciones afectadas en orden. Para evitar en la medida de lo posible que ocurran conflictos, el núcleo del diseño de paralelismo optimista es hacer rápidas previsiones y suposiciones sobre el estado mediante datos históricos, análisis estáticos, etc. Es decir, el sistema supone que ciertas operaciones o actualizaciones de estado son válidas sin una verificación completa, tratando de evitar esperar todos los procesos de verificación, para así mejorar el rendimiento y el rendimiento.
Aunque el paralelismo optimista puede evitar conflictos mediante algunas previsiones rápidas y suposiciones sobre el estado, todavía hay algunos desafíos inevitables, especialmente en la ejecución de contratos o transacciones intercadena. Si los conflictos ocurren con frecuencia, la reejecución puede reducir significativamente el rendimiento del sistema y aumentar el consumo de recursos computacionales.
El paralelismo determinista evita situaciones de conflicto que podrían surgir en el paralelismo optimista mediante la verificación de dependencias de estado antes de las transacciones, pero como requiere declarar con precisión las dependencias de estado antes de enviar la transacción, esto plantea mayores requisitos para los desarrolladores, aumentando la complejidad de la implementación.
Dilema de paralelismo EVM
El tratamiento de los conflictos de estado no solo se divide en determinista y optimista, sino que en el proceso específico de implementación en paralelo, también es necesario considerar desde la perspectiva de la arquitectura de bases de datos en cadena. El problema de conflicto de estado en la paralelización es especialmente difícil en el EVM bajo la arquitectura de árbol de Merkle. El árbol de Merkle es una estructura de hash jerárquica, y cada vez que una transacción modifica ciertos datos de estado, el valor hash raíz del árbol de Merkle también necesita ser actualizado. 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 difícil actualizar en paralelo, ya que solo se puede calcular la capa superior después de que se completen los cambios en los datos de la capa inferior.
Si dos transacciones se ejecutan en paralelo y acceden al mismo estado (como el saldo de la cuenta), se producirá un conflicto 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 valor hash raíz consistente en múltiples ramas. Esto no es fácil de implementar para el EVM, ya que necesita equilibrar 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 el libro mayor, evitando así el problema de conflictos de rutas.
Solana es paralelismo determinista. En Solana, cada transacción necesita declarar explícitamente las cuentas que accederá y los permisos de acceso necesarios (solo lectura o lectura/escritura) al ser enviada. Este diseño permite que los nodos de la blockchain puedan analizar anticipadamente los recursos que cada transacción necesitará acceder antes de su ejecución. Dado que se han declarado explícitamente todas las relaciones de dependencia de cuentas antes de que la transacción comience a ejecutarse, los nodos pueden determinar qué transacciones accederán a las mismas cuentas y cuáles pueden ejecutarse de manera segura en paralelo, logrando así una programación inteligente y evitando conflictos, estableciendo así la base para la programación en paralelo.
Debido a que cada transacción ha declarado previamente las cuentas y permisos que necesita acceder antes de la ejecución, Solana puede comprobar si hay dependencias de cuentas entre las transacciones (modelo Sealevel). Si las transacciones no comparten cuentas de lectura/escritura, el sistema puede asignarlas a diferentes procesadores para su ejecución en paralelo.
Aptos
El diseño de ejecución en paralelo de Aptos es muy diferente al de Ethereum, ya que presenta algunas innovaciones clave en su arquitectura y mecanismo, principalmente en el modelo de cuentas y 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. Aptos, por otro lado, 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í, solo se relacionan cuando hay una relación de referencia explícita. No hay caminos de árbol comunes entre los objetos, no hay competencia por bloqueos, y se puede ejecutar en paralelo por completo.
La estructura de datos subyacente de Aptos es Jellyfish Merkle Tree. El estado de cada objeto se almacena finalmente en JMT, como pares clave-valor independientes. A diferencia del MPT de Ethereum, 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 independientemente, permitiendo que las actualizaciones y búsquedas de múltiples cuentas se realicen en paralelo.
Aptos es paralelismo optimista, no necesita proporcionar previamente la declaración de todas las dependencias de cuentas. Para ello, Aptos utiliza Block-STM, que utiliza un orden de transacciones preestablecido para estimar las dependencias, reduciendo así el número de abortos.
EVM en paralelo
En comparación con los sistemas no EVM en paralelo, el EVM en paralelo enfrenta mayores dificultades técnicas en el manejo de la dependencia de estado, la detección de conflictos, la gestión de Gas y los mecanismos de reversión. Para comprender mejor esto, podemos referirnos a cómo algunos proyectos de EVM en paralelo (como Sui, Monad, Canto) abordan estos problemas.
Sui
Al igual que Aptos, Sui también utiliza un modelo de objetos para manejar el estado, usando cada objeto (como cuentas, estado de contratos inteligentes) como recursos independientes, y estos objetos se distinguen mediante identificadores únicos de objeto. Cuando las transacciones involucran diferentes objetos, estas pueden ser procesadas en paralelo, ya que operan sobre diferentes estados sin generar conflictos directos.
Aunque Sui utiliza un modelo de objetos para gestionar el estado, para ser compatible con EVM, la arquitectura de Sui utiliza una capa de adaptación adicional o mecanismos abstractos para conectar el modelo de objetos y el modelo de cuentas de EVM.
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 reversión para restaurar el estado.
Sui utiliza un modelo de objetos y técnicas de aislamiento de estado, lo que permite evitar eficazmente los problemas de dependencia de estado. Cada objeto es un recurso independiente, y diferentes transacciones pueden ejecutarse en paralelo, mejorando así el rendimiento y la eficiencia. Sin embargo, el trade-off de este método es la complejidad del modelo de objetos y el costo de los mecanismos de reversión. Si hay conflictos entre las transacciones, es necesario revertir parte del estado, lo que aumenta la carga en el sistema y puede afectar la eficiencia del procesamiento en paralelo. En comparación con los sistemas no EVM paralelos (como Solana), Sui requiere más recursos computacionales y de almacenamiento para mantener una alta eficiencia en la paralelización.
Monad
Al igual que Sui, Monad también adopta paralelismo optimista. Sin embargo, el paralelismo optimista de Monad realiza predicciones sobre algunas transacciones con dependencias antes de la ejecución específica de la transacción, y la 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 se almacenan los estados en la base de datos de Ethereum hace que acceder al estado sea muy difícil. Para hacer que la lectura del estado en el proceso de paralelización sea más eficiente, Monad también reestructuró 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 el fragmento relevante, sin necesidad de 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 del paralelismo es mejorar la eficiencia de ejecución en la capa de ejecución mediante un enfoque de ejecución de múltiples rutas, y para lograr la ejecución de múltiples rutas, la cadena necesita implementar una serie de mecanismos como detección de conflictos y mecanismos de reversión 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 la ejecución también puede lograrse reduciendo las operaciones de lectura y escritura necesarias en la base de datos para una transacción. La mejora de la velocidad de toda la cadena abarca un rango más amplio, incluyendo también la mejora de la eficiencia en la capa de consenso.
Cada tecnología tiene sus condiciones específicas de limitación. El paralelismo es solo una de las formas de mejorar la eficiencia, y la decisión final sobre si utilizar esta tecnología también debe considerar si es amigable para el desarrollador y si puede completarse sin sacrificar la descentralización, entre otras cosas. La acumulación de tecnologías no es siempre mejor, al menos para Ethereum, el paralelismo no resulta tan atractivo, si se considera únicamente desde la perspectiva de mejorar la eficiencia, la inclusión del paralelismo no es la solución óptima para Ethereum, ya sea desde la perspectiva de la simplicidad o considerando la hoja de ruta centrada en Rollup de Ethereum en la actualidad.