Доброе утро!
Сегодня мне бы хотелось рассказать о том, как мы подошли к реализации блокчейна в одном из проектов.
По ТЗ, требовалось использование блокчейна для хранения определённых данных известного формата. Основной мотивацией такого требования была возможность обезопасить реплики от изменения данных в них.
Для начала, попробуем понять, почему блокчейн даёт такую возможность. Блокчейн есть упорядоченная структура данных, в которой один объект (блок) содержит в себе хеш предыдущего блока. Таким образом, если кто-то изменит блок N, то мы сможем прочитать из блока N+1 хеш предыдущего блока, посчитать хеш блока N и убедиться, что они не сходятся. Вероятность коллизии, в данном случае, пренебрежимо мала. Добавить новый блок, конечно, в таком варианте можно, но высота блокчейна (количество блоков в нём) нам всегда известна и может храниться в другом месте, благо, это не так требовательно к ресурсом, как обращение за миллионом-другим записей по сети в надёжный источник каждый раз.
Хорошо, раз с надёжностью разобрались, давайте подумаем, как можно избавиться от лишних действий. Классический блокчейн, предложенный Сатоши Накамото, является децентрализованной системой хранения данных. Оно и понятно, ведь владельцам Bitcoin-кошельков не хочется потерять свои деньги из-за выключения какой-нибудь СХД, где они могли бы храниться, да и поддержание такой штуки довольно-таки затратно. В то же время, в сети царит недоверие пользователей друг к другу, из-за чего имеют место быть вещи вроде подтверждения блоков, ключей и т.д. В нашем же случае всё иначе: блок создаётся только в одном доверенном месте, другие пользователи получают цепь из этого же доверенного места, а единственная угроза описана выше. Таким образом, было решено реализовать централизованный блокчейн с доверенным центром.
Теперь немножко о технической стороне вопроса.
Для простоты, было решено писать на C99 под POSIX-систему. Сам блокчейн представляется в виде файла, который открывается с O_SYNC в надежде избежать кеширования записи ОС. Структура файла довольно простая: заголовок и последовательность блоков.
Заголовок включает в себя такие поля, как магическое число, версия, высота блокчейна и несколько зарезервированных байт на будущее. В добавок, сначала было предложено использовать дополнительное поле как признак открытости, но позднее, ввиду необходимости кластеризации и того, что мы научились синхронизировать это дело с помощью OCFS и вызова flock(2), нормально реализованного в её второй версии, данное поле пропало.
Блок же ещё проще — это структура, содержащая хеш родительского блока (целиком, включая хеш «дедушки»), его собственную высоту и некоторый «payload» — сами хранимые данные. В нашем случае всё очень здорово и payload — это массив предопределённого размера, гарантированно вмещающий любой возможный объект.
Такой подход даёт ряд преимуществ: адрес блока в файле может быть вычислен за константное время (смещение по размеру заголовка + константный размер блока * требуемая высота), а чтение и запись без изменения позиции для файлового дескриптора могут без проблем производиться с использованием pread(2)/pwrite(2).
В качестве хеш-функции был использован SHA256 из OpenSSL, просуммированный для предыдущих пяти блоков.
Помимо процего, на всякий случай была реализована логика, восстанавливающая потерявшуюся по каким-то причинам высоту при открытии блокчейна. Это помогло, когда в тестовом окружении блокчейн располагался в tmpfs, у которого не очень с O_SYNC.
Таким образом, читатель имеет общее представление о том, как подойти к реализации блокчейна в подобной ситуации. Может когда-нибудь напишу о том, как это дело интегрировать в джавку используя JNA, но уже более техническую.