diff --git a/basic_block_gp/blockchain.py b/basic_block_gp/blockchain.py index 54ead5c1..393d4a02 100644 --- a/basic_block_gp/blockchain.py +++ b/basic_block_gp/blockchain.py @@ -2,7 +2,6 @@ import json from time import time from uuid import uuid4 - from flask import Flask, jsonify, request @@ -15,90 +14,53 @@ def __init__(self): 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 = { - # 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 - # Append the chain to the block + self.current_transactions = [] + # Append the block to chain + self.chain.append(block) # Return the new block - pass + return self.chain 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 - - # TODO: Hash this string using sha256 + # Use .encode() to convert Python string into a BYTE string. + # Sort dict keys or we'll have inconsistent hashes - # 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 + string_block = json.dumps(block, sort_keys=True) + raw_hash = hashlib.sha256(string_block.encode()) + - # TODO: Return the hashed block string in hexadecimal format - pass + # The sha256 function returns the hash in a raw string that includes escaped characters. + # This can be hard to read, but .hexdigest() converts hash to a string of hexadecimal characters + hex_hash = raw_hash.hexdigest() + return hex_hash - @property - def last_block(self): + @property #acts like property, not method - don't have to use () + def last_block(self): #O(1) return self.chain[-1] - def proof_of_work(self, block): - """ - Simple Proof of Work Algorithm - Stringify the block and look for a proof. - Loop through possibilities, checking each one against `valid_proof` - in an effort to find a number that is a valid proof - :return: A valid proof for the provided block - """ - # TODO - pass - # return proof + # def proof_of_work(self, block): + # block_string = json.dumps(block, sort_keys=True) + # proof = 0 + # while self.valid_proof(block_string, proof): #until num proof is found + # 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 - """ - # TODO - pass - # return True or False + guess = f'{block_string}{proof}' + guess_hash = hashlib.sha256(guess).hexdigest() + + return guess_hash[:6] =='000000' # Instantiate our Node @@ -111,15 +73,38 @@ def valid_proof(block_string, proof): blockchain = Blockchain() -@app.route('/mine', methods=['GET']) +@app.route('/mine', methods=['POST']) def mine(): - # Run the proof of work algorithm to get the next proof + # Handle non json request + values = request.get_json() + + required = ['proof', 'id'] + if not all(k in values for k in required): #nested for loop, no problem, it's small, O(2n) is constant, linear + response = {'message': 'Missing values'} + return jsonify(response), 400 + + submitted_proof = values['proof'] + + block_string = json.dumps(blockchain.last_block, sort_keys=True) + if blockchain.valid_proof(block_string, submitted_proof): + #forge new block by adding ...the proof + previous_hash = blockchain.hash(blockchain.last_block) + block = blockchain.new_block(proof, previous_hash) + else: + response={ + 'message': 'Proof was invalid or late' + } + return jsonify(response), 200 + # 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 + } #dictionary for response because similar keys:values data structure return jsonify(response), 200 @@ -127,7 +112,17 @@ def mine(): @app.route('/chain', methods=['GET']) def full_chain(): response = { - # TODO: Return the chain and its current length + 'message': 'hello' + 'chain': blockchain.chain, + 'length': len(blockchain.chain)' + } + return jsonify(response), 200 + +# Add an endpoint called `last_block` that returns the last block in the chain +@app.route('/last_block', methods=['GET']) +def return_last_block(): + response = { + 'last_block': blockchain.last_block } return jsonify(response), 200 diff --git a/basic_transactions_gp/blockchain.py b/basic_transactions_gp/blockchain.py index 5a2c7e5a..dab48c1b 100644 --- a/basic_transactions_gp/blockchain.py +++ b/basic_transactions_gp/blockchain.py @@ -1,2 +1,150 @@ -# 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=1, proof=100) + + def new_block(self, proof, previous_hash=None): + 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 block to chain + self.chain.append(block) + # Return the new block + return self.chain + + def hash(self, block): + # Use json.dumps to convert json into a string + # Use hashlib.sha256 to create a hash + # Use .encode() to convert Python string into a BYTE string. + # Sort dict keys or we'll have inconsistent hashes + + string_block = json.dumps(block, sort_keys=True) + raw_hash = hashlib.sha256(string_block.encode()) + + + # The sha256 function returns the hash in a raw string that includes escaped characters. + # This can be hard to read, but .hexdigest() converts hash to a string of hexadecimal characters + hex_hash = raw_hash.hexdigest() + return hex_hash + + @property #acts like property, not method - don't have to use () + def last_block(self): #O(1) + return self.chain[-1] + + @staticmethod + def valid_proof(block_string, proof): + guess = f'{block_string}{proof}' + guess_hash = hashlib.sha256(guess).hexdigest() + + return guess_hash[:6] =='000000' + + def new_transaction(self, sender, recipient, amount): #can't be static, we adding things to current trasnactions + self.current_transactions.append({ + 'sender': sender, + 'recipient': recipient, + 'amount': amount + }) + #last block is cement, unchangeable, so newest/latest bloack is... + return self.last_block['index'] + 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('/mine', methods=['POST']) +def mine(): + # Handle non json request + values = request.get_json() + + required = ['proof', 'id'] + if not all(k in values for k in required): #nested for loop, no problem, it's small, O(2n) is constant, linear + response = {'message': 'Missing values'} + return jsonify(response), 400 + + submitted_proof = values['proof'] + + block_string = json.dumps(blockchain.last_block, sort_keys=True) + if blockchain.valid_proof(block_string, submitted_proof): + + blockchain.new_transaction('0', values['id'], 1) + + #forge new block by adding ...the proof + previous_hash = blockchain.hash(blockchain.last_block) + block = blockchain.new_block(proof, previous_hash) + else: + response={ + 'message': 'Proof was invalid or late' + } + return jsonify(response), 200 + + # 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 = { + 'new_block': block + } #dictionary for response because similar keys:values data structure + + return jsonify(response), 200 + + +@app.route('/chain', methods=['GET']) +def full_chain(): + response = { + 'message': 'hello' + 'chain': blockchain.chain, + 'length': len(blockchain.chain)' + } + return jsonify(response), 200 + +# Add an endpoint called `last_block` that returns the last block in the chain +@app.route('/last_block', methods=['GET']) +def return_last_block(): + response = { + 'last_block': blockchain.last_block + } + return jsonify(response), 200 + +@app.route('/transactions/new', methods=['POST']) +def recieve_transactions(): + values = request.get_json() + #breakpoint() + required=['sender', 'recipient', 'amount'] + if not all(k in values for k in required): + response = {'message': 'Missing values'} + return jsonify(response), 400 + index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount']) + response = { + 'message': f'Transaction will be added to block{amount}.' + } + return jsonify(response), 201 + +# Run the program on port 5000 +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) diff --git a/basic_wallet_p/README.txt b/basic_wallet_p/README.txt index 1e1db329..f5c7419a 100644 --- a/basic_wallet_p/README.txt +++ b/basic_wallet_p/README.txt @@ -9,4 +9,9 @@ This app should: Stretch Goals: * Use styling to visually distinguish coins sent and coins received - * Paginate the list of transactions if there are more than ten \ No newline at end of file + * Paginate the list of transactions if there are more than ten + +TO START SERVER: + 1. pip install flask + 2. export FLASK_APP=flask_homepg.py + 3. flask run \ No newline at end of file diff --git a/basic_wallet_p/blockchain.py b/basic_wallet_p/blockchain.py new file mode 100644 index 00000000..5379dbff --- /dev/null +++ b/basic_wallet_p/blockchain.py @@ -0,0 +1,94 @@ +import hashlib +import json +from time import time +from uuid import uuid4 +from flask import Flask, jsonify, request, render_template + + +class Blockchain(object): + def __init__(self): + self.chain = [] + self.current_transactions = [] + self.new_block(previous_hash=1, proof=100) # Create the genesis block + + def new_block(self, proof, previous_hash=None): + 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 block to chain + self.chain.append(block) + # Return the new block + return self.chain + + def hash(self, block): + string_block = json.dumps(block, sort_keys=True) + raw_hash = hashlib.sha256(string_block.encode()) + + hex_hash = raw_hash.hexdigest() + return hex_hash + + @property + def last_block(self): #O(1) + return self.chain[-1] + + def proof_of_work(self, block): + block_string = json.dumps(block, sort_keys=True) + proof = 0 + while self.valid_proof(block_string, proof): #until num proof is found + proof += 1 + return proof + + @staticmethod + def valid_proof(block_string, proof): + guess = f'{block_string}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + return guess_hash[:3] =='000' + + +# Instantiate our Node, unique address and blockchain: +app = Flask(__name__) + +node_identifier = str(uuid4()).replace('-', '') + +blockchain = Blockchain() + + +@app.route('/mine', methods=['GET']) +def mine(): + # Run the proof of work algorithm to get the next proof + # Forge the new Block by adding it to the chain with the proof + proof = blockchain.proof_of_work(blockchain.last_block) + previous_hash = blockchain.hash(blockchain.last_block) + block = blockchain.new_block(proof, previous_hash) + + response = { + 'new_block': block + } + + return jsonify(response), 200 + + +@app.route('/chain', methods=['GET']) +def full_chain(): + response = { + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + } + return jsonify(response), 200 + + +# @app.route('/') +# def homepage(): +# return render_template('index.html') + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) diff --git a/basic_wallet_p/flask_homepg.py b/basic_wallet_p/flask_homepg.py new file mode 100644 index 00000000..6552ca2a --- /dev/null +++ b/basic_wallet_p/flask_homepg.py @@ -0,0 +1,11 @@ +from flask import Flask, render_template +app = Flask(__name__) + + +@app.route('/') +def homepage(): + return render_template('index.html') + + +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file diff --git a/basic_wallet_p/index.html b/basic_wallet_p/index.html new file mode 100644 index 00000000..23b2ea15 --- /dev/null +++ b/basic_wallet_p/index.html @@ -0,0 +1,26 @@ + + + + Mining Home + + +

Whatup Coin Dawg

+

Choose User Id

+
+ type='text' + name='user_id' + value='user_id' + placeholder= "ID" +
+ + {%for transaction in new_transaction%} +

{{transaction.sender}}

+

{{transaction.reciever}}

+

{{transaction.amount}}

+ {%endfor%} + + + +* Allow the user to enter, save, or change the `id` used for the program +* Display the current balance for that user +* Display a list of all transactions for this user, including sender and recipient \ No newline at end of file diff --git a/basic_wallet_p/miner.py b/basic_wallet_p/miner.py new file mode 100644 index 00000000..91e58562 --- /dev/null +++ b/basic_wallet_p/miner.py @@ -0,0 +1,76 @@ +import hashlib +import requests +import sys +import json + + + +def proof_of_work(block): + block_string = json.dumps(block, sort_keys=True) + proof = 0 + while self.valid_proof(block_string, proof): #until num proof is found + proof += 1 + return proof + + +def valid_proof(block_string, proof): + guess = f'{block_string}{proof}' + guess_hash = hashlib.sha256(guess).hexdigest() + + return guess_hash[:6] =='000000' + + +if __name__ == '__main__': + if len(sys.argv) > 1: + node = sys.argv[1] + else: + node = "http://localhost:5000" + + # Load ID + f = open("my_id.txt", "r") + id = f.read() + print("ID is", id) + f.close() + + coins_mined = 0 + print('Starting mining.') + + # Run until interrupted, and handle non-json response: + while True: + r = requests.get(url=node + "/last_block") + try: + data = r.json() + except ValueError: + print("Error: Non-json response") + print("Response returned:") + print(r) + break + + # Get block from `data` to look for new proof: + block = data['last_block'] + new_proof = proof_of_work(block) + print(f'Proof found: {new_proof}') + + # When found, POST to server {"proof": new_proof, "id": id} + post_data = {"proof": new_proof, "id": id} + r = requests.post(url=node + "/mine", json=post_data) + + try: + data = r.json() + except ValueError: + print("Error: Non-json response") + print("Response returned:") + print(r) + break + + print(data['message']) + # If server responds with 'message': 'New Block Forged', add 1 coins mined and print it. + # Else print the message from the server. + if message == 'New Block Forged': + coins_mined += 1 + response = {'coins_mined': coins_mined} + print(response) + + else: + return jsonify(message) + diff --git a/client_mining_p/README.md b/client_mining_p/README.md index f8669ae0..9d623a44 100644 --- a/client_mining_p/README.md +++ b/client_mining_p/README.md @@ -22,14 +22,14 @@ Modify the server we created to: *Client Mining* Create a client application that will: -* Get the last block from the server + * Run the `proof_of_work` function until a valid proof is found, validating or rejecting each attempt. Use a copy of `valid_proof` to assist. * Print messages indicating that this has started and finished. * Modify it to generate proofs with *6* leading zeroes. * Print a message indicating the success or failure response from the server * Add any coins granted to a simple integer total, and print the amount of coins the client has earned -* Continue mining until the app is interrupted. + * Change the name in `my_id.txt` to your name -* (Stretch) Handle non-json responses sent by the server in the event of an error, without crashing the miner + * Stretch: Add a timer to keep track of how long it takes to find a proof diff --git a/client_mining_p/blockchain.py b/client_mining_p/blockchain.py index a0a26551..605cbf81 100644 --- a/client_mining_p/blockchain.py +++ b/client_mining_p/blockchain.py @@ -1,2 +1,100 @@ -# 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 = [] + self.new_block(previous_hash=1, proof=100) # Create the genesis block + + def new_block(self, proof, previous_hash=None): + 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 block to chain + self.chain.append(block) + # Return the new block + return self.chain + + def hash(self, block): + # Use json.dumps to convert json into a string + # Use hashlib.sha256 to create a hash + # Use .encode() to convert Python string into a BYTE string. + # Sort dict keys or we'll have inconsistent hashes + string_block = json.dumps(block, sort_keys=True) + raw_hash = hashlib.sha256(string_block.encode()) + + + # The sha256 function returns the hash in a raw string that includes escaped characters. + # This can be hard to read, but .hexdigest() converts hash to a string of hexadecimal characters + hex_hash = raw_hash.hexdigest() + return hex_hash + + @property + def last_block(self): #O(1) + return self.chain[-1] + + def proof_of_work(self, block): + block_string = json.dumps(block, sort_keys=True) + proof = 0 + while self.valid_proof(block_string, proof): #until num proof is found + proof += 1 + return proof + + @staticmethod + def valid_proof(block_string, proof): + guess = f'{block_string}{proof}' + guess_hash = hashlib.sha256(guess).hexdigest() + + return guess_hash[:3] =='000' + + +# Instantiate our Node +app = Flask(__name__) + +# Generate a globally unique address for this node +node_identifier = str(uuid4()).replace('-', '') + +# Instantiate the Blockchain +blockchain = Blockchain() + + +@app.route('/mine', methods=['GET']) +def mine(): + # Run the proof of work algorithm to get the next proof + # Forge the new Block by adding it to the chain with the proof + proof = blockchain.proof_of_work(blockchain.last_block) + previous_hash = blockchain.hash(blockchain.last_block) + block = blockchain.new_block(proof, previous_hash) + + response = { + 'new_block': block + } + + return jsonify(response), 200 + + +@app.route('/chain', methods=['GET']) +def full_chain(): + response = { + 'message': 'hello' + '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) diff --git a/client_mining_p/miner.py b/client_mining_p/miner.py index 8e211b88..e4acccd4 100644 --- a/client_mining_p/miner.py +++ b/client_mining_p/miner.py @@ -6,28 +6,18 @@ 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 + block_string = json.dumps(block, sort_keys=True) + proof = 0 + while self.valid_proof(block_string, proof): #until num proof is found + proof += 1 + 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 - """ - pass + guess = f'{block_string}{proof}' + guess_hash = hashlib.sha256(guess).hexdigest() + + return guess_hash[:6] =='000000' if __name__ == '__main__': @@ -37,16 +27,17 @@ def valid_proof(block_string, proof): else: node = "http://localhost:5000" - # Load ID + # Load ID -- ID FOR WHAT? WHAT HAS AN ID??? f = open("my_id.txt", "r") id = f.read() print("ID is", id) f.close() - # Run forever until interrupted + coins_mined = 0 + + # Run until interrupted, and handle non-json response: while True: r = requests.get(url=node + "/last_block") - # Handle non-json response try: data = r.json() except ValueError: @@ -55,16 +46,31 @@ 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 = ??? + # TODO: Get block from `data` and use it to look for a new proof + block = data['last_block'] + new_proof = proof_of_work(block) + print(f'Proof found: {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() + try: + data = r.json() + except ValueError: + print("Error: Non-json response") + print("Response returned:") + print(r) + break # 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 + coins = 0 + if message = 'New Block Forged': + coins += 1 + response = {'coins': coins} + return jsonify(response) + else: + return jsonify(message) + diff --git a/client_mining_p/my_id.txt b/client_mining_p/my_id.txt index 757227a3..bc5da297 100644 --- a/client_mining_p/my_id.txt +++ b/client_mining_p/my_id.txt @@ -1 +1 @@ -your-name-here +sandy