Як Створити Блокчейн На JavaScript

Останнім часом криптовалюта і технологія блокчейн стали неймовірно популярними. Сьогодні я розповім про свій підхід до створення блокчейн-платформи в JavaScript з використанням всього 60 рядків коду. Я початківець блокчейн-розробник, тому, якщо я в чомусь помиляюся, виправте мене в коментарях.

Що таке технологія Blockchain?

Перш ніж зайнятися програмуванням, нам потрібно зрозуміти, що таке блокчейн (блокчейн, ланцюжок взаємопов’язаних блоків). З технічної точки зору, в мінімальному вигляді, це всього лише список об’єктів, що містять деяку інформацію – на зразок тимчасової позначки, інформації про транзакції, хешів. Ці дані повинні бути незмінними і надійно захищеними від злому. Сучасні платформи, такі як Ethereum, Cardano, Polkadot, мають набагато більше можливостей, ніж просто зберігання деяких даних, але тут ми будемо прагнути до простоти.

Підготуйте своє середовище розробки

Для цього проекту ми будемо використовувати Node.js, тому якщо у вас не встановлена ця платформа, вам потрібно буде її встановити.

Я використовую об’єктно-орієнтований стиль програмування, тому очікую, що читач матиме хоча б базові знання про це.

Створення блоку

Як я вже говорив, блок – це просто об’єкт, в якому зберігається якась інформація. Тому нам знадобиться клас Block:

classBlock {
     constructor(timestamp = "", data = []) {
         this.timestamp = timestamp;
         // в this.data повинен містити інформацію, подібну до інформації про транзакції.         
this.data = data;
     }
 }

Отже, наш об’єкт матиме поля, але він все одно потребує незмінності. Домогтися цього можна за допомогою хеш-функції, за допомогою якої обробляються всі інші властивості блоку. Раджу прочитати про хеш-функції, оскільки вони відіграють ключову роль у розробці блокчейну. В цілому їх роботу можна описати так: вони отримують повідомлення і виводять «хешовані» повідомлення фіксованої довжини. Навіть невелика зміна вхідних даних призведе до зовсім інших виходів.

Я використовую алгоритм sha256. Для того, щоб реалізувати функцію хешування, я просто збираюся використовувати можливості стандартного пакета Node.js.

const crypto = require("crypto"), SHA256 = message => crypto.createHash("sha256").update(message).digest("hex");

Вищевказаний код дасть нам те, що нам потрібно. Але якщо ви хочете зрозуміти, як саме це працює, загляньте в офіційний Node.js документацію для класу Hash.

На даному етапі роботи ми повинні отримати щось на зразок наступного коду:

// Підключимо хеш функцію sha256.
const crypto = require("crypto"), SHA256 = message => crypto.createHash("sha256").update(message).digest("hex");

class Block {
    constructor(timestamp = "", data = []) {
        this.timestamp = timestamp;
        this.data = data;
        this.hash = this.getHash();
        this.prevHash = ""; // хеш попереднього блоку 
    }

    // Наша хеш-функція.
    getHash() {
        return SHA256(this.prevHash + this.timestamp + JSON.stringify(this.data));
    }
}

Всякий раз, коли щось у вмісті блоку змінюється, хеш-функція видає результат, абсолютно відмінний від попереднього. Це допоможе нам у забезпеченні незмінності блоків.

Властивість prevHash також відіграє велику роль у забезпеченні незмінного характеру блоків. Це дозволяє гарантувати, що блок залишається незмінним до тих пір, поки існує блокчейн, що містить цей блок. У ньому міститься хеш попереднього блоку, тому можна гарантувати незмінний характер цього блоку, так як навіть невелика зміна його вмісту змінить результати виклику функції getHash поточного блоку. Зараз, як бачите, ніякої цінності ця властивість не містить, але до цього ми ще повернемося.

timestampdatasha256crypto

prevHashgetHash

Блокчейн

Давайте зараз розберемося з класом.

Як я вже сказав, блокчейн – це список блоків. Тому найпростіший блокчейн можна описати за допомогою наступного класу.

Нам також потрібен так званий блок genesis, який, з технічної точки зору, є лише найпершим блоком:

А я для зручності створив функцію, яка виробляє останній блок:

Тепер нам потрібен механізм додавання блоків в блокчейн: Blockchain

class Blockchain {
    constructor() {
        // Тут знаходитимуться всі блоки.
        this.chain = [];
    }
}
class Blockchain {
    constructor() {
        // создаємо перший блок
        this.chain = [new Block(Date.now().toString())];
    }
}
    getLastBlock() {
        return this.chain[this.chain.length - 1];
    }
    addBlock(block) {
        // Коли ми додаємо новий блок, prevHash буде хешем попереднього останнього блоку
        block.prevHash = this.getLastBlock().hash;
        // оскільки тепер в prevHash є значення, потрібно перерахувати хеш блоку
        block.hash = block.getHash();
        this.chain.push(block);
    }

Перевірка блокчейну

Ми повинні знати, чи правильні дані, що зберігаються на блокчейні. Тому нам потрібен метод, який перевіряє хеші блоків. Блокчейн правильний, якщо хеші блоків дорівнюють тому, що повертає хеш-метод, і якщо властивість блоку дорівнює хешу попереднього блоку. prevHash

  isValid() {
        // Перед тим як ітерувати через ланцюжок блоків, потрібно встановити i на 1, так як перед первинним блоком блоків немає. У підсумку починаємо з другого блоку.
        for (let i = 1; i < this.chain.length; i++) {
            const currentBlock = this.chain[i];
            const prevBlock = this.chain[i-1];

            // Перевірка 
            if (currentBlock.hash !== currentBlock.getHash() || prevBlock.hash !== currentBlock.prevHash) {
                return false;
            }
        }

        return true;
    }

Алгоритм доказу роботи

Виходить, що система підтримки незмінного характеру блоків, заснована на полях і , має певні недоліки. Так, хтось може модифікувати певний блок і перерахувати хеші всіх наступних блоків для отримання абсолютно коректної ланцюжка блоків. Крім того, ми хотіли б реалізувати певний механізм, що дозволяє користувачам прийти до єдиної хронологічної історії ланцюжка блоків, розташованої в правильному порядку, відповідному порядку, в якому виконуються транзакції. Біткойн і багато інших криптовалют включають системи, засновані на алгоритмі Proof-of-Work (PoW), спрямованому на вирішення цієї проблеми.

Така система спрямована на значне збільшення обсягу робіт, необхідних для створення нового агрегату. Якщо вам потрібно змінити блок, вам потрібно буде виконати роботу, необхідну для створення цього блоку і всіх блоків, які слідують за ним. Для цього потрібно шукати значення, які після хешування дають результат, починаючи з певної кількості нульових бітів. Йдеться про так зване значення (число, яке можна використовувати тільки один раз), а кількість нульових бітів на початку такого числа відоме як «складність». У міру збільшення складності завдання видобутку (майнінгу, mining) нового блоку все більше ускладнюється. Це запобігає модифікації попередніх блоків, оскільки той, хто хотів би змінити блок, повинен буде виконати роботу, необхідну для створення цього блоку та всіх блоків, які слідують за ним. А це практично неможливо.

Ви можете реалізувати цю систему, додавши метод і властивість в клас:

Оскільки навіть невелика зміна даних блоку призводить до появи абсолютно нового хешу, ми просто збільшуємо знову і знову, поки хеш не стане таким, як нам потрібно.

(Майте на увазі, що bitcoin та інші блокчейн-системи використовують інший спосіб налаштування складності, але ми тут, як уже згадувалося, прагнемо до простоти.)

Повернемося до класу і створимо в ньому властивість для зберігання складності – :

В ньому написано число 1, воно повинно змінюватися в залежності від кількості видобутих блоків.

Нам також потрібно відредагувати код методу класів:

Тепер кожен блок, перш ніж його можна буде додати до блокчейну, повинен бути видобутий. hash prevHash nonce Block mine nonce

class Block {
    constructor(timestamp = "", data = []) {
        this.timestamp = timestamp;
        this.data = data;
        this.hash = this.getHash();
        this.prevHash = ""; // хеш попереднього блоку
        this.nonce = 0;
    }

    // Наша хеш-функція.
    getHash() {
        return SHA256(this.prevHash + this.timestamp + JSON.stringify(this.data) + this.nonce);
    }

    mine(difficulty) {
        // Тут запускается цикл, работающий до тех пор, пока хеш не будет начинаться со строки 
        // 0...000 длины <difficulty>.
        while (!this.hash.startsWith(Array(difficulty + 1).join("0"))) {
            // Инкрементируем nonce, что позволяет получить совершенно новый хеш.
            this.nonce++;
            // Пересчитываем хеш блока с учётом нового значения nonce.
            this.hash = this.getHash();
        }
    }
}

nonce Blockchain difficulty

    this.difficulty = 1;

addBlock Blockchain

    addBlock(block) {
        block.prevHash = this.getLastBlock().hash;
        block.hash = block.getHash();
        block.mine(this.difficulty);
        this.chain.push(block);
    }

Примітка

Я використовував систему PoW для цього блокчейну через прагнення до простоти. Більшість сучасних блокчейнів використовують набагато кращу систему, засновану на механізмі консенсусу Proof-of-Stake (PoS), або одній з багатьох передових систем такого типу.

Тестування блокчейну!

Створіть новий файл index.js, він буде виступати в якості точки входу.

Давайте скористаємося новоствореним блокчейном. Я назвав його .JeChain

По-перше, експортуйте необхідні класи:

module.exports = { Block, Blockchain };

Імпортуйте їх і переходьте до тесту:

Імпортуйте їх і переходьте до тесту:index.js

const { Block, Blockchain } = require("./your-blockchain-file.js");

const JeChain = new Blockchain();
// Додамо новий блок
JeChain.addBlock(new Block(Date.now().toString(), { from: "John", to: "Bob", amount: 100 }));
// (Це всього лише цікавий експеримент, для створення справжньої криптовалюти зазвичай потрібно зробити набагато більше).

// Вивід оновленого блокчейну
console.log(JeChain.chain);

Запуск системи повинен виглядати приблизно так:

Доповнення: Складність і час блоку

Block time – це постійна величина, яка представляє приблизний час, необхідний для додавання блоку в блокчейн. Платформа Bitcoin, наприклад, має час блоку 10 хвилин, а час блоку Ethereum – 13 секунд.

Складність біткойна змінюється після майнінгу кожного2016 блоку. Для розрахунку складності використовують наступну формулу:

стара складність * (2016 блоків * 10 хвилин) / час майнінгу попередніх 2016 блоків 

Тепер приступимо до програмування!

По-перше, потрібно виставити час блоку. Тут я використовував значення 30 секунд, яке становить 30 000 мілісекунд. Я використовую мілісекунди, тому що цей вираз часу краще підходить для роботи з  Date.now().

 blockTime = 30000;

(Зараз ми працюємо з кодом класу Blockchain.)

Просто як приклад створю власну систему: складність збільшиться на 1, якщо час блоку буде менше фактичного часу, за яке блок був видобутий блок. В іншому випадку вона зменшиться на 1.

   addBlock(block) {
        block.prevHash = this.getLastBlock().hash;
        block.hash = block.getHash();
        block.mine(this.difficulty);
        this.chain.push(block);

        this.difficulty += Date.now() - parseInt(this.getLastBlock().timestamp) < this.blockTime ? 1 : -1;
    }

▍Важливо

З огляду на те, як ми перевіряли складність у минулому, ця система повинна працювати нормально. Але краще перевірити складність за допомогою , а не самого значення складності. Таким чином, тепер ми можемо скористатися формулою коригування складності Bitcoin.

Однак можна створити власну формулу. Але при цьому потрібно орієнтуватися на безпеку і на хорошу продуктивність рішення. log16(difficulty)