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:
| Scenario | Consentito? | Punto di applicazione | Errore |
|---|---|---|---|
| Il chiamante passa l'account come scrivibile, il chiamato lo contrassegna come scrivibile | Sì | -- | -- |
| Il chiamante passa l'account come sola lettura, il chiamato lo contrassegna come scrivibile | No | prepare_next_instruction | PrivilegeEscalation |
| Il chiamante passa l'account come scrivibile, il chiamato lo contrassegna come sola lettura | Sì | -- | -- |
| Il chiamante passa l'account come firmatario, il chiamato lo contrassegna come firmatario | Sì | -- | -- |
| Il chiamante passa l'account come non firmatario, il chiamato lo contrassegna come firmatario, l'account è un PDA derivato dai seed del chiamante | Sì | prepare_next_instruction | -- |
| Il chiamante passa l'account come non firmatario, il chiamato lo contrassegna come firmatario, l'account NON è un PDA del chiamante | No | prepare_next_instruction | PrivilegeEscalation |
| Il chiamante passa l'account come firmatario, il chiamato lo contrassegna come non firmatario | Sì | -- | -- |
| Il programma A chiama se stesso direttamente (A -> A) | Sì | push() | -- |
| Il programma A chiama B che chiama A (rientranza indiretta) | No | push() | ReentrancyNotAllowed |
| CPI a native loader, bpf_loader, bpf_loader_deprecated o precompile | No | check_authorized_program | ProgramNotSupported |
| Account non trovato nella transazione | No | prepare_next_instruction | MissingAccount |
Le regole dei privilegi possono essere riassunte come:
- 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.
- 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. - 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:
- Controlla il numero di set di seed del firmatario rispetto a
MAX_SIGNERS(16). - Controlla la lunghezza di ogni set di seed rispetto a
MAX_SEEDS(16 seed per set). - Chiama
Pubkey::create_program_addresscon i seed e l'ID del programma del chiamante. Se i seed non producono un PDA valido, il CPI fallisce conBadSeeds. - 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_loaderebpf_loader_deprecatedbpf_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:
- Convalida il conteggio delle informazioni dell'account
rispetto a
MAX_CPI_ACCOUNT_INFOS(128, o 255 con SIMD-0339). - Addebita il costo di traduzione delle informazioni dell'account
(solo SIMD-0339):
(num_account_infos * 80) / 250CU. - Per ogni account non eseguibile e non duplicato, costruisce un
CallerAccounttraducendo 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_unitCU.
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:
- 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 restituisceReentrancyNotAllowed. - Chiama
process_executable_chainche 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. - Chiama
pop()per rimuovere il frame del chiamato e verificare che i saldi dei lamport siano invariati (UnbalancedInstructionin 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?