Кратко
Перед выполнением рантайм загружает аккаунты, проверяет плательщика комиссии, проверяет освобождение от аренды и сериализует данные аккаунта в память, доступную для программ.
Эта страница посвящена внутренним механизмам рантайма. Большинству разработчиков эта информация не нужна для создания программ. Для информации, ориентированной на разработчиков, см. Структура аккаунта.
Загрузка аккаунтов
Перед выполнением транзакции рантайм загружает все указанные аккаунты через
load_transaction_accounts().
В этом процессе выполняется несколько проверок:
-
Проверка плательщика комиссии: Плательщик комиссии (первый аккаунт) должен существовать, быть системным аккаунтом или nonce-аккаунтом и иметь достаточно лампортов для оплаты комиссии (
validate_fee_payer()). После оплаты комиссии аккаунт должен либо остаться освобождённым от аренды, либо иметь ровно 0 лампортов. Нельзя, чтобы баланс оказался между 0 и минимальным значением для освобождения от аренды. Nonce-аккаунты всегда должны сохранять достаточно лампортов для освобождения от аренды. Если плательщик не является ни системным аккаунтом, ни nonce-аккаунтом, транзакция завершится ошибкойTransactionError::InvalidAccountForFee. -
Ограничение размера загружаемых данных: Общий размер всех загруженных аккаунтов (включая
TRANSACTION_ACCOUNT_BASE_SIZEпо 64 байта на аккаунт) не должен превышатьMAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES(64 МБ). Превышение этого лимита приведёт к ошибкеTransactionError::MaxLoadedAccountsDataSizeExceeded. -
Проверка program account: Каждый program account, вызываемый инструкцией, должен существовать и принадлежать корректному загрузчику: одному из
PROGRAM_OWNERS(BPF Loader Upgradeable, BPF Loader, BPF Loader Deprecated, Loader V4) или нативному загрузчику. Если program account не существует, транзакция завершится ошибкойTransactionError::ProgramAccountNotFound. Если он существует, но имеет некорректного владельца, транзакция завершится ошибкойTransactionError::InvalidProgramForExecution. -
Несуществующие аккаунты: Аккаунты, которые не существуют в сети, загружаются как аккаунты по умолчанию (0 лампортов, пустые данные, владелец — System Program) с
rent_epoch, установленным вu64::MAX.
Формат сериализации BPF
Когда программа вызывается, рантайм сериализует аккаунты в непрерывный буфер
памяти и передаёт его в BPF VM. Формат сериализации (стандартный выровненный
формат, используемый всеми загрузчиками, кроме устаревшего loader-v1) определён
в
serialize_parameters_aligned().
Буфер начинается с u64 (8 байт, little-endian), содержащего количество
аккаунтов. Затем для каждого аккаунта в инструкции буфер содержит:
| Смещение | Размер | Поле | Тип |
|---|---|---|---|
| 0 | 1 | маркер дубликата | u8 (0xFF = уникальный, index = дубликат этого аккаунта) |
| 1 | 1 | is_signer | u8 (0 или 1) |
| 2 | 1 | is_writable | u8 (0 или 1) |
| 3 | 1 | executable | u8 (0 или 1) |
| 4 | 4 | original_data_len (зарезервировано, всегда 0) | [0u8; 4] |
| 8 | 32 | key | Pubkey |
| 40 | 32 | owner | Pubkey |
| 72 | 8 | lamports | u64 (little-endian) |
| 80 | 8 | data_len | u64 (little-endian) |
| 88 | data_len | data | [u8] |
| 88 + data_len | 10240 + padding | realloc space + alignment | Заполнено нулями до MAX_PERMITTED_DATA_INCREASE (10 КиБ) + выравнивание до BPF_ALIGN_OF_U128 (8 байт) |
| ... | 8 | rent_epoch | u64 (little-endian) |
После всех аккаунтов буфер добавляет:
| Размер | Поле |
|---|---|
| 8 | instruction_data_len (u64, little-endian) |
| instruction_data_len | instruction_data |
| 32 | program_id (Pubkey) |
Для дублирующихся аккаунтов записывается только 1 байт (маркер дубликата с индексом оригинала) плюс 7 байт заполнения.
Дедупликация аккаунтов
Когда один и тот же публичный ключ аккаунта встречается несколько раз в массиве
accounts инструкции, рантайм
проводит дедупликацию.
Каждая запись в списке аккаунтов инструкции получает свою собственную
InstructionAccount
структуру, но записи, которые ссылаются на один и тот же аккаунт на уровне
транзакции, указывают на одни и те же исходные данные.
Метод
is_instruction_account_duplicate
определяет, является ли данный индекс аккаунта инструкции первым вхождением или
дубликатом, путем поиска индекса аккаунта на уровне транзакции и нахождения
первого индекса на уровне инструкции, который на него ссылается:
- Если текущий индекс аккаунта инструкции равен первому сопоставленному индексу,
это не дубликат (возвращает
None). - В противном случае возвращается
Some(first_index), гдеfirst_index— индекс первого вхождения.
Поскольку все ссылки на один и тот же аккаунт используют одни и те же исходные
AccountSharedData, изменения через любую из ссылок сразу видны через все
остальные. Однако одновременно может быть только одна изменяемая ссылка. Попытка
получить изменяемую ссылку на один и тот же аккаунт через два разных индекса
аккаунта инструкции одновременно вернет
InstructionError::AccountBorrowFailed. Программы должны освободить одну
ссылку прежде чем получить другую на тот же аккаунт.
После выполнения программы рантайм десериализует буфер обратно
(deserialize_parameters_aligned())
и применяет все изменения к lamports, data (включая изменения длины до
MAX_PERMITTED_DATA_INCREASE), и owner.
Is this page helpful?