Categories: Топ

Собственная криптовалюта: реализация блокчейна на Python

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.

Марина

Recent Posts

Абсолютное значение числа в Python

При анализе данных часто требуется быстро найти абсолютное значение набора чисел. Для выполнения этой задачи…

19 часов ago

Библиотека Pydantic: валидация данных на Python

Pydantic - это мощная библиотека проверки данных и управления настройками для Python, созданная для повышения…

1 неделя ago

7 наилучших библиотек визуализации Python на 2024 год

Python предлагает набор библиотек, удовлетворяющих различные потребности в визуализации, будь то академические исследования, бизнес-аналитика или…

2 недели ago

Как преобразовать строку в байты в Python

В Python для представления данных в двоичной форме можно использовать байты. Из этой статьи вы…

3 недели ago

Что такое Werkzeug?

В этой статье рассказывается о том, что такое Werkzeug и как Flask использует его для…

4 недели ago

Как прибавить дни, месяцы и годы к дате в Python

При работе с датами часто возникает необходимость прибавлять к дате или вычитать из нее различные…

1 месяц ago