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

Часто задаваемые вопросы

Задавайте свои вопросы на StackExchange.

Фильтр пакетов Беркли (BPF)

Программы Solana, работающие в цепочке, компилируются с использованием инфраструктуры компилятора LLVM в исполняемый и связываемый формат (ELF), содержащий вариацию фильтра пакетов Беркли (BPF) в виде байт-кода.

Поскольку Solana использует инфраструктуру компилятора LLVM, программа может быть написана на любом языке программирования, который поддерживает BPF-бэкенд LLVM.

BPF предоставляет эффективный набор инструкций, который может выполняться в интерпретируемой виртуальной машине или как эффективные скомпилированные инструкции в режиме реального времени.

Карта памяти

Виртуальная адресная карта памяти, используемая программами SBF Solana, фиксирована и имеет следующую структуру:

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

Указанные виртуальные адреса являются стартовыми, но программы получают доступ только к подмножеству карты памяти. Программа завершится с ошибкой, если она попытается читать или записывать данные по виртуальному адресу, к которому ей не был предоставлен доступ, и будет возвращена ошибка AccessViolation, содержащая адрес и размер попытки нарушения.

InvalidAccountData

Эта ошибка программы может возникнуть по многим причинам. Обычно она вызвана передачей аккаунта в программу, который программа не ожидает, либо в неправильной позиции в инструкции, либо аккаунта, несовместимого с выполняемой инструкцией.

Реализация программы также может вызвать эту ошибку при выполнении инструкции межпрограммного взаимодействия и забывании предоставить аккаунт для программы, которую вы вызываете.

InvalidInstructionData

Эта ошибка программы может возникнуть при попытке десериализовать инструкцию. Убедитесь, что переданная структура точно соответствует инструкции. Между полями может быть некоторое выравнивание. Если программа реализует трейт Rust Pack, попробуйте упаковать и распаковать тип инструкции T, чтобы определить точное кодирование, которое ожидает программа.

MissingRequiredSignature

Некоторые инструкции требуют, чтобы аккаунт был подписан; эта ошибка возвращается, если аккаунт должен быть подписан, но не подписан.

Реализация программы также может вызвать эту ошибку при выполнении вызова между программами, который требует подписанного адреса программы, но переданные seed-подписи для invoke_signed не совпадают с seed-подписями, использованными для создания адреса программы 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 alloc на языке Rust. Для обеспечения быстрого выделения памяти используется простая куча с увеличением на 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 байтов беззнаковое число lamports, принадлежащих аккаунту
      • 8 байтов беззнаковое число байтов данных аккаунта
      • x байтов данных аккаунта
      • 10k байтов заполнения, используемого для перераспределения
      • достаточно заполнения для выравнивания смещения до 8 байтов.
      • 8 байтов эпоха аренды
  • 8 байтов беззнаковое число данных инструкции
  • x байтов данных инструкции
  • 32 байта идентификатора программы

Is this page helpful?