Riepilogo
I programmi vengono compilati in sBPF tramite LLVM ed eseguiti in una VM sandbox 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 Solana Bytecode Format (sBPF). Il binario ELF viene memorizzato on-chain in un account eseguibile.
sBPF è la variante personalizzata di Solana del bytecode eBPF, adattata per il runtime Solana. Non è eBPF standard e presenta modifiche specifiche per Solana.
Scrivere programmi
I programmi Solana sono scritti principalmente in Rust utilizzando uno dei due approcci:
Anchor
Un framework che utilizza macro Rust per ridurre il codice boilerplate. Consigliato per la maggior parte degli sviluppatori.
Rust nativo
Rust diretto senza framework. Offre il pieno controllo ma richiede più implementazione manuale.
Modello di esecuzione del programma
Quando viene elaborata una transazione, il runtime esegue ogni istruzione
sequenzialmente 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 signer e writable, e configurare l'TransactionContext. -
Verifica i precompile. Se il programma è un precompile, il runtime chiama
process_precompile(), che comunque inserisce ed estrae un frame dello stack (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 attualmente in cima allo stack delle istruzioni) è lo stesso programma. La ricorsione profonda su se stesso (A -> A -> A) è consentita, soggetta ai limiti di profondità dello stack. Altri pattern di rientranza (ad esempio, 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 cercata direttamente dalProgramCacheForTxBatch. Se il proprietario è uno dei BPF loader (bpf_loader_deprecated,bpf_loader,bpf_loader_upgradeable, oloader_v4), viene invece invocato l'entrypoint builtin del loader stesso. -
Esegue il programma BPF. Per i programmi BPF, l' entrypoint del loader cerca l'eseguibile compilato dalla cache dei programmi. La funzione
execute()quindi:- Serializza i dati dell'account in un buffer di parametri flat
- Crea la VM sBPF con stack, heap e regioni di memoria
- Esegue il codice compilato, consumando compute unit durante l'esecuzione.
Restituisce
ComputationalBudgetExceededse il budget viene superato. - Deserializza i dati dell'account dal buffer riportandoli nello stato dell'account
-
Rimuove lo stack frame. Chiama
pop()che verifica che l'istruzione non abbia violato le regole di contabilità del runtime (i saldi in lamport sono bilanciati, gli account readonly non sono stati modificati, le dimensioni dei dati degli account rientrano nei limiti). -
Accumula le compute unit. Le compute unit consumate dall'istruzione vengono aggiunte al totale della transazione tramite
saturating_add.
Cache dei programmi
Il runtime mantiene una
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 ai confini delle epoch.
Tipi di entry della cache
Ogni programma in 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 on-chain. |
Unloaded | Programma precedentemente verificato il cui eseguibile compilato è stato rimosso dalla memoria per liberare spazio. Continua a tracciare le statistiche di utilizzo. Può essere ricaricato senza ri-verifica. |
FailedVerification | Tombstone per programmi che non hanno superato il verificatore sBPF con il set di feature corrente. Può diventare Loaded se le attivazioni delle feature modificano le regole di verifica. |
Closed | Tombstone per programmi che sono stati esplicitamente chiusi o mai deployati. Utilizzato anche per account (come i buffer account) che appartengono a un loader ma non contengono codice eseguibile. |
DelayVisibility | Tombstone sintetico restituito da ProgramCacheForTxBatch::find() quando esiste un'entry Loaded ma non è ancora effettiva (il suo effective_slot è nel futuro). Mai memorizzato direttamente nella cache. |
Ritardo di visibilità
I programmi appena deployati o aggiornati non sono effettivi immediatamente. La
costante
DELAY_VISIBILITY_SLOT_OFFSET
è 1, il che significa che un programma deployato 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 da parte del runtime di "Program is not deployed."
Politica di rimozione
La cache contiene fino a
MAX_LOADED_ENTRY_COUNT
(512) voci di programmi compilati. Quando viene raggiunto il limite, 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 dell'epoch
Se l'attivazione di una funzionalità modifica l'
ProgramRuntimeEnvironments
al confine di un epoch, tutti i programmi in cache vengono
ricompilati
contro il nuovo ambiente.
Dati di ritorno
I programmi possono impostare dati di ritorno tramite la syscall
sol_set_return_data. I dati sono memorizzati in una struttura
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 è di
1.024 byte (MAX_RETURN_DATA).
Is this page helpful?