diff --git a/Pipfile.lock b/Pipfile.lock index da56139b..3839443f 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "36d1480b2857d3ca47d87e44b63f4bb1c3ba85e9fba71a9389c6f8161f5edd29" + "sha256": "1cfc261928838be069088d094d13b664d7ff98667a8a9b432e94c3241ab5ad6c" }, "pipfile-spec": 6, "requires": { @@ -18,10 +18,10 @@ "default": { "certifi": { "hashes": [ - "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", - "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" + "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", + "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" ], - "version": "==2019.6.16" + "version": "==2019.11.28" }, "chardet": { "hashes": [ @@ -39,18 +39,18 @@ }, "flask": { "hashes": [ - "sha256:0749df235e3ff61ac108f69ac178c9770caeaccad2509cb762ce1f65570a8856", - "sha256:49f44461237b69ecd901cc7ce66feea0319b9158743dd27a2899962ab214dac1" + "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52", + "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6" ], "index": "pypi", - "version": "==0.12.2" + "version": "==1.1.1" }, "idna": { "hashes": [ - "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f", - "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4" + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" ], - "version": "==2.6" + "version": "==2.8" }, "itsdangerous": { "hashes": [ @@ -61,10 +61,10 @@ }, "jinja2": { "hashes": [ - "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", - "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" + "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250", + "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49" ], - "version": "==2.10.1" + "version": "==2.11.1" }, "markupsafe": { "hashes": [ @@ -72,13 +72,16 @@ "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", @@ -95,31 +98,33 @@ "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" + "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", + "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" ], "version": "==1.1.1" }, "requests": { "hashes": [ - "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", - "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" + "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", + "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" ], "index": "pypi", - "version": "==2.18.4" + "version": "==2.22.0" }, "urllib3": { "hashes": [ - "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", - "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" + "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", + "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" ], - "version": "==1.22" + "version": "==1.25.8" }, "werkzeug": { "hashes": [ - "sha256:865856ebb55c4dcd0630cdd8f3331a1847a819dda7e8c750d3db6f2aa6c0209c", - "sha256:a0b915f0815982fb2a09161cb8f31708052d0951c3ba433ccc5e1aa276507ca6" + "sha256:169ba8a33788476292d04186ab33b01d6add475033dfc07215e6d219cc077096", + "sha256:6dc65cf9091cf750012f56f2cad759fa9e879f511b5ff8685e456b4e3bf90d16" ], - "version": "==0.15.4" + "version": "==1.0.0" } }, "develop": { @@ -132,11 +137,11 @@ }, "flake8": { "hashes": [ - "sha256:859996073f341f2670741b51ec1e67a01da142831aa1fdc6242dbf88dffbe661", - "sha256:a796a115208f5c03b18f332f7c11729812c8c3ded6c46319c59b53efd3819da8" + "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", + "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca" ], "index": "pypi", - "version": "==3.7.7" + "version": "==3.7.9" }, "mccabe": { "hashes": [ diff --git a/basic_block_gp/blockchain.py b/basic_block_gp/blockchain.py index 54ead5c1..6d69be66 100644 --- a/basic_block_gp/blockchain.py +++ b/basic_block_gp/blockchain.py @@ -31,13 +31,19 @@ def new_block(self, proof, previous_hash=None): """ block = { - # TODO + 'index': len(self.chain) + 1, + 'time': time(), + 'transactions': self.current_transactions, + 'proof': proof, + 'previous_hash': previous_hash or self.hash(self.chain[-1]), } # Reset the current list of transactions - # Append the chain to the block + self.current_transactions = [] + # Append the block to the chain + self.chain.append(block) # Return the new block - pass + return block def hash(self, block): """ @@ -56,8 +62,12 @@ def hash(self, block): # or we'll have inconsistent hashes # TODO: Create the block_string + string_object = json.dumps(block, sort_keys=True) + 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. @@ -66,7 +76,7 @@ def hash(self, block): # easier to work with and understand # TODO: Return the hashed block string in hexadecimal format - pass + return hex_hash @property def last_block(self): @@ -80,9 +90,11 @@ def proof_of_work(self, block): in an effort to find a number that is a valid proof :return: A valid proof for the provided block """ - # TODO - pass - # return proof + block_string = json.dumps(block, sort_keys=True) + proof = 0 + while self.valid_proof(block_string, proof) is False: + proof += 1 + return proof @staticmethod def valid_proof(block_string, proof): @@ -96,9 +108,10 @@ def valid_proof(block_string, proof): correct number of leading zeroes. :return: True if the resulting hash is a valid proof, False otherwise """ - # TODO - pass - # return True or False + guess = f"{block_string} {proof}".encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + return guess_hash[:6] == "000000" # Instantiate our Node @@ -109,16 +122,23 @@ def valid_proof(block_string, proof): # Instantiate the Blockchain blockchain = Blockchain() +print(blockchain.chain) +print(blockchain.hash(blockchain.last_block)) @app.route('/mine', methods=['GET']) def mine(): # Run the proof of work algorithm to get the next proof + proof = blockchain.proof_of_work(blockchain.last_block) + # 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 @@ -128,6 +148,8 @@ def mine(): def full_chain(): response = { # TODO: Return the chain and its current length + 'chain': blockchain.chain, + 'length': len(blockchain.chain) } return jsonify(response), 200 diff --git a/client_mining_p/blockchain.py b/client_mining_p/blockchain.py index a0a26551..4a260914 100644 --- a/client_mining_p/blockchain.py +++ b/client_mining_p/blockchain.py @@ -1,2 +1,192 @@ -# 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, + 'time': 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 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 + string_object = json.dumps(block, sort_keys=True) + 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 + + @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 + # """ + # block_string = json.dumps(block, sort_keys=True) + # proof = 0 + # while self.valid_proof(block_string, proof) is False: + # proof += 1 + # return proof + + @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[:6] == "000000" + + +# 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.chain) +print(blockchain.hash(blockchain.last_block)) + + +@app.route('/mine', methods=['POST']) +def mine(): + data = request.get_json() + + if "id" not in data or "proof" not in data: + response = { + "message": "Please include id and proof" + } + + return jsonify(response), 400 + + # Run the proof of work algorithm to get the next proof + proof = data["proof"] + + # Forge the new Block by adding it to the chain with the proof + block_string = json.dumps(blockchain.last_block, sort_keys=True).encode() + guessed = f'{block_string}{proof}'.encode() + guessed_hash = hashlib.sha256(guessed).hexdigest() + + # blockchain.valid_proof(block_string, proof) + if guessed_hash[:6] == "000000": + previous_hash = guessed_hash + block = blockchain.new_block(proof, previous_hash) + + response = { + "message": "New Block Forged", + "index": block['index'], + "transactions": block['transactions'], + "proof": block['proof'], + "previous_hash": block['previous_hash'], + } + + return jsonify(response), 200 + + else: + response = { + "message": "ERROR: not valid proof" + } + + return jsonify(response), 400 + + +@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(): + # returns last block in the chain. + 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/client_mining_p/derek.txt b/client_mining_p/derek.txt new file mode 100644 index 00000000..fdfa7ef8 --- /dev/null +++ b/client_mining_p/derek.txt @@ -0,0 +1,3 @@ +Derek Etman + +12 diff --git a/client_mining_p/miner.py b/client_mining_p/miner.py index 8e211b88..00faa11b 100644 --- a/client_mining_p/miner.py +++ b/client_mining_p/miner.py @@ -1,70 +1,204 @@ import hashlib -import requests - -import sys import json +from time import time +from uuid import uuid4 +from flask import Flask, jsonify, request -def proof_of_work(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 - """ - pass +class Blockchain(object): + def __init__(self): + self.chain = [] + self.current_transactions = [] -def valid_proof(block_string, proof): - """ - Validates the Proof: Does hash(block_string, proof) contain 6 - 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 - """ - pass + # Create the genesis block + self.new_block(previous_hash="I'm a teapot.", proof=100) + def new_block(self, proof, previous_hash=None): + """ + Create a new Block in the Blockchain -if __name__ == '__main__': - # What is the server address? IE `python3 miner.py https://server.com/api/` - if len(sys.argv) > 1: - node = sys.argv[1] + 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): + """ + :param sender: Address of the Recipient + :param recipient: Address of the Recipient + :param amount: Amount + :return: The index of the `block` that will hold this transaction + """ + 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 + string_object = json.dumps(block, sort_keys=True) + 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 + + @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() +print(blockchain.chain) +print(blockchain.hash(blockchain.last_block)) + +@app.route('/transaction/new', methods=['POST']) +def receive_new_transaction(): + # * use `request.get_json()` to pull the data out of the POST + # * check that 'sender', 'recipient', and 'amount' are present + # * return a 400 error using `jsonify(response)` with a 'message' + # * upon success, return a 'message' indicating index of the block + # containing the transaction + data = request.get_json() + + required = ['sender', 'recipient', 'amount'] + if not all(k in data for k in required): + # TODO Better error messaging + return "Missing values", 400 + + index = blockchain.new_transaction(data['sender'], + data['recipient'], + data['amount']) + + response = {'message': f'Transactions will be included in block {index}'} + return jsonify(response), 200 + + +@app.route('/mine', methods=['POST']) +def mine(): + data = request.get_json() + + # 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) + + block_index = blockchain.new_transaction(0, data['id'], 1) + + new_block = blockchain.new_block(proof, previous_hash) + + response = { + # TODO: Send a JSON response with the new block + "block": new_block, + "reward": f"Reward paid in block {block_index}" + } + + return jsonify(response), 200 else: - node = "http://localhost:5000" - - # Load ID - f = open("my_id.txt", "r") - id = f.read() - print("ID is", id) - f.close() - - # Run forever until interrupted - while True: - r = requests.get(url=node + "/last_block") - # Handle non-json response - try: - data = r.json() - except ValueError: - print("Error: Non-json response") - print("Response returned:") - print(r) - break - - # TODO: Get the block from `data` and use it to look for a new proof - # 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 + response = { + "message": "Bad proof" + } + + return jsonify(response), 200 + + +@app.route('/chain', methods=['GET']) +def full_chain(): + response = { + # TODO: Return the chain and its current lengt + '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) \ No newline at end of file diff --git a/client_mining_p/my_id.txt b/client_mining_p/my_id.txt deleted file mode 100644 index 757227a3..00000000 --- a/client_mining_p/my_id.txt +++ /dev/null @@ -1 +0,0 @@ -your-name-here