Основы
В этом первом разделе этого руководства мы запачкаем руки, написав нашу первую цепочку блоков. Но прежде чем углубляться в код, я думаю, нам важно иметь общее представление о том, что такое блокчейн. Здесь я буду использовать определение Википедии.
Блокчейн — это растущий список записей, называемых блоками, которые связаны друг с другом с помощью криптографии. Каждый блок содержит криптографический хэш предыдущего блока, метку времени и данные транзакции.
Подробнее о блокчейне можно прочитать здесь.
Создание нашего первого блокчейна на 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 транзакции:
- Альберт платит Бертрану 30 сыров.
- Альберт платит Камилле 10 сыров.
- Бертран платит Камилле 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
Создайте свой собственный блокчейн с помощью Python (часть 8)
Транзакционные сборыgruyaume.medium.com
использованная литература
- Блокчейн: https://en.wikipedia.org/wiki/Блокчейн
- Белая книга Сатоши Накамото: https://bitcoin.org/bitcoin.pdf
- Основы блокчейна и криптография, MIT 15.S12 Блокчейн и деньги, осень 2018 г., Гэри Генслер (Youtube)
- sha256: https://en.wikipedia.org/wiki/SHA-2