Основы

В этом первом разделе этого руководства мы запачкаем руки, написав нашу первую цепочку блоков. Но прежде чем углубляться в код, я думаю, нам важно иметь общее представление о том, что такое блокчейн. Здесь я буду использовать определение Википедии.

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

Подробнее о блокчейне можно прочитать здесь.

Создание нашего первого блокчейна на Python

Блок

Начнем с создания блока с помощью объектно-ориентированного программирования. Здесь мы создаем класс с именем Block:

# node/block.py
class Block(object):
    def __init__(self):
        pass

Пользователь будет создавать экземпляры Block следующим образом:

block_1 = Block()
block_2 = Block()
block_3 = Block()

Разве это не красиво?

Цепочка блоков

Теперь давайте соединим их вместе, очень похоже на связанный список.

# node/block.py
class Block(object):
    def __init__(self, previous_block=None):
        self.previous_block = previous_block
# Example
block_0 = Block()
block_1 = Block(previous_block=block_0)
block_2 = Block(previous_block=block_1)

Хотя это невероятно красивый фрагмент кода, эта цепочка блоков не является блокчейном, потому что наши блоки не связаны друг с другом с помощью криптографии.

Блокчейн

Глядя на наше определение в Википедии, мы видим, что для того, чтобы быть блокчейном, каждый блок должен содержать 3 вещи:

  • криптографический хэш предыдущего блока
  • временная метка
  • данные транзакции

Мы ленивы, поэтому давайте начнем с самой простой задачи: отметки времени.

# node/block.py
from datetime import datetime


class Block(object):
    def __init__(self, timestamp: float, previous_block=None):
        self.timestamp = timestamp
        self.previous_block = previous_block

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

  1. Альберт платит Бертрану 30 сыров.
  2. Альберт платит Камилле 10 сыров.
  3. Бертран платит Камилле 5 сыров.

Давайте добавим часть данных транзакции в атрибуты нашего блока.

# node/block.py

class Block:
    def __init__(self, timestamp: float, transaction_data: str, previous_block=None):
        self.transaction_data = transaction_data
        self.timestamp = timestamp
        self.previous_block = previous_block

Здесь наши данные транзакции будут содержать имя отправителя и получателя, а также количество отправленного сыра. У нас будут эти 3 части информации, хранящиеся в виде простого текста в нашем блочном объекте.

# Example
from datetime import datetime
timestamp_0 = datetime.timestamp(datetime.fromisoformat('2011-11-04 00:05:23.111'))
transaction_data_0 = "Albert,Bertrand,30"
block_0 = Block(
    transaction_data=transaction_data_0,
    timestamp=timestamp_0
)

timestamp_1 = datetime.timestamp(datetime.fromisoformat('2011-11-07 00:05:13.222'))
transaction_data_1 = "Albert,Camille,10"
block_1 = Block(
    transaction_data=transaction_data_1,
    timestamp=timestamp_1,
    previous_block=block_0
)

timestamp_2 = datetime.timestamp(datetime.fromisoformat('2011-11-09 00:11:13.333'))
transaction_data_2 = "Bertrand,Camille,5"
block_2 = Block(
    transaction_data=transaction_data_2,
    timestamp=timestamp_2,
    previous_block=block_1
)

Теперь самое интересное: каждый блок должен содержать криптографический хэш предыдущего блока. Настоятельно рекомендую прочитать о криптографических хешах и хеш-функциях на странице Википедии о них (да, Википедия — мой единственный источник информации). А пока вот некоторые свойства хеш-функций:

  • Хеш-функции сопоставляют ввод x с выходом фиксированного размера, который называется хэшем.
  • Хэши детерминированы: ввод x всегда будет давать один и тот же хэш для одной и той же хеш-функции.
  • Зная хэш, невозможно определить x (устойчивый к прообразу).
  • Невозможно найти x и y, где hash(x) = hash(y) (устойчивость к коллизиям).
  • Слегка измените x, и хэш(x) изменится значительно (лавинный эффект).
  • Зная хэш(x) и часть x, невозможно найти x (удобство головоломки)
  • Хэши легко и быстро вычисляются на современных компьютерах.

Эти свойства делают так, что если злоумышленник захочет вмешаться в блокчейн и попытается изменить любое значение внутри него, хеш последнего блока также будет изменен. Существуют различные хэш-функции, но для биткойнов и других криптовалют используемая хеш-функция называется sha256. Эта хэш-функция преобразует наши исходные данные в хэш размером 256 бит. В Python с хешированием можно легко справиться с помощью библиотеки pycryptodome. Этот пакет можно установить через pip:

pip3 install pycryptodome

Давайте создадим метод calculate_hash, роль которого будет заключаться в вычислении хэша строковых данных.

# node/utils.py
from Crypto.Hash import SHA256


def calculate_hash(data: bytes) -> str:
    h = SHA256.new()
    h.update(data)
    return h.hexdigest()

Теперь внутри нашего класса Block мы создаем два новых метода:

  • cryprographic_hash: возвращает хэш содержимого блока
  • previous_block_cryptographic_hash: возвращает криптографический хэш предыдущего блока.
# block.py
import json

from utils import calculate_hash, convert_transaction_data_to_bytes


class Block:
    def __init__(
            self,
            timestamp: float,
            transaction_data: str,
            previous_block=None,
    ):
        self.transaction_data = transaction_data
        self.timestamp = timestamp
        self.previous_block = previous_block

    @property
    def previous_block_cryptographic_hash(self):
        previous_block_cryptographic_hash = ""
        if self.previous_block:
            previous_block_cryptographic_hash = self.previous_block.cryptographic_hash
        return previous_block_cryptographic_hash

    @property
    def cryptographic_hash(self) -> str:
        block_content = {
            "transaction_data": self.transaction_data,
            "timestamp": self.timestamp,
            "previous_block_cryptographic_hash": self.previous_block_cryptographic_hash
        }
        block_content_bytes = json.dumps(block_content, indent=2).encode('utf-8')
        return calculate_hash(block_content_bytes)

Теперь давайте создадим наши 3 блока:

from datetime import datetime


timestamp_0 = datetime.timestamp(datetime.fromisoformat('2011-11-04 00:05:23.111'))
transaction_data_0 = "Albert,Bertrand,30"
block_0 = Block(
    transaction_data=transaction_data_0,
    timestamp=timestamp_0
)

timestamp_1 = datetime.timestamp(datetime.fromisoformat('2011-11-07 00:05:13.222'))
transaction_data_1 = "Albert,Camille,10"
block_1 = Block(
    transaction_data=transaction_data_1,
    timestamp=timestamp_1,
    previous_block=block_0
)

timestamp_2 = datetime.timestamp(datetime.fromisoformat('2011-11-09 00:11:13.333'))
transaction_data_2 = "Bertrand,Camille,5"
block_2 = Block(
    transaction_data=transaction_data_2,
    timestamp=timestamp_2,
    previous_block=block_1
)

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

Более эффективное использование дискового пространства: Merkle Trees

Несмотря на то, что созданный нами блокчейн великолепен, на практике обычно в каждом блоке выполняется более 1 транзакции. Например, в биткойне в каждом блоке содержится около 2759,12 транзакций. В целях экономии места в каждом блоке хранится сводка его содержимого, которую мы называем заголовком. А внутри этого заголовка хранится только хеш, представляющий все транзакции блока. В часть 2 этого руководства мы подробно рассмотрим деревья Меркла и то, как они позволяют такое.

Репозиторий кода

Создайте свой собственный блокчейн с помощью Python























использованная литература