├── README.md
└── Block_Chain.py
/README.md:
--------------------------------------------------------------------------------
1 | # Block-Chain-Example
2 | Simple example of blockchain using flask in python.
3 |
4 | ## Prerequisites
5 | - Python 3.0+
6 | - Flask and Requests
7 | ```
8 | # for install
9 | pip install Flask==0.12.2 requests==2.18.4
10 | ```
11 | ### Major steps
12 | 1. Implementing a basic proof of work
13 | 2. Making our blockchain an API endpoint
14 | 3. Creating a miner for your blockchain
15 | 4. Interacting with the blockchain
16 |
17 |
18 | ### Step 1 - Implementing a basic proof of work
19 | Understanding Proof of Work
20 |
21 | A Proof of Work algorithm (PoW) is how new Blocks are created or mined on the blockchain.
22 |
23 | The goal of PoW is to discover a number which solves a problem. The number must be difficult to find but easy to verify for anyone on the network. The number is made of cryptographic signatures, meaning that if the wrong number is sent somewhere it will be denied access. Thus the PoW algorithm means you can now send money without having to trust anybody or any organisation, as the blockchain only cares about the cryptographic signatures. This is the core idea behind Proof of Work.
24 |
25 |
26 |
27 |
str:
41 | """
42 | Creates a SHA-256 hash of a Block
43 | :param block: Block
44 | """
45 |
46 | # We must make sure that the Dictionary is Ordered, or we'll have inconsistent hashes
47 | block_string = json.dumps(block, sort_keys=True).encode()
48 | return hashlib.sha256(block_string).hexdigest()
49 |
50 | def proof_of_work(self, last_proof: int) -> int:
51 | """
52 | Simple Proof of Work Algorithm:
53 | - Find a number 0' such that hash(00') contains leading 4 zeroes, where 0 is the previous 0'
54 | - 0 is the previous proof, and 0' is the new proof
55 | """
56 |
57 | proof = 0
58 | while self.valid_proof(last_proof, proof) is False:
59 | proof += 1
60 |
61 | return proof
62 |
63 | @staticmethod
64 | def valid_proof(last_proof: int, proof: int) -> bool:
65 | """
66 | Validates the Proof
67 | :param last_proof: Previous Proof
68 | :param proof: Current Proof
69 | :return: True if correct, False if not.
70 | """
71 |
72 | guess = f'{last_proof}{proof}'.encode()
73 | guess_hash = hashlib.sha256(guess).hexdigest()
74 | return guess_hash[:4] == "0000"
75 |
76 | ```
77 |
78 |
79 | ### Step 2 - Making our blockchain an API endpoint
80 | Add the below code to the bottom of your Python file. This code turns your file into an API end point. This will allow us to use postman to send/receive requests in our blockchain.
81 |
82 | ```
83 | class Blockchain(object):
84 |
85 | # Instantiate our Node
86 | app = Flask(__name__)
87 |
88 | # Generate a globally unique address for this node
89 | node_identifier = str(uuid4()).replace('-', '')
90 |
91 | # Instantiate the Blockchain
92 | blockchain = Blockchain()
93 |
94 |
95 | @app.route('/mine', methods=['GET'])
96 | def mine():
97 | return "We'll mine a new Block"
98 |
99 | @app.route('/transactions/new', methods=['POST'])
100 | def new_transaction():
101 | return "We'll add a new transaction"
102 |
103 | @app.route('/chain', methods=['GET'])
104 | def full_chain():
105 | response = {
106 | 'chain': blockchain.chain,
107 | 'length': len(blockchain.chain),
108 | }
109 | return jsonify(response), 200
110 |
111 | if __name__ == '__main__':
112 | app.run(host='0.0.0.0', port=5000)
113 |
114 |
115 | @app.route('/transactions/new', methods=['POST'])
116 | def new_transaction():
117 | values = request.get_json()
118 |
119 | # Check that the required fields are in the POST'ed data
120 | required = ['sender', 'recipient', 'amount']
121 | if not all(k in values for k in required):
122 | return 'Missing values', 400
123 |
124 | # Create a new Transaction
125 | index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])
126 |
127 | response = {'message': f'Transaction will be added to Block {index}'}
128 | return jsonify(response), 201
129 | ```
130 |
131 | ### Step 3 - Creating a miner for your blockchain
132 | The below code will create a miner for your server, This will mine and create a new transaction to be placed on a block in your blockchain.
133 |
134 | Add this code, below your new transaction code.
135 |
136 | ```
137 | @app.route('/mine', methods=['GET'])
138 | def mine():
139 | # We run the proof of work algorithm to get the next proof...
140 | last_block = blockchain.last_block
141 | last_proof = last_block['proof']
142 | proof = blockchain.proof_of_work(last_proof)
143 |
144 | # We must receive a reward for finding the proof.
145 | # The sender is "0" to signify that this node has mined a new coin.
146 | blockchain.new_transaction(
147 | sender="0",
148 | recipient=node_identifier,
149 | amount=1,
150 | )
151 |
152 | # Forge the new Block by adding it to the chain
153 | block = blockchain.new_block(proof=proof, previous_hash=0)
154 |
155 | response = {
156 | 'message': "New Block Forged",
157 | 'index': block['index'],
158 | 'transactions': block['transactions'],
159 | 'proof': block['proof'],
160 | 'previous_hash': block['previous_hash'],
161 | }
162 | return jsonify(response), 200
163 | ```
164 |
165 | ### Step 4 - Interacting with the blockchain
166 | Start up your server.
167 |
168 | Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
169 | Now open up postman, at the top of postman you will see a search bar. Make sure the button to the left of it is set to GET.
170 |
171 | Then type into the bar http://localhost:5000/mine
172 |
--------------------------------------------------------------------------------
/Block_Chain.py:
--------------------------------------------------------------------------------
1 | import hashlib
2 | import json
3 | from time import time
4 | from urllib.parse import urlparse
5 | from uuid import uuid4
6 |
7 | import requests
8 | from flask import Flask, jsonify, request
9 |
10 |
11 | class Blockchain:
12 | def __init__(self):
13 | self.current_transactions = []
14 | self.chain = []
15 | self.nodes = set()
16 |
17 | # Create the genesis block
18 | self.new_block(previous_hash='1', proof=100)
19 |
20 | def register_node(self, address):
21 | """
22 | Add a new node to the list of nodes
23 | :param address: Address of node. Eg. 'http://192.168.0.5:5000'
24 | """
25 |
26 | parsed_url = urlparse(address)
27 | self.nodes.add(parsed_url.netloc)
28 |
29 | def valid_chain(self, chain):
30 | """
31 | Determine if a given blockchain is valid
32 | :param chain: A blockchain
33 | :return: True if valid, False if not
34 | """
35 |
36 | last_block = chain[0]
37 | current_index = 1
38 |
39 | while current_index < len(chain):
40 | block = chain[current_index]
41 | print(f'{last_block}')
42 | print(f'{block}')
43 | print("\n-----------\n")
44 | # Check that the hash of the block is correct
45 | if block['previous_hash'] != self.hash(last_block):
46 | return False
47 |
48 | # Check that the Proof of Work is correct
49 | if not self.valid_proof(last_block['proof'], block['proof']):
50 | return False
51 |
52 | last_block = block
53 | current_index += 1
54 |
55 | return True
56 |
57 | def resolve_conflicts(self):
58 | """
59 | This is our consensus algorithm, it resolves conflicts
60 | by replacing our chain with the longest one in the network.
61 | :return: True if our chain was replaced, False if not
62 | """
63 |
64 | neighbours = self.nodes
65 | new_chain = None
66 |
67 | # We're only looking for chains longer than ours
68 | max_length = len(self.chain)
69 |
70 | # Grab and verify the chains from all the nodes in our network
71 | for node in neighbours:
72 | response = requests.get(f'http://{node}/chain')
73 |
74 | if response.status_code == 200:
75 | length = response.json()['length']
76 | chain = response.json()['chain']
77 |
78 | # Check if the length is longer and the chain is valid
79 | if length > max_length and self.valid_chain(chain):
80 | max_length = length
81 | new_chain = chain
82 |
83 | # Replace our chain if we discovered a new, valid chain longer than ours
84 | if new_chain:
85 | self.chain = new_chain
86 | return True
87 |
88 | return False
89 |
90 | def new_block(self, proof, previous_hash):
91 | """
92 | Create a new Block in the Blockchain
93 | :param proof: The proof given by the Proof of Work algorithm
94 | :param previous_hash: Hash of previous Block
95 | :return: New Block
96 | """
97 |
98 | block = {
99 | 'index': len(self.chain) + 1,
100 | 'timestamp': time(),
101 | 'transactions': self.current_transactions,
102 | 'proof': proof,
103 | 'previous_hash': previous_hash or self.hash(self.chain[-1]),
104 | }
105 |
106 | # Reset the current list of transactions
107 | self.current_transactions = []
108 |
109 | self.chain.append(block)
110 | return block
111 |
112 | def new_transaction(self, sender, recipient, amount):
113 | """
114 | Creates a new transaction to go into the next mined Block
115 | :param sender: Address of the Sender
116 | :param recipient: Address of the Recipient
117 | :param amount: Amount
118 | :return: The index of the Block that will hold this transaction
119 | """
120 | self.current_transactions.append({
121 | 'sender': sender,
122 | 'recipient': recipient,
123 | 'amount': amount,
124 | })
125 |
126 | return self.last_block['index'] + 1
127 |
128 | @property
129 | def last_block(self):
130 | return self.chain[-1]
131 |
132 | @staticmethod
133 | def hash(block):
134 | """
135 | Creates a SHA-256 hash of a Block
136 | :param block: Block
137 | """
138 |
139 | # We must make sure that the Dictionary is Ordered, or we'll have inconsistent hashes
140 | block_string = json.dumps(block, sort_keys=True).encode()
141 | return hashlib.sha256(block_string).hexdigest()
142 |
143 | def proof_of_work(self, last_proof):
144 | """
145 | Simple Proof of Work Algorithm:
146 | - Find a number p' such that hash(pp') contains leading 4 zeroes, where p is the previous p'
147 | - p is the previous proof, and p' is the new proof
148 | """
149 |
150 | proof = 0
151 | while self.valid_proof(last_proof, proof) is False:
152 | proof += 1
153 |
154 | return proof
155 |
156 | @staticmethod
157 | def valid_proof(last_proof, proof):
158 | """
159 | Validates the Proof
160 | :param last_proof: Previous Proof
161 | :param proof: Current Proof
162 | :return: True if correct, False if not.
163 | """
164 |
165 | guess = f'{last_proof}{proof}'.encode()
166 | guess_hash = hashlib.sha256(guess).hexdigest()
167 | return guess_hash[:4] == "0000"
168 |
169 |
170 | # Instantiate the Node
171 | app = Flask(__name__)
172 |
173 | # Generate a globally unique address for this node
174 | node_identifier = str(uuid4()).replace('-', '')
175 |
176 | # Instantiate the Blockchain
177 | blockchain = Blockchain()
178 |
179 |
180 | @app.route('/mine', methods=['GET'])
181 | def mine():
182 | # We run the proof of work algorithm to get the next proof...
183 | last_block = blockchain.last_block
184 | last_proof = last_block['proof']
185 | proof = blockchain.proof_of_work(last_proof)
186 |
187 | # We must receive a reward for finding the proof.
188 | # The sender is "0" to signify that this node has mined a new coin.
189 | blockchain.new_transaction(
190 | sender="0",
191 | recipient=node_identifier,
192 | amount=1,
193 | )
194 |
195 | # Forge the new Block by adding it to the chain
196 | previous_hash = blockchain.hash(last_block)
197 | block = blockchain.new_block(proof, previous_hash)
198 |
199 | response = {
200 | 'message': "New Block Forged",
201 | 'index': block['index'],
202 | 'transactions': block['transactions'],
203 | 'proof': block['proof'],
204 | 'previous_hash': block['previous_hash'],
205 | }
206 | return jsonify(response), 200
207 |
208 |
209 | @app.route('/transactions/new', methods=['POST'])
210 | def new_transaction():
211 | values = request.get_json()
212 |
213 | # Check that the required fields are in the POST'ed data
214 | required = ['sender', 'recipient', 'amount']
215 | if not all(k in values for k in required):
216 | return 'Missing values', 400
217 |
218 | # Create a new Transaction
219 | index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])
220 |
221 | response = {'message': f'Transaction will be added to Block {index}'}
222 | return jsonify(response), 201
223 |
224 |
225 | @app.route('/chain', methods=['GET'])
226 | def full_chain():
227 | response = {
228 | 'chain': blockchain.chain,
229 | 'length': len(blockchain.chain),
230 | }
231 | return jsonify(response), 200
232 |
233 |
234 | @app.route('/nodes/register', methods=['POST'])
235 | def register_nodes():
236 | values = request.get_json()
237 |
238 | nodes = values.get('nodes')
239 | if nodes is None:
240 | return "Error: Please supply a valid list of nodes", 400
241 |
242 | for node in nodes:
243 | blockchain.register_node(node)
244 |
245 | response = {
246 | 'message': 'New nodes have been added',
247 | 'total_nodes': list(blockchain.nodes),
248 | }
249 | return jsonify(response), 201
250 |
251 |
252 | @app.route('/nodes/resolve', methods=['GET'])
253 | def consensus():
254 | replaced = blockchain.resolve_conflicts()
255 |
256 | if replaced:
257 | response = {
258 | 'message': 'Our chain was replaced',
259 | 'new_chain': blockchain.chain
260 | }
261 | else:
262 | response = {
263 | 'message': 'Our chain is authoritative',
264 | 'chain': blockchain.chain
265 | }
266 |
267 | return jsonify(response), 200
268 |
269 |
270 | if __name__ == '__main__':
271 | from argparse import ArgumentParser
272 |
273 | parser = ArgumentParser()
274 | parser.add_argument('-p', '--port', default=5000, type=int, help='port to listen on')
275 | args = parser.parse_args()
276 | port = args.port
277 |
278 | app.run(host='0.0.0.0', port=port)
--------------------------------------------------------------------------------