Ми будемо використовувати стандартний формат JSON для зберігання даних в кожному блоці. Дані для кожного блоку виглядають приблизно так:

{   
    "author": "author_name",
    "timestamp": "transaction_time", 
    "data": "transaction_data"
}

Щоб реалізувати це в Python, ми спочатку створюємо клас блоків з вищезгаданими атрибутами. Ми також хочемо зробити кожен блок унікальним, щоб не відбувалося дублювання:

class Block:
    def __init__(self, index, transactions, timestamp, previous_hash, nonce=0):
        self.index = index
        self.transactions = transactions
        self.timestamp = timestamp
        self.previous_hash = previous_hash
        self.nonce = nonce

Поки що не турбуйтеся про змінні – .previous_hashnonce (ми обговоримо їх пізніше)

Як ми вже згадували, однією з характеристик даних в кожному блоці є незмінність, яка може бути реалізована за допомогою криптографічної хеш-функції. Це односторонній алгоритм, який приймає довільні за розміром вхідні дані (відомі як key) і відображає їх на значеннях фіксованих розмірів (хеш-значення). Щоб проілюструвати, чим корисна для нас хеш-функція, розглянемо наступний приклад:

  1. Аліса і Боб змагаються за вирішення складної математичної задачі
  2. Аліса хоче довести Бобу, що вона правильно вирішила це спочатку, не ділячись рішенням, тому вона проводить свою відповідь через хеш-функцію та ділиться отриманим хеш-значенням з Бобом
  3. Боб нарешті вирішує проблему правильно, але чи отримала Аліса спочатку правильну відповідь?
  4. Тепер Аліса ділиться своєю відповіддю з Бобом, щоб він міг поставити її через хеш-функцію і перевірити, чи відповідає отримане хеш-значення тому, яке Аліса спочатку надала йому.
  5. Хеш-значення збігаються, а це означає, що Аліса дійсно правильно вирішила проблему до Боба.

Python може використовувати будь-яку стандартну криптографічну хеш-функцію, наприклад, у наборі функцій SHA-2 . Наприклад, SHA-256 може бути реалізований шляхом додавання методу всередині блоку класу: compute_hash

from hashlib import sha256
import json

class Block:
    def __init__(self, index, transactions, timestamp, previous_hash, nonce=0):
        self.index = index
        self.transactions = transactions
        self.timestamp = timestamp
        self.previous_hash = previous_hash
        self.nonce = nonce

    def compute_hash(self):
        block_string = json.dumps(self.__dict__, sort_keys=True)
        return sha256(block_string.encode()).hexdigest()

Хешування кожного блоку забезпечує безпеку кожного окремо, що надзвичайно ускладнює підробку даних у блоках. Тепер, коли ми створили єдиний блок, нам потрібен спосіб зв’язати їх разом.

Кодування Вашого Блокчейну

Давайте створимо новий клас для блокчейну. Для того, щоб забезпечити незмінність всього блокчейну, ми будемо використовувати розумний підхід включення хешу попереднього блоку в поточний блок. Усвідомлення всіх даних всередині кожного блоку встановлює механізм захисту цілісності всього ланцюга (як мінімум,частково). Ось чому ми включили змінну в клас блоків. Нам також потрібен спосіб ініціалізації блокчейну, тому ми визначаємо . При цьому створюється початковий блок з індексом 0 і попереднім хешем 0. Потім ми додаємо це до списку, який відстежує кожен блок. previous_hashcreate_genesis_blockmethodchain

import time

class Blockchain: 
    def __init__(self):
        self.unconfirmed_transactions = []
        self.chain = []
        self.create_genesis_block()
 
    def create_genesis_block(self):
        genesis_block = Block(0, [], time.time(), "0")
        genesis_block.hash = genesis_block.compute_hash()
        self.chain.append(genesis_block)
    @property
    def last_block(self):
        return self.chain[-1]

Система Proof-Of-Work Для Блокчейну

Хешування, яке ми описали досі, дає нам лише частину шляху. На даний момент для когось можливо змінити попередній блок у ланцюжку, а потім перекомпонувати кожен із наступних блоків, щоб створити ще один дійсний ланцюжок. Ми також хотіли б реалізувати спосіб для користувачів прийти до консенсусу щодо єдиної хронологічної історії ланцюжка в правильному порядку, в якому були здійснені транзакції. Щоб вирішити це, Сатоші Накамото створив систему proof-of-work.

Система proof-of-work поступово ускладнює виконання робіт, необхідних для створення нового блоку. Це означає, що тому, хто змінює попередній блок, доведеться переробити роботу блоку і всіх блоків, які слідують за ним. Система proof-of-work вимагає сканування на наявність значення, яке починається з певної кількості нульових бітів при хешуванні. Ця величина відома як значення nonce. Кількість вихідних нульових бітів відома як difficulty. Середня робота, необхідна для створення блоку, збільшується в геометричній прогресії з кількістю початкових нульових бітів, а тому, збільшуючи складність(difficulty) з кожним новим блоком, ми можемо в достатній мірі перешкодити користувачам модифікувати попередні блоки, так як переробити наступні блоки і наздогнати інші практично неможливо.

Для реалізації цієї системи ми можемо додати метод в клас блокчейн: proof_of_work

difficulty = 2
def proof_of_work(self, block):
        block.nonce = 
        computed_hash = block.compute_hash()
        while not computed_hash.startswith('0' * Blockchain.difficulty):
            block.nonce += 1
            computed_hash = block.compute_hash()
        return computed_hash

Тепер, коли у нас є система, яка забезпечує безпеку всього ланцюжка, ми додаємо ще кілька методів до класу блокчейн, щоб зібрати все разом, щоб ми могли фактично побудувати ланцюжок. Спочатку ми будемо зберігати дані кожної транзакції. Як тільки ми підтвердимо, що новий блок є дійсним доказом, який задовольняє критеріям складності, ми можемо додати його до ланцюжка. Процес виконання обчислювальної роботи в рамках цієї системи широко відомий як Майнінг. unconfirmed_transactions

def add_block(self, block, proof):
        previous_hash = self.last_block.hash
        if previous_hash != block.previous_hash:
            return False
        if not self.is_valid_proof(block, proof):
            return False
        block.hash = proof
        self.chain.append(block)
        return True
 
def is_valid_proof(self, block, block_hash):
        return (block_hash.startswith('0' * Blockchain.difficulty) and
                block_hash == block.compute_hash())

def add_new_transaction(self, transaction):
            self.unconfirmed_transactions.append(transaction)
 
def mine(self):
        if not self.unconfirmed_transactions:
            return False
 
        last_block = self.last_block
 
        new_block = Block(index=last_block.index + 1,
                          transactions=self.unconfirmed_transactions,
                          timestamp=time.time(),
                          previous_hash=last_block.hash)
 
        proof = self.proof_of_work(new_block)
        self.add_block(new_block, proof)
        self.unconfirmed_transactions = []
        return new_block.index

REST API Для Блокчейну

Для того, щоб використовувати його, нам потрібно буде побудувати інтерфейс, з яким можуть взаємодіяти кілька користувачів або вузлів. Для цього я буду використовувати Flask для створення REST-API. Flask – це легкий фреймворк веб-додатків, написаний для Python.

from flask import Flask, request
import requests
 
app =  Flask(__name__)

blockchain = Blockchain()

По-перше, ми визначаємо наш веб-додаток і створюємо локальний блокчейн. Потім ми створюємо кінцеву точку, яка дозволяє нам надіслати запит для відображення відповідної інформації блокчейну.

@app.route('/chain', methods=['GET'])
def get_chain():
    chain_data = []
    for block in blockchain.chain:
        chain_data.append(block.__dict__)
    return json.dumps({"length": len(chain_data),
                       "chain": chain_data})
app.run(debug=True, port=5000)

Перш ніж ми зможемо запитати його, нам спочатку потрібно активувати додаток блокчейн у командному рядку, запустивши:

python3 Blockchain.py

Ви повинні побачити приблизно таке:

* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 105-118-129

Потім в іншій оболонці ми можемо відправити запит з cURL, запустивши:

curl  http://127.0.0.1:5000/chain

На виході повинна міститися вся інформація про блокчейн:

{"length": 1, "chain": [{"index": 0, "transactions": [], "timestamp": 1576665446.403836, "previous_hash": "0", "nonce": 0, "hash": "e2a1ec32fcf89d0388f3d0d8abcd914f941d056c080df1c765a3f6035626fc94"}]}

Досить круто, правда?! Зауважимо, що ланцюжок має лише один блок у цій точці (блок генезису). Не соромтеся скористатися функцією майнінгу, яку ми побудували, щоб додати більше блоків у ланцюжок.