С точки зрения дилетантов, биткойн — это бухгалтерская книга. Гроссбух — это набор транзакций. Давайте разберемся в этом на примере.
Предположим, ты пойдешь в продуктовый магазин. Покупаете овощи за 15 долларов. Вход в бухгалтерскую книгу будет — Вы платите владельцу овощей 15 долларов.
Потом, скажем, парень с овощами идет к врачу и платит 10 долларов за медицинский осмотр. Запись в бухгалтерской книге будет — владелец овощей платит врачу 10 долларов.
Все это рассматривается как набор операций. Это похоже на то, что мы видим в выписках с банковских счетов. Бухгалтерская книга — это набор транзакций, и в конце вы получаете остаток на вашем счете. Вот что такое биткойн в двух словах.
В этой статье мы сначала познакомимся с блок-цепочкой и тем, как работает майнинг. Затем мы узнаем, как добывать биткойн на питоне.
Blockchain and Transactions
В биткойн-бухгалтерской книге все транзакции хранятся блоками. Она содержит миллионы транзакций с момента изобретения bitcoin. Она содержит несколько блоков для этих транзакций, которые растут с каждым днём.
Эти блоки связаны между собой в виде связанного списка. Точно так же, как связанные списки имеют несколько узлов, где все узлы соединены с другим, так и цепочка блоков имеет несколько блоков.
Скажем, есть цепочка из пяти блоков — Блок1, Блок2, Блок3, Блок4 и Блок5. Тогда Блок1 будет указывать на Блок2, Блок2 будет указывать на Блок3 и так далее.
Размер одного биткойн-блока — один мегабайт. Итак, в одном мегабайте мы храним несколько транзакций, затем переходим к следующему блоку для дальнейших транзакций. Все эти блоки связаны между собой.
Биткойн Криптография и Майнинг
Протокол Bitcoin имеет некоторый механизм безопасности для обнаружения мошенничества. Он использует криптографию для обеспечения безопасности транзакций. Для его реализации используется криптографическая функция под названием SHA256.
Она берет входную строку и генерирует хэш длиной 256 бит. Взломать это значение практически невозможно. Это определённая величина, но её нельзя вычислить.
На Python мы можем сгенерировать это хэш-значение с помощью приведенного ниже кода:
from hashlib import sha256
text = "XYZ"
print(sha256(text.encode('ascii')).hexdigest)
В bitcoin блок состоит не только из набора транзакций. В нём есть как предыдущий хэш, так и nonce (число один раз).
Я объясню, что такое «нонс», но сначала пойми это. Мы преобразовываем все в блоке в строку и генерируем хэш для этого блока. В любой момент времени есть особое требование, чтобы сгенерированный хэш имел в начале x число нулей.
Допустим, хэш, генерируемый блоком, равен 03a5x4bh34bh2jkiig243gh. Согласно требованию, нам нужны первые четыре цифры как ноль. Вот тут и появляется nonce. Количество нулей, которое нам нужно в нашем хэше, известно как сложность.
Добыча биткойна — это процесс угадывания nonce, который генерирует хэш с первым числом X нулей. Он состоит из сложных вычислений, в которых мы пытаемся найти требуемое значение нонсе.
В чем польза от добычи биткойна?
Майнеры получают биткойны для добычи блока. В 2009 году за добычу одного блока ты получишь 50 BTC. В 2012 году он был сокращен до 25 BTC.
Каждые четыре года награда уменьшается вдвое за добычу одного блока. В 2020 году награда была снижена до 6,25 BTC. Но за последние несколько лет bitcoin оценили очень высоко. Даже 6,25 BTC за блок означает 280 000 долларов (на момент публикации). Это довольно много денег за такую работу.
Многие люди по всему миру занимаются добычей биткойн. Это не очень сложно, но это трудоёмкая задача. Нужно много вычислительной мощности, чтобы получить правильную стоимость. Если 10 человек занимаются угадыванием, то тот, кто получит результат первым, выиграет награду.
Поэтому, чтобы выиграть награду в добыче биткойн, требуется и время, и удача.
Понятия, которые мы обсуждали до сих пор, были важны для понимания реальной работы по добыче bitcoin. Давайте перейдём к коду, который помогает нам добывать биткойны:
from hashlib import sha256 MAX_NONCE_VALUE = 100000000000 def SHA256(text): return sha256(text.encode("asci")).hexdigest()def mine(block_number, transactions, previous_hash, prefix_zeros): prefix_str = '0'*prefix_zeros for nonce in range(MAX_NONCE_VALUE): text = str(block_number)+transactions+previous_hash+str(nonce) new_hash = SHA256(text) if new_hash.startswith(prefix_str): print("Bitcoin mined for nonce value of {nonce}") return new_hash
Вот так. Мы можем добыть биткойн с этими 12 строками кода в python.
Просмотр кода
Строка 1: Импорт библиотеки sha256 в наш проект.
Строка 2: Объявите переменную с максимальным значением nonce, до которого вы хотите угадать. Она может быть увеличена или уменьшена в зависимости от вычислительной мощности вашей системы.
Строка 3-4: Определяем функцию SHA256 для генерации хэш-значения.
Строка 5: Определите другую функцию для майнинга, в которой мы берем входные параметры номера блока, транзакции, предыдущее значение хэша и сложность (количество нулей должно быть добавлено в качестве префикса в генерируемом хэше).
Строка 6: После добавления требуемого префикса мы создаем еще одну переменную prefix_str для хранения значения Hexa. В дальнейшем эта строка будет использоваться для сравнения с сгенерированным хэшем.
Строка 7-9: A for цикла итерируется для nonce значений для генерации нового хэша с помощью вызова функции SHA256, которую мы сгенерировали на строке 3.
Строка 10-12: Сравниваем только что сгенерированный префикс хэш-значения с нужным. Если он совпадает, то мы распечатываем значение nonce, для которого добывается биткойн, и возвращаем этот сгенерированный хэш.
Заключение
Я взял сложность восьми для тестирования. Приложению пришлось выполнить итерацию около 1,2 миллиона раз, прежде чем оно угадало правильный нон-це.
В настоящее время уровень сложности блоков составляет 20. Это означает, что вы должны угадать нонсе, который генерирует хэш с префиксом 20 нулей. На обычных системах этот тип вычислений может занять до года.
Добыча биткойнов требует специального оборудования. Одними из популярных являются DragonMint T1, Antminer T9+, Antminer R4, Avalon6 и Antminer S9.
Это сайт, на котором вы можете получить информацию по блокам для майнинга. Если вы действительно хотите это сделать, то я предложу немного вложить в оборудование, чтобы увеличить шансы на добычу bitcoin.
Оригинал
Электронная валюта уже ни для кого не новость, а вот собственная реализация валюты на Python обещает быть интересной. Создаем новый Bitcoin.
Как же создать новый Bitcoin, и что для этого нужно – рассмотрим в этой статье.
Простая монета (SimpleCoin) – простая, небезопасная и не до конца реализованная версия блокчейн криптовалюты на Python. Основной задумкой проекта была идея реализовать максимально похожую, простую и рабочую версию Bitcoin. Если вы тоже хотите создать что-то свое, вам стоит обратиться к Bitcoin Repository.
Вступление
Понятие блокчейн уже не раз рассматривалось, но повторение – мать учения. Блокчейн – это база транзакций, совместно используемая всеми узлами, участвующими в системе на основе биткойн-протокола. Полная копия цепочки блоков валюты содержит каждую транзакцию, когда-либо выполняемую в валюте. С помощью этой информации можно узнать, какое значение принадлежит каждому адресу в любой точке истории.
С чего начать?
Первое, что необходимо сделать, – установить requirements.txt.
pip install -r requirements.txt
В проекте должен быть файл конфига miner_config.py
с таким содержимым:
"""Этот файл нужно изменять до того, как вы запустите майнер. Для лучшего понимания смотрите в wallet.py. """ # Тут указываем сгенерированный адрес. Все монеты пойдут сюда. MINER_ADDRESS = "q3nf394hjg-random-miner-address-34nf3i4nflkn3oi" # Тут укажите URL-адрес ноды или ее ip. Если все запущено на localhost, то пишем так: MINER_NODE_URL = "http://localhost:5000" # А здесь храним URL-адреса каждого узла в сети, чтобы можно было общаться с ними. PEER_NODES = []
Далее два важных шага:
- Запустить
miner.py
, чтобы создать ноду и начать майнить; - запустить
wallet.py
, чтобы стать пользователем и отправлять транзакциии (для этого нужно также запуститьminer.py
).
Важное замечание: не запускайте майнер в среде разработке Python, а только в консоли, т. к. он использует параллельные процессы, которые не работают в IDL-e.
Как это работает?
Самый важный файл в этом проекте – miner.py
. Запустив его, вы создаете сервер, который подключается к блокчейну и обрабатывает транзакции (которые отправляют пользователи) путем майнинга. За это вы получаете несколько монет. Чем больше нод создается, тем безопаснее становится вся цепочка.
miner.py
запускает 2 параллельных процесса:
- первый следит за добычей, обновляет цепочки и создает отчеты о работе;
- второй запускает сервер, к которому могут подключаться пиры и пользователи для запроса всей цепочки, отправки транзакций и прочего.
import time import hashlib as hasher import json import requests import base64 from flask import Flask from flask import request from multiprocessing import Process, Pipe import ecdsa from miner_config import MINER_ADDRESS, MINER_NODE_URL, PEER_NODES node = Flask(__name__) class Block: def __init__(self, index, timestamp, data, previous_hash): """Возвращает новый объект Block. Каждый блок «привязан» к предыдущему по уникальному хэшу Аргументы: index (int): Номер блока. timestamp (int): Timestamp создания блока. data (str): Данные для отправки. previous_hash(str): Строка с хэшем предыдущего блока. Атрибуты: index (int): Номер блока. timestamp (int): Timestamp создания блока. data (str): Данные для отправки. previous_hash(str): Строка с хэшем предыдущего блока. hash(str): Хэш текущего блока. """ self.index = index self.timestamp = timestamp self.data = data self.previous_hash = previous_hash self.hash = self.hash_block() def hash_block(self): """Создание уникального хэша для блока при помощи sha256.""" sha = hasher.sha256() sha.update((str(self.index) + str(self.timestamp) + str(self.data) + / str(self.previous_hash)).encode('utf-8')) return sha.hexdigest() def create_genesis_block(): """Для создания нового блока. ему нужен хэш предыдущего. Первыйблок не знает хэш предыдущего, поэтому его нужно создать руками (нулевой индекс и произвольный хэш)""" return Block(0, time.time(), {"proof-of-work": 9,"transactions": None}, "0") # Копирование блокчейн-ноды BLOCKCHAIN = [] BLOCKCHAIN.append(create_genesis_block()) """ Тут хранятся транзакции, которые относятся к текущей ноде. Если нода, которой была отправлена транзакция добавляет новый блок, он успешно принимается, но есть вероятность того, что заявка будет отклонена и транзакция вернется """ NODE_PENDING_TRANSACTIONS = [] def proof_of_work(last_proof,blockchain): # Создаем переменную, которая будет использоваться для проверки работы incrementor = last_proof + 1 # Получаем время начала start_time = time.time() # Продолжаем увеличивать инкрементатор до тех пор, пока он не будет равен числу, которое # делится на 9, и доказательству работы предыдущего блока while not (incrementor % 7919 == 0 and incrementor % last_proof == 0): incrementor += 1 start_time = time.time() # Каждые 60сек проверяем, нашла ли нода подтверждение работы if (int((time.time()-start_time)%60)==0): # Если нашла - прекращаем проверку new_blockchain = consensus(blockchain) if new_blockchain != False: #(False:другая нода первая нашла подтверждение работы) return (False,new_blockchain) # Как только число найдено, можно вернуть его как доказательство return (incrementor,blockchain) def mine(a,blockchain,node_pending_transactions): BLOCKCHAIN = blockchain NODE_PENDING_TRANSACTIONS = node_pending_transactions while True: """Майнинг - единственный способ создания новых монет. Чтобы предотвратить создание большого количества монет, процесс замедляется с помощью алгоритма доказательства работы. """ # Получаем последнее доказательство last_block = BLOCKCHAIN[len(BLOCKCHAIN) - 1] last_proof = last_block.data['proof-of-work'] # Ищем доказательство работы в текущем блоке # Программа будет ждать пока новое подтверждение не будет найдено proof = proof_of_work(last_proof, BLOCKCHAIN) # Если доказательство не нашлось - начинаем майнить опять if proof[0] == False: # Обновляем блокчейн и сохраняемся в файл BLOCKCHAIN = proof[1] a.send(BLOCKCHAIN) continue else: # Как только мы найдем действительное доказательство работы, мы можем разбить блок, # и добавить транзакцию # Загружаем все ожидающие транзакции и отправляем их на сервер NODE_PENDING_TRANSACTIONS = requests.get(MINER_NODE_URL + " /txion?update=" + MINER_ADDRESS).content NODE_PENDING_TRANSACTIONS = json.loads(NODE_PENDING_TRANSACTIONS) # Затем добавляется вознаграждение за майнинг NODE_PENDING_TRANSACTIONS.append( { "from": "network", "to": MINER_ADDRESS, "amount": 1 } ) # Теперь мы можем собрать данные, необходимые для создания нового блока new_block_data = { "proof-of-work": proof[0], "transactions": list(NODE_PENDING_TRANSACTIONS) } new_block_index = last_block.index + 1 new_block_timestamp = time.time() last_block_hash = last_block.hash # Список пустых транзакций NODE_PENDING_TRANSACTIONS = [] # Теперь создаем новый блок mined_block = Block(new_block_index, new_block_timestamp, new_block_data, last_block_hash) BLOCKCHAIN.append(mined_block) # Сообщаем клиентам, что нода готова майнить print(json.dumps({ "index": new_block_index, "timestamp": str(new_block_timestamp), "data": new_block_data, "hash": last_block_hash }) + "n") a.send(BLOCKCHAIN) requests.get(MINER_NODE_URL + "/blocks?update=" + MINER_ADDRESS) def find_new_chains(): # Получаем данные о других нодах other_chains = [] for node_url in PEER_NODES: # Получаем их цепочки GET-запросом block = requests.get(node_url + "/blocks").content # Конвертим объект JSON в словарь Python block = json.loads(block) # Проверяем, чтобы другая нода была корректной validated = validate_blockchain(block) if validated == True: # Добавляем ее в наш список other_chains.append(block) return other_chains def consensus(blockchain): # Получаем блоки из других нод other_chains = find_new_chains() # Если наша цепочка не самая длинная, то мы сохраняем самую длинную цепочку BLOCKCHAIN = blockchain longest_chain = BLOCKCHAIN for chain in other_chains: if len(longest_chain) < len(chain): longest_chain = chain # Если самая длинная цепочка не наша, делаем ее самой длинной if longest_chain == BLOCKCHAIN: # Продолжаем искать подтверждение return False else: # Сдаемся, обновляем цепочку и ищем снова BLOCKCHAIN = longest_chain return BLOCKCHAIN def validate_blockchain(block): """Проверяем отправленную цепочку. Если хэши неверны, возвращаем false block(str): json """ return True @node.route('/blocks', methods=['GET']) def get_blocks(): # Загружаем текущий блокчейн. if request.args.get("update") == MINER_ADDRESS: global BLOCKCHAIN BLOCKCHAIN = b.recv() chain_to_send = BLOCKCHAIN else: # Любая нода, которая будет подключаться, будет делать так: chain_to_send = BLOCKCHAIN # Конвертим наши блоки в словари и можем отправить им json объект chain_to_send_json = [] for block in chain_to_send: block = { "index": str(block.index), "timestamp": str(block.timestamp), "data": str(block.data), "hash": block.hash } chain_to_send_json.append(block) # Отправляем нашу цепочку тому, кто попросил chain_to_send = json.dumps(chain_to_send_json) return chain_to_send @node.route('/txion', methods=['GET','POST']) def transaction(): """Каждая отправленная транзакция в эту ноду проверяется и отправляется. Потом она ждет добавления в блокчейн. Транзакции не создают новые монеты, а только перемещают их. """ if request.method == 'POST': # При каждом новом POST-запросе мы извлекаем данные транзакции new_txion = request.get_json() # Добавляем транзакцию в список if validate_signature(new_txion['from'],new_txion['signature'],new_txion['message']): NODE_PENDING_TRANSACTIONS.append(new_txion) # Транзакция успешно отправлена - сообщаем это в консоль print("New transaction") print("FROM: {0}".format(new_txion['from'])) print("TO: {0}".format(new_txion['to'])) print("AMOUNT: {0}n".format(new_txion['amount'])) return "Transaction submission successfuln" else: return "Transaction submission failed. Wrong signaturen" # Отправляем ожидающие транзакции майнеру elif request.method == 'GET' and request.args.get("update") == MINER_ADDRESS: pending = json.dumps(NODE_PENDING_TRANSACTIONS) NODE_PENDING_TRANSACTIONS[:] = [] return pending def validate_signature(public_key,signature,message): """Проверяем правильность подписи. Это используется для доказательства того, что это вы (а не кто-то еще), пытающийся совершить транзакцию за вас. Вызывается, когда пользователь пытается отправить новую транзакцию. """ public_key = (base64.b64decode(public_key)).hex() signature = base64.b64decode(signature) vk = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_key), curve=ecdsa.SECP256k1) try: return(vk.verify(signature, message.encode())) except: return False def welcome_msg(): print(""" =========================================n SIMPLE COIN v1.0.0 - BLOCKCHAIN SYSTEMn =========================================nn You can find more help at: https://github.com/cosme12/SimpleCoinn Make sure you are using the latest version or you may end in a parallel chain.nnn""") if __name__ == '__main__': welcome_msg() # Запускаем майнинг a,b=Pipe() p1 = Process(target = mine, args=(a,BLOCKCHAIN,NODE_PENDING_TRANSACTIONS)) p1.start() # Запускаем сервер для приема транзакций p2 = Process(target = node.run(), args=b) p2.start()
wallet.py
используется для пользователей. Запуск этого файла позволит вам генерировать новые адреса, отправлять монеты и проверять историю транзакций. Помните, что если вы его запускаете на локальном сервере, вам нужен процесс miner.py
.
"""Это ваш кошелек. Здесь вы можете сделать несколько вещей: - Создать новый адрес (открытый и закрытый ключ). Вы будете использовать этот адрес (открытый ключ) для отправки или получения любых транзакций. У вас может быть столько адресов, сколько пожелаете, но если вы потеряете доступ - восстановить его вы уже не сможете. - Отправлять монеты на другой адрес. - Извлекать целую цепочку и проверять баланс. Если вы впервые используете этот скрипт, не забудьте сгенерировать новый адрес и отредактируйте файл конфигурации miner. Временная метка захэширована. Когда вы отправляете транзакцию, она будет получена несколькими узлами. Если какой-либо узел майнит блок, ваша транзакция будет добавлена в blockchain, а другие узлы будут ожидать. Если какой-либо узел видит, что ваша транзакция с той же меткой времени, они должны удалить ее из node_pending_transactions, чтобы избежать ее обработки более 1 раза. """ import requests import time import base64 import ecdsa def welcome_msg(): print(""" =========================================n SIMPLE COIN v1.0.0 - BLOCKCHAIN SYSTEMn =========================================nn You can find more help at: https://github.com/cosme12/SimpleCoinn Make sure you are using the latest version or you may end in a parallel chain.nnn""") def wallet(): response = False while response not in ["1","2","3"]: response = input("""What do you want to do? 1. Generate new wallet 2. Send coins to another wallet 3. Check transactionsn""") if response in "1": # Создаем новый кошелек print("""=========================================n IMPORTANT: save this credentials or you won't be able to recover your walletn =========================================n""") generate_ECDSA_keys() elif response in "2": addr_from = input("From: introduce your wallet address (public key)n") private_key = input("Introduce your private keyn") addr_to = input("To: introduce destination wallet addressn") amount = input("Amount: number stating how much do you want to sendn") print("=========================================nn") print("Is everything correct?n") print("From: {0}nPrivate Key: {1}nTo: {2}nAmount: {3}n".format (addr_from,private_key,addr_to,amount)) response = input("y/nn") if response.lower() == "y": send_transaction(addr_from,private_key,addr_to,amount) elif response == "3": check_transactions() def send_transaction(addr_from,private_key,addr_to,amount): """Отправляем транзакцию на разные узлы. Как только главная нода начнет майнить блок, транзакция добавляется в блокчейн. Несмотря на это, существует небольшая вероятность того, что ваша транзакция будет отменена из-за других узлов, имеющих более длинную цепочку. Поэтому убедитесь, что ваша транзакция глубоко в цепочке, прежде чем утверждать, что она одобрена! """ if len(private_key) == 64: signature,message = sign_ECDSA_msg(private_key) url = 'http://localhost:5000/txion' payload = {"from": addr_from, "to": addr_to, "amount": amount, "signature": / signature.decode(), "message": message} headers = {"Content-Type": "application/json"} res = requests.post(url, json=payload, headers=headers) print(res.text) else: print("Wrong address or key length! Verify and try again.") def check_transactions(): """Извлекаем весь блокчейн. Тут вы можете проверить свой баланс. Если блокчейн очень длинный, загрузка может занять время. """ res = requests.get('http://localhost:5000/blocks') print(res.text) def generate_ECDSA_keys(): """Эта функция следит за созданием вашего private и public ключа. Очень важно не потерять ни один из них т.к. доступ к кошельку будет потерян. Если кто-то получит доступ к вашему кошельку, вы рискуете потерять свои монеты. private_key: str public_ley: base64 """ sk = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1) # private ключ private_key = sk.to_string().hex() # конвертим private ключ в hex vk = sk.get_verifying_key() # public ключ public_key = vk.to_string().hex() print("Private key: {0}".format(private_key)) # кодируем public ключ, чтобы сделать его короче public_key = base64.b64encode(bytes.fromhex(public_key)) # используем decode(), чтобы удалить b'' из строки print("Wallet address / Public key: {0}".format(public_key.decode())) def sign_ECDSA_msg(private_key): """Подписываем сообщение для отправки private ключ должен быть hex return signature: base64 message: str """ # получаем timestamp, округляем, переводим в строку и кодируем message=str(round(time.time())) bmessage = message.encode() sk = ecdsa.SigningKey.from_string(bytes.fromhex(private_key), curve=ecdsa.SECP256k1) signature = base64.b64encode(sk.sign(bmessage)) return signature,message if __name__ == '__main__': welcome_msg() wallet() input("Press any key to exit...")
Заключение
Автор призывает участвовать всех желающих в этом проекте. Главная задача – упрощение кода и повышение его читабельности. Перевод на русский осуществлен Библиотекой Программиста.
Оригинал
Другие материалы по теме:
- Python: взлом криптографической хеш-функции через BruteForce
- Блокчейн, ИИ, бессерверные вычисления: ТОП-10 технологий 2018
- 5 тенденций в программировании для 2018 года
- Логика в программировании: логические задачи с собеседований
Для включения майнинга нам нужно разработать функцию майнинга. Функциональность майнинга должна генерировать дайджест по заданной строке сообщения и предоставлять подтверждение работы. Давайте обсудим это в этой главе.
Функция дайджеста сообщения
Мы напишем служебную функцию sha256 для создания дайджеста по данному сообщению –
def sha256(message): return hashlib.sha256(message.encode('ascii')).hexdigest()
Функция sha256 принимает сообщение в качестве параметра, кодирует его в ASCII, генерирует шестнадцатеричный дайджест и возвращает значение вызывающей стороне.
Функция майнинга
Сейчас мы разрабатываем функцию шахты, которая реализует нашу собственную стратегию майнинга. В этом случае наша стратегия заключается в создании хэша для данного сообщения с префиксом с указанным числом 1. Заданное количество единиц указано в качестве параметра для моей функции, указанной в качестве уровня сложности.
Например, если вы укажете уровень сложности 2, сгенерированный хэш для данного сообщения должен начинаться с двух 1 – например, 11xxxxxxxx. Если уровень сложности равен 3, сгенерированный хэш должен начинаться с трех 1 – как 111xxxxxxxx. Учитывая эти требования, мы теперь разработаем функцию майнинга, как показано в шагах, приведенных ниже.
Шаг 1
Функция майнинга принимает два параметра – сообщение и уровень сложности.
def mine(message, difficulty=1):
Шаг 2
Уровень сложности должен быть больше или равен 1, мы обеспечиваем это следующим утверждением assert:
assert difficulty >= 1
Шаг 3
Мы создаем префиксную переменную, используя заданный уровень сложности.
prefix = '1' * difficulty
Обратите внимание, что если уровень сложности равен 2, префикс будет «11», а если уровень сложности равен 3, префикс будет «111» и т. Д. Мы проверим, существует ли этот префикс в сгенерированном дайджесте сообщения. Чтобы переварить само сообщение, мы используем следующие две строки кода:
for i in range(1000): digest = sha256(str(hash(message)) + str(i))
Мы продолжаем добавлять новый номер i в хэш сообщения в каждой итерации и генерируем новый дайджест по объединенному сообщению. Поскольку входные данные для функции sha256 меняются на каждой итерации, значение дайджеста также изменяется. Мы проверяем, имеет ли это значение дайджеста установленный префикс .
if digest.startswith(prefix):
Если условие выполнено, мы завершаем цикл for и возвращаем вызывающему значение дайджеста .
Весь мой код показан здесь –
def mine(message, difficulty=1): assert difficulty >= 1 prefix = '1' * difficulty for i in range(1000): digest = sha256(str(hash(message)) + str(i)) if digest.startswith(prefix): print ("after " + str(i) + " iterations found nonce: "+ digest) return digest
Для вашего понимания мы добавили оператор print, который печатает дайджест-значение и количество итераций, необходимых для выполнения условия перед возвратом из функции.
Тестирование функции майнинга
Чтобы проверить нашу функцию майнинга, просто выполните следующее утверждение –
mine ("test message", 2)
Когда вы запустите приведенный выше код, вы увидите вывод, похожий на приведенный ниже –
after 138 iterations found nonce: 11008a740eb2fa6bf8d55baecda42a41993ca65ce66b2d3889477e6bfad1484c
Обратите внимание, что сгенерированный дайджест начинается с «11». Если вы измените уровень сложности на 3, сгенерированный дайджест начнется с «111», и, конечно, для этого потребуется больше итераций. Как видите, майнер с большей вычислительной мощностью сможет добывать данное сообщение раньше. Вот как шахтеры конкурируют друг с другом за получение своих доходов.
Теперь мы готовы добавить больше блоков в нашу цепочку блоков. Давайте узнаем это в нашей следующей главе.
Время на прочтение
5 мин
Количество просмотров 2.3K
Построить граф по логам процесса очень просто. В распоряжении аналитиков в настоящее время достаточное многообразие профессиональных разработок, таких как Celonis, Disco, PM4PY, ProM и т.д., призванных облегчить исследование процессов. Намного сложнее найти отклонения на графах, сделать верные выводы по ним.
Что делать, если некоторые профессиональные разработки, зарекомендовавшие себя и представляющие особый интерес не доступны по тем или иным причинам, или вам хочется больше свободы в расчетах при работе с графами? Насколько сложно самим написать майнер и реализовать некоторые необходимые возможности для работы с графами? Сделаем это на практике с помощью стандартных библиотек Python, реализуем расчеты и дадим с их помощью ответы на детальные вопросы, которые могли бы заинтересовать владельцев процесса.
Сразу хочется оговориться, что решение, приведенное в статье, не является промышленной реализацией. Это некоторая попытка начать работать с логами самостоятельно с помощью простого кода, который понятно работает, а значит, позволяет легко его адаптировать. Это решение не стоит использовать на больших данных, для этого требуется существенная его доработка, например, с применением векторных вычислений или путем изменения подхода к сбору и агрегации информации о событиях.
Перед построением графа, необходимо выполнить расчеты. Собственно расчет графа и будет тем самым майнером, о котором говорилось ранее. Для выполнения расчета необходимо собрать знания о событиях — вершинах графа и связях между ними и записать их, например в справочники. Заполняются справочники с помощью процедуры расчета calc (код на github). Заполненные справочники передаются в качестве параметров процедуре отрисовки графов draw (см. код по ссылке выше). Эта процедура форматирует данные, в представленный ниже вид:
digraph f {"Permit SUBMITTED by EMPLOYEE (6255)" -> "Permit APPROVED by ADMINISTRATION (4839)" [label=4829 color=black penwidth=4.723857205400346]
"Permit SUBMITTED by EMPLOYEE (6255)" -> "Permit REJECTED by ADMINISTRATION (83)" [label=83 color=pink2 penwidth=2.9590780923760738]
"Permit SUBMITTED by EMPLOYEE (6255)" -> "Permit REJECTED by EMPLOYEE (231)" [label=2 color=pink2 penwidth=1.3410299956639813]
…
start [color=blue shape=diamond]
end [color=blue shape=diamond]}
и передает его для отрисовки графическому движку Graphviz.
Приступим к построению и исследованию графов с помощью реализованного майнера. Будем повторять процедуры чтения и сортировки данных, расчета и отрисовки графов, как в приведенных ниже примерах. Для примеров взяты логи событий по международным декларациям из соревнования BPIC2020. Ссылка на соревнование.
Считаем данные из лога, отсортируем их по дате и времени. Предварительно формат .xes преобразован в .xlsx.
df_full = pd.read_excel('InternationalDeclarations.xlsx')
df_full = df_full[['id-trace','concept:name','time:timestamp']]
df_full.columns = ['case:concept:name', 'concept:name', 'time:timestamp']
df_full['time:timestamp'] = pd.to_datetime(df_full['time:timestamp'])
df_full = df_full.sort_values(['case:concept:name','time:timestamp'], ascending=[True,True])
df_full = df_full.reset_index(drop=True)
Выполним расчет графа.
dict_tuple_full = calc(df_full)
Выполним отрисовку графа.
draw(dict_tuple_full,'InternationalDeclarations_full')
После выполнения процедур получим граф процесса:
Так как полученный граф не читаем, упростим его.
Есть несколько подходов к улучшению читаемости или упрощению графа:
- использовать фильтрацию по весам вершин или связей;
- избавиться от шума;
- сгруппировать события по схожести названия.
Применим 3 подход.
Создадим словарь объединения событий:
_dict = {'Permit SUBMITTED by EMPLOYEE': 'Permit SUBMITTED',
'Permit APPROVED by ADMINISTRATION': 'Permit APPROVED',
'Permit APPROVED by BUDGET OWNER': 'Permit APPROVED',
'Permit APPROVED by PRE_APPROVER': 'Permit APPROVED',
'Permit APPROVED by SUPERVISOR': 'Permit APPROVED',
'Permit FINAL_APPROVED by DIRECTOR': 'Permit FINAL_APPROVED',
'Permit FINAL_APPROVED by SUPERVISOR': 'Permit FINAL_APPROVED',
'Start trip': 'Start trip',
'End trip': 'End trip',
'Permit REJECTED by ADMINISTRATION': 'Permit REJECTED',
'Permit REJECTED by BUDGET OWNER': 'Permit REJECTED',
'Permit REJECTED by DIRECTOR': 'Permit REJECTED',
'Permit REJECTED by EMPLOYEE': 'Permit REJECTED',
'Permit REJECTED by MISSING': 'Permit REJECTED',
'Permit REJECTED by PRE_APPROVER': 'Permit REJECTED',
'Permit REJECTED by SUPERVISOR': 'Permit REJECTED',
'Declaration SUBMITTED by EMPLOYEE': 'Declaration SUBMITTED',
'Declaration SAVED by EMPLOYEE': 'Declaration SAVED',
'Declaration APPROVED by ADMINISTRATION': 'Declaration APPROVED',
'Declaration APPROVED by BUDGET OWNER': 'Declaration APPROVED',
'Declaration APPROVED by PRE_APPROVER': 'Declaration APPROVED',
'Declaration APPROVED by SUPERVISOR': 'Declaration APPROVED',
'Declaration FINAL_APPROVED by DIRECTOR': 'Declaration FINAL_APPROVED',
'Declaration FINAL_APPROVED by SUPERVISOR': 'Declaration FINAL_APPROVED',
'Declaration REJECTED by ADMINISTRATION': 'Declaration REJECTED',
'Declaration REJECTED by BUDGET OWNER': 'Declaration REJECTED',
'Declaration REJECTED by DIRECTOR': 'Declaration REJECTED',
'Declaration REJECTED by EMPLOYEE': 'Declaration REJECTED',
'Declaration REJECTED by MISSING': 'Declaration REJECTED',
'Declaration REJECTED by PRE_APPROVER': 'Declaration REJECTED',
'Declaration REJECTED by SUPERVISOR': 'Declaration REJECTED',
'Request Payment': 'Request Payment',
'Payment Handled': 'Payment Handled',
'Send Reminder': 'Send Reminder'}
Выполним группировку событий и отрисуем граф процесса еще раз.
df_full_gr = df_full.copy()
df_full_gr['concept:name'] = df_full_gr['concept:name'].map(_dict)
dict_tuple_full_gr = calc(df_full_gr)
draw(dict_tuple_full_gr,'InternationalDeclarations_full_gr'
После группировки событий по схожести названия читаемость графа улучшилась. Попробуем найти ответы на вопросы. Ссылка на список вопросов. Например, скольким декларациям не предшествовало предодобренное разрешение?
Для ответа на поставленный вопрос отфильтруем граф по интересующим событиям и отрисуем граф процесса еще раз.
df_full_gr_f = df_full_gr[df_full_gr['concept:name'].isin(['Permit SUBMITTED',
'Permit APPROVED',
'Permit FINAL_APPROVED',
'Declaration FINAL_APPROVED',
'Declaration APPROVED'])]
df_full_gr_f = df_full_gr_f.reset_index(drop=True)
dict_tuple_full_gr_f = calc(df_full_gr_f)
draw(dict_tuple_full_gr_f,'InternationalDeclarations_full_gr_isin')
С помощью полученного графа мы легко сможем дать ответ на поставленный вопрос – 116 и 312 декларациям не предшествовало предодобренное разрешение.
Можно дополнительно “провалиться” (отфильтровать по ‘case:concept:name’, участвующих в нужной связи) за связи 116 и 312 и убедиться, что на графах будут отсутствовать события, связанные с разрешениями.
“Провалимся” за связь 116:
df_116 = df_full_gr_f[df_full_gr_f['case:concept:name'].isin(d_case_start2['Declaration FINAL_APPROVED'])]
df_116 = df_116.reset_index(drop=True)
dict_tuple_116 = calc(df_116)
draw(dict_tuple_116,'InternationalDeclarations_full_gr_isin_116')
“Провалимся” за связь 312:
df_312 = df_full_gr_f[df_full_gr_f['case:concept:name'].isin(d_case_start2['Declaration APPROVED'])]
df_312 = df_312.reset_index(drop=True)
dict_tuple_312 = calc(df_312)
draw(dict_tuple_312,'InternationalDeclarations_full_gr_isin_312')
Так как на полученных графах полностью отсутствуют события, связанные с разрешениями, корректность ответов 116 и 312 подтверждается.
Как видим, написать майнер и реализовать необходимые возможности для работы с графами не сложная задача, с которой успешно справились встроенные функции Python и Graphviz в качестве графического движка.
In this post, we will learn to build a very simple miner in Python. Of course this miner will be comparatively slow and limited and only be useful in our test network, but it will hopefully help to explain the principles behind mining.
When we want to mine a block, we first need some information on the current state of the blockchain, like the hash of the current last block, the current value of the difficulty or the coin base value, i.e. the number of BTC that we earn when mining the block. When we are done building the block, we need to find a way to submit it into the bitcoin network so that it is accepted by all nodes and permanently added to the chain.
If we were member of a mining pool, there would be a mining server that would provide us the required information. As we want to build a solo mining script, we need to communicate with bitcoin core to get that information and to submit our final block. Fortunately, the RPC interface of bitcoin core offers methods to facilitate that communication that were introduced with BIP22.
First, there is the method getblocktemplate
. It will deliver all the required information that we need to build a valid block and even propose transactions that we should include in the block. These transactions will be taken from the so called mempool which is a collection of transactions that the bitcoin server knows which have not been added to a block yet (see miner.cpp/BlockAssembler::addPackageTxs
in the bitcoin core source code for details on how the selection process works).
If the client is done building the block, it can submit the final block using the method submitblock
. This method will run a couple of checks on the block, for instance that it can be correctly decoded, that the first transaction – and only the first – is a coinbase transaction, that it is not a duplicate and that the proof-of-work is valid. If all the checks pass, it will add the block to the local copy of the blockchain. If a check fails, it will return a corresponding error code to the caller.
With that understanding, let us now write down the processing logic for our simple miner, assuming that we only want to mine one additional block. First, we will use getblocktemplate
to get the basic parameters that we need and transaction that we can include. Then we will create a new block and a new coinbase transaction. We then add the coinbase transaction followed by all the other transactions to the block.
Once we have that block, we enter a loop. Within the loop, we calculate the hash of the block and compare this against the target. If we can meet the target, we are done and submit the newly created block using submitblock
. Otherwise, we increment the nonce and try again.
We have discussed most of these steps in some details in the previous posts, with one exception – coinbase transactions. A coinbase transaction is, by definition, a transaction which generates bitcoin because it has valid outputs, but does not spend any UTXOs. Technically, a coinbase transaction is a transaction which (see CTransaction::IsCoinBase()
)
- has exactly one transaction input
- the previous transaction ID in this input is zero (i.e. a hexadecimal string consisting of 32 zeros “00”)
- the index of the previous output is -1 (encoded as 0xFFFFFFFF)
As it does not refer to any output, the signature script of a coinbase transaction will never be executed. It can therefore essentially contain an arbitrary value. The only restriction defined by the protocol is described in BIP34, which defines that the first bytes of the signature script should be a valid script that consists of the height of the new block as pushed data. The remainder of the coinbase signature script (which is limited to 100 bytes in total) can be used by the miner at will.
Many miners use this freedom to solve a problem with the length of the nonce in the block header. Here, the nonce is a 32 bit value, which implies that a miner can try 232, i.e. roughly 4 billion different combinations. Modern mining hardware based on ASICs can search that range within fractions of seconds, and the current difficulty is so high that it is rather likely that no solution can be found by just changing the nonce. So we have to change other fields in the block header to try out more hashes.
What are good candidates for this? We could of course use the block creation time, but the bitcoin server validates this field and will reject the block if it deviates significantly from the current time. Instead miners typically use the coinbase signature script as an extra nonce that they modify to increase the range of possible hashes. Therefore the fields after the height are often combinations of an extra nonce and additional data, like the name of the mining pool (increasing the extra nonce is a bit less effective than increasing the nonce in the block header, as it changes the hash of the coinbase transactions and therefore forces us to recalculate the Merkle root, therefore this is most often implemented as an outer loop, with the inner loop being the increments of the nonce in the block header).
To illustrate this, let us look at an example. The coinbase signature script of the coinbase transaction in block #400020 is:
03941a060004c75ccf5604a070c007089dcd1424000202660a636b706f6f6c102f426974667572792f4249503130302f
If we decode this, we find that the first part is in fact a valid script and corresponds to the following sequence of instructions (keep in mind that all integers are encoded as little endian within the script):
OP_PUSH 400020 OP_0 OP_PUSH 1456430279 OP_PUSH 130052256 OP_PUSH 7350439741450669469
As specified by BIP34, the first pushed data is the height of that block as expected. After the OP_0, we see another push instruction, pushing the Unix system time corresponding to Thu Feb 25 20:57:59 2016, which is the creation time of the block.
The next pushed data is a bit less obvious. After looking at the source code of the used mining software, I assume that this is the nanoseconds within the second returned by the Unix system call clock_gettime
. This is then followed by an eight byte integer (7350439741450669469) which is the extra nonce.
The next part of the signature script is not actually a valid script, but a string – a newline character (0xa), followed by the string “ckpool”. This is a fixed sequence of bytes that indicates the mining software used.
Finally, there is one last push operation which pushes the string “/Bitfury/BIP100/”, which tells us that the block has been mined by the Bitfury pool and that this pool supports BIP100.
Enough theory – let us put this to work! Using the utility functions in my btc Python package, it is now not difficult to write a short program that performs the actual mining.
However, we need some preparations to set up our test environment that are related to our plan to use the getblocktemplate
RPC call. This call performs a few validations that can be a bit tricky in a test environment. First, it will verify that the server is connected, i.e. we need at least one peer. So we need to start two docker container, let us call them alice and bob again, and find out the IP address of the container bob in the Docker bridget network. The three following statements should do this for you.
$ docker run --rm -d -p 18332:18332 --name="alice" alice $ docker run --rm -d --name="bob" bitcoin-alpine $ docker network inspect bridge | grep -A 4 "bob" - | grep "IPv4" -
Assuming that this gives you 172.17.0.3 (replace this with whatever the result is in your case), we can now again use the addnode
RPC call to connect the two nodes.
$ bitcoin-cli --rpcuser=user --rpcpassword=password -regtest addnode "172.17.0.3" add
The next validation that the bitcoin server will perform when we ask for a block template is that the local copy of the blockchain is up to date. It does by verifying that the time stamp of the last block in the chain is less than 24 hours in the past. As it is likely that a bit more time has passed since you have created the Alice container, we therefore need to use the mining functionality built into bitcoin core to create at least one new block.
$ bitcoin-cli --rpcuser=user --rpcpassword=password -regtest generate 1
Now we are ready to run our test. The next few lines will download the code from GitHub, create one transaction that will then be included in the block. We will create this transaction using the script SendMoney.py
that we have already used in an earlier post.
$ git clone https://github.com/christianb93/bitcoin.git $ cd bitcoin $ python SendMoney.py $ python Miner.py
You should then see an output telling you that a block with two transactions (one coinbase transaction and the transaction that we have generated) was mined, along with the previous height of the blockchain and the new height which should be higher by one.
Let us now verify that everything works. First, let us get the hash of the current last block.
$ bitcoin-cli --rpcuser=user --rpcpassword=password -regtest getchaintips [ { "height": 109, "hash": "07849d5c8ddcdc609d7acc3090bc48bbe4403c36008d46b5a291185334efe1bf", "branchlen": 0, "status": "active" } ]
Take the value from the hash field in the output and feed it into a call to getblock
:
$ bitcoin-cli --rpcuser=user --rpcpassword=password -regtest getblock "07849d5c8ddcdc609d7acc3090bc48bbe4403c36008d46b5a291185334efe1bf" { "hash": "07849d5c8ddcdc609d7acc3090bc48bbe4403c36008d46b5a291185334efe1bf", "confirmations": 1, "strippedsize": 367, "size": 367, "weight": 1468, "height": 109, "version": 536870912, "versionHex": "20000000", "merkleroot": "8769987458af75adc80d6792848e5cd5cb8178a9584157bb4be79b77cda95909", "tx": [ "7ba3186ca8e5aae750614a3211422423a0a217f5999d0de6dfeb8968aeb01900", "1094360149626a421b4ddbc7eb58a815762700316c36407770b96ffc36a7735b" ], "time": 1522952768, "mediantime": 1521904060, "nonce": 1, "bits": "207fffff", "difficulty": 4.656542373906925e-10, "chainwork": "00000000000000000000000000000000000000000000000000000000000000dc", "previousblockhash": "2277e40bf4c0ebde3fb5f38fcbd384e39df3471ad192cc46f66ea8d8d96327e7" }
The second entry in the list tx
should now match the ID of the newly created transaction which was displayed when executing the SendMoney.py
script. This proves that our new transaction has been included in the block.
Congratulations, you have just mined 50 BTC – unfortunately only in your local installation, not in the production network. Of course, real miners work differently, using mining pools to split the work between many different nodes and modern ASICS to achieve the hash rates that you need to be successful in the production network. But at least we have built a simple miner more or less from scratch, relying only on the content of this and the previous posts in this series and without using any of the many Python bitcoin libraries that are out there.
This concludes my current series on the bitcoin blockchain – I hope you enjoyed the posts and had a bit of fun. If you want to learn more, here are a few excellent sources of information that I recommend.
- Of course the ultimative source of information is always the bitcoin core source code itself that we have already consulted several times
- The Bitcoin wiki contains many excellent pages on most of what we have discussed
- There is of course the original bitcoin paper which you should now be able to read and understand
- and of course there are tons of good books out there, I personally liked Mastering Bitcoin by A. Antonopoulos which is also available online
Самый быстрый способ узнать, как работает блокчейн — это создать его.
Вы здесь, потому что, как и я, немного помешаны на криптовалюте. Кроме этого, вы явно хотите узнать, как работает блокчейн — фундаментальная технология, связанная с криптовалютой.
Однако, понимание блокчейн — не самое простое дело, по крайней мере, так было в моем случае. Я прошел через тонны видеороликов, изучал руководства и постоянно разочаровывался из-за слишком маленького количества примеров.
Есть вопросы по 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 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»