понедельник, 24 апреля 2017 г.

Monero. В чем "физический смысл" майнинга?

В предыдущей статье To mine or not to mine. Криптовалюты сегодня мы вкратце рассмотрели некоторые аспекты майнинга криптовалют на CPU/GPU на примере монеты Monero (XMR). Прочитав этот пост, один из моих знакомых задал мне вопрос? Интересно, а что там считается на самом деле и зачем это нужно? Т.е. для человека было просто непонятно, зачем нужны такие огромные вычислительные мощности (сотни и тысячи современных видеокарт) объединенных в пул и что действительно получается в результате расчетов. Т.е. для большинства "майнинг" по прежнему представляет собой что-то загадочное, сравнимое чуть-ли не с рождением Skynet'а и восстанием машин. Поэтому я решил вкратце попробовать объяснить как это работает, какое задание получает майнер (worker) от пула, как оно выглядит, что считается valid share и при каком условии находится блок. Т.к. я сам начал интересоваться этим сравнительно недавно, то давайте договоримся заранее (как в том анекдоте): "в пианиста не стрелять, он играет как умеет", другими словами, я попробую объяснить вам все это на уровне своего текущего понимания (при этом у меня и у самого остались кое-какие вопросы, в которых я постараюсь разобраться в будущем).

Давайте начнем. Любой miner предназначенный для stratum pool'ов общается с ПО пула посредством stratum протокола, который фактически сводится к приему и отправке HTTP/HTTPS запросов, использующих json-rpc. Допустим, что мы настроили тот же xmr-stak-cpu на определенный пул и запустили его. Первое что происходит - это авторизация worker'а на пуле, затем майнер (клиент) получает первый запрос работы (job) от пула (сервер), который выглядит следующим образом:

{"jsonrpc":"2.0","method":"job","params":{"blob":"0505d9c3f8c705399eed95fac6409fa82e4172521be233b049aeeffc6ba250f3ee955e524912be00000000763361a7cc7b391f1fcd510212242cd1
4197ae88440e3a1201a020c72a55f82d01","job_id":"387","target":"711b0d00"}}


Давайте попробуем разобраться, что за поля мы здесь видим:

id - это id worker'а, которому предназначено это задание, blob - само задание, job_id - номер задания, target - значение привязанное к локальной сложности сети, хеш блока удовлетворяющий условию которого и должен искать worker (майнер).

Чтобы понять что из себя представляет blob нам необходим обратиться к стандартам CryptoNonte, а именно [CNS003] CryptoNote Blockchain:


Далее попытаемся разобрать blob согласно приведенной структуре:

  • 05 - major_version
  • 05 - minor_version
  • d9c3f8c7 (0xc7f8c3d9) - время создания блока в формате Unix Timestamp. Переведя 0xc7f8c3d9 в десятичную систему получим 3354969049, что соответствует Fri, 24 Apr 2076 15:50:49 GMT. Почему правда дата здесь смещена на 10 лет вперед, я честног говоря не понял. Но не суть ...
  • 05 - этого поля в приведенной таблице нет, но видимо это номер версии протокола (?) ... с 2012 года стандарт cryptonote скорее всего претерпевал какие-то изменения, но более свежего документа я не нашел. 
  • 399eed95fac6409fa82e4172521be233b049aeeffc6ba250f3ee955e524912be - hash предыдущего блока. Кликнув по ссылке вы увидите его в блокчейне Monero.
  • 00000000 - nonce.
  • 763361a7cc7b391f1fcd510212242cd14197ae88440e3a1201a020c72a55f82d01 - информация, которая содержится уже за пределами block header'а. 

Теперь давайте обратимся к [CNS010] CryptoNote Difficulty Adjustment, в котором сказано: "The work being done by peers is an iterated hash calculation [CNS008]. Every block is considered valid only if the value of its hash is less than or equal to some target value.", что в вольном переводе означает - каждый блок считается валидным, только в случае, если значение его хеша меньше или равно некому значению target (!), которое в свою очередь зависит от сложности сети.

Читаем дальше: "The target value is computed as follows: Target = Floor((2^256 - 1) / Difficulty). Alternatively, it is possible to check the hash of a block without explicitly computing the target value: the block is valid if Hash*Difficulty < 2^256".

Значение target вычисляется следующим образом: Target = Округление(2^256-1) / Сложность). Давайте попробуем посчитать, значение target для сложности в 5000. Для работы с такими большими значениями можно воспользоваться любым онлайн-калькулятором больших чисел:

(2^256 - 1) / 5000 =

0x000d 1b71 758e 2196 52bd 3c36 1134 04ea 4a8c 154c 985f 06f6 9446 7381 d7db f487
0xffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff
0x000d 1b71 ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff

Таким образом получаем target = 0x000d1b71, что собственно в обратном порядке и отдал нам пул (обратите внимание на значение target из json - "711b0d00".

В результате получаем что наш майнер должен подбирать некое число nonce в заголовке блока и хешировать его снова и снова с помощью [CNS008] CryptoNight Hash Function до тех пор, пока значение его хеша не будет <= определенному target.

Если взглянуть на картинку выше (там где майнер получает задание и отправляет решение), то можно видеть что для nonce = "7c0b0000" (0x00000b7c = 2940), майнер насчитал нам hash ef23b7d76779ea2a4994804696888b660b273e398dd2922890d19f629f300c00. Запишем его в обратном порядке:

0x000c 309f 629f d190 2892 d28d 393e 270b 668b 8896 4680 9449 2aea 7967 d7b7 23ef
0x000d 1b71 ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff

Что как мы видим меньше нашего target. Отлично. Давайте посмотрим теперь на другую картинку, как пул принял эту шару:


2017/04/24 19:07:48 [nonce=0x00000b7c] Valid share at difficulty 5000/5376.

Т.е. текущая шара получилась со сложностью 5376, что больше установленной пулом локальной сложности 5000. Поэтому шара считается пулом валидной. Но (!), майнер подобрал nonce для сложности 5000, однако общая сложность сети на момент выполнения майнером этого задания, как видно, была 6661677007 (!). Если бы сложность сети на тот момент была равна 5000, то майнер отправив шару со сложностью 5376 нашел бы блок. Но для сложности 6661677007 это условие к сожалению не выполняется.

Именно так это и работает. Майнер подключается к пулу и получает задание для определенной локальной сложности сети (!), в нашем случае это 5000. Далее он с помощью подбора nonce ищет блок для которого сложность > 5000, т.е. выполняется условие, что значение хеша для этой шары < target. Затем он отправляет получившуюся шару на пул, а пул уже проверяет является ли сложность найденного хеша больше текущей сложности сети. Т.е. грубо говоря, присланная майнером шара в нашем примере была сложности 5376 (!), так вот если бы 5376 было больше чем 6661677007 мы бы нашли блок. Весь майнинг строится на допущении, что находя валидые хеши для блоков с меньшей сложностью есть некоторая вероятность того, что этот же хещ подпадет и под условие текущей сложности сети и блок будет найден.

Для сложности сети 6661677007 валидным хешем был бы хеш (если я правильно посчитал):

0x0000 0000 a50c dee1 2a55 9275 2785 f949 096f 87ab 6eea 2ff9 6758 6f85 b01c 5590

И все что меньше его. Найденный майнером хеш для сложности в 5000 был такой:

0x000c 309f 629f d190 2892 d28d 393e 270b 668b 8896 4680 9449 2aea 7967 d7b7 23ef

Поэтому для сложности 5000 он решил бы блок, а вот для сложности 6661677007 - естественно нет.

Интересующиеся могут ознакомиться со статьей Bitcoin mining the hard way: the algorithms, protocols, and bytes, в ней правда рассказывается про Bitcoin, который использует другой алгоритм хеширования, но какие-то общие моменты можно будет почерпнуть и оттуда.

Некоторые возможно заинтересуются, а как пул посчитал сложность найденного хеша? Т.е. как получилось 5376 для хеша 0x000c309f629fd1902892d28d393e270b668b8896468094492aea7967d7b723ef. Для этого нужно (2^256) разделить на этот хеш. Результатом будет как раз: 5 376:


А теперь давайте посмотрим на результаты несколько реально работающих майнеров на протяжении суток (в качестве ПО у нас используется xmr-stak-cpu, xmr-stak-amd и xmr-stak-nvidia), в нем предусмотрен вывод Top 10 лучших найденных результатов за все время. Итак, при текущей сложности сети в 6816718873 по прошествии суток работы лучшие результаты были такими:


Т.е. лучшая сложность шары найденная майнером была 113327313 при сложности сети в 6816718873 (9 знаков в найденной шаре против 10 знаков в сложности сети). Таким образом ни одного результативного блока за это время найдено не было. Но на все, как говорится, воля случая ... ;)

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

7 комментариев :

  1. Этот комментарий был удален автором.

    ОтветитьУдалить
    Ответы
    1. Вы уже сделали это ;) Функционал комментариев как раз и предназначен для обратной связи.

      Удалить
    2. Этот комментарий был удален автором.

      Удалить
  2. Этот комментарий был удален автором.

    ОтветитьУдалить
    Ответы
    1. Изучите исходники существующих майнеров, например xmr-stak-cpu или ccminer ... проще, наверное, начать с первого, так как его можно собрать и под MSVC и при желании посмотреть под отладчиком в процессе выполнения.

      Удалить
    2. Этот комментарий был удален автором.

      Удалить
  3. добрый день. отличная статья, спасибо!
    подскажите зачем хеш записывается в обратном порядке, и как это сделать? спасибо

    майнер насчитал нам hash ef23b7d76779ea2a4994804696888b660b273e398dd2922890d19f629f300c00. Запишем его в обратном порядке:

    ОтветитьУдалить