Ко мне часто обращаются за советом люди, которые так или иначе имеют отношение к технологиям, работающим на блокчейне. Я решил написать эту статью, чтобы помочь тем, кто уже работает или планирует начать работать над проектами, в основе которых лежит распределенная база данных и блокчейн.
Я опишу вариант формирования блокчейна, который используется в Dcoin. Для его применения необходимо иметь уверенность, что ноды не смогут бесконтрольно размножаться.
В Dcoin ноды не могут бесконтрольно размножаться, т.к. каждый майнер — это живой человек, который доказал другим майнерам, что он человек и у него нет клонов. Слово «майнер» тут, скорее всего, не очень вписывается, но так уж сложилось, что я его использую именно в таком ключе.
Каждый раз после создания нового блока происходит распределение майнеров по уровням.
Предположим, что на 0-м уровне оказался майнер с ID 1056. Тогда распределение по уровням будет выглядеть так:
0: 1056
1: 1057, 1058
2: 1059, 1060, 1061, 1062
N: 1056+(2^N)-1 -> 1056+((2^N)-1)*2
Как определяется ID, который находится на 0-м уровне, я расскажу чуть позже.
Каждый майнер создает специальную пару ключей для работы ноды, публичный ключ записывается в блокчейн, а приватный ключ хранится на ноде и используется для подписания блоков.
Блок имеет следующую структуру:
Поле | Описание | Размер |
---|---|---|
BLOCK_ID | Порядковый номер блока | 4 байта |
TIME | Время, когда был создан блок | 4 байта |
USER_ID | ID пользователя, который создал блок | 5 байт |
LEVEL | Уровень, на котором был майнер в момент создания блока | 2 байта |
SIGN | Подпись от (TYPE, BLOCK_ID, PREV_BLOCK_HASH, TIME, USER_ID, LEVEL, MRKL_ROOT), сделанная при помощи node-ключа | От 128 байт до 512 байт |
TRANSACTIONS | Транзакции | До 3Mb |
Время
При получении нового блока проверяется время его генерации, которое указала нода, создавшая этот блок. При проверке времени учитывается количество секунд, затраченное на генерацию, проверку и ожидание на уровне.
Через 120 секунд после времени подписания последнего блока нода майнера на 0-м уровне создает блок, записывая в него транзакции, которые у неё накопились.
Если по какой-то причине нода майнера с 0-го уровня не отправила в сеть блок, то через 120 секунд (если 120 секунд не прошло, то все ноды ждут блока с 0-го уровня) блок могут подписать ноды с 1-го уровня и т. д. Если нода с 0-го уровня проснется и начнет всем рассылать свой блок, то у неё будет шанс его «протолкнуть» только в том случае, если большинство нодов еще не получили следующий блок.
Деление на уровни нужно для того, чтобы блоки могли добавляться в прогнозируемые промежутки времени. С каждым уровнем увеличивается вероятность, что на нем окажутся живые ноды, которые сгенерируют и подпишут новый блок.
Сравнение хэшей
Если блок подпишут ноды майнеров, например, со 2-го уровня, то получится 4 валидных блока, которые разделят всю сеть на 4 сегмента, и все сегменты будут уверены, что у них верный блок и верная цепочка блоков.
Для того чтобы избежать такой сегментации, используется поиск наименьшего хэша от user_id,block_id,prev_head_hash.
Таким образом, когда какая-нибудь нода получит, например, 4 блока от майнеров с одного уровня, то она выберет тот, у которого будет наименьший хэш, а остальные 3 проигнорирует. При этом ID у сравниваемых блоков должны быть равными.
Манипуляции с хэшем невозможны, т.к. в нем нет данных, которые можно изменить.
Процесс поиска блока с наименьшим хэшем вначале происходит между нодами одного уровня в течение от 5 до 8192 секунд в зависимости от уровня. Таким образом, в большинстве случаев в общую сеть попадают блоки с уже выбранным наименьшим хэшем.
Майнер, который находится на 0-м уровне, определяется вот так:
$ctx = hexdec(substr($hash, 0, 6));
$hi = $ctx / 127773;
$lo = $ctx % 127773;
$x = 16807 * $lo - 2836 * $hi;
if ($x <= 0)
$x += 0x7fffffff;
$level_0_miner_id = (($ctx = $x) % ($max_miner_id + 1));
$level_0_miner_id = ($level_0_miner_id==0)?1:$level_0_miner_id;
Где $hash = sha256(sha256(user_id,block_id,prev_head_hash))), $max_miner_id — порядковый номер последнего зарегистрировавшегося майнера, prev_head_hash — это предыдущий хэш от user_id,block_id,prev_head_hash.
Таким образом, сделать так, чтобы блоки подписывали какие-то определенные майнеры, невозможно, т.к. нет значения, которым можно манипулировать с целью изменения хэша (на основе которого происходит распределение майнеров по уровням). Благодаря этому описанный вариант формирования блокчейна может использоваться на практике.