Галантерея | Научите вас создавать блокчейн с помощью Python за считанные минуты

Python алгоритм искусственный интеллект блокчейн

Автор | Крошечный медвежонок
Источник | Глобальный искусственный интеллект

Исходный код: https://github.com/xilibi2003/blockchain

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

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

Готов к работе

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

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

Подготовка окружающей среды

Подготовка среды, убедитесь, что установлены Python3.6+, pip, Flask, запросы.
способ установки:

1
pip install Flask==0.12.2 requests==2.18.4

Также требуется HTTP-клиент, такой как Postman, cURL или другие.

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

Начните создавать Блокчейн


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

Блокчейн-класс

Сначала создайте класс Blockchain и создайте два списка в конструкторе, один для хранения блокчейнов, а другой для хранения транзакций.

Вот скелет класса Blockchain:

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):
        # Creates a new Block and adds it to the chain
        pass
    
    def new_transaction(self):
        # Adds a new transaction to the list of transactions
        pass
    
    @staticmethod
    def hash(block):
        # Hashes a Block
        pass

    @property
    def last_block(self):
        # Returns the last Block in the chain
        pass

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

блочная структура

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

Ниже представлена ​​структура блока:

1
2
3
4
5
6
7
8
9
10
11
12
13
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
class Blockchain(object):
    ...
    
    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> The index of the Block that will hold this transaction
        """

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

        return self.last_block['index'] + 1

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

Создать новый блок

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

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

Чтобы создать блок генезиса, нам также необходимо улучшить методы 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
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):
        """
        生成新块
        :param proof: <int> The proof given by the Proof of Work algorithm
        :param previous_hash: (Optional) <str> Hash of previous Block
        :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]),
        }

        # Reset the current list of transactions
        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> The index of the Block that will hold this transaction
        """
        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 hash值
        :param block: <dict> Block
        :return: <str>
        """

        # We must make sure that the Dictionary is Ordered, or we'll have inconsistent hashes
        block_string = json.dumps(block, sort_keys=True).encode()
        return hashlib.sha256(block_string).hexdigest()

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

Понимание доказательства работы

Для построения новых блоков используется алгоритм доказательства работы (PoW). Цель PoW — найти номер, отвечающий определенным условиям,Это число трудно подсчитать, но легко проверить. Это основная идея Proof of Work.

Для простоты понимания, вот пример:

Предположим, что хеш-значение произведения целого числа x, умноженного на другое целое число y, должно заканчиваться 0, т. е. hash(x * y) = ac23dc…0. Пусть переменная x = 5, найдите значение y?

Реализовано на Python следующим образом:

1
2
3
4
5
6
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. Потому что:

1
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
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个0开头
         - 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个0开头?
        :param last_proof: <int> Previous Proof
        :param proof: <int> Current Proof
        :return: <bool> True if correct, False if not.
        """

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

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

Теперь, когда класс Blockchain в основном завершен, следующим шагом будет использование HTTP-запросов для взаимодействия.

Блокчейн как интерфейс API

Мы будем использовать фреймворк Python Flask, облегченный фреймворк веб-приложений, который упрощает сопоставление сетевых запросов с функциями Python, а теперь давайте запустим Blockchain в сети на основе Flask.

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

  • /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):
    ...


# Instantiate our Node
app = Flask(__name__)

# Generate a globally unique address for this node
node_identifier = str(uuid4()).replace('-', '')

# Instantiate the 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: Создайте узел.
Строка 18: Создайте случайное имя для узла.
Строка 21: экземпляр класса Blockchain.
Строки 24–26: создайте GET-интерфейс /mine.
Строки 28–30: Создайте интерфейс /transactions/new POST, который может отправлять данные транзакции на интерфейс.
Строка 32-38: Создайте интерфейс /chain, чтобы вернуть всю цепочку блоков.
Строки 40–41: служба работает на порту 5000.

отправить транзакцию

Структура данных транзакции, отправляемая узлу, выглядит следующим образом:

1
2
3
4
5
{
 "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()

    # Check that the required fields are in the POST'ed data
    required = ['sender', 'recipient', 'amount']
    if not all(k in values for k in required):
        return 'Missing values', 400

    # Create a new Transaction
    index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])

    response = {'message': f'Transaction will be added to Block {index}'}
    return jsonify(response), 201

добыча полезных ископаемых

Майнинг — это волшебство, оно очень простое, оно делает три вещи:

  1. Вычислительное доказательство работы PoW

  2. Предоставьте майнеру (самому себе) монету, добавив транзакцию

  3. Создайте новый блок и добавьте его в цепочку

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
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():
    # We run the proof of work algorithm to get the next proof...
    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,
    )

    # Forge the new Block by adding it to the chain
    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. На данный момент наша цепочка блоков завершена, давайте на самом деле запустим ее.

запустить блокчейн

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

Запустите сервер:

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

Давайте майнить, запросив http://localhost:5000/mine

Добавить новую транзакцию через почтовый запрос

Если вы не используете Postman, используется тот же оператор cURL:

1
2
3
4
5
$ 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
}

Согласованность (Консенсус)

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

узел регистрации

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

  1. / Узлы / Регистрация для получения нового URL в виде списка узлов

  2. /nodes/resolve выполняет алгоритм консенсуса, разрешает любые конфликты и гарантирует, что узлы имеют правильную цепочку

Мы модифицируем функцию инициализации Blockchain и предоставляем метод для регистрации узлов:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
from urllib.parse import urlparse
...


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

    def register_node(self, address):
        """
        Add a new node to the list of nodes
        :param address: <str> Address of node. Eg. 'http://192.168.0.5:5000'
        :return: None
        """

        parsed_url = urlparse(address)
        self.nodes.add(parsed_url.netloc)

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

Реализовать алгоритм консенсуса

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

Мы используем алгоритм для достижения консенсуса в сети.

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
...
import requests


class Blockchain(object)
    ...
    
    def valid_chain(self, chain):
        """
        Determine if a given blockchain is valid
        :param chain: <list> A blockchain
        :return: <bool> True if valid, False if not
        """

        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")
            # Check that the hash of the block is correct
            if block['previous_hash'] != self.hash(last_block):
                return False

            # Check that the Proof of Work is correct
            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

        # We're only looking for chains longer than ours
        max_length = len(self.chain)

        # Grab and verify the chains from all the nodes in our network
        for node in neighbours:
            response = requests.get(f'http://{node}/chain')

            if response.status_code == 200:
                length = response.json()['length']
                chain = response.json()['chain']

                # Check if the length is longer and the chain is valid
                if length > max_length and self.valid_chain(chain):
                    max_length = length
                    new_chain = chain

        # Replace our chain if we discovered a new, valid chain longer than ours
        if new_chain:
            self.chain = new_chain
            return True

        return False

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

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

Давайте добавим два маршрута, один для регистрации узла и один для разрешения конфликтов.

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: //локальный:5000 и http://локальный:5001

1
2
pipenv run python blockchain.py
pipenv run python blockchain.py -p 5001

Затем выкопайте два блока на узле 2, чтобы убедиться, что это более длинная цепочка, а затем получите доступ к интерфейсу /nodes/resolve на узле 1. В это время цепочка узла 1 будет заменена цепочкой узла 2 через консенсус алгоритм.

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

Источник: https://learnblockchain.cn/2017/10/27/build_blockchain_by_python/

Исходный код: https://github.com/xilibi2003/blockchain