Кратко
Каждый CPI стоит примерно 1 000 CU базово плюс затраты на сериализацию. Данные аккаунтов синхронизируются до и после выполнения вызываемой программы. Подпись PDA использует program ID вызывающей программы. Возвращаемые данные ограничены 1 024 байтами.
Модель стоимости CPI
Затраты на CPI списываются из общего вычислительного бюджета транзакции (общий
счетчик). Полная формула стоимости для каждого invoke /
invoke_signed вызова:
total_cpi_cost = invocation_cost+ instruction_data_cost+ account_meta_cost (SIMD-0339 only)+ account_info_cost (SIMD-0339 only)+ per_account_data_cost (for each non-executable account)+ callee_execution_cost
Детализация затрат
| Компонент затрат | Формула | Источник |
|---|---|---|
| Вызов (фиксированная часть) | invoke_units = 1 000 CU (946 с SIMD-0339) | Списывается при входе в CPI |
| Сериализация instruction data | instruction_data_len / cpi_bytes_per_unit | cpi_bytes_per_unit = 250. Списывается в translate_instruction. |
| Сериализация account meta (SIMD-0339) | (num_account_metas * 34) / cpi_bytes_per_unit | Каждый AccountMeta — 34 байта (32 pubkey + 1 is_signer + 1 is_writable). Списывается в translate_instruction. |
| Трансляция account info (SIMD-0339) | (num_account_infos * 80) / cpi_bytes_per_unit | ACCOUNT_INFO_BYTE_SIZE = 80 байт (32 key + 32 owner + 8 lamports + 8 data_len). Списывается в translate_account_infos. |
| На каждый аккаунт | account_data_len / cpi_bytes_per_unit | Списывается за каждый аккаунт в CallerAccount::from_account_info и для исполняемых аккаунтов. |
| Выполнение вызываемой программы | Любое количество CU, потреблённое вызываемой программой | Списывается с общего счетчика во время выполнения вызываемой программы. |
Пример расчёта стоимости
CPI с 100 байтами instruction data, 5 account meta, 5 account info (каждый с 1 000 байт данных), SIMD-0339 активен:
invocation_cost = 946instruction_data_cost = 100 / 250 = 0 (integer division)account_meta_cost = (5 * 34) / 250 = 0account_info_cost = (5 * 80) / 250 = 1per_account_data_cost = 5 * (1000 / 250) = 20total (before callee) = 967 CUs
Синхронизация данных аккаунта
Состояние аккаунта синхронизируется между вызывающей и вызываемой стороной в двух точках во время CPI. Это обеспечивает обеим сторонам согласованный доступ к данным аккаунта.
Пред-CPI синхронизация (от вызывающего к вызываемому)
Перед выполнением вызываемой стороны,
update_callee_account
копирует текущие изменения вызывающего в представление аккаунта вызываемого:
| Поле | Направление | Когда |
|---|---|---|
| Lamports | Вызывающий -> Вызываемый | Если значение отличается от текущего у вызываемого |
| Data length | Вызывающий -> Вызываемый | Если вызывающий изменил размер аккаунта. Не должно превышать original_data_len + MAX_PERMITTED_DATA_INCREASE (10 КиБ). |
| Data content | Вызывающий -> Вызываемый | Если данные аккаунта можно изменять (can_data_be_changed проходит) |
| Owner | Вызывающий -> Вызываемый | Устанавливается последним, чтобы изменения данных/лампортов были разрешены под старым владельцем |
Пост-CPI синхронизация (от вызываемого к вызывающему)
После возврата вызываемой стороны,
update_caller_account
копирует изменения вызываемого обратно в представление вызывающего. Это
выполняется только для аккаунтов, отмеченных как доступные для записи:
| Поле | Направление | Когда |
|---|---|---|
| Lamports | Вызываемый -> Вызывающий | Всегда копируется обратно |
| Owner | Вызываемый -> Вызывающий | Всегда копируется обратно |
| Data length | Вызываемый -> Вызывающий | Если изменено. Указатель на срез данных VM и сериализованное поле длины обновляются. Если аккаунт был уменьшен, освобождённая память обнуляется. Если новая длина превышает original_data_len + MAX_PERMITTED_DATA_INCREASE, возвращается InvalidRealloc. |
| Data content | Вызываемый -> Вызывающий | Копируется из буфера данных вызываемого в сериализованный регион данных вызывающего |
Ограничения на перераспределение памяти
Данные аккаунта могут быть изменены по размеру во время CPI до
MAX_PERMITTED_DATA_INCREASE
= 10 240 байт (10 КиБ) сверх длины данных аккаунта на начало текущей инструкции
верхнего уровня. Это ограничение применяется как в
update_callee_account
(до CPI), так и в
update_caller_account
(после CPI). Превышение этого лимита приводит к возврату InvalidRealloc.
Подпись PDA
Когда вызывается invoke_signed, рантайм вычисляет адреса PDA из
предоставленных seed и ID программы вызывающего. Это происходит в
translate_signers_rust:
- Массив seed для подписанта проверяется: максимум
MAX_SIGNERS(16) наборов seed для подписанта. - Каждый набор seed проверяется: максимум
MAX_SEEDS(16) seed в наборе, каждый seed не болееMAX_SEED_LEN(32) байт. Pubkey::create_program_addressвызывается с seed и ID программы вызывающего (а не вызываемой).- Если seed не дают валидный PDA (т.е. полученная точка лежит на кривой
ed25519), CPI завершится с ошибкой
BadSeeds. - Полученные PDA pubkey собираются и передаются в
prepare_next_instructionкак валидные подписанты. При проверке привилегий, если аккаунт вызываемого помечен как подписант и его pubkey совпадает с одним из этих полученных PDA, проверка подписи проходит.
PDA вычисляется с использованием ID программы вызывающего, а не вызываемой. Это означает, что только программа, владеющая PDA (та, чей ID использовался для вычисления), может подписывать за него. Программа не может подписывать за PDA, полученные от других программ.
Возврат данных
Программы могут возвращать данные вызывающему с помощью механизма возврата данных. Для этого используются два системных вызова:
sol_set_return_data: Устанавливает доMAX_RETURN_DATA(1 024) байт возвращаемых данных для текущей инструкции. Стоимость —data_len / cpi_bytes_per_unit + syscall_base_costCU.sol_get_return_data: Читает возвращаемые данные, установленные последней выполненной инструкцией. Возвращает данные вместе с ID программы, которая их установила. Стоимость —(data_len + 32) / cpi_bytes_per_unit + syscall_base_costCU (32 байта для ID программы).
Данные возврата сохраняются для каждой транзакции и перезаписываются каждой
инструкцией, которая вызывает sol_set_return_data. В начале каждого вызова
программы runtime
сбрасывает данные возврата
в пустое значение. После возврата CPI вызывающая сторона может прочитать те
данные возврата, которые последними были установлены вызываемой программой (или
любой программой, которую она вызвала).
Данные возврата ограничены 1 024 байтами. Только последняя программа,
вызвавшая sol_set_return_data в цепочке вызовов, определяет, что увидит
вызывающий. Если вызываемая программа делает дополнительные CPI, которые
устанавливают данные возврата, её собственные данные возврата будут
перезаписаны.
Is this page helpful?