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