Про Blockchain сегодня не пишет только ленивый. Существует огромное количество статей разной степени понятности и полезности. Это очередная из них. Нам захотелось создать максимально простой, но работающий блокчейн и написать кратко, но понятно для неспециалистов, как же эта собака этот блокчейн работает. Так родился проект BaumankaCoin, исходники которого можно загрузить c GitHub-а.
Многие люди представляют себе технологию блокчейн и криптовалют как некий мега rocket science. Безусловно, чтобы понять все тонкости и нюансы – потребуется потратить много времени; но в действительности данная технология, если разобраться, оказывается гораздо более простой для понимания, чем принято считать. Реализовав свой coin мы намеревались помочь людям понять "на пальцах" устройство данных технологий.
BaumankaCoin разработана двумя студентами, almiku и skalniy под менторством PavelMSTU.
Основы
Транзакция
Первая смысловая единица, с которой мы разберемся — это транзакция: перевод денег с одного аккаунта на другой. В современном мире банковских систем перевод осуществляется за счет уменьшения суммы напротив одной фамилии и увеличения напротив другой. В блокчейне транзакция устроена иначе.
Пока оставим вопрос о таинственном и ужасном майнинге, а поговорим только о транзакциях в блокчейне.
Для начала, предположим, что транзакции выглядят примерно так:
- Transaction 1 «Вася отправил 50 коинов Ане»
- Transaction 2 «Гриша отправил 30 коинов Ане»
- ...
Имея информацию обо всех совершенных транзакциях можно выяснить суммы на счетах Ани, Васи и Гриши.
Общая же структура немного сложнее. В каждой транзакции есть поля:
- inputs – транзакции, на которые ссылается данная транзакция.
- tails – список структур вида: {получатель, сумма}.
В нашем случае состав транзакций Васи и Гриши будет выглядеть следующим образом:
Теперь предположим, что Аня собралась перевести Оле 70 бауманкакоинов. Как было написано выше, для этого Ане необходимо сослаться на транзакции от Васи и Гриши. Структура перевода будет выглядеть следующим образом:
Мы помним, что при переводе человек обязан сослаться на предыдущие транзакции, однако ссылаться на каждую конкретную транзакцию можно только один раз. То есть, чтобы потратить оставшиеся 10 коинов, Аня не может ссылаться на переводы денег от Васи и от Гриши. Что делать?
Из этого положения есть крайне изящный выход: Анна должна совершить перевод на оставшуюся сумму самой себе.
В будущем Аня сможет ссылаться на перевод, который сделала сама себе, что и позволит ей потратить последние 10 монет.
В итоге, все описанные выше операции можно изобразить следующим образом:
Идентификация пользователей
Давайте поговорим о том, каким образом человек получает адрес своего электронного кошелька, на который люди будут переводить ему деньги. Данная процедура состоит из трех этапов, о которых мы поговорим далее: генерация приватного и публичного ключей, получение адреса.
Главным инструментом, определяющим каждого конкретного человека, является приватный ключ. Таким образом, для создания нового аккаунта нужно просто взять случайное число из определенного диапазона. Если число выбрано действительно случайно – вероятность совпадения с тем, что кто-то еще выбрал такое же число, крайне мала.
Дальше по приватному ключу строится публичный ключ. После чего публичный ключ передается в качестве аргумента хеш-функции.
Криптографические хеш-функции используются при эмиссии коинов и в протоколах аутентификации сообщений. В данном конкретном случае хеш-функция применяется для генерации псевдослучайных цифр. Существуют различные функции для хеширования, мы рассмотрим SHA-256. Её особенность состоит в том, что она преобразует любой объем входящих данных в хеш размером 256 бит. Результат функции, он же хеш, по сути и является адресом нашего кошелька.
Необходимо сказать, что зная значение публичного ключа, вычислить по какому приватному ключу он был построен – трудно. Также количество возможных результатов работы функции равно 2256.
Валидация
Теперь при осуществлении перевода нам нужно подтвердить, что мы действительно являемся обладателями кошелька. Для этого в структуре транзакции появляется еще два поля:
- подпись;
- публичный ключ.
Шаг А: в поле публичного ключа записывается наш ключ.
Шаг Б: транзакция подписывается и подпись записывается в соответствующее поле.
Что же такое подпись? Схема создания подписи имеет следующий вид:
Собирается вся информация транзакции, т. е. inputs, tails и публичный ключ. Эта информация передается хеш-функции, а сам хеш и приватный ключ передаются алгоритму подписи. Этот алгоритм работает так, что с помощью публичного ключа можно выяснить действительно ли был использован верный приватный ключ, но сам приватный ключ опять узнать невозможно. На выходе получается строка весом 512 бит.
Общая структура транзакции выглядит так:
class Transaction
{
. . .
protected:
std::vector<Input> inputs;
std::vector<Tail> tails;
std::vector<uint8_t> pubKey = std::vector<uint8_t>(279, 0);
std::vector<uint8_t> signature = std::vector<uint8_t>(64, 0);
. . .
};
Блок
Теперь перейдем к блокам.
Блок – это объект, который хранит несколько транзакций и ссылку на предыдущий блок.
Созданием блока занимаются так называемые майнеры (о них ниже). Чтобы создать новый блок, нужно заполнить все поля и выполнить определенное условие. Схема создания блока выглядит следующим образом:
Иллюстрация показывает процесс, в результате которого появляется первый блок. Давайте рассмотрим его поподробнее.
- Первое, с чем сталкивается майнер – это проверка правильности транзакций, которые хотят осуществить участники сети. Он должен валидировать подписи и проверить, чтобы транзакции не ссылалаются на уже использованные tails. Стоит также отметить, что один блок может вместить в себя ограниченное количество информации, поэтому число транзакций для блока ограничено.
- После этого все проверенные транзакции передают аргументами хеш-функции. На выходе мы получаем один из элементов первого блока – Merkle Root.
- Данные, подаваемые на вторую функцию хеширования (см. картинку): хеш заголовка предыдущего блока; merkle root; транзакции; nonce (об этом поле чуть ниже).
- Все хешируется и происходит проверка хеша на "определенный вид". Например, осуществляется проверка, что последние 4 байта хеша являются нулями. Если это не так, то меняется поле nonce и майнер вновь пытается получить блок. Спрогнозировать результат работы хеш-функции невозможно, а значит майнеру нужно просто изменять аргумент nonce до тех пор, пока результат не примет нужный вид.
Таким образом, валидным блоком можно считать блок, который включает в себя хеш предыдущего блока, хеш всех попавших в блок транзакций, список самих транзакций и поля nonce, и хеш этого блока должен иметь определенный вид, который достигается путем внесения изменений в поле nonce.
Как только майнер подобрал нужное значение nonce, он может рассылать этот блок всем участникам сети и майнить следующий. Если же кто-то сделал этот блок раньше него, ему придется начинать работу уже с новым блоком.
Например, серия из 3 блоков будет выглядеть следующим образом:
Неизменность данных
Так как информация передается по сети, любой может ее изменить и передавать дальше ложную версию.
Устройство блокчейна нивелирует подобные попытки. Информацию внутри транзакций изменить нельзя, так как подпись станет невалидной. Информацию в самом блоке тоже изменить нельзя, так как хеш merkle root не будет соответствовать действительности и/или хеш самого блока не будет удовлетворять условиям. Таким образом добиваются неизменяемости информации.
Самая первая транзакция
У внимательного читателя может возникнуть вопрос, на что же ссылается первый блок? Специально для этого случая существует «генезис-блок» (Genesis block) с известными полями и алгоритмом генерации. Цепь считают легитимной, только если она начинается с генезис-блока и все содержащиеся в ней транзакции и блоки тоже легитимны.
В BaumankaCoin для простоты реализации нет генезис-блока. Вместо этого хеш первого блока имеет хеш предыдущего блока равный 0.
Разветвление
Любой майнер может добавить блок в блокчейн (при условии, что все проверки пройдены). И, так как блокчейн представляет из себя последовательность, блоки можно пронумеровать так называемой «высотой» — порядковым номером от генезис-блока с номером 0, однако эта высота не является уникальным идентификатором блока.
Каждый блок может ссылаться только на один предыдущий, но что произойдет в случае, если появится несколько блоков, которые ссылаются на один и тот же? У них будет одинаковая высота (именно поэтому высоту не используют в качестве идентификатора), а цепь станет больше похожа на дерево. Такое действительно происходит время от времени, когда майнеры создают блоки с разницей всего в несколько секунд (или если злоумышленники с большой вычислительной мощностью предприняли попытку откатить транзакции). Это называется «форком» (chainfork).
Каждый узел независимо выбирает, какой из этих блоков принять. Договорились останавливать выбор на первом полученном. Рано или поздно появится блок с большей высотой, тогда «основной» станет более длинная цепь, а форки постепенно забудут (как правило, дальше одного блока форки не растут).
Блоки из форков в одних источниках называют orphan, в других stale. На bitcoin.it, например, терминология меняется в разных статьях (1, 2). Мы придерживаемся названия «stale», т.к. оно ближе этимологически. Но стоит упомянуть и об orphan-блоках. Это такие блоки, информацией о предшественниках которых узел еще не владеет (т.е. не может проверить их подлинность). В последнее время мало клиентских приложений поддерживают работу с orphan-блоками, обычно их отклоняют.
Сеть
Блокчейн построен на одноранговой широковещательной сети, где каждый узел хранит полную копию всей цепи. Этого достаточно для поддержания основной функциональности (обмена блоками и транзакциями).
Например, в Биткойне это около 130GB на момент написания (актуальный размер тут).
Одна из основных проблем, с которой предстоит справиться для подключения нового узла к сети, – поиск остальных участников сети. Полностью децентрализованного решения в данный момент не существует. В Биткойне используется аналогия BitTorrent-трекера, называемая DNS Seed – сервер, который используется для помощи в поиске остальных пиров. Информация о таких серверах захардкожена.
BaumankaCoin для простоты хранит информацию об известных пирах в INFO-файле
Дальнейшее подключение ко всем известным пирам происходит одинаково и полностью децентрализовано. Давайте рассмотрим этот процесс более подробно и для определенности назовем новый узел A, а уже известный пир B. При подключении A посылает B version-сообщение такого содержания:
В BaumankaCoin урезано до
class version
{
public:
. . .
net_addr addr_recv;
net_addr addr_from;
uint32_t nonce;
};
B должен ответить пустым verack-сообщением и установить версию протокола на минимальную из своей и полученной от A, а также со своей стороны отправить version. Аналогичные действия проделывает A. Этот процесс называют «рукопожатием» (version handshake), и по его завершении узлы общаются по поддерживаемой ими обоими версии протокола.
Далее происходит обмен информацией об известных пирах. A запрашивает у B список известных ему активных пиров посредством getaddr-запроса, который ничего не содержит. Пир считается активным, если B получал от него сообщения в последнее время (в Биткойне – последние три часа). B отвечает addr-сообщением с информацией об узлах.
Inventory-сообщения
Когда кто-либо создает или получает транзакцию или блок, он отправляет inv-сообщение, содержащее несколько структур вида {тип объекта (блок/транзакция), хеш}, с информацией об этих объектах. Получатель отвечает getdata-сообщением с такими же, как у inv структурами, но перечисляя только новые для себя объекты. И только после этого происходит отправка сообщений с полной информацией о блоках (block) и транзакциях (tx).
Запрос в BaumankaCoin для простоты содержит только вышеупомянутый хеш последнего блока. В "неучебных" криптовалютах все немного сложнее.
После рукопожатия A по-прежнему ничего не известно об актуальности его блокчейна. Следовательно, эту информацию следует обновить. Для этого A посылает getblocks-запрос, содержащий хеш последнего известному ему блока.
Если B имеет более новые блоки, он отсылает их с предварительной отправкой inv-сообщений. Весь описанный процесс выглядит примерно следующим образом:
Заключение
Надеемся, эти простые объяснения непростых процессов помогли сформировать у читателя представление о технологии блокчейн. Одна из наиболее полезных статей, которая помогала нам на протяжении всей работы – Bitcoin in a Nutshell. А так же протоколы с bitcoin.org и bitcoin.it. Напоминаем, что исходники можно скачать здесь.
Автор: skalniy