Модель стоимости CPI и синхронизация данных

Кратко

Каждый 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 datainstruction_data_len / cpi_bytes_per_unitcpi_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_unitACCOUNT_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 = 946
instruction_data_cost = 100 / 250 = 0 (integer division)
account_meta_cost = (5 * 34) / 250 = 0
account_info_cost = (5 * 80) / 250 = 1
per_account_data_cost = 5 * (1000 / 250) = 20
total (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:

  1. Массив seed для подписанта проверяется: максимум MAX_SIGNERS (16) наборов seed для подписанта.
  2. Каждый набор seed проверяется: максимум MAX_SEEDS (16) seed в наборе, каждый seed не более MAX_SEED_LEN (32) байт.
  3. Pubkey::create_program_address вызывается с seed и ID программы вызывающего (а не вызываемой).
  4. Если seed не дают валидный PDA (т.е. полученная точка лежит на кривой ed25519), CPI завершится с ошибкой BadSeeds.
  5. Полученные 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_cost CU.
  • sol_get_return_data: Читает возвращаемые данные, установленные последней выполненной инструкцией. Возвращает данные вместе с ID программы, которая их установила. Стоимость — (data_len + 32) / cpi_bytes_per_unit + syscall_base_cost CU (32 байта для ID программы).

Данные возврата сохраняются для каждой транзакции и перезаписываются каждой инструкцией, которая вызывает sol_set_return_data. В начале каждого вызова программы runtime сбрасывает данные возврата в пустое значение. После возврата CPI вызывающая сторона может прочитать те данные возврата, которые последними были установлены вызываемой программой (или любой программой, которую она вызвала).

Данные возврата ограничены 1 024 байтами. Только последняя программа, вызвавшая sol_set_return_data в цепочке вызовов, определяет, что увидит вызывающий. Если вызываемая программа делает дополнительные CPI, которые устанавливают данные возврата, её собственные данные возврата будут перезаписаны.

Is this page helpful?

Управляется

© 2026 Solana Foundation.
Все права защищены.
Связаться с нами