Artículo de blog

¿Por qué Soroban no usa un JIT?

Autor

Tomer Weller

Fecha de publicación

Soroban

JIT

Contratos inteligentes

Este blog explica nuestra decisión de usar un intérprete, específicamente WASMI, y no un JIT para el lanzamiento inicial de Soroban.

Una de las razones por las que elegimos WASM para Soroban es la abundancia de herramientas maduras. Estas incluyen múltiples entornos de ejecución, algunos de los cuales cuentan con un compilador JIT (just-in-time).

Un entorno de ejecución JIT introduce una fase de “compilación” anterior a la ejecución donde convierte un contrato en código nativo de una sola vez, para que la ejecución (tanto en la invocación actual como en las potencialmente subsiguientes) se ejecute a “velocidad completa de código nativo”. Un intérprete, en comparación, ejecuta el contrato “directamente” cargando repetidamente la siguiente instrucción WASM y cambiando en un bucle, lo que significa que volverá a inspeccionar cada instrucción WASM en cada ejecución, y volverá a inspeccionar cualquier instrucción que esté en un bucle en un contrato cada vez que pase por el bucle.

Nota: Usamos JIT coloquialmente pero en realidad nos referimos a utilizar cualquier paso de compilación, ya sea justo a tiempo o anticipado (AOT)

A primera vista parece obvio: JIT => más rápido => mejor. ¿No? No exactamente.

Problemas de seguridad

Las plataformas de contratos inteligentes ejecutan código de terceros no verificado: contratos cargados por los usuarios. Eso es peligroso para empezar, pero los JIT lo hacen mucho más peligroso de dos maneras. Primero, debido al hecho de que un JIT está emitiendo código nativo, es una base de código mucho más grande que realiza un trabajo significativamente más propenso a errores, y es más probable que los errores en su implementación sean críticos (ejecución de código remoto: tomar control de un validador). Cada JIT que se ha enviado ha tenido múltiples vulnerabilidades críticas de este tipo, y no podemos esperar que los que hemos visto en el pasado sean los últimos. Segundo, la fase de compilación de un JIT es vulnerable a entradas maliciosas que pueden hacer que tarde demasiado en ejecutarse o use demasiada memoria, un fenómeno llamado "bombas JIT" al que muchos JIT son vulnerables.

Falta de variedad

El tipo de JIT que no es vulnerable a las “bombas JIT” generalmente se refiere como “compiladores de una sola pasada” o “compiladores base” – están diseñados para completar la compilación en una cantidad de espacio y tiempo limitados (generalmente lineal en el tamaño de su entrada). Actualmente, solo hay un compilador WASM de una sola pasada en el mercado: Wasmer. Wasmer es una herramienta popular utilizada por varias empresas, incluidas algunas blockchains. Sin embargo, no es una dependencia con la que nos sintamos cómodos introduciendo al ecosistema de Stellar. Aunque es de código abierto, es bastante grande con 180k líneas de código y desarrollado por una empresa con fines de lucro que está viendo mucho drama. Nuestro intérprete seleccionado, WASMI, es una base de código extremadamente simple de 13k líneas que se puede modificar y reemplazar fácilmente.

Ganancias poco claras

Pero no es solo eso. Digamos que usas un JIT. Compilar es más complicado que interpretar, por lo que estás agregando latencia de tiempo de inicio desde la carga del código hasta la primera ejecución. Esta latencia probablemente sea demasiado alta para aceptar en cada transacción (ejecutamos cientos a miles de transacciones por segundo) y por lo tanto los contratos tendrán que compilarse antes de la ejecución y su código nativo almacenado en caché, y no se pueden cargar y ejecutar nuevos contratos en una sola transacción o incluso en un solo bloque. Además, un usuario malicioso puede ejercer presión sobre la caché cargando muchos contratos, por lo que almacenar en caché todos los contratos probablemente no sea factible, lo que significa que necesitamos reflejar la diferencia entre contratos almacenados en caché y no almacenados en el modelo de tarifas expuesto a los usuarios. Lo que comenzó pareciendo una simple optimización de rendimiento se ha convertido en una reestructuración significativa de todo el flujo de precios, presentación y ejecución de contratos.

Para colmo, no sabemos con certeza que JIT proporcionará un aumento significativo del rendimiento. Soroban está construido de manera que los contratos usen funciones del anfitrión (que ya son código nativo) para cálculos pesados, no código WASM interpretado por el entorno de ejecución WASM. Esencialmente, toda la premisa de diseño para Soroban es tener código WASM que dirija los “bucles externos” de ejecución (que no importan si se interpretan) y tener código nativo de funciones del anfitrión que haga todos los “bucles internos”.

Medición

Luego está la medición. Las blockchains necesitan rastrear la cantidad de recursos utilizados por los contratos. La medición necesita tener una fuerte correlación con las tarifas y el tiempo real o de lo contrario el sistema se vuelve injusto e/ineficiente. Esta es una característica única de las blockchains, y no se incluye en la mayoría de los entornos de ejecución WASM. Hay dos formas generales de adaptar la medición en un entorno de ejecución WASM: o bien hacer que el entorno de ejecución capture esta información sistemáticamente como parte de la interpretación o compilación, o inyectar instrumentación en el código del contrato antes de enviarlo al entorno de ejecución.

Dada la naturaleza compleja de los JIT y los caminos de optimización inesperados, es difícil garantizar la estabilidad o portabilidad de la medición basada en JIT. En la mayoría de los casos, y probablemente en nuestro caso si usáramos un JIT, los usuarios de blockchain basados en JIT optan por la instrumentación del código WASM. Esto empeora aún más el problema de latencia ya que ahora antes de que un contrato se compile necesita ser instrumentado; también introduce un tipo adicional potencial de vulnerabilidad, donde el código del contrato WASM interfiere con la instrumentación inyectada para tratar de evadir los controles de costos.

Para Soroban, dado que estamos usando un intérprete simple, esencialmente ya estamos pagando suficiente sobrecarga por instrucción WASM que fue fácil modificar el intérprete para contar las instrucciones WASM ejecutadas y cobrar por ellas. Y nuevamente, dado que el diseño de Soroban delega la mayoría de los “bucles internos” a funciones del anfitrión, realizamos la medición de estas completamente por separado de la medición basada en WASM, utilizando modelos de costos calibrados de antemano a través de mediciones de CPU fuera de línea.

El resultado final

Para concluir, somos ingenieros conservadores, eso es algo importante al construir infraestructura financiera en la que confían algunos de los servicios financieros más grandes del mundo. Apreciamos la simplicidad y previsibilidad. En este momento, esto significa elegir un entorno de ejecución WASM simple y predecible: el Intérprete WASMI.

No estamos apegados a esta decisión y felices de reevaluarla en el futuro. De hecho, estamos monitoreando de cerca el trabajo que wasmtime está haciendo en la compilación de una sola pasada y podríamos reconsiderar la decisión a medida que esa base de código madure. Nuevamente, una cosa buena de WASM es que generalmente hay muchos caminos posibles para elegir.