Как написать самый простой блокчейн

Самый быстрый способ изучить работу Блокчейнов – это создать свой блокчейн. Стоит лишь только попробовать!

Скорее всего вы здесь, также, как и я, потому что были недовольны резким подъемом Криптовалюты. И вы хотите узнать, как же работают Блокчейны – фундаментальная технология, которая стоит за всеми криптовалютами.

Но понять, как работают Блокчейны не так просто – ну или, как минимум для меня, это сложно. Я с трудом пробирался сквозь множество непонятных видеороликов, сомнительных туториалов и боролся с сильным разочарованием от очень малого количества примеров. Поэтому мы с вами напишем свой блокчейн.

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

Перед тем как мы начнем…

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

Если вы не уверены в том, что такое хэш, то вот объяснение.

На кого нацелен данный туториал?

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

Что вам необходимо?

Убедитесь в том, что у вас установлен Python 3.6+ (также как и pip). Вам также необходимо установить библиотеку Flask и прекрасную библиотеку Request:

pip install Flask==0.12.2 requests==2.18.4

О, вам также понадобится HTTP-клиент, наподобие Postman или cURL. Но все будет дальше.

Где можно найти окончательную версию?

Исходный код будет доступен здесь.

Шаг 1: Создаем свой блокчейн

Запустите ваш любимый редактор кода или IDE, лично мне нравится PyCharm. Создайте новый файл с названием blockchain.py. Мы будем использовать только один файл, но если вы вдруг запутаетесь, то всегда можете обратиться к исходному коду.

Представление Блокчейна

Создадим класс Blockchain, конструктор которого будет создавать изначально пустой список (для хранения нашего блокчейна), и еще один для хранения транзакций. Ниже приведен макет нашего класса:

 	
class Blockchain(object):	
    def __init__(self):
        self.chain = []
        self.current_transactions = []
 	
    def new_block(self):
        # Создает новый Блок и добавляет его к цепочке
        pass

 	
    def new_transaction(self):
        # Добавляет новую транзакцию к списку транзакций
        pass
 	
    @staticmethod	
    def hash(block):
        # Хэшируем блоки
        pass
 	

    @property
    def last_block(self):
        # Возвращает последний блок в цепочке
        pass

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

Что из себя представляет Блок?

Каждый блок содержит в себе индекс, временную метку (timestamp, по Unix времени), список транзакций, доказательность (proof, подробнее об этом позже) и хэш предыдущего Блока.

Далее приведен пример того, как выглядит отдельный Блок:

block = {
    'index': 1,
    'timestamp': 1506057125.900785,
    'transactions': [
        {
            'sender': "8527147fe1f5426f9dd545de4b27ee00",
            'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f",
            'amount': 5, 	
        }
    ],
    'proof': 324984774000,
    'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}

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

Какой в этом смысл? Если это не так, то потребуется некоторое количество времени для того, чтобы понять, что вообще происходит – это и есть ключевая идея блокчейнов.

Добавление транзакций в блок

Итак, нам понадобится способ добавления транзакций в блок. Наш метод new_transaction() отвечает за это, и он довольно простой:

class Blockchain(object):
    ...

    def new_transaction(self, sender, recipient, amount):
        """
      Создает новую транзакцию для того чтобы перейти к следующему искомому Блоку

        :параметр sender: <str> Адрес отправителя
        :параметр recipient: <str> Адрес получателя	
        :параметр amount: <int> Количество
        :return: <int> Индекс Блока, в котором будет хранится данная транзакция
        """

        self.current_transactions.append({
            'sender': sender,
            'recipient': recipient,
            'amount': amount,
        })
 	
        return self.last_block['index'] + 1

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

Создание новых блоков, чтобы реализовать свой блокчейн

После того, как мы создали экземпляр нашего Блокчейна, нам необходимо заполнить его исходным блоком – блок у которого нет предшественников. Также нам необходимо добавить «proof» в наш исходный блок, который является результатом анализа (или алгоритма «доказательство выполнения работы»). По поводу анализа мы поговорим позднее.

Кроме этого, для создания исходного блока в нашем конструкторе, нам также необходимо добавить следующие методы: new_block(), new_transaction() и hash():

import hashlib	
import json
	
from time import time


 	
class Blockchain(object):	
    def __init__(self):
        """
        Инициализируем свой блокчейн
        """
        self.current_transactions = []
        self.chain = []


        # Create the genesis block
        self.new_block(previous_hash=1, proof=100)


    def new_block(self, proof, previous_hash=None):
        """
        Создаем новый блок в нашем Блокчейне

 	 	
        :параметр proof: <int> proof полученный после использования алгоритма «Доказательство выполнения работы»
        :параметр previous_hash: (Опциональный) <str> Хэш предыдущего Блока
        :return: <dict> New Block
        """

        block = {
            'index': len(self.chain) + 1,
            'timestamp': time(),
            'transactions': self.current_transactions,
            'proof': proof,
            'previous_hash': previous_hash or self.hash(self.chain[-1]),
        }

 	
        # Сбрасываем текущий список транзакций
        self.current_transactions = []

 	
        self.chain.append(block)
        return block
 	
    def new_transaction(self, sender, recipient, amount):
        """
        Создает новую транзакцию для перехода к следующему замайненному Блоку

        :param sender: <str> Address of the Sender
        :param recipient: <str> Address of the Recipient
        :param amount: <int> Amount
        :return: <int> Индекс блока который будет хранить в себе эту транзакцию
        """

        self.current_transactions.append({
            'sender': sender,
            'recipient': recipient,
            'amount': amount,
        })
 	
        return self.last_block['index'] + 1
 	
    @property
    def last_block(self):
        return self.chain[-1]

    @staticmethod
    def hash(block):
        """
        Создает a SHA-256 хэш блока

        :параметр block: <dict> Блок
        :return: <str>
        """

        # Мы должны быть уверены что наш Словарь упорядочен, или мы можем непоследовательные хэши
        block_string = json.dumps(block, sort_keys=True).encode()	
        return hashlib.sha256(block_string).hexdigest()

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

Разбираемся с алгоритмом «Доказательство выполнения работы»

Алгоритм «Доказательство выполнения работы» (PoW) – это то, как новые блоки создаются или майнятся в блокчейне. Целью алгоритма PoW является нахождение такого числа (метки), которое будет решать проблему. Число должно быть таким, чтобы его было сложно найти и легко проверить. Говоря в вычислительном отношении не важно кем в сети это может быть сделано. В этом и заключается основная идея данного алгоритма.

Итак, давайте взглянем на простой пример, которой поможет нам во всем разобраться.

Предположим, что хэш некоторого целочисленного числа x, умноженного на другое целочисленное число y, должен заканчиваться на 0. Следовательно, hash(x * y) = ac23dc…0. И для нашего упрощенного примера исправим x на 5. Реализуем это в Python:

from hashlib import sha256


x = 5
y = 0  # Пока мы не знаем каким должен быть y


while sha256(f'{x*y}'.encode()).hexdigest()[-1] != "0":
    y += 1


print(f'The solution is y = {y}')

Решением здесь будет y=21. Поскольку полученный хэш заканчивается на 0:

hash(5 * 21) = 1253e9373e...5e3600155e860

В сфере Биткоинов, алгоритм «Доказательство выполнения работы» называется Hashcash. И он не сильно отличается от нашего базового примера выше. Это алгоритм, который майнеры используют в гонке по решению задачи создания новых блоков. Как правило, сложность определяется количеством символов, которые необходимо обнаружить в строке. После чего майнеры получают награду за свое решение в качестве биткойна при транзакции.

Сеть может легко проверить их решение.

Реализация простого алгоритма PoW

Давайте реализуем простой алгоритм для того, чтобы реализовать свой блокчейн. Наше правило будет аналогично приведенному выше примеру:

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

import hashlib	
import json

 		
from time import time	
from uuid import uuid4

	
class Blockchain(object):	
    ...

    def proof_of_work(self, last_proof):
        """
        Простой алгоритм Proof of Work:
         - Ищем число p' такое, чтобы hash(pp') содержал в себе 4 лидирующих нуля, где p это предыдущий p'
         - p это предыдущий proof, а p' это новый proof

        :параметр last_proof: <int>
        :return: <int>
        """

        proof = 0
        while self.valid_proof(last_proof, proof) is False:
            proof += 1

        return proof


    @staticmethod
    def valid_proof(last_proof, proof):
        """
        Проверяем Proof: Содержит ли hash(last_proof, proof) 4 лидирующих нуля?

        :параметр last_proof: <int> предыдущий Proof
        :параметр proof: <int> Тукущий Proof
        :return: <bool> True если все верно, иначе False.
        """
	
        guess = f'{last_proof}{proof}'.encode()	
        guess_hash = hashlib.sha256(guess).hexdigest() 	
        return guess_hash[:4] == "0000"

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

Наш класс практически готов, и мы готовы начать взаимодействовать с ним посредствам HTTP-запросов.

Шаг 2: Свой блокчейн в качестве API

Мы будем использовать фреймворк Flask. Данный микро-фреймворк упрощает размещение конечных точек (endpoints) в Python-функциях. Это позволит нам обращаться к нашему блокчейну за счет веб-соединения с помощью HTTP-запросов.

Создадим три метода:

  • /transactions/new для создания новой транзакции в блоке;
  • /mine для передачи нашему серверу информации о том, что пора майнить новый блок;
  • /chain для возврата всего Блокчейна.

Настраиваем Flask для того, чтобы реализовать свой блокчейн

Наш «сервер» будет формировать одиночный узел в нашей блокчейн-сети. Давайте напишем некоторый шаблонный код:

import hashlib	
import json
	
from textwrap import dedent 	
from time import time	
from uuid import uuid4

	
from flask import Flask

	
class Blockchain(object):	
    ...

 	
# Создаем экземпляр нашего узла	
app = Flask(__name__)
 	
# Генерируем уникальный глобальный адрес для этого узла	
node_identifier = str(uuid4()).replace('-', '')

# Создаем экземпляр Blockchain	
blockchain = Blockchain()

 	
@app.route('/mine', methods=['GET']) 	
def mine():
    return "We'll mine a new Block"
 	
@app.route('/transactions/new', methods=['POST'])	
def new_transaction():
    return "We'll add a new transaction"

@app.route('/chain', methods=['GET'])
def full_chain():
    response = {
        'chain': blockchain.chain,
        'length': len(blockchain.chain),
    }	
    return jsonify(response), 200
 	
if __name__ == '__main__':	
    app.run(host='0.0.0.0', port=5000)

Небольшое пояснение того, что мы добавили в примере выше:

  • Строка 15: Создаем экземпляр узла. Более подробно узнать о Flask можно здесь.
  • Строка 18: Генерируем случайное имя для нашего узла;
  • Строка 21: Создаем экземпляр класса Blockchain;
  • Строки 24-26: Создаем endpoint для метода /mine, который является GET-запросом;
  • Строки 28-30: Создаем endpoint для метода /transactions/new, который является POST-запросом, поскольку мы будем отправлять сюда данные;
  • Строки 32-38: Создаем endpoint для метода /chain, который будет возвращать весь Блокчейн;
  • Строки 40-41: Запускаем наш сервер на порт 5000.

Endpoint для транзакций

Вот так будет выглядеть запрос транзакции. То есть, именно эту информацию пользователь отправляет на сервер:

{
 "sender": "my address",
 "recipient": "someone else's address",
 "amount": 5
}

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

import hashlib 	
import json
	
from textwrap import dedent 	
from time import time	
from uuid import uuid4

 	
from flask import Flask, jsonify, request

...
	
@app.route('/transactions/new', methods=['POST'])	
def new_transaction():
    values = request.get_json()


    # Проверяем, что обязательные поля переданы в POST-запрос
    required = ['sender', 'recipient', 'amount']
    if not all(k in values for k in required):
        return 'Missing values', 400

 	
    # Создаем новую транзакцию	
    index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])
 	
    response = {'message': f'Transaction will be added to Block {index}'}
    return jsonify(response), 201

Endpoint для майнинга

Наш endpoint майнинга – это то, где происходит магия, и в ней нет ничего сложного. Здесь совершаются три следующих вещи:

  1. Расчет алгоритма PoW;
  2. Майнер(ы) получают награду в виде транзакции, которая гарантируем им 1 биткойн;
  3. Формирование нового блока, путем его добавления в цепочку.
import hashlib	
import json

 	
from time import time
from uuid import uuid4


from flask import Flask, jsonify, request
	
...
 	
@app.route('/mine', methods=['GET'])
def mine():
    # Мы запускаем алгоритм PoW для того чтобы найти следующий proof...
    last_block = blockchain.last_block	
    last_proof = last_block['proof']
    proof = blockchain.proof_of_work(last_proof)

 	
    # Мы должны получить награду за найденный proof.
    # Если sender = "0", то это означает что данный узел заработал биткойн.	
    blockchain.new_transaction(
        sender="0",	
        recipient=node_identifier,
        amount=1,
    )


    # Формируем новый блок, путем добавления его в цепочку
    block = blockchain.new_block(proof)
 	
    response = {
        'message': "New Block Forged",
        'index': block['index'],
        'transactions': block['transactions'],
        'proof': block['proof'],
        'previous_hash': block['previous_hash'],	
    }	
    return jsonify(response), 200

Обратите внимание, что получателем замайненного блока является адрес нашего узла. И большинство из того, что мы здесь сделали, просто взаимодействует с методами нашего класса Blockchain. На данном этапе мы закончили с подготовкой нашего блокчейна и теперь готовы взаимодействовать с ним.

Шаг 3: Взаимодействие с нашим Блокчейном

Вы можете использовать простой, но уже устаревший cURL или Postman, для взаимодействия с нашим API через сеть.

Запускаем наш сервер:

$ python blockchain.py* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Давайте попробуем смайнить блок. Для этого воспользуемся GET-запросом на http://localhost:5000/mine:

Создадим новую транзакцию с помощью POST-запроса на http://localhost:5000/transactions/new с телом, содержащим структуру нашей транзакции:

Если не хотите использовать Postman, то вы можете сделать аналогичные запрос с помощью cURL:

$ curl -X POST -H "Content-Type: application/json" -d '{
 "sender": "d4ee26eee15148ee92c6cd394edd974e",
 "recipient": "someone-other-address",
 "amount": 5
}' "http://localhost:5000/transactions/new"

Я перезагрузил свой сервер, и смайнил два блока, чтобы в итоге получилось три. Давайте проверим всю цепочку, с помощью запроса на http://localhost:5000/chain:

{
  "chain": [
    {
      "index": 1,
      "previous_hash": 1,
      "proof": 100,
      "timestamp": 1506280650.770839,
      "transactions": []
    },
    {
      "index": 2,
      "previous_hash": "c099bc...bfb7",
      "proof": 35293,
      "timestamp": 1506280664.717925,
      "transactions": [
        {
          "amount": 1,
          "recipient": "8bbcb347e0634905b0cac7955bae152b",
          "sender": "0"
        }
      ]
    },
    {
      "index": 3,
      "previous_hash": "eff91a...10f2",
      "proof": 35089,
      "timestamp": 1506280666.1086972,
      "transactions": [
        {
          "amount": 1,
          "recipient": "8bbcb347e0634905b0cac7955bae152b",
          "sender": "0"
        }
      ]
    }
  ],
  "length": 3
}

Шаг 4: Консенсус

Пока, все что мы делали, это круто. Мы получили базовый Блокчейн, который может принимать транзакции, тем самым позволяя нам майнить новые Блоки. Однако вся суть Блокчейнов заключается в том, что они должны быть децентрализованы. Но если блокчейны децентрализованы, то как мы можем гарантировать, что все они отражают одну и ту же цепочку? Данная проблема называется проблемой Консенсуса (конфликтов). Мы реализуем алгоритм Консенсуса, если мы конечно хотим, чтобы в нашей сети было больше одного узла.

Регистрация новых узлов

Перед тем как мы сможем реализовать алгоритм Консенсуса, нам необходимо придумать способ, как наши узлы будут знать о своих «соседях» в сети. Каждый узел в нашей сети будет хранить в себе запись о других узлах в сети. Итак, нам необходимо еще некоторое количество endpoint-ов:

  1. /nodes/register чтобы можно было принимать список новых узлов в форме URL;
  2. /nodes/resolve для реализации нашего алгоритма Консенсуса, который разрешит любые конфликтные ситуации, чтобы каждый узел содержал корректную цепочку.

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

...	
from urllib.parse import urlparse	
...

 	
class Blockchain(object):
    def __init__(self):
        ...
        self.nodes = set()
        ...

    def register_node(self, address):
        """
        Добавляем новый узел в список узлов

        :параметр address: <str> Адрес узла, например: 'http://192.168.0.5:5000'
        :return: None
        """
 	
        parsed_url = urlparse(address)
        self.nodes.add(parsed_url.netloc)

Обратите внимание, что мы использовали set() для хранения списка узлов. Это самый дешёвый способ гарантировать, что новый узлы будут добавляться, не изменяя при этом объект, то есть не важно сколько раз мы добавляли определенный узел, он появится ровно один раз.

Реализация алгоритма Консенсуса

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

...
import requests

 	
class Blockchain(object)	
    ...
 	
    def valid_chain(self, chain):
        """
        Определяем, что данный блокчейн прошел проверку
 
        :параметр chain: <list> Блокчейн
        :return: <bool> True если прошел проверку, иначе False
        """

        last_block = chain[0]
        current_index = 1
 	
        while current_index < len(chain):
            block = chain[current_index]
            print(f'{last_block}')	
            print(f'{block}')
            print("n-----------n")
            # Проверяем, что хэш этого блока корректен
            if block['previous_hash'] != self.hash(last_block):
                return False

            # Проверяем, что алгоритм PoW корректен
            if not self.valid_proof(last_block['proof'], block['proof']):
                return False

 	
            last_block = block
 	
            current_index += 1

        return True

    def resolve_conflicts(self):
        """
        Это наш алгоритм Консенсуса, он разрешает конфликт путём
        замены нашей цепочки на самую длинную в нашей сети.

        :return: <bool> True если наша цепочка была заменена, False если это не так
        """

 	
        neighbours = self.nodes
        new_chain = None

        # Мы ищем цепочки длиннее наших
        max_length = len(self.chain)

        # Берем все цепочки со всех узлов нашей сети и проверяем их
        for node in neighbours:
            response = requests.get(f'http://{node}/chain')
 	
            if response.status_code == 200:
                length = response.json()['length']
                chain = response.json()['chain']

                # Проверяем, что цепочка имеет 
                # максимальную длину и она корректна
                if length > max_length and self.valid_chain(chain):
                    max_length = length
                    new_chain = chain

        # Заменяем нашу цепочку, если нашли другую, 
        # которая имеет большую длину и является корректной
        if new_chain:
            self.chain = new_chain
            return True
 	
        return False

Первый метод valid_chain () отвечает за проверку цепочки на корректность, путем прогонки её по циклу через каждый блок, в котором сравнивается хэш и proof.

resolve_conflicts() – это метод который в цикле проходит по всем соседним узлам, скачивает их цепочки и проверяет их, используя метод выше. Если найдена необходимая цепочка, то мы заменяем текущую на эту.

Давайте зарегистрируем два endpoint-а в нашем API, один будет предназначен для добавления соседних узлов, а второй для разрешения конфликтных ситуаций.

@app.route('/nodes/register', methods=['POST'])
def register_nodes():
    values = request.get_json()

 	
    nodes = values.get('nodes')
    if nodes is None:
        return "Error: Please supply a valid list of nodes", 400

    for node in nodes:
        blockchain.register_node(node)

    response = {
        'message': 'New nodes have been added', 	
        'total_nodes': list(blockchain.nodes),
    }
    return jsonify(response), 201

 	
@app.route('/nodes/resolve', methods=['GET'])
def consensus():
    replaced = blockchain.resolve_conflicts()
 	
    if replaced:
        response = {
            'message': 'Our chain was replaced',
            'new_chain': blockchain.chain
        }
    else:
        response = {
            'message': 'Our chain is authoritative',
            'chain': blockchain.chain
        }

    return jsonify(response), 200

На этом этапе вы можете задействовать любое количество машин, по вашему усмотрению, и реализовать различные узлы в вашей сети. Или же реализовать все то же самое на одной машине, используя разные порты. Я это реализовал вторым способом, используя разные порты. То есть, я зарегистрировал другой узел, уже с имеющимся узлом. Итак, у меня есть два узла: http://localhost:5000 и http://localhost:5001.

свой блокчейн

После чего я замайнил новые блоки на узел 2, для того чтобы цепочка стала длиннее. Потом, я вызвал GET /nodes/resolve на узел 1, где цепочка была заменена по алгоритму Консенсуса:

И это просто обёртка… Объединитесь с друзьями для того, чтобы затестить свой Блокчейн.

Итак, мы с вами написали свой блокчейн. Я надеюсь, что данная статья способствует тому, что вы создадите что-то новое для себя.

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

Другие материалы по теме

Простейший блокчейн своими руками
10 полезных ресурсов по технологии blockchain

Ссылка на оригинальную статью
Перевод: Александр Давыдов

Самый быстрый способ узнать, как работает блокчейн — это создать его.

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

Однако, понимание блокчейн — не самое простое дело, по крайней мере, так было в моем случае. Я прошел через тонны видеороликов, изучал руководства и постоянно разочаровывался из-за слишком маленького количества примеров.

Есть вопросы по Python?

На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!

Telegram Чат & Канал

Вступите в наш дружный чат по Python и начните общение с единомышленниками! Станьте частью большого сообщества!

Паблик VK

Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!

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

Перед тем, как начать…

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

Если вы не знаете, что такое хеш, то вот вам статьи:

  • Шифрование и криптография в Python
  • Генерация случайных данных в Python (Руководство)

На кого нацелена данная статья?

В целом, вы уже должны более-менее свободно читать и писать основы Python, наряду с пониманием работы запросов HTTP, так как мы будем говорить о блокчейне на HTTP.

Что нам нужно? Убедитесь, что у вас установлен Python 3.6+ (а также pip). Вам также нужно будет установить Flask и замечательную библиотеку Requests:

pip install Flask==0.12.2 requests==2.18.4

И да, вам также нужен будет HTTP клиент, такой как Postman или cURL. В целом, что-нибудь подойдет.

Исходный код статьи

Вот здесь вы можете ознакомиться с исходным: https://github.com/dvf/blockchain

Шаг 1: Создание блокчейна

Открывайте свой любимый редактор текста, или IDE, лично я предпочитаю PyCharm. Создайте новый файл под названием blockchain.py. Мы используем только один файл, но если вы запутаетесь, вы всегда можете пройтись по исходному коду.

Скелет блокчейна

Мы создадим класс блокчейна, чей конструктор создает начальный пустой лист (для хранения нашего блокчейна), и еще один — для хранения транзакций. Вот чертёж нашего класса:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

class Blockchain(object):

    def __init__(self):

        self.chain = []

        self.current_transactions = []

    def new_block(self):

        # Создает новый блок и вносит его в цепь

        pass

    def new_transaction(self):

        # Вносит новую транзакцию в список транзакций

        pass

    @staticmethod

    def hash(block):

        # Хеширует блок

        pass

    @property

    def last_block(self):

        # Возвращает последний блок в цепочке

        pass

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

Как выглядит блок?

Каждый блок содержит индекс, временной штамп (время unix), список транзакций, доказательство (об этом позже) и хеш предыдущего блока.

Вот пример того, как выглядит один блок:

block = {

    ‘index’: 1,

    ‘timestamp’: 1506057125.900785,

    ‘transactions’: [

        {

            ‘sender’: «8527147fe1f5426f9dd545de4b27ee00»,

            ‘recipient’: «a77f5cdfa2934df3954a5c7c7da5df1f»,

            ‘amount’: 5,

        }

    ],

    ‘proof’: 324984774000,

    ‘previous_hash’: «2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824»

}

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

Имеет ли это смысл? Если нет — то вам нужно уделить время, чтобы понять и осознать это, так как мы говорим о фундаментальном принципе работы блокчейна.

Внесение транзакций в блок

Нам нужен будет способом внесения транзакций в блок. Наш метод new_transaction() отвечает за это, и он достаточно прямолинейный:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

class Blockchain(object):

    ...

    def new_transaction(self, sender, recipient, amount):

        «»»

        Направляет новую транзакцию в следующий блок

        :param sender: <str> Адрес отправителя

        :param recipient: <str> Адрес получателя

        :param amount: <int> Сумма

        :return: <int> Индекс блока, который будет хранить эту транзакцию

        «»»

        self.current_transactions.append({

            ‘sender’: sender,

            ‘recipient’: recipient,

            ‘amount’: amount,

        })

        return self.last_block[‘index’] + 1

После того, как new_transaction() внесет транзакцию в список, он вернет индекс блока, в которой должна будет быть внесена транзакция — а именно следующая. В будущем, это будет полезно для пользователя, отправляющего транзакцию.

Создание новых блоков

После того, как мы получили экземпляр блокчейна, нам нужно посадить в него блок генезиса — первый блок без предшественников. Нам также нужно внести “пруф” в наш блок генезиса, который представляет собой результат майнинга (доказательства проведенной работы). Мы рассмотрим майнинг позже.

В дополнению к созданию блока генезиса в конструкторе, мы также выкатим методы для new_block(), new_transaction() и hash():

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

import hashlib

import json

from time import time

class Blockchain(object):

    def __init__(self):

        self.current_transactions = []

        self.chain = []

        # Создание блока генезиса

        self.new_block(previous_hash=1, proof=100)

    def new_block(self, proof, previous_hash=None):

        «»»

        Создание нового блока в блокчейне

        :param proof: <int> Доказательства проведенной работы

        :param previous_hash: (Опционально) хеш предыдущего блока

        :return: <dict> Новый блок

        «»»

        block = {

            ‘index’: len(self.chain) + 1,

            ‘timestamp’: time(),

            ‘transactions’: self.current_transactions,

            ‘proof’: proof,

            ‘previous_hash’: previous_hash or self.hash(self.chain[1]),

        }

        # Перезагрузка текущего списка транзакций

        self.current_transactions = []

        self.chain.append(block)

        return block

    def new_transaction(self, sender, recipient, amount):

        «»»

        Направляет новую транзакцию в следующий блок

        :param sender: <str> Адрес отправителя

        :param recipient: <str> Адрес получателя

        :param amount: <int> Сумма

        :return: <int> Индекс блока, который будет хранить эту транзакцию

        «»»

        self.current_transactions.append({

            ‘sender’: sender,

            ‘recipient’: recipient,

            ‘amount’: amount,

        })

        return self.last_block[‘index’] + 1

    @property

    def last_block(self):

        return self.chain[1]

    @staticmethod

    def hash(block):

        «»»

        Создает хэш SHA-256 блока

        :param block: <dict> Блок

        :return: <str>

        «»»

        # Мы должны убедиться в том, что словарь упорядочен, иначе у нас будут непоследовательные хеши

        block_string = json.dumps(block, sort_keys=True).encode()

        return hashlib.sha256(block_string).hexdigest()

Код выше должен быть достаточно ясным — я внес несколько комментариев и документацию, чтобы все было понятно. Структура данных будет в json. Мы почти закончили с скелетом нашего блокчейна. Однако на данный момент, вам наверное интересно, как создаются новые блоки?

Понимание подтверждения работы

Алгоритм пруфа работы (Proof of Work, PoW) — это то, как новые блоки созданы или майнятся в блокчейне. Цель PoW — это найти число, которое решает проблему. Число должно быть таким, чтобы его тяжело было найти, но легко подтвердить (говоря о вычислениях) кем угодно в интернете. Это главная задача алгоритма.

Рассмотрим простой пример, чтобы получить лучшее представление.

Скажем, что хеш того или иного числа х, умноженного на другое число должен заканчиваться нулем. Таким образом, hash(x * y) = ac23dc…0. Для этого упрощенного примера, представим что x = 5. Как это работает в Python:

from hashlib import sha256

x = 5

y = 0  # Мы еще не знаем, чему равен y…

while sha256(f‘{x*y}’.encode()).hexdigest()[1] != «0»:

    y += 1

print(f‘The solution is y = {y}’)

Решение здесь следующее: y = 21, так как созданный хеш заканчивается нулем:

hash(5 * 21) = 1253e9373e…5e3600155e860

В биткоине, такой алгоритм называется Hashcash. И он особо не отличается от приведенного выше примера. Это алгоритм, который поколение майнеров (читай, отдельная раса) пытается решить, чтобы создать новый блок. В целом, сложность определяется количеством символом, которые рассматриваются в строке. Майнеры неплохо вознаграждаются за решение задачи получением коина в транзакции.

Реализация базового PoW

Давайте реализуем аналогичный алгоритм для нашего блокчейна. Наше правило будет аналогично указанному ранее:

Найдите число «p«, которое хешировано с предыдущим созданным решением блока с хешем содержащим 4 заглавных нуля.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

import hashlib

import json

from time import time

from uuid import uuid4

class Blockchain(object):

    ...

    def proof_of_work(self, last_proof):

        «»»

        Простая проверка алгоритма:

         — Поиска числа p`, так как hash(pp`) содержит 4 заглавных нуля, где p — предыдущий

         — p является предыдущим доказательством, а p` — новым

        :param last_proof: <int>

        :return: <int>

        «»»

        proof = 0

        while self.valid_proof(last_proof, proof) is False:

            proof += 1

        return proof

    @staticmethod

    def valid_proof(last_proof, proof):

        «»»

        Подтверждение доказательства: Содержит ли hash(last_proof, proof) 4 заглавных нуля?

        :param last_proof: <int> Предыдущее доказательство

        :param proof: <int> Текущее доказательство

        :return: <bool> True, если правильно, False, если нет.

        «»»

        guess = f‘{last_proof}{proof}’.encode()

        guess_hash = hashlib.sha256(guess).hexdigest()

        return guess_hash[:4] == «0000»

Чтобы скорректировать сложность алгоритма, мы можем изменить количество заглавных нулей. В нашем случае, 4 — достаточно. Вы узнаете, что внесение одного ведущего нуля создает колоссальную разницу во времени, необходимом для поиска решения (майнинга).

Наш класс практически готов, так что мы можем начать взаимодействовать с ним через HTTP запросы.

Шаг 2: Блокчейн как API

Здесь мы задействуем фреймворк под названием Flask. Это макро-фреймворк, который заметно упрощает сопоставление конечных точек с функциями Python. Это позволяет нам взаимодействовать с нашим блокчейном в интернете при помощиHTTP-запросов.

Мы создадим три метода:

  • /transactions/new для создания новой транзакции в блоке;
  • /mine, чтобы указать серверу, что нужно майнить новый блок;
  • /chain для возвращения всего блокчейна

Настройка Flask

Наш “сервер” сформирует единый узел в нашей сети блокчейна. Давайте создадим шаблонный код:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

import hashlib

import json

from textwrap import dedent

from time import time

from uuid import uuid4

from flask import Flask

class Blockchain(object):

    ...

# Создаем экземпляр узла

app = Flask(__name__)

# Генерируем уникальный на глобальном уровне адрес для этого узла

node_identifier = str(uuid4()).replace(‘-‘, »)

# Создаем экземпляр блокчейна

blockchain = Blockchain()

@app.route(‘/mine’, methods=[‘GET’])

def mine():

    return «We’ll mine a new Block»

@app.route(‘/transactions/new’, methods=[‘POST’])

def new_transaction():

    return «We’ll add a new transaction»

@app.route(‘/chain’, methods=[‘GET’])

def full_chain():

    response = {

        ‘chain’: blockchain.chain,

        ‘length’: len(blockchain.chain),

    }

    return jsonify(response), 200

if __name__ == ‘__main__’:

    app.run(host=‘0.0.0.0’, port=5000)

Краткое объяснение того, что мы только что добавили:

  • Строка 15: Создание экземпляра узла. Можете больше узнать о Flask здесь;
  • Строка 18: Создание случайного имени нашего узла;
  • Строка 21: Создание экземпляра класса Blockchain;
  • Строки 24-26: Создание конечной точки /mine, которая является GET-запросом;
  • Строки 28-30: Создание конечной точки /transactions/new, которая являетсяPOST-запросом, так как мы будем отправлять туда данные;
  • Строки 32-38: Создание конечной точки /chain, которая возвращает весь блокчейн;
  • Строки 40-41: Запускает сервер на порт: 5000.

Конечная точка транзакций

Вот так запрос транзакции должен будет выглядеть. Это то, что пользователь отправляет в сервер:

{

    «sender»: «my address»,

    «recipient»: «someone else’s address»,

    «amount»: 5

}

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

import hashlib

import json

from textwrap import dedent

from time import time

from uuid import uuid4

from flask import Flask, jsonify, request

...

@app.route(‘/transactions/new’, methods=[‘POST’])

def new_transaction():

    values = request.get_json()

    # Убедитесь в том, что необходимые поля находятся среди POST-данных

    required = [‘sender’, ‘recipient’, ‘amount’]

    if not all(k in values for k in required):

        return ‘Missing values’, 400

    # Создание новой транзакции

    index = blockchain.new_transaction(values[‘sender’], values[‘recipient’], values[‘amount’])

    response = {‘message’: f‘Transaction will be added to Block {index}’}

    return jsonify(response), 201

Конечная точка майнинга

Конечная точка майнинга — это часть, где происходит магия, и это просто! Для этого нужно сделать три вещи:

  • Подсчитать PoW;
  • Наградить майнера (нас), добавив транзакцию, дающую нам 1 коин;
  • Слепить следующий блок, внеся его в цепь.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

import hashlib

import json

from time import time

from uuid import uuid4

from flask import Flask, jsonify, request

...

@app.route(‘/mine’, methods=[‘GET’])

def mine():

    # Мы запускаем алгоритм подтверждения работы, чтобы получить следующее подтверждение…

    last_block = blockchain.last_block

    last_proof = last_block[‘proof’]

    proof = blockchain.proof_of_work(last_proof)

    # Мы должны получить вознаграждение за найденное подтверждение

    # Отправитель “0” означает, что узел заработал крипто-монету

    blockchain.new_transaction(

        sender=«0»,

        recipient=node_identifier,

        amount=1,

    )

    # Создаем новый блок, путем внесения его в цепь

    previous_hash = blockchain.hash(last_block)

    block = blockchain.new_block(proof, previous_hash)

    response = {

        ‘message’: «New Block Forged»,

        ‘index’: block[‘index’],

        ‘transactions’: block[‘transactions’],

        ‘proof’: block[‘proof’],

        ‘previous_hash’: block[‘previous_hash’],

    }

    return jsonify(response), 200

Обратите внимание на то, что получатель замайненого блока — это адрес нашего узла. Большая часть того, что мы здесь сделали, это просто взаимодействие с методами в нашем классе Blockchain. С этого момента, мы закончили, и можем начать взаимодействовать с нашим blockchain на Python.

Шаг 3: Взаимодействие с нашим блокчейном

Вы можете использовать старый добрый cURL или Postman для взаимодействия с нашим API в сети.

Запускаем сервер:

$ python blockchain.py

* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Давайте попробуем майнить блок, создав GET-запрос к узлу http://localhost:5000/mine:

curl http://localhost:5000/mine

Создаем Blockchain с нуля на Python

Теперь, давайте создадим новую транзакцию, отправив POST-запрос к узлу http://localhost:5000/transactions/new с телом, содержащим структуру нашей транзакции:

Создаем Blockchain с нуля на Python

Если вы не пользуетесь Postman, тогда вы можете создать аналогичный запрос при помощи cURL:

$ curl X POST H «Content-Type: application/json» d {

«sender»: «d4ee26eee15148ee92c6cd394edd974e»,

«recipient»: «someone-other-address»,

«amount»: 5

} «http://localhost:5000/transactions/new»

Я перезапустил свой сервер и замайнил два блока, итого их количество 3. Давайте проверим всю цепочку, выполнив запрос к узлу http://localhost:5000/chain:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

{

  «chain»: [

    {

      «index»: 1,

      «previous_hash»: 1,

      «proof»: 100,

      «timestamp»: 1506280650.770839,

      «transactions»: []

    },

    {

      «index»: 2,

      «previous_hash»: «c099bc…bfb7»,

      «proof»: 35293,

      «timestamp»: 1506280664.717925,

      «transactions»: [

        {

          «amount»: 1,

          «recipient»: «8bbcb347e0634905b0cac7955bae152b»,

          «sender»: «0»

        }

      ]

    },

    {

      «index»: 3,

      «previous_hash»: «eff91a…10f2»,

      «proof»: 35089,

      «timestamp»: 1506280666.1086972,

      «transactions»: [

        {

          «amount»: 1,

          «recipient»: «8bbcb347e0634905b0cac7955bae152b»,

          «sender»: «0»

        }

      ]

    }

  ],

  «length»: 3

}

Шаг 4: Консенсус

Пока всё идет очень здорово. У нас есть базовый blockchain, который принимает транзакции и дает возможность майнить новые блоки. Но вся суть блокчейна в том, что они должны быть децентрализованными. А если они децентрализованы, каким образом мы можем гарантировать, все они отображают одну цепочку?

Это называется проблемой Консенсуса, так что нам нужно реализовать алгоритм Консенсуса, если нам нужно больше одного узла в нашей цепи.

Регистрация новых узлов

Чтобы мы смогли реализовать алгоритм Консенсуса, нам нужно найти способом дать узлу знать о существовании соседних узлов в цепи. Каждый узел в нашей цепи должен содержать регистр других узлов в цепи. Следовательно, нам понадобиться больше конечных точек:

  • /nodes/register для принятия список новых узлов в форме URL-ов;
  • /nodes/resolve для реализации нашего алгоритма Консенсуса, который решает любые конфликты, связанные с подтверждением того, что узел находиться в своей цепи.

Нам нужно будет изменить конструктор нашего Blockchain и привнести метод для регистрации узлов:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

...

from urllib.parse import urlparse

...

class Blockchain(object):

    def __init__(self):

        ...

        self.nodes = set()

        ...

    def register_node(self, address):

        «»»

        Вносим новый узел в список узлов

        :param address: <str> адрес узла , другими словами: ‘http://192.168.0.5:5000’

        :return: None

        «»»

        parsed_url = urlparse(address)

        self.nodes.add(parsed_url.netloc)

Обратите внимание на то, что мы использовали set() для хранения списка узлов. Это легкий способ убедиться в том, что внесение новых узлов является идемпотентным — это означает, что вне зависимости от того, сколько раз мы внесем определенный узел, он возникнет только один раз.

Реализация алгоритма Консенсуса

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

...

import requests

class Blockchain(object)

    ...

    def valid_chain(self, chain):

        «»»

        Проверяем, является ли внесенный в блок хеш корректным

        :param chain: <list> blockchain

        :return: <bool> True если она действительна, False, если нет

        «»»

        last_block = chain[0]

        current_index = 1

        while current_index < len(chain):

            block = chain[current_index]

            print(f‘{last_block}’)

            print(f‘{block}’)

            print(«n————n»)

            # Проверьте правильность хеша блока

            if block[‘previous_hash’] != self.hash(last_block):

                return False

            # Проверяем, является ли подтверждение работы корректным

            if not self.valid_proof(last_block[‘proof’], block[‘proof’]):

                return False

            last_block = block

            current_index += 1

        return True

    def resolve_conflicts(self):

        «»»

        Это наш алгоритм Консенсуса, он разрешает конфликты,

        заменяя нашу цепь на самую длинную в цепи

        :return: <bool> True, если бы наша цепь была заменена, False, если нет.

        «»»

        neighbours = self.nodes

        new_chain = None

        # Ищем только цепи, длиннее нашей

        max_length = len(self.chain)

        # Захватываем и проверяем все цепи из всех узлов сети

        for node in neighbours:

            response = requests.get(f‘http://{node}/chain’)

            if response.status_code == 200:

                length = response.json()[‘length’]

                chain = response.json()[‘chain’]

                # Проверяем, является ли длина самой длинной, а цепь — валидной

                if length > max_length and self.valid_chain(chain):

                    max_length = length

                    new_chain = chain

        # Заменяем нашу цепь, если найдем другую валидную и более длинную

        if new_chain:

            self.chain = new_chain

            return True

        return False

Первый метод valid_chain() отвечает за проверку того, является ли цепь валидной, запустив цикл через каждый блок и проводя верификацию как хеша, так и пруфа.

Метод resolve_conflicts(), который запускает цикл через все наши соседние узлы, загружает их цепи и проводит проверку, как и в предыдущем методе. Если валидная цепь, длина которой больше, чем наша, мы заменяем нашу.

Давайте зарегистрируем две конечные точки нашего API, одну для внесения соседних узлов, а вторую — для решения конфликтов:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

@app.route(‘/nodes/register’, methods=[‘POST’])

def register_nodes():

    values = request.get_json()

    nodes = values.get(‘nodes’)

    if nodes is None:

        return «Error: Please supply a valid list of nodes», 400

    for node in nodes:

        blockchain.register_node(node)

    response = {

        ‘message’: ‘New nodes have been added’,

        ‘total_nodes’: list(blockchain.nodes),

    }

    return jsonify(response), 201

@app.route(‘/nodes/resolve’, methods=[‘GET’])

def consensus():

    replaced = blockchain.resolve_conflicts()

    if replaced:

        response = {

            ‘message’: ‘Our chain was replaced’,

            ‘new_chain’: blockchain.chain

        }

    else:

        response = {

            ‘message’: ‘Our chain is authoritative’,

            ‘chain’: blockchain.chain

        }

    return jsonify(response), 200

Теперь вы можете сесть за другой компьютер (если хотите), и развернуть разные узлы в своей сети. Также вы можете развернуть процессы при помощи различных портов на одном и том же компьютере. Я развернул еще один узел на своем компьютере, но на другом порте и зарегистрировал его при помощи моего текущего узла. Таким образом, я получил два узла: http://localhost:5000 и http://localhost:5001.

Создаем Blockchain с нуля на Python

После этого я получил два новых блока в узле 2, чтобы убедиться в том, что цепь была длиннее. После этого, я вызвал GET /nodes/resolve в узле 1, где цепь была заменена нашим алгоритмом консенсуса:

Создаем Blockchain с нуля на Python

И это была обертка… Найдите друзей и вместе попробуйте протестировать ваш блокчейн!

Подведем итоги

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

Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.

E-mail: vasile.buldumac@ati.utm.md

Образование
Universitatea Tehnică a Moldovei (utm.md)

  • 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
  • 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»

В предыдущей статье мы познакомились с предпосылками появления блокчейна и осознали его преимущества. Чтиво было достаточно легкое. Ознакомиться с предыдущей статьей можно по ссылке ниже:

  • Блокчейн. Мягкое погружение. Часть 1

Сегодня же мы разберем принцип того, как устроен блокчейн. А сделаем это на примере биткоина – первой в истории криптовалюты, работающей на основе технологии блокчейна. И не только разберем, а по сути изобретем его с нуля. Постепенно такая внешне сложная и многоступенчатая конструкция как блокчейн станет ясной и понятной, а потом вы сможете блеснуть новыми знаниями перед своими друзьями.

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

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

Криптовалюта — это то, что циркулирует в данной системе как платежная единица.

Грубая аналогия:

Блокчейн — это сам банк, но распределенный, без головного офиса и топ-менеджмента, которым может пользоваться каждый человек на земле независимо от расы, вероисповедания, политических взглядов и *добавьте то, что считаете важным*. Нужен лишь интернет, причем не самый быстрый. А криптовалюта — это валюта, которая используется в банке для осуществления расчётов (в российском банке это рубли, доллары в США, а в блокчейне биткоина — Bitcoin. Да, здесь есть некоторая тавтология, т. к. в большинстве случаев сам блокчейн и криптовалюта, которая в нем циркулирует, имеют одно и то же название.

Зачем это надо вот лично мне?

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

Ну, а для тех, кто увидел для себя что-то занимательное, приступим!

И сразу предлагаю перейти к примерам, а в конце вернемся к вам с формальным определением. Теория – это, вообще, для скучных лекций в университете. На основе достаточно простых аналогий из реального мира, я покажу вам, как на самом деле работает блокчейн. Предположим, однажды вы осознали, что тратите деньги как-то неразумно, т.к. в начале месяца после зарплаты мед течет рекой, вы живете на широкую ногу и, возможно, часто одалживаете деньги людям из своего окружения. И, вроде бы, вы помните, что ваш друг Вася вам должен 2000, Лёха – 5000, а сосед по подъезду Альберт за последние несколько месяцев уже, вроде как, настрелял порядка 7-10 тысяч. Допустим, урегулирование всего этого безобразия вы начали с того, что будете записывать, сколько вы кому занимаете. Пусть вас зовут, к примеру, Герман. Вы главный герой этого повествования. Вы посидели, прикинули и пришли к выводу, что Альберт офигел в край и должен вам уже 10 000 рублей.

Это точка отсчёта вашей финансовой истории, а сам факт долга в 10 000 рублей – это запись в вашем блокноте.

Герман -> Альберт 10 000 руб.

На данном этапе здесь есть все, что вам нужно. Одна сторона, вторая сторона, сумма.

Продолжим. Альберт через некоторое время вернул 1000 рублей. Делаем новую запись.

Альберт -> Герман 1000 руб.

Имея две такие записи можем посчитать итог. Аналогично вы могли бы подсчитать ваши взаимные обязательства, если бы в блокноте было больше записей, причем и об операциях с другими людьми. В таком случае вы бы просто прошлись по списку, посмотрели все записи вида Герман -> Альберт, сложили все числа и получили бы общую сумму вашего займа Альберту. Затем сделали бы то же самое для записей вида Альберт –> Герман и получили бы общую сумму, которую он вам отдал. Останется лишь вычесть из первой суммы вторую, и вы получите, сколько вам должен Альберт на данный момент.

Итак, у Германа 1000 рублей, у Альберта 9000 рублей.

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

Альберт -> Герман 10000 руб.

И с нечистой совестью уходит домой. Это один из способов как при таком способе организации информации можно было бы “рассчитаться” с долгом. Что можно было бы сделать ещё?

Да можно было бы просто добавить новую запись:

Альберт → Герман 9000 руб.

И все, долга как ни бывало.

Ценность вашей финансовой истории уже нулевая – любой может вписать, изменить любую транзакцию, а вы даже не сможете проверить, верна она или нет.

Целостность

Защитимся от этого. Как? Математикой, конечно!

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

Берем любой текст, кидаем в хэш-функцию, а на выходе строка из мешанины букв и цифр, НО «мешанина» эта фиксированной длины

Строка:

Герман -> Альберт 2000 руб.

Хэш (результат хеширования):

40698c1a4e6e63977d250490b44273596b5f8c73371b76ccb2c1507995bdea4a

Строка:

Герман -> Лев Валерьянович Лещенко 10 000 000 руб.

Хэш:

7f2c05a4eeb66ace430d087860d555bfc6d0ce2e3ddbaf0c7922ee8e6f111251

Как видно из примера, две исходные строки отличаются по длине, но вот их хэши имеют одну и ту же длину. Очень удобно. По секрету скажу: хэш можно посчитать даже от файла в 100 Гб. И результат всегда будет таким вот коротким.

Хэши имеют несколько чудесных свойств:

  • Посчитать хэш любой строки можно за долю секунды. Посчитать исходное значение строки имея хэш – займет у вас тысячелетия. Буквально. Потому что так работает сама функция – она однонаправленная, и для нее не предполагается считать исходное значение по результату.
  • Изменение хотя бы одного символа в исходной строке ПОЛНОСТЬЮ меняет хэш.
  • Результат хеширования одной и той же строки всегда один и тот же (строго говоря, только у криптографических хэш-функций, коей и является функция SHA256 в биткоине)

Строка:

Герман -> Альберт 2000 руб.

Хэш (результат хеширования):

40698c1a4e6e63977d250490b44273596b5f8c73371b76ccb2c1507995bdea4a

Измененная строка:

Герман -> альберт 2000 руб.

Хэш:

2888a1392da12949b9080aaf21331de789f14d854222d269ba6ff5ecedb85c98

Я поменял всего одну заглавную букву в имени Альберт на строчную. Хэш изменился вплоть до каждого символа!

Эти три замечательных свойства позволяют контролировать целостность данных. То есть факт того, что данные никто не изменял.

Однако сами по себе хэши вас не спасут – ведь про хэши знают все (или могут загуглить). Если вы просто начнете писать хэш напротив ваших строк, это, конечно, подтвердит целостность строки, но только пока злой должник не подменит и сам хэш. Или не допишет, например, новую запись и новый хэш.

Поэтому нам нужен не просто список наших транзакций. Нам нужен связный список — такая структура данных, в которой каждый следующий элемент жестко связан с предыдущим каким-нибудь правилом:

Герман -> Альберт 2000 руб. = 40698c1a4e6e63977d250490b44273596b5f8c73371b76ccb2c1507995bdea4a
Альберт -> Герман 1000 руб. + 40698c1a4e6e63977d250490b44273596b5f8c73371b76ccb2c1507995bdea4a =
= d49f407b5aa6f80b6a2223284a2d82f46240ff5eacca903ab5ee853e2eec0ac3

Прим. в данном случае знак “+” означает не сложение как таковое, а конкатенацию. По сути просто дописывание одной строки в конец другой. Ох, сколько умных слов: хеширование, конкатенация… Пора, наверное, глоссарий заводить. Упс, еще одно слово 😅

И так далее. То есть каждая запись формируется путем хеширования строки текущей записи, к которой добавляется хэш прошлой. И такой список не займет много места – ведь хеширование любой строки дает короткий результат. И подменить что-то в середине просто не получится – обладатель списка всегда может его просто пересчитать, и если в список закралась подмена транзакции – хэши не будут совпадать. Если Альберт изменит 1 на 2 (т.е. как будто бы Альберт отдал не 1000 р., а 2000 р.), то финальный хэш выглядит абсолютно иначе:

Герман -> Альберт 2000 руб. = 40698c1a4e6e63977d250490b44273596b5f8c73371b76ccb2c1507995bdea4a
Альберт -> Герман 2000 руб. + 40698c1a4e6e63977d250490b44273596b5f8c73371b76ccb2c1507995bdea4a =
= 692040a862fb9d8a7112e90c91be82834bb03b9d2df5702f69acc19e8061f375

Стало сильно надежнее. Но пытливый ум Альберта может все равно сломать эту конструкцию – Альберт может также потратить время и пересчитать все хэши и заменить их значения. Ну, или просто дописать в конце новую запись, связав ее с последней.

Неужели все эти усложнения были зря? Вовсе нет! Добавим новую примочку в нашу схему. Заставим каждого, кто захочет внести запись в данную книжку, решать математическую задачу. Из минусов – вам тоже придется ее решать, чтобы внести запись. Из плюсов – Альберту придется поломать голову прежде, чем вас обмануть.

Но мы не можем ставить задачки типа «сколько будет дважды два?». Да и логарифмическое уравнение тоже не поможет, ведь хоть и Альберт плохо учился и не знает, как их решать – у него есть компьютер, а вот уж он точно легко с ними справится.

Поэтому нам нужна такая задача, чтобы даже компьютер не имел алгоритма ее решения. И все, что бы мог делать как человек, так и машина – это просто перебирать все возможные варианты до тех пор пока не повезет найти ответ. И таких задач много. Но создатель биткоина — Сатоши Накомото — выбрал следующую:

Найти такое число, чтобы сложив его с исходной строкой, хэш результата начинался бы с 10 нулей.

Запишем в явном виде:

Герман -> Альберт 2000 руб. + 40698c1a4e6e63977d250490b44273596b5f8c73371b76ccb2c1507995bdea4a + Х =
= 0000000000aj35ch……….

Как это выглядит на практике:

Как видите, к исходной строке мы добавляем впритык хэш предыдущей записи, а в конце добавляем 1 и, собственно, хэшируем

Опа, на 6 попытку получили хэш, у которого в начале присутствует один 0

Вы уже знаете, что стоит изменить в данной строке хотя бы один символ и хэш полностью изменится. Именно это свойство и гарантирует, что для такой задачи нет никакого способа решения, кроме полного перебора. Вам нужно просто подставлять вместо Х числа в выражение до тех пор, пока итоговый хэш не начнется с 10 нулей. Как только нужное число будет найдено (оно, кстати, называется nonce) – добавляем запись в наш список.

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

Если хотите, поиграйтесь в данном калькуляторе, взяв, к примеру, свое имя и попробовав получить в начале хэша хотя бы 2 нуля. Пишите в комментариях к статье (или в нашем чате), за сколько попыток у вас вышло :)

Децентрализация

Предположим на мгновение, что записей в книжке у Германа не так много, скажем 30. А чтобы посчитать хэш нужен один мощный игровой ноутбук, который может провести поиск одного хэша за 5 мин. Итого чтобы пересчитать все записи нам нужно:

30 * 5 = 150 (мин) ~ 2.5 ч

Относительно недолго, опять же при заданных условиях. И, вроде бы, Альберт готов раствориться в ночи победителем, но не тут то было!

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

Именно благодаря подобной синхронизации людей (компьютеров/узлов в сети) и решается проблема подмены операций!

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

Вам осталось мысленно подставить вместо действующих лиц в нашем рассказе компьютеры, каждый из которых хранит в себе ту самую книжку/блокнот с записями (базу данных) и которые постоянно проверяют верность своих и чужих записей (транзакций пользователей), а также занимаются обработкой новых, выполняя поиск хэша и все, блокчейн готов! Практически :)

Чтобы подытожить, дадим формальное определение блокчейна:

Блокчейн (англ. Blockchain, block – блок, chain — цепочка) – распределенная база данных, которая представляет собой цепочку записей – блоков – соединенных криптографическими методами. Каждый блок хранит в себе транзакции пользователей, причем только те, которые не противоречат протоколу.

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

Investment Hollow — компания единомышленников, которая видит в криптоиндустрии будущее, а также хороший источник заработка. Ежедневно анализируем рынок в поисках интересных проектов для инвестиций.

Мы всегда рады новым членам: как с опытом в сфере блокчейна и криптовалют, так и без. Присоединяйтесь к нашему сообществу, поможем, чем сможем и двинемся вместе покорять новые вершины 💪🏼

Понравилась статья? Поделить с друзьями:
  • Как написать самый английскими буквами
  • Как написать свое древо
  • Как написать самую простую программу для новичков
  • Как написать свое апи
  • Как написать самую простую игру