Как стать автором
Обновить
2550.69
Рейтинг
RUVDS.com
VDS/VPS-хостинг. Скидка 10% по коду HABR10

Блокчейн на JavaScript

Блог компании RUVDS.com Децентрализованные сети Разработка веб-сайтов *JavaScript *
Перевод
Автор оригинала: Phu Minh
В последнее время криптовалюты и блокчейн-технологии стали невероятно популярными. Сегодня я расскажу о моём подходе к созданию блокчейн-платформы на JavaScript с использованием всего 60 строк кода. Я — начинающий блокчейн-разработчик, поэтому если я в чём-то ошибаюсь — поправьте меня в комментариях.



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


Прежде чем мы займёмся программированием — надо разобраться с тем, что такое блокчейн (blockchain, цепочка взаимосвязанных блоков). С технической точки зрения, в минимальном представлении, это всего лишь список объектов, содержащих некую информацию — вроде отметки времени, сведений о транзакциях, хешей. Эти данные должны быть иммутабельными и надёжно защищёнными от взлома. Современные платформы, вроде Ethereum, Cardano, Polkadot, обладают гораздо более широкими возможностями, чем просто хранение неких данных, но мы тут будем стремиться к простоте.

Подготовка среды разработки


Для этого проекта мы будем использовать Node.js, поэтому вам, если у вас эта платформа не установлена, понадобится её установить.

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

Создание блока


Как я уже сказал, блок — это просто объект, в котором хранится какая-то информация. Нам, следовательно, понадобится класс Block:

class Block {
    constructor(timestamp = "", data = []) {
        this.timestamp = timestamp;
        // в this.data должна содержаться информация наподобие сведений о транзакциях.
        this.data = data;
    }
}

Итак, у нашего объекта будут поля timestamp и data, но он ещё нуждается в иммутабельности. Добиться этого можно, используя хеш-функцию, с помощью которой обрабатывают все остальные свойства блока. Советую вам почитать о хеш-функциях в Википедии, так как они играют ключевую роль в блокчейн-разработке. В целом их работу можно описать так: они принимают сообщения и выводят «хешированные» сообщения фиксированной длины. Даже небольшое изменение входных данных приведёт к появлению совершенно других выходных данных.

Я применяю алгоритм sha256. Для того чтобы реализовать хеширующую функцию — я собираюсь просто воспользоваться возможностями стандартного Node.js-пакета crypto:

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 текущего блока. Сейчас, как видите, это свойство не содержит никакого значения, но мы ещё к этому вернёмся.

Блокчейн


Займёмся теперь классом Blockchain.

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

class Blockchain {
    constructor() {
        // В этом свойстве будут содержаться все блоки.
        this.chain = [];
    }
}

Ещё нам необходим так называемый первичный блок (genesis block), который, с технической точки зрения, представляет собой всего лишь самый первый блок:

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;
    }

Алгоритм доказательства выполнения работы


Оказывается, что система поддержания иммутабельности блоков, основанная на полях hash и prevHash, имеет определённые недостатки. Так, кто-то может модифицировать некий блок и пересчитать хеши всех следующих блоков для получения вполне корректной цепочки блоков. Кроме того, нам хотелось бы реализовать некий механизм, позволяющий пользователям приходить к консенсусу по поводу единой хронологической истории цепочки блоков, расположенных в правильном порядке, соответствующем порядку выполнения транзакций. Bitcoin и многие другие криптовалюты включают в себя системы, основанные на алгоритме доказательства выполнения работы (Proof-of-Work, PoW), направленные на решение этой проблемы.

Подобная система направлена на то, чтобы значительно увеличить объём работ, необходимых для создания нового блока. Если нужно модифицировать некий блок — понадобится выполнить работу, необходимую для создания этого блока и всех блоков, которые идут за ним. Это требует поиска значений, которые, после хеширования, дают результат, начинающийся с определённого количества нулевых битов. Речь идёт о так называемом значении nonce (number that can only be used once — число, которое может быть использовано один раз), а количество нулевых битов в начале такого числа известно как «сложность». По мере увеличения сложности задача добычи (майнинга, mining) нового блока становится всё сложнее и сложнее. Это позволяет предотвратить модификацию предыдущих блоков, так как тому, кто хотел бы модифицировать некий блок, пришлось бы выполнить работу, необходимую для создания этого блока и всех блоков, которые идут за ним. А это практически невозможно.

Реализовать эту систему можно, добавив в класс 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 до тех пор, пока хеш не окажется таким, как нам нужно.

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

Вернёмся к классу Blockchain и создадим в нём свойство для хранения сложности — difficulty:

    this.difficulty = 1;

В него записано число 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 секундам.

▍Формула сложности Bitcoin


Сложность Bitcoin меняется после добычи каждых 2016 блоков. Для вычисления сложности используется следующая формула:

старая сложность * (2016 блоков * 10 минут) / время добычи предыдущих 2016 блоков

Теперь займёмся программированием!

Для начала нужно задать время блока. Я использовал тут значение 30 секунд, то есть — 30000 миллисекунд. Я использую именно миллисекунды, так как такое выражение времени лучше подходит для работы с 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;
    }

▍Важное примечание


Учитывая то, как ранее мы проверяли сложность, эта система должна работать нормально. Но лучше проверять сложность с использованием log16(difficulty), а не самого значения сложности. Мы, поступая так, теперь можем воспользоваться формулой настройки сложности Bitcoin.

Можете, правда, создать и собственную формулу. Но при этом нужно ориентироваться на безопасность и на хорошую производительность решения.

Итоги


Вот исходный код моего проекта на GitHub.

Хочу сказать, что многое узнал о блокчейне из материалов YouTube-канала Simply Explained. Я не написал бы эту статью, если бы не серия учебных видео по блокчейну с этого канала. Я, кроме того, почерпнул кое-что из этой статьи. Рекомендую эти материалы всем, кому интересна тема блокчейна.

Доводилось ли вам пользоваться блокчейн-технологиями?

Теги:
Хабы:
Всего голосов 41: ↑41 и ↓0 +41
Просмотры 10K
Комментарии Комментарии 11

Информация

Дата основания
Местоположение
Россия
Сайт
ruvds.com
Численность
11–30 человек
Дата регистрации
Представитель
ruvds