Résumé
Les programmes sont compilés en sBPF via LLVM et s'exécutent dans une VM isolée avec un budget de 1,4 M d'UC 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'1 slot.
Compilation
Solana utilise LLVM pour compiler les programmes en binaires ELF contenant le format de bytecode Solana (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 au runtime Solana. Il ne s'agit pas d'eBPF standard et il comporte des modifications spécifiques à Solana.
Écrire des programmes
Les programmes Solana sont principalement écrits en Rust selon l'une des trois approches suivantes :
Anchor
Un framework qui utilise des macros Rust pour réduire le code répétitif. Recommandé pour la plupart des développeurs.
Pinocchio
Une bibliothèque Rust légère à copie zéro, optimisée pour la consommation d'UC et la taille des binaires. Inclut des crates spécifiques aux programmes pour les CPI courantes.
Rust natif
Du Rust direct sans frameworks. Offre un contrôle total mais nécessite une implémentation plus manuelle.
Consultez Cross-Program Invocations pour les concepts et exemples de CPI qui s'appliquent à ces différentes approches.
Modèle d'exécution des programmes
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 de l'instruction. Appelle
prepare_next_top_level_instruction()pour mapper les indices de comptes 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 pousse et dépile quand même un cadre de pile (viapush()etpop()) mais contourne la VM sBPF et la recherche dans le cache de programmes, en exécutant le code natif directement. -
Pousse un cadre de pile. (Les étapes 3 à 6 se déroulent dans
InvokeContext::process_instruction()etprocess_executable_chain(), appelés depuisprocess_message().) Appellepush()surInvokeContext, ce qui incrémente la hauteur de la pile d'instructions et applique la règle de réentrance : un programme ne peut se réentrer 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 motifs de réentrance (par ex., A appelle B qui appelle A) retournentInstructionError::ReentrancyNotAllowed. -
Résout le programme. Le runtime appelle
process_executable_chain()qui détermine le chargeur. Si le propriétaire du program account est le chargeur natif, le programme est un builtin et sa fonction 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_upgradeableouloader_v4), le point d'entrée builtin propre au 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 :- Sérialise les données des comptes 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. Retourne
ComputationalBudgetExceededsi le budget est dépassé. - Désérialise les données des comptes du tampon vers l'état des comptes
-
Dépile le cadre de pile. Appelle
pop()qui vérifie que l'instruction n'a pas violé les règles de comptabilité du runtime (les soldes en lamport sont équilibrés, les comptes en lecture seule n'ont pas été modifiés, les tailles des données des comptes sont dans les limites autorisées). -
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 programmes
Le runtime maintient un
ProgramCache
mondial qui stocke les programmes vérifiés et compilés. Il est conscient du graphe de fourches et gère
les règles de visibilité des déploiements, l'éviction et la recompilation aux limites d'epoch.
Types d'entrées du cache
Chaque programme mis en cache possède un
ProgramCacheEntryType
qui détermine son comportement à l'exécution :
| Type | Description |
|---|---|
Loaded | Programme vérifié et compilé, prêt à l'exécution. |
Builtin | Programme natif compilé dans le binaire du validator (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 les statistiques d'utilisation. Peut être rechargé sans re-vérification. |
FailedVerification | Marqueur de suppression pour les programmes n'ayant pas passé le vérificateur sBPF avec l'ensemble de fonctionnalités actuel. Peut devenir Loaded si des activations de fonctionnalités modifient les règles de vérification. |
Closed | Marqueur de suppression pour les programmes explicitement fermés ou jamais déployés. Utilisé également pour les comptes (tels que les comptes tampon) appartenant à un chargeur mais ne contenant pas de code exécutable. |
DelayVisibility | Marqueur de suppression 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 à jour ne sont pas effectifs immédiatement. La
constante DELAY_VISIBILITY_SLOT_OFFSET
vaut 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 peut contenir 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 une activation de fonctionnalité modifie les
ProgramRuntimeEnvironments
à une limite d'epoch, tous les programmes en cache sont
recompilés
par rapport au nouvel environnement.
Données de retour
Les programmes peuvent définir des données de retour via l'appel système sol_set_return_data. Les données sont
stockées dans une structure
TransactionReturnData
de niveau transaction qui contient les octets de données et le program_id du programme dont
l'instruction a appelé l'appel système. La taille maximale est de 1 024 octets
(MAX_RETURN_DATA).
Is this page helpful?