Zusammenfassung
Programme werden über LLVM zu sBPF kompiliert und laufen in einer Sandbox-VM mit einem Budget von 1,4 Mio. CU pro Transaktion. Die Runtime cached bis zu 512 kompilierte Programme, 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 on-chain in einem ausführbaren Konto gespeichert.
sBPF ist Solanas angepasste Variante des eBPF-Bytecodes, zugeschnitten auf die Solana-Runtime. Es ist kein Standard-eBPF und weist Solana-spezifische Modifikationen auf.
Programme schreiben
Solana-Programme werden hauptsächlich in Rust geschrieben, wobei einer von zwei Ansätzen verwendet wird:
Anchor
Ein Framework, das Rust-Makros verwendet, um Boilerplate zu reduzieren. Empfohlen für die meisten Entwickler.
Native Rust
Direktes Rust ohne Frameworks. Bietet volle Kontrolle, erfordert aber mehr manuelle Implementierung.
Programmausführungsmodell
Wenn eine Transaktion verarbeitet wird, führt die Runtime jede Instruktion
sequenziell über
process_message()
aus. Für jede Instruktion führt die Runtime folgende Schritte aus:
-
Bereitet den Instruktionskontext vor. Ruft
prepare_next_top_level_instruction()auf, um die Konto-Indizes der Instruktion zuzuordnen, Signer- und Writable-Flags zu setzen und denTransactionContextzu konfigurieren. -
Prüft Precompiles. Wenn das Programm ein Precompile ist, ruft die Runtime
process_precompile()auf, was immer noch einen Stack-Frame pusht und popt (überpush()undpop()), aber die sBPF-VM und das Programm-Cache-Lookup umgeht und nativen Code direkt ausführt. -
Schiebt einen Stack-Frame. (Schritte 3-6 erfolgen innerhalb von
InvokeContext::process_instruction()undprocess_executable_chain(), aufgerufen vonprocess_message().) Ruftpush()aufInvokeContextauf, was die Höhe des Instruction-Stacks erhöht und die Reentrancy-Regel durchsetzt: Ein Programm darf sich nur dann selbst erneut aufrufen, wenn der unmittelbare Aufrufer (das Programm an der aktuellen Spitze des Instruction-Stacks) dasselbe Programm ist. Tiefe Selbstrekursion (A -> A -> A) ist erlaubt, vorbehaltlich der Stack-Tiefenbeschränkungen. Andere Reentrancy-Muster (z. B. A ruft B auf, B ruft A auf) gebenInstructionError::ReentrancyNotAllowedzurück. -
Löst das Programm auf. Die Runtime ruft
process_executable_chain()auf, was den Loader bestimmt. Wenn der Owner des Program-Kontos der Native Loader ist, handelt es sich bei dem Programm um ein Builtin und seine Entrypoint-Funktion wird direkt aus demProgramCacheForTxBatchnachgeschlagen. Wenn der Owner einer der BPF-Loader ist (bpf_loader_deprecated,bpf_loader,bpf_loader_upgradeableoderloader_v4), wird stattdessen der Builtin-Entrypoint des Loaders aufgerufen. -
Führt das BPF-Programm aus. Bei BPF-Programmen schlägt der Loader-Entrypoint die kompilierte ausführbare Datei aus dem Programm-Cache nach. Die Funktion
execute():- Serialisiert Kontodaten in einen flachen Parameter-Buffer
- 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 Buffer zurück in den Kontostatus
-
Entfernt den Stack-Frame. Ruft
pop()auf, was überprüft, dass die Instruktion die Accounting-Regeln der Runtime nicht verletzt hat (Lamport-Guthaben sind ausgeglichen, schreibgeschützte Konten wurden nicht modifiziert, Kontodatengrößen liegen innerhalb der Grenzen). -
Akkumuliert Compute Units. Die von der Instruktion verbrauchten Compute Units werden über
saturating_addzur Transaktionssumme hinzugefügt.
Programm-Cache
Die Runtime verwaltet einen globalen
ProgramCache,
der verifizierte und kompilierte Programme speichert. Er ist Fork-Graph-bewusst
und verarbeitet Deployment-Sichtbarkeitsregeln, Eviction 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 Validator-Binärdatei kompiliert wurde (System, Stake, Vote usw.). Nicht on-chain gespeichert. |
Unloaded | Zuvor verifiziertes Programm, dessen kompilierte ausführbare Datei aus dem Speicher entfernt wurde, um Platz freizugeben. Verfolgt weiterhin Nutzungsstatistiken. Kann ohne erneute Verifizierung neu geladen werden. |
FailedVerification | Tombstone für Programme, die den sBPF-Verifier 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 Accounts (wie Buffer-Accounts) verwendet, 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 deployete oder aktualisierte Programme sind nicht sofort wirksam. Die
DELAY_VISIBILITY_SLOT_OFFSET-Konstante
beträgt 1, was bedeutet, dass ein in Slot N deployetes Programm in Slot N+1
wirksam wird. Während des Deployment-Slots gibt jeder Versuch, die neue Version
aufzurufen, DelayVisibility zurück, wodurch die Runtime "Program is not
deployed" meldet.
Eviction-Richtlinie
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 entfernt. Die
Nutzung wird durch
tx_usage_counter
(wird jedes Mal erhöht, wenn eine Transaktion auf das Programm verweist) und
latest_access_slot
nachverfolgt.
Neukompilierung an Epoch-Grenzen
Wenn eine Feature-Aktivierung den
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 einer transaktionsweiten
TransactionReturnData-Struktur
gespeichert, die die Datenbytes und die program_id des Programms enthält,
dessen Instruktion den Syscall aufgerufen hat. Die maximale Größe beträgt 1.024
Bytes (MAX_RETURN_DATA).
Is this page helpful?