Resumen
Los programas se compilan a sBPF mediante LLVM y se ejecutan en una VM aislada con un presupuesto de 1.4M CU por transacción. El runtime almacena en caché hasta 512 programas compilados, proporciona syscalls para logging, 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 Solana Bytecode Format (sBPF). El binario ELF se almacena on-chain 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 tiene modificaciones específicas de Solana.
Escribir programas
Los programas de Solana se escriben principalmente en Rust utilizando uno de dos enfoques:
Anchor
Un framework que utiliza macros de Rust para reducir código repetitivo. Recomendado para la mayoría de desarrolladores.
Rust nativo
Rust directo sin frameworks. Ofrece control total pero requiere más implementación manual.
Modelo de ejecución de programas
Cuando se procesa una transacción, el runtime ejecuta cada instrucción
secuencialmente a través de
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 flags de firmante y escritura, y configurar elTransactionContext. -
Verifica precompilados. Si el programa es un precompilado, el runtime llama a
process_precompile(), que aún empuja y extrae un stack frame (mediantepush()ypop()) pero omite la VM sBPF y la búsqueda en la caché de programas, ejecutando código nativo directamente. -
Empuja un marco de pila. (Los pasos 3-6 ocurren dentro de
InvokeContext::process_instruction()yprocess_executable_chain(), llamados desdeprocess_message().) Llama apush()enInvokeContext, que incrementa la altura de la pila de instrucciones y aplica la regla de reentrada: un programa solo puede volver a entrar en sí mismo si el llamador inmediato (el programa en la parte superior actual de la pila de instrucciones) es el mismo programa. Se permite la auto-recursión profunda (A -> A -> A), sujeta a límites de profundidad de pila. Otros patrones de reentrada (por ejemplo, A llama a B llama a A) devuelvenInstructionError::ReentrancyNotAllowed. -
Resuelve el programa. El runtime llama a
process_executable_chain()que determina el cargador. Si el propietario de la cuenta del programa es el cargador nativo, el programa es un builtin y su función de punto de entrada se busca directamente desde elProgramCacheForTxBatch. Si el propietario es uno de los cargadores BPF (bpf_loader_deprecated,bpf_loader,bpf_loader_upgradeable, oloader_v4), se invoca en su lugar el punto de entrada builtin del propio cargador. -
Ejecuta el programa BPF. Para programas BPF, el punto de entrada del cargador busca el ejecutable compilado desde la caché de programas. La función
execute()entonces:- Serializa los datos de la 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 excede el presupuesto. - Deserializa los datos de la cuenta desde el búfer de vuelta al estado de la cuenta
-
Extrae el marco de pila. Llama a
pop()que verifica que la instrucción no violó las reglas de contabilidad del runtime (los balances de lamports están equilibrados, las cuentas de solo lectura no fueron modificadas, los tamaños de datos de cuenta están dentro de los límites). -
Acumula unidades de cómputo. Las unidades de cómputo consumidas por la instrucción se agregan al total de la transacción mediante
saturating_add.
Caché de programas
El runtime mantiene una
ProgramCache
global que almacena programas verificados y compilados. Es consciente del grafo
de bifurcaciones y maneja las reglas de visibilidad de despliegue, desalojo y
recompilación en límites de epoch.
Tipos de entrada de 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 ejecución. |
Builtin | Programa nativo compilado en el binario del validador (System, Stake, Vote, etc.). No se almacena en cadena. |
Unloaded | Programa previamente verificado cuyo ejecutable compilado fue desalojado de la memoria para liberar espacio. Aún rastrea estadísticas de uso. Puede recargarse sin re-verificación. |
FailedVerification | Marcador para programas que no pasaron el verificador sBPF bajo el conjunto de características actual. Puede convertirse en Loaded si las activaciones de características cambian las reglas de verificación. |
Closed | Marcador para programas que fueron explícitamente cerrados o nunca desplegados. También se usa para cuentas (como cuentas de búfer) que pertenecen a un cargador pero no contienen código ejecutable. |
DelayVisibility | Marcador 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 inmediatamente.
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, causando que el runtime
reporte "El programa no está desplegado".
Política de desalojo
La caché almacena hasta
MAX_LOADED_ENTRY_COUNT
(512) entradas de programas compilados. Cuando se alcanza el límite, los
programas menos utilizados se desalojan al estado Unloaded. El uso se
rastrea mediante
tx_usage_counter
(incrementado cada vez que una transacción hace referencia al programa) y
latest_access_slot.
Recompilación en límite de epoch
Si una activación de característica cambia el
ProgramRuntimeEnvironments
en un límite de epoch, todos los programas en caché se
recompilan
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 1.024 bytes
(MAX_RETURN_DATA).
Is this page helpful?