SVM (Solana Virtual Machine): что это и как работает — BPF, CPI и отличие от EVM

SVM — это исполняющая среда Solana для смарт-контрактов (программ), спроектированная так, чтобы распараллеливать транзакции по данным. В отличие от сетей с одной глобальной очередью, где конфликт выявляется «на лету», транзакции в SVM заранее объявляют какими аккаунтами (участками состояния) они будут пользоваться и какие права им нужны (read/write, подпись). Благодаря этому рантайм и планировщик могут заранее разложить работу по независимым «островкам данных» и исполнять вызовы одновременно. Общий обзор протокола см. Архитектура Solana: Объяснение Высокой Производительности, PoH, Sealevel, детали параллелизма — Sealevel (Solana): параллельное исполнение, рантайм и планировщик, устройство хранения — Solana: модель аккаунтов — PDAs, read/write-локи, rent-exempt, ALTs.

SVM (Solana Virtual Machine)

Зачем SVM нужна Solana

  • Параллелизм по данным. Если две операции не затрагивают одни и те же аккаунты на запись, они могут идти одновременно.
  • Предсказуемость исполнения. Зависимости видны до запуска — планировщик собирает партии из неконфликтующих транзакций.
  • Композиция программ. Контракты в Solana строятся из «программ» и «аккаунтов», что упрощает модульность, миграции и аудит.

Базовые сущности

Программа (program) — развёрнутый исполняемый код. Программа неизменяема (код размещается однажды), вызывается инструкциями транзакций или через CPI.

Аккаунт (account) — контейнер данных/ресурс. В вызове для каждого аккаунта заранее указываются флаги: *is_signer*, *is_writable* и порядок.

Инструкция (instruction) — вызов функции программы с явным списком account metas (какие аккаунты и как используются). Транзакция может включать несколько инструкций.

Compute Units (CU) — квота на вычисления для транзакции. Помогает держать предсказуемый расход ресурсов и ограничивать «тяжёлые» пути.

CPI (Cross-Program Invocation) — вызов одной программы из другой в рамках той же транзакции.

Жизненный цикл транзакции в SVM (упрощённо)

  1. Клиент формирует транзакцию из одной или нескольких инструкций и перечисляет все аккаунты, которые могут понадобиться даже во внутренних вызовах (CPI).
  2. Узел-лидер получает поток транзакций, строит для каждой множество аккаунтов и права доступа, группирует в партии без write-конфликтов.
  3. Партия отправляется на параллельное исполнение. Завершившиеся транзакции освобождают «замки» на аккаунтах и дают дорогу следующей волне.
  4. Если в процессе CPI выясняется, что нужен аккаунт или режим доступа, не объявленный заранее, выполнение прерывается — конфликт скрыть нельзя.

Чем SVM отличается от классической EVM-модели

  • Хранилище. В SVM нет «глобального storage»; данные лежат в аккаунтах. Контракты получают ссылки на аккаунты, а не произвольный доступ по адресу.
  • Планирование. Конфликты известны заранее (по account metas), поэтому возможно параллельное исполнение независимых вызовов.
  • Комиссии и приоритет. Конкуренция формируется локально вокруг «горячих» аккаунтов/рынков, а не в одном глобальном аукционе газа.
  • Безопасность интерфейсов. CPI не может «тайно» расширить набор прав/ресурсов — поверхность транзакции обязана перечислить всё заранее.
  • Эвристики оптимизации. За счёт явных зависимостей проще профилировать узкие места и распараллеливать «горячие» потоки.

Модель аккаунтов: почему это основа параллелизма

Аккаунт — это не «баланс», а единица состояния. Любая запись делает аккаунт «горячим» для текущего контекста.

  • Чтение-чтение совместимо. Несколько транзакций могут параллельно читать один аккаунт.
  • Запись конфликтует с чтением/записью. Если два вызова хотят писать в один аккаунт, они будут сериализованы.
  • Шардирование по ключам. Продуманная адресация (например, PDA на основе публичного ключа пользователя или идентификатора рынка) позволяет естественно «рассыпать» состояние так, чтобы операции затрагивали разные аккаунты.

Подробнее см. Solana: модель аккаунтов — PDAs, read/write-локи, rent-exempt, ALTs.

CPI и детерминизм зависимостей

CPI делает возможной композицию (AMM вызывает токен-программу, ордербук — клиринг и т. п.), но накладывает правило: все аккаунты и права должны быть известны заранее на поверхности транзакции. Это дисциплинирует API программ — вызывающая сторона не может неожиданно обратиться к «левому» аккаунту, которого нет в списке.

Практика:

  1. документируйте для каждой инструкции минимальный/достаточный набор аккаунтов;
  2. не «подтягивайте» ресурсы динамически — вместо этого заставляйте клиента явно их объявлять;
  3. валидируйте права (read/write, signer) в самом начале.

Compute Units (CU) и границы сложности

Лимит CU защищает узлы от «пылесосов CPU» и стимулирует эффективный код. Общие приёмы:

  • разбивайте тяжёлые пути на несколько инструкций;
  • кешируйте сериализацию/десериализацию, экономьте на ненужных копиях;
  • удаляйте неиспользуемые поля/проверки в «горячем» пути;
  • используйте профилирование и лимиты CU на клиенте для предсказуемого UX.

Паттерны проектирования dApp под SVM

  • Шардируйте состояние. Уходите от «единого бухгалтерского аккаунта» ко множеству «мелких» по пользователям/рынкам/ключам.
  • Разделяйте «горячее» и «холодное». Константы/метаданные держите отдельно и не трогайте на запись при каждом вызове.
  • Делайте «узкие» инструкции. Лучше несколько маленьких, чем одна «всеядная» — планировщик лучше утилизирует параллелизм.
  • Минимизируйте фан-аут CPI. Глубокие деревья вызовов повышают расход CU и риск конфликтов.
  • Декларативные права. Если почти всегда нужна запись — объявляйте write сразу; «оптимистичное» read ради красоты часто приводит к отказам.

Анти-паттерны (и как их починить)

  • Глобальные счётчики/пулы. Любая запись «затыкает» всех. Замените на иерархию счётчиков, привязанных к пользователю/рынку.
  • Длинная транзакция «за всё». Сложнее распараллелить, выше риск упереться в CU. Разбейте на независимые шаги.
  • Скрытые зависимости в CPI. Нельзя «дотянуть» новый аккаунт из глубины — объявляйте всё наверху и проверяйте флаги доступа.

Поведение под нагрузкой и UX-следствия

В SVM пиковая конкуренция локализуется вокруг «горячих» аккаунтов (пулы ликвидности, минты, ордербуки). Это означает:

  1. Транзакции, не касающиеся очага, проходят по базовой цене — пользователь не переплачивает из-за чужого ажиотажа.
  2. Транзакции, которые касаются очага, соревнуются между собой; помогает разумная доплата за приоритет и корректные ретраи.
  3. «Прыжки» задержек (p95/p99) чаще объясняются конкретным рынком, а не всей сетью.

Диагностика и метрики для разработчика

Симптом Возможная причина Что делать
Много отказов «account not found / borrow as immutable» Неполный список аккаунтов или неверный режим доступа Явно перечислите все аккаунты на поверхности, синхронизируйте флаги read/write и signer
Пики латентности при минтах/арбитраже Локальный аукцион приоритета вокруг «горячих» аккаунтов Подсказки по приоритетной доплате в UI, варианты «обычно/быстро/срочно»
Низкая утилизация параллелизма Конфликты write–write на общих аккаунтах Перепроектируйте схему хранения, шардируйте по ключам
Высокий расход CU «Толстые» инструкции, лишняя сериализация Декомпозируйте, кешируйте, оптимизируйте критический путь

Частые вопросы (FAQ)

Это «параллельный консенсус»? Нет. Параллелится исполнение. Консенсус/финализация описаны отдельно (PoH и Tower BFT).

Можно ли скрыть конфликт, подтянув аккаунт через CPI? Нельзя. Все аккаунты и права объявляются заранее; иначе выполнение прервётся.

Почему две простые операции иногда ждут друг друга? Скорее всего, обе пишут в один и тот же «горячий» аккаунт (пул, ордербук). Шардируйте состояние и разделяйте права доступа.

Поможет ли «высокая комиссия» обогнать соседей? В пределах локального рынка — да. Это влияет на порядок в «очаге», но не повышает цену для остальной сети.

SVM совместима с EVM? Нет. Это другой ABI и модель данных. Порты возможны, но требуют переписывания под аккаунтную модель и явные account metas.

Мини-глоссарий

  • Account metas — список аккаунтов и прав, передаваемый в инструкцию.
  • PDA (Program-Derived Address) — адрес аккаунта, детерминированно выводимый программой по сид-параметрам.
  • CPI — вызов одной программы из другой внутри транзакции.
  • Compute Units (CU) — лимит вычислений на транзакцию.
  • Conflict set — пересечение транзакций по аккаунтам на запись; определяет сериализацию.

См. также

Sealevel (Solana): параллельное исполнение, рантайм и планировщик Solana: модель аккаунтов — PDAs, read/write-локи, rent-exempt, ALTs

Task Runner