Документація SolanaРозробка програм

FAQ

Задавайте свої запитання на StackExchange.

Berkeley Packet Filter (BPF)

Онлайн-програми Solana компілюються за допомогою інфраструктури компілятора LLVM у Executable and Linkable Format (ELF), що містить варіацію байткоду Berkeley Packet Filter (BPF).

Оскільки Solana використовує інфраструктуру компілятора LLVM, програма може бути написана будь-якою мовою програмування, яка може орієнтуватися на бекенд BPF від LLVM.

BPF надає ефективний набір інструкцій, який може виконуватися в інтерпретованій віртуальній машині або як ефективні нативні інструкції, скомпільовані за технологією just-in-time.

Карта пам'яті

Карта віртуальної адресної пам'яті, яку використовують програми Solana SBF, є фіксованою і має таку структуру

  • Код програми починається з 0x100000000
  • Дані стеку починаються з 0x200000000
  • Дані купи починаються з 0x300000000
  • Вхідні параметри програми починаються з 0x400000000

Вищезазначені віртуальні адреси є початковими, але програмам надається доступ до підмножини карти пам'яті. Програма аварійно завершиться, якщо спробує прочитати або записати у віртуальну адресу, до якої їй не надано доступу, і буде повернуто помилку "AccessViolation", яка містить адресу та розмір спроби порушення.

InvalidAccountData

Ця помилка програми може виникнути з багатьох причин. Зазвичай вона спричинена передачею програмі облікового запису, який програма не очікує, або в неправильній позиції в інструкції, або облікового запису, несумісного з інструкцією, що виконується.

Реалізація програми також може спричинити цю помилку під час виконання міжпрограмної інструкції, якщо забути надати обліковий запис для програми, яку ви викликаєте.

InvalidInstructionData

Ця помилка програми може виникнути під час спроби десеріалізації інструкції. Перевірте, що передана структура точно відповідає інструкції. Між полями може бути деяке заповнення. Якщо програма реалізує Rust трейт Pack, спробуйте упакувати та розпакувати тип інструкції T, щоб визначити точне кодування, яке очікує програма.

MissingRequiredSignature

Деякі інструкції вимагають, щоб обліковий запис був підписантом; ця помилка повертається, якщо очікується, що обліковий запис буде підписаний, але він не підписаний.

Реалізація програми також може спричинити цю помилку при виконанні міжпрограмного виклику, який вимагає підписаної програмної адреси, але передані насіння підписанта до invoke_signed не відповідають насінню підписанта, використаному для створення програмної адреси create_program_address.

Stack

SBF використовує фрейми стеку замість змінного вказівника стеку. Кожен фрейм стеку має розмір 4 КБ.

Якщо програма порушує розмір фрейму стеку, компілятор повідомить про перевищення як попередження.

Наприклад:

Error: Function _ZN16curve25519_dalek7edwards21EdwardsBasepointTable6create17h178b3d2411f7f082E Stack offset of -30728 exceeded max offset of -4096 by 26632 bytes, please minimize large stack variables

Повідомлення визначає, який символ перевищує свій фрейм стеку, але назва може бути спотворена.

Щоб розшифрувати символ Rust, використовуйте rustfilt.

Наведене вище попередження надійшло з програми Rust, тому розшифрована назва символу:

rustfilt _ZN16curve25519_dalek7edwards21EdwardsBasepointTable6create17h178b3d2411f7f082E
curve25519_dalek::edwards::EdwardsBasepointTable::create

Причина, з якої повідомляється попередження, а не помилка, полягає в тому, що деякі залежні пакети можуть включати функціональність, яка порушує обмеження фрейму стеку, навіть якщо програма не використовує цю функціональність. Якщо програма порушує розмір стеку під час виконання, буде повідомлено про помилку AccessViolation.

Фрейми стеку SBF займають діапазон віртуальних адрес, починаючи з 0x200000000.

Розмір купи

Програми мають доступ до купи під час виконання через API Rust alloc. Для забезпечення швидкого розподілу пам'яті використовується проста 32КБ bump-купа. Купа не підтримує free або realloc.

Внутрішньо програми мають доступ до області пам'яті розміром 32КБ, що починається з віртуальної адреси 0x300000000, і можуть реалізувати власну купу на основі конкретних потреб програми.

Програми Rust реалізують купу безпосередньо, визначаючи власний global_allocator

Завантажувачі

Програми розгортаються та виконуються за допомогою завантажувачів середовища виконання, наразі підтримуються два завантажувачі BPF Loader та BPF loader deprecated

Завантажувачі можуть підтримувати різні бінарні інтерфейси додатків, тому розробники повинні писати свої програми для одного завантажувача та розгортати їх на ньому ж. Якщо програма, написана для одного завантажувача, розгортається на іншому, результатом зазвичай є помилка AccessViolation через невідповідність десеріалізації вхідних параметрів програми.

Для всіх практичних цілей програми завжди слід писати для найновішого BPF завантажувача, і найновіший завантажувач є типовим для інтерфейсу командного рядка та JavaScript API.

Розгортання

Розгортання програми SBF — це процес завантаження спільного об'єкта BPF у дані облікового запису програми та позначення облікового запису як виконуваного. Клієнт розбиває спільний об'єкт SBF на менші частини та надсилає їх як дані інструкцій Write до завантажувача, де завантажувач записує ці дані в дані облікового запису програми. Після отримання всіх частин клієнт надсилає інструкцію Finalize до завантажувача, завантажувач потім перевіряє, що дані SBF є дійсними, і позначає обліковий запис програми як виконуваний. Після того, як обліковий запис програми позначено як виконуваний, наступні транзакції можуть видавати інструкції для обробки цією програмою.

Коли інструкція спрямована на виконуваний SBF-програму, завантажувачналаштовує середовище виконання програми, серіалізує вхідні параметри програми, викликає точку входу програми та повідомляє про будь-які виявлені помилки.

Для отримання додаткової інформації див. розгортання програм.

Серіалізація вхідних параметрів

Завантажувачі SBF серіалізують вхідні параметри програми в байтовий масив, який потім передається в точку входу програми, де програма відповідає за десеріалізацію на блокчейні. Одна з відмінностей між застарілим завантажувачем і поточним завантажувачем полягає в тому, що вхідні параметри серіалізуються таким чином, щоб різні параметри розташовувалися на вирівняних зміщеннях у вирівняному байтовому масиві. Це дозволяє реалізаціям десеріалізації безпосередньо посилатися на байтовий масив і надавати вирівняні вказівники програмі.

Останній завантажувач серіалізує вхідні параметри програми наступним чином (усе кодування здійснюється у форматі little endian):

  • 8 байтів беззнакове число акаунтів
  • Для кожного акаунту
    • 1 байт, що вказує, чи є це дублікатом акаунту, якщо не дублікат, то значення дорівнює 0xff, інакше значення є індексом акаунту, дублікатом якого він є.
    • Якщо дублікат: 7 байтів заповнення
    • Якщо не дублікат:
      • 1 байт булевого значення, true якщо акаунт є підписантом
      • 1 байт булевого значення, true якщо акаунт доступний для запису
      • 1 байт булевого значення, true якщо акаунт виконуваний
      • 4 байти заповнення
      • 32 байти відкритого ключа акаунту
      • 32 байти відкритого ключа власника акаунту
      • 8 байтів беззнакове число lamport, що належать акаунту
      • 8 байтів беззнакове число байтів даних акаунту
      • x байтів даних акаунту
      • 10k байтів заповнення, використовується для realloc
      • достатньо заповнення для вирівнювання зміщення до 8 байтів.
      • 8 байтів епохи rent
  • 8 байтів беззнакового числа instruction data
  • x байтів instruction data
  • 32 байти ідентифікатора програми

Is this page helpful?