Zusammenfassung
Programme werden über LLVM zu sBPF kompiliert und in einer isolierten VM mit einem Budget von 1,4 Mio. CU pro Transaktion ausgeführt. Die Laufzeit speichert bis zu 512 kompilierte Programme im Cache, stellt Syscalls für Logging, CPI, Kryptografie und Speicher bereit und verzögert neue Deployments um 1 slot.
Kompilierung
Solana verwendet LLVM, um Programme in ELF-Binärdateien zu kompilieren, die das Solana Bytecode Format (sBPF) enthalten. Die ELF-Binärdatei wird onchain in einem ausführbaren Konto gespeichert.
sBPF ist Solanas benutzerdefinierte Variante von eBPF-Bytecode, angepasst für die Solana-Laufzeit. Es handelt sich nicht um Standard-eBPF und enthält Solana-spezifische Modifikationen.
Programme schreiben
Solana-Programme werden hauptsächlich in Rust mit einem von drei Ansätzen geschrieben:
Anchor
Ein Framework, das Rust-Makros verwendet, um Boilerplate zu reduzieren. Empfohlen für die meisten Entwickler.
Pinocchio
Eine leichtgewichtige Zero-Copy-Rust-Bibliothek, optimiert für Compute-Verbrauch und Binärgröße. Enthält programmspezifische Crates für gängige CPIs.
Natives Rust
Direktes Rust ohne Frameworks. Bietet vollständige Kontrolle, erfordert jedoch mehr manuelle Implementierung.
Siehe Cross-Program Invocations für CPI-Konzepte und Beispiele, die für alle diese Ansätze gelten.
Programmausführungsmodell
Bei der Verarbeitung einer Transaktion führt die Laufzeit jede Anweisung
sequenziell über
process_message()
aus.
Für jede Anweisung führt die Laufzeit Folgendes durch:
-
Bereitet den Anweisungskontext vor. Ruft
prepare_next_top_level_instruction()auf, um die Kontoindizes der Anweisung zuzuordnen, Signer- und Schreibberechtigungen zu setzen und denTransactionContextzu konfigurieren. -
Prüft Precompiles. Wenn das Programm ein Precompile ist, ruft die Laufzeit
process_precompile()auf, das zwar noch einen Stack-Frame schiebt und entfernt (überpush()undpop()), jedoch die sBPF-VM und den Programm-Cache-Lookup umgeht und nativen Code direkt ausführt. -
Schiebt einen Stack-Frame. (Schritte 3–6 finden innerhalb von
InvokeContext::process_instruction()undprocess_executable_chain(), aufgerufen vonprocess_message(), statt.) Ruftpush()aufInvokeContextauf, was die Anweisungs-Stack-Höhe erhöht und die Reentrant-Regel durchsetzt: Ein Programm darf sich nur selbst erneut aufrufen, wenn der unmittelbare Aufrufer (das Programm am aktuellen oberen Ende des Anweisungs-Stacks) dasselbe Programm ist. Tiefe Selbstrekursion (A -> A -> A) ist erlaubt, unterliegt jedoch Stack-Tiefenbeschränkungen. Andere Reentrant-Muster (z. B. A ruft B auf, B ruft A auf) gebenInstructionError::ReentrancyNotAllowedzurück. -
Löst das Programm auf. Die Laufzeit ruft
process_executable_chain()auf, das den Loader bestimmt. Wenn der Eigentümer des Programmkontos der native loader ist, ist das Programm ein Builtin und sein Einstiegspunkt wird direkt aus demProgramCacheForTxBatchnachgeschlagen. Wenn der Eigentümer einer der BPF-Loader ist (bpf_loader_deprecated,bpf_loader,bpf_loader_upgradeableoderloader_v4), wird stattdessen der eigene Builtin-Einstiegspunkt des Loaders aufgerufen. -
Führt das BPF-Programm aus. Für BPF-Programme sucht der Loader-Einstiegspunkt das kompilierte Executable aus dem Programm-Cache. Die
execute()-Funktion führt dann Folgendes durch:- Serialisiert Kontodaten in einen flachen Parameterpuffer
- Erstellt die sBPF-VM mit Stack, Heap und Speicherbereichen
- Führt den kompilierten Code aus und verbraucht dabei Compute Units. Gibt
ComputationalBudgetExceededzurück, wenn das Budget überschritten wird. - Deserialisiert Kontodaten aus dem Puffer zurück in den Kontostatus
-
Entfernt den Stack-Frame. Ruft
pop()auf, das überprüft, ob die Anweisung die Abrechnungsregeln der Laufzeit nicht verletzt hat (Lamport-Salden sind ausgeglichen, schreibgeschützte Konten wurden nicht geändert, Kontodatengrößen liegen innerhalb der Grenzen). -
Akkumuliert Compute Units. Die von der Anweisung verbrauchten Compute Units werden über
saturating_addzum Transaktionsgesamtwert addiert.
Programm-Cache
Die Laufzeit pflegt einen globalen
ProgramCache,
der verifizierte und kompilierte Programme speichert. Er ist fork-graph-bewusst und verwaltet
Deployment-Sichtbarkeitsregeln, Verdrängung und Neukompilierung an Epoch-Grenzen.
Cache-Eintragstypen
Jedes gecachte Programm hat einen
ProgramCacheEntryType,
der sein Laufzeitverhalten bestimmt:
| Typ | Beschreibung |
|---|---|
Loaded | Verifiziertes und kompiliertes Programm, bereit zur Ausführung. |
Builtin | Natives Programm, das in die Validatoren-Binärdatei kompiliert ist (System, Stake, Vote usw.). Wird nicht onchain gespeichert. |
Unloaded | Zuvor verifiziertes Programm, dessen kompiliertes Executable aus dem Speicher verdrängt wurde, um Platz freizugeben. Verfolgt weiterhin Nutzungsstatistiken. Kann ohne erneute Verifizierung neu geladen werden. |
FailedVerification | Tombstone für Programme, die den sBPF-Verifizierer unter dem aktuellen Feature-Set nicht bestanden haben. Kann zu Loaded werden, wenn Feature-Aktivierungen die Verifizierungsregeln ändern. |
Closed | Tombstone für Programme, die explizit geschlossen oder nie deployed wurden. Wird auch für Konten verwendet (wie Buffer-Accounts), die zu einem Loader gehören, aber keinen ausführbaren Code enthalten. |
DelayVisibility | Synthetischer Tombstone, der von ProgramCacheForTxBatch::find() zurückgegeben wird, wenn ein Loaded-Eintrag existiert, aber noch nicht wirksam ist (sein effective_slot liegt in der Zukunft). Wird nie direkt im Cache gespeichert. |
Sichtbarkeitsverzögerung
Neu deployede oder aktualisierte Programme sind nicht sofort wirksam. Die
DELAY_VISIBILITY_SLOT_OFFSET-Konstante
hat den Wert 1, was bedeutet, dass ein in slot N deployedes Programm in slot
N+1 wirksam wird. Während des Deployment-Slots gibt jeder Versuch, die neue Version aufzurufen,
DelayVisibility zurück, wodurch die Laufzeit "Program is not deployed." meldet.
Verdrängungsrichtlinie
Der Cache enthält bis zu
MAX_LOADED_ENTRY_COUNT
(512) kompilierte Programmeinträge. Wenn das Limit erreicht ist, werden die am wenigsten genutzten
Programme in den Unloaded-Zustand verdrängt. Die Nutzung wird durch
tx_usage_counter
(wird jedes Mal erhöht, wenn eine Transaktion das Programm referenziert) und
latest_access_slot
verfolgt.
Neukompilierung an Epoch-Grenzen
Wenn eine Feature-Aktivierung die
ProgramRuntimeEnvironments
an einer Epoch-Grenze ändert, werden alle gecachten Programme
neu kompiliert
gegen die neue Umgebung.
Rückgabedaten
Programme können Rückgabedaten über den sol_set_return_data-Syscall setzen. Die Daten werden
in einem transaktionsweiten
TransactionReturnData-Struct
gespeichert, das die Datenbytes und die program_id des Programms enthält, dessen
Anweisungen den Syscall aufgerufen haben. Die maximale Größe beträgt 1.024 Bytes
(MAX_RETURN_DATA).
Is this page helpful?