Обзор
Разработчики могут писать и развертывать свои собственные программы в блокчейне Solana.
Пример Helloworld — хорошая отправная точка для того, чтобы увидеть, как программа пишется, собирается, развертывается и взаимодействует с ней в сети.
Пакетный фильтр Беркли (BPF)
Сетевые программы Solana компилируются через инфраструктуру компилятора LLVM в исполняемый и связываемый формат (ELF) содержащий вариант байт-кода Berkeley Packet Filter (BPF).
Поскольку Solana использует инфраструктуру компилятора LLVM, программа может быть написана на любом языке программирования, который может быть нацелен на серверную часть LLVM BPF. В настоящее время Solana поддерживает написание программ на Rust и C/C++.
BPF предоставляет эффективный набор инструкций, который может выполняться на интерпретируемой виртуальной машине или как эффективная компилируемая нативная инструкции.
Карта памяти
Карта памяти виртуальных адресов, используемая программами Solana BPF, исправлена и представлена следующим образом.
- Код программы начинается с 0x100000000
- Данные стека начинаются с 0x200000000
- Данные кучи начинаются с 0x300000000
- Входные параметры программы начинаются с 0x400000000
Вышеупомянутые виртуальные адреса являются начальными адресами, но программам предоставляется доступ к подмножеству карты памяти. Программа запаникует, если попытается прочитать или записать в виртуальный адрес, к которому ей не был предоставлен доступ, и будет возвращена ошибка AccessViolation, содержащая адрес и размер попытки нарушения.
Куча
BPF использует кадры стека вместо переменного указателя стека. Размер каждого кадра стека составляет 4 КБ.
Если программа нарушает этот размер кадра стека, компилятор сообщит о переполнении как предупреждение.
Например: «Ошибка: функция _ZN16curve25519_dalek7edwards21EdwardsBasepointTable6create17h178b3d2411f7f082E Смещение стека -30728 превышает максимальное смещение -4096 на 26632 байта, пожалуйста, минимизируйте большие переменные стека».
В сообщении указывается, какой символ выходит за рамки стека, но имя может быть искажено, если это символ Rust или C++. Чтобы разобрать символ Rust, используйте rustfilt. Приведенное выше предупреждение исходит от программы на Rust, поэтому имя символа в разобранном виде:
$ rustfilt _ZN16curve25519_dalek7edwards21EdwardsBasepointTable6create17h178b3d2411f7f082E
curve25519_dalek::edwards::EdwardsBasepointTable::create
Чтобы разобрать символ C++, используйте c++filt
из binutils.
Причина, по которой выдается предупреждение, а не ошибка, заключается в том, что некоторые зависимые крейты могут включать функциональные возможности, нарушающие ограничения фрейма стека, даже если программа не использует эти функциональные возможности. Если программа нарушает размер стека во время выполнения, будет сообщено об ошибке AccessViolation.
Кадры стека BPF занимают диапазон виртуальных адресов, начинающийся с 0x200000000.
Глубина вызова
Программы вынуждены работать быстро, и для облегчения этого стек вызовов программы ограничен максимальной глубиной в 64 кадра.
Куча
Программы имеют доступ к динамической куче либо непосредственно в C, либо через API alloc
Rust. Для облегчения быстрого распределения используется простая куча ударов размером 32 КБ. Куча не поддерживает free
или realloc
, поэтому используйте ее с умом.
Внутренне программы имеют доступ к области памяти размером 32 КБ, начиная с виртуального адреса 0x300000000, и могут реализовывать настраиваемую кучу в зависимости от конкретных потребностей программы.
Поддержка плавающих значений
Программы поддерживают ограниченное подмножество операций с плавающей запятой в Rust. Если программа попытается использовать операцию с плавающей запятой, которая не поддерживается, среда выполнения сообщит об ошибке неразрешенного символа.
Операции с плавающей запятой выполняются с помощью программных библиотек, в частности встроенных функций LLVM с плавающей запятой. Из-за эмулируемого программного обеспечения они потребляют больше вычислительных единиц, чем целочисленные операции. В общем, операции с фиксированной точкой рекомендуются там, где это возможно.
Математические тесты Solana Program Library сообщат о производительности некоторых математических операций: https://github.com/solana-labs/solana-program-library/tree/master/libraries/math
Чтобы запустить тест, синхронизируйте репо и запустите:
$ load test-bpf -- --nocapture --test-threads=1
Недавние результаты показывают, что операции с плавающей запятой требуют больше инструкций по сравнению с эквивалентами целых чисел. Реализации с фиксированной точкой могут различаться, но они также будут меньше, чем эквиваленты с плавающей запятой:
u64 f32
Multipy 8 176
Divide 9 219
Статические записываемые данные
Общие объекты программы не поддерживают доступные для записи общие данные. Программы совместно используются несколькими параллельными выполнениями с использованием одного и того же общего кода и данных только для чтения. Это означает, что разработчики не должны включать в программы какие-либо статические перезаписываемые или глобальные переменные. В будущем может быть добавлен механизм копирования при записи для поддержки записываемых данных.
Знаковое деление
Набор инструкций BPF не поддерживает signed деление. Добавление инструкции деления со знаком является рассмотрением.
Загрузчики
Программы развертываются и выполняются с помощью загрузчиков времени выполнения, в настоящее время существует два поддерживаемых загрузчика BPF Loader и загрузчик BPF устарел
Загрузчики могут поддерживать разные бинарные интерфейсы приложений, поэтому разработчики должны писать свои программы и развертывать их для одного и того же загрузчика. Если программа, написанная для одного загрузчика, развертывается на другом, результатом обычно является ошибка AccessViolation из-за несоответствующей десериализации входных параметров программы.
Для всех практических целей программа всегда должна быть написана для использования с последним загрузчиком BPF, а последний загрузчик используется по умолчанию для интерфейса командной строки и API-интерфейсов javascript.
Для получения информации о конкретном языке о реализации программы для конкретного загрузчика см.:
Развертывание
Развертывание программы BPF — это процесс загрузки общего объекта BPF в данные учетной записи программы и пометки учетной записи как исполняемой. Клиент разбивает общий объект BPF на более мелкие части и отправляет их в виде данных инструкции [Write
](https://github.com/solana-labs/solana/blob/bc7133d7526a041d1aaee807b80922baa89b6f90/sdk/program/src/loader_instruction. rs#L13) инструкции загрузчику, где загрузчик записывает эти данные в данные учетной записи программы. Как только все части получены, клиент отправляет загрузчику инструкцию Finalize
. , затем загрузчик проверяет правильность данных BPF и помечает учетную запись программы как исполняемую. Как только учетная запись программы помечена как исполняемая, последующие транзакции могут выдавать инструкции для обработки этой программы.
Когда инструкция направлена на исполняемую программу BPF, загрузчик настраивает среду выполнения программы, сериализует входные параметры программы, вызывает точку входа программы и сообщает о любых обнаруженных ошибках.
Для получения дополнительной информации см. deploying
Сериализация входных параметров
Загрузчики BPF сериализуют входные параметры программы в массив байтов, который затем передается в точку входа программы, где программа отвечает за десериализацию в цепочке. Одно из различий между устаревшим загрузчиком и текущим загрузчиком заключается в том, что входные параметры сериализуются таким образом, что в результате различные параметры попадают на выровненные смещения в выровненном массиве байтов. Это позволяет реализациям десериализации напрямую ссылаться на массив байтов и предоставлять выровненные указатели на программу.
Сведения о сериализации для конкретного языка см. в следующих разделах:
Последний загрузчик сериализует входные параметры программы следующим образом (все кодировки с прямым порядком байтов):
- 8 байт беззнаковое количество счетов
- Для каждого аккаунта
- 1 байт, указывающий, является ли это дубликатом учетной записи, если это не дубликат, то значение равно 0xff, в противном случае значение является индексом учетной записи, дубликатом которой он является.
- Если дубликат: 7 байтов заполнения
- Если не дублировать:
- 1 байт логическое значение, true, если учетная запись является подписантом
- 1 байт логическое значение, true, если учетная запись доступна для записи
- 1 байт логическое значение, true, если учетная запись является исполняемой
- 4 байта заполнения
- 32 байта открытого ключа аккаунта
- 32 байта открытого ключа владельца учетной записи
- 8 байт беззнаковое количество ламппортов, принадлежащих аккаунту
- 8 байт беззнаковое количество байт данных учетной записи
- x байт данных учетной записи
- 10k байт заполнения, используемого для realloc
- достаточно заполнения, чтобы выровнять смещение до 8 байтов.
- 8 байт эпохи аренды
- 8 байт беззнакового числа данных инструкции
- x байт данных инструкции
- 32 байта идентификатора программы