Ejecución de programas

Resumen

Los programas se compilan a sBPF mediante LLVM y se ejecutan en una VM en sandbox con un presupuesto de 1,4 M de UC por transacción. El runtime almacena en caché hasta 512 programas compilados, proporciona syscalls para registro, CPI, criptografía y memoria, y retrasa los nuevos despliegues en 1 slot.

Compilación

Solana utiliza LLVM para compilar programas en binarios ELF que contienen el Formato de Bytecode de Solana (sBPF). El binario ELF se almacena en la cadena en una cuenta ejecutable.

sBPF es la variante personalizada de Solana del bytecode eBPF, adaptada para el runtime de Solana. No es eBPF estándar y cuenta con modificaciones específicas de Solana.

Escribir programas

Los programas de Solana se escriben principalmente en Rust mediante uno de tres enfoques:

Consulta Cross-Program Invocations para conocer los conceptos y ejemplos de CPI que aplican a estos enfoques.

Modelo de ejecución de programas

Cuando se procesa una transacción, el runtime ejecuta cada instrucción secuencialmente mediante process_message(). Para cada instrucción, el runtime:

  1. Prepara el contexto de la instrucción. Llama a prepare_next_top_level_instruction() para mapear los índices de cuenta de la instrucción, establecer los indicadores de firmante y escritura, y configurar el TransactionContext.

  2. Comprueba las precompilaciones. Si el programa es una precompile, el runtime llama a process_precompile(), que igualmente introduce y extrae un frame de pila (mediante push() y pop()) pero omite la VM sBPF y la búsqueda en la caché de programas, ejecutando el código nativo directamente.

  3. Introduce un frame de pila. (Los pasos 3-6 ocurren dentro de InvokeContext::process_instruction() y process_executable_chain(), llamados desde process_message().) Llama a push() sobre InvokeContext, que incrementa la altura de la pila de instrucciones y hace cumplir la regla de reentrada: un programa solo puede volver a entrar en sí mismo si el llamador inmediato (el programa en la cima actual de la pila de instrucciones) es el mismo programa. La auto-recursión profunda (A -> A -> A) está permitida, sujeta a los límites de profundidad de pila. Otros patrones de reentrada (p. ej., A llama a B que llama a A) devuelven InstructionError::ReentrancyNotAllowed.

  4. Resuelve el programa. El runtime llama a process_executable_chain(), que determina el loader. Si el propietario del program account es el native loader, el programa es un builtin y su función de punto de entrada se busca directamente en el ProgramCacheForTxBatch. Si el propietario es uno de los loaders BPF (bpf_loader_deprecated, bpf_loader, bpf_loader_upgradeable o loader_v4), se invoca el propio punto de entrada builtin del loader.

  5. Ejecuta el programa BPF. Para los programas BPF, el loader entrypoint busca el ejecutable compilado en la caché de programas. La función execute() entonces:

    • Serializa los datos de cuenta en un búfer de parámetros plano
    • Crea la VM sBPF con regiones de pila, heap y memoria
    • Ejecuta el código compilado, consumiendo unidades de cómputo durante la ejecución. Devuelve ComputationalBudgetExceeded si se supera el presupuesto.
    • Deserializa los datos de cuenta del búfer de vuelta al estado de la cuenta
  6. Extrae el frame de pila. Llama a pop(), que verifica que la instrucción no haya violado las reglas de contabilidad del runtime (los saldos de lamport están equilibrados, las cuentas de solo lectura no fueron modificadas, los tamaños de datos de las cuentas están dentro de los límites).

  7. Acumula unidades de cómputo. Las unidades de cómputo consumidas por la instrucción se añaden al total de la transacción mediante saturating_add.

Caché de programas

El runtime mantiene un ProgramCache global que almacena programas verificados y compilados. Es consciente del grafo de forks y gestiona las reglas de visibilidad de despliegue, la expulsión y la recompilación en los límites de epoch.

Tipos de entradas en caché

Cada programa en caché tiene un ProgramCacheEntryType que determina su comportamiento en tiempo de ejecución:

TipoDescripción
LoadedPrograma verificado y compilado, listo para su ejecución.
BuiltinPrograma nativo compilado en el binario del validator (System, Stake, Vote, etc.). No se almacena en la cadena.
UnloadedPrograma previamente verificado cuyo ejecutable compilado fue expulsado de la memoria para liberar espacio. Aún registra estadísticas de uso. Puede recargarse sin reverificación.
FailedVerificationMarcador de exclusión para programas que no superaron el verificador sBPF bajo el conjunto de características actual. Puede pasar a Loaded si las activaciones de características cambian las reglas de verificación.
ClosedMarcador de exclusión para programas que fueron cerrados explícitamente o que nunca fueron desplegados. También se utiliza para cuentas (como las cuentas de búfer) que pertenecen a un loader pero no contienen código ejecutable.
DelayVisibilityMarcador de exclusión sintético devuelto por ProgramCacheForTxBatch::find() cuando existe una entrada Loaded pero aún no es efectiva (su effective_slot está en el futuro). Nunca se almacena directamente en la caché.

Retraso de visibilidad

Los programas recién desplegados o actualizados no son efectivos de inmediato. La constante DELAY_VISIBILITY_SLOT_OFFSET es 1, lo que significa que un programa desplegado en el slot N se vuelve efectivo en el slot N+1. Durante el slot de despliegue, cualquier intento de invocar la nueva versión devuelve DelayVisibility, lo que hace que el runtime informe "El programa no está desplegado."

Política de expulsión

La caché almacena hasta MAX_LOADED_ENTRY_COUNT (512) entradas de programas compilados. Cuando se alcanza el límite, los programas menos utilizados son expulsados al estado Unloaded. El uso se rastrea mediante tx_usage_counter (incrementado cada vez que una transacción referencia el programa) y latest_access_slot.

Recompilación en límites de epoch

Si una activación de características cambia los ProgramRuntimeEnvironments en un límite de epoch, todos los programas en caché son recompilados contra el nuevo entorno.

Datos de retorno

Los programas pueden establecer datos de retorno mediante la syscall sol_set_return_data. Los datos se almacenan en una estructura TransactionReturnData a nivel de transacción que contiene los bytes de datos y el program_id del programa cuya instrucción llamó a la syscall. El tamaño máximo es de 1024 bytes (MAX_RETURN_DATA).

Is this page helpful?

Tabla de Contenidos

Editar Página