├── .gitignore ├── test ├── __init__.py └── blockchain │ ├── __init__.py │ ├── test_block.py │ ├── test_transaction.py │ └── test_blockchain.py ├── api ├── globals.py ├── home.py ├── schema │ ├── blockchain.py │ ├── miner.py │ ├── block.py │ └── transaction.py ├── miner.py ├── blockchain.py └── transaction.py ├── Pipfile ├── blockchain ├── transaction.py ├── block.py └── __init__.py ├── README.md ├── app.py ├── LICENSE └── Pipfile.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/blockchain/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/globals.py: -------------------------------------------------------------------------------- 1 | from blockchain import Blockchain 2 | 3 | blockchain = Blockchain() -------------------------------------------------------------------------------- /api/home.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | api = Blueprint('api', __name__) 4 | 5 | @api.route('/', defaults={'page': 'index'}) 6 | def show(page): 7 | return "Home" -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.python.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | flask = "*" 8 | requests = "*" 9 | flasgger = "*" 10 | flask-marshmallow = "*" 11 | apispec = "*" 12 | 13 | [dev-packages] 14 | 15 | [requires] 16 | python_version = "3.8" 17 | -------------------------------------------------------------------------------- /api/schema/blockchain.py: -------------------------------------------------------------------------------- 1 | from flask_marshmallow import Schema 2 | from marshmallow.fields import Nested 3 | from api.schema.block import BlockSchema 4 | 5 | 6 | class BlockchainSchema(Schema): 7 | class Meta: 8 | # Fields to expose 9 | fields = ["blockchain"] 10 | 11 | blockchain = Nested(BlockSchema, many=True) -------------------------------------------------------------------------------- /api/schema/miner.py: -------------------------------------------------------------------------------- 1 | from flask_marshmallow import Schema 2 | from marshmallow.fields import Nested, Str 3 | from api.schema.block import BlockSchema 4 | 5 | 6 | class MineSchema(Schema): 7 | class Meta: 8 | # Fields to expose 9 | fields = ["message", "block"] 10 | 11 | message = Str() 12 | block = Nested(BlockSchema) -------------------------------------------------------------------------------- /test/blockchain/test_block.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from blockchain.block import Block 3 | 4 | 5 | class TestBlock(TestCase): 6 | def test_block_hash(self): 7 | """ 8 | Test hashing blocks 9 | """ 10 | block = Block(1, [], 0, '0') 11 | 12 | # If we recalculate the hash on the block we should get the same result as we have stored 13 | self.assertEqual(block.hash, block.hash_block()) 14 | -------------------------------------------------------------------------------- /api/schema/block.py: -------------------------------------------------------------------------------- 1 | from flask_marshmallow import Schema 2 | from marshmallow.fields import Nested, Str, Number 3 | from api.schema.transaction import TransactionSchema 4 | 5 | 6 | class BlockSchema(Schema): 7 | class Meta: 8 | # Fields to expose 9 | fields = ["index", "timestamp", "transactions", "nonce", "previous_hash", "hash"] 10 | 11 | index = Number() 12 | nonce = Str() 13 | timestamp = Number() 14 | previous_hash = Str() 15 | hash = Str() 16 | transactions = Nested(TransactionSchema, many=True) 17 | -------------------------------------------------------------------------------- /api/schema/transaction.py: -------------------------------------------------------------------------------- 1 | from flask_marshmallow import Schema 2 | from marshmallow.fields import Str, Number, Nested 3 | 4 | 5 | class TransactionSchema(Schema): 6 | class Meta: 7 | # Fields to expose 8 | fields = ["sender", "recipient", "amount", "timestamp"] 9 | 10 | sender = Str() 11 | recipient = Str() 12 | amount = Number() 13 | timestamp = Number() 14 | 15 | 16 | class TransactionCreatedSchema(Schema): 17 | class Meta: 18 | # Fields to expose 19 | fields = ["message", "transaction"] 20 | 21 | message = Str() 22 | transaction = Nested(TransactionSchema) -------------------------------------------------------------------------------- /blockchain/transaction.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | class Transaction: 5 | def __init__(self, sender, recipient, amount): 6 | """ 7 | Creates a new transaction 8 | 9 | :param sender: sender account 10 | :param recipient: recipient account 11 | :param amount: amount to be transferred 12 | """ 13 | self.sender = sender 14 | self.recipient = recipient 15 | self.timestamp = time.time() 16 | self.amount = amount 17 | 18 | def validate(self): 19 | """ 20 | Checks if a transaction is valid 21 | 22 | :return: True if it is valid, False if not. 23 | """ 24 | 25 | # Prevent stealing by creating negative transactions 26 | if self.amount < 0: 27 | return False 28 | 29 | return True 30 | 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blockchainpy 2 | 3 | A very minimal implementation of a blockchain in Python. Please note that this is by no means intended to be use in a real scenario, the code used here is for educational purposes only. 4 | 5 | ## Feature Roadmap: 6 | 7 | - [X] Possibility to add blocks to the chain 8 | - [X] Simple Proof of Work (PoW) algorithm 9 | - [X] Possibility to add transactions 10 | - [X] Possibility to mine new blocks 11 | - [X] Possibility to replace the chain with a new one 12 | - [ ] Wallet management 13 | - [ ] Sign transactions 14 | - [ ] Peer to Peer communication 15 | 16 | ## Set Up 17 | 18 | 1. Check out the code 19 | 2. Install requirements 20 | ``` 21 | pipenv install 22 | ``` 23 | 3. Start the server with: 24 | ``` 25 | pipenv run python -m flask run 26 | ``` 27 | 28 | 4. Visit http://localhost/apidocs 29 | 30 | ## Tests 31 | 32 | The code is covered by tests, to run the tests please execute 33 | 34 | ``` 35 | pipenv run python -m unittest 36 | ``` -------------------------------------------------------------------------------- /api/miner.py: -------------------------------------------------------------------------------- 1 | from http import HTTPStatus 2 | from flask import Blueprint 3 | from flasgger import swag_from 4 | from api.globals import blockchain 5 | from api.schema.miner import MineSchema 6 | 7 | miner_api = Blueprint('miner', __name__) 8 | 9 | 10 | @miner_api.route('/mine', methods=['POST']) 11 | @swag_from({ 12 | 'responses': { 13 | HTTPStatus.OK.value: { 14 | 'description': 'The block with all its transactions.', 15 | 'schema': MineSchema 16 | } 17 | } 18 | }) 19 | def mine(): 20 | """ 21 | Mines a new block into the chain 22 | Consolidates the pending transactions into a new block, and adds the block to the blockchain 23 | --- 24 | produces: 25 | - application/json 26 | responses: 27 | 200: 28 | description: Result of the mining attempt and the new block 29 | """ 30 | block = blockchain.mine('address') 31 | 32 | response = { 33 | 'message': "New Block Mined", 34 | 'block': block 35 | } 36 | 37 | return MineSchema().dumps(response), 200 38 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_marshmallow import Marshmallow 3 | from flasgger import Swagger 4 | from api.home import api 5 | from api.blockchain import blockchain_api 6 | from api.miner import miner_api 7 | from api.transaction import transaction_api 8 | 9 | def create_app(): 10 | app = Flask(__name__) 11 | return app 12 | 13 | app = create_app() 14 | ma = Marshmallow(app) 15 | 16 | app.config['SWAGGER'] = { 17 | 'title': 'Blockchain API', 18 | } 19 | swagger = Swagger(app) 20 | 21 | 22 | app.register_blueprint(api, url_prefix='/api') 23 | app.register_blueprint(blockchain_api, url_prefix='/api/blockchain') 24 | app.register_blueprint(miner_api, url_prefix='/api/miner') 25 | app.register_blueprint(transaction_api, url_prefix='/api/transaction') 26 | 27 | 28 | if __name__ == '__main__': 29 | from argparse import ArgumentParser 30 | 31 | parser = ArgumentParser() 32 | parser.add_argument('-p', '--port', default=5000, type=int, help='port to listen on') 33 | args = parser.parse_args() 34 | port = args.port 35 | 36 | app.run(host='0.0.0.0', port=port) 37 | -------------------------------------------------------------------------------- /test/blockchain/test_transaction.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from blockchain import Blockchain 3 | 4 | 5 | class TestTransaction(TestCase): 6 | def test_create_transaction(self): 7 | """ 8 | Test creating a transaction 9 | """ 10 | blockchain = Blockchain() 11 | _, valid = blockchain.create_transaction('sender', 'recipient', 1) 12 | 13 | transaction = blockchain.last_transaction 14 | 15 | # Let's now validate the transaction 16 | self.assertTrue(valid) 17 | self.assertEqual(transaction.sender, 'sender') 18 | self.assertEqual(transaction.recipient, 'recipient') 19 | self.assertEqual(transaction.amount, 1) 20 | 21 | def test_create_negative_transaction(self): 22 | """ 23 | Test creating a transaction with a negative amount 24 | """ 25 | blockchain = Blockchain() 26 | transaction, valid = blockchain.create_transaction('sender', 'recipient', -1) 27 | 28 | # Let's now validate the transaction 29 | self.assertIsNone(transaction) 30 | self.assertFalse(valid) 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Live Code Stream 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /blockchain/block.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | from api.schema.block import BlockSchema 3 | from time import time 4 | 5 | 6 | class Block: 7 | def __init__(self, index, transactions, nonce, previous_hash): 8 | """ 9 | Constructs a new block 10 | 11 | :param index: 12 | :param transactions: 13 | :param previous_hash: 14 | """ 15 | self.index = index 16 | self.timestamp = time() 17 | self.transactions = transactions 18 | self.nonce = nonce 19 | self.previous_hash = previous_hash 20 | self.hash = self.hash_block() 21 | 22 | def serialize(self, ignore=None): 23 | """ 24 | Serializes a block into a string 25 | 26 | :return: 27 | """ 28 | if ignore is None: 29 | ignore = [] 30 | block_params = {x: self.__dict__[x] for x in self.__dict__ if x not in ignore} 31 | 32 | return BlockSchema().dumps(block_params) 33 | 34 | def hash_block(self): 35 | """ 36 | Calculates the hash of the block 37 | 38 | :return: 39 | """ 40 | sha = hashlib.sha256() 41 | sha.update(self.serialize(['hash']).encode('utf-8')) 42 | return sha.hexdigest() 43 | -------------------------------------------------------------------------------- /api/blockchain.py: -------------------------------------------------------------------------------- 1 | from http import HTTPStatus 2 | from flask import Blueprint, abort 3 | from flasgger import swag_from 4 | from api.globals import blockchain 5 | from api.schema.blockchain import BlockchainSchema 6 | from api.schema.block import BlockSchema 7 | 8 | blockchain_api = Blueprint('blockchain', __name__) 9 | 10 | 11 | @blockchain_api.route('/') 12 | @swag_from({ 13 | 'responses': { 14 | HTTPStatus.OK.value: { 15 | 'description': 'The blockchain as a list of blocks with all transactions.', 16 | 'schema': BlockchainSchema 17 | } 18 | } 19 | }) 20 | def get_chain(): 21 | """ 22 | Returns the full blockchain 23 | Returns blockchain as a list of blocks with all transactions. 24 | --- 25 | """ 26 | chain = blockchain.full_chain 27 | response = { 28 | 'blockchain': chain 29 | } 30 | 31 | return BlockchainSchema().dump(response), 200 32 | 33 | 34 | @blockchain_api.route('/block/') 35 | @swag_from({ 36 | 'parameters': [ 37 | { 38 | "name": "block_hash", 39 | "in": "path", 40 | "type": "string", 41 | "required": True, 42 | } 43 | ], 44 | 'responses': { 45 | HTTPStatus.OK.value: { 46 | 'description': 'The block with all its transactions.', 47 | 'schema': BlockSchema 48 | }, 49 | HTTPStatus.NOT_FOUND.value: { 50 | 'description': 'Block not found.' 51 | } 52 | } 53 | }) 54 | def get_block(block_hash): 55 | """ 56 | Returns the full blockchain 57 | Returns blockchain as a list of blocks with all transactions. 58 | --- 59 | """ 60 | blocks = [x for x in blockchain.full_chain if x.hash == block_hash] 61 | if len(blocks) > 0: 62 | return BlockSchema().dump(blocks[0]), 200 63 | 64 | abort(404) 65 | -------------------------------------------------------------------------------- /api/transaction.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, request 2 | from http import HTTPStatus 3 | from flasgger import swag_from 4 | from api.globals import blockchain 5 | from api.schema.transaction import TransactionSchema, TransactionCreatedSchema 6 | 7 | transaction_api = Blueprint('transaction', __name__) 8 | 9 | 10 | @transaction_api.route('/', methods=['POST']) 11 | @swag_from({ 12 | 'parameters': [ 13 | { 14 | "name": "transaction", 15 | "in": "body", 16 | "schema": { 17 | 'type': 'object', 18 | 'properties': { 19 | 'sender': { 20 | 'type': 'string' 21 | }, 22 | 'recipient': { 23 | 'type': 'string' 24 | }, 25 | 'amount': { 26 | 'type': 'number' 27 | } 28 | } 29 | }, 30 | "required": True, 31 | } 32 | ], 33 | 'responses': { 34 | HTTPStatus.CREATED.value: { 35 | 'description': 'The transaction was created and its pending to be included in a block.', 36 | 'schema': TransactionSchema 37 | }, 38 | HTTPStatus.BAD_REQUEST.value: { 39 | 'description': 'Invalid transaction' 40 | } 41 | } 42 | }, validation=True) 43 | def new_transaction(): 44 | """ 45 | Generates a new transaction 46 | Consolidates the pending transactions into a new block, and adds the block to the blockchain 47 | --- 48 | """ 49 | values = request.get_json() 50 | 51 | # Check that the required fields are in the body 52 | required = ['sender', 'recipient', 'amount'] 53 | if not all(k in values for k in required): 54 | response = { 55 | 'message': 'Invalid transaction' 56 | } 57 | 58 | return TransactionCreatedSchema().dumps(response), 400 59 | 60 | # Create a new Transaction 61 | transaction, valid = blockchain.create_transaction(values['sender'], values['recipient'], values['amount']) 62 | 63 | if valid: 64 | response = { 65 | 'message': "New transaction registered", 66 | 'transaction': transaction 67 | } 68 | return TransactionCreatedSchema().dumps(response), 201 69 | 70 | response = { 71 | 'message': 'Invalid transaction' 72 | } 73 | 74 | return TransactionCreatedSchema().dumps(response), 400 -------------------------------------------------------------------------------- /test/blockchain/test_blockchain.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from blockchain import Blockchain 3 | from blockchain.block import Block 4 | 5 | 6 | class TestBlockchain(TestCase): 7 | def test_mine_empty_transaction_block(self): 8 | """ 9 | Test mining an empty transaction block 10 | """ 11 | miner_address = 'miner_address' 12 | 13 | blockchain = Blockchain() 14 | block = blockchain.mine(miner_address) 15 | 16 | # First we look that a new block could be mined 17 | self.assertIsNotNone(block) 18 | 19 | # Let's see if the block was added to the chain 20 | self.assertEqual(blockchain.last_block.hash, block.hash) 21 | 22 | # We need to check that the block contains only the reward transaction 23 | self.assertEqual(len(block.transactions), 1) 24 | 25 | reward_transaction = block.transactions[0] 26 | 27 | # We make sure the reward function has no sender, and gives away exactly 1 coin 28 | self.assertEqual(reward_transaction.sender, '0') 29 | self.assertEqual(reward_transaction.recipient, miner_address) 30 | self.assertEqual(reward_transaction.amount, 1) 31 | 32 | def test_mine_simple_transaction_block(self): 33 | """ 34 | Test mining a simple transaction block 35 | """ 36 | miner_address = 'miner_address' 37 | 38 | blockchain = Blockchain() 39 | blockchain.create_transaction('sender', 'recipient', 1) 40 | blockchain.create_transaction('sender2', 'recipient2', 1.5) 41 | self.assertEqual(len(blockchain.pending_transactions), 2) 42 | 43 | block = blockchain.mine(miner_address) 44 | 45 | # First we look that a new block could be mined 46 | self.assertIsNotNone(block) 47 | 48 | # Let's see if the block was added to the chain 49 | self.assertEqual(blockchain.last_block.hash, block.hash) 50 | 51 | # We need to check that the transaction list is empty 52 | self.assertEqual(0, len(blockchain.pending_transactions)) 53 | 54 | # We need to check that the block contains all of the transactions 55 | self.assertEqual(3, len(block.transactions)) 56 | 57 | reward_transaction = block.transactions[-1] 58 | 59 | # We make sure the reward function has no sender, and gives away exactly 1 coin 60 | self.assertEqual('0', reward_transaction.sender) 61 | self.assertEqual(miner_address, reward_transaction.recipient) 62 | self.assertEqual(1, reward_transaction.amount) 63 | 64 | def test_validate_empty_chain(self): 65 | """ 66 | Test validating an empty chain 67 | """ 68 | miner_address = 'miner_address' 69 | 70 | blockchain = Blockchain() 71 | block = blockchain.mine(miner_address) 72 | 73 | self.assertTrue(blockchain.validate_chain(blockchain.full_chain)) 74 | 75 | def test_validate_chain_with_tempered_block_nonce(self): 76 | """ 77 | Test validating a chain with a tempered block nonce 78 | """ 79 | miner_address = 'miner_address' 80 | 81 | blockchain = Blockchain() 82 | last_block = blockchain.mine(miner_address) 83 | 84 | # First we look that a new block could be mined 85 | self.assertIsNotNone(last_block) 86 | 87 | chain = blockchain.full_chain 88 | 89 | # Hack a block 90 | chain.append(Block(1, [], 1, last_block.hash)) 91 | 92 | self.assertFalse(blockchain.validate_chain(blockchain.full_chain)) 93 | 94 | def test_replace_chain(self): 95 | """ 96 | Test that the chain is replaced for a larger one 97 | 98 | :return: 99 | """ 100 | import copy 101 | miner_address = 'miner_address' 102 | 103 | blockchain1 = Blockchain() 104 | blockchain1.mine(miner_address) 105 | 106 | blockchain2 = copy.deepcopy(blockchain1) 107 | blockchain2.mine(miner_address) 108 | 109 | # Now let's make sure that each blockchain has its own number of blocks 110 | self.assertEqual(2, len(blockchain1.full_chain)) 111 | self.assertEqual(3, len(blockchain2.full_chain)) 112 | 113 | # Then let's replace blockchain1 with blockchain2 114 | blockchain1.replace_chain(blockchain2.full_chain) 115 | 116 | self.assertEqual(3, len(blockchain1.full_chain)) 117 | self.assertEqual(3, len(blockchain2.full_chain)) 118 | self.assertEqual(blockchain1.last_block.hash, blockchain2.last_block.hash) 119 | 120 | def test_replace_chain_keep_original(self): 121 | """ 122 | Test that the chain is not replaced for a smaller one 123 | 124 | :return: 125 | """ 126 | import copy 127 | miner_address = 'miner_address' 128 | 129 | blockchain1 = Blockchain() 130 | blockchain1.mine(miner_address) 131 | 132 | blockchain2 = copy.deepcopy(blockchain1) 133 | blockchain1.mine(miner_address) 134 | 135 | # Now let's make sure that each blockchain has its own number of blocks 136 | self.assertEqual(3, len(blockchain1.full_chain)) 137 | self.assertEqual(2, len(blockchain2.full_chain)) 138 | 139 | # Then let's replace blockchain1 with blockchain2 140 | blockchain1.replace_chain(blockchain2.full_chain) 141 | 142 | self.assertEqual(3, len(blockchain1.full_chain)) 143 | self.assertEqual(2, len(blockchain2.full_chain)) 144 | 145 | 146 | -------------------------------------------------------------------------------- /blockchain/__init__.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | from blockchain.block import Block 3 | from blockchain.transaction import Transaction 4 | 5 | 6 | class Blockchain: 7 | 8 | def __init__(self): 9 | self.__current_transactions = [] 10 | self.__chain = [] 11 | # Create genesis block 12 | self.create_genesis() 13 | 14 | def create_genesis(self): 15 | """ 16 | Creates the Genesis block and passes it to the chain 17 | 18 | :return: None 19 | """ 20 | genesis_block = Block(0, self.__current_transactions, 0, '00') 21 | self.__chain.append(genesis_block) 22 | 23 | def add_block(self, block): 24 | """ 25 | Creates a new block and passes it to the chain 26 | 27 | :param block: Block to add to the chain 28 | :return: True if the block is added to the chain, False if not. 29 | """ 30 | if self.validate_block(block, self.last_block): 31 | self.__chain.append(block) 32 | 33 | # Remove transactions from the list 34 | self.__current_transactions = [] 35 | 36 | return True 37 | 38 | return False 39 | 40 | def create_transaction(self, sender, recipient, amount): 41 | """ 42 | Creates a new transaction to go into the next block 43 | 44 | :param sender: sender address 45 | :param recipient: recipient address 46 | :param amount: amount 47 | :return: generated transaction 48 | """ 49 | transaction = Transaction(sender, recipient, amount) 50 | 51 | if transaction.validate(): 52 | self.__current_transactions.append(transaction) 53 | 54 | return transaction, True 55 | 56 | return None, False 57 | 58 | def mine(self, reward_address): 59 | """ 60 | Mines a new block into the chain 61 | 62 | :param reward_address: address where the reward coin will be transferred to 63 | :return: result of the mining attempt and the new block 64 | """ 65 | last_block = self.last_block 66 | index = last_block.index + 1 67 | previous_hash = last_block.hash 68 | 69 | # Let's start with the heavy duty, generating the proof of work 70 | nonce = self.generate_proof_of_work(last_block) 71 | 72 | # In the next step we will create a new transaction to reward the miner 73 | # In this particular case, the miner will receive coins that are just "created", so there is no sender 74 | self.create_transaction( 75 | sender="0", 76 | recipient=reward_address, 77 | amount=1, 78 | ) 79 | 80 | # Add the block to the new chain 81 | block = Block(index, self.__current_transactions, nonce, previous_hash) 82 | 83 | if self.add_block(block): 84 | return block 85 | 86 | return None 87 | 88 | @staticmethod 89 | def validate_proof_of_work(last_nonce, last_hash, nonce): 90 | """ 91 | Validates the nonce 92 | 93 | :param last_nonce: Nonce of the last block 94 | :param nonce: Current nonce to be validated 95 | :param last_hash: Hash of the last block 96 | :return: True if correct, False if not. 97 | """ 98 | sha = hashlib.sha256(f'{last_nonce}{last_hash}{nonce}'.encode()) 99 | return sha.hexdigest()[:4] == '0000' 100 | 101 | def generate_proof_of_work(self, block): 102 | """ 103 | Very simple proof of work algorithm: 104 | 105 | - Find a number 'p' such that hash(pp') contains 4 leading zeroes 106 | - Where p is the previous proof, and p' is the new proof 107 | 108 | :param block: reference to the last block object 109 | :return: generated nonce 110 | """ 111 | last_nonce = block.nonce 112 | last_hash = block.hash 113 | 114 | nonce = 0 115 | while not self.validate_proof_of_work(last_nonce, last_hash, nonce): 116 | nonce += 1 117 | 118 | return nonce 119 | 120 | def validate_block(self, current_block, previous_block): 121 | """ 122 | Validates a block with reference to its previous 123 | 124 | :param current_block: 125 | :param previous_block: 126 | :return: 127 | """ 128 | # Check the block index 129 | if current_block.index != previous_block.index + 1: 130 | return False 131 | 132 | if current_block.previous_hash != previous_block.hash: 133 | return False 134 | 135 | if current_block.hash != current_block.hash_block(): 136 | return False 137 | 138 | if not self.validate_proof_of_work(previous_block.nonce, previous_block.hash, current_block.nonce): 139 | return False 140 | 141 | return True 142 | 143 | def validate_chain(self, chain_to_validate): 144 | """ 145 | Verifies if a given chain is valid 146 | 147 | :param chain_to_validate: <[Block]> 148 | :return: True if the chain is valid 149 | """ 150 | # First validate both genesis blocks 151 | if chain_to_validate[0].hash_block() != self.__chain[0].hash_block(): 152 | return False 153 | 154 | # Then we compare each block with its previous one 155 | for x in range(1, len(chain_to_validate)): 156 | if not self.validate_block(chain_to_validate[x], chain_to_validate[x - 1]): 157 | return False 158 | 159 | return True 160 | 161 | def replace_chain(self, new_chain): 162 | """ 163 | Attempts to replace the chain for a new one 164 | 165 | :param new_chain: 166 | :return: True if the chain was replace, False if not. 167 | """ 168 | # We only replace if the new chain is bigger than the current one 169 | if len(new_chain) <= len(self.__chain): 170 | return False 171 | 172 | # Validate the new chain 173 | if not self.validate_chain(new_chain): 174 | return False 175 | 176 | new_blocks = new_chain[len(self.__chain):] 177 | for block in new_blocks: 178 | self.add_block(block) 179 | 180 | @property 181 | def last_block(self): 182 | return self.__chain[-1] 183 | 184 | @property 185 | def last_transaction(self): 186 | return self.__current_transactions[-1] 187 | 188 | @property 189 | def pending_transactions(self): 190 | return self.__current_transactions 191 | 192 | @property 193 | def full_chain(self): 194 | return self.__chain 195 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "4e075ea2b49c6d4bb71df081ace4fa7569218d0693dec0e03e25817e1deca37f" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.8" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.python.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "apispec": { 20 | "hashes": [ 21 | "sha256:419d0564b899e182c2af50483ea074db8cb05fee60838be58bb4542095d5c08d", 22 | "sha256:9bf4e51d56c9067c60668b78210ae213894f060f85593dc2ad8805eb7d875a2a" 23 | ], 24 | "index": "pypi", 25 | "version": "==3.3.0" 26 | }, 27 | "attrs": { 28 | "hashes": [ 29 | "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", 30 | "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" 31 | ], 32 | "version": "==19.3.0" 33 | }, 34 | "certifi": { 35 | "hashes": [ 36 | "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304", 37 | "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519" 38 | ], 39 | "version": "==2020.4.5.1" 40 | }, 41 | "chardet": { 42 | "hashes": [ 43 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", 44 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" 45 | ], 46 | "version": "==3.0.4" 47 | }, 48 | "click": { 49 | "hashes": [ 50 | "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", 51 | "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" 52 | ], 53 | "version": "==7.1.2" 54 | }, 55 | "flasgger": { 56 | "hashes": [ 57 | "sha256:37137b3292738580c42e03662bfb8731656a11d636e76f76d30e572c1fa5bd0d", 58 | "sha256:7c187f7a7caeb42645f7de652335b794375925467407e40322620fb9d401b38c" 59 | ], 60 | "index": "pypi", 61 | "version": "==0.9.4" 62 | }, 63 | "flask": { 64 | "hashes": [ 65 | "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", 66 | "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" 67 | ], 68 | "index": "pypi", 69 | "version": "==1.1.2" 70 | }, 71 | "flask-marshmallow": { 72 | "hashes": [ 73 | "sha256:6e6aec171b8e092e0eafaf035ff5b8637bf3a58ab46f568c4c1bab02f2a3c196", 74 | "sha256:a1685536e7ab5abdc712bbc1ac1a6b0b50951a368502f7985e7d1c27b3c21e59" 75 | ], 76 | "index": "pypi", 77 | "version": "==0.12.0" 78 | }, 79 | "idna": { 80 | "hashes": [ 81 | "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", 82 | "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" 83 | ], 84 | "version": "==2.9" 85 | }, 86 | "itsdangerous": { 87 | "hashes": [ 88 | "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", 89 | "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" 90 | ], 91 | "version": "==1.1.0" 92 | }, 93 | "jinja2": { 94 | "hashes": [ 95 | "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", 96 | "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" 97 | ], 98 | "version": "==2.11.2" 99 | }, 100 | "jsonschema": { 101 | "hashes": [ 102 | "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163", 103 | "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a" 104 | ], 105 | "version": "==3.2.0" 106 | }, 107 | "markupsafe": { 108 | "hashes": [ 109 | "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", 110 | "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", 111 | "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", 112 | "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", 113 | "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", 114 | "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", 115 | "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", 116 | "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", 117 | "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", 118 | "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", 119 | "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", 120 | "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", 121 | "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", 122 | "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", 123 | "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", 124 | "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", 125 | "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", 126 | "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", 127 | "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", 128 | "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", 129 | "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", 130 | "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", 131 | "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", 132 | "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", 133 | "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", 134 | "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", 135 | "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", 136 | "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", 137 | "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", 138 | "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", 139 | "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", 140 | "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", 141 | "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" 142 | ], 143 | "version": "==1.1.1" 144 | }, 145 | "marshmallow": { 146 | "hashes": [ 147 | "sha256:c2673233aa21dde264b84349dc2fd1dce5f30ed724a0a00e75426734de5b84ab", 148 | "sha256:f88fe96434b1f0f476d54224d59333eba8ca1a203a2695683c1855675c4049a7" 149 | ], 150 | "version": "==3.6.0" 151 | }, 152 | "mistune": { 153 | "hashes": [ 154 | "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e", 155 | "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4" 156 | ], 157 | "version": "==0.8.4" 158 | }, 159 | "pyrsistent": { 160 | "hashes": [ 161 | "sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3" 162 | ], 163 | "version": "==0.16.0" 164 | }, 165 | "pyyaml": { 166 | "hashes": [ 167 | "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", 168 | "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", 169 | "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", 170 | "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", 171 | "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", 172 | "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", 173 | "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", 174 | "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", 175 | "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", 176 | "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", 177 | "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" 178 | ], 179 | "version": "==5.3.1" 180 | }, 181 | "requests": { 182 | "hashes": [ 183 | "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", 184 | "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" 185 | ], 186 | "index": "pypi", 187 | "version": "==2.23.0" 188 | }, 189 | "six": { 190 | "hashes": [ 191 | "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", 192 | "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" 193 | ], 194 | "version": "==1.14.0" 195 | }, 196 | "urllib3": { 197 | "hashes": [ 198 | "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", 199 | "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" 200 | ], 201 | "version": "==1.25.9" 202 | }, 203 | "werkzeug": { 204 | "hashes": [ 205 | "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", 206 | "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" 207 | ], 208 | "version": "==1.0.1" 209 | } 210 | }, 211 | "develop": {} 212 | } 213 | --------------------------------------------------------------------------------