from functools import reduce
import hashlib as hl
from collections import OrderedDict
import json
# Import two functions from our hash_util.py file. Omit the ".py" in the import
from hash_util import hash_string_256, hash_block
# The reward we give to miners (for creating a new block)
MINING_REWARD = 10
# Our starting block for the blockchain
genesis_block = {
'previous_hash': '',
'index': 0,
'transactions': [],
'proof': 100
}
# Initializing our (empty) blockchain list
blockchain = [genesis_block]
# Unhandled transactions
open_transactions = []
# We are the owner of this blockchain node, hence this is our identifier (e.g. for sending coins)
owner = 'Max'
# Registered participants: Ourself + other people sending/ receiving coins
participants = {'Max'}
def load_data():
with open('blockchain.txt', mode='r') as f:
file_content = f.readlines()
global blockchain
global open_transactions
blockchain = json.loads(file_content[0][:-1])
open_transactions = json.loads(file_content[1])
load_data()
def save_data():
with open('blockchain.txt', mode='w') as f:
f.write(json.dumps(blockchain))
f.write('\n')
f.write(json.dumps(open_transactions))
def valid_proof(transactions, last_hash, proof):
"""Validate a proof of work number and see if it solves the puzzle algorithm (two leading 0s)
Arguments:
:transactions: The transactions of the block for which the proof is created.
:last_hash: The previous block's hash which will be stored in the current block.
:proof: The proof number we're testing.
"""
# Create a string with all the hash inputs
guess = (str(transactions) + str(last_hash) + str(proof)).encode()
# Hash the string
# IMPORTANT: This is NOT the same hash as will be stored in the previous_hash. It's a not a block's hash. It's only used for the proof-of-work algorithm.
guess_hash = hash_string_256(guess)
print(guess_hash)
# Only a hash (which is based on the above inputs) which starts with two 0s is treated as valid
# This condition is of course defined by you. You could also require 10 leading 0s - this would take significantly longer (and this allows you to control the speed at which new blocks can be added)
return guess_hash[0:2] == '00'
def proof_of_work():
"""Generate a proof of work for the open transactions, the hash of the previous block and a random number (which is guessed until it fits)."""
last_block = blockchain[-1]
last_hash = hash_block(last_block)
proof = 0
# Try different PoW numbers and return the first valid one
while not valid_proof(open_transactions, last_hash, proof):
proof += 1
return proof
def get_balance(participant):
"""Calculate and return the balance for a participant.
Arguments:
:participant: The person for whom to calculate the balance.
"""
# Fetch a list of all sent coin amounts for the given person (empty lists are returned if the person was NOT the sender)
# This fetches sent amounts of transactions that were already included in blocks of the blockchain
tx_sender = [[tx['amount'] for tx in block['transactions']
if tx['sender'] == participant] for block in blockchain]
# Fetch a list of all sent coin amounts for the given person (empty lists are returned if the person was NOT the sender)
# This fetches sent amounts of open transactions (to avoid double spending)
open_tx_sender = [tx['amount']
for tx in open_transactions if tx['sender'] == participant]
tx_sender.append(open_tx_sender)
print(tx_sender)
amount_sent = reduce(lambda tx_sum, tx_amt: tx_sum + sum(tx_amt)
if len(tx_amt) > 0 else tx_sum + 0, tx_sender, 0)
# This fetches received coin amounts of transactions that were already included in blocks of the blockchain
# We ignore open transactions here because you shouldn't be able to spend coins before the transaction was confirmed + included in a block
tx_recipient = [[tx['amount'] for tx in block['transactions']
if tx['recipient'] == participant] for block in blockchain]
amount_received = reduce(lambda tx_sum, tx_amt: tx_sum + sum(tx_amt)
if len(tx_amt) > 0 else tx_sum + 0, tx_recipient, 0)
# Return the total balance
return amount_received - amount_sent
def get_last_blockchain_value():
""" Returns the last value of the current blockchain. """
if len(blockchain) < 1:
return None
return blockchain[-1]
def verify_transaction(transaction):
"""Verify a transaction by checking whether the sender has sufficient coins.
Arguments:
:transaction: The transaction that should be verified.
"""
sender_balance = get_balance(transaction['sender'])
return sender_balance >= transaction['amount']
# This function accepts two arguments.
# One required one (transaction_amount) and one optional one (last_transaction)
# The optional one is optional because it has a default value => [1]
def add_transaction(recipient, sender=owner, amount=1.0):
""" Append a new value as well as the last blockchain value to the blockchain.
Arguments:
:sender: The sender of the coins.
:recipient: The recipient of the coins.
:amount: The amount of coins sent with the transaction (default = 1.0)
"""
# transaction = {
# 'sender': sender,
# 'recipient': recipient,
# 'amount': amount
# }
transaction = OrderedDict(
[('sender', sender), ('recipient', recipient), ('amount', amount)])
if verify_transaction(transaction):
open_transactions.append(transaction)
participants.add(sender)
participants.add(recipient)
save_data()
return True
return False
def mine_block():
"""Create a new block and add open transactions to it."""
# Fetch the currently last block of the blockchain
last_block = blockchain[-1]
# Hash the last block (=> to be able to compare it to the stored hash value)
hashed_block = hash_block(last_block)
proof = proof_of_work()
# Miners should be rewarded, so let's create a reward transaction
# reward_transaction = {
# 'sender': 'MINING',
# 'recipient': owner,
# 'amount': MINING_REWARD
# }
reward_transaction = OrderedDict(
[('sender', 'MINING'), ('recipient', owner), ('amount', MINING_REWARD)])
# Copy transaction instead of manipulating the original open_transactions list
# This ensures that if for some reason the mining should fail, we don't have the reward transaction stored in the open transactions
copied_transactions = open_transactions[:]
copied_transactions.append(reward_transaction)
block = {
'previous_hash': hashed_block,
'index': len(blockchain),
'transactions': copied_transactions,
'proof': proof
}
blockchain.append(block)
save_data()
return True
def get_transaction_value():
""" Returns the input of the user (a new transaction amount) as a float. """
# Get the user input, transform it from a string to a float and store it in user_input
tx_recipient = input('Enter the recipient of the transaction: ')
tx_amount = float(input('Your transaction amount please: '))
return tx_recipient, tx_amount
def get_user_choice():
"""Prompts the user for its choice and return it."""
user_input = input('Your choice: ')
return user_input
def print_blockchain_elements():
""" Output all blocks of the blockchain. """
# Output the blockchain list to the console
for block in blockchain:
print('Outputting Block')
print(block)
else:
print('-' * 20)
def verify_chain():
""" Verify the current blockchain and return True if it's valid, False otherwise."""
for (index, block) in enumerate(blockch