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..6daafc9a 100644 --- a/basic_block_gp/blockchain.py +++ b/basic_block_gp/blockchain.py @@ -12,7 +12,7 @@ def __init__(self): self.current_transactions = [] # Create the genesis block - self.new_block(previous_hash=1, proof=100) + self.new_block(previous_hash="I'm a little teapot", proof=100) def new_block(self, proof, previous_hash=None): """ @@ -31,13 +31,19 @@ def new_block(self, proof, previous_hash=None): """ block = { - # TODO + '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 - # 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_obj = json.dumps(block, sort_keys=True) + block_string = string_obj.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,12 @@ 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 +109,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 +123,20 @@ 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) + block = blockchain.new_block(proof, previous_hash) response = { # TODO: Send a JSON response with the new block + "New_Block": block } return jsonify(response), 200 @@ -127,11 +145,13 @@ def mine(): @app.route('/chain', methods=['GET']) def full_chain(): response = { - # TODO: Return the chain and its current length + # Return the chain and its current length + 'Chain': blockchain.chain, + 'Length': len(blockchain.chain), } return jsonify(response), 200 # Run the program on port 5000 if __name__ == '__main__': - app.run(host='0.0.0.0', port=5000) + app.run(port=5000) diff --git a/basic_transactions_gp/blockchain.py b/basic_transactions_gp/blockchain.py index 5a2c7e5a..b6346120 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 +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="I'm a teapot.", 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): + """ + :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[: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('/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): + response = { + 'message': "Missing values" + } + return jsonify(response), 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 = { + "message": 'success', + "block": new_block, + "reward": f"Reward paid in block {block_index}" + } + + return jsonify(response), 200 + else: + response = { + "message": "failure" + } + + 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(port = 5000) \ No newline at end of file diff --git a/basic_wallet_p/README.txt b/basic_wallet_p/README.txt index 1e1db329..e3352fb2 100644 --- a/basic_wallet_p/README.txt +++ b/basic_wallet_p/README.txt @@ -1,4 +1,6 @@ -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. diff --git a/basic_wallet_p/app.py b/basic_wallet_p/app.py new file mode 100644 index 00000000..f5002252 --- /dev/null +++ b/basic_wallet_p/app.py @@ -0,0 +1,149 @@ +import hashlib +import requests +import sys +import json +import time + + +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 + """ + start = time.time() + block_string = json.dumps(block, sort_keys=True) + proof = 0 + while valid_proof(block_string, proof) is False: + proof += 1 + end = time.time() + timer = end - start + print(f'Runtime: {timer} seconds') + return proof + + +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 + """ + guess = f'{block_string}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + return guess_hash[:6] == "000000" + + +class User: + def __init__(self, name): + self.name = name + self.balance = 0 + self.transactions = [] + + def change_name(self, new_name): + self.name = new_name + return f'\nYou have changed your name to {self.name}\n' + + def get_balance(self): + return f"\n{self.name} has {self.balance} coin(s).\n" + + def get_transactions(self): + return f"\n{self.name} transaction's: {self.transactions}\n" + + +if __name__ == '__main__': + confirm = True + while confirm == True: + name = input("Enter your name: ") + confirmation = input(f'Are you sure you want your name to be {name}?') + if (confirmation == 'y'): + newUser = User(name) + confirm = False + # Prints a message to confirm creation of new user + print(f"\nNew user created for {newUser.name}!\n") + # Asks the user what it would like to do + next_input = str(input("What would you like to do:\n Change name(1)\n Get balance(2)\n Get transactions(3)\n Make transaction(4)\n Mine for coin(5)\n or Quit(q)?\n~~~~~~~~~>")) + # Loops forever until user enters q + while True: + if (next_input == '1'): + new_name = str(input('\nWhat would you like to change your name to?\n')) + print(newUser.change_name(new_name)) + next_input = str(input("What would you like to do:\n Change name(1)\n Get balance(2)\n Get transactions(3)\n Make transaction(4)\n Mine for coin(5)\n or Quit(q)?\n~~~~~~~~~>")) + + elif (next_input == '2'): + print(newUser.get_balance()) + next_input = str(input("What would you like to do:\n Change name(1)\n Get balance(2)\n Get transactions(3)\n Make transaction(4)\n Mine for coin(5)\n or Quit(q)?\n~~~~~~~~~>")) + + elif (next_input == '3'): + print(newUser.get_transactions()) + next_input = str(input("What would you like to do:\n Change name(1)\n Get balance(2)\n Get transactions(3)\n Make transaction(4)\n Mine for coin(5)\n or Quit(q)?\n~~~~~~~~~>")) + + elif (next_input == '4'): + node = "http://127.0.0.1:5000" + recip = input("\nWho is the recipient?\n") + amount = input("\nHow many coins do you want to send?\n") + + while (int(amount) > newUser.balance): + print("\nYou don't have enough coins.\n") + amount = input("\nHow many coins do you want to send?\n") + + post_data = {"sender": newUser.name, "recipient": recip, "amount": amount} + r = requests.post(url=node + '/transaction/new', json=post_data) + data = r.json() + print('\n' + data['message'] + '\n') + newUser.balance -= int(amount) + newUser.transactions.append({'sender': newUser.name, 'recipient': recip, 'amount': int(amount), 'index': data['message']}) + + next_input = str(input("What would you like to do:\n Change name(1)\n Get balance(2)\n Get transactions(3)\n Make transaction(4)\n Mine for coin(5)\n or Quit(q)?\n~~~~~~~~~>")) + + elif (next_input == '5'): + node = "http://127.0.0.1:5000" + coins_mined = 0 + 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 = proof_of_work(data['last_block']) + + # When found, POST it to the server {"proof": new_proof, "id": username} + print(f'Id is {newUser.name}') + post_data = {"proof": new_proof, "id": newUser.name} + + r = requests.post(url=node + "/mine", json=post_data) + data = r.json() + print(data['message']) + # TODO: If the server responds with a 'message' 'success' + # add 1 to the number of coins mined and print it. Otherwise, + # print the message from the server. + if (data['message'].lower() == 'success'): + newUser.balance += 1 + coins_mined += 1 + print(f'You have mined {coins_mined} coin(s). This round.') + else: + print(data['message']) + stop = input("\n Would you like to stop mining?(y/n)\n") + if (stop.lower() == 'y'): + break + next_input = str(input("What would you like to do:\n Change name(1)\n Get balance(2)\n Get transactions(3)\n Make transaction(4)\n Mine for coin(5)\n or Quit(q)?\n~~~~~~~~~>")) + + elif (next_input.lower() == 'q'): + break + + else: + print('You did not give a proper input. Try again...') + next_input = str(input("What would you like to do:\n Change name(1)\n Get balance(2)\n Get transactions(3)\n Make transaction(4)\n Mine for coin(5)\n or Quit(q)?\n~~~~~~~~~>")) diff --git a/client_mining_p/blockchain.py b/client_mining_p/blockchain.py index a0a26551..985ce9bd 100644 --- a/client_mining_p/blockchain.py +++ b/client_mining_p/blockchain.py @@ -1,2 +1,152 @@ -# 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="I'm a little teapot", 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 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_obj = json.dumps(block, sort_keys=True) + block_string = string_obj.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 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 + """ + 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() + + required = ['proof', 'id'] + if not all(k in data for k in required): + response = {'message': "Missing proof or id."} + return jsonify(response), 400 + else: + #Check if valid(Success) or not(failure) + block_string = json.dumps(blockchain.last_block, sort_keys=True) + if (blockchain.valid_proof(block_string, data['proof']) == True): + response = {'message': "Success"} + else: + response = {'message': "Failure"} + return jsonify(response), 200 + + +@app.route('/chain', methods=['GET']) +def full_chain(): + response = { + # 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 get_last(): + response = { + 'last_block': blockchain.last_block + } + return jsonify(response), 200 + +# Run the program on port 5000 +if __name__ == '__main__': + app.run(port=5000) diff --git a/client_mining_p/miner.py b/client_mining_p/miner.py index 8e211b88..de77e6eb 100644 --- a/client_mining_p/miner.py +++ b/client_mining_p/miner.py @@ -1,8 +1,8 @@ import hashlib import requests - import sys import json +import time def proof_of_work(block): @@ -13,7 +13,15 @@ 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 + start = time.time() + block_string = json.dumps(block, sort_keys=True) + proof = 0 + while valid_proof(block_string, proof) is False: + proof += 1 + end = time.time() + timer = end - start + print(f'Runtime: {timer} seconds') + return proof def valid_proof(block_string, proof): @@ -27,23 +35,27 @@ 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() + + return guess_hash[:6] == "000000" if __name__ == '__main__': + coins = 0 # What is the server address? IE `python3 miner.py https://server.com/api/` if len(sys.argv) > 1: node = sys.argv[1] else: - node = "http://localhost:5000" - + node = "http://127.0.0.1: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 @@ -55,8 +67,8 @@ def valid_proof(block_string, proof): print(r) break - # TODO: Get the block from `data` and use it to look for a new proof - # new_proof = ??? + # Get the block from `data` and use it to look for a new proof + new_proof = proof_of_work(data['last_block']) # When found, POST it to the server {"proof": new_proof, "id": id} post_data = {"proof": new_proof, "id": id} @@ -64,7 +76,11 @@ def valid_proof(block_string, proof): r = requests.post(url=node + "/mine", json=post_data) data = r.json() - # TODO: If the server responds with a 'message' 'New Block Forged' + # If the server responds with a 'message' 'success' # add 1 to the number of coins mined and print it. Otherwise, # print the message from the server. - pass + if (data['message'] == 'Success'): + coins += 1 + print(f'You have mined {coins} coin(s).') + else: + print(data['message']) diff --git a/client_mining_p/my_id.txt b/client_mining_p/my_id.txt index 757227a3..e1773ab4 100644 --- a/client_mining_p/my_id.txt +++ b/client_mining_p/my_id.txt @@ -1 +1 @@ -your-name-here +samuel