├── .deepsource.toml ├── .gitignore ├── README.md ├── __init__.py ├── app.py ├── blockchain.py ├── driver.py └── requirements.txt /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "python" 5 | enabled = true 6 | runtime_version = "3.x.x" 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | 3 | *.pyc 4 | *.*~ 5 | *~ 6 | 7 | .coverage 8 | .coverage.* 9 | 10 | .idea/ 11 | .DS_Store 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple BlockChain implemented in Python 2 | 3 | 4 | ## Requirements 5 | 6 | 1. Python3.5 7 | 2. Flask 8 | 3. python-requests 9 | 10 | 11 | ## Contributing 12 | 13 | Please feel free to raise Pull Requests :) 14 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gauravvjn/blockchain-python/30b82b30dc31fce7513e5f497120b848985c9286/__init__.py -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from uuid import uuid4 2 | 3 | import requests 4 | from flask import Flask, jsonify, url_for, request 5 | 6 | from blockchain import BlockChain 7 | 8 | 9 | app = Flask(__name__) 10 | 11 | blockchain = BlockChain() 12 | 13 | node_address = uuid4().hex # Unique address for current node 14 | 15 | 16 | @app.route('/create-transaction', methods=['POST']) 17 | def create_transaction(): 18 | """ 19 | Input Payload: 20 | { 21 | "sender": "address_1" 22 | "recipient": "address_2", 23 | "amount": 3 24 | } 25 | """ 26 | transaction_data = request.get_json() 27 | 28 | index = blockchain.create_new_transaction(**transaction_data) 29 | 30 | response = { 31 | 'message': 'Transaction has been submitted successfully', 32 | 'block_index': index 33 | } 34 | 35 | return jsonify(response), 201 36 | 37 | 38 | @app.route('/mine', methods=['GET']) 39 | def mine(): 40 | block = blockchain.mine_block(node_address) 41 | 42 | response = { 43 | 'message': 'Successfully Mined the new Block', 44 | 'block_data': block 45 | } 46 | return jsonify(response) 47 | 48 | 49 | @app.route('/chain', methods=['GET']) 50 | def get_full_chain(): 51 | response = { 52 | 'chain': blockchain.get_serialized_chain 53 | } 54 | return jsonify(response) 55 | 56 | 57 | @app.route('/register-node', methods=['POST']) 58 | def register_node(): 59 | 60 | node_data = request.get_json() 61 | 62 | blockchain.create_node(node_data.get('address')) 63 | 64 | response = { 65 | 'message': 'New node has been added', 66 | 'node_count': len(blockchain.nodes), 67 | 'nodes': list(blockchain.nodes), 68 | } 69 | return jsonify(response), 201 70 | 71 | 72 | @app.route('/sync-chain', methods=['GET']) 73 | def consensus(): 74 | 75 | def get_neighbour_chains(): 76 | neighbour_chains = [] 77 | for node_address in blockchain.nodes: 78 | resp = requests.get(node_address + url_for('get_full_chain')).json() 79 | chain = resp['chain'] 80 | neighbour_chains.append(chain) 81 | return neighbour_chains 82 | 83 | neighbour_chains = get_neighbour_chains() 84 | if not neighbour_chains: 85 | return jsonify({'message': 'No neighbour chain is available'}) 86 | 87 | longest_chain = max(neighbour_chains, key=len) # Get the longest chain 88 | 89 | if len(blockchain.chain) >= len(longest_chain): # If our chain is longest, then do nothing 90 | response = { 91 | 'message': 'Chain is already up to date', 92 | 'chain': blockchain.get_serialized_chain 93 | } 94 | else: # If our chain isn't longest, then we store the longest chain 95 | blockchain.chain = [blockchain.get_block_object_from_block_data(block) for block in longest_chain] 96 | response = { 97 | 'message': 'Chain was replaced', 98 | 'chain': blockchain.get_serialized_chain 99 | } 100 | 101 | return jsonify(response) 102 | 103 | 104 | if __name__ == '__main__': 105 | 106 | from argparse import ArgumentParser 107 | parser = ArgumentParser() 108 | parser.add_argument('-H', '--host', default='127.0.0.1') 109 | parser.add_argument('-p', '--port', default=5000, type=int) 110 | args = parser.parse_args() 111 | 112 | app.run(host=args.host, port=args.port, debug=True) 113 | -------------------------------------------------------------------------------- /blockchain.py: -------------------------------------------------------------------------------- 1 | import time 2 | import hashlib 3 | 4 | 5 | class Block(object): 6 | 7 | def __init__(self, index, proof, previous_hash, transactions, timestamp=None): 8 | self.index = index 9 | self.proof = proof 10 | self.previous_hash = previous_hash 11 | self.transactions = transactions 12 | self.timestamp = timestamp or time.time() 13 | 14 | @property 15 | def get_block_hash(self): 16 | block_string = "{}{}{}{}{}".format(self.index, self.proof, self.previous_hash, self.transactions, self.timestamp) 17 | return hashlib.sha256(block_string.encode()).hexdigest() 18 | 19 | def __repr__(self): 20 | return "{} - {} - {} - {} - {}".format(self.index, self.proof, self.previous_hash, self.transactions, self.timestamp) 21 | 22 | 23 | class BlockChain(object): 24 | 25 | def __init__(self): 26 | self.chain = [] 27 | self.current_node_transactions = [] 28 | self.nodes = set() 29 | self.create_genesis_block() 30 | 31 | @property 32 | def get_serialized_chain(self): 33 | return [vars(block) for block in self.chain] 34 | 35 | def create_genesis_block(self): 36 | self.create_new_block(proof=0, previous_hash=0) 37 | 38 | def create_new_block(self, proof, previous_hash): 39 | block = Block( 40 | index=len(self.chain), 41 | proof=proof, 42 | previous_hash=previous_hash, 43 | transactions=self.current_node_transactions 44 | ) 45 | self.current_node_transactions = [] # Reset the transaction list 46 | 47 | self.chain.append(block) 48 | return block 49 | 50 | @staticmethod 51 | def is_valid_block(block, previous_block): 52 | if previous_block.index + 1 != block.index: 53 | return False 54 | 55 | elif previous_block.get_block_hash != block.previous_hash: 56 | return False 57 | 58 | elif not BlockChain.is_valid_proof(block.proof, previous_block.proof): 59 | return False 60 | 61 | elif block.timestamp <= previous_block.timestamp: 62 | return False 63 | 64 | return True 65 | 66 | def create_new_transaction(self, sender, recipient, amount): 67 | self.current_node_transactions.append({ 68 | 'sender': sender, 69 | 'recipient': recipient, 70 | 'amount': amount 71 | }) 72 | return True 73 | 74 | @staticmethod 75 | def is_valid_transaction(): 76 | # Not Implemented 77 | pass 78 | 79 | @staticmethod 80 | def create_proof_of_work(previous_proof): 81 | """ 82 | Generate "Proof Of Work" 83 | 84 | A very simple `Proof of Work` Algorithm - 85 | - Find a number such that, sum of the number and previous POW number is divisible by 7 86 | """ 87 | proof = previous_proof + 1 88 | while not BlockChain.is_valid_proof(proof, previous_proof): 89 | proof += 1 90 | 91 | return proof 92 | 93 | @staticmethod 94 | def is_valid_proof(proof, previous_proof): 95 | return (proof + previous_proof) % 7 == 0 96 | 97 | @property 98 | def get_last_block(self): 99 | return self.chain[-1] 100 | 101 | def is_valid_chain(self): 102 | """ 103 | Check if given blockchain is valid 104 | """ 105 | previous_block = self.chain[0] 106 | current_index = 1 107 | 108 | while current_index < len(self.chain): 109 | 110 | block = self.chain[current_index] 111 | 112 | if not self.is_valid_block(block, previous_block): 113 | return False 114 | 115 | previous_block = block 116 | current_index += 1 117 | 118 | return True 119 | 120 | def mine_block(self, miner_address): 121 | # Sender "0" means that this node has mined a new block 122 | # For mining the Block(or finding the proof), we must be awarded with some amount(in our case this is 1) 123 | self.create_new_transaction( 124 | sender="0", 125 | recipient=miner_address, 126 | amount=1, 127 | ) 128 | 129 | last_block = self.get_last_block 130 | 131 | last_proof = last_block.proof 132 | proof = self.create_proof_of_work(last_proof) 133 | 134 | last_hash = last_block.get_block_hash 135 | block = self.create_new_block(proof, last_hash) 136 | 137 | return vars(block) # Return a native Dict type object 138 | 139 | def create_node(self, address): 140 | self.nodes.add(address) 141 | return True 142 | 143 | @staticmethod 144 | def get_block_object_from_block_data(block_data): 145 | return Block( 146 | block_data['index'], 147 | block_data['proof'], 148 | block_data['previous_hash'], 149 | block_data['transactions'], 150 | timestamp=block_data['timestamp'] 151 | ) 152 | -------------------------------------------------------------------------------- /driver.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | from blockchain import BlockChain 4 | 5 | 6 | blockchain = BlockChain() 7 | 8 | 9 | # --------------------------- Testing The Blockchain Class --------------------------------------------- 10 | 11 | 12 | def print_blockchain(chain): 13 | for block in chain: 14 | print(vars(block)) 15 | 16 | 17 | def class_tests(): 18 | print("Length of Current blockchain is: {}".format(len(blockchain.chain))) 19 | print_blockchain(blockchain.chain) 20 | 21 | blockchain.mine_block('address_x') 22 | print("\nAfter Mining . . .") 23 | print("Length of Updated blockchain is: {}".format(len(blockchain.chain))) 24 | print_blockchain(blockchain.chain) 25 | 26 | blockchain.mine_block('address_y') 27 | print("\nAfter One more Mining . . .") 28 | print("Length of Updated blockchain is: {}".format(len(blockchain.chain))) 29 | print_blockchain(blockchain.chain) 30 | 31 | 32 | # --------------------------------- Testing Blockchain APIs -------------------------------------------- 33 | 34 | 35 | def register_node(node_addr, parent_server): 36 | resp = requests.post(parent_server + '/register-node', json={'address': node_addr}) 37 | print("\nOn Server {}: Node-{} has been registered successfully!\n".format(parent_server, node_addr)) 38 | return resp 39 | 40 | 41 | def create_transaction(server, data): 42 | resp = requests.post(server + '/create-transaction', json=data).json() 43 | print("On Server {}: Transaction has been processed!\n".format(server)) 44 | return resp 45 | 46 | 47 | def mine_block(server): 48 | resp = requests.get(server + '/mine').json() 49 | print("On Server {}: Block has been mined successfully!\n".format(server)) 50 | return resp 51 | 52 | 53 | def get_server_chain(server): 54 | resp = requests.get(server + '/chain').json() 55 | print("On Server {}: Chain is-\n{}\n".format(server, resp)) 56 | return resp 57 | 58 | 59 | def sync_chain(server): 60 | print("On Server {}: Started Syncing Chain . . .".format(server)) 61 | resp = requests.get(server + '/sync-chain') 62 | print("On Server {}: Chain synced!\n".format(server)) 63 | return resp 64 | 65 | 66 | def api_tests(): 67 | 68 | server1 = 'http://127.0.0.1:5000' 69 | server2 = 'http://127.0.0.1:5001' 70 | 71 | register_node(server2, server1) # server2 node will be register inside server1 72 | 73 | create_transaction(server2, {'sender': 'I', 'recipient': 'you', 'amount': 3}) 74 | 75 | mine_block(server2) # Mined a new block on server2 76 | 77 | get_server_chain(server1) # server1's chain 78 | get_server_chain(server2) # server2's chain 79 | 80 | sync_chain(server1) # updating server1's chain with neighbour node's chain 81 | 82 | get_server_chain(server1) # server1's chain after syncing 83 | 84 | 85 | if __name__ == "__main__": 86 | class_tests() 87 | api_tests() 88 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2019.6.16 2 | chardet==3.0.4 3 | Click==7.0 4 | Flask==1.1.1 5 | idna==2.8 6 | itsdangerous==1.1.0 7 | Jinja2==2.10.1 8 | MarkupSafe==1.1.1 9 | pkg-resources==0.0.0 10 | requests==2.22.0 11 | urllib3==1.25.3 12 | Werkzeug==0.15.6 13 | --------------------------------------------------------------------------------