Когда я читал статью про блокчейн на JavaScript, мне было интересно познакомиться с идеями о блокчейн-разработке, которые отличаются от тех, что мне уже известны. А как только я начал читать код, мне захотелось сопоставить его с аналогичным Python-кодом, чтобы ещё и разобраться с его отличиями от кода, написанного на JavaScript.
Цель этого материала заключается в том, чтобы выявить отличия языков. Его можно считать Python-дополнением к исходной статье.
Несмотря на то, что исходная статья появилась на свет после того, как её автор ознакомился с примером блокчейн-разработки на Python, мне хотелось написать Python-код, который как можно более точно воспроизводит JavaScript-код из статьи. Это позволит сопоставить реализацию блокчейна на разных языках.
Я, кроме того, собираюсь сделать так, чтобы моя реализация блокчейна, как и в статье про JavaScript, тоже поместилась бы в 60 строк.
Хотя я собирался повторить структуру того материала, чтобы тем же путём, что и его автор, прийти к готовому коду, я, всё же, включу сюда и кое-что своё. В частности, я предпочитаю другое определение блокчейна. Оно звучит так: «Блокчейн — это система регистрации информации, выполняемой таким способом, который усложняет или делает невозможным изменение информации, взлом системы или мошенничество с информацией».
В этом проекте мы будем использовать Python, поэтому, если он у вас не установлен — найдите дистрибутив, подходящий для вашей ОС, и установите его.
Блок — это объект, в котором имеется некая информация. Поэтому начнём работу с создания класса
Определения класса
Комментарии тоже выполняются похожим образом. В Python для оформления однострочных комментариев применяется символ
Реализацию алгоритма
В методе
Займёмся классом
Классы, представляющие собой блокчейн, похожи в обоих языках.
Для того чтобы создать первичный блок, мы просто вызываем
Преобразование числа в строку выполняется в Python с помощью функции
Похоже выглядит и метод для получения самого свежего блока. Только тут, в отличие от JavaScript-проекта, для выяснения длины цепочки блоков, вместо свойства
Для того чтобы добавить блок в блокчейн, мы просто вызываем метод
В методе, используемом для проверки блокчейна, мы пользуемся функцией
При проверке условий в Python, вместо
Реализовать алгоритм доказательства выполнения работы мы можем, начав с добавления в класс
Создадим свойство, в котором будет храниться сложность:
Отредактируем метод
Импортируем модуль и воспользуемся классом
Результаты работы этого кода должны выглядеть примерно так:
Но заработает это всё только после добавления в класс
Для настройки времени блока нам понадобится соответствующее свойство:
Посмотрим на тернарный оператор, используемый в системе настройки сложности. Если переписать JS-конструкцию с тернарным оператором на Python, то получится следующее:
Итоговый Python-код (без нормального форматирования) укладывается в обещанные 60 строк:
Надеюсь, вам понравились оба материала, и вы нашли в них что-то полезное.
Если бы вам понадобилось создать блокчейн-систему — какими инструментами вы воспользовались бы?
Цель этого материала заключается в том, чтобы выявить отличия языков. Его можно считать 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)
MAXH0
17.11.2021 20:52+1Не совсем в тему может быть выскажусь, но наткнулся на это именно собираясь писать такой вот пет. проект про блокчейн. Мне кажется из оборота уходит хорошая криптография. Такая что ей стоило бы доверять. Что то закрылось, что то скомпрометировано, что то давно не обновлялось.
Поэтому >> "Если бы понадобилось создать блокчейн-систему" << то я бы дал волю своей паранойе. Например, вычислял бы хэш-функцию по двум разным алгоритмам.
lizergil
Вот вам сравнение, весь JS-код можно оформить в виде одной строки, а Python нет. Шах и мат.
MAXH0
\n отменили?
kAIST
Ну может у человека клавиша enter сломалась, а программировать хочется.
MAXH0
Так и в Python ничего не мешает. Не очень спортивно однострочники через \n писать, но если прижало, то можно...
kAIST
Не обязательно ;) https://habr.com/ru/post/588955/