Блокчейн на JavaScript

Блокчейн на JavaScript


Что такое блокчейн

Блокчейн - это цепочка объектов, каждый из которых содержит информацию, хеш и хеш предыдущего блока.

Выглядит это примерно так:

[ { hash: '9rffwefafe...', data: 'Hi', prevHash: '98qw67ft' } ]
[ { hash: '9r3aa988f...', data: 'Hello', prevHash: '9rffwefafe'  } ]

Хеш - это уникальный идентификатор каждого блока. Обычно это hex строка, но для идентификации можно выбрать что угодно.

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

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

Перед тем как кодить

В этом туториале я буду использовать crypto.js, чтобы не заморачиваться с функцией хеша.

Его можно установить через npm:

npm i crypto-js

Или просто включить как скрипт:

<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js"></script>

Так как мы будем использовать только одну функцию, то импортнём только SHA512:

// ESM
import { SHA512 } from 'crypto-js'
// UMD
const { SHA512 } = CryptoJS

Класс блока

Для понятности мы будем описывать блокчейн как классы.

У блока есть:

• время майнинга

• хеш

• хеш предыдущего блока

• данные

Время вычисляется простым увеличением на 1 в течение цикла майнинга, то есть во время майнинга блока:

while (this.hash.slice(0, dif) !== Array(dif + 1).join('0')) {
  this.uptime++ // Counting time...
  this.hash = this.calcHash()
}

Хеш тоже вычисляется в процессе майнинга, хеш предыдущего блока ставится в самом блокчейне. Данные мы задаём сами.

Поняв как работает блок, можно начать писать класс блока.

Добавим все те свойства, которые я перечислил ранее:

class Block {
 constructor(data, prevHash = '') {
   this.hash = this.calcHash()
   this.data = data
   this.prevHash = prevHash
   this.uptime = 0
 }

prevHash = '' пригодится для genesis блока, т.е. самого первого блока в цепи, у которого очевидно нет хеша предыдущего блока.

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

const salt = () => {
 let txt = ''
 let alph = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
 for (let i = 0; i < 8; i++) txt += alph.charAt(Math.floor(Math.random() * alph.length))
 return txt
}

Также чтобы легко было отслеживать изменения в блокчейне, создадим массив mined, в котором будут появляться сообщения. Переменная будет объявлена в самом начале кода:

let mined = []

Теперь можно начать писать функцию для вычисления хеша.

Вычисление хеша

Хеш вычисляется по свойствам блока, т.е. мы просто кладём все свойства , получаем строку, например ('Hello000') и затем у этой строки появляется идентификатор, то есть хеш.

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

calcHash() {
   return SHA512(this.data + this.prevHash + this.uptime + salt()).toString()
 }

Майнинг блока

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

 mineBlock(dif) {
   mined.push('Mining...')
   // Calculate hash till "0" * diff
   while (this.hash.slice(0, dif) !== Array(dif + 1).join('0')) {
     this.uptime++
     this.hash = this.calcHash()
   }
   // Push calculated hash to array
   mined.push(`Block mined: ${this.hash.slice(0, 10)}`)
 }
}

Всё, класс блока готов. Протестировать его можно таким образом:

const block = new Block('hi')
const newBlock = new Block('hello', block.hash) // prevHash = block.hash
block.mineBlock(2)
console.log(mined)

На выводе мы получим что-то такое:

["Mining...", "Block mined: 00c313abc969fbca706b5b5595adf73a609aa2e634954c5ef17903ec44bb511879c12592d634dcd120309a4ab7c1794b7344d6fc4792ede09b277f9203f1e086"]

Класс блокчейна

Блокчейн нужен для связывания блоков между собой путём выставления хешей, майнингов каждого блока и т.д.

Единственное, что мы задаём у класса в конструкторе, это сложность майнинга.

class Blockchain {
 constructor(dif) {
   this.dif = dif
   this.chain = [new Block('Chain started.')]
 }

Свойство chain и есть наш блокчейн. Он преставляет из себя массив блоков. В самом начале находится genesis блок, он начинает цепь, и также у него нет хеша предыдущего блока.

Добавление блоков

Нам нужно как-то добавлять новые блоки в цепь, для этого создадим метод addBlock:

 addBlock(block) {
   const chain = this.chain
   block.prevHash = chain[chain.length - 1].hash
   block.mineBlock(this.dif)
   chain.push(block)
 }

Здесь мы берём хеш одного блока и ставим свойство prevHash на следующем.

Валидация блокчейна

Последнее, что осталось в классе блокчейна, это проверка его валидности.

isValid() {
   let chain = this.chain
   if (chain.length <= 1) return true
   for (let i = 1; i < chain.length; i++) {
     if (chain[i].prevHash !== chain[i - 1].hash) return false
     else return true
   }
 }
}

Если длина цепочки меньше или равна нулю, то это значит что пока что кроме genesis блока новых не добавлялось.

Далее мы проходимся по каждому блоку, сравниваем хеш с предыдущим блоком. Если они не совпадают, кидаем false. Если всё ок, то возвращаем true.

Блокчейн готов. Теперь давайте протестируем его.

Тест блокчейна

Создадим новый блокчейн через класс, зададим сложность на 3 и добавим пару блоков:

const chain = new Blockchain(3)
chain.addBlock(new Block('some data'))
chain.addBlock(new Block('hello'))

Проверим валидность

chain.isValid() // true

Посмотрим что находится в mined, здесь нет genesis блока потому что мы его не майнили:

Mining...
Block mined: 00045b555e
Mining...
Block mined: 00034066b6

в mined находится укороченная версия хеша, чтобы посмотреть весь хеш, возьмём отдельный блок

chain.chain[1].hash
// "00034066b6
2e460f39fede61199534a65ae3536a1733f20cfe24ffb82f11f4fad278309f1ed5a8cda0cc7e31181424a1ef79dfd8ba66ffad16874c94c8d660c0"

Попробуем сломать блокчейн.

chain.chain[1].hash = 'blah blah'
chain.isValid() // false

Заключение

Блокчейн целиком и полностью рабочий, но он очень базовый. В нём нет Proof-of-Stake, счёта "валюты" и т.д.

Демо с интерфейсом можно посмотреть здесь.



Report Page