Commit 096236e9 authored by mehmed.mustafa's avatar mehmed.mustafa

Version 3 beta + reward distribution

parent da0e4ce3
import hashlib
import json
from transactions import Transaction, MemeTemplateTransaction, MemeTransaction, UpvoteTransaction, RewardDistributionTransaction
class Block:
# Constructor for Block
def __init__(self, index, transactions, transaction_counter, timestamp, previous_hash, proof_of_work=0):
def __init__(self, index, walletAddrMiner, transactions, transaction_counter, timestamp, previous_hash, proof_of_work=0):
self.index = index
self.walletAddrMiner = walletAddrMiner
self.transactions = transactions
self.transaction_counter = transaction_counter
self.timestamp = timestamp
......@@ -25,6 +27,9 @@ class Block:
return transactions_data
def get_transaction_classes(self):
return transactions
# Use these 2 methods for your safety
def get_index(self):
return int(self.index)
......@@ -39,20 +44,21 @@ class Block:
# Create class instances for all transactions
transactions_data = json_data["transactions"]
for Transaction in transactions_data:
transaction_type = Transaction["transaction_type"]
for _Transaction in transactions_data:
transaction_type = _Transaction["transaction_type"]
if(transaction_type == "MemeTemplate"):
new_transaction = MemeTemplateTransaction.from_json(Transaction)
new_transaction = MemeTemplateTransaction.from_json(_Transaction)
elif(transaction_type == "Meme"):
new_transaction = MemeTransaction.from_json(Transaction)
new_transaction = MemeTransaction.from_json(_Transaction)
elif(transaction_type == "Upvote"):
new_transaction = UpvoteTransaction.from_json(Transaction)
new_transaction = UpvoteTransaction.from_json(_Transaction)
else:
return "ERROR: WRONG TRANSACTION TYPE" # Should never enter here
transaction_classes.append(new_transaction)
newClass = cls(int(json_data["index"]),
json_data["walletAddrMiner"],
transaction_classes,
len(transaction_classes),
json_data["timestamp"],
......
......@@ -2,12 +2,11 @@ import hashlib
import json
import datetime
import sys
import copy
import requests
from block import Block
from transactions import image_base64encoding
from transactions import Transaction, MemeTemplateTransaction, MemeTransaction, UpvoteTransaction, RewardDistributionTransaction
import block, transactions
class Blockchain:
# difficulty level of the Proof of Work
......@@ -25,7 +24,7 @@ class Blockchain:
def create_origin_block(self):
# The block has empty list of transactions
# The block has 0 as a value for index, previous_hash, proof_of_work
origin_block = Block(0, [], 0, "0", 0)
origin_block = block.Block(0, "NoMiner", [], 0, "0", 0)
origin_block.hash = origin_block.compute_hash()
self.chain.append(origin_block)
......@@ -36,31 +35,31 @@ class Blockchain:
# Finds a value for proof_of_work which produces
# a hash that satisfies the difficulty pattern
@staticmethod
def proof_of_work(block):
def proof_of_work(_block):
satisfying_hash = False
block.proof_of_work = 0
_block.proof_of_work = 0
while not satisfying_hash:
computed_hash = block.compute_hash()
computed_hash = _block.compute_hash()
if(computed_hash.startswith(Blockchain.difficultyPattern)):
satisfying_hash = True
else:
block.proof_of_work += 1
_block.proof_of_work += 1
return computed_hash
# Checks whether the hash value of a block is valid
# and satisfies the difficulty pattern or not
@classmethod
def is_proof_valid(cls, block, block_hash):
def is_proof_valid(cls, _block, _block_hash):
# If the hash of the block doesn't match with the computed hash
if(block.compute_hash() != block_hash):
if(_block.compute_hash() != _block_hash):
return False
# Check for blocks other than the origin block
if(block.index != 0):
if(_block.index != 0):
# does the hash satisfy the difficulty level
if not block_hash.startswith(Blockchain.difficultyPattern):
if not _block_hash.startswith(Blockchain.difficultyPattern):
return False
return True
......@@ -99,15 +98,15 @@ class Blockchain:
return chain_is_valid
# Appends a block to the chain after verifying it's validity
def append_block(self, block, proof):
if not (Blockchain.is_proof_valid(block, proof)):
def append_block(self, _block, _proof):
if not (Blockchain.is_proof_valid(_block, _proof)):
return False
if (self.previous_block().hash != block.previous_hash):
if (self.previous_block().hash != _block.previous_hash):
return False
block.hash = proof
self.chain.append(block)
_block.hash = _proof
self.chain.append(_block)
# reset the list since the transactions are confirmed now
self.transactions_to_be_confirmed = []
......@@ -146,33 +145,81 @@ class Blockchain:
# Checks whether the blockchain contains image with imageId
# if exists, returns image's decoded ascii value, otherwise -1
def find_image(self, imageId):
for Block in self.chain:
current_block_transactions = Block.get_transactions()
for Transaction in current_block_transactions:
for _Block in self.chain:
current_block_transactions = _Block.get_transactions()
for _Transaction in current_block_transactions:
# If the transaction has imageId field and that field matches with the parameter imageId
if (Transaction.get("id") and Transaction["id"] == imageId):
return Transaction["base64_val"]
if (_Transaction.get("id") and _Transaction["id"] == imageId):
return _Transaction["base64_val"]
return -1
# Checks whether the blockchain contains image with imageId
# if exists, returns True, otherwise False
def does_image_exist(self, imageID):
for Block in self.chain:
current_block_transactions = Block.get_transactions()
for Transaction in current_block_transactions:
transaction_type = Transaction["transaction_type"]
for _Block in self.chain:
current_block_transactions = _Block.get_transactions()
for _Transaction in current_block_transactions:
transaction_type = _Transaction["transaction_type"]
if(transaction_type == "Meme" or transaction_type == "MemeTemplate"):
if(Transaction["id"] == imageID):
if(_Transaction["id"] == imageID):
return True
return False
# Returns the wallet address of template owner, block and tx index
def find_last_owner_template(self, _templateID):
wallet_addr = -1
block_index = -1
for _Block in self.chain:
current_block_transactions = _Block.get_transactions()
for _Transaction in current_block_transactions:
transaction_type = _Transaction["transaction_type"]
if(transaction_type == "MemeTemplate"):
#print("T_id[{}]_Temp_id[{}]".format(_Transaction["id"], _templateID))
#print("[{}]_[{}]".format(type(_Transaction["id"]), type(_templateID)))
if(_Transaction["id"] == _templateID):
wallet_addr = _Transaction["walletAddrMTOwner"]
block_index = _Block.get_index()
return wallet_addr, block_index
# Returns the wallet address of meme owner, block and tx index
def find_last_owner_meme(self, _memeID):
wallet_addr = -1
block_index = -1
for _Block in self.chain:
current_block_transactions = _Block.get_transactions()
for _Transaction in current_block_transactions:
transaction_type = _Transaction["transaction_type"]
if(transaction_type == "Meme"):
#print("T_id[{}]_Temp_id[{}]".format(_Transaction["id"], _memeID))
#print("[{}]_[{}]".format(type(_Transaction["id"]), type(_memeID)))
if(_Transaction["id"] == _memeID):
wallet_addr = _Transaction["walletAddrMOwner"]
block_index = _Block.get_index()
return wallet_addr, block_index
def find_template_of_meme(self, _memeID):
for _Block in self.chain:
current_block_transactions = _Block.get_transactions()
for _Transaction in current_block_transactions:
transaction_type = _Transaction["transaction_type"]
if(transaction_type == "Meme"):
if(_Transaction["id"] == _memeID):
return _Transaction["templateID"]
return -1
# mine_block encapsulation -> to be used in app_mine_block() function
def mine_block(self):
def mine_block(self, host_wallet):
previous_block = self.previous_block()
new_block = Block(index=previous_block.index + 1,
new_block = block.Block(index=previous_block.index + 1,
walletAddrMiner=copy.copy(host_wallet),
transactions=self.transactions_to_be_confirmed,
transaction_counter = len(self.transactions_to_be_confirmed),
timestamp=str(datetime.datetime.now()),
......@@ -180,7 +227,9 @@ class Blockchain:
proof_of_work=0)
proof_hash = self.proof_of_work(new_block)
self.append_block(new_block, proof_hash)
isSuccessful = self.append_block(new_block, proof_hash)
return isSuccessful
# Consensus mechanism to make sure that the nodes in
# the network always have the longest (valid) chain
......@@ -211,15 +260,14 @@ def consensus_mechanism(_chain, _connected_nodes, _host_address):
return chain_updated
# A function which builds chain and transactions structure
# from the json data
# A function which builds chain from the json chain
def construct_chain_again(json_chain):
generated_blockchain = Blockchain()
generated_blockchain.create_origin_block()
for json_block in json_chain:
# Create a Block instance
current_block = Block.from_json(json_block)
current_block = block.Block.from_json(json_block)
# Skip the origin block
if current_block.get_index() == 0:
......@@ -229,7 +277,26 @@ def construct_chain_again(json_chain):
# If a block of the chain cannot be validated
if not generated_blockchain.append_block(current_block, proof):
print("WRONG_BLOCK" + current_block.to_json(False))
print("WRONG_BLOCK: {}".format(current_block.to_json(True)))
raise Exception("The chain data is altered!")
return generated_blockchain
# A function which builds transactions from the json transactions
def construct_transactions_again(json_transactions):
transaction_classes = []
for transaction in json_transactions:
transaction_type = transaction["transaction_type"]
if(transaction_type == "MemeTemplate"):
new_transaction = transactions.MemeTemplateTransaction.from_json(transaction)
elif(transaction_type == "Meme"):
new_transaction = transactions.MemeTransaction.from_json(transaction)
elif(transaction_type == "Upvote"):
new_transaction = transactions.UpvoteTransaction.from_json(transaction)
else:
return "ERROR: WRONG TRANSACTION TYPE" # Should never enter here
transaction_classes.append(new_transaction)
return transaction_classes
......@@ -7,15 +7,10 @@ import hashlib
import json
import datetime
import sys
from flask import Flask, jsonify, request
import requests
from block import Block
from blockchain import Blockchain, consensus_mechanism as consensus, construct_chain_again as construct_chain
from transactions import image_base64encoding
from transactions import Transaction, MemeTemplateTransaction, MemeTransaction, UpvoteTransaction, RewardDistributionTransaction
from wallet import Wallet
from flask import Flask, jsonify, request
import block, blockchain, transactions, wallet
# Creating a Flash Web App
app = Flask(__name__)
......@@ -38,25 +33,25 @@ if(host_address == "http://127.0.0.1:5000/"):
connected_nodes.add(host_address)
# Each node starts initially with INITIAL_CREDITS amount
INITIAL_CREDITS = 1.5
INITIAL_CREDITS = 2.5
# The wallet of the node/host running this flask app instance
host_wallet = Wallet(app_port, INITIAL_CREDITS)
host_wallet = wallet.Wallet(app_port, INITIAL_CREDITS)
# Creating the Blockchain
blockchain = Blockchain()
blockchainInstance = blockchain.Blockchain()
# Creating the origin(genesis) Block
blockchain.create_origin_block()
blockchainInstance.create_origin_block()
# A function which triggers /append_block POST method of
# other connected nodes in order to register the newly mined block
def notify_all_nodes_new_block(block):
def notify_all_nodes_new_block(_block):
for node in connected_nodes:
# If the node is the host skip
if(node == host_address):
continue
requests.post("{}append_block".format(node),
data=json.dumps(block.to_json(False), sort_keys=True),
data=json.dumps(_block.to_json(False), sort_keys=True),
headers={"Content-Type": "application/json"})
# POST method for pushing a newly mined block by someone else to a node's chain
......@@ -64,11 +59,11 @@ def notify_all_nodes_new_block(block):
@app.route('/append_block', methods=['POST'])
def app_append_block():
block_data = request.get_json()
block = Block.from_json(block_data)
_block = block.Block.from_json(block_data)
proof = block_data["hash"]
# Append the block to the chain if the block is validated
if(blockchain.append_block(block, proof)):
if(blockchainInstance.append_block(_block, proof)):
response, status_code = {"Notification": "The block was appended to the chain."}, 201
else:
response, status_code = {"Error": "The block was invalid and discarded!"}, 400
......@@ -92,20 +87,16 @@ def app_append_transaction():
# Create a class instance from json according to the type
if(transaction_type == "MemeTemplate"):
new_transaction = MemeTemplateTransaction.from_json(transaction_data)
new_transaction = transactions.MemeTemplateTransaction.from_json(transaction_data)
elif(transaction_type == "Meme"):
new_transaction = MemeTransaction.from_json(transaction_data)
new_transaction = transactions.MemeTransaction.from_json(transaction_data)
elif(transaction_type == "Upvote"):
new_transaction = UpvoteTransaction.from_json(transaction_data)
new_transaction = transactions.UpvoteTransaction.from_json(transaction_data)
else:
return "ERROR: WRONG TRANSACTION TYPE" # Should never enter here
# Assign the current node's wallet address as a miner
# Each node/host assigns it's own wallet address to the miner field
new_transaction.assign_miner(host_wallet)
# Store the transaction received from the network in the local transactions_to_be_confirmed list
blockchain.add_transaction(new_transaction)
blockchainInstance.add_transaction(new_transaction)
return "Created", 201
# A function which triggers /update_nodes_list POST method of
......@@ -136,10 +127,41 @@ def app_update_nodes_list():
return "Created", 201
# A function which triggers /receive_rewards POST method of
# all connected nodes in order to make them receive reward credits
def notify_all_nodes_rewards(json_rewards):
for node in connected_nodes:
requests.post("{}receive_rewards".format(node),
data=json.dumps(json_rewards, sort_keys=True),
headers={"Content-Type": "application/json"})
# POST method for awarding credits to the node
# Used internally to receive rewards from the network
@app.route('/receive_rewards', methods=['POST'])
def app_receive_rewards():
rewards_data = request.get_json()
for key, value in rewards_data.items():
rewards_data_main = value
for _wallet, _value in rewards_data_main.items():
if(_wallet == "previous_upvoters"):
rewards_previous_blocks = _value
for _block, _data in rewards_previous_blocks.items():
for __voter_wallet, __credits in _data.items():
reward_port = (__voter_wallet.split(">")[1]).split("'")[0]
if(int(reward_port) == int(app_port)):
host_wallet.credit_amount(float(__credits))
else:
reward_port = (_wallet.split(">")[1]).split("'")[0]
if(int(reward_port) == int(app_port)):
host_wallet.credit_amount(float(_value))
return "Created", 201
# GET request for checking if the node's copy of the Blockchain is valid
@app.route('/check_validity', methods = ['GET'])
def app_check_validity():
if(Blockchain.check_validity(blockchain.chain)):
if(blockchain.Blockchain.check_validity(blockchainInstance.chain)):
response, status_code = {"Chain Validation": "The current state of the Blockchain is valid!"}, 200
else:
response, status_code = {"Chain Validation": "OPS! The current state of the Blockchain is not valid!"}, 200
......@@ -153,20 +175,20 @@ def app_check_validity():
# Prints the shorter value of base64_val by default
def app_get_chain(isShort=True):
# Make blocks inside the chain json serializable
serialized_blocks = blockchain.get_serialized_blocks(isShort)
serialized_blocks = blockchainInstance.get_serialized_blocks(isShort)
# Create a response
response = {"length": len(serialized_blocks),
"chain": serialized_blocks,
"peers": list(connected_nodes),
"pending transactions": blockchain.get_pending_transactions(isShort)}
"pending transactions": blockchainInstance.get_pending_transactions(isShort)}
return jsonify(response), 200
# GET request for pending transactions
@app.route('/get_pending_transactions', methods = ['GET'])
# Prints the shorter value of base64_val by default
def app_get_pending_transactions(isShort=True):
response = {"pending_transactions": blockchain.get_pending_transactions(isShort)}
response = {"pending_transactions": blockchainInstance.get_pending_transactions(isShort)}
return jsonify(response), 200
# POST method for pushing a new memeTemplate to the local mempool
......@@ -180,12 +202,12 @@ def app_memeTemplate():
if not (transaction_data.get("path") or transaction_data.get("name")):
return jsonify({"Error": "Missing path or name element!"}), 400
new_template = MemeTemplateTransaction(transaction_index=len(blockchain.transactions_to_be_confirmed),
new_template = transactions.MemeTemplateTransaction(transaction_index=len(blockchainInstance.transactions_to_be_confirmed),
timestamp = str(datetime.datetime.now()),
walletAddrMTOwner=host_wallet,
name=transaction_data["name"])
encodedTemplate = image_base64encoding(transaction_data["path"])
encodedTemplate = transactions.image_base64encoding(transaction_data["path"])
new_template.assign_base64(base64_val=encodedTemplate.decode('ascii'),
base64_len=len(encodedTemplate))
......@@ -194,7 +216,9 @@ def app_memeTemplate():
# so they can add it to their local mempool
notify_all_nodes_new_transaction(new_template)
return jsonify({"Notification": "The MemeTemplate transaction was received."}), 201
response = {"Notification": "The MemeTemplate transaction was received.",
"MemeTemplate Info": new_template.to_json(True)}
return jsonify(response), 201
# POST method for pushing a new meme to the local mempool
@app.route('/add_meme', methods=['POST'])
......@@ -210,14 +234,19 @@ def app_add_meme():
return jsonify({"Error": "Missing template element!"}), 400
# Check if the template exists inside the chain
if(blockchain.does_image_exist(transaction_data["templateID"])):
new_meme = MemeTransaction(transaction_index=len(blockchain.transactions_to_be_confirmed),
if(blockchainInstance.does_image_exist(transaction_data["templateID"])):
# Find the wallet addr of the MemeTemplate owner
_walletAddrMTOwner, index = blockchainInstance.find_last_owner_template(transaction_data["templateID"])
new_meme = transactions.MemeTransaction(transaction_index=len(blockchainInstance.transactions_to_be_confirmed),
timestamp = str(datetime.datetime.now()),
walletAddrMOwner=host_wallet,
walletAddrMOwner=host_wallet,
walletAddrMTOwner=_walletAddrMTOwner,
name=transaction_data["name"],
templateID=transaction_data["templateID"])
encodedMeme = image_base64encoding(transaction_data["path"])
encodedMeme = transactions.image_base64encoding(transaction_data["path"])
new_meme.assign_base64(base64_val= encodedMeme.decode('ascii'),
base64_len= len(encodedMeme))
......@@ -228,34 +257,50 @@ def app_add_meme():
else:
return jsonify({"Error": "Template for this meme was not found inside the chain!"}), 400
return jsonify({"Notification": "The Meme transaction was received."}), 201
response = {"Notification": "The Meme transaction was received.",
"Meme Info": new_meme.to_json(True)}
return jsonify(response), 201
# POST method for upvoting a meme inside the blockchain
@app.route('/upvote', methods=['POST'])
def app_upvote():
# Expected JSON data format
# {"transaction_type":"Upvote","memeVoteId":"memeIdValue"}
# {"transaction_type":"Upvote","id_memeVote":"memeIdValue"}
transaction_data = request.get_json()
if not (transaction_data.get("transaction_type") == "Upvote"):
return jsonify({"Error": "Missing type element or not Upvote!"}), 400
if not (transaction_data.get("memeVoteId")):
if not (transaction_data.get("id_memeVote")):
return jsonify({"Error": "Missing memeVoteId element!"}), 400
# Check if the meme to be voted exists inside the chain
if(blockchain.does_image_exist(transaction_data["memeVoteId"])):
new_vote = UpvoteTransaction(transaction_index=len(blockchain.transactions_to_be_confirmed),
timestamp = str(datetime.datetime.now()),
walletAddrVoter=host_wallet,
voteMemeID=transaction_data["memeVoteId"],
credits=1)
# Notify all nodes in the network for this new vote transaction
# so they can add it to their local mempool
notify_all_nodes_new_transaction(new_vote)
if(blockchainInstance.does_image_exist(transaction_data["id_memeVote"])):
credits_before = host_wallet.get_credits()
credits_voting = transactions.CREDITS_TAKEN_FOR_VOTING
if(credits_before >= credits_voting):
credits_after = host_wallet.discredit_amount(credits_voting)
new_vote = transactions.UpvoteTransaction(transaction_index=len(blockchainInstance.transactions_to_be_confirmed),
timestamp = str(datetime.datetime.now()),
walletAddrVoter=host_wallet,
id_memeVote=transaction_data["id_memeVote"],
credits=credits_voting)
# Notify all nodes in the network for this new vote transaction
# so they can add it to their local mempool
notify_all_nodes_new_transaction(new_vote)
else:
return jsonify({"Error": "Not enough credits to perform voting!",
"Credits available": credits_before,
"Credits expected": credits_voting}), 400
else:
return jsonify({"Error": "Meme to be voted was not found inside the chain!"}), 400
return jsonify({"Notification": "The Upvote transaction was received."}), 201
response = {"Notification": "The Upvote transaction was received.",
"Upvote Info": new_vote.to_json(),
"Credits Before": credits_before,
"Credits After": credits_after}
return jsonify(response), 201
# GET method for visualizing image by it's name
@app.route('/get_image', methods=['GET'])
......@@ -267,7 +312,7 @@ def app_get_image():
if not image_data.get("imageId"):
return jsonify({"Error": "Missing imageName element!"}), 400
image_ascii = blockchain.find_image(image_data["imageId"])
image_ascii = blockchainInstance.find_image(image_data["imageId"])
if(image_ascii == -1):
return jsonify({"Error": "Image was not found!"}), 400
......@@ -281,23 +326,28 @@ def app_get_image():
def app_mine_block():
# If there are no transactions to be confirmed,
# then there isn't a reason to mine a new block
if not blockchain.pending_transactions_len():
if not blockchainInstance.pending_transactions_len():
return jsonify({"Warning": "No pending transactions available!"}), 405
blockchain.mine_block()
mined_block = blockchain.previous_block()
# Store the local lenght of the current node
chain_length = len(blockchain.chain)
chain_length = len(blockchainInstance.chain)
# Check if the chain of the current node is up-to-date with the network
consensus(blockchain.chain, connected_nodes, host_address)
blockchain.consensus_mechanism(blockchainInstance.chain, connected_nodes, host_address)
# If our lenght haven't changed, then we are up-to-date
if chain_length == len(blockchain.chain):
if chain_length == len(blockchainInstance.chain):
blockchainInstance.mine_block(host_wallet)
mined_block = blockchainInstance.previous_block()
# Notify other nodes in the network for the recently mined block
notify_all_nodes_new_block(mined_block)
# Get rewards in json format
json_rewards = transactions.prepare_upvote_rewards(blockchainInstance)
# Notify other nodes in the network for the rewards
notify_all_nodes_rewards(json_rewards)
response = {"Notification": "Wohooo, you have just mined a block!",
"Block Info": mined_block.to_json(True)}
return jsonify(response), 200
"Block Info": mined_block.to_json(True),
"Rewards": json_rewards}
return jsonify(response), 201
# POST method for connecting a new node to the network
# Used internally to receive connections from nodes
......@@ -341,11 +391,11 @@ def app_connect_to_node():
# and construct the local copy of them. This response
# is a result of the return app_get_chain(False) in /connect_code
if response.status_code == 200:
global blockchain
global blockchainInstance
global connected_nodes
# update chain, pending transactions and the connected_nodes
blockchain = construct_chain(response.json()["chain"])
blockchain.transactions_to_be_confirmed = response.json()["pending transactions"]
blockchainInstance = blockchain.construct_chain_again(response.json()["chain"])
blockchainInstance.transactions_to_be_confirmed = blockchain.construct_transactions_again(response.json()["pending transactions"])
connected_nodes.update(response.json()["peers"])
return "Connection successful", 200
else:
......
......@@ -11,7 +11,7 @@ from block import Block
from blockchain import Blockchain, consensus_mechanism as consensus, construct_chain_again as construct_chain
from transactions import Transaction, MemeTemplateTransaction, MemeTransaction, UpvoteTransaction, RewardDistributionTransaction
"""
def image_base64encoding(imagePath):
with open(imagePath, "rb") as media_file:
encoded_image = base64.b64encode(media_file.read())
......@@ -69,9 +69,15 @@ for attr, value in block2.__dict__.items():
print("HASH_B1_[{}]".format(block1.compute_hash()))