Little endian и big endian что это
Перейти к содержимому

Little endian и big endian что это

Разбираемся с прямым и обратным порядком байтов

Наиболее важная концепция заключается в понимании разницы между числами и данными, которые эти числа представляют. Число — это абстрактное понятия, как исчислитель чего-то. У Вас есть десять пальцев. Понятие “десять” не меняется, в зависимости от использованного представления: десять, 10, diez (испанский), ju (японский), 1010 (бинарное представление), Х (римские числа)… Все эти представления указывают на понятие “десяти”.

Сравним это с данными. Данные — это физическое понятие, просто последовательность битов и байтов, хранящихся на компьютере. Данные не имеют неотъемлемого значения и должны быть интерпретированы тем, кто их считывает.

Данные — это как человеческое письмо, просто набор отметок на бумаге. Этим отметкам не присуще какое-либо значение. Если мы видим линию и круг (например, |O), то можно интерпретировать это как “десять”. Но это лишь предположение, что считанные символы представляют число. Это могут быть буквы “IO” — название спутника Юпитера. Или, возможно, имя греческой богини. Или аббревиатура для ввода/вывода. Или чьи-то инициалы. Или число 2 в бинарном представлении (“10”). Этот список предположений можно продолжить. Дело в том, что один фрагмент данных (|O) может быть интерпретировано по разному, и смысл остается не ясен, пока кто-то не уточнит намерения автора.

Компьютеры сталкиваются с такой же проблемой. Они хранят данные, а не абстрактные понятия, используя при этом 1 и 0. Позднее они считывают эти 1 и 0 и пытаются воссоздать абстрактные понятия из набора данных. В зависимости от сделанных допущений, эти 1 и 0 могут иметь абсолютно разное значение.

Почему так происходит? Ну, вообще-то нет такого правила, что компьютеры должны использовать один и тот же язык, так же, как нет такого правила и для людей. Каждый компьютер одного типа имеет внутреннюю совместимость (он может считывать свои собственные данные), но нет никакой гарантии, как именно интерпретирует эти данные компьютер другого типа.

  • Данные (биты и байты или отметки на бумаге) сами по себе не имеют смысла. Они должны быть интерпретированы в какое-то абстрактное понятие, например, число.
  • Как и люди, компьютеры имеют различные способы хранения одного и того же абстрактного понятия (например, мы можем различными способами сказать “10”).
Храним числа как данные
  • Бит имеет два состояния (включен или выключен, 1 или 0).
  • Байт — это последовательность из 8 бит. Крайний левый бит в байте является старшим. То есть двоичная последовательность 00001001 является десятичным числом девять. 00001001 = (2^3 + 2^0 = 8 + 1 = 9).
  • Биты нумеруются справа налево. Бит 0 является крайним правым и он наименьший. Бит 7 является крайним левым и он наибольший.

Так в чем же проблема — компьютеры отлично ладят с одиночными байтами, правда? Ну, все превосходно для однобайтных данных, таких как ASCII-символы. Однако, много данных используют для хранения несколько байтов, например, целые числа или числа с плавающей точкой. И нет никакого соглашения о том, в каком порядке должны хранится эти последовательности.

Пример с байтом

Рассмотрим последовательность из 4 байт. Назовем их W X Y и Z. Я избегаю наименований A B C D, потому что это шестнадцатеричные числа, что может немного запутывать. Итак, каждый байт имеет значение и состоит из 8 бит.

Например, W — это один байт со значением 0х12 в шестнадцатеричном виде или 00010010 в бинарном. Если W будет интерпретироваться как число, то это будет “18” в десятеричной системе (между прочим, ничто не указывает на то, что мы должны интерпретировать этот байт как число — это может быть ASCII-символ или что-то совсем иное). Вы все еще со мной? Мы имеем 4 байта, W X Y и Z, каждый с различным значением.

Понимаем указатели

Указатели являются ключевой частью программирования, особенно в языке С. Указатель представляет собой число, являющееся адресом в памяти. И это зависит только от нас (программистов), как интерпретировать данные по этому адресу.

В языке С, когда вы кастите (приводите) указатель к конкретному типу (такому как char * или int *), это говорит компьютеру, как именно интерпретировать данные по этому адресу. Например, давайте объявим:

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

Теперь предположим, что мы напишем:

Этот оператор говорит компьютеру, что р указывает на то же место, и данные по этому адресу нужно интерпретировать как один символ (1 байт). В этом случае, с будет указывать на память по адресу 0, или на байт W. Если мы выведем с, то получим значение, хранящееся в W, которое равно шестнадцатеричному 0x12 (помните, что W — это полный байт). Этот пример не зависит от типа компьютера — опять же, все компьютеры одинаково хорошо понимают, что же такое один байт (в прошлом это было не всегда так).

Этот пример полезен, он одинаково работает на все компьютерах — если у нас есть указатель на байт (char *, один байт), мы можем проходить по памяти, считывая по одному байту за раз. Мы можем обратиться к любому месту в памяти, и порядок хранения байт не будет иметь никакого значения — любой компьютер вернет нам одинаковую информацию.

Так в чем же проблема?

Проблемы начинаются, когда компьютер пытается считать несколько байт. Многие типы данных состоят больше чем из одного байта, например, длинные целые (long integers) или числа с плавающей точкой. Байт имеет только 256 значений и может хранить числа от 0 до 255.

  • Машины с порядком хранения от старшего к младшему (прямой порядок) хранят старший байт первым. Если посмотреть на набор байтов, то первый байт (младший адрес) считается старшим.
  • Машины с порядком хранения от младшего к старшему (обратный порядок) хранят младший байт первым. Если посмотреть на набор байт, то первый байт будет наименьшим.

Повторюсь, порядок следования байтов не имеет значения пока Вы работаете с одним байтом. Если у Вас есть один байт, то это просто данные, которые Вы считываете и есть только один вариант их интерпретации (опять таки, потому что между компьютерами согласовано понятие одного байта).

Теперь предположим, что у нас есть 4 байта (WXYZ), которые хранятся одинаково на машинах с обоими типами порядка записи байтов. То есть, ячейка памяти 0 соответствует W, ячейка 1 соответствует X и т. д.

Мы можем создать такое соглашение, помня, что понятие “байт” является машинно-независимым. Мы можем обойти память по одному байту за раз и установить необходимые значения. Это будет работать на любой машине.

Такой код будет работать на любой машине и успешно установит значение байт W, X, Y и Z расположенных на соответствующих позициях 0, 1, 2 и 3.

Интерпретация данных

Теперь давайте рассмотрим пример с многобайтными данными (наконец-то!). Короткая сводка: “short int” это 2-х байтовое число (16 бит), которое может иметь значение от 0 до 65535 (если оно беззнаковое). Давайте используем его в примере.

  • Машина с прямым порядком хранения: Я думаю, short int состоит из двух байт, а значит я считаю их. Позиция s это адрес 0 (W или 0х12), а позиция s + 1 это адрес 1 (X или 0х34). Поскольку первый байт является старшим, то число должно быть следующим 256 * байт 0 + байт 1 или 256 * W + X, или же 0х1234. Я умножаю первый байт на 256 (2^8) потому что его нужно сдвинуть на 8 бит.
  • Машина с обратным порядком хранения: Я не знаю что курит мистер “От старшего к младшему”. Я соглашусь, что short int состоит из 2 байт и я считаю их точно также: позиция s со значение 0х12 и позиция s + 1 со значением 0х34. Но в моем мире первым является младший байт! И число должно быть байт 0 + 256 * байт 1 или 256 * X + W, или 0х3412.

Теперь Вы видите проблему? Машина с порядком хранения от старшего к младшему считает, что s = 0x1234, в то время как машина с порядком хранения от младшего к старшему думает, что s = 0x3412. Абсолютно одинаковые данные дают в результате два совершенно разных числа.

И еще один пример

Давайте для “веселья” рассмотрим еще один пример с 4 байтовым целым:

  • Машина с прямым порядком хранения: тип int состоит из 4 байт и первый байт является старшим. Считываю 4 байта (WXYZ) из которых старший W. Полученное число: 0х12345678.
  • Машина с обратным порядком хранения: несомненно, int состоит из 4 байт, но старшим является последний. Так же считываю 4 байта (WXYZ), но W будет расположен в конце — так как он является младшим. Полученное число: 0х78563412.
Проблема NUXI

Проблему с порядком байт иногда называют проблемой NUXI: слово UNIX, сохраненное на машинах с порядком хранения от старшего к младшему, будет отображаться как NUXI на машинах с порядком от младшего к старшему.

Допустим, что мы собираемся сохранить 4 байта (U, N, I, и X), как два short int: UN и IX. Каждая буква занимает целый байт, как в случае с WXYZ. Для сохранения двух значений типа short int напишем следующий код:

Этот код не является специфичным для какой-то машины. Если мы сохраним значение “UN” на любой машине и считаем его обратно, то обратно получим тоже “UN”. Вопрос порядка следования байт не будет нас волновать, если мы сохраняем значение на одной машине, то должны получить это же значение при считывании.

Однако, если пройтись по памяти по одному байту за раз (используя трюк с char *), то порядок байт может различаться. На машине с прямым порядком хранения мы увидим:

Что имеет смысл. “U” является старшим байтом в “UN” и соответственно хранится первым. Такая же ситуация для “IX”, где “I” — это старший байт и хранится он первым.

На машине с обратным порядком хранения мы скорее всего увидим:

Но и это тоже имеет смысл. “N” является младшим байтом в “UN” и значит хранится он первым. Опять же, хотя байты хранятся в “обратном порядке” в памяти, машины с порядком хранения от младшего к старшему знают что это обратный порядок байт, и интерпретирует их правильно при чтении. Также, обратите внимание, что мы можем определять шестнадцатеричные числа, такие как 0x1234, на любой машине. Машина с обратным порядком хранения байтов знает, что Вы имеете в виду, когда пишите 0x1234 и не заставит Вас менять значения местами (когда шестнадцатеричное число отправляется на запись, машина понимает что к чему и меняет байты в памяти местами, скрывая это от глаз. Вот такой трюк.).

Рассмотренный нами сценарий называется проблемой “NUXI”, потому что последовательность “UNIX” интерпретируется как “NUXI” на машинах с различным порядком хранения байтов. Опять же, эта проблема возникает только при обмене данными — каждая машина имеет внутреннюю совместимость.

Обмен данными между машинами с различным порядком хранения байтов

Сейчас компьютеры соединены — прошли те времена, когда машинам приходилось беспокоиться только о чтении своих собственных данных. Машинам с различным порядком хранения байтов нужно как-то обмениваться данными и понимать друг друга. Как же они это делают?

Решение 1: Использовать общий формат

Самый простой подход состоит в согласовании с общим форматом для передачи данных по сети. Стандартным сетевым является порядок от старшего к младшему, но некоторые люди могут расстроиться, что не победил порядок от младшего к старшему, поэтому просто назовем его “сетевой порядок”.

Для конвертирования данных в соответствии с сетевым порядком хранения байтов, машины вызывают функцию hton() (host-to-network). На машинах с прямым порядком хранения эта функция не делает ничего, но мы не будем говорить здесь об этом (это может разозлить машины с обратным порядком хранения 🙂 ).

Но важно использовать функцию hton() перед отсылкой данных даже если Вы работаете на машине с порядком хранения от старшего к младшему. Ваша программа может стать весьма популярной и будет скомпилирована на различных машинах, а Вы ведь стремитесь к переносимости своего кода (разве не так?).

Точно также существует функция ntoh() (network-to-host), которая используется для чтения данных из сети. Вы должны использовать ее, чтобы быть уверенными, что правильно интерпретируете сетевые данные в формат хоста. Вы должны знать тип данных, которые принимаете, чтобы расшифровать их правильно. Функции преобразования имеют следующий вид:

Помните, что один байт — это один байт и порядок не имеет значения.

Эти функции имеют критическое значение при выполнении низкоуровневых сетевых операций, таких как проверка контрольной суммы IP-пакетов. Если Вы не понимаете сути проблемы с порядком хранения байтов, то Ваша жизнь будет наполнена болью — поверьте мне на слово. Используйте функции преобразования и знайте, зачем они нужны.

Решение 2: Использования маркера последовательности байтов (Byte Order Mark — BOM)

Этот подход подразумевает использование некого магического числа, например 0xFEFF, перед каждым куском данных. Если Вы считали магическое число и его значение 0xFEFF, значит данные в том же формате, что и у Вашей машины и все хорошо. Если Вы считали магическое число и его значение 0xFFFE, это значит, что данные были записаны в формате, отличающемся от формата вашей машины и Вы должны будете преобразовать их.

Нужно отметить несколько пунктов. Во-первых, число не совсем магическое, как известно программисты часто используют этот термин для описания произвольно выбранных чисел (BOM может быть любой последовательностью различных байтов). Такая пометка называется маркером последовательности байтов потому что показывает в каком порядке данные были сохранены.

Во-вторых, BOM добавляет накладные расходы для всех передаваемых данных. Даже в случае передачи 2 байт информации Вы должны добавлять к ним 2 байта маркера BOM. Пугающе, не так ли?

Unicode использует BOM, когда сохраняет многобайтные данные (некоторые кодировки Unicode могут иметь по 2, 3 и даже 4 байта на символ). XML позволяет избежать этой путаницы, сохраняя данные сразу в UTF-8 по умолчанию, который сохраняет информацию Unicode по одному байту за раз. Почему это так круто?

Повторяю в 56-й раз — потому что проблема порядка хранения не имеет значения для единичных байт.

Опять же, в случае использования BOM может возникнуть другие проблемы. Что, если Вы забудете добавить BOM? Будете предполагать, что данные были отправлены в том же формате, что и Ваши? Прочитаете данные и, увидев что они “перевернуты” (что бы это не значило), попытаетесь преобразовать их? Что, если правильные данные случайно будут содержать неправильный BOM? Эти ситуации не очень приятные.

Почему вообще существует эта проблема? Нельзя ли просто договориться?

Ох, какой же это философский вопрос. Каждый порядок хранения байтов имеет свои преимущества. Машины с порядком следования от младшего к старшему позволяют читать младший байт первым, не считывая при этом остальные. Таким образом можно легко проверить является число нечетным или четным (последний бит 0), что очень здорово, если Вам необходима такая проверка. Машины с порядком от старшего к младшему хранят данные в памяти в привычном для человека виде (слева направо), что упрощает низкоуровневую отладку.

Так почему же все просто не договорятся об использовании одной из систем? Почему одни компьютеры пытаются быть отличными от других? Позвольте мне ответить вопросом на вопрос: почему не все люди говорят на одном языке? Почему в некоторых языках письменность слева направо, а у других справа налево?

Иногда системы развиваются независимо, а в последствии нуждаются во взаимодействии.

Little endian и big endian что это

Понятие Byte order, или порядок следования байт (или endianness) относится к очередности размещения в памяти многобайтовых величин (обычно целых чисел и чисел с плавающей точкой; хотя числа с плавающей точкой не используются в Linux kernel, они могут работать в пользовательских программах), как это поддерживается аппаратурой процессора. Соответственно бывает 2 варианта порядка байт — big endian и little endian. Big endian это такой порядок байт, когда самый значимый по значению байт числа (most significant byte) сохранен в памяти первым по порядку (т. е. у него самый маленький абсолютный адрес байта, в сравнении с остальными байтами числа). Соответственно little endian это противоположный порядок байт, когда наименее значимый байт сохраняется в памяти первым.

Чтобы было понятнее, рассмотрим пример. 4-байтное целое число 0x01020304 будет сохранено в памяти системы big endian следующим образом:

Байт0 Байт1 Байт2 Байт3
0x01 0x02 0x03 0x04

Big endian всегда используется для так называемого сетевого порядка байт (network byte order), который применяется при кодировании адресов в сетевых протоколах.

Та же самая величина, которая будет храниться в памяти системы little endian, разместится в противоположном порядке:

Байт0 Байт1 Байт2 Байт3
0x04 0x03 0x02 0x01

Обычно при программировании можно не обращать внимания на endianness, то есть не важно, как процессор сохранит байты чисел в системе — big endian или little endian; ядро CPU просто загружает данные из памяти и сохраняет данные в память, и представляет данные в Вашей программе уже в правильном виде. Однако, когда нужно обмениваться данными с другой системой, обе системы должны учитывать формат хранения данных в памяти (endianness).

Linux kernel может быть либо big endian, либо little endian, в зависимости от архитектуры, в расчете на которую kernel скомпилировано. Ниже в таблице показан endianness для различных типов архитектур процессоров и протоколов.

Примечание: процессор ARM может быть либо с архитектурой big endian, либо little endian, в зависимости от типа применяемого чипа, однако чаще всего это big endian. Архитектура PowerPC может быть сконфигурирована для работы либо в режиме big endian, либо little endian, но в Linux используется только big endian.

[Почему следует беспокоиться об endianness]

Как уже упоминалось, порядок байт на низком уровне в системе полностью прозрачен для программиста (про endianness можно часто забыть). Однако могут быть проблемы, когда происходит обмен данными с другой системой, поскольку эта другая система может ожидать появления данных в блоке памяти в противоположном порядке.

Например, если заранее нельзя предсказать тип системы на каком-то дальнем окончании сетевого соединения, сетевые протоколы должны заранее определить порядок байт, используемый для хранения многобайтных величин в заголовках пакетов. В этом случае порядок байт называют сетевым порядком байт (network byte order), и для протокола TCP/IP это будет big endian. Таким образом, отправляющая пакеты система конвертирует данные из локального порядка хранения байт в сетевой. После этого принимающая система преобразует данные из сетевого порядка байт в локальный. На практике, когда есть жесткие требования к быстродействию и заранее известно, что локальный порядок байт такой же, как сетевой, операция конверсии отбрасывается в целях оптимизации.

Другой хороший пример — протокол USB, у которого порядок байт для многобайтных величин little endian.

[Как программно определить endianness]

Можно написать простую программу, которая будет определять порядок байт в имеющейся системе.

Строки 1..4 определяют переменную foo, к которой можно обращаться либо как к числу типа int (тип int почти всегда состоит из нескольких байт) или как к массиву символов characters. На строке 6 переменная инициализируется целым значением 1, так что как минимум один байт в многобайтном числе станет равен 1 (наименее значащий байт), а все остальные значащие байты будут нулями. Если байт 0 массива наименее значимый, то он станет равным 1, и это означает, что система little endian. Если байт 0 массива самый значимый байт, то он будет нулем, и значит система big endian.

Kernel определяет различные типы переменных и макросы для обработки значений, которые зависят от порядка байт, отличающегося от используемого процессором текущей системы.

[Идентификаторы типов]

Следующие идентификаторы соответствуют типам u16, u32 и u64, за исключением случаев, когда они определены с поразрядным (bitwise) атрибутом, который вводят для ограничения применения их как целых чисел. Bitwise-атрибут используется утилитой sparse, чтобы гарантировать, что переменная преобразована в локальный тип процессора перед тем, как над переменной выполнятся другие (небезопасные, unsafe) операции.

Следующие типы можно применять для endian-зависимых переменных, после подключения header-файла linux/kernel.h.

[Макросы для преобразований]

Имеется множество макросов для преобразования порядка байт, используемого текущим процессором, в порядок либо big, либо little endian. Дополнительно для каждого типа конверсии имеются отдельные макросы для 16-, 32- и 64-разрядных значений. Имена макросов кодируют исходный и целевой порядок байт значения, так что по имени сразу понятно, что каждый макрос делает.

В целях написания портируемого кода Вы должны всегда применять эти макросы для преобразования из одного целевого порядка байт в другой, даже если заранее знаете, что это не требуется для того процессора, который сейчас используете. Если исходный и целевой порядок байт одинаковый, то макрос не будет выполнять никаких действий, так что применение макросов на быстродействии никак не скажется.

Следующие макросы вернут значение после конвертации. Обратите внимание, что заголовочный файл linux/kernel.h является заголовком, который должен быть подключен к файлам исходного кода, где макросы используются, но это не тот файл заголовка, где макросы реально определены.

Следующие макросы такие же, как и предыдущие, отличие только в том, что параметр макроса — это указатель на преобразуемую величину. Обратите внимание, что имена этих макросов такие же, только добавлен суффикс «p» (от слова pointer) в конце каждого имени.

Следующие макросы делают то же самое, что и предыдущие, но здесь место расположения исходной величины и преобразованной величины совпадают. Обратите внимание, что имена этих макросов такие же, только добавлен суффикс «s» (от латинской фразы in situ, что обозначает в том же месте) в конце каждого имени.

Следующие макросы предоставляют алиасы для имен функций, которые обычно применяются для преобразования порядка байт в коде сетевых приложений. Первые два макроса используются для преобразования из локального в сетевой порядок байт. Остальные два предоставляют обратное преобразование. Буквы «s» и «l» в конце имен в этом случае означают short (16-битное значение) и long (32-битное значение).

Как отмечалось ранее, сетевой порядок байт всегда big endian, и реализация этих макросов гарантирует корректное использование порядка байт сетевым хостом.

Понимание заголовков блоков в блокчейне Биткойна

Составление заголовка блока — довольно сложно устроенный и в высшей степени значимый процесс. Если Биткойн — живой дышащий организм, то заголовок блока — это сердце всей машины. Каждые 10 минут в блоках Биткойна перемещаются и рассчитываются миллионы долларов капитала. Заголовок блока как будто заверяет нотариально перемещение средств в транзакциях, сигнализирует о поддержке предложений об изменении сети и в конечном счете определяет движение и правомерность биткойн-транзакций.

Номер версии предоставляет возможность для обновления сети посредством софт-форков. Хеш предыдущего блока связывает текущий блок с родительским, создавая цепочку из блоков — блокчейн. Корень Меркла криптографически связывает все транзакции в блоке с его заголовком. Метки времени действуют как верифицируемая система, полезная для работы многих приложений на основе Биткойна. Закодированная целевая сложность дает майнерам знать, какой хеш сеть примет как валидный. Наконец, nonce обеспечивает выделенное пространство для поиска валидного хеша, чтобы майнеры могли выполнять свою работу, обеспечивая работу сети.

В сегодняшнем посте я подробно расскажу о каждом из этих полей заголовка — как оно устроено и для чего используется.

Введение

Блокчейн Биткойна состоит из блоков. Блоки (и транзакции) представляют собой версионированные двоичные структуры данных. Блок состоит из двух частей: блока транзакций (в котором содержатся все включенные в блок транзакции) и заголовка блока. Заголовок — это короткое и строго форматированное поле данных, предваряющее каждый блок. Он включает в себя шесть полей, все с различными функциональными возможностями, и SHA256d-хеш этих полей действует как идентификатор блока. В центре внимания этой статьи находится именно заголовок блока, и мы продолжим в него углубляться, но сначала давайте кратко коснемся блока транзакций.

Когда пользователь транслирует в сеть новую транзакцию, она добавляется к списку всех прочих неподтвержденных транзакций. Этот список называется мемпулом. Майнеры — это участники сети, ответственные за выбор неподтвержденных транзакций из мемпула для включения в следующий блок. Этот следующий блок называется блоком-кандидатом. Создание валидного блока — ответственность майнера. Валидный блок — это такой блок, хеш заголовка которого представляет собой число, меньшее или равное установленному сетью целевому значению.

Майнер выполняет большое количество операций хеширования в поисках допустимого хеша, отвечающего этому условию. Для создания новых хешей из тех же данных блока, используется поле заголовка, называемое nonce, которое майнер может быстро изменять, чтобы генерировать уникальные хеши в поиске первого валидного. Как еще будет описано ниже, nonce содержит только четыре байта (2³² бита), которые можно изменять. Таким образом, пространство поиска составляет 2³² бита, то есть через изменение только поля nonce для блока может быть сгенерировано до 4 294 967 296 уникальных хешей. Это может показаться большим пространством для поиска, но на самом деле оно довольно мало, учитывая мощности доступного сегодня ASIC-оборудования. Например, производительность Antminer S19 Pro от Bitmain составляет 110 Тхеш/сек. Это около 2⁴⁶ хешей в секунду. То есть эта машина полностью исчерпывает пространство поиска, предоставляемое полем nonce, менее чем за одну миллисекунду.

Если пространство поиска оказывается исчерпано, а валидный хеш не найден, то майнеру придется формировать новый блок из нового набора транзакций. Сбор нового блока может быть вычислительно затратным и интенсивным с точки зрения пропускной способности. Поэтому у майнеров есть стимул находить способы к расширению пространства поиска за пределы выделенного четырехбайтового nonce. Есть несколько способов расширить пространство поиска: использовать (1) свободные биты поля версии, (2) часть битов поля времени, (3) несколько дополнительных битов в подписи скрипта coinbase-транзакции. Последнее часто называют extranonce, хоть в протоколе Биткойна это и не является формально определенным полем.

Как и любые данные, транзакции сводятся к последовательностям байтов. Иметь слишком «тяжелые» блоки, «весящие» слишком много байтов, нежелательно по причине различных ресурсных ограничений. Два наиболее распространенных из них связаны с (1) каналом исходящей связи (пропускная способность / задержка) и (2) проверкой (насколько быстро CPU может проверять транзакции). Ограничения, связанные с хранением данных, сейчас не представляют большой проблемы, поскольку оно стоит сравнительно недорого, а ноды могут сжиматься. Однако большие блоки могут помешать людям с ограниченным местом для хранения данных запускать полную ноду, тем самым нанося ущерб децентрализации сети. Кроме того, без ограничения размера блока сеть становится восприимчивой к DDoS-атакам со стороны злоумышленников, транслирующих в нее огромное количество транзакции с минимальной стоимостью. Чтобы противостоять этим явлениям, существует определенное консенсусом сети ограничение размера блока, называемого весом блока.

Что побуждает майнеров майнить блоки? У майнеров есть два источника дохода: субсидия блока (о которой мы еще будем говорить позже) и комиссии за транзакции. Им нужно соблюсти тонкий баланс: максимизировать прибыль от комиссий, включив в блок-кандидат как можно больше транзакций, но при этом сохранить вес блока ниже 4 Мб. Это сродни известной задаче о рюкзаке.

Есть также один особый тип транзакций, имеющий отношение к теме заголовка блока: coinbase-транзакция. Coinbase-транзакция включается в каждый блок первой и отвечает за создание новых биткойнов (субсидии блока) и выплату вознаграждения майнеру. И как я уже упоминал, несколько дополнительных битов в coinbase-транзакции используются майнерами для увеличения пространства поиска хеша.

Заголовок блока

Теперь мы готовы переходить к обсуждению заголовка блока в Биткойне. Заголовок имеет размер 80 байт и содержит шесть различных полей данных, все в формате little-endian.

Для того чтобы анализировать и понимать заголовок блока и содержимое транзакции, нужно обладать пониманием двоичных, шестнадцатеричных чисел и порядка следования байтов. Чтобы это объяснить, я сделаю небольшое отступление в следующем подразделе. Те, кто уже знаком с этими понятиями, могут смело пропустить эту часть.

Системы счисления и порядок следования байтов

Двоичная, десятичная и шестнадцатеричная системы счисления

Целые числа чаще всего выражаются в системе счисления с основанием 10, десятичной системе. Но в равной мере они могут быть выражены и в системе с любым другим основанием. Две другие системы счисления, полезные для понимания более низкоуровневых концептов Биткойна, — это двоичная (с основанием 2) и шестнадцатеричная (с основанием 16). Изменение основания с 10 на 2 или 16 (или любое другое число, если на то пошло) не меняет значение целого числа, только то, как оно выражается.

В двоичной системе числа имеют основание 2, и для выражения полного диапазона целых чисел требуется только два значения: 0 и 1. Приведу в качестве примера следующие числа типа u8 (каждое число имеет длину 8 бит):

Понимание заголовков блоков в блокчейне Биткойна

Сравнение десятичных и двоичных чисел

Обратите внимание, насколько длиннее записываются двоичные числа в сравнении с десятичными.

Обычно к двоичным числам добавляется в качестве идентификатора символ b. Например, двоичный эквивалент десятичного числа 4 записывается как b0100 .

Вы можете спросить, почему для выражения, например, двоичного числа b0000 0001 используется четыре цифры. Почему не просто b1 ? Это потому, что в памяти компьютера необходимо заранее выбрать количество битов, требуемое для целого числа. Так что если целое число может представлять любое число до b1111 1111 , то в памяти целое число всегда должно занимать восемь цифр, даже если в действительности оно использует не все их.

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

В шестнадцатеричной системе числа имеют основание 16, поэтому для выражения полного диапазона целых чисел требуется шестнадцать значений: 0–9 и A–F.

Понимание заголовков блоков в блокчейне Биткойна

Сравнение десятичных и шестнадцатеричных чисел

Заметьте, что в этом случае десятичная система обладает более пространной записью, чем шестнадцатеричная.

Обычно к шестнадцатеричным числам добавляются в качестве идентификатора символы 0x. Например, шестнадцатеричный эквивалент десятичного числа 3 735 928 559 записывается как 0xDEADBEEF .

Обратите внимание, что 0xDEADBEEF — это четырехбайтовое число. Каждые два символа эквивалентны одному байту: 0xDE — это один байт, 0xAD — один байт и так далее.

Для полноты сравнения, двоичный эквивалент десятичного числа 3 735 928 559 записывается как b1101 1110 1010 1101 1011 1110 1110 1111 .

Порядок следования байтов

Понятие «порядок следования байтов» можно наглядно продемонстрировать на следующем примере: в русском языке мы говорим «23», а, например, в немецком это число произносится как «3 и 20». Это одно и то же число, только выраженное по-разному. Порядок следования байтов — аналогичная концепция: при прямом порядке (от старшего к младшему) сначала считываются наибольшие значения, при обратном порядке (от младшего к старшему), наоборот, сначала считываются наименьшие. Английские термины, используемые для обозначения порядка байтов — big-endian (от большего к меньшему) и little-endian (от меньшего к большему), — являются отсылкой к «Путешествиям Гулливера» Джонатана Свифта. В романе Свифта были две враждующие группы людей: тупоконечники (Big Endian) и остроконечники (Little Endian). Одни разбивали и начинали чистить яйца с широкой части, другие — с узкой. В романе эта незначительная разница в методологии разбивания яиц в итоге приводит к хаосу.

На базовом уровне порядок следования байтов относится к тому, как процессор компьютера загружает значения в свои регистры (память), а именно, как упорядочиваются байты.

В формате big-endian сначала считываются наиболее значимые байты. Именно так мы интуитивно читаем числа. Десятичное число 3 735 928 559 в шестнадцатеричном big-endian формате записывается как 0xDEADBEEF — совершенно ожидаемым образом. В big-endian системе это число в двоичном формате хранится в памяти компьютера следующим образом (шестнадцатеричные значения тоже представлены для наглядности):

Понимание заголовков блоков в блокчейне Биткойна

Двоичные и шестнадцатеричные числа в формате big-endian

Или же число может сохраняться в памяти компьютера в формате little-endian (от младшего к старшему). В little-endian сначала считываются наименее значимые байты числа. Это не то, как мы интуитивно читаем числа.

В little-endian системе это число в двоичном формате хранится в памяти компьютера следующим образом (шестнадцатеричные значения тоже представлены для наглядности):

Понимание заголовков блоков в блокчейне Биткойна

Двоичные и шестнадцатеричные числа в формате little-endian

При сравнении big-endian и little-endian форматов (надеюсь) становится ясно, что единственное различие заключается в порядке байтов. В big-endian шестнадцатеричный эквивалент числа 3 735 928 559 записывается как 0xDEADBEEF . В little-endian он записывается как 0xEFBEADDE . Порядок следования байтов важно иметь в виду, потому что в формате little-endian число 0xEFBEADDE по-прежнему эквивалентно десятичному числу 3 735 928 559, а не 4 022 250 974.

Помните, что все эти числа эквивалентны, просто выражены в разных форматах. По большей части то, какой порядок байтов используется в системе или программе, не имеет значения; важно постоянство и единообразие подхода.

В Биткойне (в основном) используется формат little-endian и это важно знать перед тем, как двигаться дальше.

Возвращаясь к заголовку блока

Вернемся к заголовку блока.

В шестнадцатеричном формате типичный заголовок блока выглядит следующим образом:

Понимание заголовков блоков в блокчейне Биткойна

Заголовок блока Биткойна на высоте 645 536

Этот заголовок взят из блока на высоте 645 536, добытого Slushpool. Мы будем использовать его в качестве примера на протяжении всей статьи.

Каждое поле, его тип данных и краткое описание поля приведены в следующей таблице.

Понимание заголовков блоков в блокчейне Биткойна

Поля данных заголовка блока

Как заголовок блока используется в процессе майнинга

На высоком уровне процесс майнинга сводится к следующим этапам:

  1. Выбор подходящего номера версии и времени в текущей эпохе. Из сети получается хеш предыдущего блока (из последнего блока в самой длинной ветке блокчейна) и целевое значение хеша.
  2. Выбор неподтвержденных транзакций из мемпула для включения в блок-кандидат.
  3. Создание coinbase-транзакции.
  4. Вычисление корня Меркла из всех транзакций блока, включая coinbase-транзакцию.
  5. Конкатенация этих значений для создания сообщения блока.
  6. Выбор числа для nonce и добавление его к сообщению блока — завершение создания полного заголовка блока-кандидата.
  7. Выполнение SHA256-хеширования заголовка блока (дважды) и сравнение результата с целевым значением.
  8. Если хеш заголовка блока меньше или равен целевому значению, то блок валиден, и майнер транслирует его в сеть для подтверждения другими участниками. Затем он возвращается к шагу 1 и начинает майнинг следующего блока-кандидата.
  9. Если хеш заголовка блока не удовлетворяет целевому значению, то блок невалиден, и майнер возвращается к шагу 6, увеличивая значение nonce и снова испытывая удачу.

Эти шаги показаны на схеме:

Понимание заголовков блоков в блокчейне Биткойна

Схема процесса майнинга для построения заголовка блока

Теперь, когда мы дали определение заголовку блока и описали, насколько важную роль он играет в Биткойне, можно более подробно изучить каждый из его параметров.

Версия

Понимание заголовков блоков в блокчейне Биткойна

Поле версии в блоке на высоте 645 536

Поле версии обычно обрабатывается как четырехбайтовое число в формате big-endian, интерпретируемое как тип int32 и содержащее дополнительные биты, которые майнеры могут использовать для подачи сигнала о готовности поддержать софт-форк и для получения дополнительных 2¹⁶ пространства поиска.

На первый взгляд непритязательное, на поверку поле версии блока оказывается не так просто устроено. Номер версии предоставляет майнерам инструмент для сигнализации о своей готовности поддержать те или иные обновления сети посредством софт-форка. Успех или непринятие активируемого майнерами софт-форка зависит от того, достаточное ли количество поступающих в сеть валидных блоков сигнализировало о готовности принять предлагаемые изменения. «Готовность» здесь означает, что майнер обновил используемое ПО до совместимости с версией биткойн-клиента, содержащей предлагаемые изменения. Примером голосования по софт-форку и его фиксации может служить активация SegWit.

Три бита с самым большим значением в поле версии зарезервированы для возможных будущих обновлений механизма. В настоящее время три самых значимых бита должны быть установлены в значение b001. Это значит, что минимально допустимый номер версии в формате big-endian — это 0x2000000 ( b0010 0000 0000 0000 0000 0000 0000 0000 ). Максимальный номер версии в big-endian формате — 0x3FFFFFF ( b0011 1111 1111 1111 1111 1111 1111 1111 ).

На момент написания этой статьи (декабрь 2020) номер версии записывается в соответствии со спецификацией BIP9. Прежде чем углубиться в BIP9, давайте посмотрим, как изменялся номер версии с момента основания сети.

Краткая история поля версии в заголовке блока

Версия 1 началась с блока генезиса в 2009 году. До сентября 2012 никакого значимого сигнализирования не просматривается.

В сентябре 2012 Bitcoin Core v0.7.0 представило BIP34, выводившее блоки версии 1 из стандарта Биткойна. Это BIP подразумевало введение двух новшеств:

  1. Структурированный способ для майнеров сигнализировать о своей готовности принять предлагаемый софт-форк. Это достигалось с помощью механизма переключения с двойным порогом, метода IsSuperMajority() , в котором определены два пороговых значения, называемые правилом 75% и правилом 95%. Правило 75% гласит, что, как только 750 из последних 1000 блоков сигнализируют о готовности принять изменения, установив номер версии на 2 или выше, все новые блоки, в заголовке которых указана версия 2, должны соответствовать предлагаемым в софт-форке спецификациям. Иначе блок будет отклонен сетью, даже будучи валидным по стандартам версии 1. Правило 95%, следующий этап, гласит, что, как только 950 из последних 1000 блоков будут иметь версию 2 или выше, все блоки с версией 1 будут отклоняться сетью как недействительные.
  2. Актуальное предложение о софт-форке, требовавшее, чтобы все блоки версии 2 или выше включали высоту блока как первый элемент в подписи скрипта coinbase-транзакции.

На то, чтобы блоки версии 2 стали стандартом, ушло около шести месяцев, а последний блок версии 1 на высоте 227 835 имел метку времени 2013–03–24 15:49:13 GMT.

Предложение софт-форка, определяющее версию 3, описано в BIP66 и было представлено в феврале 2015 в Bitcoin Core v0.10.0. Блоки версии 3 ограничивают подписи строго кодировкой DER. Метод подачи сигнала о поддержке софт-форка соответствовал BIP34.

Предложение софт-форка, определяющее версию 4, описано в BIP65 и было представлено в ноябре 2015 в Bitcoin Core v0.11.2. Блоки версии 4 стали распознавать новый опкод для системы скриптов Биткойна, OP_CHECKLOCKTIMEVERIFY , позволяющий блокировать UTXO от расходования на заданное время.

Хотя метод подачи сигнала IsSuperMajority() , описанный в BIP34, работал, он имел некоторые недочеты. А именно (1) отсутствие таймаута и (2) использование для подачи сигнала целых числовых значений, а не битов (объясняется ниже). BIP9 решил обе эти проблемы.

Улучшения поля версии в BIP9

BIP9 определяет поведение поля версии, применяемое сегодня. Требуется, чтобы каждое предложение софт-форка имело четыре параметра:

  1. Отличительное название.
  2. Бит в поле версии, изменение которого будет сигнализировать о готовности майнера принять изменения. Номер позиции бита обозначается N.
  3. Время старта: с какого момента определенный бит обретает актуальное сигнальное значение.
  4. Тайм-аут: если предложение не будет принято к определенной дате X, оно будет считаться непрошедшим.

Изменение одного бита в поле версии, вместо записи в поле заданного целого числа (как в BIP34), позволяет подавать сигналы по нескольким предложениям одновременно.

В блогпосте (англ.) Расти Рассела отлично объясняется эволюция и значимость поля версии. Отталкиваясь от этого поста, здесь я буду рассказывать о поле версии более детально. Давайте начнем с того, как именно работает сигнализация с помощью битов, описанная в BIP9.

Полное 32-битное двоичное представление типичного big-endian номера версии 0x20000000 выглядит так:

Это соответствует правилам, установленным в BIP9, согласно которым самые верхние биты должны быть b001 или больше.

Теперь представим, что есть новое предложение о софт-форке, называемое BIPN₀, которое устанавливает позицию сигнального бита на 0 (N = 0). Для сигнализации о поддержке предложения нулевой бит нужно изменить на 1. Тогда номер версии в big-endian формате будет выглядеть как 0x20000001 , или

Теперь этот майнер сигнализировал о своей поддержке BIPN₀.

Теперь представим, что одновременно активны еще два предложения по софт-форку: BIPN₁ (N = 1) и BIPN₂ (N = 2). Наш майнер готов поддержать только BIPN₀ и BIPN₂, но не BIPN₁. С механизмом BIP9 это не проблема. Сигнализировать о поддержке только BIPN₀ и BIPN₂ можно, установив значения нулевого и второго битов на 1, а бит на первой позиции оставить без изменений: 0. Тогда номер версии в big-endian формате будет выглядеть как 0x20000005 , или

Возможность в любой момент времени иметь несколько активных предложений о софт-форке делает этот механизм более совершенным в сравнении с использованием целочисленных значений согласно BIP34.

Также майнеры могут использовать и используют поле версии для получения дополнительного пространства поиска хеша. Первоначально, после того как было представлено BIP9, майнеры по-прежнему использовали для расширения пространства поиска все свободные биты поля версии. Однако это привело к тому, что ноды генерировали предупреждения, поскольку пытались интерпретировать биты как сигнал по несуществующим предложениям о софт-форке. С BIP320 эта проблема была решена путем выделения 16 бит для расширения пространства поиска и резервирования 13 бит для подачи сигналов по софт-форкам. Это позволяет майнеру расширить пространство поиска на 2¹⁶ и оставляет место для подачи сигналов по 13 актуальным предложениям о софт-форке.

Хеш предыдущего блока

Понимание заголовков блоков в блокчейне Биткойна

Поле хеша предыдущего блока в блоке на высоте 645 536

Поле хеша предыдущего блока представляет собой 32-битное значение в формате little-endian, интерпретируемое как тип char[32] , и является хешем предыдущего блока. Это поле обеспечивает связь между текущим и предыдущим блоком в сети.

Корень Меркла

Понимание заголовков блоков в блокчейне Биткойна

Поле корня Меркла в блоке на высоте 645 536

Поле корня Меркла представляет собой 32-байтовое значение в формате little-endian, интерпретируемое как тип char[32] . Структура дерева Меркла используется в области компьютерных наук для многих целей. В Биткойне дерево Меркла используется для криптографической привязки каждой транзакции в блоке к заголовку этого блока в лаконичных 32 байтах.

Каждая транзакция — это лист дерева Меркла. Транзакции должны быть включены в блок в топологическом порядке. То есть, если txₙ₊₁ расходует выход txₙ, то txₙ₊₁ должна быть отсортирована на более позднюю позицию в блоке, чем txₙ. А самый первый лист в дереве Меркла зарезервирован для coinbase-транзакции.

Поиск корня Меркла

После того как майнер отобрал транзакции для включения в блок-кандидат, для получения корня Меркла выполняются следующие шаги:

  1. Id (SHA256d-хеш) каждой транзакции попарно конкатенируются в один.
  2. Конкатенированная пара транзакций хешируется алгоритмом SHA256d. Получаемая хеш-сумма становится новой ветвью в дереве.
  3. Хеш-суммы, опять же, попарно конкатенируются и хешируются алгоритмом SHA256d, создавая новую ветвь в дереве.

Шаг 3 повторяется, пока не останется только один хеш, корень Меркла. Это значение и сохраняется в заголовок блока.

Понимание заголовков блоков в блокчейне Биткойна

Мерклизация произвольного набора данных

Время

Понимание заголовков блоков в блокчейне Биткойна

Поле времени в блоке на высоте 645 536

Поле времени представляет собой четырехбайтовое значение в формате little-endian, интерпретируемое как тип unit32 , которое является меткой времени внутри эпохи для текущего блока. С помощью меток времени сеть может определить текущую скорость подтверждения блоков, чтобы при необходимости периодически ее корректировать (каждые 2016 блоков, или около 2 недель). Правила консенсуса ограничивают диапазон допустимых значений временных меток примерно трехчасовым окном, оставляя свободными 2¹³ бит, которые можно использовать для расширения пространства поиска.

Валидная метка времени должна быть больше, чем среднее значение временных меток предыдущих 11 блоков. При скорости подтверждения 10 минут на блок, это составляет один час с момента отправки блока-кандидата. Метка времени должна быть также меньше времени, скорректированного по сети, плюс два часа. Это трехчасовое окно составляет 10 800 секунд, что дает дополнительные 2¹³ бит для расширения пространства поиска хеша:

Скорректированное по сети время — это локальное время (UTC) ноды плюс медианное смещение всех подключенных нод. Время сети никогда не корректируется более чем на 70 минут от локального системного времени.

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

Понимание заголовков блоков в блокчейне Биткойна

Поле битов в блоке на высоте 645 536

Поле битов представляет собой четырехбайтовое значение в формате little-endian, интерпретируемое как тип int32, в котором кодируется текущий целевой порог для валидного хеша. Результирующий хеш блока, чтобы рассматриваться сетью как допустимое решение, должен быть меньше либо равен этому целевому значению. Целевое значение обновляется каждые 2016 блоков (около двух недель), так что каждый заголовок блока, транслированный в течение этого периода в 2016 блоков, будет иметь одно и то же целевое значение.

Как мы знаем, для поиска хеша блока поля данных в заголовке блока (версия, хеш предыдущего блока, корень Меркла, целевой порог хеша, время и nonce) конкатенируются и хешируются вместе с помощью SHA256d. Результирующий хеш является хешем заголовка блока-кандидата и выступает уникальным идентификатором этого блока. Чтобы считаться валидным, хеш должен быть меньше либо равен текущему целевому значению, устанавливаемому сетью, — таким образом задается сложность добычи валидного блока для майнера. Без целевого порога создать допустимый блок не составляло бы никакого труда, и сеть бы просто распалась.

Максимальное целевое значение, создаваемое хешем SHA256d — это 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF или в десятичной системе счисления 26 959 946 667 150 639 794 667 015 087 019 630 673 637 144 422 540 572 481 103 610 249 215. Это очень большое число. Биткойн хранит целевое значение в виде числа с плавающей точкой, поэтому используется усеченная форма 0x00000000FFFF0000000000000000000000000000000000000000000000000000 , эквивалентная 26 959 535 291 011 309 493 156 476 344 723 991 336 010 898 738 574 164 086 137 773 096 960 в десятичной системе счисления. Это все еще огромное число.

Чтобы вместить его в четырехбайтовое пространство, используется компактный формат записи. Этот формат жертвует детализацией в пользу экономии байтового пространства. Следующие шаги подробно описывают получение целевого порога из значения поля битов.

1. Изменение формата следования байтов с little-endian (используемого в заголовке) на big-endian: 0x2F931D1A → 0x1A1D932F . В конечном счете важно не то, какой порядок байтов используется, но постоянство и последовательность подхода.

2. Отделение первого байта, 0x1A , от остальных, 0x1D932F . Этот первый байт называется мантиссой. Мантисса — это часть числа с плавающей точкой, представляющая количество значащих цифр. Затем мантисса умножается на основание (в данном случае 2) и возводится в степень, чтобы получить значение числа.

То есть целевое значение [хеша] извлекается из поля битов с помощью следующей операции:

Используя это уравнение, вычислим целевой порог для нашего блока:

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

3. Теперь результат вычисления из шага 2 можно сравнить с хешем заголовка. Если хеш заголовка меньше или равен целевому значению, он удовлетворяет правилам консенсуса Биткойна и может быть добавлен в его блокчейн.

Запись тех же чисел с основанием 10 выглядит следующим образом:

Следовательно, поскольку значение хеша меньше целевого, заголовок является валидным.

Давайте разберемся немного глубже, почему была выбрана эта формула и как она работает. Сам формат основан на стандарте IEEE 754 для арифметики с плавающей точкой, в котором подробно описывается, как компьютеры хранят числа с плавающей точкой.

Рассмотрим 16-битный регистр, содержащий число 11:

В 16-битном регистре, как вы уже догадались, есть место только для 16 бит. Если мы сместим биты на количество доступных битов (16), то столкнемся с переполнением и потеряем наше число 11:

Чтобы этого избежать, мы устанавливаем, что можем смещаться только на доступное количество битов минус то число битов, которые мы хотим сохранить. В данном случае, 16 – 4 = 12, что даст:

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

Nonce

Понимание заголовков блоков в блокчейне Биткойна

Поле nonce в блоке на высоте 645 536

Поле nonce представляет собой четырехбайтовое значение в формате little-endian, интерпретируемое как тип int32 , которое пошагово увеличивается для создания новых хешей текущего блока, чтобы найти удовлетворяющий целевому значению. Оно обеспечивает 2³² бита дополнительного пространства поиска хеша.

Параметр nonce играет важную роль в поиске валидного блока. Без nonce, когда майнер создает блок, не соответствующий сложности сети, для получения нового хеша ему пришлось бы изменять набор транзакций в блоке, пересчитывать корень Меркла и перестраивать заголовок блока. Такой процесс требует немалого объема вычислительной работы. С nonce же майнеру достаточно один раз создать заголовок блока-кандидата и сохранять постоянными все переменные, за исключением nonce. Майнер пошагово увеличивает значение nonce и пересчитывает хеш до тех пор, пока либо (1) хеш заголовка блока не будет соответствовать порогу сложности (найден валидный заголовок блока), либо (2) блок станет устаревшим (конкурирующий майнер нашел валидный заголовок блока), либо (3) будет исчерпано пространство поиска внутри поля nonce.

Coinbase-транзакция

Coinbase-транзакция — это особый тип транзакции в каждом блоке Биткойна, единственная транзакция, которая не указывает на существующий UTXO, но вместо этого выпускает новые BTC в соответствии с монетарной политикой протокола.

Coinbase-транзакции важны по трем основным причинам: (1) в них создаются новые UTXO, (2) они обеспечивают дополнительное пространство для поиска хеша и (3) позволяют майнерам помечать блоки как собственные.

Coinbase-транзакция для блока 645 536 выглядит следующим образом:

Понимание заголовков блоков в блокчейне Биткойна

Coinbase-транзакция в блоке на высоте 645 536

Каждое поле, его тип данных и краткое описание поля приведены в таблице:

Понимание заголовков блоков в блокчейне Биткойна

Поля данных транзакции

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

Подпись скрипта (скрипт разблокировки)

Понимание заголовков блоков в блокчейне Биткойна

Подпись скрипта в coinbase-транзакции блока на высоте 645 536

Подпись скрипта, называемая также скриптом разблокировки, имеет переменную длину в байтах, которая ограничивается консенсусом сети. Обычно скрипт разблокировки содержит условия, позволяющие использовать UTXO предыдущей транзакции. Однако в coinbase-транзакции существующие UTXO не используются, а только создаются новые. Вместо этого, подпись скрипта содержит высоту блока (в соответствии с BIP34) и любую другую информацию, которую майнер решит здесь разместить. Очень часто сюда включаются параметры extranonce, а также любые идентифицирующие «подписи», которые майнер может размещать по своему усмотрению.

Параметр extranonce — это не строгое поле, определенное протоколом Биткойна, но просто некоторые биты, которые майнер может использовать для расширения пространства поиска хеша. Многие пулы поддерживают параметр extranonce и накладывают ограничения на его длину. Чаще всего можно увидеть восьмибайтовый extranonce.

Разбирая подпись скрипта, первые 1–9 байтов обозначают длину скрипта разблокировки, являющуюся переменной. В данном случае длина составляет 0x4B (десятичные 75) байт.

Следующий байт обозначает длину следующей части coinbase-транзакции. В данном случае это 0x03 , что говорит о том, что следующие три байта, 0xA0D909 , имеют значение. В эти байтах записана высота блока, 645 536, в шестнадцатеричном little-endian формате, в соответствии с BIP34. Следующие 64 байта содержат extranonce и любую другую информацию, которую майнер решит здесь разместить. Размер параметра extranonce варьируется от пула к пулу.

Наконец, последние семь байтов, 0x2F736C7573682F , — это необязательный идентификатор, используемый SlushPool для пометки своих блоков, /slush/ . Снабжая блок этим идентификатором, пул публично сообщает, что этот блок был получен от него.

Конечно, любой другой субъект сети, добывший валидный блок, может включить в coinbase-транзакцию тот же текст и притвориться SlushPool или каким-то другим майнером. И наоборот, Slush (или любой другой майнер) может исключить из подписи скрипта свой обычный идентификатор и добыть блок анонимно.

Большинство майнеров обычно добавляют свое имя, чтобы показать, что они добывают блоки (это имеет смысл с точки зрения маркетинга, чтобы продемонстрировать успешность вашего пула), но точно так же здесь можно разместить любые другие данные или не размещать ничего. Чтобы составить представление о том, какой процент майнеров включают в coinbase-транзакцию свой идентификатор, в четырехдневный период с 25 по 28 сентября 2020 года идентификатор присутствовал в 77,7% блоков.

Заключение

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

Это инструмент, посредством которого все транзакции в блоке соединяются вместе через корень Меркла, и который затем связывает новый блок с родительским через хеш предыдущего блока, создавая таким образом цепочку блоков, блокчейн. Номер версии используется майнерами для поддержки обновления протокола и подачи сигнала о готовности принять предлагаемые изменения. Закодированная целевая сложность говорит майнерам о том, какой хеш заголовка сеть примет как валидный, а nonce дает им выделенное пространство для поиска такого хеша.

Заголовок блока исключителен в своей простоте, но при этом имеет множество применений и вариантов использования. Существуя в таком формате уже более десяти лет, он все еще достаточно гибок, чтобы адаптироваться по мере изменения и роста сети. То, как поля работают, может меняться и меняется к лучшему, как видно на примере механизма сигнализации о софт-форках в поле версии. Меняется и использование полей: например, майнеры могут использовать поля версии, метки времени и coinbase-транзакции для расширения пространства поиска хеша. Креативность разработчиков и пользователей в использовании заголовка блока и его полей для достижения новых целей и создания новых вариантов его применения впечатляет, и мне не терпится узнать, что принесет нам следующее десятилетие инноваций. Но что бы это ни было, я уверен, что заголовок блока будет лежать в основе этой прекрасной системы.

Подписывайтесь на BitNovosti в Telegram!
Делитесь вашим мнением об этой статье в комментариях ниже.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *