Резюме
Программы компилируются в sBPF через LLVM и выполняются в изолированной VM с бюджетом 1,4 млн CU на транзакцию. Среда выполнения кэширует до 512 скомпилированных программ, предоставляет системные вызовы для логирования, CPI, криптографии и работы с памятью, а новые развёртывания задерживаются на 1 slot.
Компиляция
Solana использует LLVM для компиляции программ в бинарные файлы ELF, содержащие Solana Bytecode Format (sBPF). ELF-бинарник хранится в сети в исполняемом аккаунте.
sBPF — это пользовательский вариант байткода eBPF от Solana, адаптированный для среды выполнения Solana. Это не стандартный eBPF — он содержит модификации, специфичные для Solana.
Написание программ
Программы Solana преимущественно пишутся на Rust с использованием одного из трёх подходов:
Anchor
Фреймворк, использующий макросы Rust для сокращения шаблонного кода. Рекомендуется большинству разработчиков.
Pinocchio
Лёгкая библиотека Rust с нулевым копированием, оптимизированная для снижения потребления вычислительных ресурсов и размера бинарного файла. Включает специализированные крейты для распространённых CPI.
Native Rust
Чистый Rust без фреймворков. Обеспечивает полный контроль, но требует более детальной ручной реализации.
См. Cross-Program Invocations для ознакомления с концепциями и примерами CPI, применимыми во всех этих подходах.
Модель выполнения программ
При обработке транзакции среда выполнения последовательно выполняет каждую инструкцию
через
process_message().
Для каждой инструкции среда выполнения:
-
Подготавливает контекст инструкции. Вызывает
prepare_next_top_level_instruction()для сопоставления индексов аккаунтов инструкции, установки флагов подписанта и записи, а также настройкиTransactionContext. -
Проверяет прекомпиляции. Если программа является прекомпиляцией, среда выполнения вызывает
process_precompile(), которая по-прежнему помещает и извлекает кадр стека (черезpush()иpop()), но обходит sBPF VM и поиск в кэше программ, выполняя нативный код напрямую. -
Помещает кадр в стек. (Шаги 3–6 выполняются внутри
InvokeContext::process_instruction()иprocess_executable_chain(), вызываемых изprocess_message().) Вызываетpush()наInvokeContext, что увеличивает высоту стека инструкций и применяет правило реентерабельности: программа может повторно войти в себя только в том случае, если непосредственный вызывающий (программа на текущей вершине стека инструкций) является той же программой. Глубокая саморекурсия (A -> A -> A) допускается с учётом ограничений глубины стека. Другие паттерны реентерабельности (например, A вызывает B, B вызывает A) возвращаютInstructionError::ReentrancyNotAllowed. -
Разрешает программу. Среда выполнения вызывает
process_executable_chain(), которая определяет загрузчик. Если владельцем program account является нативный загрузчик, программа является встроенной, и её точка входа извлекается напрямую изProgramCacheForTxBatch. Если владелец — один из BPF-загрузчиков (bpf_loader_deprecated,bpf_loader,bpf_loader_upgradeableилиloader_v4), вместо этого вызывается собственная встроенная точка входа загрузчика. -
Выполняет BPF-программу. Для BPF-программ точка входа загрузчика извлекает скомпилированный исполняемый файл из кэша программ. Функция
execute()затем:- Сериализует данные аккаунтов в плоский буфер параметров
- Создаёт sBPF VM со стеком, кучей и регионами памяти
- Выполняет скомпилированный код, расходуя вычислительные единицы в процессе выполнения.
Возвращает
ComputationalBudgetExceeded, если бюджет превышен. - Десериализует данные аккаунтов из буфера обратно в состояние аккаунтов
-
Извлекает кадр из стека. Вызывает
pop(), которая проверяет, что инструкция не нарушила правила учёта среды выполнения (балансы lamport сбалансированы, аккаунты только для чтения не изменялись, размеры данных аккаунтов находятся в допустимых пределах). -
Накапливает вычислительные единицы. Вычислительные единицы, потреблённые инструкцией, добавляются к итогу транзакции через
saturating_add.
Кэш программ
Среда выполнения поддерживает глобальный
ProgramCache,
хранящий верифицированные и скомпилированные программы. Он учитывает граф форков и
обрабатывает правила видимости при развёртывании, вытеснение и перекомпиляцию
на границах epoch.
Типы записей кэша
Каждая кэшированная программа имеет
ProgramCacheEntryType,
определяющий её поведение в среде выполнения:
| Тип | Описание |
|---|---|
Loaded | Верифицированная и скомпилированная программа, готовая к выполнению. |
Builtin | Нативная программа, скомпилированная в бинарник validator (System, Stake, Vote и др.). Не хранится в сети. |
Unloaded | Ранее верифицированная программа, скомпилированный исполняемый файл которой был вытеснен из памяти для освобождения места. По-прежнему отслеживает статистику использования. Может быть перезагружена без повторной верификации. |
FailedVerification | Метка-захоронение для программ, не прошедших верификатор sBPF при текущем наборе функций. Может стать Loaded, если активация функций изменит правила верификации. |
Closed | Метка-захоронение для программ, которые были явно закрыты или так и не были развёрнуты. Также используется для аккаунтов (например, буферных аккаунтов), принадлежащих загрузчику, но не содержащих исполняемый код. |
DelayVisibility | Синтетическая метка-захоронение, возвращаемая ProgramCacheForTxBatch::find(), когда запись Loaded существует, но ещё не вступила в силу (её effective_slot находится в будущем). Никогда не хранится напрямую в кэше. |
Задержка видимости
Вновь развёрнутые или обновлённые программы не вступают в силу немедленно. Константа
DELAY_VISIBILITY_SLOT_OFFSET
равна 1, то есть программа, развёрнутая в slot N, вступает в силу в slot
N+1. В течение slot развёртывания любая попытка вызвать новую версию возвращает
DelayVisibility, и среда выполнения сообщает «Program is not deployed.»
Политика вытеснения
Кэш содержит не более
MAX_LOADED_ENTRY_COUNT
(512) записей скомпилированных программ. При достижении лимита наименее используемые
программы вытесняются в состояние Unloaded. Использование отслеживается с помощью
tx_usage_counter
(увеличивается каждый раз, когда транзакция обращается к программе) и
latest_access_slot.
Перекомпиляция на границе epoch
Если активация функции изменяет
ProgramRuntimeEnvironments
на границе epoch, все кэшированные программы
перекомпилируются
под новую среду.
Возвращаемые данные
Программы могут задавать возвращаемые данные через системный вызов sol_set_return_data. Данные
хранятся в структуре уровня транзакции
TransactionReturnData,
содержащей байты данных и program_id программы, чья инструкция вызвала системный вызов.
Максимальный размер составляет 1 024 байта
(MAX_RETURN_DATA).
Is this page helpful?