Esecuzione CPI e privilegi

Riepilogo

Le CPI attraversano 11 passaggi del runtime inclusi controllo dei privilegi, traduzione degli account e sincronizzazione dei dati. Profondità massima di chiamata: 5 (9 con SIMD-0268). Le regole dei privilegi impediscono al chiamato di escalare oltre quanto concesso dal chiamante.

Regole dei privilegi

Le CPI estendono i privilegi degli account del chiamante al chiamato con applicazione rigorosa. Il runtime verifica queste regole in prepare_next_instruction:

ScenarioConsentito?Punto di applicazioneErrore
Il chiamante passa l'account come scrivibile, il chiamato lo contrassegna come scrivibile----
Il chiamante passa l'account come sola lettura, il chiamato lo contrassegna come scrivibileNoprepare_next_instructionPrivilegeEscalation
Il chiamante passa l'account come scrivibile, il chiamato lo contrassegna come sola lettura----
Il chiamante passa l'account come firmatario, il chiamato lo contrassegna come firmatario----
Il chiamante passa l'account come non firmatario, il chiamato lo contrassegna come firmatario, l'account è un PDA derivato dai seed del chiamanteprepare_next_instruction--
Il chiamante passa l'account come non firmatario, il chiamato lo contrassegna come firmatario, l'account NON è un PDA del chiamanteNoprepare_next_instructionPrivilegeEscalation
Il chiamante passa l'account come firmatario, il chiamato lo contrassegna come non firmatario----
Il programma A chiama se stesso direttamente (A -> A)push()--
Il programma A chiama B che chiama A (rientranza indiretta)Nopush()ReentrancyNotAllowed
CPI a native loader, bpf_loader, bpf_loader_deprecated o precompileNocheck_authorized_programProgramNotSupported
Account non trovato nella transazioneNoprepare_next_instructionMissingAccount

Le regole dei privilegi possono essere riassunte come:

  1. Il privilegio di scrittura non può essere escalato. Se il chiamante contrassegna un account come di sola lettura, il chiamato non può contrassegnarlo come scrivibile.
  2. Il privilegio di firmatario richiede autorizzazione. Un account può essere un firmatario nel chiamato solo se (a) era già un firmatario nel chiamante, OPPURE (b) è un PDA derivato dai seed del programma chiamante tramite invoke_signed.
  3. La riduzione dei privilegi è sempre consentita. Il chiamato può utilizzare meno privilegi di quelli concessi dal chiamante.

Flusso di esecuzione CPI

Una CPI attraversa diversi livelli di runtime. Questa sezione documenta l'intera pipeline dalla chiamata SDK del programma attraverso il confine syscall nel runtime e ritorno. Ogni passaggio fa riferimento al file sorgente che lo implementa.

L'altezza massima dell'invocazione dell'istruzione del programma è chiamata max_instruction_stack_depth ed è impostata alla costante MAX_INSTRUCTION_STACK_DEPTH di 5. Con MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268 attivo, questo aumenta a 9.

L'altezza dello stack 1 è l'istruzione di transazione iniziale. Ogni CPI incrementa l'altezza di 1. Un massimo di 5 significa che un programma può effettuare CPI fino a 4 livelli di profondità (8 livelli di profondità con SIMD-0268).

Passaggio 1: il programma chiama invoke o invoke_signed

Il programma chiama invoke o invoke_signed. invoke è un wrapper sottile che chiama invoke_signed con un array di seed del firmatario vuoto. La funzione SDK serializza Instruction, lo slice AccountInfo e i seed del firmatario nella memoria della VM, quindi attiva la syscall.

Passaggio 2: ingresso syscall

La VM SBF invia al gestore syscall sol_invoke_signed_rust, che chiama il punto di ingresso condiviso: cpi_common.

Passaggio 3: consumare il costo di invocazione

La prima azione all'interno di cpi_common è addebitare il costo fisso di invocazione dal contatore di calcolo condiviso: invoke_units = 1.000 CU (o 946 CU con SIMD-0339).

Passaggio 4: tradurre l'istruzione dalla memoria VM

Il gestore della syscall traduce l'istruzione dallo spazio di indirizzamento VM del programma ai tipi Rust lato host tramite translate_instruction_rust, che legge una struct StableInstruction, valida la lunghezza dei dati rispetto a MAX_INSTRUCTION_DATA_LEN (10.240 byte), quindi addebita il costo di serializzazione dei dati.

Passaggio 5: tradurre i seed del firmatario e derivare i PDA

Il gestore chiama translate_signers_rust. Per ogni set di seed del firmatario, il runtime:

  1. Controlla il numero di set di seed del firmatario rispetto a MAX_SIGNERS (16).
  2. Controlla la lunghezza di ogni set di seed rispetto a MAX_SEEDS (16 seed per set).
  3. Chiama Pubkey::create_program_address con i seed e l'ID del programma del chiamante. Se i seed non producono un PDA valido, il CPI fallisce con BadSeeds.
  4. Raccoglie le pubkey PDA risultanti in un vec signers.

Questi PDA derivati sono trattati come firmatari validi per l'istruzione del chiamato.

Passaggio 6: verificare il programma autorizzato

Prima di procedere, il runtime chiama check_authorized_program per verificare che il programma di destinazione sia consentito per il CPI. I seguenti programmi sono bloccati:

  • Il native loader
  • bpf_loader e bpf_loader_deprecated
  • bpf_loader_upgradeable (eccetto istruzioni di gestione specifiche: upgrade, set_authority, set_authority_checked (feature-gated), extend_program_checked (feature-gated), close)
  • Programmi precompilati (ed25519, secp256k1, ecc.)

La violazione restituisce ProgramNotSupported.

Passaggio 7: verifica dei privilegi (prepare_next_instruction)

Il runtime chiama prepare_next_instruction che costruisce l'elenco InstructionAccount del chiamato e applica le regole dei privilegi. Vedi Regole dei privilegi di seguito per la tabella decisionale completa.

Passaggio 8: tradurre le informazioni dell'account

L'handler chiama translate_accounts che:

  1. Convalida il conteggio delle informazioni dell'account rispetto a MAX_CPI_ACCOUNT_INFOS (128, o 255 con SIMD-0339).
  2. Addebita il costo di traduzione delle informazioni dell'account (solo SIMD-0339): (num_account_infos * 80) / 250 CU.
  3. Per ogni account non eseguibile e non duplicato, costruisce un CallerAccount traducendo i puntatori dalla memoria VM alla memoria host. Questo include l'addebito del costo di serializzazione dei dati per account: account_data_len / cpi_bytes_per_unit CU.

Passaggio 9: sincronizzazione dell'account pre-CPI (da chiamante a chiamato)

Prima di eseguire il chiamato, il runtime sincronizza le modifiche dell'account del chiamante in modo che il chiamato possa vederle. La funzione update_callee_account viene chiamata per ogni account tradotto, copiando lamport, dati e proprietario. Vedi Sincronizzazione dei dati dell'account per la mappatura dettagliata dei campi.

Passaggio 10: push del contesto dell'istruzione, esecuzione del chiamato e pop

Il runtime chiama process_instruction, che:

  1. Chiama push() per aggiungere un nuovo frame allo stack delle istruzioni. push() applica la regola di rientranza: un programma può chiamare se stesso solo se è il chiamante diretto (cioè, il programma A può chiamare A, ma A non può chiamare B che chiama A). La violazione restituisce ReentrancyNotAllowed.
  2. Chiama process_executable_chain che risolve il punto di ingresso del programma chiamato e lo invoca. Il chiamato viene eseguito con lo stesso contatore di compute condiviso. Tutto il consumo di CU da parte del chiamato riduce il budget rimanente del chiamante.
  3. Chiama pop() per rimuovere il frame del chiamato e verificare che i saldi dei lamport siano invariati (UnbalancedInstruction in caso contrario).

Passaggio 11: sincronizzazione dell'account post-CPI (da chiamato a chiamante)

Dopo che process_instruction ritorna (che include il pop), il runtime sincronizza le modifiche al chiamante tramite update_caller_account per ogni account scrivibile. Inoltre, update_caller_account_region aggiorna le mappature delle regioni di memoria VM per gli account le cui regioni di dati sono cambiate. Vedi Sincronizzazione dei dati dell'account per la mappatura dettagliata dei campi.

La syscall CPI restituisce 0 (successo) al programma chiamante.

Is this page helpful?

Gestito da

© 2026 Solana Foundation.
Tutti i diritti riservati.
Resta connesso