Podsumowanie
Programy są kompilowane do sBPF za pomocą LLVM i uruchamiane w izolowanej maszynie wirtualnej z budżetem 1,4M CU na transakcję. Środowisko uruchomieniowe przechowuje w pamięci podręcznej do 512 skompilowanych programów, udostępnia syscalle do logowania, CPI, kryptografii i pamięci oraz opóźnia nowe wdrożenia o 1 slot.
Kompilacja
Solana używa LLVM do kompilacji programów do binariów ELF zawierających Solana Bytecode Format (sBPF). Binarium ELF jest przechowywane w łańcuchu bloków w koncie wykonywalnym.
sBPF to niestandardowy wariant kodu bajtowego eBPF dostosowany do środowiska uruchomieniowego Solany. Nie jest to standardowy eBPF — zawiera modyfikacje specyficzne dla Solany.
Pisanie programów
Programy Solany są pisane przede wszystkim w Rust z wykorzystaniem jednego z trzech podejść:
Anchor
Framework wykorzystujący makra Rust w celu ograniczenia powtarzalnego kodu. Zalecany dla większości programistów.
Pinocchio
Lekka biblioteka Rust bez kopiowania, zoptymalizowana pod kątem zużycia jednostek obliczeniowych i rozmiaru binarium. Zawiera dedykowane skrzynki dla popularnych CPI.
Natywny Rust
Bezpośredni Rust bez frameworków. Zapewnia pełną kontrolę, ale wymaga więcej ręcznej implementacji.
Zobacz Cross-Program Invocations, aby zapoznać się z konceptami i przykładami CPI stosowanymi we wszystkich tych podejściach.
Model wykonywania programów
Gdy transakcja jest przetwarzana, środowisko uruchomieniowe wykonuje każdą instrukcję
kolejno przez
process_message().
Dla każdej instrukcji środowisko uruchomieniowe:
-
Przygotowuje kontekst instrukcji. Wywołuje
prepare_next_top_level_instruction(), aby odwzorować indeksy kont instrukcji, ustawić flagi podpisującego i zapisu oraz skonfigurowaćTransactionContext. -
Sprawdza prekompilacje. Jeśli program jest prekompilacją, środowisko uruchomieniowe wywołuje
process_precompile(), które nadal odkłada i zdejmuje ramkę stosu (poprzezpush()ipop()), ale omija maszynę wirtualną sBPF i wyszukiwanie w pamięci podręcznej programów, wykonując natywny kod bezpośrednio. -
Odkłada ramkę stosu. (Kroki 3–6 odbywają się wewnątrz
InvokeContext::process_instruction()orazprocess_executable_chain(), wywoływanych zprocess_message().) Wywołujepush()naInvokeContext, co zwiększa wysokość stosu instrukcji i egzekwuje regułę reentrant: program może ponownie wejść do siebie tylko wtedy, gdy bezpośredni wywołujący (program na aktualnym szczycie stosu instrukcji) jest tym samym programem. Głęboka samodzielna rekurencja (A -> A -> A) jest dozwolona, z zastrzeżeniem limitów głębokości stosu. Inne wzorce reentrant (np. A wywołuje B wywołuje A) zwracająInstructionError::ReentrancyNotAllowed. -
Rozwiązuje program. Środowisko uruchomieniowe wywołuje
process_executable_chain(), które określa loader. Jeśli właścicielem program account jest natywny loader, program jest wbudowany i jego funkcja punktu wejścia jest wyszukiwana bezpośrednio zProgramCacheForTxBatch. Jeśli właścicielem jest jeden z loaderów BPF (bpf_loader_deprecated,bpf_loader,bpf_loader_upgradeablelubloader_v4), wywoływany jest własny wbudowany punkt wejścia loadera. -
Wykonuje program BPF. Dla programów BPF, punkt wejścia loadera wyszukuje skompilowany plik wykonywalny z pamięci podręcznej programów. Następnie funkcja
execute():- Serializuje dane kont do płaskiego bufora parametrów
- Tworzy maszynę wirtualną sBPF ze stosem, stertą i regionami pamięci
- Uruchamia skompilowany kod, zużywając jednostki obliczeniowe podczas wykonywania. Zwraca
ComputationalBudgetExceeded, jeśli budżet zostanie przekroczony. - Deserializuje dane kont z bufora z powrotem do stanu konta
-
Zdejmuje ramkę stosu. Wywołuje
pop(), który weryfikuje, że instrukcja nie naruszyła zasad rozliczeniowych środowiska uruchomieniowego (salda lamport są zbilansowane, konta tylko do odczytu nie zostały zmodyfikowane, rozmiary danych kont mieszczą się w limitach). -
Sumuje jednostki obliczeniowe. Jednostki obliczeniowe zużyte przez instrukcję są dodawane do sumy transakcji za pomocą
saturating_add.
Pamięć podręczna programów
Środowisko uruchomieniowe utrzymuje globalny
ProgramCache,
który przechowuje zweryfikowane i skompilowane programy. Jest świadomy grafu forków i obsługuje
reguły widoczności wdrożeń, eksmisję oraz rekompilację na granicy epoch.
Typy wpisów w pamięci podręcznej
Każdy program w pamięci podręcznej ma
ProgramCacheEntryType,
który określa jego zachowanie w czasie wykonywania:
| Typ | Opis |
|---|---|
Loaded | Zweryfikowany i skompilowany program, gotowy do wykonania. |
Builtin | Natywny program skompilowany do binarium validator (System, Stake, Vote itp.). Nieprzechowywany w łańcuchu bloków. |
Unloaded | Wcześniej zweryfikowany program, którego skompilowany plik wykonywalny został usunięty z pamięci w celu zwolnienia miejsca. Nadal śledzi statystyki użycia. Może zostać ponownie załadowany bez ponownej weryfikacji. |
FailedVerification | Nagrobek dla programów, które nie przeszły weryfikatora sBPF przy bieżącym zestawie funkcji. Może stać się Loaded, jeśli aktywacje funkcji zmienią reguły weryfikacji. |
Closed | Nagrobek dla programów, które zostały jawnie zamknięte lub nigdy nie zostały wdrożone. Używany również dla kont (takich jak konta buforów) należących do loadera, które nie zawierają kodu wykonywalnego. |
DelayVisibility | Syntetyczny nagrobek zwracany przez ProgramCacheForTxBatch::find(), gdy wpis Loaded istnieje, ale nie jest jeszcze aktywny (jego effective_slot jest w przyszłości). Nigdy nie jest przechowywany bezpośrednio w pamięci podręcznej. |
Opóźnienie widoczności
Nowo wdrożone lub zaktualizowane programy nie są aktywne natychmiast. Stała
DELAY_VISIBILITY_SLOT_OFFSET
ma wartość 1, co oznacza, że program wdrożony w slot N staje się aktywny w slot
N+1. Podczas slot wdrożenia każda próba wywołania nowej wersji zwraca
DelayVisibility, powodując, że środowisko uruchomieniowe zgłasza komunikat "Program is not deployed."
Polityka eksmisji
Pamięć podręczna przechowuje do
MAX_LOADED_ENTRY_COUNT
(512) skompilowanych wpisów programów. Po osiągnięciu limitu najmniej używane
programy są eksmitowane do stanu Unloaded. Użycie jest śledzone przez
tx_usage_counter
(inkrementowany przy każdym odwołaniu transakcji do programu) oraz
latest_access_slot.
Rekompilacja na granicy epoch
Jeśli aktywacja funkcji zmienia
ProgramRuntimeEnvironments
na granicy epoch, wszystkie programy w pamięci podręcznej są
rekompilowane
pod kątem nowego środowiska.
Dane zwrotne
Programy mogą ustawiać dane zwrotne za pomocą syscalla sol_set_return_data. Dane są
przechowywane w strukturze
TransactionReturnData
na poziomie transakcji, która przechowuje bajty danych oraz program_id programu, którego
instrukcja wywołała syscall. Maksymalny rozmiar wynosi 1024 bajty
(MAX_RETURN_DATA).
Is this page helpful?