Уважаемые пользователи Голос!
Сайт доступен в режиме «чтение» до сентября 2020 года. Операции с токенами Golos, Cyber можно проводить, используя альтернативные клиенты или через эксплорер Cyberway. Подробности здесь: https://golos.io/@goloscore/operacii-s-tokenami-golos-cyber-1594822432061
С уважением, команда “Голос”
GOLOS
RU
EN
UA
vp-webdev
6 лет назад

Пишем свой блокчейн. Хранение данных


Когда-то давно в одном из постов @golosmedia была ссылка на очень полезный материал Блокчейн и майнинг своими руками.
Я все эти месяцев думал о нём и хотел освоить всё, что в нём написано. Наконец добрался:)
В данном материале не будет сетей на базе Ethereum, форков Голоса, поднятия ноды или запуска демона. Разберём именно суть алгоритма блокчейна и реализуем его на PHP+MySQL. Проще уж некуда(@denis-skripnik, ООП тоже не будет:) ).
Если вы знаете что такое print_r и select * from blablabla - смело читайте дальше, вам всё будет понятно.

Немного теории, кратко

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

  • номер блока (id)
  • автора блока (author)
  • данные (data)
  • время создания (timestamp)
  • хэш блока (hash)

Сразу уточню

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

Хранить блоки будем в таблице базы данных MySQL. Всё наглядно и удобно.

CREATE TABLE `blocks` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `author` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `data` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `timestamp` int(11) NOT NULL,
  `hash` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Таблица простейшая, её колонки я пояснил чуть выше.

Формирование хэша блока

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

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

Код

Весь код я помещу в четырёх файлах:

  • config.php - подключение к БД
  • create_block.php - создание блока
  • functions.php - функции для работы блокчейна
  • verify.php - проверка целостности цепочки блокчейна

Листинг config.php

В этом файле только подключение к БД.

$db = new PDO('mysql:dbname=blockchain; host=localhost',"db_user","db_pass",
    array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));

Листинг functions.php

Данные для записи в блок я буду генерировать случайным образом. Вы можете брать данные из отправленной вебформы, брать из RSS/API или парсить откуда-нибудь.

//формируем случайные данные для наполнения блока
function getRandomData() {
    $authors = ['bob', 'jack', 'tom',
    'jane', 'rose', 'liam', 'hugo',
    'amber', 'faith', 'zara', 'alex', 'jude'];
    shuffle($authors);
    $author = $authors[0];

    $data = json_encode(
      [
        'data' => 'Random string ' . strrev($authors[5]),
        'custom_data' => 'Random number ' . rand()
      ]
    );

    $timestamp = time();

    return [
        'author' => $author,
        'data' => $data,
        'timestamp' => $timestamp,
    ];
}

Для нахождения последнего блока просто берём последнюю строчку в БД.

//находим хэш последнего блока
function getLastHash() {
    global $db;
    //для первого блока хэш будет пустым
    $last_hash = '';

    //тянем hash последней записи
    $sql = "select hash from `blocks` order by id desc limit 1";
    $sth = $db->prepare($sql);
    $sth->execute();
    $res = $sth->fetch();
    if (!empty($res['hash'])) {
        $last_hash = $res['hash'];
    }

    return $last_hash;
}

Формируем контрольную сумму блока.

//формирование хэша блока
function createBlockHash($author, $data, $timestamp, $last_hash) {
    // формируем хэш блока, исходя из автора, данных, времени и хэша предыдущего блока
    $block_hash = hash('sha256', json_encode([$author, $data, (int) $timestamp, $last_hash]));
    return $block_hash;
}

Тут уже непосредственно сохраняем блок в БД. Уже сформированные данные просто записываем в базу в виде строки таблицы.

//создание блока
function insertNewBlock($author, $data, $timestamp, $block_hash) {
    global $db;
    $sql = "insert into `blocks` (`author`, `data`, `timestamp`, `hash`) values (:author, :data, :timestamp, :hash)";
    $sth = $db->prepare($sql);
    $sth->bindValue(':author', $author);
    $sth->bindValue(':data', $data);
    $sth->bindValue(':timestamp', $timestamp);
    $sth->bindValue(':hash', $block_hash);
    $sth->execute();
    $id = $db->lastInsertId();
    return $id;
}

Листинг create_block.php

Тут всё просто. Используя функции из файла functions.php формируем "блок" и сохраняем его в БД.
Для добавления блока в свой блокчейн просто обновите эту страницу несколько раз и всё, или добавьте в неё строку <meta http-equiv="refresh" content="1"> и браузер будет "создавать блок" каждую секунду, пока страница открыта в браузере.

include_once './config.php';
include_once './functions.php';

// генерируем данные для записи в блок
$data = getRandomData();

// получаем хэш последнего блока
$last_hash = getLastHash();

// формируем хэш нового блока
$block_hash = createBlockHash($data['author'], $data['data'], $data['timestamp'], $last_hash);

//сохраняем блок
$new_block_id = insertNewBlock($data['author'], $data['data'], $data['timestamp'], $block_hash);

if (!empty($new_block_id)) {
    echo 'Block ' . $new_block_id . ' created';
} else {
    echo 'Block creation error';
}

Листинг verify.php

В этом файле мы просто считаем все блоки и проверим правильность сформированных хэшэй.
Если вы сформируете несколько блоков и откроете verify.php - то увидите, что все блоки корректны. Если после этого вы вручную исправите что-то в таблице blocks - то увидите, что блок испорчен. Что именно вы исправили - уже не выяснить. Важен именно сам факт того, что было произведено исправление и "доверять" такому блокчейну(а именно битому блоку) нельзя.

include_once './config.php';
include_once './functions.php';

$sql = "select * from `blocks` order by id asc";
$sth = $db->prepare($sql);
$sth->execute();
$res = $sth->fetchAll();
$last_hash = '';
foreach ($res as $block) {
    // пересчитываем хэш блока
    $block_hash = createBlockHash($block['author'], $block['data'], $block['timestamp'], $last_hash);

    //сравниваем сформированный хэш с хэшом блока в блокчейне(БД)
    if ($block_hash == $block['hash']) {
        echo 'Block ' . $block['id'].' ok<br>';
    } else {
        echo 'Block ' . $block['id'].' fail<br>';
    }

    //"сохраняем" в памяти хэш предыдущего блока
    $last_hash = $block['hash'];
}

Давайте проверим

Я создал 10 блоков.

И проверил целостность цепочки(verify.php).

Теперь я внесу изменения в пятый блок, а именно - заменю имя автора с amber на shmamber.

И проверяем целостность цепочки(verify.php).

Как видите - сверка хэшей не прошла! Значит данные в пятом блоке исправлены.

Скачать код

Весь код архивом

Код на gist.github.com

А как же майнинг?

С ним я тоже разобрался. Правда как прикрутить его к нашей таблице blocks пока незнаю. Алгоритм майнинга будет в одной из следующих статей, с такими-же примерами.

P.S.

Друзья, если я где-то принципиально не прав - буду рад вашим поправкам.

Заглавное фото с сайта kfarchitecture.com

90
162.116 GOLOS
На Golos с August 2017
Комментарии (8)
Сортировать по:
Сначала старые