Пишем свой блокчейн. Хранение данных
Когда-то давно в одном из постов @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