Когда я читал статью про блокчейн на JavaScript, мне было интересно познакомиться с идеями о блокчейн-разработке, которые отличаются от тех, что мне уже известны. А как только я начал читать код, мне захотелось сопоставить его с аналогичным Python-кодом, чтобы ещё и разобраться с его отличиями от кода, написанного на JavaScript.

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



Несмотря на то, что исходная статья появилась на свет после того, как её автор ознакомился с примером блокчейн-разработки на Python, мне хотелось написать Python-код, который как можно более точно воспроизводит JavaScript-код из статьи. Это позволит сопоставить реализацию блокчейна на разных языках.

Я, кроме того, собираюсь сделать так, чтобы моя реализация блокчейна, как и в статье про JavaScript, тоже поместилась бы в 60 строк.

О блокчейне


Хотя я собирался повторить структуру того материала, чтобы тем же путём, что и его автор, прийти к готовому коду, я, всё же, включу сюда и кое-что своё. В частности, я предпочитаю другое определение блокчейна. Оно звучит так: «Блокчейн — это система регистрации информации, выполняемой таким способом, который усложняет или делает невозможным изменение информации, взлом системы или мошенничество с информацией».

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


В этом проекте мы будем использовать Python, поэтому, если он у вас не установлен — найдите дистрибутив, подходящий для вашей ОС, и установите его.

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


Блок — это объект, в котором имеется некая информация. Поэтому начнём работу с создания класса Block:

class Block:

    def __init__(self, timestamp=None, data=None):
        self.timestamp = timestamp or time()
        # В this.data должна храниться информация, вроде сведений о транзакциях.
        self.data = [] if data is None else data

Определения класса Block в Python и JavaScript получились очень похожими. В Python вместо this используется self, а аналогом метода constructor является init.

Комментарии тоже выполняются похожим образом. В Python для оформления однострочных комментариев применяется символ #, а в JavaScript — конструкция //.

Реализацию алгоритма sha256 я взял из библиотеки hashlib. В JS-проекте она берётся из пакета crypto.

from hashlib import sha256

class Block:

    def __init__(self, timestamp=None, data=None):
        self.timestamp = timestamp or time()
        self.data = [] if data is None else data
        self.hash = self.getHash()
        self.prevHash = None # Хеш предыдущего блока

    def getHash(self):
        hash = sha256()
        hash.update(str(self.prevHash).encode('utf-8'))
        hash.update(str(self.timestamp).encode('utf-8'))
        hash.update(str(self.data).encode('utf-8'))
        return hash.hexdigest()

В методе getHash всё начинается с пустого хеша, который мы формируем с использованием данных, хранящихся в блоке. Хеш вычисляется на основе хеша предыдущего блока, отметки времени и данных, хранящихся в блоке. Всё это преобразуется в последовательности байтов с помощью метода .encode('utf-8').

Блокчейн


Займёмся классом Blockchain.

class Blockchain:
    def __init__(self):
        # В этом свойстве будут содержаться все блоки.
        self.chain = []

Классы, представляющие собой блокчейн, похожи в обоих языках.

Для того чтобы создать первичный блок, мы просто вызываем Block с текущей отметкой времени, для получения которой используем time(). Для этого нам нужно импортировать библиотеку time.

Преобразование числа в строку выполняется в Python с помощью функции str(), а не с помощью метода toString(), как делается в JavaScript.

from time import time

class Blockchain:
    def __init__(self):
        # Создаём первичный блок
        self.chain = [Block(str(int(time())))]

Похоже выглядит и метод для получения самого свежего блока. Только тут, в отличие от JavaScript-проекта, для выяснения длины цепочки блоков, вместо свойства length используется функция len().

    def getLastBlock(self):
        return self.chain[len(self.chain) - 1]

Для того чтобы добавить блок в блокчейн, мы просто вызываем метод addBlock. Код получился почти таким же, как в JS-проекте, только тут, вместо метода push(), используется метод append().

def addBlock(self, block):
        # Так как мы добавляем новый блок, prevHash будет хешем предыдущего последнего блока.
        block.prevHash = self.getLastBlock().hash
        # Так как теперь в prevHash имеется значение, мы должны пересчитать хеш блока.
        block.hash = block.getHash()
        self.chain.append(block)

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


В методе, используемом для проверки блокчейна, мы пользуемся функцией range(). В этом — серьёзное отличие нашего кода от кода JS-проекта. И, кроме того, так как мы в Python не пользуемся константами, тут мы просто применяем обычные переменные.

При проверке условий в Python, вместо ||, используется or.

def isValid(self):
    # Перед перебором цепочки блоков нужно установить i в 1, так как до первичного блока никаких блоков нет. В результате мы начинаем со второго блока.
    for i in range(1, len(self.chain)):
        currentBlock = self.chain[i]
        prevBlock = self.chain[i - 1]

        # Проверка
        if (currentBlock.hash != currentBlock.getHash() or prevBlock hash != currentBlock.prevHash):
            return False

    return True

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


Реализовать алгоритм доказательства выполнения работы мы можем, начав с добавления в класс Block метода mine() и свойства nonce. Тут стоит проявить внимательность, так как свойство nonce должно быть объявлено до вызова метода self.getHash(). В противном случае будет выдана ошибка AttributeError: 'Block' object has no attribute 'nonce'.

class Block:

    def __init__(self, timestamp=None, data=None):
        self.timestamp = timestamp or time()
        self.data = [] if data is None else data
        self.prevHash = None # хеш предыдущего блока
        self.nonce = 0
        self.hash = self.getHash()

    # Наша хеш-функция.
    def getHash(self):

        hash = sha256()
        hash.update(str(self.prevHash).encode('utf-8'))
        hash.update(str(self.timestamp).encode('utf-8'))
        hash.update(str(self.data).encode('utf-8'))
        hash.update(str(self.nonce).encode('utf-8'))
        return hash.hexdigest()

    def mine(self, difficulty):
        # Тут запускается цикл, работающий до тех пор, пока хеш не будет начинаться со строки
        # 0...000 длины <difficulty>.
        while self.hash[:difficulty] != '0' * difficulty:
            # Инкрементируем nonce, что позволяет получить совершенно новый хеш.
            self.nonce += 1
            # Пересчитываем хеш блока с учётом нового значения nonce.
            self.hash = self.getHash()

Создадим свойство, в котором будет храниться сложность:

self.difficulty = 1

Отредактируем метод addBlock:

    def addBlock(self, block):
        block.prevHash = self.getLastBlock().hash
        block.hash = block.getHash()
        block.mine(self.difficulty)
        self.chain.append(block)

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


Импортируем модуль и воспользуемся классом Blockchain так же, как таким же классом в JS-проекте:

from blockchain import Block
from blockchain import Blockchain
from time import time

JeChain = Blockchain()

# Добавим новый блок
JeChain.addBlock(Block(str(int(time())), ({"from": "John", "to": "Bob", "amount": 100})))
# (Это - всего лишь интересный эксперимент, для создания настоящей криптовалюты обычно нужно сделать намного больше, чем сделали мы).

# Вывод обновлённого блокчейна
print(JeChain)

Результаты работы этого кода должны выглядеть примерно так:

[
    {
        "data": [],
        "timestamp": "1636153236",
        "nonce": 0,
        "hash": "4caa5f684eb3871cb0eea217a6d043896b3775f047e699d92bd29d0285541678",
        "prevHash": null
    },
    {
        "data": {
            "from": "John",
            "to": "Bob",
            "amount": 100
        },
        "timestamp": "1636153236",
        "nonce": 14,
        "hash": "038f82c6e6605acfcad4ade04e454eaa1cfa3d17f8c2980f1ee474eefb9613e9",
        "prevHash": "4caa5f684eb3871cb0eea217a6d043896b3775f047e699d92bd29d0285541678"
    }
]

Но заработает это всё только после добавления в класс Blockchain метода __repr__():

import json

    def __repr__(self):
        return json.dumps([{'data': item.data, 'timestamp': item.timestamp, 'nonce': item.nonce, 'hash': item.hash, 'prevHash': item.prevHash} for item in self.chain], indent=4)

Дополнение: сложность и время блока


Для настройки времени блока нам понадобится соответствующее свойство:

self.blockTime = 30000

Посмотрим на тернарный оператор, используемый в системе настройки сложности. Если переписать JS-конструкцию с тернарным оператором на Python, то получится следующее: (if_test_is_false, if_test_is_true)[test]:

def addBlock(self, block):
        block.prevHash = self.getLastBlock().hash
        block.hash = block.getHash()
        block.mine(self.difficulty)
        self.chain.append(block)

        self.difficulty += (-1, 1)[int(time()) - int(self.getLastBlock().timestamp) < self.blockTime]

Итоговый Python-код (без нормального форматирования) укладывается в обещанные 60 строк:

# -*- coding: utf-8 -*-

from hashlib import sha256
import json
from time import time

class Block:

    def __init__(self, timestamp=None, data=None):
        self.timestamp = timestamp or time()
        self.data = [] if data is None else data
        self.prevHash = None
        self.nonce = 0
        self.hash = self.getHash()

    def getHash(self):

        hash = sha256()
        hash.update(str(self.prevHash).encode('utf-8'))
        hash.update(str(self.timestamp).encode('utf-8'))
        hash.update(str(self.data).encode('utf-8'))
        hash.update(str(self.nonce).encode('utf-8'))
        return hash.hexdigest()

    def mine(self, difficulty):
        while self.hash[:difficulty] != '0' * difficulty:
            self.nonce += 1
            self.hash = self.getHash()

class Blockchain:

    def __init__(self):
        self.chain = [Block(str(int(time())))]
        self.difficulty = 1
        self.blockTime = 30000

    def getLastBlock(self):
        return self.chain[len(self.chain) - 1]

    def addBlock(self, block):
        block.prevHash = self.getLastBlock().hash
        block.hash = block.getHash()
        block.mine(self.difficulty)
        self.chain.append(block)

        self.difficulty += (-1, 1)[int(time()) - int(self.getLastBlock().timestamp) < self.blockTime]

    def isValid(self):
        for i in range(1, len(self.chain)):
            currentBlock = self.chain[i]
            prevBlock = self.chain[i - 1]

            if (currentBlock.hash != currentBlock.getHash() or prevBlock.hash != currentBlock.prevHash):
                return False

        return True

    def __repr__(self):
        return json.dumps([{'data': item.data, 'timestamp': item.timestamp, 'nonce': item.nonce, 'hash': item.hash, 'prevHash': item.prevHash} for item in self.chain], indent=4)

Надеюсь, вам понравились оба материала, и вы нашли в них что-то полезное.

Если бы вам понадобилось создать блокчейн-систему — какими инструментами вы воспользовались бы?

Комментарии (10)


  1. lizergil
    17.11.2021 16:49
    -5

    Вот вам сравнение, весь JS-код можно оформить в виде одной строки, а Python нет. Шах и мат.


    1. MAXH0
      17.11.2021 20:29

      \n отменили?


      1. kAIST
        18.11.2021 15:06

        Ну может у человека клавиша enter сломалась, а программировать хочется.


        1. MAXH0
          19.11.2021 06:49

          Так и в Python ничего не мешает. Не очень спортивно однострочники через \n писать, но если прижало, то можно...


          1. kAIST
            19.11.2021 12:30

            Не обязательно ;) https://habr.com/ru/post/588955/


  1. dark_ruby
    17.11.2021 17:06
    +2

    Тоже пилю блокчейн с нуля, но на Rust, не продакшена ради, а изучения языка для!


    1. the_mix
      29.11.2021 14:29

      ✋????тоже как раз собирался попрактиковаться в rust с аналогичной задачей, поделитесь ссылочкой на репу?:)


  1. Naikras
    17.11.2021 20:22
    +3

    Так, бог троицу любит. Надо кому-нибудь мой блокчейн на С#?))


    1. Reformat
      18.11.2021 06:31

      Реквестую, пишите статью!)


  1. MAXH0
    17.11.2021 20:52
    +1

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

    Поэтому >> "Если бы понадобилось создать блокчейн-систему" << то я бы дал волю своей паранойе. Например, вычислял бы хэш-функцию по двум разным алгоритмам.