Bitcoin — всеобщий любимец из числа криптовалют. Новшество, которое он привнес, — решение проблемы двойного расходования при помощи блокчейна — цепочки блоков информации. Вместо того чтобы хранить значения в централизованном банке, блокчейн формирует децентрализованную систему.
Какую структуру имеет блокчейн? Каждый блок имеет хеш-сумму, также в нем содержатся данные о транзакции и метка времени. Кроме того, в блоке есть копия хеш-суммы предыдущего блока. Выглядит все это примерно так:
Хм, на что же это похоже? На какую структуру данных? Точно! Это же связный список!
Итак, давайте возьмем класс связного списка, который мы создали ранее (см. статью «Связный список на Python: что это такое и как его реализовать», — прим. ред. Pythonist), и превратим его в простой блокчейн. Мы внесем в него несколько простых изменений, и вы увидите, что объектно-ориентированное программирование на Python позволяет создавать крутые вещи.
Внедряем ООП
Для начала нам нужно внедрить в наш класс связного списка некоторые принципы ООП (объектно-ориентированного программирования). Да, поначалу это кажется сложным. Но если мы хотим, чтобы наш блокчейн был защищенным, нам нужно сделать кое-какие вещи приватными. Этот принцип называется инкапсуляцией. Суть его в том, что некоторые поля и методы делаются доступными только изнутри класса. Поскольку мы не хотим, чтобы кто-то мог нарушить структуру нашей криптовалюты, нам нужно ограничить возможность взаимодействия с ней.
Давайте сначала перечитаем класс Node
, который мы создали ранее. Изменим его имя на Block
, поскольку теперь он будет представлять один блок блокчейна. Также добавим пару импортов, которые будут использоваться в классе.
import datetime import math import pprint class Block: def __init__(self, data): self.next = None self.data = data def append(self, val): end = Block(val) n = self while (n.next): n = n.next n.next = end
Стоит отметить, что блокчейн обладает парой отличительных свойств. Во-первых, он отличается иммутабельностью, т. е. записи в нем трудно изменить (если вообще возможно). Во-вторых, он публичный. Да, именно так. Блокчейн Bitcoin-а это публичные записи, вы можете просмотреть их хоть сейчас.
В настоящий момент данные нашего узла хранятся просто как data. При помощи дот-нотации кто угодно может получить к ним доступ и изменить их: my_block.data = "One Million Dollarssss"
. Нас это не устраивает. Мы можем сделать это поле приватным, добавив к имени data
двойной символ подчеркивания (__data
), а затем создав специальный метод, позволяющий пользователям читать данные (т. е. публичный доступ сохранится).
def __init__(self, data): self.next = None self.__data = data
Теперь давайте напишем метод, позволяющий пользователям читать значения. В ООП мы называем это get
-методом, с его помощью мы просто возвращаем значение желаемого поля.
def get_data(self): return self.__data
Прокачиваем наш связный список
Теперь, когда мы встроили в наш класс защитный механизм, давайте добавим то, что сделает его собственно блокчейном. Для начала нам нужно сохранить кое-что в переменной data. Мы превратим ее в словарь, хранящий хеш блока, предыдущий хеш и метку времени. В блокчейне Bitcoin-а, как вы видели, в одном блоке могут храниться несколько транзакций (в форме дерева хешей). Мы не станем так углубляться и будем хранить только одну «транзакцию». Обязательно передайте в качестве параметров предыдущий хеш, транзактора и сумму.
def __init__(self, prev_hash, transactor, amount): self.next = None self.__data = { "prev_hash": prev_hash, "time": datetime.datetime.now().time(), "transactor": transactor, "amount": amount, "hash": ??? }
Как сделать хеш блока? В блокчейне это делается так: вы берете хеш предыдущего блока и видоизменяете его, используя суперсекретный алгоритм хеширования. Вы можете сделать свой алгоритм сколь угодно сложным, но он может быть и очень простым:
def make_hash(self): return self.__data["prev_hash"] + 1
Теперь у нас есть автоматически инкрементирующаяся SQL-база данных. Прекрасно. Давайте сделаем нечто более сложное. Возьмем сумму транзакции, возведем ее в странную степень, прибавим порядковый номер последней буквы имени транзактора, и прибавим все это к предыдущему хешу.
def make_hash(self): return self.__data["prev_hash"] + int(int(self.__data["amount"])**1.5) + ord(self.__data["transactor"][-1])
Итак, у нас есть функция хеширования, теперь нужно вызвать ее в конструкторе. Вызывать нужно непременно после определения __data
.
def __init__(self, prev_hash, transactor, amount): self.next = None self.__data = { "prev_hash": prev_hash, "time": datetime.datetime.now().time(), "transactor": transactor, "amount": amount, "hash": "" } self.__data["hash"] = self.make_hash()
Наконец, нужно обновить метод append()
. Вместо data мы в качестве параметров будем передавать имя транзактора и сумму. Затем, прежде чем создать новый блок, мы получим последний хеш и передадим его в Block
месте с другими данными. После этого добавим блок к концу цепочки.
def append(self, transactor, amount): n = self while (n.next): n = n.next prev_hash = n.get_data()["hash"] end = Block(prev_hash, transactor, amount) n.next = end
Делаем цепочку неизменяемой
Как вы помните, блокчейны отличаются неизменяемостью (иммутабельностью). То есть изменить их очень сложно. Это свойство завязано на том, что каждый хеш зависит от сохраняемых в блоке данных (по крайней мере в нашей функции). При изменении значения в блоке должны обновиться все хеши во всех последующих блоках. Если блоков всего несколько, это не займет много времени, но представьте, что у нас очень длинная цепочка, а ее обновление имеет временную сложность O(N).
Для обновления суммы транзакции мы создадим специальный метод. Блокчейн Bitcoin-а вполне может быть реализован как-то иначе, но у нас ведь просто его подобие. В ООП это называется set
-методом. Он принимает новое значение и устанавливает скрытое значение data, исключая прямое вмешательство пользователя. Не забудьте вызвать функцию make_hash
, чтобы получить обновленный хеш для блока.
def set_amount(self, amount): self.__data["amount"] = amount self.__data["hash"] = self.make_hash()
Но погодите, это еще не все. Нам нужно обновить хеши всех последующих блоков. Как это сделать? Конечно же путем обхода связного списка! (См. статью «Удаление дубликатов из связного списка в Python», — прим. ред. Pythonist). Как вы помните, мы начинаем со временного указателя и используем цикл while
.
temp = self while(temp.next):
Для каждого блока нам нужно получить его обновленный хеш и сохранить его как prev_hash
. Затем, перейдя к следующему блоку, мы обновляем его хеши, используя хеш предыдущего блока. Мы будем делать это при помощи скрытого метода __update_hashes()
.
def set_amount(self, amount): self.__data["amount"] = amount self.__data["hash"] = self.make_hash() temp = self while (temp.next): prev_hash = temp.__data["hash"] temp = temp.next temp.__update_hashes(prev_hash)
В нашем методе __update_hashes()
мы принимаем в качестве параметра предыдущий хеш, сохраняем его в __data
, а затем используем функцию make_hash()
для создания хеша текущего блока.
def __update_hashes(self, new_prev): self.__data["prev_hash"] = new_prev self.__data["hash"] = self.make_hash()
Вот и все! Мы создали базовый функционал нашего блокчейна.
Проверяем
Давайте построим блокчейн при помощи нашего метода Block
. Следующий код вы можете просто скопировать (он позволит вывести нашу цепочку блоков).
def print_chain(chain): pp = pprint.PrettyPrinter(indent=4) node = chain pp.pprint(node.get_data()) while node.next: node = node.next pp.pprint(node.get_data())
Давайте создадим первый блок. Передадим в него имя и сумму. Предыдущий хеш для первого блока в цепочке — всегда 0.
chain = Block(0, 'Tim', 120.00)
Теперь добавим еще несколько блоков.
chain.append('Jamil', 200.00) chain.append('Carla', 123.45) chain.append('Sarah', 450.00) print_chain(chain)
Вы увидите следующий вывод:
{ 'amount': 120.0, 'hash': 1423, 'prev_hash': 0, 'time': datetime.time(0, 11, 46, 849539), 'transactor': 'Tim'} { 'amount': 200.0, 'hash': 4359, 'prev_hash': 1423, 'time': datetime.time(0, 11, 46, 849608), 'transactor': 'Jamil'} { 'amount': 123.45, 'hash': 5820, 'prev_hash': 4359, 'time': datetime.time(0, 11, 46, 849617), 'transactor': 'Carla'} { 'amount': 450.0, 'hash': 15469, 'prev_hash': 5820, 'time': datetime.time(0, 11, 46, 849624), 'transactor': 'Sarah'}
Если вы использовали другой алгоритм хеширования, ваши хеши могут отличаться. Но суть в том, что каждый хеш создается на базе предыдущего.
Давайте попробуем обновить сумму. Установим новое значение суммы для второго человека, Джамиля. По идее мы должны увидеть, что обновятся хеши всех последующих блоков, а хеш блока Тима останется тем же.
chain.next.set_amount(700) print("UPDATE:") print_chain(chain)
Вывод:
UPDATE: { 'amount': 120.0, 'hash': 1423, 'prev_hash': 0, 'time': datetime.time(0, 11, 46, 849539), 'transactor': 'Tim'} { 'amount': 700, 'hash': 20051, 'prev_hash': 1423, 'time': datetime.time(0, 11, 46, 849608), 'transactor': 'Jamil'} { 'amount': 123.45, 'hash': 21512, 'prev_hash': 20051, 'time': datetime.time(0, 11, 46, 849617), 'transactor': 'Carla'} { 'amount': 450.0, 'hash': 31161, 'prev_hash': 21512, 'time': datetime.time(0, 11, 46, 849624), 'transactor': 'Sarah'}
Все прошло по плану! Вы создали собственную криптовалюту! Ну ладно, нечто похожее на нее. Если вам интересно, можете сделать форк оригинального кода Bitcoin и на его основе создать что-то свое. Код доступен на GitHub.