From d38caf1f6f92b83d14f387f6e2fbfa4af4782cdb Mon Sep 17 00:00:00 2001 From: SamH3pn3r Date: Wed, 12 Feb 2020 10:27:19 -0800 Subject: [PATCH 1/9] Made pipfile.lock and completed new block function --- Pipfile.lock | 57 ++++++++++++++++++++---------------- basic_block_gp/blockchain.py | 22 ++++++++++---- 2 files changed, 47 insertions(+), 32 deletions(-) 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..9af9f688 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): """ @@ -109,6 +115,7 @@ def valid_proof(block_string, proof): # Instantiate the Blockchain blockchain = Blockchain() +print(blockchain.chain) @app.route('/mine', methods=['GET']) @@ -119,6 +126,7 @@ def mine(): response = { # TODO: Send a JSON response with the new block + "message": "Hello World!" } return jsonify(response), 200 @@ -127,11 +135,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) From 77a9da6f2490dc997461876b63a7e3ee40da33f8 Mon Sep 17 00:00:00 2001 From: SamH3pn3r Date: Wed, 12 Feb 2020 10:34:14 -0800 Subject: [PATCH 2/9] completed hash function --- basic_block_gp/blockchain.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/basic_block_gp/blockchain.py b/basic_block_gp/blockchain.py index 9af9f688..7af5844c 100644 --- a/basic_block_gp/blockchain.py +++ b/basic_block_gp/blockchain.py @@ -62,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. @@ -72,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): @@ -116,7 +120,7 @@ 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(): @@ -126,7 +130,7 @@ def mine(): response = { # TODO: Send a JSON response with the new block - "message": "Hello World!" + "message": "Hello World!", } return jsonify(response), 200 @@ -137,7 +141,7 @@ def full_chain(): response = { # Return the chain and its current length 'Chain': blockchain.chain, - 'Length': len(blockchain.chain) + 'Length': len(blockchain.chain), } return jsonify(response), 200 From 00511eb02521d5219f54fe283585352e1fdadfbf Mon Sep 17 00:00:00 2001 From: SamH3pn3r Date: Wed, 12 Feb 2020 11:05:20 -0800 Subject: [PATCH 3/9] Completed file --- basic_block_gp/blockchain.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/basic_block_gp/blockchain.py b/basic_block_gp/blockchain.py index 7af5844c..6daafc9a 100644 --- a/basic_block_gp/blockchain.py +++ b/basic_block_gp/blockchain.py @@ -90,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): @@ -106,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 @@ -125,12 +129,14 @@ def valid_proof(block_string, proof): @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 - "message": "Hello World!", + "New_Block": block } return jsonify(response), 200 From edd1ef646f9746835b55729a4e196fb3de049788 Mon Sep 17 00:00:00 2001 From: SamH3pn3r Date: Wed, 12 Feb 2020 13:53:25 -0800 Subject: [PATCH 4/9] Did some blockchaining --- client_mining_p/blockchain.py | 154 +++++++++++++++++++++++++++++++++- client_mining_p/miner.py | 24 ++++-- client_mining_p/my_id.txt | 2 +- 3 files changed, 171 insertions(+), 9 deletions(-) diff --git a/client_mining_p/blockchain.py b/client_mining_p/blockchain.py index a0a26551..991a377f 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 +from miner import proof_of_work + + +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) + 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..a4e112ca 100644 --- a/client_mining_p/miner.py +++ b/client_mining_p/miner.py @@ -13,7 +13,11 @@ 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) + proof = 0 + while valid_proof(block_string, proof) is False: + proof += 1 + return proof def valid_proof(block_string, proof): @@ -27,15 +31,19 @@ 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") @@ -56,7 +64,7 @@ def valid_proof(block_string, proof): break # TODO: Get the block from `data` and use it to look for a new proof - # 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 +72,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' + # 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. - pass + if (data['message'] == 'success'): + coins += 1 + print(f'You have mined {coins} coins.') + 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 From 2773e177aec52eadb67f3bb12b5f5c9f6da0c610 Mon Sep 17 00:00:00 2001 From: SamH3pn3r Date: Wed, 12 Feb 2020 15:56:12 -0800 Subject: [PATCH 5/9] Completed MVP and added time --- client_mining_p/blockchain.py | 1 + client_mining_p/miner.py | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/client_mining_p/blockchain.py b/client_mining_p/blockchain.py index 991a377f..0327315b 100644 --- a/client_mining_p/blockchain.py +++ b/client_mining_p/blockchain.py @@ -123,6 +123,7 @@ def mine(): 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: diff --git a/client_mining_p/miner.py b/client_mining_p/miner.py index a4e112ca..cb7080f2 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,10 +13,14 @@ 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 """ + 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 @@ -75,8 +79,8 @@ def valid_proof(block_string, proof): # 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'] == 'success'): + if (data['message'] == 'Success'): coins += 1 - print(f'You have mined {coins} coins.') + print(f'You have mined {coins} coin(s).') else: print(data['message']) From 6c2ae42b58ceedd46f88d50b5bcf250f87527018 Mon Sep 17 00:00:00 2001 From: SamH3pn3r Date: Thu, 13 Feb 2020 15:23:46 -0800 Subject: [PATCH 6/9] Made some cool things along with some mistakes. --- basic_transactions_gp/blockchain.py | 204 +++++++++++++++++++++++++++- basic_wallet_p/README.txt | 4 +- basic_wallet_p/app.py | 148 ++++++++++++++++++++ client_mining_p/blockchain.py | 1 - client_mining_p/miner.py | 8 +- 5 files changed, 357 insertions(+), 8 deletions(-) create mode 100644 basic_wallet_p/app.py diff --git a/basic_transactions_gp/blockchain.py b/basic_transactions_gp/blockchain.py index 5a2c7e5a..0927eda5 100644 --- a/basic_transactions_gp/blockchain.py +++ b/basic_transactions_gp/blockchain.py @@ -1,2 +1,202 @@ -# 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): + # 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 = { + "message": 'success', + "block": new_block, + "reward": f"Reward paid in block {block_index}" + } + + 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(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..be0c685d --- /dev/null +++ b/basic_wallet_p/app.py @@ -0,0 +1,148 @@ +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)?")) + # 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)?")) + + 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)?")) + + 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)?")) + + elif (next_input == '4'): + recip = input("\nWho is the recipient?\n") + amount = input("\nHow many coins do you want to send?\n") + + while (int(amount) > newUser.balance): + print("You don't have enough coins.") + 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.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)?")) + + 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() + + # 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'] == '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)?")) + + 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)?")) diff --git a/client_mining_p/blockchain.py b/client_mining_p/blockchain.py index 0327315b..985ce9bd 100644 --- a/client_mining_p/blockchain.py +++ b/client_mining_p/blockchain.py @@ -4,7 +4,6 @@ from uuid import uuid4 from flask import Flask, jsonify, request -from miner import proof_of_work class Blockchain(object): diff --git a/client_mining_p/miner.py b/client_mining_p/miner.py index cb7080f2..de77e6eb 100644 --- a/client_mining_p/miner.py +++ b/client_mining_p/miner.py @@ -48,14 +48,14 @@ def valid_proof(block_string, proof): node = sys.argv[1] else: 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 @@ -67,7 +67,7 @@ def valid_proof(block_string, proof): print(r) break - # TODO: Get the block from `data` and use it to look for a 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} @@ -76,7 +76,7 @@ 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' 'success' + # 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'] == 'Success'): From 74fe2505aea7cc56331c2c875004a420f2c40d19 Mon Sep 17 00:00:00 2001 From: SamH3pn3r Date: Thu, 13 Feb 2020 15:59:44 -0800 Subject: [PATCH 7/9] User can now make transactions --- basic_transactions_gp/blockchain.py | 6 ++++-- basic_wallet_p/app.py | 17 +++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/basic_transactions_gp/blockchain.py b/basic_transactions_gp/blockchain.py index 0927eda5..4bf8b20a 100644 --- a/basic_transactions_gp/blockchain.py +++ b/basic_transactions_gp/blockchain.py @@ -137,8 +137,10 @@ def receive_new_transaction(): required = ['sender', 'recipient', 'amount'] if not all(k in data for k in required): - # TODO Better error messaging - return "Missing values", 400 + response = { + 'message': "Missing values" + } + return jsonify(response), 400 index = blockchain.new_transaction(data['sender'], data['recipient'], diff --git a/basic_wallet_p/app.py b/basic_wallet_p/app.py index be0c685d..849cd0f9 100644 --- a/basic_wallet_p/app.py +++ b/basic_wallet_p/app.py @@ -69,28 +69,29 @@ def get_transactions(self): # 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)?")) + 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)?")) + 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)?")) + 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)?")) + 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("You don't have enough coins.") + 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} @@ -100,7 +101,7 @@ def get_transactions(self): 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)?")) + 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" @@ -138,11 +139,11 @@ def get_transactions(self): 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)?")) + 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)?")) + 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")) From 9e0d5a1b87a448ddbc43c8c3e0e137ebb2e66914 Mon Sep 17 00:00:00 2001 From: SamH3pn3r Date: Thu, 13 Feb 2020 16:00:40 -0800 Subject: [PATCH 8/9] and decrement balance in make transaction --- basic_wallet_p/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/basic_wallet_p/app.py b/basic_wallet_p/app.py index 849cd0f9..0c4708fd 100644 --- a/basic_wallet_p/app.py +++ b/basic_wallet_p/app.py @@ -98,7 +98,7 @@ def get_transactions(self): r = requests.post(url=node + '/transaction/new', json=post_data) data = r.json() print('\n' + data['message'] + '\n') - + newUser.balance -= 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")) From 8c283ffbce7abe0da172884331322deca3cc3eef Mon Sep 17 00:00:00 2001 From: SamH3pn3r Date: Thu, 13 Feb 2020 16:29:55 -0800 Subject: [PATCH 9/9] addded more fun things --- basic_transactions_gp/blockchain.py | 2 +- basic_wallet_p/app.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/basic_transactions_gp/blockchain.py b/basic_transactions_gp/blockchain.py index 4bf8b20a..b6346120 100644 --- a/basic_transactions_gp/blockchain.py +++ b/basic_transactions_gp/blockchain.py @@ -177,7 +177,7 @@ def mine(): return jsonify(response), 200 else: response = { - "message": "Bad proof" + "message": "failure" } return jsonify(response), 200 diff --git a/basic_wallet_p/app.py b/basic_wallet_p/app.py index 0c4708fd..f5002252 100644 --- a/basic_wallet_p/app.py +++ b/basic_wallet_p/app.py @@ -69,21 +69,21 @@ def get_transactions(self): # 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")) + 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")) + 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")) + 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")) + 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" @@ -98,10 +98,10 @@ def get_transactions(self): r = requests.post(url=node + '/transaction/new', json=post_data) data = r.json() print('\n' + data['message'] + '\n') - newUser.balance -= amount + 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")) + 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" @@ -126,11 +126,11 @@ def get_transactions(self): 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'] == 'Success'): + if (data['message'].lower() == 'success'): newUser.balance += 1 coins_mined += 1 print(f'You have mined {coins_mined} coin(s). This round.') @@ -139,11 +139,11 @@ def get_transactions(self): 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")) + 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")) + 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~~~~~~~~~>"))