Zusammenfassung
CPIs durchlaufen 11 Runtime-Schritte, einschließlich Berechtigungsprüfung, Konten-Übersetzung und Datensynchronisation. Maximale Aufruftiefe: 5 (9 mit SIMD-0268). Berechtigungsregeln verhindern, dass der Aufgerufene über das hinausgeht, was der Aufrufer gewährt hat.
Berechtigungsregeln
CPIs erweitern die Konten-Berechtigungen des Aufrufers auf den Aufgerufenen mit
strikter Durchsetzung. Die Runtime prüft diese Regeln in
prepare_next_instruction:
| Szenario | Erlaubt? | Durchsetzungspunkt | Fehler |
|---|---|---|---|
| Aufrufer übergibt Konten als beschreibbar, Aufgerufener markiert als beschreibbar | Ja | -- | -- |
| Aufrufer übergibt Konten als schreibgeschützt, Aufgerufener markiert als beschreibbar | Nein | prepare_next_instruction | PrivilegeEscalation |
| Aufrufer übergibt Konten als beschreibbar, Aufgerufener markiert als schreibgeschützt | Ja | -- | -- |
| Aufrufer übergibt Konten als Signer, Aufgerufener markiert als Signer | Ja | -- | -- |
| Aufrufer übergibt Konten als Nicht-Signer, Aufgerufener markiert als Signer, Konten ist eine PDA, die von den Seeds des Aufrufers abgeleitet wurde | Ja | prepare_next_instruction | -- |
| Aufrufer übergibt Konten als Nicht-Signer, Aufgerufener markiert als Signer, Konten ist KEINE PDA vom Aufrufer | Nein | prepare_next_instruction | PrivilegeEscalation |
| Aufrufer übergibt Konten als Signer, Aufgerufener markiert als Nicht-Signer | Ja | -- | -- |
| Programm A ruft sich selbst direkt auf (A -> A) | Ja | push() | -- |
| Programm A ruft B auf, welches A aufruft (indirekte Reentrancy) | Nein | push() | ReentrancyNotAllowed |
| CPI an nativen Loader, bpf_loader, bpf_loader_deprecated oder Precompile | Nein | check_authorized_program | ProgramNotSupported |
| Konten nicht in Transaktion gefunden | Nein | prepare_next_instruction | MissingAccount |
Die Berechtigungsregeln lassen sich wie folgt zusammenfassen:
- Schreibberechtigung kann nicht erweitert werden. Wenn der Aufrufer ein Konto als schreibgeschützt markiert, kann der Aufgerufene es nicht als beschreibbar markieren.
- Signer-Berechtigung erfordert Autorisierung. Ein Konto kann im
Aufgerufenen nur dann ein Signer sein, wenn (a) es bereits im Aufrufer ein
Signer war, ODER (b) es sich um eine PDA handelt, die aus den Seeds des
aufrufenden Programms über
invoke_signedabgeleitet wurde. - Berechtigungsreduzierung ist immer erlaubt. Der Aufgerufene kann weniger Berechtigungen nutzen, als der Aufrufer gewährt hat.
CPI-Ausführungsablauf
Ein CPI durchläuft mehrere Runtime-Ebenen. Dieser Abschnitt dokumentiert die vollständige Pipeline vom Programm-SDK-Aufruf über die Syscall-Grenze in die Runtime und zurück. Jeder Schritt verweist auf die Quelldatei, die ihn implementiert.
Die maximale Höhe der Programminstruktionsaufrufe wird als
max_instruction_stack_depth
bezeichnet und ist auf die
MAX_INSTRUCTION_STACK_DEPTH
Konstante von 5 gesetzt. Mit MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268 aktiv, erhöht sich diese auf 9.
Stack-Höhe 1 ist die initiale Transaktionsinstruktion. Jeder CPI erhöht die Höhe um 1. Ein Maximum von 5 bedeutet, dass ein Programm CPIs bis zu 4 Ebenen tief ausführen kann (8 Ebenen tief mit SIMD-0268).
Schritt 1: Programm ruft invoke oder invoke_signed auf
Das Programm ruft
invoke
oder
invoke_signed
auf. invoke ist ein dünner Wrapper, der invoke_signed mit einem
leeren Signer-Seeds-Array aufruft. Die SDK-Funktion serialisiert die
Instruction, AccountInfo Slice und Signer-Seeds in den VM-Speicher
und löst dann den Syscall aus.
Schritt 2: Syscall-Einstieg
Die SBF-VM leitet an den
sol_invoke_signed_rust
Syscall-Handler weiter, der in den gemeinsamen Einstiegspunkt aufruft:
cpi_common.
Schritt 3: Aufrufkosten verbrauchen
Die erste Aktion innerhalb von cpi_common besteht darin,
die festen Aufrufkosten zu berechnen
vom gemeinsamen Compute-Meter:
invoke_units
= 1.000 CUs (oder 946 CUs mit SIMD-0339).
Schritt 4: Anweisung aus VM-Speicher übersetzen
Der Syscall-Handler übersetzt die Anweisung aus dem VM-Adressraum des Programms
in hostseitige Rust-Typen über
translate_instruction_rust,
welcher eine StableInstruction-Struktur liest, die Datenlänge gegen
MAX_INSTRUCTION_DATA_LEN
(10.240 Bytes) validiert und dann die
Datenserialisierungskosten berechnet.
Schritt 5: Signer-Seeds übersetzen und PDAs ableiten
Der Handler ruft
translate_signers_rust
auf. Für jeden Satz von Signer-Seeds führt die Runtime folgende Schritte aus:
- Prüft die Anzahl der Signer-Seed-Sätze gegen
MAX_SIGNERS(16). - Prüft die Länge jedes Seed-Satzes gegen
MAX_SEEDS(16 Seeds pro Satz). - Ruft
Pubkey::create_program_addressmit den Seeds und der Programm-ID des Aufrufers auf. Wenn die Seeds keine gültige PDA erzeugen, schlägt der CPI mitBadSeedsfehl. - Sammelt die resultierenden PDA-Pubkeys in einem
signers-Vec.
Diese abgeleiteten PDAs werden als gültige Signer für die Callee-Anweisung behandelt.
Schritt 6: Autorisiertes Programm prüfen
Bevor fortgefahren wird, ruft die Runtime
check_authorized_program
auf, um zu überprüfen, ob das Zielprogramm für CPI zugelassen ist. Die folgenden
Programme sind blockiert:
- Der native Loader
bpf_loaderundbpf_loader_deprecatedbpf_loader_upgradeable(außer spezifische Verwaltungs-Anweisungen:upgrade,set_authority,set_authority_checked(Feature-gesteuert),extend_program_checked(Feature-gesteuert),close)- Precompile-Programme (ed25519, secp256k1, etc.)
Ein Verstoß gibt
ProgramNotSupported
zurück.
Schritt 7: Berechtigungsüberprüfung (prepare_next_instruction)
Die Runtime ruft
prepare_next_instruction
auf, welche die InstructionAccount-Liste des Callees erstellt und
Berechtigungsregeln durchsetzt. Siehe Berechtigungsregeln
unten für die vollständige Entscheidungstabelle.
Schritt 8: Konten-Informationen übersetzen
Der Handler ruft translate_accounts auf, welcher:
- Validiert die Anzahl der Konten-Informationen
gegen
MAX_CPI_ACCOUNT_INFOS(128 oder 255 mit SIMD-0339). - Berechnet die Kosten für die Übersetzung der Konten-Informationen
(nur SIMD-0339):
(num_account_infos * 80) / 250CUs. - Erstellt für jedes nicht-ausführbare, nicht-duplizierte Konto ein
CallerAccount, indem Zeiger vom VM-Speicher in den Host-Speicher übersetzt werden. Dies beinhaltet die Berechnung der Serialisierungskosten pro Konto:account_data_len / cpi_bytes_per_unitCUs.
Schritt 9: Pre-CPI-Konten-Synchronisation (Aufrufer zu Aufgerufenem)
Vor der Ausführung des Aufgerufenen synchronisiert die Runtime die Änderungen
des Aufrufers, damit der Aufgerufene diese sehen kann. Die Funktion
update_callee_account
wird für jedes übersetzte Konto aufgerufen und kopiert Lamports, Daten und
Eigentümer. Siehe
Synchronisation von Konten-Daten
für die detaillierte Feld-Zuordnung.
Schritt 10: Instruktionskontext pushen, Aufgerufenen ausführen und poppen
Die Runtime ruft
process_instruction
auf, welcher:
- Ruft
push()auf, um einen neuen Frame zum Instruktions-Stack hinzuzufügen.push()erzwingt die Reentrancy-Regel: Ein Programm darf sich nur selbst aufrufen, wenn es der direkte Aufrufer ist (d. h. Programm A kann A aufrufen, aber A kann nicht B aufrufen, welches A aufruft). Bei Verletzung wirdReentrancyNotAllowedzurückgegeben. - Ruft
process_executable_chainauf, welcher den Einstiegspunkt des aufgerufenen Programms auflöst und es aufruft. Der Aufgerufene läuft mit demselben gemeinsamen Compute-Meter. Jeder CU-Verbrauch durch den Aufgerufenen reduziert das verbleibende Budget des Aufrufers. - Ruft
pop()auf, um den Frame des Aufgerufenen zu entfernen und zu überprüfen, dass die Lamport-Salden unverändert sind (UnbalancedInstructionfalls nicht).
Schritt 11: Post-CPI-Konten-Synchronisation (Aufgerufener zu Aufrufer)
Nachdem process_instruction zurückkehrt (was das Poppen einschließt),
synchronisiert die Runtime die Änderungen zurück zum Aufrufer über
update_caller_account
für jedes beschreibbare Konto. Zusätzlich aktualisiert
update_caller_account_region
die VM-Speicherregion-Zuordnungen für Konten, deren Datenbereiche sich geändert
haben. Siehe
Synchronisation von Konten-Daten
für die detaillierte Feld-Zuordnung.
Der CPI-Syscall gibt 0 (Erfolg) an das aufrufende Programm zurück.
Is this page helpful?