Riepilogo
I programmi vengono compilati in sBPF tramite LLVM ed eseguiti in una VM isolata con un budget di 1,4M CU per transazione. Il runtime memorizza nella cache fino a 512 programmi compilati, fornisce syscall per logging, CPI, crittografia e memoria, e ritarda i nuovi deployment di 1 slot.
Compilazione
Solana utilizza LLVM per compilare i programmi in binari ELF contenenti il Solana Bytecode Format (sBPF). Il binario ELF viene memorizzato onchain in un account eseguibile.
sBPF è la variante personalizzata di Solana del bytecode eBPF, adattata per il runtime di Solana. Non è eBPF standard e presenta modifiche specifiche per Solana.
Scrivi programmi
I programmi Solana sono scritti principalmente in Rust utilizzando uno dei tre approcci seguenti:
Anchor
Un framework che utilizza macro Rust per ridurre il codice boilerplate. Consigliato per la maggior parte degli sviluppatori.
Pinocchio
Una libreria Rust leggera e zero-copy, ottimizzata per il consumo di unità di calcolo e la dimensione del binario. Include crate specifici per programmi per le CPI più comuni.
Rust nativo
Rust diretto senza framework. Offre il pieno controllo ma richiede un'implementazione più manuale.
Consulta Cross-Program Invocations per i concetti e gli esempi di CPI applicabili a questi approcci.
Modello di esecuzione dei programmi
Quando una transazione viene elaborata, il runtime esegue ogni istruzione
in sequenza tramite
process_message().
Per ogni istruzione, il runtime:
-
Prepara il contesto dell'istruzione. Chiama
prepare_next_top_level_instruction()per mappare gli indici degli account dell'istruzione, impostare i flag di firmatario e scrivibile, e configurare ilTransactionContext. -
Verifica i precompilati. Se il programma è un precompilato, il runtime chiama
process_precompile(), che inserisce e rimuove comunque uno stack frame (tramitepush()epop()) ma bypassa la VM sBPF e la ricerca nella cache dei programmi, eseguendo il codice nativo direttamente. -
Inserisce uno stack frame. (I passaggi 3-6 avvengono all'interno di
InvokeContext::process_instruction()eprocess_executable_chain(), chiamati daprocess_message().) Chiamapush()suInvokeContext, che incrementa l'altezza dello stack delle istruzioni e applica la regola di rientranza: un programma può rientrare in se stesso solo se il chiamante immediato (il programma in cima all'attuale stack delle istruzioni) è lo stesso programma. La ricorsione profonda (A -> A -> A) è consentita, nel rispetto dei limiti di profondità dello stack. Altri pattern di rientranza (es. A chiama B che chiama A) restituisconoInstructionError::ReentrancyNotAllowed. -
Risolve il programma. Il runtime chiama
process_executable_chain()che determina il loader. Se il proprietario del program account è il native loader, il programma è un builtin e la sua funzione entrypoint viene recuperata direttamente dalProgramCacheForTxBatch. Se il proprietario è uno dei loader BPF (bpf_loader_deprecated,bpf_loader,bpf_loader_upgradeableoloader_v4), viene invocato invece l'entrypoint builtin del loader stesso. -
Esegue il programma BPF. Per i programmi BPF, il loader entrypoint recupera l'eseguibile compilato dalla cache dei programmi. La funzione
execute()quindi:- Serializza i dati degli account in un buffer di parametri piatto
- Crea la VM sBPF con stack, heap e regioni di memoria
- Esegue il codice compilato, consumando unità di calcolo durante l'esecuzione. Restituisce
ComputationalBudgetExceededse il budget viene superato. - Deserializza i dati degli account dal buffer ripristinando lo stato degli account
-
Rimuove lo stack frame. Chiama
pop()che verifica che l'istruzione non abbia violato le regole di contabilità del runtime (i saldi lamport sono bilanciati, gli account in sola lettura non sono stati modificati, le dimensioni dei dati degli account rientrano nei limiti). -
Accumula le unità di calcolo. Le unità di calcolo consumate dall'istruzione vengono aggiunte al totale della transazione tramite
saturating_add.
Cache dei programmi
Il runtime mantiene un
ProgramCache
globale che memorizza i programmi verificati e compilati. È consapevole del fork-graph e gestisce
le regole di visibilità del deployment, l'eviction e la ricompilazione al confine di epoch.
Tipi di voce nella cache
Ogni programma memorizzato nella cache ha un
ProgramCacheEntryType
che determina il suo comportamento a runtime:
| Tipo | Descrizione |
|---|---|
Loaded | Programma verificato e compilato, pronto per l'esecuzione. |
Builtin | Programma nativo compilato nel binario del validator (System, Stake, Vote, ecc.). Non memorizzato onchain. |
Unloaded | Programma precedentemente verificato il cui eseguibile compilato è stato rimosso dalla memoria per liberare spazio. Mantiene ancora le statistiche di utilizzo. Può essere ricaricato senza una nuova verifica. |
FailedVerification | Segnaposto per i programmi che non hanno superato il verificatore sBPF con il set di funzionalità attuale. Potrebbe diventare Loaded se le attivazioni di funzionalità modificano le regole di verifica. |
Closed | Segnaposto per i programmi chiusi esplicitamente o mai distribuiti. Utilizzato anche per account (come i buffer account) che appartengono a un loader ma non contengono codice eseguibile. |
DelayVisibility | Segnaposto sintetico restituito da ProgramCacheForTxBatch::find() quando esiste una voce Loaded ma non è ancora efficace (il suo effective_slot è nel futuro). Non viene mai memorizzato direttamente nella cache. |
Ritardo di visibilità
I programmi appena distribuiti o aggiornati non diventano effettivi immediatamente. La
costante DELAY_VISIBILITY_SLOT_OFFSET
è 1, il che significa che un programma distribuito nello slot N diventa effettivo nello slot
N+1. Durante lo slot di deployment, qualsiasi tentativo di invocare la nuova versione restituisce
DelayVisibility, causando la segnalazione "Program is not deployed." da parte del runtime.
Policy di eviction
La cache contiene fino a
MAX_LOADED_ENTRY_COUNT
(512) voci di programmi compilati. Quando il limite viene raggiunto, i programmi meno utilizzati
vengono rimossi allo stato Unloaded. L'utilizzo è tracciato da
tx_usage_counter
(incrementato ogni volta che una transazione fa riferimento al programma) e
latest_access_slot.
Ricompilazione al confine di epoch
Se l'attivazione di una funzionalità modifica i
ProgramRuntimeEnvironments
al confine di un epoch, tutti i programmi nella cache vengono
ricompilati
contro il nuovo ambiente.
Dati di ritorno
I programmi possono impostare i dati di ritorno tramite la syscall sol_set_return_data. I dati vengono
memorizzati in una struct
TransactionReturnData
a livello di transazione che contiene i byte dei dati e il program_id del programma la cui
istruzione ha chiamato la syscall. La dimensione massima è 1.024 byte
(MAX_RETURN_DATA).
Is this page helpful?