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:
Anchor
Un framework que utiliza macros de Rust para reducir el código repetitivo. Recomendado para la mayoría de los desarrolladores.
Pinocchio
Una biblioteca de Rust ligera y de copia cero optimizada para el consumo de cómputo y el tamaño del binario. Incluye crates específicos de programas para CPIs comunes.
Rust nativo
Rust directo sin frameworks. Ofrece control total, pero requiere una implementación más manual.
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:
-
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 elTransactionContext. -
Comprueba las precompilaciones. Si el programa es una precompile, el runtime llama a
process_precompile(), que igualmente introduce y extrae un frame de pila (mediantepush()ypop()) pero omite la VM sBPF y la búsqueda en la caché de programas, ejecutando el código nativo directamente. -
Introduce un frame de pila. (Los pasos 3-6 ocurren dentro de
InvokeContext::process_instruction()yprocess_executable_chain(), llamados desdeprocess_message().) Llama apush()sobreInvokeContext, 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) devuelvenInstructionError::ReentrancyNotAllowed. -
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 elProgramCacheForTxBatch. Si el propietario es uno de los loaders BPF (bpf_loader_deprecated,bpf_loader,bpf_loader_upgradeableoloader_v4), se invoca el propio punto de entrada builtin del loader. -
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
ComputationalBudgetExceededsi se supera el presupuesto. - Deserializa los datos de cuenta del búfer de vuelta al estado de la cuenta
-
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). -
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:
| Tipo | Descripción |
|---|---|
Loaded | Programa verificado y compilado, listo para su ejecución. |
Builtin | Programa nativo compilado en el binario del validator (System, Stake, Vote, etc.). No se almacena en la cadena. |
Unloaded | Programa 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. |
FailedVerification | Marcador 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. |
Closed | Marcador 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. |
DelayVisibility | Marcador 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?