Следующие форматы (в настоящее время) доступны интерфейсу и коду смарт-контракта в прототипе 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