Wykonywanie programów

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ść:

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:

  1. Przygotowuje kontekst instrukcji. Wywołuje prepare_next_top_level_instruction(), aby odwzorować indeksy kont instrukcji, ustawić flagi podpisującego i zapisu oraz skonfigurować TransactionContext.

  2. Sprawdza prekompilacje. Jeśli program jest prekompilacją, środowisko uruchomieniowe wywołuje process_precompile(), które nadal odkłada i zdejmuje ramkę stosu (poprzez push() i pop()), ale omija maszynę wirtualną sBPF i wyszukiwanie w pamięci podręcznej programów, wykonując natywny kod bezpośrednio.

  3. Odkłada ramkę stosu. (Kroki 3–6 odbywają się wewnątrz InvokeContext::process_instruction() oraz process_executable_chain(), wywoływanych z process_message().) Wywołuje push() na InvokeContext, 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.

  4. 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 z ProgramCacheForTxBatch. Jeśli właścicielem jest jeden z loaderów BPF (bpf_loader_deprecated, bpf_loader, bpf_loader_upgradeable lub loader_v4), wywoływany jest własny wbudowany punkt wejścia loadera.

  5. 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
  6. 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).

  7. 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:

TypOpis
LoadedZweryfikowany i skompilowany program, gotowy do wykonania.
BuiltinNatywny program skompilowany do binarium validator (System, Stake, Vote itp.). Nieprzechowywany w łańcuchu bloków.
UnloadedWcześ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.
FailedVerificationNagrobek 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.
ClosedNagrobek 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.
DelayVisibilitySyntetyczny 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?

Spis treści

Edytuj stronę