Строим собственный блокчейн на Python и разбираемся в его особенностях

Перед тем как начать строить блокчейн, необходимо понять его основы.

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

Его главная особенность заключается в том, что хранение данных не происходит в одном центральном органе. Благодаря этому не получится сфальсифицировать данные, взломав только один главный пункт. Самое известное применение блокчейна  —  это Bitcoin, один из видов криптовалют.

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

Требования

Убедитесь, что у вас установлена самая последняя версия Python, а также две его библиотеки  —  Flask и Requests.

pip install Flask request

Для создания запросов рекомендуем скачать Postman.

Начало

Создадим новый файл в Python и назовём его app.py. Сначала нам нужно написать класс Blockchain:

class Blockchain(object):

После создания конструктора добавляем пустой список для хранения блокчейна:

def __init__(self): 
 
  self.chain = []

Ещё нам понадобится список для хранения транзакций, поэтому получаем вот такой класс Blockchain:

class Blockchain(object):
    def __init__(self):
       self.chain = []
       self.current_transactions = []

Для внесения данных в эти списки создадим два метода: addBlock() и addTransaction().

def addBlock(self):
    pass

def addTransaction(self):
    pass

Пока эти функции не будут выполнять никаких действий, но скоро мы к ним вернёмся.

Далее хешируем данные, чтобы обезопасить их. Создадим метод hash(). Поскольку он статический, не забудьте добавить @staticmethod:

@staticmethod
    def hash(block):
        pass

И наконец напишем метод lastBlock():

@property
    def lastBlock(self):
        pass

Создание блока

Теперь подумаем над тем, как будет выглядеть блок. В первую очередь для него нужен индекс index и временной штамп timestamp. А как же Proof? Что это? К этому мы вернёмся позже.

Также необходимо добавить список транзакций transactions и хеш предыдущего блока previous_hash.

Внесение новых транзакций

Новые транзакции создаём с помощью метода addTransaction(). Теперь заполним его кодом.

Сначала введём несколько аргументов: self, sender, recipient, amount.

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

Далее добавим новую транзакцию в список:

self.current_transactions.append({
            ‘sender’: sender,
            ‘recipient’: recipient,
            ‘amount’: amount,
          })

В конце этот метод должен возвращать индекс транзакции:

return self.last_block[‘index’] + 1

Что придаёт ему такой вид:

def addTransaction(self, sender, recipient, amount):
        self.current_transactions.append({
            'sender': sender,
            'recipient': recipient,
            'amount': amount,
        })

        return self.last_block['index'] + 1

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

Прежде чем начать строить новые блоки, нужно создать в конструкторе один центральный блок:

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

Затем заполняем метод addBlock():

def addBlock(self, proof, previous_hash=None):
        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

Как вы могли заметить, нужно создать метод hash(), чтобы код заработал.

@staticmethod
    def hash(block):
        block_string = json.dumps(block, sort_keys=True).encode()
        return hashlib.sha256(block_string).hexdigest()

Использовать хеширование довольно легко. По сути, это шифрование строки.

Дальше нам нужно добавить ещё один метод  —  lastBlock():

@property
    def last_block(self):
        return self.chain[-1]

Что такое Proof

Proof of Work (доказательство выполнения работы)  —  это алгоритм, основная цель которого препятствовать кибератакам, например DDoS-атаке. Впервые идея о Proof of Work (PoW) была опубликована Синтией Дворк и Мони Наором в 1993 году.

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

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

Применение PoW

Теперь реализуем Proof of Work для нашего блокчейна со следующей задачей:

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

Чтобы её внести, добавим следующие модули:

import hashlib
import json
 
from time import time
from uuid import uuid4

Напишем два новых методов:

def proof_of_work(self, last_proof):

И:

@staticmethod
def valid_proof(last_proof, proof):

Заполним их вот так:

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

        return proof

    @staticmethod
    def valid_proof(last_proof, proof):

        guess = f'{last_proof}{proof}'.encode()
        guess_hash = hashlib.sha256(guess).hexdigest()
        return guess_hash[:4] == "0000"

Применение Flask для API

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

from flask import Flask
app = Flask(__name__)
node_identifier = str(uuid4()).replace('-', '')
blockchain = Blockchain()
@app.route('/mine', methods=['GET'])
def mine():
    return "Mining a new Block"
  
@app.route('/transactions/new', methods=['POST'])
def new_transaction():
    return "Adding 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)

Подробнее о Flask можно узнать из его документации.

Новые транзакции

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

@app.route('/transactions/new', methods=['POST'])
def newTransaction():
    values = request.get_json()
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

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

Здесь нам нужно добавить немного кода перед тем как заработает точка майнинга. И должно произойти лишь:

· доказательная работа;

· вознаграждения майнера;

· новый блок + добавление его к блокчейну.

@app.route('/mine', methods=['GET'])
    def mine():
        last_block = blockchain.last_block
        last_proof = last_block['proof']
        proof = blockchain.proof_of_work(last_proof)
        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 Created",
            'index': block['index'],
            'transactions': block['transactions'],
            'proof': block['proof'],
            'previous_hash': block['previous_hash'],
        }
        return jsonify(response), 200

Запуск API блокчейна

Запустим API, открыв программу Python:

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

Для майнинга блока можно использовать Postman или Curl:

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

А узнать количество замайненных блоков можно на http://localhost:5000/chain. Появится цепь в формате JSON:

{
  "chain": [
    {
      "index": 1,
      "previous_hash": 1,
      "proof": 100,
      "timestamp": 1606910340,
      "transactions": []
    },
}

Полный код

mport requests
from time import time
import hashlib
import json
from flask import Flask
from time import time
from uuid import uuid4


class Blockchain(object):
    app = Flask(__name__)
    node_identifier = str(uuid4()).replace('-', '')

    blockchain = Blockchain()

    @app.route('/mine', methods=['GET'])
    def mine():
  
        last_block = blockchain.last_block
        last_proof = last_block['proof']
        proof = blockchain.proof_of_work(last_proof)

    
        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

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

        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

    @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)

    def __init__(self):
        self.chain = []
        self.current_transactions = []

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

        return proof

    @staticmethod
    def valid_proof(last_proof, proof):

        guess = f'{last_proof}{proof}'.encode()
        guess_hash = hashlib.sha256(guess).hexdigest()
        return guess_hash[:4] == "0000"

    def addBlock(self, proof, previous_hash=None):
        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 addTransaction(self, sender, recipient, amount):
        self.current_transactions.append({
            'sender': sender,
            'recipient': recipient,
            'amount': amount,
        })

        return self.last_block['index'] + 1

    @staticmethod
    def hash(block):
        block_string = json.dumps(block, sort_keys=True).encode()
        return hashlib.sha256(block_string).hexdigest()

    @property
    def last_block(self):
        return self.chain[-1]

Заключение

На этом всё. Вот как вы можете создать простой блокчейн на Python!

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Bryan Dijkhuizen: Understand Blockchains by Building Your Own in Python

Предыдущая статьяПереоткрываем для себя эмулятор Android для ускорения тестирования
Следующая статьяВ США ограничивают использование технологий распознавания лиц