Электронная валюта уже ни для кого не новость, а вот собственная реализация валюты на 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 successful\n"
else:
return "Transaction submission failed. Wrong signature\n"
# Отправляем ожидающие транзакции майнеру
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 SYSTEM\n
=========================================\n\n
You can find more help at: https://github.com/cosme12/SimpleCoin\n
Make sure you are using the latest version or you may end in
a parallel chain.\n\n\n""")
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 SYSTEM\n
=========================================\n\n
You can find more help at: https://github.com/cosme12/SimpleCoin\n
Make sure you are using the latest version or you may end in
a parallel chain.\n\n\n""")
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 transactions\n""")
if response in "1":
# Создаем новый кошелек
print("""=========================================\n
IMPORTANT: save this credentials or you won't be able to recover your wallet\n
=========================================\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 key\n")
addr_to = input("To: introduce destination wallet address\n")
amount = input("Amount: number stating how much do you want to send\n")
print("=========================================\n\n")
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/n\n")
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...")
Заключение
Автор призывает участвовать всех желающих в этом проекте. Главная задача – упрощение кода и повышение его читабельности. Перевод на русский осуществлен Библиотекой Программиста.
Комментарии
Я если честно не разбираюсь в пайтоне можете сделать видео обучение по этой статье выше, и можете объяснить от куда взять ссылку на манер кошелек и другие адреса пожалуйста!