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

Accounts Model — это основа хранения и доступа к данным в Solana. В отличие от «глобального сториджа» в классической EVM-модели, Solana хранит состояние в аккаунтах (accounts), а транзакции заранее объявляют, какими аккаунтами они будут пользоваться и с какими правами (read/write, требуется ли подпись). Такая конструкция делает зависимости видимыми до исполнения, позволяет рантайму распараллеливать независимые вызовы (см. Sealevel (Solana): параллельное исполнение, рантайм и планировщик) и формирует предсказуемый UX при нагрузке (см. SVM (Solana Virtual Machine): что это и как работает — BPF, CPI и отличие от EVM и обзор Архитектура Solana: Объяснение Высокой Производительности, PoH, Sealevel).

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

Коротко о Accounts Model в Solana

  • Аккаунт — атом состояния: у него есть владелец (owner-программа), данные (bytes), баланс лампортов и флаг *executable*.
  • Права доступа задаются в инструкции: транзакция обязана перечислить все аккаунты и режимы (read/write, signer) ещё до запуска.
  • Параллелизм возникает из независимости наборов аккаунтов; write-конфликты сериализуют вызовы.
  • Композиция через CPI возможна только поверх объявленных аккаунтов — «тайно» подтянуть новый нельзя.
  • Дизайн dApp = дизайн аккаунтов: как вы разрежете состояние, так сеть и распараллелит работу.

Что такое аккаунт в Solana

Аккаунт — универсальный контейнер состояния и/или ресурса. Ключевые поля:

  • address — 32-байтный публичный ключ (обычно ключ пользователя или PDA);
  • owner — программа-владелец, которая может изменять данные аккаунта;
  • lamports — баланс (оплата хранения/ренты и др.);
  • data — произвольные байты (структуры, записи ордербука, конфиги и т. п.);
  • executable — флаг «исполняемый» (для программ).

Особые типы:

  • PDA (Program-Derived Address) — адрес, детерминированно производный от сидов и id программы; не имеет приватного ключа, но контролируется логикой программы.
  • Sysvars — системные аккаунты только для чтения (слот/время, недавние блокхэши и др.).
  • ALTs (Address Lookup Tables) — таблицы, позволяющие ссылаться на длинные наборы адресов без раздувания транзакции.

Жизненный цикл доступа: от инструкции к планированию

Инструкция (instruction) описывает мини-вызов функции программы и содержит account metas — список аккаунтов и флаги доступа:

  • *is_writable* — будем ли писать в аккаунт;
  • *is_signer* — нужна ли подпись владельца (или системная подпись, делегированная программой);
  • порядок — влияет на проверку прав и доступ к данным.

Транзакция — это одна или несколько инструкций плюс подписи. Важные свойства:

  • Все аккаунты, которые могут понадобиться даже во внутренних вызовах (CPI), должны быть перечислены заранее.
  • Режим доступа (read/write) нельзя «усугубить» в глубине CPI без явного объявления на поверхности.
  • Набор account metas задаёт «шахматку независимости», по которой планировщик Sealevel строит параллельные партии.

Почему именно аккаунты, а не «глобальный storage»

Критерий Модель аккаунтов (Solana) Глобальный storage (классическая EVM)
Гранулярность состояния Отдельные контейнеры-аккаунты Единая глобальная карта слотов
Видимость зависимостей Явно до исполнения (по account metas) Выявляются во время исполнения
Параллелизм По несовпадающим наборам аккаунтов Практически последовательная очередь
Контроль прав В инструкции (signer/write), валидируется у входа Зависит от логики контракта
UX при пиках Локальные рынки комиссий вокруг «горячих» данных Глобальный аукцион газа для всех

Разделение состояния на аккаунты делает возможным масштабирование «вширь»: если операции затрагивают разные аккаунты, они могут идти параллельно. Если же несколько вызовов пишут в один и тот же аккаунт, возникает write-конфликт — такие операции будут сериализированы.

Права доступа и валидаторы инвариантов

При входе в программу типичные проверки выглядят так:

  • владелец нужного аккаунта — мы (owner = id программы), иначе запись запрещена;
  • если требуется подпись пользователя — соответствующий аккаунт помечен *is_signer*;
  • если будем менять данные/баланс — аккаунт помечен *is_writable*;
  • для чувствительных путей — защита от «borrowed as immutable», двойных займов и др.

Базовый принцип: лучше отказать раньше, чем тратить CU на вычисления, которые потом упрётся в права.

CPI: композиция с предсказуемостью

CPI (Cross-Program Invocation) позволяет одной программе вызывать другую в рамках той же транзакции. Ограничения:

  • CPI не может «внезапно» открыть новый аккаунт — он должен быть в списке transaction.accounts;
  • права доступа к аккаунтам не могут расширяться в глубине без объявления наверху;
  • программы должны документировать минимальный/достаточный набор аккаунтов.

Эта дисциплина делает систему предсказуемой для планировщика: никаких «скрытых» зависимостей, которые ломают параллелизм.

Конфликты и их причины

Тип конфликта Что происходит Как исправить в dApp
write–write на одном аккаунте Две транзакции хотят одновременно писать в один и тот же объект Разделить состояние: делать per-user/per-market аккаунты, уходить от «единого бухгалтерского счёта»
read–write на «горячем» аккаунте Читатель сталкивается с писателем: блокировки и ретраи Использовать кэши/снимки; финальные проверки инвариантов перед коммитом
Скрытая зависимость через CPI В глубине вызова нужен аккаунт, которого не было в transaction.accounts Поднять все потенциальные ресурсы на поверхность; документировать требования к аккаунтам
CU-лимит и «тяжёлые» инструкции Транзакция съедает квоту, блокируя воркер Декомпозировать инструкции; оптимизировать сериализацию/путь горячих данных

Дизайн аккаунтов: паттерны, которые «делают скорость»

  • Шардируйте по пользователю/рынку. Один общий аккаунт ликвидности → узкое место; вместо него — множество аккаунтов по пулам/рынкам.
  • Разделяйте «горячее» и «холодное». Константы и редко меняющиеся поля вынесите в отдельные read-only аккаунты.
  • Нормализуйте формат данных. Структуры фиксированной длины, предсказуемые смещения; меньше аллокаций/реаллоков.
  • PDA как естественные шард-ключи. Формируйте адреса от (program_id, user_pubkey, market_id, тип записи) — это «раскладывает» состояние по ключам.
  • Узкие инструкции. Несколько коротких путей лучше, чем одна «всеядная» инструкция: планировщик использует параллелизм эффективнее.
  • Идempotентность там, где возможно. Повторный сабмит не должен ломать инварианты при повторном исполнении.

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

  • Глобальные счётчики и «центральная бухгалтерия». Любая запись сериализует всех. Лечится иерархией счётчиков/балансов.
  • Длинные цепочки CPI с динамическими зависимостями. Это б’є по CU и ломает планирование. Лечится декларативными API и явными account metas.
  • Переиспользование «общих» аккаунтов ради «удобства». Удобство для разработчика = узкое место под нагрузкой. Лечится правильной схемой PDA.
  • Преждевременная запись. Пишите позже, валидируйте раньше; сократите «время удержания» write-блокировок.

Рента, реалоцирование и «жизненный цикл» аккаунта

  • Рента/минимальный депозит. Хранение данных требует лампорты; если баланс ниже порога, аккаунт может быть очищен. Практика — сразу делать аккаунт «rent-exempt».
  • Реалоцирование (realloc). Менять размер *data* можно, но это операция с затратами: проектируйте форматы так, чтобы минимизировать realloc (фиксированные структуры, отдельные аккаунты под массивы).
  • Закрытие и возврат лампортов. Данные можно «обнулить», отправив лампорты на адрес получателя и освободив место. Учитывайте инварианты безопасности: кто имеет право «закрыть» и когда.
  • Executable-аккаунты. Программы деплоятся как отдельные аккаунты с флагом *executable*; обновляемость (если разрешена) контролируется механикой владельца и политиками сети.

Адреса и ALTs (Address Lookup Tables)

Когда инструкция оперирует длинным списком аккаунтов, размер транзакции растёт. ALTs позволяют:

  • вынести список адресов в таблицу и ссылаться на них по индексам;
  • сократить размер транзакции и подписи;
  • облегчить построение сложных маршрутов (DEX-аггрегаторы, ордеры, кросс-програмные пути).

Важно следить за жизненным циклом ALT (создание, расширение, деактивация) и правами доступа к ней.

Как аккаунты связаны с комиссиями и UX

В Solana комиссии формируются локально вокруг «горячих» аккаунтов. Следствия:

  • Если ваша операция не касается очага, она не переплачивает — базовая цена остаётся стабильной.
  • Если касается — вы соревнуйтесь с похожими транзакциями; помогает приоритетная доплата и разумные ретраи.
  • Разработчик влияет на UX дизайном аккаунтов: чем меньше пересечений по write, тем ровнее p95/p99 задержек и тем реже пользователю приходится «докручивать» цену.

(Базовые страницы для контекста: Local Fee Market и Priority Fee.)

Наблюдаемость и профилирование

Что смотреть в метриках:

  • Долю write-конфликтов по ключевым аккаунтам/программам — индикатор шардирования данных.
  • Распределение CU на инструкцию/программу — где горит CPU и сериализация.
  • p95/p99 латентности по типам операций, отдельно внутри и вне «горячих» рынков.
  • Ошибки CPI и прав доступа: account not found, borrowed as immutable, insufficient privileges.

Практика диагностики:

  • логируйте «набор аккаунтов» на поверхности транзакции (хеш/идентификатор набора);
  • отражайте в телеметрии «крупные» аккаунты (пулы, ордербуки) и их конфликтность;
  • автоматизируйте «lint» для инструкций: проверка консистентности флагов read/write/signer.

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

Можно ли «динамически» добавить аккаунт в процессе CPI? Нет. Все ресурсы перечисляются заранее. Попытка обратиться к неуказанному аккаунту вызовет отказ.

Почему две простые операции иногда ждут друг друга? Они пишут в один и тот же «горячий» аккаунт. Лекарство — шардирование по пользователю/рынку и декомпозиция инструкций.

Поможет ли просто «поднять комиссию»? Только внутри локального рынка (вокруг затронутых аккаунтов). На операции, не касающиеся очага, это не влияет.

Есть ли «правильный» размер аккаунта? Нет универсального. Избегайте частых realloc; выносите динамические массивы в отдельные аккаунты фиксированных кусков.

Чем PDA лучше обычного адреса пользователя? PDA детерминирован, не требует подписи приватным ключом, но подчиняется логике программы-владельца — это даёт безопасность и модульность.

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

  • Account metas — список адресов/прав для инструкции (read/write, signer, порядок).
  • PDA (Program-Derived Address) — адрес, получаемый из сидов и id программы, без приватного ключа.
  • Sysvar — системный аккаунт только для чтения с параметрами среды.
  • ALT — таблица подстановки адресов для «длинных» транзакций.
  • Realloc — изменение размера *data* аккаунта.
  • CPI — вызов одной программы из другой в рамках транзакции.

См. также

Sealevel (Solana): параллельное исполнение, рантайм и планировщик SVM (Solana Virtual Machine): что это и как работает — BPF, CPI и отличие от EVM

Task Runner