├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── blockchain.py ├── example_block.json └── tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # editors 104 | .vscode 105 | workspace 106 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "3.6" 5 | 6 | install: 7 | - pip install -r requirements.txt 8 | - pip install pipenv 9 | - pipenv install --dev 10 | 11 | script: 12 | - pipenv run python -m unittest 13 | - coverage run tests.py 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to this project 2 | 3 | If you are opening a new issue or submitting a pull request, **go for it!** 4 | Don't be afraid that it's a dumb idea or a duplicate of another issue or an 5 | unwanted change or whatever. Maybe it is! We're still glad to have you! :-) 6 | 7 | **License.** This project is [licensed under the BSD License](https://github.com/jparicka/blockchain/blob/master/LICENSE). 8 | 9 | **It's Free Software** Have fun with it and enjoy! :) 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2018, Jan Paricka 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blockchain in Python from scratch 2 | 3 | Understanding Blockchain isn't easy. At least it wasn't for me. I had to go through number of frustrations due to too few funcional examples of how this technology works. And I like learning by doing so if you do the same, allow me to guide you and by the end you will have a functioning Blockchain with a solid idea of how they work. 4 | 5 | ### Before you get started.. 6 | 7 | Remember that a Blockchain is an immutable, sequential chain of records called Blocks. They can contain transactions, files or any data you like, really. But the important thing is that they’re chained together using hashes. 8 | 9 | ### What is needed? 10 | 11 | Make sure that you have Python 3.6+ installed (along with pip) and you will also need Flask and Requests library. 12 | 13 | ```sh 14 | $ pip3 install -r requirements 15 | ``` 16 | 17 | You will also need an HTTP client like Postman or curl. But anything will do. 18 | 19 | # Step 1: Building a Blockchain 20 | 21 | So what does a block look like? 22 | 23 | Each block has an index, timestamp, transactions, proof (more on that later) and a hash of the previous transaction. 24 | 25 | Here is an example of what a single Block looks like: 26 | 27 | ```python 28 | block = { 29 | 'index': 1, 30 | 'timestamp': 1506092455, 31 | 'transactions': [ 32 | { 33 | 'sender': "852714982as982341a4b27ee00", 34 | 'recipient': "a77f5cdfa2934hv25c7c7da5df1f", 35 | 'amount': 5, 36 | } 37 | ], 38 | 'proof': 323454734020, 39 | 'previous_hash': "2cf24dba5fb0a3202h2025c25e7304249898" 40 | } 41 | ``` 42 | 43 | ### Represenging a Blockchain 44 | 45 | We'll create a Blockchain class whose constructor creates a list to store our Blockchain and another to store transactions. Here is how the Class will look like: 46 | 47 | ```python 48 | class BlockChain(object): 49 | def __init__(self): 50 | self.chain = [] 51 | self.current_transactions = [] 52 | 53 | @staticmethod 54 | def hash(block): 55 | pass 56 | 57 | def new_block(self): 58 | pass 59 | 60 | @property 61 | def last_block(self): 62 | return self.chain[-1] 63 | ``` 64 | This Blockchain class is responsible for managing the chain. It will store transactions and have helper functions. 65 | 66 | The new_block method will create a new block and adds it on the chain and returns the last block in the chain. 67 | 68 | The last_block method will return the last block in the chain. 69 | 70 | Each block contains the hash and the hash of the previous block. This is what gives blockchains it's immutability - i.e. if anyone attack this, all subsequent blocks will be corrupt. 71 | 72 | It's the core idea of blockchains. :) 73 | 74 | 75 | ### Adding transactions to the block 76 | 77 | We will need some way of adding transactions to the block. 78 | 79 | ```python 80 | class BlockChain(object): 81 | ... 82 | def new_transaction(self, sender, recipient, amount): 83 | self.current_transactions.append({ 84 | 'sender': sender, 85 | 'recipient': recipient, 86 | 'amount': amount, 87 | }) 88 | return self.last_block['index'] + 1 89 | ``` 90 | 91 | The new_transaction returns index of the block which will be added to current_transactions and is next one to be mined.. 92 | 93 | ### Creating new blocks 94 | 95 | In addition to creating the genesis block in our constructor, we will also need to flesh out methods for the new_block(), add_new_transaction() and hash(). 96 | 97 | 98 | ```python 99 | import hashlib 100 | import json 101 | import time 102 | 103 | class BlockChain(object): 104 | def __init__(self): 105 | self.chain = [] 106 | self.current_transactions = [] 107 | # create the genesis block 108 | self.new_block(previous_hash=1, proof=100) 109 | 110 | @staticmethod 111 | def hash(block): 112 | # hashes a block 113 | # also make sure that the transactions are ordered otherwise we will have insonsistent hashes! 114 | block_string = json.dumps(block, sort_keys=True).encode() 115 | return hashlib.sha256(block_string).hexdigest() 116 | 117 | def new_block(self, proof, previous_hash=None): 118 | # creates a new block in the Blockchain 119 | block = { 120 | 'index': len(self.chain)+1, 121 | 'timestamp': time.time(), 122 | 'transactions': self.current_transactions, 123 | 'proof': proof, 124 | 'previous_hash': previous_hash or self.hash(self.chain[-1]), 125 | } 126 | # reset the current list of transactions 127 | self.current_transactions = [] 128 | self.chain.append(block) 129 | return block 130 | 131 | @property 132 | def last_block(self): 133 | # returns last block in the chain 134 | return self.chain[-1] 135 | 136 | def new_transaction(self, sender, recipient, amount): 137 | # adds a new transaction into the list of transactions 138 | # these transactions go into the next mined block 139 | self.current_transactions.append({ 140 | "sender":sender, 141 | "recipient":recipient, 142 | "data":amount, 143 | }) 144 | return int(self.last_block['index'])+1 145 | ``` 146 | 147 | Once our block is initiated, we need to feed it with the genesis block (a block with no predecessors). We will also need to add "a proof of work" to our genesis block which is the result of mining. 148 | 149 | At this point, we're nearly done representing our Blockchain. 150 | 151 | So lets talk about how the new blocks are created, forged and mined. :) 152 | 153 | 154 | ### Understanding Proof of Work 155 | 156 | A proof of work algorithm are how new Blocks are created or mined on the Blockchain. 157 | 158 | The goal is to discover a number that solves a problem. 159 | 160 | The number must be difficult and resources consuming to find but super quick and easy to verify. 161 | 162 | This is the core idea of Proof of Work. :) 163 | 164 | 165 | So lets work out some stupid-shit math problem that we are going to require to be solved in order for a block to be mined. 166 | 167 | Lets say that hash of some integer ```x``` multiplied by another ```y``` must always end in 0. So, as an example, the ```hash(x * y) = 4b4f4b4f54...0```. 168 | 169 | ```python 170 | from hashlib import sha256 171 | 172 | x = 5 173 | y = 0 # we do not know what y should be yet 174 | while sha256(f'{x*y}'.encode()).hexdigest()[-1] != "0": y+1 175 | 176 | print(f'The solution is y = {y}) 177 | ``` 178 | 179 | In this example we fixed the ```x = 5```. 180 | The solution in this case is ```x = 5 ands y = 21``` since it procuced hash ```0```. 181 | 182 | ```python 183 | hash(5 * 21) = "1253e9373e781b7500266caa55150e08e210bc8cd8cc70d89985e3600155e860" 184 | ``` 185 | 186 | In the Bitcoin world, the Proof of Work algorithm is called Hashcash. And it's not any different from the example above. It's the very algorithm that miners race to solve in order to create a new block. The difficulty is of course determined by the number of the characters searched for in the string. In our example we simplified it by defining that the resultant hash must end in 0 to make the whole thing in our case quicker and less resource intensive but this is how it works really. 187 | 188 | The miners are rewarded for finding a solution by receiving a coin. In a transaction. There are many opinions on effectiness of this but this is how it works. And it really is that simple and this way the network is able to easily verify their solution. :) 189 | 190 | 191 | ### Implementing Proof of Work 192 | 193 | Let's implement a similar algorithm for our Blockchain. Our rule will be similar to the example above. 194 | 195 | "Find a number p that when hashed with the previous block's solution a hash with 4 leading 0 is produced." 196 | 197 | ```python 198 | import hashlib 199 | import json 200 | 201 | from time import time 202 | from uuid import uuid4 203 | 204 | class BlockChain(object): 205 | ... 206 | def proof_of_work(self, last_proof): 207 | # simple proof of work algorithm 208 | # find a number p' such as hash(pp') containing leading 4 zeros where p is the previous p' 209 | # p is the previous proof and p' is the new proof 210 | proof = 0 211 | while self.valid_proof(last_proof, proof) is False: 212 | proof += 1 213 | return proof 214 | 215 | @staticmethod 216 | def validate_proof(last_proof, proof): 217 | # validates the proof: does hash(last_proof, proof) contain 4 leading zeroes? 218 | guess = f'{last_proof}{proof}'.encode() 219 | guess_hash = hashlib.sha256(guess).hexdigest() 220 | return guess_hash[:4] == "0000" 221 | ``` 222 | 223 | To adjust the difficulty of the algorithm, we could modify the number of leading zeors. But strictly speaking 4 is sufficient enough. Also, you may find out that adding an extra 0 makes a mammoth difference to the time required to find a solution. 224 | 225 | Now, our Blockchain class is pretty much complete, let's begin to interact with the ledger using the HTTP requests. 226 | 227 | 228 | # Step 2: Blockchain as an API 229 | 230 | We'll use Python Flask framework. It's a micro-framework and it's really easy to use so for our example it'll do nicely. 231 | 232 | We'll create three simple API endpoints: 233 | 234 | - /transactions/new to create a new transaction block 235 | - /mine to tell our service to mine a new block 236 | - /chain to return the full Blockchain 237 | 238 | ### Setting up Flask 239 | 240 | Our server will form a single node in our Blockchain. So let's create some code. 241 | 242 | ```python 243 | import hashlib 244 | import json 245 | from time import time 246 | from uuid import uuid4 247 | 248 | from flask import Flask, jsonify, request 249 | 250 | # initiate the node 251 | app = Flask(__name__) 252 | # generate a globally unique address for this node 253 | node_identifier = str(uuid4()).replace('-', '') 254 | # initiate the Blockchain 255 | blockchain = BlockChain() 256 | 257 | @app.route('/mine', methods=['GET']) 258 | def mine(): 259 | return "We will mine a new block" 260 | 261 | @app.route('/transaction/new', methods=['GET']) 262 | def transaction_new(): 263 | return "We will add a new transaction" 264 | 265 | @app.route('/chain', methods=['GET']) 266 | def chain(): 267 | response = { 268 | 'chain': blockchain.chain, 269 | 'length': len(blockchain.chain), 270 | } 271 | return jsonify(response), 200 272 | 273 | if __name__ == '__main__': 274 | app.run(host='0.0.0.0', 5000) 275 | ``` 276 | 277 | ### The transaction endpoint 278 | 279 | This is what the request for the transaction will look like. It's what the user will send to the server. 280 | 281 | ```json 282 | { 283 | "sender": "sender_address", 284 | "recipient": "recipient_address", 285 | "amount": 100 286 | } 287 | ``` 288 | 289 | Since we already have the method for adding transactions to a block, the rest is easy and pretty straight forward. 290 | 291 | ```python 292 | import hashlib 293 | import json 294 | 295 | from time import time 296 | from uulib import uulib4 297 | from flask import Flask, jsonify, request 298 | 299 | ... 300 | 301 | @app.route('/transactions/new', methods=['POST']) 302 | def new_transaction(): 303 | 304 | values = request.get_json() 305 | required = ['sender', 'recipient', 'amount'] 306 | 307 | if not all(k in values for k in required): 308 | return 'Missing values', 400 309 | 310 | # create a new transaction 311 | index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount']) 312 | response = {'message': f'Transaction will be added to the Block {index}.'} 313 | 314 | return jsonify(response, 200) 315 | ``` 316 | 317 | ### The mining endpoint 318 | 319 | Our mining endpoint is where the mining happens and it's actually very easy as all it has to do are three things: 320 | 321 | 1) Calculate proof of work 322 | 323 | 2) Reward the miner by adding a transaction granting miner 1 coin 324 | 325 | 3) Forge the new Block by adding it to the chain 326 | 327 | 328 | So, let's add on the mining function on our API: 329 | 330 | ```python 331 | import hashlib 332 | import json 333 | 334 | from time import time 335 | from uulib import uulib4 336 | from flask import Flask, jsonify, request 337 | 338 | ... 339 | 340 | @app.route('/mine', methods=['GET']) 341 | def mine(): 342 | 343 | # first we have to run the proof of work algorithm to calculate the new proof.. 344 | last_block = blockchain.last_block 345 | last_proof = last_block['proof'] 346 | proof = blockchain.proof_of_work(last_proof) 347 | 348 | # we must receive reward for finding the proof 349 | blockchain.new_transaction( 350 | sender=0, 351 | recipient=node_identifier, 352 | amount=1, 353 | ) 354 | 355 | # forge the new block by adding it to the chain 356 | previous_hash = blockchain.hash(last_block) 357 | block = blockchain.new_block(proof, previous_hash) 358 | 359 | response = { 360 | 'message': "Forged new block.", 361 | 'index': block['index'], 362 | 'transactions': block['transaction'], 363 | 'proof': block['proof'], 364 | 'previous_hash': block['previous_hash'], 365 | } 366 | return jsonify(response), 200 367 | ``` 368 | 369 | At this point, we are done, and we can start interacting with out blockchain. :) 370 | 371 | 372 | # Step 3: Interacting with our Blockchain 373 | 374 | You can use a plain old cURL or Postman to interact with our Blockchain API ovet the network. 375 | 376 | Fire up the server: 377 | 378 | ``` 379 | $ python3 blockchain.py 380 | * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) 381 | 382 | ``` 383 | 384 | So first off let's try mining a block by making a GET request to the "mine" http://localhost:5000/mine: 385 | 386 | ```json 387 | [ 388 | { 389 | "index": 1, 390 | "message": "Forged new block.", 391 | "previous_hash": "7cd122100c9ded644768ccdec2d9433043968352e37d23526f63eefc65cd89e6", 392 | "proof": 35293, 393 | "transactions": [ 394 | { 395 | "data": 1, 396 | "recipient": "6a01861c7b3f483eab90727e621b2b96", 397 | "sender": 0 398 | } 399 | ] 400 | }, 401 | 200 402 | ] 403 | ``` 404 | 405 | Motherfucker, very good! :) 406 | 407 | Now lets create a new transaction by making a POST request to http://localhost:5000/transaction/new with a body containing our transaction structure. Let's make this call using the cURL: 408 | 409 | ``` 410 | $ curl -X POST -H "Content-Type: application/json" -d '{ 411 | "sender": "d4ee26eee15148ee92c6cd394edd974e", 412 | "recipient": "recipient-address", 413 | "amount": 5 414 | }' "http://localhost:5000/transactions/new" 415 | ``` 416 | 417 | I have restarted the server, mined two blocks, to give 3 in total. So let's inspect the full chain by requesting http://localhost:5000/chain: 418 | 419 | ```json 420 | { 421 | "chain": [ 422 | { 423 | "index": 1, 424 | "previous_hash": 1, 425 | "proof": 100, 426 | "timestamp": 1506280650.770839, 427 | "transactions": [] 428 | }, 429 | { 430 | "index": 2, 431 | "previous_hash": "c099bc...bfb7", 432 | "proof": 35293, 433 | "timestamp": 1506280664.717925, 434 | "transactions": [ 435 | { 436 | "amount": 1, 437 | "recipient": "8bbcb347e0631231...e152b", 438 | "sender": "0" 439 | } 440 | ] 441 | }, 442 | { 443 | "index": 3, 444 | "previous_hash": "eff91a...10f2", 445 | "proof": 35089, 446 | "timestamp": 1506280666.1086972, 447 | "transactions": [ 448 | { 449 | "amount": 1, 450 | "recipient": "9e2e234e12e0631231...e152b", 451 | "sender": "0" 452 | } 453 | ] 454 | } 455 | ], 456 | "length": 3 457 | } 458 | ``` 459 | 460 | # Step 4: Transaction verification 461 | 462 | For this we will be using Python NaCl to generate a public/private signing key pair: private.key, public.key which need to be generated before runtime. We will employ the cryptography using the Public-key signature standards X.509 for Public Key Certificates. 463 | 464 | 465 | # Step 5: Smart wallet 466 | 467 | This is very cool. Wallet is a gateway to decentralized applications on the Blockchain. It allows you to hold and secure tokens and other crypto-assets. This Blockchain example is built on ERC-20 standards and therefore should be compatible and working out of the box with your regular wallet. :) 468 | 469 | 470 | # Step 6: Consensus 471 | 472 | This is very cool actually. We've got a fully valid basic Blockchain that accepts transactions and allows us to mine a new block (and get rewarded for it). But the whole point of Blockchains is to be decentralized, and how on earth do we ensure that all the data reflect the same chain? Well, it's actually a well know problem of Consensus, and we are going to have to implement a Consensus Algorithm if we want more that a single node in our network. So better buckle up, we're moving onto registering the new nodes. :) 473 | 474 | ### Registering new Nodes 475 | 476 | OK, first off, before you start adding new nodes you'd need to let your node to know about his neighbouring nodes. This needs to be done before you even start implementing Consensus Algorithm. Each node on our network needs to keep registry of other nodes on the network. And therefore we will need to add more endpoints to orchestrate our miner nodes: 477 | 478 | - /miner/register - to register a new miner node into the operation 479 | - /miner/nodes/resolve - to implement our consensus algorithm to resolve any potential conflicts, making sure all nodes have the correct and up to date chain 480 | 481 | First we're goint to modify the Blockchain class constructor and add in the method for registering nodes: 482 | 483 | 484 | ```python 485 | ... 486 | from urllib.parse import urlparse 487 | ... 488 | 489 | class BlockChain(object): 490 | def __init__(self): 491 | ... 492 | self.nodes = set() 493 | ... 494 | 495 | def register_miner_node(self, address): 496 | # add on the new miner node onto the list of nodes 497 | parsed_url = urlparse(address) 498 | self.nodes.add(parse_url.netloc) 499 | return 500 | ``` 501 | 502 | ### Implementing the Consensus Algorithm 503 | 504 | As mentioned, conflict is when one node has a different chain to another node. To resolve this, we'll make the rule that the longest valid chain is authoritative. In other words, the longest valid chain is de-facto one. Using this simple rule, we reach Consensuns amongs the nodes in our network. 505 | 506 | ```python 507 | import requests 508 | ... 509 | class BlockChain(object): 510 | ... 511 | def valid_chain(self, chain): 512 | 513 | # determine if a given blockchain is valid 514 | last_block = chain[0] 515 | current_index = 1 516 | 517 | while current_index < len(chain): 518 | block = chain[current_index] 519 | # check that the hash of the block is correct 520 | if block['previous_hash'] != self.hash(last_block) 521 | return False 522 | # check that the proof of work is correct 523 | if not self.valid_proof(last_block['proof'], block['proof']) 524 | return False 525 | 526 | last_block = block 527 | current_index += 1 528 | 529 | return True 530 | 531 | def resolve_conflicts(self): 532 | # this is our Consensus Algorithm, it resolves conflicts by replacing 533 | # our chain with the longest one in the network. 534 | 535 | neighbours = self.nodes 536 | new_chain = None 537 | 538 | # we are only looking for the chains longer than ours 539 | max_length = len(self.chain) 540 | 541 | # grab and verify chains from all the nodes in our network 542 | for node in neighbours: 543 | 544 | # we utilize our own api to construct the list of chains :) 545 | response = request.get(f'http://{node}/chain') 546 | 547 | if response.status_code == 200: 548 | 549 | length = response.json()['length'] 550 | chain = response.json()['chain'] 551 | 552 | # check if the chain is longer and whether the chain is valid 553 | if length > max_length and self.valid_chain(chain): 554 | max_length = length 555 | new_chain = chain 556 | 557 | # replace our chain if we discover a new longer valid chain 558 | if new_chain: 559 | self.chain = new_chain 560 | return True 561 | 562 | return False 563 | ``` 564 | 565 | OK so the first method the valid_chain loops through each block and checks that the chain is valid by verifying both the hash and the proof. 566 | 567 | The resolve_conflicts method loops through all the neighbouring nodes, downloads their chain and verify them using the above valid_chain method. If a valid chain is found, and it is longer than ours, we replace our chain with this new one. 568 | 569 | So, what is left are the very last two API endpoints, specifically one for adding a neighbouring node and another for resolving the conflicts, and it's quite straight forward: 570 | 571 | ```python 572 | @app.route('/miner/register', method=['POST']) 573 | def register_new_miner(): 574 | values = request.get_json() 575 | 576 | # get the list of miner nodes 577 | nodes = values.get('nodes') 578 | if nodes is None: 579 | return "Error: Please supply list of valid nodes", 400 580 | 581 | # register nodes 582 | for node in nodes: 583 | blockchain.register_node(node) 584 | 585 | response = { 586 | 'message': 'New nodes have been added.', 587 | 'total_nodes': list(blockchain.nodes), 588 | } 589 | return jsonify(response), 200 590 | 591 | @app.route('/miner/nodes/resolve', method=['POST']) 592 | def consensus(): 593 | # an attempt to resolve conflicts to reach the consensus 594 | conflicts = blockchain.resolve_conflicts() 595 | 596 | if(conflicts): 597 | response = { 598 | 'message': 'Our chain was replaced.', 599 | 'new_chain': blockchain.chain, 600 | } 601 | return jsonify(response), 200 602 | 603 | response = { 604 | 'message': 'Our chain is authoritative.', 605 | 'chain': blockchain.chain, 606 | } 607 | return jsonify(response), 200 608 | ``` 609 | 610 | And here comes the big one, the one you have been waiting for as at this point you can grab a different machine or a computer if you like and spin up different miners on our network. :) 611 | 612 | Or you can run multiple miners on your single machine by running the same process but using a different port number. 613 | As an example, I can run another miner node on my machine by running it on a different port and register it with the current miner. Therefore I have two miners: http://localhost:5000 and http://localhost:5001. 614 | 615 | ### Registering a new node 616 | 617 | ``` 618 | $ curl -X POST -H "Content-Type: application/json" -d '{ 619 | "nodes": ["http://127.0.0.1:5001"], 620 | }' "http://localhost:5000/nodes/register" 621 | ``` 622 | 623 | Request OK, returned: 624 | ```json 625 | { 626 | "message": "New nodes have been added.", 627 | "all_nodes": [ 628 | "127.0.0.1:5001" 629 | ] 630 | } 631 | ``` 632 | 633 | ### Consensus Algorithm at Work 634 | 635 | ``` 636 | $ curl http://localhost:5000/nodes/resolve" 637 | ``` 638 | 639 | Request OK, returns: 640 | 641 | ```json 642 | { 643 | "message": "Our chain was replaced.", 644 | "new_chain": [ 645 | { 646 | "index": 1, 647 | "previous_hash": 1, 648 | "proof": 100, 649 | "timestamp": 1525160363.12144, 650 | "transactions": [], 651 | }, 652 | { 653 | "index": 2, 654 | "previous_hash": "7cd122100c9ded644768ccdec2d9433043968352e37d23526f63eefc65cd89e6", 655 | "proof": 35293, 656 | "timestamp": 1525160706.82745, 657 | "transactions": [ 658 | { 659 | "amount": 1, 660 | "recipient": "a77f5cdfa2934hv25c7c7da5df1f", 661 | "sender": 0, 662 | } 663 | ] 664 | }, 665 | ] 666 | } 667 | ``` 668 | 669 | And that's a wrap... Now go get some friends to mine your Blockchain. :) 670 | 671 | 672 | License 673 | ---- 674 | 675 | BSD-2-Clause 676 | 677 | 678 | Can I contribute? 679 | ---- 680 | 681 | Sure, open an issue, point out errors, and what not. Wanna fix something yourselves, you're welcome to open a PR and I appreciate it. 682 | 683 | 684 | ### Donation Address 685 | 686 | ETH: 0xbcFAB06E0cc4Fe694Bdf780F1FcB1bB143bD93Ad 687 | 688 | Have fun! :) 689 | -------------------------------------------------------------------------------- /blockchain.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import json 3 | from time import time 4 | from uuid import uuid4 5 | from urllib.parse import urlparse 6 | 7 | from flask import Flask, jsonify, request 8 | 9 | 10 | class BlockChain(object): 11 | """ Main BlockChain class """ 12 | def __init__(self): 13 | self.chain = [] 14 | self.current_transactions = [] 15 | self.nodes = set() 16 | # create the genesis block 17 | self.new_block(previous_hash=1, proof=100) 18 | 19 | @staticmethod 20 | def hash(block): 21 | # hashes a block 22 | # also make sure that the transactions are ordered otherwise we will have insonsistent hashes! 23 | block_string = json.dumps(block, sort_keys=True).encode() 24 | return hashlib.sha256(block_string).hexdigest() 25 | 26 | def new_block(self, proof, previous_hash=None): 27 | # creates a new block in the blockchain 28 | block = { 29 | 'index': len(self.chain)+1, 30 | 'timestamp': time(), 31 | 'transactions': self.current_transactions, 32 | 'proof': proof, 33 | 'previous_hash': previous_hash or self.hash(self.chain[-1]), 34 | } 35 | 36 | # reset the current list of transactions 37 | self.current_transactions = [] 38 | self.chain.append(block) 39 | return block 40 | 41 | @property 42 | def last_block(self): 43 | # returns last block in the chain 44 | return self.chain[-1] 45 | 46 | def new_transaction(self, sender, recipient, amount): 47 | # adds a new transaction into the list of transactions 48 | # these transactions go into the next mined block 49 | self.current_transactions.append({ 50 | "sender":sender, 51 | "recient":recipient, 52 | "data":amount, 53 | }) 54 | return int(self.last_block['index'])+1 55 | 56 | def proof_of_work(self, last_proof): 57 | # simple proof of work algorithm 58 | # find a number p' such as hash(pp') containing leading 4 zeros where p is the previous p' 59 | # p is the previous proof and p' is the new proof 60 | proof = 0 61 | while self.validate_proof(last_proof, proof) is False: 62 | proof += 1 63 | return proof 64 | 65 | @staticmethod 66 | def validate_proof(last_proof, proof): 67 | # validates the proof: does hash(last_proof, proof) contain 4 leading zeroes? 68 | guess = f'{last_proof}{proof}'.encode() 69 | guess_hash = hashlib.sha256(guess).hexdigest() 70 | return guess_hash[:4] == "0000" 71 | 72 | def register_node(self, address): 73 | # add a new node to the list of nodes 74 | parsed_url = urlparse(address) 75 | self.nodes.add(parsed_url.netloc) 76 | 77 | def full_chain(self): 78 | # xxx returns the full chain and a number of blocks 79 | pass 80 | 81 | 82 | # initiate the node 83 | app = Flask(__name__) 84 | # generate a globally unique address for this node 85 | node_identifier = str(uuid4()).replace('-', '') 86 | # initiate the Blockchain 87 | blockchain = BlockChain() 88 | 89 | @app.route('/mine', methods=['GET']) 90 | def mine(): 91 | 92 | # first we need to run the proof of work algorithm to calculate the new proof.. 93 | last_block = blockchain.last_block 94 | last_proof = last_block['proof'] 95 | proof = blockchain.proof_of_work(last_proof) 96 | 97 | # we must recieve reward for finding the proof in form of receiving 1 Coin 98 | blockchain.new_transaction( 99 | sender=0, 100 | recipient=node_identifier, 101 | amount=1, 102 | ) 103 | 104 | # forge the new block by adding it to the chain 105 | previous_hash = blockchain.hash(last_block) 106 | block = blockchain.new_block(proof, previous_hash) 107 | 108 | response = { 109 | 'message': "Forged new block.", 110 | 'index': block['index'], 111 | 'transactions': block['transactions'], 112 | 'proof': block['proof'], 113 | 'previous_hash': block['previous_hash'], 114 | } 115 | return jsonify(response, 200) 116 | 117 | @app.route('/transaction/new', methods=['GET']) 118 | def new_transaction(): 119 | 120 | values = request.get_json() 121 | required = ['sender', 'recipient', 'amont'] 122 | 123 | if not all(k in values for k in required): 124 | return 'Missing values.', 400 125 | 126 | # create a new transaction 127 | index = blockchain.new_transaction( 128 | sender = values['sender'], 129 | recipient = values['recipient'], 130 | amount = values['amount'] 131 | ) 132 | 133 | response = { 134 | 'message': f'Transaction will be added to the Block {index}', 135 | } 136 | return jsonify(response, 200) 137 | 138 | @app.route('/chain', methods=['GET']) 139 | def full_chain(): 140 | response = { 141 | 'chain': blockchain.chain, 142 | 'length': len(blockchain.chain), 143 | } 144 | return jsonify(response), 200 145 | 146 | @app.route('/nodes/register', methods=['POST']) 147 | def register_nodes(): 148 | values = request.get_json() 149 | 150 | print('values',values) 151 | nodes = values.get('nodes') 152 | if nodes is None: 153 | return "Error: Please supply a valid list of nodes", 400 154 | 155 | # register each newly added node 156 | for node in nodes: blockchain.register_node(node) 157 | 158 | response = { 159 | 'message': "New nodes have been added", 160 | 'all_nodes': list(blockchain.nodes), 161 | } 162 | 163 | return jsonify(response), 201 164 | 165 | 166 | if __name__ == '__main__': 167 | app.run(host='0.0.0.0', port=5000) 168 | 169 | -------------------------------------------------------------------------------- /example_block.json: -------------------------------------------------------------------------------- 1 | { 2 | 'index': 1, 3 | 'description': 'This would be an example of the blockchain block', 4 | 'timestamp': 1506123125, 5 | 'transactions': [ 6 | { 7 | 'sender': "8527147jh1g23jhg12j3hgj12hg31j2gh37ee00", 8 | 'recipient': "a77f12j3h1kjh23kj12h3k1jhk2jh3123", 9 | 'amount': 5, 10 | } 11 | ], 12 | 'proof': 324123123774000, 13 | 'previous_hash': "2cf24dba5fbllkj1l2j3lk1j2lk3j12lkj3lj12l3kjl12kj3l12kj3l12kj3lk21jl31kj2" 14 | } 15 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import awesome 4 | 5 | 6 | class TestMethods(unittest.TestCase): 7 | def test_add(self): 8 | self.assertEqual(awesome.smile(), ":)") 9 | 10 | 11 | if __name__ == '__main__': 12 | unittest.main() 13 | --------------------------------------------------------------------------------