Документация 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.

Размер кучи

Программы имеют доступ к куче времени выполнения через Rust alloc API. Для обеспечения быстрого выделения памяти используется простая 32КБ куча с последовательным выделением. Куча не поддерживает 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?

Содержание

Редактировать страницу