CPI-Ausführung und Berechtigungen

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:

SzenarioErlaubt?DurchsetzungspunktFehler
Aufrufer übergibt Konten als beschreibbar, Aufgerufener markiert als beschreibbarJa----
Aufrufer übergibt Konten als schreibgeschützt, Aufgerufener markiert als beschreibbarNeinprepare_next_instructionPrivilegeEscalation
Aufrufer übergibt Konten als beschreibbar, Aufgerufener markiert als schreibgeschütztJa----
Aufrufer übergibt Konten als Signer, Aufgerufener markiert als SignerJa----
Aufrufer übergibt Konten als Nicht-Signer, Aufgerufener markiert als Signer, Konten ist eine PDA, die von den Seeds des Aufrufers abgeleitet wurdeJaprepare_next_instruction--
Aufrufer übergibt Konten als Nicht-Signer, Aufgerufener markiert als Signer, Konten ist KEINE PDA vom AufruferNeinprepare_next_instructionPrivilegeEscalation
Aufrufer übergibt Konten als Signer, Aufgerufener markiert als Nicht-SignerJa----
Programm A ruft sich selbst direkt auf (A -> A)Japush()--
Programm A ruft B auf, welches A aufruft (indirekte Reentrancy)Neinpush()ReentrancyNotAllowed
CPI an nativen Loader, bpf_loader, bpf_loader_deprecated oder PrecompileNeincheck_authorized_programProgramNotSupported
Konten nicht in Transaktion gefundenNeinprepare_next_instructionMissingAccount

Die Berechtigungsregeln lassen sich wie folgt zusammenfassen:

  1. Schreibberechtigung kann nicht erweitert werden. Wenn der Aufrufer ein Konto als schreibgeschützt markiert, kann der Aufgerufene es nicht als beschreibbar markieren.
  2. 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_signed abgeleitet wurde.
  3. 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:

  1. Prüft die Anzahl der Signer-Seed-Sätze gegen MAX_SIGNERS (16).
  2. Prüft die Länge jedes Seed-Satzes gegen MAX_SEEDS (16 Seeds pro Satz).
  3. Ruft Pubkey::create_program_address mit den Seeds und der Programm-ID des Aufrufers auf. Wenn die Seeds keine gültige PDA erzeugen, schlägt der CPI mit BadSeeds fehl.
  4. 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_loader und bpf_loader_deprecated
  • bpf_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:

  1. Validiert die Anzahl der Konten-Informationen gegen MAX_CPI_ACCOUNT_INFOS (128 oder 255 mit SIMD-0339).
  2. Berechnet die Kosten für die Übersetzung der Konten-Informationen (nur SIMD-0339): (num_account_infos * 80) / 250 CUs.
  3. 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_unit CUs.

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:

  1. 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 wird ReentrancyNotAllowed zurückgegeben.
  2. Ruft process_executable_chain auf, 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.
  3. Ruft pop() auf, um den Frame des Aufgerufenen zu entfernen und zu überprüfen, dass die Lamport-Salden unverändert sind (UnbalancedInstruction falls 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?

Verwaltet von

© 2026 Solana Foundation.
Alle Rechte vorbehalten.
Verbinden Sie sich