Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Corrections #2

Merged
merged 2 commits into from
Feb 10, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
268 changes: 186 additions & 82 deletions blockchain.py
Original file line number Diff line number Diff line change
@@ -1,53 +1,105 @@
import hashlib
import json

from time import time
from urllib.parse import urlparse
from uuid import uuid4

from flask import Flask
from flask import Flask, jsonify, request


class Blockchain(object):
class Blockchain:
def __init__(self):
self.chain = []
self.current_transactions = []
self.chain = []
self.nodes = set()

# Create the genesis block
self.new_block(previous_hash=1, proof=100)
self.new_block(previous_hash='1', proof=100)

def proof_of_work(self, last_proof):
def register_node(self, address):
"""
Simple Proof of Work Algorithm:
- Find a number p' such that hash(pp') contains leading 4 zeroes, where p is the previous p'
- p is the previous proof, and p' is the new proof
:param last_proof: <int>
:return: <int>
Add a new node to the list of nodes
:param address: Address of node. Eg. 'http://192.168.0.5:5000'
"""

proof = 0
while self.valid_proof(last_proof, proof) is False:
proof += 1
parsed_url = urlparse(address)
if parsed_url.netloc:
self.nodes.add(parsed_url.netloc)
elif parsed_url.path:
# Accepts an URL without scheme like '192.168.0.5:5000'.
self.nodes.add(parsed_url.path)
else:
raise ValueError('Invalid URL')

return proof

@staticmethod
def valid_proof(last_proof, proof):
def valid_chain(self, chain):
"""
Validates the Proof: Does hash(last_proof, proof) contain 4 leading zeroes?
:param last_proof: <int> Previous Proof
:param proof: <int> Current Proof
:return: <bool> True if correct, False if not.
Determine if a given blockchain is valid
:param chain: A blockchain
:return: True if valid, False if not
"""

guess = f'{last_proof}{proof}'.encode()
guess_hash = hashlib.sha256(guess).hexdigest()
return guess_hash[:4] == "0000"
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")
# Check that the hash of the block is correct
last_block_hash = self.hash(last_block)
if block['previous_hash'] != last_block_hash:
return False

def new_block(self, proof, previous_hash=None):
# Check that the Proof of Work is correct
if not self.valid_proof(last_block['proof'], block['proof'], last_block_hash):
return False

last_block = block
current_index += 1

return True

def resolve_conflicts(self):
"""
This is our consensus algorithm, it resolves conflicts
by replacing our chain with the longest one in the network.
:return: True if our chain was replaced, False if not
"""

neighbours = self.nodes
new_chain = None

# We're only looking for chains longer than ours
max_length = len(self.chain)

# Grab and verify the chains from all the nodes in our network
for node in neighbours:
response = requests.get(f'http://{node}/chain')

if response.status_code == 200:
length = response.json()['length']
chain = response.json()['chain']

# Check if the length is longer and the chain is valid
if length > max_length and self.valid_chain(chain):
max_length = length
new_chain = chain

# Replace our chain if we discovered a new, valid chain longer than ours
if new_chain:
self.chain = new_chain
return True

return False

def new_block(self, proof, previous_hash):
"""
Create a new Block in the Blockchain
:param proof: <int> The proof given by the Proof of Work algorithm
:param previous_hash: (Optional) <str> Hash of previous Block
:return: <dict> New Block
:param proof: The proof given by the Proof of Work algorithm
:param previous_hash: Hash of previous Block
:return: New Block
"""

block = {
Expand All @@ -67,10 +119,10 @@ def new_block(self, proof, previous_hash=None):
def new_transaction(self, sender, recipient, amount):
"""
Creates a new transaction to go into the next mined Block
:param sender: <str> Address of the Sender
:param recipient: <str> Address of the Recipient
:param amount: <int> Amount
:return: <int> The index of the Block that will hold this transaction
:param sender: Address of the Sender
:param recipient: Address of the Recipient
:param amount: Amount
:return: The index of the Block that will hold this transaction
"""
self.current_transactions.append({
'sender': sender,
Expand All @@ -88,32 +140,48 @@ def last_block(self):
def hash(block):
"""
Creates a SHA-256 hash of a Block
:param block: <dict> Block
:return: <str>
:param block: Block
"""

# We must make sure that the Dictionary is Ordered, or we'll have inconsistent hashes
block_string = json.dumps(block, sort_keys=True).encode()
return hashlib.sha256(block_string).hexdigest()

def new_transaction(self, sender, recipient, amount):
def proof_of_work(self, last_block):
"""
Creates a new transaction to go into the next mined Block
:param sender: <str> Address of the Sender
:param recipient: <str> Address of the Recipient
:param amount: <int> Amount
:return: <int> The index of the Block that will hold this transaction
Simple Proof of Work Algorithm:
- Find a number p' such that hash(pp') contains leading 4 zeroes
- Where p is the previous proof, and p' is the new proof

:param last_block: <dict> last Block
:return: <int>
"""

self.current_transactions.append({
'sender': sender,
'recipient': recipient,
'amount': amount,
})
last_proof = last_block['proof']
last_hash = self.hash(last_block)

proof = 0
while self.valid_proof(last_proof, proof, last_hash) is False:
proof += 1

return proof

@staticmethod
def valid_proof(last_proof, proof, last_hash):
"""
Validates the Proof
:param last_proof: <int> Previous Proof
:param proof: <int> Current Proof
:param last_hash: <str> The hash of the Previous Block
:return: <bool> True if correct, False if not.
"""

return self.last_block['index'] + 1
guess = f'{last_proof}{proof}{last_hash}'.encode()
guess_hash = hashlib.sha256(guess).hexdigest()
return guess_hash[:4] == "0000"

# Instantiate our Node

# Instantiate the Node
app = Flask(__name__)

# Generate a globally unique address for this node
Expand All @@ -125,22 +193,31 @@ def new_transaction(self, sender, recipient, amount):

@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"
# We run the proof of work algorithm to get the next proof...
last_block = blockchain.last_block
proof = blockchain.proof_of_work(last_block)

# We must receive a reward for finding the proof.
# The sender is "0" to signify that this node has mined a new coin.
blockchain.new_transaction(
sender="0",
recipient=node_identifier,
amount=1,
)

# Forge the new Block by adding it to the chain
previous_hash = blockchain.hash(last_block)
block = blockchain.new_block(proof, previous_hash)

@app.route('/chain', methods=['GET'])
def full_chain():
response = {
'chain': blockchain.chain,
'length': len(blockchain.chain),
'message': "New Block Forged",
'index': block['index'],
'transactions': block['transactions'],
'proof': block['proof'],
'previous_hash': block['previous_hash'],
}
return jsonify(response), 200

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

@app.route('/transactions/new', methods=['POST'])
def new_transaction():
Expand All @@ -155,33 +232,60 @@ def new_transaction():
index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])

response = {'message': f'Transaction will be added to Block {index}'}
return jsonify(response), 201

@app.route('/mine', methods=['GET'])
def mine():
# We run the proof of work algorithm to get the next proof...
last_block = blockchain.last_block
last_proof = last_block['proof']
proof = blockchain.proof_of_work(last_proof)
return jsonify(response), 201

# We must receive a reward for finding the proof.
# The sender is "0" to signify that this node has mined a new coin.
blockchain.new_transaction(
sender="0",
recipient=node_identifier,
amount=1,
)

# Forge the new Block by adding it to the chain
previous_hash = blockchain.hash(last_block)
block = blockchain.new_block(proof, previous_hash)
@app.route('/chain', methods=['GET'])
def full_chain():
response = {
'chain': blockchain.chain,
'length': len(blockchain.chain),
}
return jsonify(response), 200


@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 Block Forged",
'index': block['index'],
'transactions': block['transactions'],
'proof': block['proof'],
'previous_hash': block['previous_hash'],
'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


if __name__ == '__main__':
from argparse import ArgumentParser

parser = ArgumentParser()
parser.add_argument('-p', '--port', default=5000, type=int, help='port to listen on')
args = parser.parse_args()
port = args.port

return jsonify(response), 200
app.run(host='0.0.0.0', port=port)