Підсумок
Перед виконанням середовище виконання завантажує облікові записи, перевіряє платника комісії, перевіряє звільнення від орендної плати та серіалізує дані облікових записів у структуру пам'яті, до якої програми можуть отримати доступ.
Ця сторінка описує внутрішню будову середовища виконання. Більшості розробників ця інформація не потрібна для створення програм. Перегляньте Структуру облікового запису для ознайомлення з точки зору розробника.
Завантаження облікових записів
Перед виконанням транзакції середовище виконання завантажує всі посилання на
облікові записи через
load_transaction_accounts().
Цей процес виконує кілька перевірок:
-
Перевірка платника комісії: платник комісії (перший обліковий запис) повинен існувати, бути системним обліковим записом або обліковим записом nonce, і мати достатньо lamports для покриття комісій (
validate_fee_payer()). Після сплати комісій обліковий запис повинен або залишитися звільненим від орендної плати, або мати рівно 0 lamports. Він не може опинитися між 0 та мінімумом для звільнення від орендної плати. Облікові записи nonce завжди повинні зберігати достатньо lamports, щоб залишатися звільненими від орендної плати. Якщо платник не є ні системним обліковим записом, ні обліковим записом nonce, транзакція завершується помилкоюTransactionError::InvalidAccountForFee. -
Обмеження розміру завантажених даних: загальний розмір усіх завантажених облікових записів (включаючи
TRANSACTION_ACCOUNT_BASE_SIZEу 64 байти на обліковий запис) не повинен перевищуватиMAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES(64 МіБ). Перевищення цього ліміту призводить доTransactionError::MaxLoadedAccountsDataSizeExceeded. -
Перевірка облікового запису програми: кожна програма, викликана інструкцією, повинна існувати та належати дійсному завантажувачу: одному з
PROGRAM_OWNERS(BPF Loader Upgradeable, BPF Loader, BPF Loader Deprecated, Loader V4) або нативному завантажувачу. Якщо обліковий запис програми не існує, транзакція завершується помилкоюTransactionError::ProgramAccountNotFound. Якщо він існує, але має недійсного власника, транзакція завершується помилкоюTransactionError::InvalidProgramForExecution. -
Неіснуючі облікові записи: облікові записи, які не існують в ланцюзі, завантажуються як типові облікові записи (0 лампортів, порожні дані, належать системній програмі) з
rent_epochвстановленим наu64::MAX.
Формат серіалізації BPF
Коли програма викликається, середовище виконання серіалізує облікові записи в
суміжний буфер пам'яті та передає його до BPF VM. Формат серіалізації (для
стандартного вирівняного формату, який використовується всіма завантажувачами,
окрім застарілого loader-v1) визначено в
serialize_parameters_aligned().
Буфер починається з u64 (8 байтів, little-endian), що містить кількість
облікових записів. Потім для кожного облікового запису в інструкції буфер
містить:
| Зміщення | Розмір | Поле | Тип |
|---|---|---|---|
| 0 | 1 | маркер дубліката | u8 (0xFF = унікальний, індекс = дублікат цього облікового запису) |
| 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 + вирівнювання | простір для realloc + вирівнювання | Заповнено нулями до 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?