Следующие форматы (в настоящее время) доступны интерфейсу и коду смарт-контракта в прототипе Qtum x86.
Формат кодирования длины во время выполнения
В блокчейне все данные контракта для x86 используют вариант кодирования длины выполнения (RLE) для байтов, равных 0. Ненулевые данные не кодируются. Когда в потоке байтов встречается 0, он становится длиной, указывающей, сколько нулевых байтов следует за ним. Длина 0 для RLE категорически запрещена и приведет к недействительной транзакции, которая не может быть включена в блоки или в мемпул.
Полезная нагрузка RLE имеет префикс с 32-битным целым числом, которое содержит 24-битное поле декодированного размера. Если этот размер не совсем соответствует декодированной полезной нагрузке, транзакция недействительна.
остальные 8 бит, оставшиеся в 32-битном целом, зарезервированы как номер версии для потенциальной реализации других форматов сжатия.
Обратите внимание, что размер полезной нагрузки можно проверить без выделения дополнительной памяти.
Соответствующий комментарий к коду:
//format: decoded payload length (uint32_t) | payload
//the compression only compresses 0 bytes.
//when 0x00 is encountered in the bytestream, it is transformed converted to a run-length encoding
//encoding examples:
//0x00 00 00 00 -> 0x00 04
//0x00 -> 0x00 01
//0x00 00 -> 0x00 02
//0x00 (repeated 500 times) -> 0x00 0xFF 0x00 0xF5
//as part of the encoding process, a 32 bit length field is prefixed to the payload
Кодировка RLE используется поверх любых других форматов.
Код для кодирования и декодирования включен в x86Lib, но должен быть тривиальным для реализации на любом необходимом языке.
Поле длины может быть удалено позже, так как сейчас оно не дает никакой пользы. Первоначальное обоснование заключалось в том, что можно было бы использовать постоянный профиль выделения памяти, а не потенциально требующий многократного выделения. Однако декодированный формат данных сейчас хранится в резервной базе данных в Qtum, что делает декодирование одноразовой операцией.
Формат байт-кода смарт-контракта
Виртуальная машина x86 работает иначе, чем EVM кодирует и хранит контракты. В EVM все, что дается, — это плоский массив опкодов. Затем эти коды операций выполняются, и они решают, какая часть этих кодов операций и данных фактически хранится в постоянном хранилище в виде байт-кода контракта.
С виртуальной машиной Qtum x86 это намного проще и, как правило, легче рассуждать. Пользовательский двоичный формат с отдельными разделами для параметров (например, информация о конфигурации контракта и т. д.), инициализированных данных и кода. Весь двоичный файл хранится непосредственно в постоянном хранилище. Это предотвращает ситуацию в EVM, когда контракт также должен содержать хранитель контракта. Это не слишком сложно в EVM, но с существующими парадигмами, используемыми в разработке x86, это было бы крайне необычно, вероятно, потребовало бы написания двух совершенно отдельных программ, чтобы предотвратить предполагаемые ошибки адресной памяти и т. д.
Инструмент предоставляется в программе тестового стенда x86Lib для преобразования программы ELF в этот пользовательский двоичный формат. Причина отказа от прямого использования ELF заключается в том, что ELF содержит значительную часть потенциальной сложности, даже если большинство программ ELF достаточно просты для анализа. Вместо того, чтобы делать особый случай реализации типа «вы не можете использовать эти функции в ELF», вместо этого проще использовать очень простой пользовательский формат и предоставить инструменты преобразования. Из-за этого также потенциально возможно обеспечить поддержку плоского двоичного, PE и других двоичных форматов.
Пользовательский формат представляет собой плоский массив байтов с картой префиксов, указывающей длину каждого раздела:
//The data field available is only a flat data field, so we need some format for storing
//Code, data, and options.
//Thus, it is prefixed with 4 uint32 integers.
//1st, the size of options, 2nd the size of code, 3rd the size of data
//4th is unused (for now) but is kept for padding and alignment purposes
struct ContractMapInfo {
//This structure is CONSENSUS-CRITICAL
//Do not add or remove fields nor reorder them!
uint32_t optionsSize;
uint32_t codeSize;
uint32_t dataSize;
uint32_t reserved;
} __attribute__((__packed__));
После этой карты остается просто плоский массив байтов.
Поток:
- LOCATION = 0, что является первым байтом после ContractMapInfo.
- скопировать данные опций в буфер из LOCATION в LOCATION + optionsSize. Увеличить LOCATION по параметрамSize
- скопировать данные кода в буфер из LOCATION в LOCATION + codeSize. Увеличение LOCATION на codeSize
- и т.д и т.д…
Описание раздела:
- Options в настоящее время не используется, поэтому optionsSize должен быть равен 0. Позже он будет содержать графы зависимостей, параметры доверенных контрактов и другие специализированные данные конфигурации. Эти данные не передаются непосредственно коду контракта, если опция специально не указывает на это.
- Код — это исполняемый код, доступный только для чтения. Он загружается в пространство памяти контракта по адресу 0x1000 и в настоящее время имеет максимальный размер 1 МБ. Контракту не разрешается изменять содержимое этой памяти. Они также могут быть дополнительной платой за выполнение кода за пределами этого раздела, поскольку это может усложнить JIT и другие оптимизации. Прямо сейчас этот раздел имеет фиксированный размер 1 МБ, а все данные за пределами codeSize устанавливаются равными 0.
- Данные инициализируются и читаются-записываются. Он загружается в пространство памяти контракта по адресу 0x100000 и также имеет ограничение в 1 МБ. В некоторых документах этот участок памяти также упоминается как «рабочая память». Прямо сейчас это пространство имеет фиксированный размер 1 Мб, и любые данные за пределами dataSize установлены на 0. Раздел «.BSS» файлов ELF (неинициализированная память) также окажется в этой области памяти, но, поскольку нет связанных данных при этом нет необходимости предоставлять виртуальной машине какую-либо информацию о .BSS. Конвертер файлов ELF проверит, чтобы размер данных + размер bss не превышал 1Mb.
Формат транзакционного вызова смарт-контракта
Контракты имеют стек вызовов, который можно использовать для отправки аргументов в вызываемые контракты и для вызываемых контрактов для возврата данных. Однако наличие множества объектов данных в транзакции нетривиально для целей проверки. Таким образом, в формате транзакции для вызова контрактов будет только 1 фактическое поле данных (такое же, как сейчас для EVM). Тем не менее, ABI предоставляется для упрощения ответственности смарт-контракта за его анализ. Формат, конечно, можно игнорировать, имея только один большой аргумент, который представляет собой массив байтов, который вы хотите передать.
Поток:
- LOCATION = 0, первый байт данных
- 32-битное целое число, SIZE, считывается из LOCATION.
- LOCATION увеличивается на 2 (размер целого числа)
- Выделяется буфер размером SIZE, а затем в него копируется память из LOCATION в LOCATION + SIZE
- LOCATION увеличивается на SIZE
- Повторяйте, пока не останется данных
Интерфейс системного вызова
Qtum использует системные вызовы очень похоже на системы на базе Linux. Стек вообще не используется, а используются только регистры. Если требуется больше аргументов, чем существует регистров, то один регистр должен указывать на область памяти с этими дополнительными аргументами в какой-то структуре. В настоящее время для всех системных вызовов Qtum используется номер прерывания 0x40.
Регистрации для звонков:
- EAX — номер системного вызова
- EBX, ECX, EDX, ESI, EDI, EBP — аргументы 1–6
- ESP, EFLAGS — не используется
Регистры по возвращении из системного вызова:
- EAX — возвращаемое значение (0 обычно означает успех, за исключением операций, возвращающих длину)
- EBX, ECX, EDX, ESI, EBP, ESP, EFLAGS — без изменений
В libqtum предусмотрена функция-оболочка для упрощения этого интерфейса для C ABI с использованием языков:
.global __qtum_syscall
// long syscall(long number, long p1, long p2, long p3, long p4, long p5, long p6)
__qtum_syscall:
push %ebp
mov %esp,%ebp
push %edi
push %esi
push %ebx
mov 8+0*4(%ebp),%eax
mov 8+1*4(%ebp),%ebx
mov 8+2*4(%ebp),%ecx
mov 8+3*4(%ebp),%edx
mov 8+4*4(%ebp),%esi
mov 8+5*4(%ebp),%edi
mov 8+6*4(%ebp),%ebp
int $0x40
pop %ebx
pop %esi
pop %edi
pop %ebp
ret
Однако большинству людей никогда не нужно ничего делать с системными вызовами, поскольку libqtum предоставляет простые в использовании функции-оболочки для каждого системного вызова.
Оригинал: http://earlz.net/view/2018/10/22/2252/exposed-formats-in-qtum-x86