Wykonywanie CPI i uprawnienia

Podsumowanie

CPI przechodzi przez 11 etapów środowiska uruchomieniowego, w tym sprawdzanie uprawnień, translację kont i synchronizację danych. Maksymalna głębokość wywołań: 5 (9 z SIMD-0268). Zasady uprawnień uniemożliwiają podwyższanie uprawnień przez wywoływany program ponad to, co przyznał wywołujący.

Zasady uprawnień

CPI rozszerza uprawnienia kont wywołującego na wywoływany program przy ścisłym przestrzeganiu zasad. Środowisko uruchomieniowe sprawdza te zasady w prepare_next_instruction:

ScenariuszDozwolone?Punkt egzekwowaniaBłąd
Wywołujący przekazuje konto jako zapisywalne, wywoływany oznacza jako zapisywalneTak----
Wywołujący przekazuje konto jako tylko do odczytu, wywoływany oznacza jako zapisywalneNieprepare_next_instructionPrivilegeEscalation
Wywołujący przekazuje konto jako zapisywalne, wywoływany oznacza jako tylko do odczytuTak----
Wywołujący przekazuje konto jako podpisujący, wywoływany oznacza jako podpisującyTak----
Wywołujący przekazuje konto jako niepodpisujące, wywoływany oznacza jako podpisujące, konto to PDA z seedów wywołującegoTakprepare_next_instruction--
Wywołujący przekazuje konto jako niepodpisujące, wywoływany oznacza jako podpisujące, konto NIE jest PDA od wywołującegoNieprepare_next_instructionPrivilegeEscalation
Wywołujący przekazuje konto jako podpisujące, wywoływany oznacza jako niepodpisująceTak----
Program A wywołuje sam siebie bezpośrednio (A -> A)Takpush()--
Program A wywołuje B, który wywołuje A (pośrednia reentrancja)Niepush()ReentrancyNotAllowed
CPI do native loader, bpf_loader, bpf_loader_deprecated lub precompileNiecheck_authorized_programProgramNotSupported
Konto nie znalezione w transakcjiNieprepare_next_instructionMissingAccount

Zasady przyznawania uprawnień można podsumować następująco:

  1. Uprawnienie do zapisu nie może być eskalowane. Jeśli wywołujący oznaczy konto jako tylko do odczytu, wywoływany nie może oznaczyć go jako zapisywalne.
  2. Uprawnienie do podpisu wymaga autoryzacji. Konto może być podpisującym u wywoływanego tylko wtedy, gdy (a) już było podpisującym u wywołującego LUB (b) jest to PDA wyprowadzone z seedów programu wywołującego za pomocą invoke_signed.
  3. Redukcja uprawnień jest zawsze dozwolona. Wywoływany może użyć mniejszych uprawnień niż te przyznane przez wywołującego.

Przebieg wykonania CPI

CPI przechodzi przez kilka warstw środowiska uruchomieniowego. Ta sekcja dokumentuje cały pipeline od wywołania SDK programu, przez granicę syscall, do środowiska uruchomieniowego i z powrotem. Każdy krok odnosi się do pliku źródłowego, który go implementuje.

Maksymalna wysokość wywołania instrukcji programu nazywana jest max_instruction_stack_depth i jest ustawiona na MAX_INSTRUCTION_STACK_DEPTH o wartości 5. Przy aktywnym MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268 wzrasta ona do 9.

Wysokość stosu 1 oznacza początkową instrukcję transakcji. Każdy CPI zwiększa wysokość o 1. Maksymalna wartość 5 oznacza, że program może wykonać CPI do 4 poziomów w głąb (do 8 poziomów z SIMD-0268).

Krok 1: Program wywołuje invoke lub invoke_signed

Program wywołuje invoke lub invoke_signed. invoke to cienki wrapper, który wywołuje invoke_signed z pustą tablicą seedów podpisujących. Funkcja SDK serializuje Instruction, AccountInfo (slice) oraz seedy podpisujących do pamięci VM, a następnie wywołuje syscall.

Krok 2: Wejście do syscall

SBF VM przekazuje obsługę do sol_invoke_signed_rust, który wywołuje wspólny punkt wejścia: cpi_common.

Krok 3: Pobranie kosztu wywołania

Pierwszą akcją wewnątrz cpi_common jest pobranie stałego kosztu wywołania z współdzielonego licznika obliczeń: invoke_units = 1 000 CU (lub 946 CU z SIMD-0339).

Krok 4: Translacja instrukcji z pamięci VM

Handler syscall tłumaczy instrukcję z przestrzeni adresowej VM programu na typy Rust po stronie hosta za pomocą translate_instruction_rust, który odczytuje strukturę StableInstruction, sprawdza długość danych względem MAX_INSTRUCTION_DATA_LEN (10 240 bajtów), a następnie pobiera koszt serializacji danych.

Krok 5: Translacja seedów podpisujących i wyprowadzanie PDA

Handler wywołuje translate_signers_rust. Dla każdego zestawu seedów podpisujących, runtime:

  1. Sprawdza liczbę zestawów seedów podpisujących względem MAX_SIGNERS (16).
  2. Sprawdza długość każdego zestawu seedów względem MAX_SEEDS (16 seedów na zestaw).
  3. Wywołuje Pubkey::create_program_address z seedami i program ID wywołującego. Jeśli seed nie generuje poprawnego PDA, CPI kończy się błędem BadSeeds.
  4. Zbiera powstałe klucze publiczne PDA do wektora signers.

Wyprowadzone w ten sposób PDA są traktowane jako ważni podpisujący dla instrukcji wywoływanego programu.

Krok 6: Sprawdzenie autoryzowanego programu

Przed kontynuacją runtime wywołuje check_authorized_program, aby zweryfikować, czy docelowy program jest dozwolony dla CPI. Następujące programy są blokowane:

  • Natwny loader
  • bpf_loader oraz bpf_loader_deprecated
  • bpf_loader_upgradeable (z wyjątkiem określonych instrukcji zarządzających: upgrade, set_authority, set_authority_checked (zależne od funkcji), extend_program_checked (zależne od funkcji), close)
  • Programy prekompilowane (ed25519, secp256k1 itd.)

Naruszenie skutkuje zwróceniem ProgramNotSupported.

Krok 7: Weryfikacja uprawnień (prepare_next_instruction)

Runtime wywołuje prepare_next_instruction, który buduje listę InstructionAccount wywoływanego programu i egzekwuje reguły uprawnień. Pełna tabela decyzyjna znajduje się w sekcji Reguły uprawnień poniżej.

Krok 8: Tłumaczenie informacji o kontach

Handler wywołuje translate_accounts, który:

  1. Weryfikuje liczbę informacji o kontach względem MAX_CPI_ACCOUNT_INFOS (128 lub 255 z SIMD-0339).
  2. Pobiera opłatę za tłumaczenie informacji o koncie (tylko SIMD-0339): (num_account_infos * 80) / 250 CU.
  3. Dla każdego nie-wykonywalnego, niepowtarzającego się konta buduje CallerAccount poprzez tłumaczenie wskaźników z pamięci VM do pamięci hosta. Obejmuje to naliczanie kosztu serializacji danych konta: account_data_len / cpi_bytes_per_unit CU.

Krok 9: Synchronizacja kont przed CPI (od wywołującego do wywoływanego)

Przed wykonaniem wywoływanego programu, runtime synchronizuje zmiany na kontach wywołującego, aby wywoływany mógł je zobaczyć. Funkcja update_callee_account wywoływana jest dla każdego przetłumaczonego konta, kopiując lamporty, dane i właściciela. Szczegółowe mapowanie pól znajdziesz w sekcji Synchronizacja danych konta.

Krok 10: Dodanie kontekstu instrukcji, wykonanie wywoływanego i usunięcie z kontekstu

Runtime wywołuje process_instruction, który:

  1. Wywołuje push() aby dodać nową ramkę do stosu instrukcji. push() wymusza regułę reentrancy: program może wywołać sam siebie tylko jeśli jest bezpośrednim wywołującym (czyli program A może wywołać A, ale A nie może wywołać B, który wywołuje A). Naruszenie skutkuje zwróceniem ReentrancyNotAllowed.
  2. Wywołuje process_executable_chain, który rozwiązuje punkt wejścia programu wywoływanego i uruchamia go. Wywoływany działa na tym samym współdzielonym liczniku CU. Całe zużycie CU przez wywoływanego zmniejsza pozostały budżet wywołującego.
  3. Wywołuje pop() aby usunąć ramkę wywoływanego i zweryfikować, że salda lamportów się nie zmieniły (UnbalancedInstruction w przeciwnym razie).

Krok 11: Synchronizacja kont po CPI (od wywoływanego do wywołującego)

Po powrocie z process_instruction (w tym usunięciu z kontekstu), runtime synchronizuje zmiany z powrotem do wywołującego za pomocą update_caller_account dla każdego zapisywalnego konta. Dodatkowo, update_caller_account_region aktualizuje mapowanie regionów pamięci VM dla kont, których regiony danych się zmieniły. Szczegółowe mapowanie pól znajdziesz w sekcji Synchronizacja danych konta.

Syscall CPI zwraca 0 (sukces) do programu wywołującego.

Is this page helpful?

Zarządzane przez

© 2026 Solana Foundation.
Wszelkie prawa zastrzeżone.
Bądź na bieżąco