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:
| Scenariusz | Dozwolone? | Punkt egzekwowania | Błąd |
|---|---|---|---|
| Wywołujący przekazuje konto jako zapisywalne, wywoływany oznacza jako zapisywalne | Tak | -- | -- |
| Wywołujący przekazuje konto jako tylko do odczytu, wywoływany oznacza jako zapisywalne | Nie | prepare_next_instruction | PrivilegeEscalation |
| Wywołujący przekazuje konto jako zapisywalne, wywoływany oznacza jako tylko do odczytu | Tak | -- | -- |
| Wywołujący przekazuje konto jako podpisujący, wywoływany oznacza jako podpisujący | Tak | -- | -- |
| Wywołujący przekazuje konto jako niepodpisujące, wywoływany oznacza jako podpisujące, konto to PDA z seedów wywołującego | Tak | prepare_next_instruction | -- |
| Wywołujący przekazuje konto jako niepodpisujące, wywoływany oznacza jako podpisujące, konto NIE jest PDA od wywołującego | Nie | prepare_next_instruction | PrivilegeEscalation |
| Wywołujący przekazuje konto jako podpisujące, wywoływany oznacza jako niepodpisujące | Tak | -- | -- |
| Program A wywołuje sam siebie bezpośrednio (A -> A) | Tak | push() | -- |
| Program A wywołuje B, który wywołuje A (pośrednia reentrancja) | Nie | push() | ReentrancyNotAllowed |
| CPI do native loader, bpf_loader, bpf_loader_deprecated lub precompile | Nie | check_authorized_program | ProgramNotSupported |
| Konto nie znalezione w transakcji | Nie | prepare_next_instruction | MissingAccount |
Zasady przyznawania uprawnień można podsumować następująco:
- 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.
- 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. - 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:
- Sprawdza liczbę zestawów seedów podpisujących względem
MAX_SIGNERS(16). - Sprawdza długość każdego zestawu seedów względem
MAX_SEEDS(16 seedów na zestaw). - Wywołuje
Pubkey::create_program_addressz seedami i program ID wywołującego. Jeśli seed nie generuje poprawnego PDA, CPI kończy się błędemBadSeeds. - 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_loaderorazbpf_loader_deprecatedbpf_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:
- Weryfikuje liczbę informacji o kontach
względem
MAX_CPI_ACCOUNT_INFOS(128 lub 255 z SIMD-0339). - Pobiera opłatę za tłumaczenie informacji o koncie
(tylko SIMD-0339):
(num_account_infos * 80) / 250CU. - Dla każdego nie-wykonywalnego, niepowtarzającego się konta buduje
CallerAccountpoprzez 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_unitCU.
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:
- 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óceniemReentrancyNotAllowed. - 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. - Wywołuje
pop()aby usunąć ramkę wywoływanego i zweryfikować, że salda lamportów się nie zmieniły (UnbalancedInstructionw 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?