Виконання CPI та привілеї

Підсумок

CPI проходять через 11 кроків середовища виконання, включаючи перевірку привілеїв, трансляцію акаунтів та синхронізацію даних. Максимальна глибина викликів: 5 (9 з SIMD-0268). Правила привілеїв запобігають підвищенню прав викликаної програми понад те, що надав викликач.

Правила привілеїв

CPI розширюють привілеї акаунтів викликача на викликану програму з суворим контролем. Середовище виконання перевіряє ці правила в prepare_next_instruction:

СценарійДозволено?Точка контролюПомилка
Викликач передає акаунт як доступний для запису, викликана програма позначає як доступний для записуТак----
Викликач передає акаунт як тільки для читання, викликана програма позначає як доступний для записуНіprepare_next_instructionPrivilegeEscalation
Викликач передає акаунт як доступний для запису, викликана програма позначає як тільки для читанняТак----
Викликач передає акаунт як підписувач, викликана програма позначає як підписувачТак----
Викликач передає акаунт як не-підписувач, викликана програма позначає як підписувач, акаунт є PDA, отриманим із seed'ів викликачаТакprepare_next_instruction--
Викликач передає акаунт як не-підписувач, викликана програма позначає як підписувач, акаунт НЕ є PDA від викликачаНіprepare_next_instructionPrivilegeEscalation
Викликач передає акаунт як підписувач, викликана програма позначає як не-підписувачТак----
Програма A викликає саму себе безпосередньо (A -> A)Такpush()--
Програма A викликає B, яка викликає A (непрямий повторний вхід)Ніpush()ReentrancyNotAllowed
CPI до native loader, bpf_loader, bpf_loader_deprecated або precompileНіcheck_authorized_programProgramNotSupported
Акаунт не знайдено в транзакціїНіprepare_next_instructionMissingAccount

Правила привілеїв можна узагальнити так:

  1. Привілей запису не може бути підвищений. Якщо викликач позначає обліковий запис як доступний лише для читання, викликаний не може позначити його як доступний для запису.
  2. Привілей підписанта вимагає авторизації. Обліковий запис може бути підписантом у викликаному лише якщо (а) він уже був підписантом у викликачі, АБО (б) це PDA, отриманий із seeds викликаючої програми через invoke_signed.
  3. Зменшення привілеїв завжди дозволено. Викликаний може використовувати менше привілеїв, ніж надав викликач.

Потік виконання CPI

CPI проходить через кілька рівнів середовища виконання. Цей розділ документує повний конвеєр від виклику SDK програми через межу syscall до середовища виконання і назад. Кожен крок посилається на файл вихідного коду, який його реалізує.

Максимальна висота виклику інструкції програми називається max_instruction_stack_depth і встановлена на константу MAX_INSTRUCTION_STACK_DEPTH зі значенням 5. З активним MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268 це значення збільшується до 9.

Висота стека 1 — це початкова інструкція транзакції. Кожен CPI збільшує висоту на 1. Максимум 5 означає, що програма може виконувати CPI на глибину до 4 рівнів (8 рівнів з SIMD-0268).

Крок 1: програма викликає invoke або invoke_signed

Програма викликає invoke або invoke_signed. invoke — це тонка обгортка, яка викликає invoke_signed з порожнім масивом seeds підписанта. Функція SDK серіалізує Instruction, зріз AccountInfo і seeds підписанта в пам'ять VM, а потім ініціює syscall.

Крок 2: вхід у syscall

Віртуальна машина SBF передає керування обробнику syscall sol_invoke_signed_rust, який викликає спільну точку входу: cpi_common.

Крок 3: Споживання вартості виклику

Перша дія всередині cpi_common полягає в стягненні фіксованої вартості виклику з загального лічильника обчислень: invoke_units = 1 000 CU (або 946 CU з SIMD-0339).

Крок 4: Трансляція інструкції з пам'яті VM

Обробник системного виклику транслює інструкцію з адресного простору VM програми в типи Rust на стороні хоста через translate_instruction_rust, який зчитує структуру StableInstruction, перевіряє довжину даних відповідно до MAX_INSTRUCTION_DATA_LEN (10 240 байтів), а потім стягує вартість серіалізації даних.

Крок 5: Трансляція seed-ів підписувача та виведення PDA

Обробник викликає translate_signers_rust. Для кожного набору seed-ів підписувача середовище виконання:

  1. Перевіряє кількість наборів seed-ів підписувача відповідно до MAX_SIGNERS (16).
  2. Перевіряє довжину кожного набору seed-ів відповідно до MAX_SEEDS (16 seed-ів на набір).
  3. Викликає Pubkey::create_program_address з seed-ами та ID програми викликача. Якщо seed-и не створюють дійсний PDA, CPI завершується з помилкою BadSeeds.
  4. Збирає отримані публічні ключі PDA у вектор signers.

Ці виведені PDA розглядаються як дійсні підписувачі для інструкції викликуваної програми.

Крок 6: Перевірка авторизованої програми

Перед продовженням середовище виконання викликає check_authorized_program для перевірки, чи цільова програма дозволена для CPI. Наступні програми заблоковані:

  • Нативний завантажувач
  • bpf_loader та bpf_loader_deprecated
  • bpf_loader_upgradeable (за винятком конкретних інструкцій управління: upgrade, set_authority, set_authority_checked (з feature-gate), extend_program_checked (з feature-gate), close)
  • Програми попередньої компіляції (ed25519, secp256k1 тощо)

Порушення повертає ProgramNotSupported.

Крок 7: Перевірка привілеїв (prepare_next_instruction)

Середовище виконання викликає prepare_next_instruction, який будує список InstructionAccount викликуваної програми та застосовує правила привілеїв. Див. Правила привілеїв нижче для повної таблиці рішень.

Крок 8: Трансляція інформації про облікові записи

Обробник викликає translate_accounts, який:

  1. Перевіряє кількість інформації про облікові записи відносно MAX_CPI_ACCOUNT_INFOS (128 або 255 з SIMD-0339).
  2. Стягує вартість трансляції інформації про облікові записи (тільки SIMD-0339): (num_account_infos * 80) / 250 обчислювальних одиниць.
  3. Для кожного невиконуваного, недубльованого облікового запису створює CallerAccount, транслюючи вказівники з пам'яті віртуальної машини в пам'ять хоста. Це включає стягнення вартості серіалізації даних для кожного облікового запису: account_data_len / cpi_bytes_per_unit обчислювальних одиниць.

Крок 9: Синхронізація облікових записів перед CPI (від викликача до викликаного)

Перед виконанням викликаного середовище виконання синхронізує модифікації облікових записів викликача, щоб викликаний міг їх побачити. Функція update_callee_account викликається для кожного транслюваного облікового запису, копіюючи lamports, дані та власника. Дивіться Синхронізація даних облікових записів для детального відображення полів.

Крок 10: Додавання контексту інструкції, виконання викликаного та видалення

Середовище виконання викликає process_instruction, яке:

  1. Викликає push() для додавання нового фрейму до стеку інструкцій. push() застосовує правило реентерабельності: програма може викликати саму себе лише якщо вона є безпосереднім викликачем (тобто програма A може викликати A, але A не може викликати B, яка викликає A). Порушення повертає ReentrancyNotAllowed.
  2. Викликає process_executable_chain, яка визначає точку входу програми викликаного та викликає її. Викликаний працює з тим самим спільним лічильником обчислень. Усе споживання обчислювальних одиниць викликаним зменшує залишковий бюджет викликача.
  3. Викликає pop() для видалення фрейму викликаного та перевірки незмінності балансів lamport (UnbalancedInstruction у разі порушення).

Крок 11: Синхронізація облікових записів після CPI (від викликаного до викликача)

Після повернення process_instruction (що включає видалення), середовище виконання синхронізує зміни назад до викликача через update_caller_account для кожного записуваного облікового запису. Додатково, update_caller_account_region оновлює відображення регіонів пам'яті віртуальної машини для облікових записів, чиї регіони даних змінилися. Дивіться Синхронізація даних облікових записів для детального відображення полів.

Системний виклик CPI повертає 0 (успіх) програмі-викликачу.

Is this page helpful?

Керується

© 2026 Фонд Solana.
Всі права захищені.
Залишайтеся на зв'язку