Exécution et privilèges des CPI

Résumé

Les CPI passent par 11 étapes du runtime incluant la vérification des privilèges, la traduction des comptes et la synchronisation des données. Profondeur d'appel maximale : 5 (9 avec SIMD-0268). Les règles de privilèges empêchent l'appelé d'escalader au-delà de ce que l'appelant a accordé.

Règles de privilèges

Les CPI étendent les privilèges de compte de l'appelant à l'appelé avec une application stricte. Le runtime vérifie ces règles dans prepare_next_instruction :

ScénarioAutorisé ?Point d'applicationErreur
L'appelant passe le compte en écriture, l'appelé le marque en écritureOui----
L'appelant passe le compte en lecture seule, l'appelé le marque en écritureNonprepare_next_instructionPrivilegeEscalation
L'appelant passe le compte en écriture, l'appelé le marque en lecture seuleOui----
L'appelant passe le compte comme signataire, l'appelé le marque comme signataireOui----
L'appelant passe le compte comme non-signataire, l'appelé le marque comme signataire, le compte est un PDA dérivé des seeds de l'appelantOuiprepare_next_instruction--
L'appelant passe le compte comme non-signataire, l'appelé le marque comme signataire, le compte n'est PAS un PDA de l'appelantNonprepare_next_instructionPrivilegeEscalation
L'appelant passe le compte comme signataire, l'appelé le marque comme non-signataireOui----
Le programme A s'appelle lui-même directement (A -> A)Ouipush()--
Le programme A appelle B qui appelle A (réentrance indirecte)Nonpush()ReentrancyNotAllowed
CPI vers native loader, bpf_loader, bpf_loader_deprecated ou precompileNoncheck_authorized_programProgramNotSupported
Compte introuvable dans la transactionNonprepare_next_instructionMissingAccount

Les règles de privilège peuvent être résumées comme suit :

  1. Le privilège d'écriture ne peut pas être élevé. Si l'appelant marque un compte comme lecture seule, l'appelé ne peut pas le marquer comme accessible en écriture.
  2. Le privilège de signataire nécessite une autorisation. Un compte peut être un signataire dans l'appelé uniquement si (a) il était déjà un signataire dans l'appelant, OU (b) il s'agit d'un PDA dérivé des seeds du programme appelant via invoke_signed.
  3. La réduction de privilège est toujours autorisée. L'appelé peut utiliser moins de privilèges que ceux accordés par l'appelant.

Flux d'exécution CPI

Un CPI traverse plusieurs couches d'exécution. Cette section documente le pipeline complet depuis l'appel SDK du programme à travers la limite syscall jusqu'au runtime et retour. Chaque étape fait référence au fichier source qui l'implémente.

La hauteur maximale de l'invocation d'instruction de programme est appelée max_instruction_stack_depth et est définie à la constante MAX_INSTRUCTION_STACK_DEPTH de 5. Avec MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268 actif, cela augmente à 9.

La hauteur de pile 1 correspond à l'instruction de transaction initiale. Chaque CPI incrémente la hauteur de 1. Un maximum de 5 signifie qu'un programme peut effectuer des CPI jusqu'à 4 niveaux de profondeur (8 niveaux de profondeur avec SIMD-0268).

Étape 1 : le programme appelle invoke ou invoke_signed

Le programme appelle invoke ou invoke_signed. invoke est un wrapper léger qui appelle invoke_signed avec un tableau de seeds de signataire vide. La fonction SDK sérialise l'Instruction, le slice AccountInfo et les seeds de signataire dans la mémoire VM, puis déclenche le syscall.

Étape 2 : entrée syscall

La VM SBF dispatche vers le gestionnaire syscall sol_invoke_signed_rust, qui appelle le point d'entrée partagé : cpi_common.

Étape 3 : consommer le coût d'invocation

La première action à l'intérieur de cpi_common consiste à facturer le coût d'invocation fixe à partir du compteur de calcul partagé : invoke_units = 1 000 CU (ou 946 CU avec SIMD-0339).

Étape 4 : traduire l'instruction depuis la mémoire de la VM

Le gestionnaire de syscall traduit l'instruction depuis l'espace d'adressage de la VM du programme vers les types Rust côté hôte via translate_instruction_rust, qui lit une structure StableInstruction, valide la longueur des données par rapport à MAX_INSTRUCTION_DATA_LEN (10 240 octets), puis facture le coût de sérialisation des données.

Étape 5 : traduire les seeds de signataire et dériver les PDA

Le gestionnaire appelle translate_signers_rust. Pour chaque ensemble de seeds de signataire, le runtime :

  1. Vérifie le nombre d'ensembles de seeds de signataire par rapport à MAX_SIGNERS (16).
  2. Vérifie la longueur de chaque ensemble de seeds par rapport à MAX_SEEDS (16 seeds par ensemble).
  3. Appelle Pubkey::create_program_address avec les seeds et l'ID de programme de l'appelant. Si les seeds ne produisent pas de PDA valide, le CPI échoue avec BadSeeds.
  4. Collecte les clés publiques PDA résultantes dans un vec signers.

Ces PDA dérivés sont traités comme des signataires valides pour l'instruction appelée.

Étape 6 : vérifier le programme autorisé

Avant de continuer, le runtime appelle check_authorized_program pour vérifier que le programme cible est autorisé pour le CPI. Les programmes suivants sont bloqués :

  • Le chargeur natif
  • bpf_loader et bpf_loader_deprecated
  • bpf_loader_upgradeable (sauf instructions de gestion spécifiques : upgrade, set_authority, set_authority_checked (avec feature gate), extend_program_checked (avec feature gate), close)
  • Programmes de précompilation (ed25519, secp256k1, etc.)

Une violation renvoie ProgramNotSupported.

Étape 7 : vérification des privilèges (prepare_next_instruction)

Le runtime appelle prepare_next_instruction qui construit la liste InstructionAccount de l'appelé et applique les règles de privilèges. Voir Règles de privilèges ci-dessous pour la table de décision complète.

Étape 8 : traduire les informations de compte

Le gestionnaire appelle translate_accounts qui :

  1. Valide le nombre d'informations de compte par rapport à MAX_CPI_ACCOUNT_INFOS (128, ou 255 avec SIMD-0339).
  2. Facture le coût de traduction des informations de compte (SIMD-0339 uniquement) : (num_account_infos * 80) / 250 CU.
  3. Pour chaque compte non exécutable et non dupliqué, construit un CallerAccount en traduisant les pointeurs de la mémoire VM vers la mémoire hôte. Cela inclut la facturation du coût de sérialisation des données par compte : account_data_len / cpi_bytes_per_unit CU.

Étape 9 : synchronisation pré-CPI des comptes (appelant vers appelé)

Avant d'exécuter l'appelé, le runtime synchronise les modifications de compte de l'appelant afin que l'appelé puisse les voir. La fonction update_callee_account est appelée pour chaque compte traduit, copiant les lamports, les données et le propriétaire. Voir Synchronisation des données de compte pour le mappage détaillé des champs.

Étape 10 : empiler le contexte d'instruction, exécuter l'appelé et dépiler

Le runtime appelle process_instruction, qui :

  1. Appelle push() pour ajouter une nouvelle frame à la pile d'instructions. push() applique la règle de réentrance : un programme ne peut s'appeler lui-même que s'il est l'appelant direct (c'est-à-dire que le programme A peut appeler A, mais A ne peut pas appeler B qui appelle A). Une violation retourne ReentrancyNotAllowed.
  2. Appelle process_executable_chain qui résout le point d'entrée du programme appelé et l'invoque. L'appelé s'exécute avec le même compteur de calcul partagé. Toute consommation de CU par l'appelé réduit le budget restant de l'appelant.
  3. Appelle pop() pour retirer la frame de l'appelé et vérifier que les soldes de lamports sont inchangés (UnbalancedInstruction sinon).

Étape 11 : synchronisation post-CPI des comptes (appelé vers appelant)

Après le retour de process_instruction (qui inclut le dépilage), le runtime synchronise les modifications vers l'appelant via update_caller_account pour chaque compte modifiable. De plus, update_caller_account_region met à jour les mappages de régions mémoire VM pour les comptes dont les régions de données ont changé. Voir Synchronisation des données de compte pour le mappage détaillé des champs.

Le syscall CPI renvoie 0 (succès) au programme appelant.

Is this page helpful?

Géré par

© 2026 Fondation Solana.
Tous droits réservés.
Restez connecté