CPI-uitvoering en privileges

Samenvatting

CPI's doorlopen 11 runtime-stappen, waaronder privilege-controle, account-vertaling en data-synchronisatie. Maximale aanroepdiepte: 5 (9 met SIMD-0268). Privilege-regels voorkomen dat de aangeroepene verder escaleert dan wat de aanroeper heeft toegestaan.

Privilege-regels

CPI's breiden de account-privileges van de aanroeper uit naar de aangeroepene met strikte handhaving. De runtime controleert deze regels in prepare_next_instruction:

ScenarioToegestaan?HandhavingspuntFout
Aanroeper geeft account door als schrijfbaar, aangeroepene markeert als schrijfbaarJa----
Aanroeper geeft account door als alleen-lezen, aangeroepene markeert als schrijfbaarNeeprepare_next_instructionPrivilegeEscalation
Aanroeper geeft account door als schrijfbaar, aangeroepene markeert als alleen-lezenJa----
Aanroeper geeft account door als ondertekenaar, aangeroepene markeert als ondertekenaarJa----
Aanroeper geeft account door als niet-ondertekenaar, aangeroepene markeert als ondertekenaar, account is een PDA afgeleid van seeds van aanroeperJaprepare_next_instruction--
Aanroeper geeft account door als niet-ondertekenaar, aangeroepene markeert als ondertekenaar, account is GEEN PDA van aanroeperNeeprepare_next_instructionPrivilegeEscalation
Aanroeper geeft account door als ondertekenaar, aangeroepene markeert als niet-ondertekenaarJa----
Programma A roept zichzelf direct aan (A -> A)Japush()--
Programma A roept B aan, dat A aanroept (indirecte reentrancy)Neepush()ReentrancyNotAllowed
CPI naar native loader, bpf_loader, bpf_loader_deprecated of precompileNeecheck_authorized_programProgramNotSupported
Account niet gevonden in transactieNeeprepare_next_instructionMissingAccount

De privilege-regels kunnen als volgt worden samengevat:

  1. Schrijfbaar privilege kan niet escaleren. Als de aanroeper een account als alleen-lezen markeert, kan de aangeroepene het niet als schrijfbaar markeren.
  2. Ondertekenaar privilege vereist autorisatie. Een account kan alleen een ondertekenaar zijn in de aangeroepene als (a) het al een ondertekenaar was in de aanroeper, OF (b) het een PDA is afgeleid van de seeds van het aanroepende programma via invoke_signed.
  3. Privilege-vermindering is altijd toegestaan. De aangeroepene mag minder privileges gebruiken dan de aanroeper heeft verleend.

CPI uitvoeringsflow

Een CPI passeert verschillende runtime-lagen. Deze sectie documenteert de volledige pipeline vanaf de programma SDK-aanroep door de syscall-grens naar de runtime en terug. Elke stap verwijst naar het bronbestand dat het implementeert.

De maximale hoogte van de programma-instructie-aanroep wordt de max_instruction_stack_depth genoemd en is ingesteld op de MAX_INSTRUCTION_STACK_DEPTH constante van 5. Met MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268 actief, neemt dit toe tot 9.

Stack-hoogte 1 is de initiële transactie-instructie. Elke CPI verhoogt de hoogte met 1. Een maximum van 5 betekent dat een programma CPI's tot 4 niveaus diep kan maken (8 niveaus diep met SIMD-0268).

Stap 1: Programma roept invoke of invoke_signed aan

Het programma roept invoke of invoke_signed aan. invoke is een dunne wrapper die invoke_signed aanroept met een lege ondertekenaar seeds-array. De SDK-functie serialiseert de Instruction, AccountInfo slice, en ondertekenaar seeds naar VM-geheugen, en triggert vervolgens de syscall.

Stap 2: Syscall-entry

De SBF VM dispatcht naar de sol_invoke_signed_rust syscall-handler, die aanroept naar het gedeelde entry point: cpi_common.

Stap 3: Invocatiekosten verbruiken

De eerste actie binnen cpi_common is om de vaste invocatiekosten in rekening te brengen van de gedeelde compute meter: invoke_units = 1.000 CU's (of 946 CU's met SIMD-0339).

Stap 4: Instructie vertalen vanuit VM-geheugen

De syscall handler vertaalt de instructie vanuit de adresruimte van de VM van het programma naar host-side Rust types via translate_instruction_rust, die een StableInstruction struct leest, de datalengte valideert tegen MAX_INSTRUCTION_DATA_LEN (10.240 bytes), en vervolgens de kosten voor dataserialisatie in rekening brengt.

Stap 5: Signer seeds vertalen en PDA's afleiden

De handler roept translate_signers_rust aan. Voor elke set signer seeds doet de runtime het volgende:

  1. Controleert het aantal signer seed sets tegen MAX_SIGNERS (16).
  2. Controleert de lengte van elke seed set tegen MAX_SEEDS (16 seeds per set).
  3. Roept Pubkey::create_program_address aan met de seeds en de programma-ID van de aanroeper. Als de seeds geen geldige PDA opleveren, mislukt de CPI met BadSeeds.
  4. Verzamelt de resulterende PDA pubkeys in een signers vec.

Deze afgeleide PDA's worden behandeld als geldige signers voor de callee instructie.

Stap 6: Geautoriseerd programma controleren

Voordat wordt doorgegaan, roept de runtime check_authorized_program aan om te verifiëren dat het doelprogramma is toegestaan voor CPI. De volgende programma's zijn geblokkeerd:

  • De native loader
  • bpf_loader en bpf_loader_deprecated
  • bpf_loader_upgradeable (behalve specifieke beheerinstructies: upgrade, set_authority, set_authority_checked (feature-gated), extend_program_checked (feature-gated), close)
  • Precompile programma's (ed25519, secp256k1, etc.)

Overtreding retourneert ProgramNotSupported.

Stap 7: Privilege verificatie (prepare_next_instruction)

De runtime roept prepare_next_instruction aan die de InstructionAccount lijst van de callee opbouwt en privilege regels afdwingt. Zie Privilege regels hieronder voor de volledige beslissingstabel.

Stap 8: Vertaal accountinformatie

De handler roept translate_accounts aan die:

  1. Valideert het aantal accountinformatie tegen MAX_CPI_ACCOUNT_INFOS (128, of 255 met SIMD-0339).
  2. Brengt kosten voor accountinformatievertaling in rekening (alleen SIMD-0339): (num_account_infos * 80) / 250 CU's.
  3. Voor elk niet-uitvoerbaar, niet-duplicaat account, bouwt een CallerAccount door pointers te vertalen van VM-geheugen naar hostgeheugen. Dit omvat het in rekening brengen van dataserialisatiekosten per account: account_data_len / cpi_bytes_per_unit CU's.

Stap 9: Pre-CPI accountsynchronisatie (aanroeper naar aangeroepene)

Voordat de aangeroepene wordt uitgevoerd, synchroniseert de runtime de accountwijzigingen van de aanroeper zodat de aangeroepene deze kan zien. De functie update_callee_account wordt aangeroepen voor elk vertaald account, waarbij lamports, data en eigenaar worden gekopieerd. Zie Accountdatasynchronisatie voor de gedetailleerde veldtoewijzing.

Stap 10: Push instructiecontext, voer aangeroepene uit en pop

De runtime roept process_instruction aan, die:

  1. Roept push() aan om een nieuw frame toe te voegen aan de instructiestack. push() handhaaft de reentrancy-regel: een programma mag zichzelf alleen aanroepen als het de directe aanroeper is (d.w.z. programma A kan A aanroepen, maar A kan niet B aanroepen die A aanroept). Overtreding retourneert ReentrancyNotAllowed.
  2. Roept process_executable_chain aan die het toegangspunt van het aangeroepen programma oplost en aanroept. De aangeroepene draait met dezelfde gedeelde compute meter. Al het CU-verbruik door de aangeroepene vermindert het resterende budget van de aanroeper.
  3. Roept pop() aan om het frame van de aangeroepene te verwijderen en te verifiëren dat lamport-saldi ongewijzigd zijn (UnbalancedInstruction indien niet).

Stap 11: Post-CPI accountsynchronisatie (aangeroepene naar aanroeper)

Nadat process_instruction terugkeert (wat de pop omvat), synchroniseert de runtime wijzigingen terug naar de aanroeper via update_caller_account voor elk schrijfbaar account. Daarnaast werkt update_caller_account_region VM-geheugenregiotoewijzingen bij voor accounts waarvan de dataregio's zijn gewijzigd. Zie Accountdatasynchronisatie voor de gedetailleerde veldtoewijzing.

De CPI syscall retourneert 0 (succes) naar het aanroepende programma.

Is this page helpful?

Beheerd door

© 2026 Solana Foundation.
Alle rechten voorbehouden.
Blijf Verbonden