diff --git a/basic_transactions_gp/blockchain.py b/basic_transactions_gp/blockchain.py index 5a2c7e5a..1aef3300 100644 --- a/basic_transactions_gp/blockchain.py +++ b/basic_transactions_gp/blockchain.py @@ -1,2 +1,204 @@ # Paste your version of blockchain.py from the client_mining_p # folder here + +# Paste your version of blockchain.py from the basic_block_gp +# folder here +import hashlib +import json +from time import time +from uuid import uuid4 + +from flask import Flask, jsonify, request + + +class Blockchain(object): + def __init__(self): + self.chain = [] # list + self.current_transactions = [] + + # Create the genesis block --- start of the chain, initial block + self.new_block(previous_hash='blehehe', proof=100) + + def new_block(self, proof, previous_hash=None): + """ + Create a new Block in the Blockchain + + A block should have: + * Index + * Timestamp + * List of current transactions + * The proof used to mine this block + * The hash of the previous block + + :param proof: The proof given by the Proof of Work algorithm + :param previous_hash: (Optional) Hash of previous Block + :return: New Block + """ + + block = { + 'index': len(self.chain) + 1, + 'timestamp': time(), + 'transactions': self.current_transactions, + 'proof': proof, + 'previous_hash' : previous_hash or self.hash(self.chain[-1]), + } + + # Reset the current list of transactions + self.current_transactions = [] + # Append the block to the chain + self.chain.append(block) + # Return the new block + return block + + def new_transaction(self, sender, recipient, amount): + + transaction = { + 'sender': sender, + 'recipient': recipient, + 'amount': amount + } + + self.current_transactions.append(transaction) + + return self.last_block['index'] + 1 + + def hash(self, block): + """ + Creates a SHA-256 hash of a Block + + :param block": Block + "return": + """ + + # Use json.dumps to convert json into a string + # Use hashlib.sha256 to create a hash + # It requires a `bytes-like` object, which is what + # .encode() does. + # It converts the Python string into a byte string. + # We must make sure that the Dictionary is Ordered, + # or we'll have inconsistent hashes + + # TODO: Create the block_string + # stringifies json .dumps() + string_object = json.dumps(block, sort_keys=True) + # python string has metadata etc, encode() + block_string = string_object.encode() + + # TODO: Hash this string using sha256 + raw_hash = hashlib.sha256(block_string) + hex_hash = raw_hash.hexdigest() + + # By itself, the sha256 function returns the hash in a raw string + # that will likely include escaped characters. + # This can be hard to read, but .hexdigest() converts the + # hash to a string of hexadecimal characters, which is + # easier to work with and understand + + # TODO: Return the hashed block string in hexadecimal format + return hex_hash + + # decorator - makes func a property, don't need () + @property + def last_block(self): + return self.chain[-1] + + # def proof_of_work(self, block): + # """ + # Simple Proof of Work Algorithm + # Stringify the block and look for a proof. + # Loop through possibilities, checking each one against `valid_proof` + # in an effort to find a number that is a valid proof + # :return: A valid proof for the provided block + # """ + # # TODO + # block_string = json.dumps(block, sort_keys=True) + # proof = 0 + # while self.valid_proof(block_string, proof) is False: + # proof += 1 + # + # return proof + + # can run without instance + @staticmethod + def valid_proof(block_string, proof): + """ + Validates the Proof: Does hash(block_string, proof) contain 3 + leading zeroes? Return true if the proof is valid + :param block_string: The stringified block to use to + check in combination with `proof` + :param proof: The value that when combined with the + stringified previous block results in a hash that has the + correct number of leading zeroes. + :return: True if the resulting hash is a valid proof, False otherwise + """ + # TODO + guess = f'{block_string}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + return guess_hash[:3] == "000" + + +# Instantiate our Node +app = Flask(__name__) + +# Generate a globally unique address for this node +node_identifier = str(uuid4()).replace('-', '') + +# Instantiate the Blockchain +blockchain = Blockchain() +print('blockchain:', blockchain) +print('last_block:', blockchain.hash(blockchain.last_block)) + +@app.route('/mine', methods=['POST',]) +def mine(): + data = request.get_json() + # breakpoint() + # Run the proof of work algorithm to get the next proof + proof = data['proof'] + + last_block = blockchain.last_block + last_block_string = json.dumps(last_block, sort_keys=True) + + if blockchain.valid_proof(last_block_string, proof): + + # Forge the new Block by adding it to the chain with the proof + previous_hash = blockchain.hash(blockchain.last_block) + new_block = blockchain.new_block(proof, previous_hash) + + + response = { + # TODO: Send a JSON response with the new block + "block": new_block + } + + return jsonify(response), 200 + + else: + response = { + "message": "Bad proof" + } + + return jsonify(response), 200 + + +@app.route('/chain', methods=['GET']) +def full_chain(): + response = { + # TODO: Return the chain and its current length + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + } + return jsonify(response), 200 + +@app.route('/last_block', methods=['GET']) +def last_block(): + response = { + 'last_block': blockchain.last_block + } + return jsonify(response), 200 + + +# Run the program on port 5000 +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) + diff --git a/basic_wallet_p/README.txt b/basic_wallet_p/README.txt index 1e1db329..cc9514e0 100644 --- a/basic_wallet_p/README.txt +++ b/basic_wallet_p/README.txt @@ -1,6 +1,9 @@ -Account balances in a blockchain currency are not real values that are stored somewhere. Instead, wallet programs derive this balance by adding and subtracting all of the transactions for the user that are recorded in the ledger, to calculate the current balance. +Account balances in a blockchain currency are not real values that are stored somewhere. Instead, wallet programs +derive this balance by adding and subtracting all of the transactions for the user that are recorded in the ledger, +to calculate the current balance. -Build a simple wallet app using the front-end technology of your choice. You will not be evaluated on the aesthetics of your app. +Build a simple wallet app using the front-end technology of your choice. You will not be evaluated on the aesthetics +of your app. This app should: * Allow the user to enter, save, or change the `id` used for the program diff --git a/client_mining_p/blockchain.py b/client_mining_p/blockchain.py index a0a26551..18db8085 100644 --- a/client_mining_p/blockchain.py +++ b/client_mining_p/blockchain.py @@ -1,2 +1,151 @@ -# Paste your version of blockchain.py from the basic_block_gp -# folder here +import hashlib +import json +from time import time +from uuid import uuid4 +from flask import Flask, jsonify, request + +class Blockchain(object): + def __init__(self): + self.chain = [] + self.current_transactions = [] + + # Create the genesis block + self.new_block(previous_hash=1, proof=100) + + def new_block(self, proof, previous_hash=None): + """ + Create a new Block in the Blockchain + A block should have: + * Index + * Timestamp + * List of current transactions + * The proof used to mine this block + * The hash of the previous block + :param proof: The proof given by the Proof of Work algorithm + :param previous_hash: (Optional) Hash of previous Block + :return: New Block + """ + + block = { + 'index': len(self.chain) + 1, + 'timestamp': time(), + 'transactions': self.current_transactions, + 'proof': proof, + 'previous_hash': previous_hash # or self.hash(self.chain[-1]) + } + + # Reset the current list of transactions + self.current_transactions = [] + # Append the chain to the block + self.chain.append(block) + # Return the new block + return block + + @staticmethod + def hash(block): + """ + Creates a SHA-256 hash of a Block + :param block": Block + "return": + """ + + # Use json.dumps to convert json into a string + # Use hashlib.sha256 to create a hash + # It requires a `bytes-like` object, which is what + # .encode() does. + # It convertes the string to bytes. + # We must make sure that the Dictionary is Ordered, + # or we'll have inconsistent hashes + + # Create the block_string + # Hash this string using sha256 + block_string = json.dumps(block, sort_keys=True).encode() + + # By itself, the sha256 function returns the hash in a raw string + # that will likely include escaped characters. + # This can be hard to read, but .hexdigest() converts the + # hash to a string of hexadecimal characters, which is + # easier to work with and understand + + # Return the hashed block string in hexadecimal format + return hashlib.sha256(block_string).hexdigest() + + @property + def last_block(self): + return self.chain[-1] + + + @staticmethod + def valid_proof(block_string, proof): + """ + Validates the Proof: Does hash(block_string, proof) contain 3 + leading zeroes? Return true if the proof is valid + :param block_string: The stringified block to use to + check in combination with `proof` + :param proof: The value that when combined with the + stringified previous block results in a hash that has the + correct number of leading zeroes. + :return: True if the resulting hash is a valid proof, False otherwise + """ + guess = f'{block_string}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + return guess_hash[:3] == "000" + +# 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=['POST']) +def mine(): + data = request.get_json() + + if data["proof"] is None or data["id"] is None: + return jsonify({'message': 'Error - Provide a proof and an ID'}), 400 + + block_string = json.dumps(blockchain.last_block, sort_keys=True).encode() + valid_proof = blockchain.valid_proof(block_string, data["proof"]) + + if valid_proof: + last_block = blockchain.last_block + previous_hash = blockchain.hash(last_block) + new_block = blockchain.new_block(data["proof"], previous_hash) + + # Returns full response of the mined coin instead of just the message + response = { + 'message': "New Block Forged!", + 'index': new_block['index'], + 'transactions': new_block['transactions'], + 'proof': new_block['proof'], + 'previous_hash': new_block['previous_hash'] + } + + return jsonify(response), 200 + + else: + return jsonify({'message': "Incorrect proof"}), 400 + +@app.route('/chain', methods=['GET']) +def full_chain(): + response = { + 'chain': blockchain.chain, + 'chain_length': len(blockchain.chain) + } + return jsonify(response), 200 + +@app.route('/last_block', methods=['GET']) +def last_block(): + response = { + 'last_block': blockchain.last_block + } + return jsonify(response), 200 + +# Run the program on port 5000 +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) \ No newline at end of file diff --git a/client_mining_p/miner.py b/client_mining_p/miner.py index 8e211b88..69018206 100644 --- a/client_mining_p/miner.py +++ b/client_mining_p/miner.py @@ -3,6 +3,9 @@ import sys import json +import time +# from flask import Flask, jsonify, request +# from flask_cors import CORS def proof_of_work(block): @@ -13,7 +16,12 @@ def proof_of_work(block): in an effort to find a number that is a valid proof :return: A valid proof for the provided block """ - pass + block_string = json.dumps(block, sort_keys=True).encode() + + proof = 0 + while valid_proof(block_string, proof) is False: + proof += 1 + return proof def valid_proof(block_string, proof): @@ -27,7 +35,12 @@ def valid_proof(block_string, proof): correct number of leading zeroes. :return: True if the resulting hash is a valid proof, False otherwise """ - pass + + guess = f'{block_string}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + # We hash the guess, which is then evaluated to see if it's valid + # print(guess_hash) + return guess_hash[:6] == "000000" if __name__ == '__main__': @@ -43,8 +56,11 @@ def valid_proof(block_string, proof): print("ID is", id) f.close() + coins = 0 + # Run forever until interrupted while True: + print("\n Starting to mine: ") r = requests.get(url=node + "/last_block") # Handle non-json response try: @@ -56,15 +72,25 @@ def valid_proof(block_string, proof): break # TODO: Get the block from `data` and use it to look for a new proof - # new_proof = ??? + last_block = data['last_block'] + start_time = time.time() + new_proof = proof_of_work(last_block) + + # print(new_proof) # When found, POST it to the server {"proof": new_proof, "id": id} post_data = {"proof": new_proof, "id": id} - r = requests.post(url=node + "/mine", json=post_data) data = r.json() - # TODO: If the server responds with a 'message' 'New Block Forged' - # add 1 to the number of coins mined and print it. Otherwise, - # print the message from the server. - pass + if data["message"] == "New Block Forged!": + end_time = time.time() + print(f"Search time: {(end_time - start_time):1f}") + coins += 1 + print(f'Coins: {coins}') + print(data) + + else: + end_time = time.time() + print(f"Search time: {(end_time - start_time):1f}") + print(data["message"]) \ No newline at end of file