Самый быстрый способ изучить работу Блокчейнов – это создать свой блокчейн. Стоит лишь только попробовать!
Скорее всего вы здесь, также, как и я, потому что были недовольны резким подъемом Криптовалюты. И вы хотите узнать, как же работают Блокчейны – фундаментальная технология, которая стоит за всеми криптовалютами.
Но понять, как работают Блокчейны не так просто – ну или, как минимум для меня, это сложно. Я с трудом пробирался сквозь множество непонятных видеороликов, сомнительных туториалов и боролся с сильным разочарованием от очень малого количества примеров. Поэтому мы с вами напишем свой блокчейн.
Вообще, я люблю изучать что-либо практическим путём. Это заставляет меня разобраться с сутью вопроса на уровне кода, который позволяет уловить суть происходящего. Если вы делаете точно также, то к концу этого туториала, у вас будет полностью функционирующий свой Блокчейн, и твёрдое понимание того, как они работают.
Перед тем как мы начнем…
Обратите внимание, что блокчейн – это неизменяемая последовательная цепочка записей, называемых Блоками. Они могут состоять из транзакций, файлов, или любой другой информации, которая вам необходима. Но самым важным здесь является то, что все они связаны вместе с помощью хэшей.
Если вы не уверены в том, что такое хэш, то вот объяснение.
На кого нацелен данный туториал?
У вас не должно возникать трудностей с чтением синтаксиса и написанием базовых вещей на 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 майнинга – это то, где происходит магия, и в ней нет ничего сложного. Здесь совершаются три следующих вещи:
- Расчет алгоритма PoW;
- Майнер(ы) получают награду в виде транзакции, которая гарантируем им 1 биткойн;
- Формирование нового блока, путем его добавления в цепочку.
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-ов:
- /nodes/register чтобы можно было принимать список новых узлов в форме URL;
- /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
Ссылка на оригинальную статью
Перевод: Александр Давыдов
Время на прочтение
9 мин
Количество просмотров 76K
Доброго всем! Мы тут потихоньку начали исследовать новое совсем для нас направление для обучения — блокчейны и нашли то, что оказалось интересным в рамках нашего курса по Python, в том числе. Чем, собственно, и хотим поделиться с вами.
Я могу узнать, когда у меня появился первый Bitcoin, из истории кошелька в моем аккаунте на Coinbase — входящая транзакция в 2012 году в подарок за регистрацию. Bitcoin в то время стоил около 6.50$. Если бы я сохранил те 0.1 BTC, на момент написания статьи это бы уже стоило более 500$. Если кому-то интересно, я продал их, когда Bitcoin стоил 2000$. Так что я получил только 200$ вместо ныне возможных 550$. Не стоило торопиться.
О существовании Bitcoin я знал, но особо не интересовался. Я видел взлеты и падения курса $/BTC. Я видел, как люди говорят, что за ним будущее, а видел статьи о его полной бессмысленности. Но личного мнения у меня не было — просто наблюдал со стороны.
Точно так же я почти не следил за блокчейнами. Но в последнее время мой отец несколько раз упоминал, что на CNBC и Bloomberg, которые он смотрит по утрам, часто рассказывают о блокчейнах, и он понятия не имеет, что это.
И тогда я внезапно понял, что нужно чуть глубже разобраться в этой теме. И начал с “исследования” — прочитал огромное количество статей в интернете, объясняющую их суть. Некоторые были хорошие, некоторые плохие, некоторые глубокие, а некоторые очень поверхностные.
Чтения оказалось недостаточно, а если существует одна вещь, которую я знаю наверняка, так это то, что чтение не объяснит и сотой доли того, что объяснит программирование. И так я понял, что стоит написать свой собственный локальный блокчейн.
Нужно учитывать, что есть большая разница между базовым блокчейном, который я описываю и “профессиональным” блокчейном. Эта цепь не создаст криптовалюту. Блокчейны не требуют производства монет, которые можно продавать и менять на физические деньги.
Блокчейны используются для хранения и подтверждения информации. Монеты побуждают узлы участвовать в валидации, но их наличие не обязательно.
Я пишу пост по нескольким причинам: 1) Чтобы люди, прочитавшие его, смогли узнать больше о блокчейнах; 2) Чтобы я смог понять больше, объяснив код, а не просто написав его.
В этом посте я покажу способ хранения данных блокчейна и генерации начального блока, синхронизацию узла с локальными данными блокчейна, отображение блокчейна (что впоследствии будет использоваться для синхронизации с другими узлами), а затем, майнинг и создание валидных новых блоков. В первом посте не будет никаких других узлов. Никаких кошельков, пиров, важных данных. О них поговорим позднее.
В двух словах
Если вы не хотите углубляться в детали и читать код, или если вы наткнулись на этот пост, рассчитывая на статью, которая бы понятным языком объясняла блокчейны, я постараюсь кратко резюмировать, как они работают.
На самом высоком уровне, блокчейн — база данных, где каждый, участвующий в блокчейне, может хранить, просматривать, подтверждать и никогда не удалять данные.
На более низком уровне, данные в этих блоках могут быть чем угодно, пока это позволяет конкретный блокчейн. Например, данные в Bitcoin блокчейне — исключительно транзакции Bitcoin между аккаунтами. Ethereum блокчейн позволяет как аналогичные транзакции Ether, так и транзакции, использующиеся для запуска кода.
Прежде чем блок будет создан и объединен в блокчейн, он подтверждается большинством людей, работающих над блокчейном — их называют узлами. Настоящий блокчейн — цепь, состоящая из огромного множества блоков, подтвержденных большинством узлов. Таким образом, если узел попытается изменить данные предыдущего блока, новые блоки не будут валидны, и узлы не будут доверять данным из некорректного блока.
Не волнуйтесь, если это сбивает с толку. Мне понадобилось время, чтобы самому вникнуть в это, и еще больше времени на написание такого поста, чтобы даже моя сестра (которая ничего не знает о блокчейнах) смогла понять.
Если хотите изучить код, посмотрите ветку part 1 на Github. Смело присылайте мне любые вопросы, комментарии, правки и похвалы (если вы в настроении сделать что-то особо хорошее), или просто пишите в твиттер.
Шаг 1 — Классы и Файлы
Первый шаг — написание класса, обрабатывающего блоки при запуске узлов. Я назову этот класс Block. Честно говоря, много делать не придется. В функции __init__ мы будем верить, что вся необходимая информация уже представлена в словаре. Для производственного блокчейна — это не самое мудрое решение, но подходит в качестве примера, потому что код пишу только я. Также я напишу метод, запаковывающий важную информацию блока в словарь, а после заведу более удобный способ для отображения информации блока при его печати в терминал.
class Block(object):
def __init__(self, dictionary):
'''
We're looking for index, timestamp, data, prev_hash, nonce
'''
for k, v in dictionary.items():
setattr(self, k, v)
if not hasattr(self, 'hash'): #in creating the first block, needs to be removed in future
self.hash = self.create_self_hash()
def __dict__(self):
info = {}
info['index'] = str(self.index)
info['timestamp'] = str(self.timestamp)
info['prev_hash'] = str(self.prev_hash)
info['hash'] = str(self.hash)
info['data'] = str(self.data)
return info
def __str__(self):
return "Block<prev_hash: %s,hash: %s>" % (self.prev_hash, self.hash)
Чтобы создать первый блок, запустим этот простой код:
def create_first_block():
# index zero and arbitrary previous hash
block_data = {}
block_data['index'] = 0
block_data['timestamp'] = date.datetime.now()
block_data['data'] = 'First block data'
block_data['prev_hash'] = None
block = Block(block_data)
return block
Отлично. Последний вопрос в этой части — где хранить данные в файловой системе. Это необходимо, если мы не хотим потерять локальные данные блока при отключении узла.
Я назову папку с данными ‘chaindata’, в какой-то степени подражая схеме папок Etherium Mist. Каждому блоку теперь присвоен отдельный файл, названный по его индексу. Нужно убедиться, что имена файлов содержат в начале достаточное количество нулей, чтобы блоки перечислялись по порядку.
С учетом кода выше, нужно написать следующее для создание первого блока:
#check if chaindata folder exists.
chaindata_dir = 'chaindata'
if not os.path.exists(chaindata_dir):
#make chaindata dir
os.mkdir(chaindata_dir)
#check if dir is empty from just creation, or empty before
if os.listdir(chaindata_dir) == []:
#create first block
first_block = create_first_block()
first_block.self_save()
Шаг 2 — Синхронизация блокчейна, локально
Прежде чем начать майнинг, интерпретацию данных или отправку/создание новых данных для цепи, необходимо синхронизировать узел. В нашем случае других узлов нет, поэтому я говорю только о чтении блоков из локальных файлов. В будущем частью синхронизации будет не только чтение из файлов, но и коммуникация с пирами для сбора блоков, которые были сгенерированы, пока ваш узел не был запущен.
def sync():
node_blocks = []
#We're assuming that the folder and at least initial block exists
chaindata_dir = 'chaindata'
if os.path.exists(chaindata_dir):
for filename in os.listdir(chaindata_dir):
if filename.endswith('.json'): #.DS_Store sometimes screws things up
filepath = '%s/%s' % (chaindata_dir, filename)
with open(filepath, 'r') as block_file:
block_info = json.load(block_file)
block_object = Block(block_info) #since we can init a Block object with just a dict
node_blocks.append(block_object)
return node_blocks
Пока просто и красиво. Чтение строк из файлов их загрузка в структуры данных не требуют чрезмерно сложного кода. Пока это работает. Но в будущих постах, где я буду писать о возможностях коммуникации разных узлов, эта функция sync станет значительно сложнее.
Шаг 3 — Отображение блокчейна
Теперь наш блокчейн находится в памяти, и поэтому я хочу отобразить цепь в браузере. Для того, чтобы сделать это прямо сейчас, есть две причины. Во-первых, необходимо подтвердить в браузере, что изменения произошли. Во-вторых, я буду использовать браузер в будущем для просмотра и совершения каких-либо операций, связанных с блокчейном. Например, отправка транзакций или управление кошельком.
Для этого я использую Flask — у него низкий порог вхождения, и я решил, что он подходит для наших целей.
Ниже представлен код для отображения json блокчейна. Я проигнорирую импорты для экономии места.
node = Flask(__name__)
node_blocks = sync.sync() #inital blocks that are synced
@node.route('/blockchain.json', methods=['GET'])
def blockchain():
'''
Shoots back the blockchain, which in our case, is a json list of hashes
with the block information which is:
index
timestamp
data
hash
prev_hash
'''
node_blocks = sync.sync() #regrab the nodes if they've changed
# Convert our blocks into dictionaries
# so we can send them as json objects later
python_blocks = []
for block in node_blocks:
python_blocks.append(block.__dict__())
json_blocks = json.dumps(python_blocks)
return json_blocks
if __name__ == '__main__':
node.run()
Запустите этот код, зайдите на localhost:3000/blockchain.json и увидите текущий блок.
Шаг 4 — “Майнинг”, также известный как создание блока
Сейчас есть только генезис блок, но если у нас появится больше данных, которые необходимо хранить и распределять, нужен способ включить это в новый блок. Вопрос — как создать новый блок и соединить его с предыдущим.
Сатоши описывает это следующим образом в Bitcoin whitepaper. Учтите, что “timestamp сервер” назван “узлом”.
“Начнем описание нашего решения с timestamp сервера. Его работа заключается в хэшировании блока данных, на который нужно поставить timestamp, и открытой публикации этого хэша… Timestamp показывает, что в данный момент конкретные данные существовали и потому попали в хэш блока. Каждый хэш включает в себя предыдущий timestamp: так выстраивается цепь, где очередное звено укрепляет все предыдущие.”
Скриншот изображения, прикрепленного под описанием:
Основная идея раздела — при необходимости соединить блоки, мы создаем хэш информации о новом блоке, включая время создания блока, хэш предыдущего блока и информацию в самом блоке. Я буду называть всю эту информацию “хедером” блока. Таким образом, мы можем проверить корректность блока, посчитав все хэши перед ним, подтвердив последовательность.
В данном случае хедер, который я создаю, объединяет значения строки в одну огромную строку. Я включил следующие данные:
- Индекс, показывающий каким по счету является блок;
- Хэш предыдущего блока;
- Данные — просто случайные строки. Для bitcoin они называются Merkle root и содержат информацию о транзакциях;
- Timestamp майнинга этого блока.
def generate_header(index, prev_hash, data, timestamp):
return str(index) + prev_hash + data + str(timestamp)
Поясню один момент — объединение строк информации не является обязательным для создания хедера. Требование состоит в том, чтобы каждый знал, как генерировать хедер блока и хэш предыдущего блока внутри него. Делается это для того, чтобы каждый мог убедиться в корректности хэша в новом блоке и подтвердить связь между двумя блоками.
Хедер Bitcoin значительно сложнее объединения строк. Он использует хэши данных и времени и завязан на то, как данные расположены в памяти. Но в нашем случае объединения строк достаточно.
Теперь у нас есть хедер и можно вычислить валидность хэша. Я буду использовать метод, отличающийся от метода Bitcoin, но все равно запущу хедер блока через функцию sha256.
def calculate_hash(index, prev_hash, data, timestamp, nonce):
header_string = generate_header(index, prev_hash, data, timestamp, nonce)
sha = hashlib.sha256()
sha.update(header_string)
return sha.hexdigest()
Для майнинга блока мы используем функцию выше, чтобы получить хэш, положить его в новый блок и сохранить этот блок в директории chaindata.
node_blocks = sync.sync()
def mine(last_block):
index = int(last_block.index) + 1
timestamp = date.datetime.now()
data = "I block #%s" % (int(last_block.index) + 1) #random string for now, not transactions
prev_hash = last_block.hash
block_hash = calculate_hash(index, prev_hash, data, timestamp)
block_data = {}
block_data['index'] = int(last_block.index) + 1
block_data['timestamp'] = date.datetime.now()
block_data['data'] = "I block #%s" % last_block.index
block_data['prev_hash'] = last_block.hash
block_data['hash'] = block_hash
return Block(block_data)
def save_block(block):
chaindata_dir = 'chaindata'
filename = '%s/%s.json' % (chaindata_dir, block.index)
with open(filename, 'w') as block_file:
print new_block.__dict__()
json.dump(block.__dict__(), block_file)
if __name__ == '__main__':
last_block = node_blocks[-1]
new_block = mine(last_block)
save_block(new_block)
Готово! Но при таком типе создания блока кто угодно с самым быстрым CPU сможет создавать самые длинные цепи, которые другие узлы посчитают корректными. Нужен способ снизить скорость создания блока и подтверждение до перехода к следующему блоку.
Шаг 5 — Доказательство выполнения работы
Для снижения скорость я использую Доказательство выполнения работы, как и Bitcoin. Доказательство доли владения — другой способ, используемый в блокчейнах для достижения консенсуса, но в этом случае я воспользуюсь работой.
Способ сделать это — установить требования к структуре хэша блока. Как и в случае с bitcoin, необходимо убедиться, что хэш начинается с определенного количества нулей, перед тем, как перейти к следующему. А для этого нужно добавить в хедер дополнительную информацию — случайно перебираемое число (nonce).
def generate_header(index, prev_hash, data, timestamp, nonce):
return str(index) + prev_hash + data + str(timestamp) + str(nonce)
Теперь функция майнинга настроена для создания хэша, но если хэш блока не содержит достаточного количества нулей, мы увеличиваем значение nonce, создаем новый хедер, вычисляем новый хэш и проверяем хватает ли нулей.
NUM_ZEROS = 4
def mine(last_block):
index = int(last_block.index) + 1
timestamp = date.datetime.now()
data = "I block #%s" % (int(last_block.index) + 1) #random string for now, not transactions
prev_hash = last_block.hash
nonce = 0
block_hash = calculate_hash(index, prev_hash, data, timestamp, nonce)
while str(block_hash[0:NUM_ZEROS]) != '0' * NUM_ZEROS:
nonce += 1
block_hash = calculate_hash(index, prev_hash, data, timestamp, nonce)
block_data = {}
block_data['index'] = int(last_block.index) + 1
block_data['timestamp'] = date.datetime.now()
block_data['data'] = "I block #%s" % last_block.index
block_data['prev_hash'] = last_block.hash
block_data['hash'] = block_hash
block_data['nonce'] = nonce
return Block(block_data)
Отлично. Новый блок содержит валидное значение nonce, поэтому другие узлы могут подтвердить хэш. Мы можем сгенерировать, сохранить и распределить новый блок остальным.
Заключение
На этом все! Пока что. Осталось еще много вопросов и фичей в блокчейнах, которые я не объяснил.
Например, как задействовать другие узлы? Как узлы передают данные, когда включаются в блок? Существуют ли иные способы хранения данных кроме огромных строк данных?
Ответы на эти вопросы можно будет найти в следующих частях этой серии постов, как только я сам найду на них ответы. Пожелания по содержанию можно писать мне в твиттер, в комментарии к посту или через форму обратной связи!
Спасибо моей сестре Саре за уточняющие вопросы о блокчейнах и помощь в редактировании поста!
THE END
Комментарии, вопросы, как всегда, приветствуются и тут, и на дне открытых дверей.
#статьи
- 17 авг 2022
-
13
Рассказываем, для чего нужен блокчейн в программировании и как его используют, а также пишем небольшое приложение.
Иллюстрация: Merry Mary для Skillbox Media
Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.
Блокчейн — это последовательная цепочка блоков, которые защищены от подмены. А блок — это файл, содержащий информацию о предыдущих действиях, например транзакциях о переводе криптовалют. Но при этом ошибочно считать, что эта технология используется только в криптовалюте, — на самом деле применить блокчейн можно где угодно.
Разбираемся, что же такое блокчейн, и пишем приложение с его использованием.
Прежде чем говорить про блокчейн, нужно разобраться, что такое хеширование.
Хеширование — это преобразование данных произвольной длины в битовую строку фиксированной длины. То есть мы можем взять любой текст, например книгу «451 градус по Фаренгейту», а потом зашифровать его с помощью специального алгоритма.
Каждый раз этот алгоритм будет преобразовывать текст в один и тот же хеш. Например, такой:
6de4c03655fdc3e982e3bfc4f055dae5064547e8b78ef951b80c94b657a5c684
Но если будет пропущена хотя бы одна запятая или какая-нибудь буква поменяет регистр, то хеш полностью изменится.
efdc5e0108e3906fea8c93efb1d9616b7a26aa26549c5d47a1b6c3fda8199ac9
Тут зашифрована та же строка, что и сверху, но в конце я убрал точку. Как видно, результат уже совсем не похож на исходный хеш.
Пока что единственный способ расшифровать хеш — перебор. Теоретически, чтобы найти верный вариант, нужно попробовать бесконечное количество комбинаций. Поэтому хеш применяют, чтобы защитить данные от взлома — даже если они будут утеряны или украдены.
Один из вариантов использования хешей — хранение паролей. Когда пользователь будет регистрироваться в системе, его данные будут храниться не в чистом виде, а в виде хеша.
Каждый раз, когда он будет вводить пароль, тот будет снова хешироваться, а уже потом сравниваться с тем, что записан в базе. Даже если кто-то взломает базу данных, он не узнает, какой пароль использовался. Всё, что он увидит, — это набор непонятных символов.
Теперь можно вернуться к блокчейну. Особенность каждого его блока — в том, что он не только захеширован, но и хранит информацию о предыдущем блоке.
Рассмотрим это на примере блокнота. Каждая запись в нём будет восприниматься как отдельный блок.
Допустим, есть вот такая первая запись:
Привет, это первая заметка!
Её хеш выглядит так:
7cb70ea12f13042dbfbbf492c20e943b2c62a1007a9921e36f5479f6aa748bc9
Вторая запись содержит в себе саму запись, а также хеш первой записи. То есть:
7cb70ea12f13042dbfbbf492c20e943b2c62a1007a9921e36f5479f6aa748bc9_разделитель_Привет, это вторая запись!
В таком виде заметка хешируется:
8af30f625141cd685dd3ad40f32b0046b562e4efd9746f8225f5a56543a0e50a
И этот хеш становится частью третьей записи.
Если мы изменим вторую запись, то её хеш станет совсем другим. Следовательно, он не будет совпадать с тем, что был записан в третью запись. То есть блокчейн позволяет нам создавать записи, которые невозможно изменить незаметно.
Теоретически можно подобрать строку, хеш которой будет совпадать с тем, который нужно заменить. Но, как уже говорилось выше, сделать это сложно, особенно если блоков очень много.
Чтобы обойти эту проблему, используется децентрализация. То есть блоки хранятся не на едином сервере, а на всех компьютерах, которые используют приложение на блокчейне.
Эта мера используется, например, в криптовалюте BitCoin. И если кто-то захочет её у вас украсть, ему придётся одновременно заменить блоки на всех компьютерах.
Использовать эту технологию можно для чего угодно, а не только для криптовалюты. Например, можно хранить в блокчейне удостоверения личности. Это не позволит мошенникам использовать документы умерших людей, заменив в них фотографию.
Есть и другие варианты:
- В блокчейне можно хранить ставки — тогда ни букмекер, ни игрок не смогут обмануть друг друга.
- С помощью блокчейна можно проводить выборы — что исключит возможность фальсификации результатов.
- Можно заменить нотариуса блокчейном — и заверять документы без него.
Это далеко не весь список.
Не стоит думать, что такая система надёжна на 100%. Возможны ошибки в самом программном обеспечении, которые позволят воровать данные до того, как они будут захешированы.
Чтобы лучше понимать, как работает блокчейн, напишем приложение с использованием этой технологии. Здесь будут приведены только самые важные части кода, поэтому реализация интерфейса останется за кадром. Если же вам интересно увидеть приложение полностью, его можно найти в этом репозитории на GitHub.
Начнём с создания класса, который представляет собой блок. Назовём его Note.
В своей работе он будет использовать преобразование строки в байты и обратно, а также шифрование, поэтому нужно подключить соответствующие пространства имён:
using System.Text; using System.Security.Cryptography;
Теперь можно приступать к работе над самим классом:
///<Summary>Заметка для блокнота, который работает на блокчейне</Summary> class Note { public static string Splitter = "_splitter_"; private string text; private byte[] hash; public Note(string text) { this.text = text; this.hash = ComputeHash(text); } private byte[] ComputeHash(string text) { SHA256 sha = SHA256.Create(); //Создаём объект, который будет вычислять хеш byte[] textBytes = Encoding.Default.GetBytes(text); //Преобразуем текст заметки в байты return sha.ComputeHash(textBytes); //Возвращаем хеш } ///<Summary>Полный текст заметки</Summary> public string Text { get { return this.text; } } ///<Summary>Текст заметки без хеша прошлого блока</Summary> public string ClearText { get { return this.text.Remove(0, this.text.IndexOf(Note.Splitter) + Note.Splitter.Length); } } ///<Summary>Хеш прошлого блока</Summary> public string PreviousHash { get { return this.text.Remove(this.text.IndexOf(Note.Splitter)); } } ///<Summary>Хеш текущего блока в виде строки</Summary> public string HashString { get { //При каждом получении текущего хеша нужно считать его заново //Так мы будем уверены, что он актуален var hash = ComputeHash(this.text); return Encoding.Default.GetString(hash); } } ///<Summary>Хеш в виде байтов</Summary> public byte[] Hash { get { return this.hash; } } }
Остаётся только написать программу, которая будет использовать эти блоки. Первым делом нужно создать список блоков:
List<Note> notes = new List<Note>(); //Список блоков notes.Add(new Note "start" + Note.Splitter + "First note")); //Добавляем первую заметку
Потом — реализовать метод добавления новых блоков.
static void AddNote(string text, List<Note> notes) { notes.Add(new Note(notes[notes.Count - 1].HashString + Note.Splitter + text)); }
Когда пользователь вводит текст для новой заметки, он передаётся в метод AddNote().Там создаётся новый блок, который содержит текст вида
Хеш предыдущего блока_разделитель_текст заметки
Созданный блок тут же добавляется в список:
После того как будет создано несколько заметок, можно проверить список:
Здесь отображается только текст заметок, но, если посмотреть на полную информацию о них, можно увидеть, что они содержат в себе хеш прошлого блока:
Скриншот: Skillbox Media
Просматривать всё это вручную, чтобы проверить достоверность данных, нет смысла, потому что блоков может быть очень много. Вместо этого напишем метод валидации:
///<summary>Проверка подлинности блоков</summary> static void ValidateNotes(List<Note> notes) { for(int i = 0; i < notes.Count; i++) { if(i == 0) //Не проверять первый блок { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("0 - Valid"); } else { Console.ForegroundColor = ConsoleColor.White; //Проверяем, соответствует ли хеш, записанный в текущий блок, хешу прошлого блока if(@notes[i].PreviousHash == @notes[i - 1].HashString) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine(i + " - Valid"); } else { Console.WriteLine($"nn | {notes[i].PreviousHash} | {notes[i - 1].HashString} | nn"); Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(i + " - Changed"); } } } Console.WriteLine("n"); }
Теперь можно проверить, всё ли в порядке с заметками, которые мы сохранили:
Если же в каком-нибудь блоке изменится хоть один символ, это сразу станет ясно. Поэтому такой блокнот — идеальный вариант для хранения долговых расписок и другой подобной информации.
Это только малая часть того, что можно узнать по этой теме. Мы написали приложение, которое использует блокчейн, и использовали главный принцип блокчейна — последовательность хеширования данных.
Если же вы хотите изучить тему глубже, советую почитать статью на Habr. Там же есть ссылка на учебник по защите информации.
Учись бесплатно:
вебинары по программированию, маркетингу и дизайну.
Участвовать
На наших глазах разворачивается Web 3.0 интернет, который будет сильно отличаться от того, что мы привыкли видеть последние 20 лет. Одной из ключевых отличительных особенностей Web 3.0 — это широкое использование блокчейн технологий.
Широкая аудитория уже понимает, что блокчейн может быть использован для создания криптовалют, но по мере того, как Ethereum набирал популярность, стало ясно, что блокчейн — это ещё и смарт-контракты, децентрализованные приложения и NFT. Прежде, чем создавать свой блокчейн проект надо определиться с тем, что именно вы хотите создать.
Криптовалюта
Хотя попытки создать цифровые валюты делались задолго до появления Биткоина, но сталкивались c проблемой т.н “двойного расходования” одних и тех же средств. Проблему удалось решить с помощью технологии блокчейн, как разновидности распределенного реестра. Это выглядело амбициозной идей, так как с помощью блокчейна возможно было выстроить монетарную систему, которая не нуждалась бы в посредничестве старых традиционных финансовых институтов, вроде центробанков.
Согласно данным Statista, на момент февраля 2022 существует уже более 10 000 разновидностей криптовалют. Для того, чтобы сделать свою криптовалюту в 2022 нужно предлагать нечто большее, чем децентрализацию или быстрые транзакции. Если посмотреть на ТОП криптовалют и отбросить BTC, занявший свое место на правах первопроходца, также привязанные к фиату стейблкоины, то мы увидим крипту, имеющую инфраструктурную ценность: Ethereum, BNB, Cardano, Solana и.т.д. Сейчас криптовалюте, чтобы быть успешной надо становиться нативным токеном платформы, построенной на блокчейне, на которой делаются децентрализованные приложения.
Приложения на блокчейне
Если вы решили реализовать приложение на блокчейне, то дальше вам надо определиться с типом приложения. Подскажем вам несколько перспективных идей:
- Крипто-кошелёк на смартфон.
- Лаунчпад для сбора инвестиций стартапами.
- Централизованная криптобиржа.
- Децентрализованная криптобиржа.
- NFT маркетплейс.
- Крипто-магазин.
- Телеграм-бот для работы с крипто.
- Расширение к браузеру.
- Play2Earn или Move-to-Earn игра, использующая NFT.
- Децентрализованная социальная сеть.
- Децентрализованная стриминговая платформа.
И многое, многое другое. Вариантов множество, но любой из них потребует слаженной работы опытной команды разработчиков.
Команда
Для того, чтобы создать полноценный блокчейн-проект вам потребуется работа в трёх основных направлениях, а также руководитель команды.
Разработка
Front-end разработчики, Back-end разработчики. Например, если вы работаете c блокчейном Ethereum, то вам потребуются специалисты по Solidity, если вы собираетесь писать блокчейн с нуля могут пригодиться специалисты по C, C++, C#, Java и Python.
Дизайн
Создание сайтов, логотипов, иллюстрации, графического интерфейса, оформление документов презентаций — всё это потребует людей, способных работать с графикой.
Маркетинг
Мало создать свой проект, его ещё надо продать. В наше время борьба за клиентов носит особенно ожесточенный характер, потому что блокчейн-отрасль стремительно растёт в популярности, а это значит, что у вас будет множество конкурентов. Для того, чтобы привлечь клиентов и инвестиции, потребуется постоянная работа в социальных сетях, блогах, работа с поисковыми алгоритмами — без всего этого невозможно стать узнаваемыми на рынке.
Руководитель
Наконец, вам потребуется человек, который может координировать все три отдела между собой. Быть руководителем для блокчейн-проекта непросто, так как это требует компетенции и в разработке, и в дизайне, и в маркетинге, да и в других смежных направлениях, поэтому такие специалисты очень ценны и редки.
Как с нуля построить свою блокчейн сеть
Блокчейн — децентрализованная база данных, хранящая информацию о всех операциях в виде цепи блоков. Особенностью сети является то, что записи находятся не на одном сервере, а на сотнях, из-за чего незаметно подделать их или удалить невозможно. Блокчейн — надежная и безопасная технология, которую можно использовать для обмена данными, деньгами и даже документами.
Какие задачи решает блокчейн
Технологию активно внедряют, потому что она решает несколько задач:
-
защита конфиденциальности — если банки и платежные системы знают сумму сделки, как зовут отправителей и получателей, то блокчейн сохраняет анонимность. Это обусловлено тем, что для проведения операции нужно знать только публичные и приватные ключи кошельков;
-
обеспечение безопасности — данные сохраняются на всех блоках в цепи, поэтому полностью удалить или изменить какую-либо запись в системе невозможно;
-
бесперебойная работа — сеть функционирует благодаря группе участников, занимающихся майнингом. Если центральный сервер из-за технических работ или проблем с электричеством может временно выйти из строя, то блокчейн будет работать всегда. А при увеличении нагрузки система способна масштабироваться.
Кроме того, блокчейн гарантирует прозрачность сделок благодаря смарт-контрактам. Это программный код, в котором зафиксированы условия, например, если пользователь А отправит 100 монет Б, то Б передаст А NFT-картинку. Он позволяет отказаться от лишних посредников, взимающих комиссии. Смарт-контракт невозможно изменить, а операция выполняется автоматически.
Как самостоятельно сделать блокчейн
Чтобы разработать собственный блокчейн, потребуется:
-
Python 3.6 или новее;
-
библиотека Flask;
-
HTTP-клиент (Postman, cURL).
Пишем скелет
Начать работу можно в любом совместимом с Python редакторе, т. е. Integrated Development Environment, например: PyCharm, PyDev, WingWare, Komodo IDE. Создадим файл под названием «Blockchain», чтобы потом не потерять код.
Сначала необходимо прописать класс блокчейна, конструктор которого сделает 2 пустых листа для хранения исходного кода и информации о транзакциях.
Пример чертежа:
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
Этот класс блокчейна управляет цепью, то есть он будет хранить информацию и иметь несколько методов внесения новых блоков в систему.
Создаем первый блок
Первый блок в цепи называют Genesis block. В нем хранится информация, являющаяся фундаментом всех последующих элементов системы. Генезис необходим для связи блоков между собой и проверки транзакций, так как перевод не будет завершен, пока майнер не сравнит данные в новом блоке с предыдущими.
Важно уделить особое внимание алгоритмам, заложенным в него, чтобы запущенная система работала без сбоев, а пользователи не нашли уязвимости, позволяющие украсть активы других участников.
Чтобы разработать первый блок, нужно запустить следующий программный код:
def create_first_block():
block_data = {}
block_data['index'] = 0
block_data['timestamp'] = date.datetime.now()
block_data['data'] = 'First block data'
block_data['prev_hash'] = None
block = Block(block_data)
return block
Теперь стоит добавить функцию, позволяющую сохранять информацию о транзакциях на локальном диске. Она сделает систему безопаснее, т. к. в случае отключения узла данные не потеряются.
Назовем новую папку «blockchaindata» и допишем программный код:
chaindata_dir = 'chaindata'
if not os.path.exists(blockchaindata_dir):
os.mkdir(blockchaindata_dir)
if os.listdir(blockchaindata_dir) == []:
first_block = create_first_block()
first_block.self_save()
Теперь у каждого блока есть отдельный файл, названный по его уникальному идентификатору. Стоит проверить, содержат ли имена файлов несколько нулей в начале, чтобы они отображались строго по порядку и не перемешались при первой нагрузке на сеть.
Синхронизируем блокчейн
Перед тем как майнить, интерпретировать данные или создавать новые блоки, нужно локально синхронизировать первый узел. Других элементов в системе еще нет, поэтому на нынешнем этапе достаточно разобраться с чтением блоков из локальных файлов, хранящихся на диске.
В будущем синхронизация будет подразумевать не столько прочтение файлов, сколько коммуникацию с пирами для сборки блоков, которые сгенерировались еще до запуска узла.
Программный код:
def sync():
node_blocks = []
chaindata_dir = 'chaindata'
if os.path.exists(blocchaindata_dir):
for filename in os.listdir(blockchaindata_dir):
if filename.endswith('.json'):
filepath = '%s/%s' % (blockchaindata_dir, filename)
with open(filepath, 'r') as block_file:
block_info = json.load(block_file)
block_object = Block(block_info)
node_blocks.append(block_object)
return node_blocks
Стоит сказать, что это примитивный, но рабочий код, так как чтение и загрузка файлов в структуры не требуют сложных решений.
Прописываем отображение через браузер
Пока блокчейн просто находится в памяти, с ним проблематично работать, поэтому цепочку стоит отобразить в браузере. Веб-обозреватель можно использовать для просмотра и совершения операций, таких как отправка цифровых активов или контроль криптокошелька.
Для создания веб-приложения стоит воспользоваться фреймворком Flask. Это легкий и понятный софт, имеющий все необходимые функции для работы. Flask отличается тем, что позволяет быстро разработать приложение, используя только один файл Python. Также подойдут Django, CherryPy, Pyramid, TurboGears.
Код для отображения блокчейна:
node = Flask(__name__)
node_blocks = sync.sync() #inital blocks that are synced
@node.route('/blockchain.json', methods=['GET'])
def blockchain():
node_blocks = sync.sync() #regrab the nodes if they've changed
python_blocks = []
for block in node_blocks:
python_blocks.append(block.__dict__())
json_blocks = json.dumps(python_blocks)
return json_blocks
if __name__ == '__main__':
node.run()
Проигнорируем импорты для экономии места.
Теперь вы можете посмотреть имеющийся блок, запустив программный код и перейдя в localhost:3000/blockchain.json
.
Создание цепочки блоков
Сейчас к сети подключен только genesis block, имея который уже можно работать с блокчейном, но с обработкой большого объема данных он не справится. Рекомендуется еще до запуска проекта продумать, как будут создаваться новые элементы цепи и как они будут соединяться друг с другом.
Сатоши Накамото, создавший Bitcoin, придумал, какой должна быть цепочка в блокчейне: timestamp-сервер (узел) хэширует блок данных и показывает, что информация существовала и потому сохранилась. Каждый хэш состоит не только из новых данных, но и предыдущих. Таким образом, образуется цепь, в которой новые звенья укрепляют предыдущие.
Благодаря связи всех блоков система может проверить корректность нового, определив все хэши и подтвердив порядок. В нашем случае информация нового и предыдущего блоков будет объединяться в одну огромную строчку, в которую включены:
-
порядковый номер;
-
хэш предшественника;
-
данные;
-
timestamp майнинга.
Для этого нужно прописать команду:
def generate_header(index, prev_hash, data, timestamp):
return str(index) + prev_hash + data + str(timestamp)
Объединять строки необязательно, поэтому программист может выбрать другой вариант для создания хедера блока. Главное требование — прозрачность генерации хедера и хэша. Это необходимо для того, чтобы каждый пользователь сети мог проверить корректность блока и определить связь между другими элементами сети.
Для вычисления валидности хэша будем использовать метод, немного отличающийся от использованного Сатоши Накамото, но запустим хедер посредством функции sha256.
Программный код:
def calculate_hash(index, prev_hash, data, timestamp, nonce):
header_string = generate_header(index, prev_hash, data, timestamp, nonce)
sha = hashlib.sha256()
sha.update(header_string)
return sha.hexdigest()
Для майнинга новых блоков можно внедрить вышеуказанную функцию. Она поможет получить хэш, положить его в новый блок и сохранить последний в директории «blockchaindata».
Код:
node_blocks = sync.sync()
def mine(last_block):
index = int(last_block.index) + 1
timestamp = date.datetime.now()
data = "I block #%s" % (int(last_block.index) + 1) #random string for now, not transactions
prev_hash = last_block.hash
block_hash = calculate_hash(index, prev_hash, data, timestamp)
block_data = {}
block_data['index'] = int(last_block.index) + 1
block_data['timestamp'] = date.datetime.now()
block_data['data'] = "I block #%s" % last_block.index
block_data['prev_hash'] = last_block.hash
block_data['hash'] = block_hash
return Block(block_data)
def save_block(block):
chaindata_dir = 'chaindata'
filename = '%s/%s.json' % (chaindata_dir, block.index)
with open(filename, 'w') as block_file:
print new_block.__dict__()
json.dump(block.__dict__(), block_file)
if __name__ == '__main__':
last_block = node_blocks[-1]
new_block = mine(last_block)
save_block(new_block)
Теперь майнеры могут подключаться к memorypool и обрабатывать информацию. Устройства узнают о транзакции, изучат ее, запишут в блок, посчитают хэш, за что их владельцы будут получать вознаграждение. Однако при таком типе блокчейна компьютеры с самым мощным CPU будут создавать длинные цепи, которые другие узлы определяют как корректные.
Настройка алгоритма подтверждения
Для подтверждения действительности переводов, зафиксированных в блокчейне, разработчики используют несколько механизмов консенсуса. Наиболее популярными являются:
-
Proof of Work (PoW) — подтверждение работы;
-
Proof of Stake (PoS) — доказательство доли владения.
Первый вариант считают более безопасным с точки зрения уязвимости, однако актуальность вопроса децентрализации для PoW блокчейнов ежегодно растет. Постепенно майнить становится сложнее, поэтому пользователи объединяются в пулы. В 2021 году было зафиксировано, что 50% мощности сети Bitcoin принадлежит 4 пулам, то есть постепенно происходит стягивание всех процессов к нескольким крупным серверам.
Proof of Stake появился позже. Сейчас его в основном используют в проектах альткоинов. Его особенность в том, что пользователям не нужны огромные мощности, так как PoS не подразумевает майнинг. Приоритеты участников зависят от количества криптовалюты, хранящейся на балансе.
Чтобы получить право проверить блок, пользователь должен заблокировать монеты с помощью смарт-контракта. Этот процесс называют стейкингом. Затем алгоритмы блокчейна выбирают, кто займется валидацией следующего блока.
Рассмотрим все существенные различия механизмов:
Proof of Work |
Proof of Stake |
|
Кто участвует в майнинге/валидации |
Владельцы компьютеров с наибольшей вычислительной мощностью |
Пользователи с большим количеством заблокированных монет |
Требующееся оборудование |
Оборудование, заточенное под добычу криптовалюты |
Любое устройство с постоянным выходом в интернет |
Распределение наград |
Их забирают пользователи, добывшие блок первыми |
Валидатору выплачивают часть комиссии за переводы, собираемые с подтвержденного блока |
Безопасность |
Чем больше хэш, тем защищеннее система |
Смарт-контракты блокируют валюту участников |
В нашем случае использован Proof of Work, как у Биткоина. Чтобы настроить механизм, необходимо поставить требования к структуре хэша.
Программный код:
def generate_header(index, prev_hash, data, timestamp, nonce):
return str(index) + prev_hash + data + str(timestamp) + str(nonce)
Теперь майнинг настроен. Стоит сказать, что если хэш блока не содержит достаточного количества нулей, необходимо увеличить значение nonce, создать новый хэдер, пересчитать хэш и перепроверить число нулей.
Код:
NUM_ZEROS = 4
def mine(last_block):
index = int(last_block.index) + 1
timestamp = date.datetime.now()
data = "I block #%s" % (int(last_block.index) + 1) #random string for now, not transactions
prev_hash = last_block.hash
nonce = 0
block_hash = calculate_hash(index, prev_hash, data, timestamp, nonce)
while str(block_hash[0:NUM_ZEROS]) != '0' * NUM_ZEROS:
nonce += 1
block_hash = calculate_hash(index, prev_hash, data, timestamp, nonce)
block_data = {}
block_data['index'] = int(last_block.index) + 1
block_data['timestamp'] = date.datetime.now()
block_data['data'] = "I block #%s" % last_block.index
block_data['prev_hash'] = last_block.hash
block_data['hash'] = block_hash
block_data['nonce'] = nonce
return Block(block_data)
Когда блок получит валидное значение показателя nonce, другие узлы смогут подтвердить хэш.
Как создать пару ключей
Чтобы зашифровать данные, стоит воспользоваться RSA. В результате вы получите пару ключей: публичный и приватный.
Программный код:
from Crypto.PublicKey import RSA
code = 'nooneknows'
key = RSA.generate(2048)
encrypted_key = key.exportKey(
passphrase=code,
pkcs=8,
protection="scryptAndAES128-CBC"
)
with open('my_private_rsa_key.bin', 'wb') as f:
f.write(encrypted_key)
with open('my_rsa_public.pem', 'wb') as f:
f.write(key.publickey().exportKey())
Сначала нужно импортировать RSA из Crypto.PublicKey, а затем создать простой код доступа и сгенерировать ключ RSA на 2048 битов. Чтобы создать приватный ключ, необходимо вызвать метод exportKey и отдать ему код доступа. Последний будет использован стандартом PKCS, схема шифровки которого подходит для защиты конфиденциальной информации.
Полученный файл следует записать на диск. Остается создать приватный ключ через метод publickey, связав его с вызовом exportKey.
Реализация подписей
Перевод необходимо подписать, так как это единственный способ гарантировать его надежность. Если подпись окажется недействительной, то и транзакция не добавится в цепочку. Учитывая, что транзакции открывают предыдущие выходы, распределяют их значения, а затем блокируют новые выходы, необходимо подписать:
-
хэши открытых ключей в разблокированных выходах, чтобы идентифицировать отправителя;
-
хэши открытых ключей в новых выходах для подтверждения получателя;
-
значения новых выходов.
Перед отправкой файлов участник сети генерирует цифровую подпись, используя закрытый ключ аккаунта. А подписание может осуществляться несколькими способами, например, в клиенте блокчейн-платформы.
Алгоритм проверки транзакции:
-
Получение информации и ЭЦП из новой транзакции.
-
Сбор хэша.
-
Расшифровка подписи и открытого ключа с помощью RSA Decode.
-
Сравнение хэшей, полученных на втором и третьем этапе.
Если значения совпали, значит, информация корректна, а транзакция действительно подписана ключом владельца. В случае одобрения перевода данные добавляются в блок.
Теперь у вас есть работающий блокчейн. Мы не задействовали еще много функций, однако его уже можно использовать для обмена информацией в небольшой компании или между друзьями.
Как запустить свой блокчейн: выбираем движок
Перед разработкой собственного блокчейна ваша команда должна четко понимать, для чего необходим блокчейн и какой бюджет вы сможете выделить на его содержание.
Проектирование и запуск блокчейна имеют свои нюансы. Их можно легко упустить при планировании, если вы неверно оценили объем и сложность задачи.
Чтобы помочь проектам избежать таких ошибок, руководитель отдела исследований MixBytes Сергей Прилуцкий подготовил пошаговое руководство по запуску блокчейна.
Данная статья поможет вам определиться с выбором движка для построения собственного блокчейна. Технические свойства и ограничения блокчейна описаны здесь, выбор алгоритма консенсуса — здесь.
Блокчейн с нуля
Попытки создать блокчейн с нуля без сомнения похвальны, ведь благодаря им появились многие существующие решения. Тем не менее нужно трезво оценивать возможности своей команды.
Написание с нуля кода блокчейн-ноды напоминает создание собственной базы данных с механизмом надежной сетевой репликации. Если вы поищете, сколько таких БД было создано за последние десятилетия, то найдете максимум сотню проектов. Огромной долей рынка владеют всего несколько компаний (Oracle, MS SQL Server, MySQL, PostgreSQL), а разработчики ядра таких систем ценятся крайне высоко.
Для разработки блокчейн-ноды вам придется собрать команду очень крутых и редких специалистов, имеющих опыт работы с многопоточным программированием, криптографией, сетевыми протоколами, сложными внутренними алгоритмами и понимающих работу современных операционных систем.
Особенное место для блокчейнов занимает тестирование, так как алгоритмы консенсуса могут прекрасно себя вести на нескольких валидаторах и совершенно по другому при наличии десятков и сотен узлов под нагрузкой. Увеличение числа разработчиков в данном случае приведет разве что к усложнению коммуникаций внутри команды.
По опыту нашей компании, бизнес-заказчики редко выбирают этот путь. Создание своего блокчейна — это задача для группы исследователей, энтузиастов, которые могут себе позволить работать свободно, не имея жестких сроков и бизнес-плана. Такая команда должна иметь возможность свободно исследовать любой встретившийся вопрос, не сильно заботясь о сроках сдачи проектов. На текущий момент работа над такими проектами, как биткоин и Ethereum, производится независимыми разработчиками по всему миру, без жестких дедлайнов. Внешнее давление бизнес-факторов может сыграть с вашим проектом злую шутку, заставив быстрее решать проблемы, не продумав последствия.
Готовые блокчейн-движки
Я намеренно назвал раздел «движки», так как этот термин в области ПО часто используется для обозначения комплексов разнопланового ПО, предназначенного для решения конкретной задачи. Например, «поисковый движок» или «графический движок» — это не только код, но и вспомогательное ПО, дополнительные утилиты, описания алгоритмов и многое другое. Учитывая существование нескольких основных ядер блокчейнов, на базе которых построены уже существующие сети, будет удобно называть их движками (например, «построен на движке Ethereum»).
Если ваш блокчейн не обладает уникальной архитектурой и ваша задача — доставка решения за определенный промежуток времени, лучшим вариантом будет работа с уже существующими движками. Они позволяют реализовать ваш вид консенсуса и транзакций, по-своему организовать управление валидаторами сети. Вы сможете использовать готовый открытый код, проверенный в реальных сетях. Вам не придется изменять код блокчейн-ноды, а для реализации своей логики нужно будет менять только часть, предусмотренную разработчиками движков. Не внося новых уязвимостей и не решая проблемы сетевого слоя, вы сможете сосредоточиться только на бизнес-логике вашего блокчейна.
Разберем готовые для работы блокчейн-движки, используя которые вы сможете запустить собственный блокчейн, спроектировать и реализовать его внутреннюю экономику и организовать запуск для проведения сложных сделок.
Ethereum
Этот комплекс ПО построен на базе ядра публичного блокчейна Ethereum. Публичный Ethereum использует консенсус типа Proof-of-Work, а его многочисленные тестовые сети — различные виды Proof-of-Authority и Proof-of-Stake консенсусов. ПО отвечает самым строгим критериям безопасности, проверено в десятках реально работающих сетей и, на мой взгляд, является наиболее развитым для создания блокчейнов с любыми видами консенсусов и полноценными, многофункциональными смарт-контрактами.
Нужно отметить роль проекта POA Network, чьи разработчики проделали огромную работу и запустили уже несколько быстрых и надежных сетей. POA Network существенно быстрее оригинального Ethereum, но при этом обладает той же стойкостью и универсальностью для заключения любых сделок, а роль валидаторов (майнеров) исполняют компьютеры, честная работа которых заверяется юридически. Эту сеть можно считать эталоном для запуска корпоративных блокчейнов на базе Ethereum.
Код блокчейн-ноды и консенсус
Существуют две основных имплементации кода ноды Ethereum: на языке Rust (код, названия: poa-parity (старое) или openethereum(новое)) и на Go (код, название: geth).
На момент написания при построении PoA-сети на geth (Go) вам будет доступен только консенсус Clique — это простейший и небезопасный протокол без финализации, который можно использовать только в тестовых целях.
Консенсус, реализованный в poa-parity (Rust), состоит из двух алгоритмов: schedule валидаторов Aura и finality gadget GRANDPA. Именно этот вариант, проверенный и безопасный, работает в POA-сетях на базе Ethereum. POA Network работают также над имплементацией перспективного BFT-консенсуса HoneyBadger.
Отдельного упоминания заслуживает новая блокчейн-нода Nethermind, написанная на C# для платформы .NET Core. Она полностью поддерживает Ethereum, большое число операционных систем и является отличным выбором для компаний, которые используют .NET Core.
Смарт-контракты и управление сетью
POA Ethereum использует виртуальную машину EVM и смарт-контракты, которые лучше всего писать на языке Solidity. EVM давно стала стандартом для виртуальных машин с большим количеством готового кода и паттернов разработки. Код контрактов под EVM отвечает за большие суммы криптовалюты, и любая найденная уязвимость вызывает мощную реакцию сообщества и СМИ, поэтому безопасность контрактов EVM на текущий момент крайне высока.
Управление списком валидаторов осуществляется посредством смарт-контрактов — это потрясающе удобно. Можно оперировать одним или несколькими токенами или вообще избавиться от них. Можно сделать процедуру добавления валидаторов гибкой или максимально упростить, добавив «всемогущий» аккаунт. Мощь этой схемы в том, что буквально один разработчик контрактов может создать полную экономику сети на одной платформе с высочайшим уровнем безопасности и переносимости, реализовав сразу и управление сетью, и логику сделок, и другие свойства.
Дополнительное ПО
С Ethereum можно использовать JavaScript-библиотеку web3.js, вне зависимости от консенсуса, валидаторов и ее расположения.
Для POA Ethereum существует репозитарий для автоматизации операций по развертыванию готовой сети — deployment-playbooks.
Если вы планируете запускать POA Ethereum, используйте эту инструкцию. Она проведет вас от создания ключей валидаторов к запуску первых нод, развертыванию системных контрактов и запуску интерфейса валидаторов и обозревателя блоков.
Готовая POA-сеть Ethereum присутствует в AWS, но я все же рекомендую контролировать запуск своими руками. Вы должны понимать, какие сервисы вы запускаете и как они работают.
EOS и его форки
Вторым по гарантиям работоспособности и безопасности будет EOS. “OS” в его названии появилась не случайно.
EOS можно запустить в качестве отдельной сети, в PoS- или PoA-варианте. Как и Ethereum, это ПО уже проверено в бою, обладает высокой безопасностью и функционалом, который позволяет запустить собственный блокчейн со смарт-контрактами для автоматизации любых сделок
Если Ethereum имеет простую систему адресов, то в EOS сразу же используется иерархическая система аккаунтов и права на различные действия. Все это делает EOS похожей по дизайну на операционную систему — «программу для запуска других программ».
В качестве межкорпоративной платформы EOS позволяет из коробки получить удобную систему управления аккаунтами и быстрый консенсус, а также легко интегрировать практически любой функционал при помощи плагинов на C++ и смарт-контрактов на C++/WebAssembly (например, можно добавить другую криптографию).
Дизайн консенсуса в EOS и быстрые блоки позволяют достичь очень быстрого времени ответа пользователю, что крайне важно для построения децентрализованных приложений со сложным функционалом (например, проекты Cyberway, Golos.io или соцсеть Commun). Cyberway недавно произвел сложнейшую миграцию всей бизнес-логики из предыдущего блокчейна прозрачно для пользователей, что лишний раз доказывает гибкость и универсальность EOS.
Код блокчейн-ноды и консенсус
Код EOS написан на C++ и развивался на основе опыта, полученного разработчиками при работе над движками Graphene, Bitshares, Steemit. Используется собственный вариант DPoS-консенсуса.
Сейчас почти все проекты, использующие DPoS, строят свои алгоритмы очень похожим на EOS образом: это аккаунты, «голосующие» балансом токена за топ валидаторов. Валидаторы подписывают блоки по одиночке, но каждый в назначенный квант времени, согласно расписанию. Затем они коллективно фиксируют так называемый Last Irreversible Block (LIB), на котором собирается 2/3 + 1 подписей от валидаторов.
Многие форки EOS пытаются улучшить это консенсус. Например, наш вариант Haya использует для фиксации LIB другой finality gadget — RANDPA, чтобы достичь времени финальности в 2-3 секунды.
Переход к корпоративному POA-консенсусу не вызывает затруднений, так как список валидаторов управляется системными смарт-контрактами.
Смарт-контракты и управление сетью
Смарт-контракты в EOS используют модифицированную виртуальную машину WebAssembly, обычно пишутся на языке C++ и могут создаваться и использоваться любым аккаунтом. Писать смарт-контракты не сложно, во многом они перекликаются с Solidity.
В EOS, как и в POA Ethereum, управление сетью, основной токен (или токены) и типы транзакций можно реализовать в системных смарт-контрактаx (вот, например, системный токен). Интересной особенностью контрактов EOS является использование абстракции table для хранения данных контракта. В Ethereum в основном используется mapping (ассоциативный массив).
Еще одна особенность смарт-контрактов в EOS — upgradeability. Владелец контракта может заменить его, обновив логику или исправив ошибку. Это сильно отличается от Ethereum, где неизменность контрактов — важное условие, гарантирующее, что логика контракта никогда уже не будет изменена, если не произойдет хардфорк.
Для межкорпоративных блокчейнов, на мой взгляд, возможность изменять код контрактов — важное преимущество. Незаметно что-то украсть здесь все равно не получится, зато обновить код по соглашению сторон можно без всякого участия валидаторов.
В EOS возможно организовать «спонсорские» транзакции, оплачиваемые владельцами контракта, а не самими пользователями. Это мощнейшая возможность для привлечения новых пользователей. Вы ведь помните, что «бесплатных» транзакций без потери безопасности в блокчейнах не бывает?
Дополнительное ПО
BOSCore, Telos, Haya и еще десяток форков EOS доказывают, что это ПО интересно большому количеству проектов. Для EOS существует достаточно инструментов, и вам не придется с нуля реализовывать сопутствующее ПО.
Eosjs — аналог web3.js, позволяет работать с контрактами любой сети на базе EOS из браузера и любых приложений.
EOSTracker — обозреватель блоков с открытым кодом и децентрализованными приложениями для голосований за валидаторов.
У EOS нет одного большого и мощного интегратора, как POA Network для Ethereum, поэтому каждый проект строит собственное решение. Тем не менее, основной код ноды стабилен и работает под серьезными нагрузками без сбоев.
Parity Substrate
Substrate создается командой компании Parity. Разработано огромное количество ПО: кошельки, блокчейн-ноды, системы смарт-контрактов, компиляторы, виртуальные машины.
Parity Substrate позволяет разработчику достаточно легко создать свой вариант блокчейна из готовых модулей со сложным консенсусом и логикой обработки транзакций. Substrate — это конструктор блокчейнов, на котором, к примеру, можно сделать блокчейн-ноду Ethereum или биткоина.
Substrate — это часть крупного проекта Polkadot — системы, состоящей из основной цепочки и множества цепочек-шардов с индивидуальной логикой.
Преимущество «подключения» своего блокчейна к Polkadot заключается в возможности ончейн-обмена данными с другими цепочками и возможностью использовать их контракты, аккаунты, токены без дополнительного ПО.
Код блокчейн-ноды и консенсус
Код Substrate написан на языке Rust. На мой взгляд, в структуре Substrate чувствуется большой опыт команды по созданию блокчейнов, так как все компоненты отлично структурированы, разделены на отдельные модули, а в коде присутствуют подробные комментарии. Доказательством гибкости этого движка является существование клиента для сети биткоина и ZCash на основе кода Substrate.
Что касается консенсуса, то можно выбрать из нескольких готовых вариантов или написать свой собственный. В большинстве случаев это PoA или DPoS, что в случае Substrate означает использование алгоритма Aura и GRANDPA.
Производительность блокчейнов на базе Substrate высока. Основная цепочка Polkadot была протестирована нами в конфигурации с 99 валидаторами, распределенными по трем континентам, и показала отличные результаты.
Преимуществом Substrate я считаю продуманность архитектуры, стек разработки (Rust), и огромное поле для развития. Это крайне гибкая сеть, на базе которой можно построить решения любого уровня сложности.
Смарт-контракты и управление сетью
Substrate, в отличие от Ethereum и EOS, обрабатывает транзакции при помощи кода, который размещается валидаторами, а не пользователями. Это код называется “runtime” и исполняется виртуальной машиной WebAssembly.
Напомню, что runtime — это по сути один большой смарт-контракт, который обновляется валидаторами и собирается разработчиком из отдельных модулей. Модули содержат логику аккаунтов, токенов, сделок любой сложности, и т.д. Именно это свойство превращает Substrate в конструктор. Вполне возможно, для решения ваших задач потребуется просто скомбинировать несколько готовых модулей или незначительно доработать один из них.
Особого упоминания заслуживают модули пользовательских смарт контрактов: WASM и EVM. Они дают возможность пользователям размещать свои смарт-контракты, поэтому запуск универсального блокчейна на Substrate тоже возможен.
Ограничения на запуск транзакций реализуются разработчиками runtime — можно сделать все транзакции за одну и ту же цену, учитывать ресурсы с точностью до бита или сделать все бесплатным и вообще не использовать внутреннюю криптовалюту.
В плане гибкости у runtime есть множество преимуществ — разработчик может комбинировать их, создавать сложные роли, объединять управление сетью, внутреннюю логику и экономику. Учитывать следует лишь то, что обновление кода runtime проводится кворумом валидаторов.
Дополнительное ПО
Для Substrate есть ряд полезных решений: polkascan — обозреватель блоков и комплекс программ на JS для работы с Polkadot и сетями на базе Substrate. Возможно, вам пригодятся ansible-сценарии для развертывания готового кластера на базе Substrate, который мы использовали для тестирования Polkadot.
У Substrate нет богатого выбора универсального ПО, кошельков и обозревателей блоков, как у Ethereum или EOS, так как цепочки могут сильно отличаться между собой. Проект активно развивается, и множество команд параллельно создают сопутствующее ПО.
Cosmos SDK
Cosmos — это проект на базе одной основной цепочки и множества дочерних блокчейнов, называемых «zones». Дочерние цепочки строятся на основе Cosmos SDK — набора ПО для построения блокчейнов.
Cosmos — это продолжение проекта Tendermint, из которого ключевыми технологиями является надежный консенсус и концепция Application, сходная с runtime в Substrate.
Как и в случае Polkadot+Substrate, блокчейн, созданный с помощью Cosmos SDK, может жить отдельно или подключиться к экосистеме Cosmos как дочерняя цепочка.
Весь комплекс ПО Cosmos написан на Go и отлично структурирован и активно используется. На его основе уже работают несколько проектов, среди которых Binance Chain.
Если ваши разработчики пишут на Go — Cosmos SDK может вам подойти. Он работает и активно развивается в реальных проектах, чьи блокчейны и транзакции можно увидеть в публичных сетях.
Код блокчейн-ноды и консенсус
Главная концепция Cosmos называется Application. Любой блокчейн представляет собой машину состояний, и в Cosmos она вынесена в отдельную часть кода.
По сути, разработчик просто задает правила, по которым одни данные превращаются в другие при внешнем воздействии, программируя так называемую функцию state transition. Это сложно звучит, но по факту обработка транзакции — это state transition, которая меняет несколько балансов. Именно этим занимается Application — принимает некоторое воздействие извне (транзакцию) и меняет свое состояние (state). Получившиеся изменения фиксируются в блокчейне. При этом разработчик не должен решать проблемы консенсуса и сети — сеть сама договорится между собой и придет к консенсусу относительно результатов.
Консенсус в Cosmos построен на базе консенсуса Tendermint, крайне близкого к pBFT. Его особенность в том, что подтверждения валидаторов собираются на каждый блок, что означает мгновенную финальность, как только блок принят сетью. Этот алгоритм требует много сообщений между валидаторами, и в случае проблем с сетью этот консенсус будет медленнее финализировать цепочку.
Зато он является наиболее предсказуемым, защищенным от форков, имеет формальные математические доказательства надежности и, по моему мнению, является наиболее строгим и безопасным решением из всех существующих консенсусов.
Смарт-контракты и управление сетью
Application в Cosmos можно рассматривать как единый смарт-контракт, ответственный за обработку всех видов транзакций. Вот пример структуры Application для сервиса регистрации имен.
Одновременно с созданием кода для блокчейн-нод, Cosmos SDK создает код клиента, который умеет формировать транзакции нужных типов.
Для ограничения транзакций в Cosmos, как в Ethereum, используется газ. Исполняя транзакцию, валидаторы вычисляют ее стоимость в условных единицах «gas». Отправляя транзакцию, пользователь указывает цену, которую он готов платить за единицу газа и лимит, который он готов потратить. Это является основанием для вычисления цены за транзакцию.
Важным для Application в Cosmos являются требования к детерминизму кода, т.е. разрабатываемые операции не должны порождать разные результаты в разные моменты времени или на разных архитектурах, иначе блокчейн не будет работать.
Дополнительное ПО
Параллельно с созданием кода Application, Cosmos SDK позволяет сразу же получить код, который вызывает нужные функции с клиентских машин. Этот код можно использовать на сайте, работающем с Cosmos, или в кошельке (клиенте) сети.
На JavaScript я нашел несколько полезных библиотек: js-cosmos, cosmosjs и универсальную js-abci, реализующую интерфейс ABCI. Их удобно использовать, если взаимодействие с вашим блокчейном планируется из браузера. ABCI позволяет создавать Application на разных языках, среди которых Java, C++, Python. Проект lotion, например, позволяет создать блокчейн полностью на Javascript.
Cosmos бурно развивается, на этом движке запускается много разных проектов. Рекомендую обратить на него внимание, если у вас есть экспертиза в Go и вы хотите надежное работающее решение.
Подписывайтесь на канал Forklog в YouTube!
Нашли ошибку в тексте? Выделите ее и нажмите CTRL+ENTER
Рассылки ForkLog: держите руку на пульсе биткоин-индустрии!
Самый быстрый способ узнать, как работает блокчейн — это создать его.
Вы здесь, потому что, как и я, немного помешаны на криптовалюте. Кроме этого, вы явно хотите узнать, как работает блокчейн — фундаментальная технология, связанная с криптовалютой.
Однако, понимание блокчейн — не самое простое дело, по крайней мере, так было в моем случае. Я прошел через тонны видеороликов, изучал руководства и постоянно разочаровывался из-за слишком маленького количества примеров.
Есть вопросы по 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 |
Теперь, давайте создадим новую транзакцию, отправив 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» |
Я перезапустил свой сервер и замайнил два блока, итого их количество 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.
После этого я получил два новых блока в узле 2, чтобы убедиться в том, что цепь была длиннее. После этого, я вызвал GET /nodes/resolve в узле 1, где цепь была заменена нашим алгоритмом консенсуса:
И это была обертка… Найдите друзей и вместе попробуйте протестировать ваш блокчейн!
Подведем итоги
Надеюсь, эта статья вдохновила вас на что-нибудь новое. Я в восторге от криптовалют, так как я верю, что блокчейн радикально изменят наше представление об экономике, правительстве и учетных записях!
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»