From bed7ac598224955d8bd78105ee4ae2a42ff2057b Mon Sep 17 00:00:00 2001 From: Jonathan Traverso Date: Thu, 16 Apr 2020 21:20:12 -0400 Subject: [PATCH 1/2] Initial commit. Server running. --- basic_block_gp/blockchain.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/basic_block_gp/blockchain.py b/basic_block_gp/blockchain.py index 54ead5c1..ff042591 100644 --- a/basic_block_gp/blockchain.py +++ b/basic_block_gp/blockchain.py @@ -111,6 +111,15 @@ def valid_proof(block_string, proof): blockchain = Blockchain() +@app.route("/", methods=['GET']) +def greet(): + response = { + "greeting": "Hello, World!" + } + + return jsonify(response), 200 + + @app.route('/mine', methods=['GET']) def mine(): # Run the proof of work algorithm to get the next proof From c1445a398b3adbf9f8de63557fcb34f8199478d1 Mon Sep 17 00:00:00 2001 From: Jonathan Traverso Date: Wed, 22 Apr 2020 19:12:27 -0400 Subject: [PATCH 2/2] Pushing latest WIP. --- Pipfile | 3 + Pipfile.lock | 94 +++++++++++++++++--- README.md | 15 ++-- basic_block_gp/blockchain.py | 37 ++++++-- client_mining_p/blockchain.py | 159 +++++++++++++++++++++++++++++++++- client_mining_p/miner.py | 51 ++++++----- 6 files changed, 309 insertions(+), 50 deletions(-) diff --git a/Pipfile b/Pipfile index bb66cbe1..018742d5 100644 --- a/Pipfile +++ b/Pipfile @@ -5,10 +5,13 @@ verify_ssl = true [dev-packages] flake8 = "*" +autopep8 = "*" +pylint = "*" [packages] requests = ">=2.20.0" flask = ">=1.0.0" +pip = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index c6bb80f0..99e9d5a9 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "1cfc261928838be069088d094d13b664d7ff98667a8a9b432e94c3241ab5ad6c" + "sha256": "5a1afa109f98b1f2da7e1f4bf10bc639133ffe6cd2dbb8960ecd35a7ac5ebb7d" }, "pipfile-spec": 6, "requires": { @@ -18,10 +18,10 @@ "default": { "certifi": { "hashes": [ - "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", - "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" + "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304", + "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519" ], - "version": "==2019.11.28" + "version": "==2020.4.5.1" }, "chardet": { "hashes": [ @@ -39,11 +39,11 @@ }, "flask": { "hashes": [ - "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52", - "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6" + "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", + "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" ], "index": "pypi", - "version": "==1.1.1" + "version": "==1.1.2" }, "idna": { "hashes": [ @@ -61,10 +61,10 @@ }, "jinja2": { "hashes": [ - "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250", - "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49" + "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", + "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" ], - "version": "==2.11.1" + "version": "==2.11.2" }, "markupsafe": { "hashes": [ @@ -114,10 +114,10 @@ }, "urllib3": { "hashes": [ - "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", - "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" + "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", + "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" ], - "version": "==1.25.8" + "version": "==1.25.9" }, "werkzeug": { "hashes": [ @@ -128,6 +128,20 @@ } }, "develop": { + "astroid": { + "hashes": [ + "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a", + "sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42" + ], + "version": "==2.3.3" + }, + "autopep8": { + "hashes": [ + "sha256:cc6be1dfd46f2c7fa00e84a357f1a269683985b09eaffb47654ed551194399eb" + ], + "index": "pypi", + "version": "==1.5.1" + }, "entrypoints": { "hashes": [ "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", @@ -143,6 +157,39 @@ "index": "pypi", "version": "==3.7.9" }, + "isort": { + "hashes": [ + "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", + "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd" + ], + "version": "==4.3.21" + }, + "lazy-object-proxy": { + "hashes": [ + "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", + "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", + "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", + "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", + "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", + "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", + "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", + "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", + "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", + "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", + "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", + "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", + "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", + "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", + "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", + "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", + "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", + "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", + "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", + "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", + "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0" + ], + "version": "==1.4.3" + }, "mccabe": { "hashes": [ "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", @@ -163,6 +210,27 @@ "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" ], "version": "==2.1.1" + }, + "pylint": { + "hashes": [ + "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd", + "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4" + ], + "index": "pypi", + "version": "==2.4.4" + }, + "six": { + "hashes": [ + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" + ], + "version": "==1.14.0" + }, + "wrapt": { + "hashes": [ + "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1" + ], + "version": "==1.11.2" } } } diff --git a/README.md b/README.md index 204d46a7..b4b59ef9 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,23 @@ # Block-Chain-Python + https://imgur.com/xSlgvtl ## Projects ### Part 1 in Class Project -* Basic Setup and Proof of Work (basic_block_gp) + +- Basic Setup and Proof of Work (basic_block_gp) ### Part 1 Take Home Project -* Client Miners (client_mining_p) + +- Client Miners (client_mining_p) ### Part 2 in Class Project -* Basic Transactions (basic_transactions_gp) + +- Basic Transactions (basic_transactions_gp) ### Part 2 Take Home Project -* Basic Wallet (basic_wallet_p) -Based on blockchain by dvf. Used under MIT license: https://github.com/dvf/blockchain +- Basic Wallet (basic_wallet_p) + +Based on blockchain by dvf. Used under MIT license: https://github.com/dvf/blockchain diff --git a/basic_block_gp/blockchain.py b/basic_block_gp/blockchain.py index ff042591..102b7baa 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, + "timestamp": time(), + "transactions": self.current_transactions, + "proof": proof, + "previous_hash": previous_hash or self.hash(self.last_block) } # Reset the current list of transactions + self.current_transactions = [] # Append the chain to the block + 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 + block_string = json.dumps(block, sort_keys=True) # TODO: Hash this string using sha256 + # first convert to a byte string + + hashed_block = hashlib.sha256(block_string.encode()) # 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 hashed_block.hexdigest() @property def last_block(self): @@ -81,8 +91,13 @@ def proof_of_work(self, block): :return: A valid proof for the provided block """ # TODO - pass + block_string = json.dumps(block, sort_keys=True) + proof = 0 + + while self.valid_proof(block_string, proof) is False: + proof += 1 # return proof + return proof @staticmethod def valid_proof(block_string, proof): @@ -97,7 +112,12 @@ def valid_proof(block_string, proof): :return: True if the resulting hash is a valid proof, False otherwise """ # TODO - pass + guess = block_string + str(proof) + guess = guess.encode() + hash_value = hashlib.sha256(guess).hexdigest() + + return hash_value[:3] == "000" + # return True or False @@ -123,11 +143,13 @@ def greet(): @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 + new_block = blockchain.new_block(proof) response = { - # TODO: Send a JSON response with the new block + "block": new_block } return jsonify(response), 200 @@ -136,7 +158,8 @@ def mine(): @app.route('/chain', methods=['GET']) def full_chain(): response = { - # TODO: Return the chain and its current length + "len": len(blockchain.chain), + "chain": blockchain.chain } return jsonify(response), 200 diff --git a/client_mining_p/blockchain.py b/client_mining_p/blockchain.py index a0a26551..df78336e 100644 --- a/client_mining_p/blockchain.py +++ b/client_mining_p/blockchain.py @@ -1,2 +1,157 @@ -# Paste your version of blockchain.py from the basic_block_gp -# folder here +import hashlib +import json +from time import time +from uuid import uuid4 + +from flask import Flask, jsonify, request + + +class Blockchain(object): + def __init__(self): + self.chain = [] + self.current_transactions = [] + + # Create the genesis block + self.new_block(previous_hash=1, proof=100) + + def new_block(self, proof, previous_hash=None): + """ + Create a new Block in the Blockchain + + A block should have: + * Index + * Timestamp + * List of current transactions + * The proof used to mine this block + * The hash of the previous block + + :param proof: The proof given by the Proof of Work algorithm + :param previous_hash: (Optional) Hash of previous Block + :return: New Block + """ + + block = { + "index": len(self.chain) + 1, + "timestamp": time(), + "transactions": self.current_transactions, + "proof": proof, + "previous_hash": previous_hash or self.hash(self.last_block) + } + + # Reset the current list of transactions + self.current_transactions = [] + # Append the chain to the block + self.chain.append(block) + # Return the new block + return block + + 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 + block_string = json.dumps(block, sort_keys=True) + + # TODO: Hash this string using sha256 + # first convert to a byte string + + hashed_block = hashlib.sha256(block_string.encode()) + + # By itself, the sha256 function returns the hash in a raw string + # that will likely include escaped characters. + # This can be hard to read, but .hexdigest() converts the + # hash to a string of hexadecimal characters, which is + # easier to work with and understand + + # TODO: Return the hashed block string in hexadecimal format + return hashed_block.hexdigest() + + @property + def last_block(self): + return self.chain[-1] + + +# Instantiate our Node +app = Flask(__name__) + +# Generate a globally unique address for this node +node_identifier = str(uuid4()).replace('-', '') + +# Instantiate the Blockchain +blockchain = Blockchain() + + +@app.route("/", methods=['GET']) +def greet(): + response = { + "greeting": "Hello, World!" + } + + return jsonify(response), 200 + + +@app.route('/mine', methods=['POST']) +def mine(): + # just like getting request body + data = request.get_json() + + if "id" not in data or "proof" not in data: + response = {"message": "missing data"} + return jsonify(response), 400 + + # Run the proof of work algorithm to get the next proof + proof = data["proof"] + last_block = blockchain.last_block + block_string = json.dumps(last_block, sort_keys=True) + + if blockchain.valid_proof(block_string, proof): + # return a the new block + new_block = blockchain.new_block(proof) + response = { + "block": new_block + } + return jsonify(response), 201 + else: + response = { + "message": "invalid proof" + } + + # Forge the new Block by adding it to the chain with the proof + new_block = blockchain.new_block(proof) + + response = { + "block": new_block + } + + return jsonify(response), 200 + + +@app.route('/last_block', methods=['GET']) +def last_block(): + pass + + +@app.route('/chain', methods=['GET']) +def full_chain(): + response = { + "len": len(blockchain.chain), + "chain": blockchain.chain + } + 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/miner.py b/client_mining_p/miner.py index 8e211b88..291a8c2c 100644 --- a/client_mining_p/miner.py +++ b/client_mining_p/miner.py @@ -1,9 +1,8 @@ +import json +import sys import hashlib import requests -import sys -import json - def proof_of_work(block): """ @@ -13,7 +12,12 @@ def proof_of_work(block): in an effort to find a number that is a valid proof :return: A valid proof for the provided block """ - pass + block_string = json.dumps(block, sort_keys=True) + proof = 0 + while valid_proof(block_string, proof) is False: + proof += 1 + + return proof def valid_proof(block_string, proof): @@ -27,7 +31,10 @@ 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 = block_string + str(proof) + guess = guess.encode() + hash_value = hashlib.sha256(guess).hexdigest() + return hash_value[:3] == '000' if __name__ == '__main__': @@ -36,14 +43,13 @@ def valid_proof(block_string, proof): node = sys.argv[1] else: node = "http://localhost:5000" - - # Load ID + coins_mined = 0 + # Load ID f = open("my_id.txt", "r") id = f.read() print("ID is", id) f.close() - - # Run forever until interrupted + # Run forever until interrupted while True: r = requests.get(url=node + "/last_block") # Handle non-json response @@ -54,17 +60,16 @@ def valid_proof(block_string, proof): 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 + # 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": id} + post_data = {"proof": new_proof, "id": id} + r = requests.post(url=node + "/mine", json=post_data) + data = r.json() + print(data) + if 'block' in data: + # we have succeeded! + coins_mined += 1 + print(f"Total coins mined: {coins_mined}") + else: + print(data['message'])