Compare commits
5 Commits
c9c37849c9
...
f9f6265afb
Author | SHA1 | Date | |
---|---|---|---|
f9f6265afb | |||
93ae02916d | |||
8379de4870 | |||
00fcf3962c | |||
3979bfd2ac |
4
.gitignore
vendored
4
.gitignore
vendored
@ -167,7 +167,8 @@ cython_debug/
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
@ -188,3 +189,4 @@ Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
.idea
|
163
DPOS/blockChain.py
Normal file
163
DPOS/blockChain.py
Normal file
@ -0,0 +1,163 @@
|
||||
# Implementation of a simple decentralized blockchain class with multiple nodes and DPoS consensus in the network
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from datetime import datetime
|
||||
from urllib.parse import urlparse
|
||||
import requests
|
||||
from random import randint
|
||||
|
||||
|
||||
# Blockchain class
|
||||
class Blockchain(object):
|
||||
|
||||
# Constructor which creates lists to store the blockchain and the transactions
|
||||
def __init__(self):
|
||||
|
||||
# List to store the blockchain
|
||||
self.chain = []
|
||||
|
||||
# List to store the unverified transactions
|
||||
self.unverified_transactions = []
|
||||
|
||||
# List to store verified transactions
|
||||
self.verified_transactions = []
|
||||
|
||||
# Genesis block
|
||||
self.new_block(previous_hash=1)
|
||||
|
||||
# Set containing the nodes in the network. Used set here to prevent the same node getting added again.
|
||||
self.nodes = set()
|
||||
|
||||
# List containing all the nodes along with their stake in the network
|
||||
self.all_nodes = []
|
||||
|
||||
# List of all the voting nodes in the network
|
||||
self.voteNodespool = []
|
||||
|
||||
# List which stores all the nodes in descending order of votes received
|
||||
self.starNodespool = []
|
||||
|
||||
# List to store the top 3 nodes with the highest (stake * votes_received)
|
||||
self.superNodespool = []
|
||||
|
||||
# List to store the address of the delegate nodes selected for mining process
|
||||
self.delegates = []
|
||||
|
||||
# Method to create a new block in the Blockchain
|
||||
def new_block(self, previous_hash=None):
|
||||
block = {
|
||||
'index': len(self.chain) + 1,
|
||||
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'transactions': self.unverified_transactions,
|
||||
'previous_hash': previous_hash or self.hash(self.chain[-1])
|
||||
}
|
||||
self.verified_transactions += self.unverified_transactions
|
||||
print(self.verified_transactions)
|
||||
self.unverified_transactions = []
|
||||
|
||||
# appending the block at the end of the blockchain
|
||||
self.chain.append(block)
|
||||
return block
|
||||
|
||||
# Method to add a new transaction in the next block
|
||||
def new_transaction(self, sender, item_name, bill_amount):
|
||||
self.unverified_transactions.append({
|
||||
'Customer name': sender,
|
||||
'Recipient': "Dexter's Coffee Shop",
|
||||
'Item name': item_name,
|
||||
'Total billing amount': bill_amount,
|
||||
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
})
|
||||
return self.last_block['index'] + 1
|
||||
|
||||
@property
|
||||
def last_block(self):
|
||||
return self.chain[-1]
|
||||
|
||||
# Static method to create an SHA-256 Hash of a given block
|
||||
@staticmethod
|
||||
def hash(block):
|
||||
block_string = json.dumps(block, sort_keys=True).encode()
|
||||
hash_val = hashlib.sha256(block_string).hexdigest()
|
||||
return hash_val
|
||||
|
||||
# Method to add node using its IP address to our Blockchain network.
|
||||
def add_node(self, address, stake):
|
||||
parsed_url = urlparse(address)
|
||||
authority = stake
|
||||
self.nodes.add((parsed_url.netloc, authority))
|
||||
|
||||
# Method to simulate the voting process
|
||||
def add_vote(self):
|
||||
self.all_nodes = list(self.nodes)
|
||||
|
||||
for x in self.all_nodes:
|
||||
y = list(x)
|
||||
y.append(x[1] * randint(0, 100))
|
||||
self.voteNodespool.append(y)
|
||||
|
||||
print(self.voteNodespool)
|
||||
|
||||
# Method to select top three nodes based on voting results
|
||||
def selection(self):
|
||||
self.starNodespool = sorted(self.voteNodespool, key=lambda vote: vote[2], reverse=True)
|
||||
print(self.starNodespool)
|
||||
|
||||
for x in range(3):
|
||||
self.superNodespool.append(self.starNodespool[x])
|
||||
print(self.superNodespool)
|
||||
|
||||
for y in self.superNodespool:
|
||||
self.delegates.append(y[0])
|
||||
print(self.delegates)
|
||||
|
||||
# Method to sync the list
|
||||
def sync(self):
|
||||
r = requests.get('http://localhost:5000/delegates/show')
|
||||
print(r)
|
||||
|
||||
if r.status_code == 200:
|
||||
delegates = r.json()['node_delegates']
|
||||
self.delegates = delegates[0:3]
|
||||
print(self.delegates)
|
||||
|
||||
# Method to check if the chain is validated.
|
||||
def valid_chain(self, chain):
|
||||
last_block = chain[0]
|
||||
current_index = 1
|
||||
|
||||
while current_index < len(chain):
|
||||
block = chain[current_index]
|
||||
|
||||
# If the hash value of the current block isn't correct then return false
|
||||
if block['previous_hash'] != self.hash(last_block):
|
||||
return False
|
||||
|
||||
last_block = block
|
||||
current_index += 1
|
||||
|
||||
return True
|
||||
|
||||
# Method to replace the blockchain with the longest validated chain in the network.
|
||||
def resolve_chain(self):
|
||||
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
|
163
DPOS/dpos_api.py
Normal file
163
DPOS/dpos_api.py
Normal file
@ -0,0 +1,163 @@
|
||||
# Interacting with the Dexter's blockchain with multiple nodes using HTTP requests
|
||||
|
||||
from flask import Flask, jsonify, request
|
||||
|
||||
from blockChain import Blockchain
|
||||
|
||||
# Initialise our node with identifier and instantiate the Blockchain class
|
||||
app = Flask(__name__)
|
||||
blockchain = Blockchain()
|
||||
|
||||
|
||||
# API endpoint to mine a block, it is an HTTP GET request
|
||||
@app.route('/mine', methods=['GET'])
|
||||
# Method to mine a block
|
||||
def mine():
|
||||
# To ensure that only delegates elected by voting can mine a new block
|
||||
current_port = "localhost:" + str(port)
|
||||
if current_port in blockchain.delegates:
|
||||
|
||||
# To ensure that a new block is mined only if there are atleast 2 transactions
|
||||
if len(blockchain.unverified_transactions) >= 2:
|
||||
last_block = blockchain.last_block
|
||||
previous_hash = blockchain.hash(last_block)
|
||||
block = blockchain.new_block(previous_hash)
|
||||
|
||||
response = {
|
||||
'message': "New block mined!",
|
||||
'index': block['index'],
|
||||
'transactions': block['transactions'],
|
||||
'previous_hash': block['previous_hash']
|
||||
}
|
||||
print(len(blockchain.unverified_transactions))
|
||||
return jsonify(response), 200
|
||||
|
||||
else:
|
||||
response = {
|
||||
'message': 'Not enough transactions to mine a new block and add to chain!'
|
||||
}
|
||||
print(len(blockchain.unverified_transactions))
|
||||
return jsonify(response), 400
|
||||
else:
|
||||
response = {
|
||||
'message': 'You are not authorised to mine block! Only delegates can mine.'
|
||||
}
|
||||
return jsonify(response), 400
|
||||
|
||||
|
||||
# Endpoint for a new transaction
|
||||
@app.route('/transactions/new', methods=['POST'])
|
||||
def new_transaction():
|
||||
values = request.get_json()
|
||||
|
||||
required = ['Customer name', 'Item name', 'Total billing amount']
|
||||
if not all(k in values for k in required):
|
||||
return 'Missing values! Please enter customer name, item name and billing amount.', 400
|
||||
|
||||
index = blockchain.new_transaction(values['Customer name'], values['Item name'], values['Total billing amount'])
|
||||
|
||||
response = {
|
||||
'message': f'Transaction will be added to block {index}'
|
||||
}
|
||||
return jsonify(response), 201
|
||||
|
||||
|
||||
# Endpoint for viewing the blockchain
|
||||
@app.route('/chain', methods=['GET'])
|
||||
def full_chain():
|
||||
response = {
|
||||
'chain': blockchain.chain,
|
||||
'length': len(blockchain.chain)
|
||||
}
|
||||
return jsonify(response), 200
|
||||
|
||||
|
||||
# Endpoint for adding HTTP address of new nodes along with their stakes in the network.
|
||||
@app.route('/nodes/add', methods=['POST'])
|
||||
def add_nodes():
|
||||
values = request.get_json()
|
||||
required = ['nodes', 'stake']
|
||||
|
||||
if not all(k in values for k in required):
|
||||
return 'Error', 400
|
||||
|
||||
blockchain.add_node(values['nodes'], values['stake'])
|
||||
|
||||
response = {
|
||||
'message': 'New nodes are added!',
|
||||
'total_nodes': list(blockchain.nodes)
|
||||
}
|
||||
print(blockchain.nodes)
|
||||
return jsonify(response), 201
|
||||
|
||||
|
||||
# Endpoint to start the voting process
|
||||
@app.route('/voting', methods=['GET'])
|
||||
def voting():
|
||||
if port == 5000:
|
||||
show_votes = blockchain.add_vote()
|
||||
|
||||
response = {
|
||||
'message': 'The voting results are as follows:',
|
||||
'nodes': blockchain.voteNodespool
|
||||
}
|
||||
|
||||
return jsonify(response), 200
|
||||
|
||||
else:
|
||||
response = {
|
||||
'message': 'You are not authorized to conduct the election process!'
|
||||
}
|
||||
return jsonify(response), 400
|
||||
|
||||
|
||||
# Endpoint to view the list of all three elected delegate nodes
|
||||
@app.route('/delegates/show', methods=['GET'])
|
||||
def delegates():
|
||||
show_delegates = blockchain.selection()
|
||||
|
||||
response = {
|
||||
'message': 'The 3 delegate nodes selected for block mining are:',
|
||||
'node_delegates': blockchain.delegates
|
||||
}
|
||||
return jsonify(response), 200
|
||||
|
||||
|
||||
# Endpoint to synchronise the list of elected delegates with all other nodes in the network
|
||||
@app.route('/delegates/sync', methods=['GET'])
|
||||
def sync_delegates():
|
||||
sync_delegates = blockchain.sync()
|
||||
|
||||
response = {
|
||||
'message': 'The delegate nodes are:',
|
||||
'node_delegates': blockchain.delegates
|
||||
}
|
||||
return jsonify(response), 200
|
||||
|
||||
|
||||
# Endpoint to resolve and replace current chain with the longest validated one,achieving consensus
|
||||
@app.route('/chain/resolve', methods=['GET'])
|
||||
def consensus():
|
||||
replaced = blockchain.resolve_chain()
|
||||
|
||||
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
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from argparse import ArgumentParser
|
||||
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument('-p', '--port', default=5000, type=int, help='Listening on port')
|
||||
args = parser.parse_args()
|
||||
port = args.port
|
||||
app.run(host='0.0.0.0', port=port)
|
115
POW/blockChain.py
Normal file
115
POW/blockChain.py
Normal file
@ -0,0 +1,115 @@
|
||||
import hashlib
|
||||
import requests
|
||||
import json
|
||||
from hashlib import sha256
|
||||
from time import time
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
class Blockchain(object):
|
||||
def __init__(self):
|
||||
self.chain = [{'index': 0,
|
||||
'timestamp': time(),
|
||||
'transactions': 0,
|
||||
'proof': 0,
|
||||
'previous_hash': 0}]
|
||||
self.current_transactions = []
|
||||
self.nodes = set()
|
||||
|
||||
# create a new node based on url
|
||||
def register_node(self, address):
|
||||
parsed_url = urlparse(address)
|
||||
self.nodes.add(parsed_url.netloc)
|
||||
|
||||
# create a new block and add it to the chain.
|
||||
def new_block(self, proof, previous_hash):
|
||||
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
|
||||
|
||||
# add a new transaction to the list of transactions.
|
||||
def new_transaction(self, sender, recipient, amount):
|
||||
self.current_transactions.append({
|
||||
'sender': sender,
|
||||
'recipient': recipient,
|
||||
'amount': amount
|
||||
})
|
||||
|
||||
return self.last_block['index'] + 1
|
||||
|
||||
# PoW algo that increments proof value until valid_proof returns true.
|
||||
def proof_of_work(self, last_proof):
|
||||
proof = 0
|
||||
while self.valid_proof(last_proof, proof) is False:
|
||||
proof += 1
|
||||
|
||||
return proof
|
||||
|
||||
# verify the current chain has not already been solved
|
||||
def valid_chain(self, chain):
|
||||
last_block = chain[0]
|
||||
current_index = 1
|
||||
|
||||
while current_index < len(chain):
|
||||
block = chain[current_index]
|
||||
if block['previous_hash'] != self.hash(last_block):
|
||||
return False
|
||||
|
||||
# verify values in the chain are correct
|
||||
if not self.valid_proof(last_block['proof'], block['proof']):
|
||||
return False
|
||||
|
||||
last_block = block
|
||||
current_index += 1
|
||||
return True
|
||||
|
||||
# consensus algo to prevent conflicts when multiple nodes are in a network
|
||||
def resolve_conflicts(self):
|
||||
neighbors = self.nodes
|
||||
new_chain = None
|
||||
max_length = len(self.chain)
|
||||
|
||||
# check each node to find the longest chain
|
||||
for node in neighbors:
|
||||
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
|
||||
|
||||
# checks if the hash has 4 leading zeros. returns boolean.
|
||||
@staticmethod
|
||||
def valid_proof(last_proof, proof):
|
||||
guess = f'{last_proof}{proof}'.encode()
|
||||
guess_hash = hashlib.sha256(guess).hexdigest()
|
||||
# the addition of leading zeros can make a massive difference to the time required to find a solution.
|
||||
return guess_hash[:4] == "0000"
|
||||
|
||||
# hashes a block
|
||||
@staticmethod
|
||||
def hash(block):
|
||||
block_string = json.dumps(block, sort_keys=True).encode()
|
||||
return sha256(block_string).hexdigest()
|
||||
|
||||
# returns the last block of the chain
|
||||
@property
|
||||
def last_block(self):
|
||||
return self.chain[-1]
|
116
POW/pow_api.py
Normal file
116
POW/pow_api.py
Normal file
@ -0,0 +1,116 @@
|
||||
from flask import Flask, jsonify, request
|
||||
from uuid import uuid4
|
||||
from blockChain import Blockchain
|
||||
|
||||
# Instantiate our Node
|
||||
app = Flask(__name__)
|
||||
|
||||
# Generate a globally unique address for this node
|
||||
node_identifier = str(uuid4()).replace('-', '')
|
||||
|
||||
# Instantiate the Blockchain
|
||||
blockchain = Blockchain()
|
||||
|
||||
|
||||
@app.route('/mine', methods=['GET'])
|
||||
def mine():
|
||||
# run the PoW algo
|
||||
last_block = blockchain.last_block
|
||||
last_proof = last_block['proof']
|
||||
proof = blockchain.proof_of_work(last_proof)
|
||||
|
||||
# get reward for solving a block
|
||||
blockchain.new_transaction(sender="0", recipient=node_identifier, amount=1)
|
||||
|
||||
# add a new block to the chain
|
||||
previous_hash = blockchain.hash(last_block)
|
||||
block = blockchain.new_block(proof, previous_hash)
|
||||
|
||||
# create response
|
||||
response = {
|
||||
'message': "New block forged",
|
||||
'index': block['index'],
|
||||
'transactions': block['transactions'],
|
||||
'proof': block['proof'],
|
||||
'previous_hash': block['previous_hash']
|
||||
}
|
||||
|
||||
# return response
|
||||
return jsonify(response), 200
|
||||
|
||||
|
||||
@app.route('/transactions/new', methods=['POST'])
|
||||
def new_transaction():
|
||||
# get values from request
|
||||
values = request.get_json()
|
||||
|
||||
# verify all values are present
|
||||
required = ['sender', 'recipient', 'amount']
|
||||
if not all(k in values for k in required):
|
||||
return "Missing values", 400
|
||||
|
||||
# create a new transaction
|
||||
index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])
|
||||
|
||||
# create and return response
|
||||
response = {'message': f'Transaction will be added to Block {index}'}
|
||||
return jsonify(response), 200
|
||||
|
||||
|
||||
# return the full chain
|
||||
@app.route('/chain', methods=['GET'])
|
||||
def full_chain():
|
||||
response = {
|
||||
'chain': blockchain.chain,
|
||||
'length': len(blockchain.chain),
|
||||
}
|
||||
return jsonify(response), 200
|
||||
|
||||
|
||||
# create a new node
|
||||
@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
|
||||
|
||||
|
||||
# consensus algo
|
||||
@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
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# app.run(host='0.0.0.0', port=5000)
|
||||
from argparse import ArgumentParser
|
||||
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument('-p', '--port', default=6000, type=int, help='Listening on port')
|
||||
args = parser.parse_args()
|
||||
port = args.port
|
||||
app.run(host='0.0.0.0', port=port)
|
Loading…
Reference in New Issue
Block a user