image

В этой статье я опишу то, как я один написал криптовалюту с нуля, какие интересные технологии я оттуда вынес, с каким опытом ушел и что произошло потом. Это не туториал, а просто описание происходящих событий, но, если будет желание — могу написать пару статей на тему что и почему, хотя не уверен что это кому-нибудь нужно. В конце статьи будут выводы, к которым я пришел после 3х лет создания.

Всем привет, давно тут ничего не писал. Прошлый мой пост про бота на прологе был более-менее удачным и понравился публике. Так как я люблю вписываться во всякого рода странные затеи – захотелось что-то сделать.

Немного истории


Итак, в 2017 году летом я начал читать про блокчейн и криптовалюты, а так, как изучать у меня лучше всего получается именно на практике – решил написать свою криптовалюту. На самом деле цель была не просто в изучении, все началось с одной простой мысли – блокчейн – реестр, а значит – база данных. Почему тогда в блокчейне хранят только финансовые данные – почему бы не хранить произвольные?

С этими мыслями я начал изучение блокчейна биткоина, и полностью повторил его на nodejs со своими выкладками (и естественно велосипедами) за 4 месяца. К октябрю 2017 года у меня был рабочий проект с очень кривым кодом, который тяжело было поддерживать, буквально в ноябре я написал еще SPV клиент и добавил поддержку легких клиентов в сеть, кроме того переделал майнинг пул (единственная часть, которую не делал с нуля, а просто взял готовый и доработал), создал и оформил обзорщик блоков, который общался с локальной нодой через rpc методы.

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

Факт того, что у меня была рабочая криптовалюта с определенной идеей и инфраструктурой и я пропустил момент, когда все, абсолютно все криптовалюты показывали свои максимумы и можно было запустить любую криптовалюту (даже какой-нибудь токен, обеспеченный кирпичами) — добавил немного разочарования в мои светлые замыслы, но я не отчаялся.

В тот момент я не мог продолжить разработку, но было время изучить топ 10 криптовалют, поэтому сделал телеграм канал, в котором произвел анализ основных криптовалют, входивших на тот момент в топ 10 с технической стороны. Кстати, если хабраюзеры захотят — могу «портировать» эти статьи сюда.

Этот анализ в дальнейшем, когда я таки вернулся к разработке — позволили мне улучшить модель и более детально продумать архитектуру будущей криптовалюты. В 2019 году я вернулся к разработке и начал с нуля. Более детально продумал будущую архитектуру, избавился от перекрестных ссылок в коде, удалил лишние модули, перевел часть модулей в отдельные npm пакеты и собрал криптовалюту вот совсем недавно.

Детали


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

Криптовалюта


После первого относительно удачного опыта я осмелел и решил, что следующий проект должен быть красивее в плане кода, чем первый прототип. Кроме того, я решил делать систему модульной, в виде фреймворка, чтобы иметь возможность подключать и отключать модули при необходимости.

Вообще, написать криптовалюту в техническом плане — не такая трудная задача, если кратко:

  • выстраиваете базу данных для блоков и индексации
  • организуете протокол обмена сообщениями между нодами
  • определяете формат сериализации данных и алгоритм подписания транзакций
  • определяетесь с консенсусом и настраиваете правила добавления и валидации
  • rpc сервер по усмотрению
  • а далее настраиваете консольного клиента и пытаетесь запустить, сгенерировав генезис блок и настроив валидаторов
  • спустя тонну исправленных ошибок и неточностей — у вас рабочая криптовалюта, поздравляю

После этого идет этап добавления функций, которых нет в основе. Например у меня это datascript, основа проекта.

Datascript


Проект, который я написал за 4 месяца называлась orwell и является форком bitcoin в большей мере, но так же предполагает, что каждая транзакция кроме финансовой информации содержит дополнительные данные в HEX формате, которые (если транзакция валидна и попала в блок) — транслируются в специализированную VM, которая дешифрует эти данные и транслирует их в обычную базу данных. Эту VM я назвал orwelldb, а формат — datascript, пример дешифровки:

Hex:

ef01fd4f01190b6578616d706c6564617461fd3f0154fd3b011f000f0fa1067e45f40003f1096f776e65725f6b65798230343233633635653064373364626161386537393435633965663135626338366533643864396638636364323636626366323362623032646336333432386636623239343766336164643731636536333962653739646561333933313237613061336661623136613339306630326537363966633135373561333535333130323461f30a70726976696c6567657301f1008230346231363165656566623739363163366637306137643338666434373764616633333437643231346561663365313639643964316434336232346437323338383665343063643962633563633232393638333635656531663962653635333932303539383630643735656432356136303836653830666462653138336565613364f20b7772697465536372697074fdb815

[
{
   "dataset": "exampledata",
   "operator": "create",
   "content": {
    "owner_key": "0423c65e0d73dbaa8e7945c9ef15bc86e3d8d9f8ccd266bcf23bb02dc63428f6b2947f3add71ce639be79dea393127a0a3fab16a390f02e769fc1575a35531024a",
    "privileges": [
"04b161eeefb7961c6f70a7d38fd477daf3347d214eaf3e169d9d1d43b24d723886e40cd9bc5cc22968365ee1f9be65392059860d75ed25a6086e80fdbe183eea3d"
    ],
    "writeScript": 5560
   },
   "canRead": true,
   "success": true
  }
]

Расскажу немного про содержимое.

Датаскрипт содержит массив обращений к определенной базе и её датасетам, в данном случае в транзакции описано создание нового датасета (т.е. таблицы, если идет речь о реляционных базах данных), это следует из содержимого поля «operator». Кроме create там еще может быть «write» и «settings» — добавление (или изменение) данных и обновление параметров датасета соответственно. «content» это как раз те данные, которые записываются в датасет. В данном случае мы создаем таблицу, поэтому контент идентичен тому, что прописывается при operator=settings — настройки таблицы.

Чтобы описать что именно там написано, надо обратиться к концепции датаскрипта.
Продемонстирую диаграмму из 2017 года, созданную для этих целей:



Датаскрипт по аналогии с bitcoin и его lock и unlock скриптами содержит read и writeScript, первый определяет кто может читать сообщение, а второй — кто может писать в определенный датасет. Скрипты являются стек-ориентированными языками. Readscript прописывается в самом сообщении и указывает на того, кто может прочитать сообщение. Реализуется это при помощи шифрования. При указании шифрования — прочитать может только тот, у кого есть необходимый ключ в keystore, иначе — любой желающий. Сообщения с оператором create/settings никогда не должны быть зашифрованы.

Пример открытого readScript (читать может любой желающий):

DATA_HEXJSON + jsonhexbytes 
где DATA_HEXJSON  = 0x54

Зашифрованный:

DATA_HEXJSONENCRYPTED + var_str(encrypted data) + PUSHDATA_DBREADPRIVATEKEY + uint8(encryption) + OP_DECRYPT + OP_HASH256 + DATA_HASH + char[32](hash) + OP_EQUAL

в сыром виде:

0x53 + var_str + 0x56 + (0x1 or 0x2) + 0x57 + 0x59 + 0x58 + char[32] + 0x87

Тест расшифровки данных с проверкой хеша от расшифрованных данных.

Если кому-то интересно, более подробно я описал всё в документации к протоколу: github: datascript docs.

А вот указанный в коде jsonhex — это моя реализация перевода json в бинарный формат. Казалось бы, можно было оставить обычный json, но хотелось единообразия, поэтому разработал отдельный протокол. Если хотите прочитать и про него — вот документация: github: bitowl.


Writescript же — прописывается в настройках датасета, в примере это 0x5560. В данном случае там написано:

PUSHDATA_DBWRITEPUBLICKEY  OP_CHECKDBPRIVILEGES

Т.е. отправить публичный ключ отправителя сообщения на стек — и проверить список привилегий для этого датасета. Если этот ключ есть в списке привилегий (или отправитель create сообщения, или owner_key) — тогда он может писать. Кроме того, если writescript пустой — это значит ALL, т.е. писать в данный датасет может любой желающий.

Для operator=settings всё выглядит так же, меняются только настройки внутри content. Для write — content содержит собственно данные.

Немного про блокчейн


А как же указывается база данных, спросите вы? В моем случае я сделал хак — если транзакция содержит датаскрипт содержимое — первый выход данной транзакции всегда нулевой, и на него посылается данный датаскрипт, т.е. каждый адрес в сети является базой данных.

Эта модель позволяет хранить данные внутри самого блокчейна, что дает некоторую гибкость, но при этом добавляет проблем с хранением, ведь блокчейн биткоина разросся уже почти до 500 гигабайт, и это только на финансовых данных. Если добавить сюда еще и произвольные — получится избыточно. Поэтому хранение ограничилось лишь всякими ключами и связями для авторизации. И к слову, валидация всех правил происходит на уровне VM при создании, но необходимо синхронизировать её с блокчейном.

Например, сделал пару системных датасетов в системной базе данных: domain, masternode, token, dapp, тем самым можно создавать домены для каждого адреса/базы данных и оперировать не набором непонятных символов, а удобным ником (к примеру), кроме того, домены, по задумке могут использоваться в dapps (о которых напишу позже).
Что касается dataset token и masternode — в первом хранятся пользовательские токены, которые каждый участник может создать, а masternode хранит список публичных ключей участников, которые являются валидаторами сети. Тут необходимо небольшое отступление.

Consensus


Основа блокчейна — это консенсус, т.е. договоренность между нодами, некоторый набор правил, которые действуют в сети и все их исполняют чтобы сеть была работоспособной. Например — в биткоине действует консенсус Proof of Work, или майнинг, о котором, я уверен, писали тут множество раз. Суть консенсуса сводится к проверке новых блоков, публикуемых участниками сети. В биткоине участники сети в случайном порядке публикуют блоки — кто первый найдет, того и награда. В своей сети изначально делал так же, но позже решил, что это не рационально, так как хватит одного майнера из биткоина, чтобы нарушить работу моей сети и применить атаку 51%. Поэтому спустя какое-то время я реализовал модуль консенсуса consensusjs, который описал несколько разных консенсусов: centralized, PoW, PoS (PoW+PoS), static dpow, static dos, dynamic dpos. На последнем я и остановился.

Dynamic delegate pos (ddpos) предполагает, что в самом начале если определенное число делегатов (валидаторов) список которых сортируется по рейтингу и количеству монет в пользовании — и создается раунд, в течении которого каждый валидатор из списка публикует блок в строгом порядке. Когда раунд заканчивается рассчитывается новый, при этом каждый участник сети имеет возможность самостоятельно рассчитать текущий раунд и следующий раунд на основе открытых данных из сети.

Собственно для этого и необходима таблица masternode, в ней мы храним всех, кто изъявил желание быть валидатором, и их текущий рейтинг. В начале каждого раунда мы производим сортировку этой таблицы, и создаем раунд с получившимися N валидаторами. В случае же, если число валидаторов меньше N — создаем раунд с стандартными валидаторами сети, описанными в конфиг файле (их публичные ключи).

democracy


Концепция голосований внутри сети придумана и реализована в старой версии криптовалюты, она позволяет получать усредненные данные из сети от всех проголосовавших нод. В новой версии я реализовал отдельный модуль, но не успел его встроить. Предполагалось, что с помощью democracy можно было бы менять параметры сети путем честного голосования, а так же уменьшать размер хранимого блокчейна путем смещения генезис блока (генезис блок становится больше, путем размещения в нем старых UTXO и данных), но позволило бы синхронизировать не миллион блоков, а всего лишь последние 1000, к примеру. Кроме того, голосования бы могли управлять форками и изменениями в сети, а так же решать организационные вопросы, связанные с сетью. Правда внедрить этот модуль, как я писал выше — не успел, но задумка осталась.

dApps


Концепцию dapps была придумана чуть погодя после пика bitcoin в начале 18го. Тогда была идея сделать её через регистрацию приложения (публичного ключа) в блокчейне, регистрацию воркеров (публичных ключей) в системные таблицы, связь воркеров с приложением и уже воркеры бы работали в своем, изолированном блокчейне. В этой концепции есть еще viewer, т.е. часть приложения с интерфейсом для общения с клиентом, в качестве viewer может выступать как браузер, так и отдельное приложение. Позже от этой идеи я отказался, так как довольно долго реализовывать и сделал несколько проще.

Вы так же можете зарегистрировать dApp в блокчейне, связать его с доменом, а участник, зная домен, который связан с публичным ключом приложения — взаимодействовать с этим приложением. При обращении к домену, прозрачный dns сервер в клиенте сети считывает запрос пользователя, шифрует его с помощью ecdh шифрования, так, что прочитать содержимое сможет только участник с публичным ключом приложения и отправляет в сеть. Приложение получает этот запрос, отправляет его на endpoint, указанный в конфиге для этого приложения, и возвращает результат в сеть, так же зашифрованным.
Кстати, забавный факт: так как ноды общение между собой шифруют с помощью ecdh шифрования и dApp отправляет и принимает данные зашифровано — в моменты пересылок между нодами часть сообщения зашифрована два раза.

При таком методе получается зашифрованная сеть, где каждый участник может создавать сайты (и не только), а контролировать трафик становится проще, так как каждый участник содержит свой публичный ключ, которым он «просматривает сайт», ip адреса при этом вырезаются из запросов и ответов, делая сеть анонимной. Тем самым изначально есть авторизация, а так же возможно посмотреть баланс пользователя, баланс токенов (связанных с сайтом, например) и совершать процесс оплаты не уходя с сайта и из сети. Единственным минусом на момент тестов была производительность — в таком виде сайты грузятся в 10 раз медленнее, так как пересылаются по децентрализованной сети. Описанный выше механизм это всего лишь концепт, но уже наполовину реализованный..

Под спойлер запихнул ссылку на код, который это реализует (гитхаб):


С чего бы я начал сейчас


И в конце хочу написать пару приемов, которые бы я применил, если бы пришлось всё начинать с начала с текущими знаниями.

Модульная архитектура — определенно только да, чтобы каждый модуль был минимально связан со всеми остальными и мог быть заменен в случае, например, изменения консенсуса

Изоляция модулей — по сути продолжение предыдущего пункта, уже конкретно про функциональные модули, а не логические. Например — в моем коде консенсус реализован отдельным модулем consensusjs, что позволяет вдоль и поперек протестировать его перед внедрением в рабочий проект, тоже самое касается так называемых примитивов (формат транзакций, блоков и их сериализация/десериализация), тем самым можно протестировать функционал конкретного модуля до внедрения его в основной код.

Тесты — покрывать тестами каждый модуль — основа хорошего самочувствия в процессе запуска и нормального сна ночью (проверено на себе).

Версии во всем — так как криптовалюты являются замкнутой системой — необходимо заранее продумать о том, что будет дальше — как вы это будете поддерживать. Необходимо продумать модель обновления функционала от версии к версии у всех участников, при этом чтобы не страдали те, кто еще не обновился, так как в обратном случае может произойти не намеренный форк основной сети. В моем модуле consensusjs была реализован функционал определения форков и допустимых версий блоков, но в основной код я это еще не внес.

Итоги


Как я и обещал, в конце статьи подведу некоторые итоги. Данный пост можно считать точкой в истории с самодельной криптовалютой. С одной стороны я понимал, что это не для продакшена, необходим более чуткий контроль кода, покрытие тестами и прочие штуки из продакшн проектов, а так же больше, чем один программист. И финансы-финансы-финансы. Без этого никуда. У меня же, всего этого нет, и не было, поэтому мне просто было интересно попробовать, изучить технологию и понять стоит ли это того или нет. Это того стоило. Могу сказать, что после проделанной работы я лучше стал разбираться во многих аспектах программирования, технологий (очень продвинулся в работе с байт последовательностями, например) и определенно изучил блокчейн и технологии в основе него.

Например я точно знаю, что эфириум использует схожую концепцию с данными у транзакции и виртуальной машиной, обрабатывающей эти данные, только эфириум использует данные как функции (если по-простому) и обращения к ним (не считая создания контрактов). Я точно знаю, что внутренние скрипты биткоина (стековый язык) — довольно сильная штука, Сатоши подразумевал создание смарт-контрактов именно на их основе задолго до того, как появилось понятие «смарт-контракт», но использование этих скриптов урезано в биткоине, так как возможно появление неизвестных ранее багов.

Если говорить о технологиях, которые мне пришлись по душе после моего 3х летнего исследования — определенно это консенсус, т.е. договоренность между участниками сети и общие правила валидации сообщений. Фильтр блума — довольно интересная штука, позволяющая не раскрывая подробностей — посылать другому участнику фильтр, по которому он фильтрует отправляемые данные. Тем самым — вы получаете данные, не раскрывая какие именно данные вам нужны. «Дерево Меркла» как структура хранения данных, основа легких нод в bitcoin и деревьев состояний (там их три) ethereum. PoW — такая простая, но при этом такая элегантная задумка, «все дружно ищем число меньше чем общее средняя сложность за последние N блоков, высчитываемое обычным отношением».

Что же касается минусов — их тоже предостаточно. Первый из них — трудозатратность. Со временем я выгорел к этой идее, хоть она меня и захватывала около 3х лет. Например сейчас криптовалюта вроде как даже запущена, и казалось бы, уже ничего не держит чтобы запустить её, но после 5 тысяч блоков образовался странный баг с тем, что по какой-то причине отсчет блоков начинается с нуля (видимо как-то рушатся индексы), искать который пока нет не времени, ни желания.

Возможно когда-нибудь я вернусь к этой идее или схожей с ней (или основанной на ней). Если у вас есть желание — вы можете изучить код или сделать форк проекта friday на моем github, весь код под MIT лицензией. Определенно точно не перестану работать в этом направлении, ведь децентрализованные технологии это очень интересно, хоть и довольно трудозатратно. Жду в ЛС если есть какие-либо вопросы/предложения или уточнения. Помогу разобраться если вдруг нужно. Ниже организую опрос — стоит ли что-либо еще писать по этой теме здесь, хотя рейтинг сам все расставит на места.