Résumé
Les programmes se compilent en sBPF via LLVM et s'exécutent dans une VM isolée avec un budget de 1,4M CU par transaction. Le runtime met en cache jusqu'à 512 programmes compilés, fournit des appels système pour la journalisation, les CPI, la cryptographie et la mémoire, et retarde les nouveaux déploiements d'un slot.
Compilation
Solana utilise LLVM pour compiler les programmes en binaires ELF contenant le Solana Bytecode Format (sBPF). Le binaire ELF est stocké on-chain dans un compte exécutable.
sBPF est la variante personnalisée de Solana du bytecode eBPF, adaptée pour le runtime Solana. Ce n'est pas de l'eBPF standard et comporte des modifications spécifiques à Solana.
Écrire des programmes
Les programmes Solana sont principalement écrits en Rust en utilisant l'une des deux approches :
Anchor
Un framework qui utilise des macros Rust pour réduire le code répétitif. Recommandé pour la plupart des développeurs.
Rust natif
Rust direct sans frameworks. Offre un contrôle total mais nécessite plus d'implémentation manuelle.
Modèle d'exécution de programme
Lorsqu'une transaction est traitée, le runtime exécute chaque instruction
séquentiellement via
process_message().
Pour chaque instruction, le runtime :
-
Prépare le contexte d'instruction. Appelle
prepare_next_top_level_instruction()pour mapper les indices de compte de l'instruction, définir les indicateurs de signataire et d'écriture, et configurer leTransactionContext. -
Vérifie les précompilations. Si le programme est une précompilation, le runtime appelle
process_precompile(), qui empile et dépile toujours une frame de pile (viapush()etpop()) mais contourne la VM sBPF et la recherche dans le cache de programmes, exécutant le code natif directement. -
Pousse une trame de pile. (Les étapes 3 à 6 se produisent dans
InvokeContext::process_instruction()etprocess_executable_chain(), appelées depuisprocess_message().) Appellepush()surInvokeContext, qui incrémente la hauteur de la pile d'instructions et applique la règle de réentrance : un programme ne peut se réappeler lui-même que si l'appelant immédiat (le programme au sommet actuel de la pile d'instructions) est le même programme. La récursion profonde (A -> A -> A) est autorisée, sous réserve des limites de profondeur de pile. Les autres schémas de réentrance (par exemple, A appelle B qui appelle A) renvoientInstructionError::ReentrancyNotAllowed. -
Résout le programme. Le runtime appelle
process_executable_chain()qui détermine le chargeur. Si le propriétaire du compte de programme est le chargeur natif, le programme est un builtin et sa fonction de point d'entrée est recherchée directement dans leProgramCacheForTxBatch. Si le propriétaire est l'un des chargeurs BPF (bpf_loader_deprecated,bpf_loader,bpf_loader_upgradeable, ouloader_v4), le point d'entrée builtin du chargeur est invoqué à la place. -
Exécute le programme BPF. Pour les programmes BPF, le point d'entrée du chargeur recherche l'exécutable compilé dans le cache de programmes. La fonction
execute()effectue ensuite les opérations suivantes :- Sérialise les données de compte dans un tampon de paramètres plat
- Crée la VM sBPF avec les régions de pile, de tas et de mémoire
- Exécute le code compilé, en consommant des unités de calcul pendant
l'exécution. Renvoie
ComputationalBudgetExceededsi le budget est dépassé. - Désérialise les données de compte du tampon vers l'état du compte
-
Dépile la trame de pile. Appelle
pop()qui vérifie que l'instruction n'a pas violé les règles comptables du runtime (les soldes en lamports sont équilibrés, les comptes en lecture seule n'ont pas été modifiés, les tailles de données de compte sont dans les limites). -
Accumule les unités de calcul. Les unités de calcul consommées par l'instruction sont ajoutées au total de la transaction via
saturating_add.
Cache de programme
Le runtime maintient un
ProgramCache
global qui stocke les programmes vérifiés et compilés. Il est conscient du
graphe de fork et gère les règles de visibilité de déploiement, l'éviction et la
recompilation aux limites d'epoch.
Types d'entrées de cache
Chaque programme en cache possède un
ProgramCacheEntryType
qui détermine son comportement à l'exécution :
| Type | Description |
|---|---|
Loaded | Programme vérifié et compilé, prêt pour l'exécution. |
Builtin | Programme natif compilé dans le binaire du validateur (System, Stake, Vote, etc.). Non stocké on-chain. |
Unloaded | Programme précédemment vérifié dont l'exécutable compilé a été évincé de la mémoire pour libérer de l'espace. Conserve toujours les statistiques d'utilisation. Peut être rechargé sans revérification. |
FailedVerification | Marqueur pour les programmes qui n'ont pas passé le vérificateur sBPF sous l'ensemble de fonctionnalités actuel. Peut devenir Loaded si les activations de fonctionnalités modifient les règles de vérification. |
Closed | Marqueur pour les programmes qui ont été explicitement fermés ou jamais déployés. Également utilisé pour les comptes (tels que les comptes tampons) qui appartiennent à un chargeur mais ne contiennent pas de code exécutable. |
DelayVisibility | Marqueur synthétique retourné par ProgramCacheForTxBatch::find() lorsqu'une entrée Loaded existe mais n'est pas encore effective (son effective_slot est dans le futur). Jamais stocké directement dans le cache. |
Délai de visibilité
Les programmes nouvellement déployés ou mis à niveau ne sont pas effectifs
immédiatement. La constante
DELAY_VISIBILITY_SLOT_OFFSET
est 1, ce qui signifie qu'un programme déployé dans le slot N devient effectif
dans le slot N+1. Pendant le slot de déploiement, toute tentative d'invoquer la
nouvelle version retourne DelayVisibility, ce qui amène le runtime à
signaler « Le programme n'est pas déployé ».
Politique d'éviction
Le cache contient jusqu'à
MAX_LOADED_ENTRY_COUNT
(512) entrées de programmes compilés. Lorsque la limite est atteinte, les
programmes les moins utilisés sont évincés vers l'état Unloaded.
L'utilisation est suivie par
tx_usage_counter
(incrémenté à chaque fois qu'une transaction référence le programme) et
latest_access_slot.
Recompilation à la limite d'epoch
Si l'activation d'une fonctionnalité modifie le
ProgramRuntimeEnvironments
à une limite d'epoch, tous les programmes en cache sont
recompilés
contre le nouvel environnement.
Données de retour
Les programmes peuvent définir des données de retour via le syscall
sol_set_return_data. Les données sont stockées dans une structure
TransactionReturnData
au niveau de la transaction qui contient les octets de données et le
program_id du programme dont l'instruction a appelé le syscall. La taille
maximale est de 1 024 octets (MAX_RETURN_DATA).
Is this page helpful?