Invocación entre programas (CPI)

Una invocación entre programas (cross program invocation o CPI) se refiere a cuando un programa invoca las instrucciones de otro programa. Este mecanismo permite la composición de programas en Solana.

Puede pensar en instrucciones como endpoints API que un programa expone a la red y una CPI como si una API internamente invoca otra API.

Cross Program InvocationCross Program Invocation

Cuando un programa inicia una invocación a otro programa (CPI):

  • Dada una transacción al programa A que invoca al programa B, las firmas aplicadas a la transacción se extienden a la invocación del programa B
  • El programa invocado (B) puede hacer CPI a otros programas, hasta una profundidad máxima de 4 (ej.: B->C, C->D)
  • Los programas pueden "firmar" en nombre de las PDAs derivadas de su identificador
Info

El tiempo de ejecución del programa Solana define una constante llamada max_invoke_stack_height, que se establece en el valor de 5. Esto representa la altura máxima de la pila de la invocación de instrucciones del programa. La altura de la pila comienza en 1 para las instrucciones de una transacción, incrementa en 1 cada vez que un programa invoca otra instrucción. Este ajuste efectivamente limita la profundidad de invocación de una CPI a 4.

Puntos clave #

  • Las CPIs habilitan a programas de Solana para invocar directamente las instrucciones de otro programa.

  • Los derechos de los que firman en el programa invocador se extienden al programa invocado.

  • Al crear una CPI, los programas pueden "firmar" en nombre de PDAs derivadas de su propio identificador.

  • El programa invocado puede hacer CPI adicionales para otros programas, hasta una profundidad máxima de 4.

Cómo escribir una CPI #

Escribir una instrucción para un CPI sigue el mismo patrón que construir una instrucción para añadir a una transacción. Bajo el capó, cada instrucción CPI debe especificar la siguiente información:

  • Program address: Especifica el programa que se está invocando
  • Accounts: Lista todas las cuentas a las que leen o escriban las instrucciones, incluyendo otros programas
  • Instruction Data: Especifica qué instrucción se debe invocar en el programa, además de cualquier información adicional requerida por la instrucción (argumentos de la función)

Dependiendo del programa al que esté haciendo la invocación, puede haber crates disponibles con funciones que te ayuden a construir la instrucción. A continuación, los programas ejecutan las CPI utilizando una de las siguientes funciones del crate solana_program:

  • invoke - usado cuando no hay firmas PDA
  • invoke_signed - usado cuando el programa invocador necesita firmar con una PDA derivada de su identificador

CPI básica #

La función invoke se utiliza cuando se crea una CPI que no requiere firmas de PDA. Al crear CPIs, las cuentas que proporcionan firmas al programa invocador extienden sus privilegios al programa invocado.

pub fn invoke(
    instruction: &Instruction,
    account_infos: &[AccountInfo<'_>]
) -> Result<(), ProgramError>

Aquí hay un programa de ejemplo en Solana Playground que hace una CPI usando la función invoke para llamar a la instrucción de transferencia en el programa del sistema. También puedes hacer referencia a la Guía básica de CPI para más detalles.

CPI con firmas de PDAs #

La función invoke_signed se utiliza cuando se crea una CPI que requiere firmas de PDA. Las seeds usadas para derivar las PDAs que firman se pasan a la función invoke_signed como signer_seeds.

Puedes hacer referencia a la página Dirección Derivada de un Programa para ver detalles sobre cómo se derivan las PDA.

pub fn invoke_signed(
    instruction: &Instruction,
    account_infos: &[AccountInfo<'_>],
    signers_seeds: &[&[&[u8]]]
) -> Result<(), ProgramError>

El tiempo de ejecución utiliza los derechos otorgados al programa invocador para determinar qué derechos pueden extenderse al programa invocado. En este contexto, los privilegios se refieren a los firmantes y a las cuentas con permisos de escritura. Por ejemplo, si la instrucción que el invocador está procesando contiene un firmante o una cuenta con permisos de escritura, entonces el invocador puede invocar una instrucción que también contenga ese firmante y/o cuenta con permisos de escritura.

Aunque las PDA no tienen llaves privadas, pueden actuar como firmantes en una instrucción a través de una CPI. Para verificar que una PDA es derivada del programa invocador, las seeds utilizadas para generar la PDA deben ser incluidas como signers_seeds.

Cuando la CPI es procesada, el tiempo de ejecución de Solana internamente llama a create_program_address usando el signers_seeds y el program_id del programa invocador. Si se encuentra una PDA válida, la dirección se añade como firmante válido.

Aquí hay un programa de ejemplo en Solana Playground que hace una CPI usando la función invoke_signed para llamar a la instrucción de transferencia en el programa del sistema con un firmante PDA. Puede consultar la guía CPI con firmante PDA para más detalles.