Created
October 9, 2024 12:06
-
-
Save scopalaffairs/bec6df785a6f75c9daf5076630aa4437 to your computer and use it in GitHub Desktop.
Blockchain from Scratch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import json | |
| import sys | |
| import time | |
| from hashlib import sha256 | |
| import requests | |
| from flask import Flask, request | |
| class Block: | |
| def __init__(self, index, transactions, timestamp, previous_hash): | |
| self.index = index | |
| self.transactions = transactions | |
| self.timestamp = timestamp | |
| self.previous_hash = previous_hash | |
| self.nonce = 0 | |
| def compute_hash(self): | |
| """ | |
| A function that return the hash of the block contents. | |
| """ | |
| block_string = json.dumps(self.__dict__, sort_keys=True) | |
| return sha256(block_string.encode()).hexdigest() | |
| class Blockchain: | |
| # difficulty of our PoW algorithm | |
| difficulty = 0 | |
| def __init__(self): | |
| self.unconfirmed_transactions = [] | |
| self.chain = [] | |
| self.create_genesis_block() | |
| def create_genesis_block(self): | |
| """ | |
| A function to generate genesis block and appends it to | |
| the chain. The block has index 0, previous_hash as 0, and | |
| a valid hash. | |
| """ | |
| genesis_block = Block(0, [], 0, "0") | |
| genesis_block.hash = genesis_block.compute_hash() | |
| self.chain.append(genesis_block) | |
| @property | |
| def last_block(self): | |
| return self.chain[-1] | |
| def add_block(self, block, proof): | |
| """ | |
| A function that adds the block to the chain after verification. | |
| Verification includes: | |
| * Checking if the proof is valid. | |
| * The previous_hash referred in the block and the hash of latest block | |
| in the chain match. | |
| """ | |
| previous_hash = self.last_block.hash | |
| print(self.last_block.hash) | |
| print(type(previous_hash)) | |
| print(type(block.previous_hash)) | |
| if previous_hash != block.previous_hash: | |
| print("PREV HASH", block.previous_hash) | |
| return False | |
| if not Blockchain.is_valid_proof(block, proof): | |
| print("NOT VALID PROOF", proof) | |
| return False | |
| block.hash = proof | |
| print("chain", block) | |
| self.chain.append(block) | |
| return True | |
| def proof_of_work(self, block): | |
| """ | |
| Function that tries different values of nonce to get a hash | |
| that satisfies our difficulty criteria. | |
| """ | |
| block.nonce = 0 | |
| computed_hash = block.compute_hash() | |
| while not computed_hash.startswith('0' * Blockchain.difficulty): | |
| block.nonce += 1 | |
| computed_hash = block.compute_hash() | |
| return computed_hash | |
| def add_new_transaction(self, transaction): | |
| self.unconfirmed_transactions.append(transaction) | |
| @classmethod | |
| def is_valid_proof(cls, block, block_hash): | |
| """ | |
| Check if block_hash is valid hash of block and satisfies | |
| the difficulty criteria. | |
| """ | |
| print("computehash", block.compute_hash(), json.dumps(block.__dict__)) | |
| print("block_hash", block_hash) | |
| return (block_hash.startswith('0' * Blockchain.difficulty) and | |
| block_hash == block.compute_hash()) | |
| @classmethod | |
| def check_chain_validity(cls, chain): | |
| result = True | |
| previous_hash = "0" | |
| for block in chain: | |
| block_hash = block.hash | |
| # remove the hash field to recompute the hash again | |
| # using `compute_hash` method. | |
| delattr(block, "hash") | |
| if not cls.is_valid_proof(block, block.hash) or \ | |
| previous_hash != block.previous_hash: | |
| result = False | |
| break | |
| block.hash, previous_hash = block_hash, block_hash | |
| return result | |
| def mine(self): | |
| """ | |
| This function serves as an interface to add the pending | |
| transactions to the blockchain by adding them to the block | |
| and figuring out Proof Of Work. | |
| """ | |
| if not self.unconfirmed_transactions: | |
| return False | |
| last_block = self.last_block | |
| new_block = Block(index=last_block.index + 1, | |
| transactions=self.unconfirmed_transactions, | |
| timestamp=time.time(), | |
| previous_hash=last_block.hash) | |
| proof = self.proof_of_work(new_block) | |
| self.add_block(new_block, proof) | |
| self.unconfirmed_transactions = [] | |
| # announce it to the network | |
| announce_new_block(new_block) | |
| print(new_block.index) | |
| return new_block.index | |
| app = Flask(__name__) | |
| # the node's copy of blockchain | |
| blockchain = Blockchain() | |
| # the address to other participating members of the network | |
| peers = set() | |
| # endpoint to submit a new transaction. This will be used by | |
| # our application to add new data (posts) to the blockchain | |
| @app.route('/new_transaction', methods=['POST']) | |
| def new_transaction(): | |
| tx_data = request.get_json() | |
| required_fields = ["author", "content"] | |
| for field in required_fields: | |
| if not tx_data.get(field): | |
| return "Invalid transaction data", 404 | |
| tx_data["timestamp"] = time.time() | |
| blockchain.add_new_transaction(tx_data) | |
| return "Success", 201 | |
| @app.route('/patients', methods=['GET']) | |
| def get_patients(): | |
| userprofile = "[{\"firstName\":\"Markus\",\"lastName\":\"Fischer\",\ | |
| \"birthdate\":\"11.08.1995\",\"gender\":\"male\",\"patientId\":1}]" | |
| return userprofile, 200 | |
| @app.route('/getdata', methods=['GET']) | |
| def get_data(): | |
| get_chain() | |
| return 200 | |
| @app.route('/provideData', methods=['POST']) | |
| def provide_data(): | |
| author = "1" | |
| tx_data = {"content": {"fileName": "test.json", "fileHash": 1234}, "author": 1, "timestamp": time.time()} | |
| blockchain.add_new_transaction(tx_data) | |
| return "Success", 201 | |
| # endpoint to return the node's copy of the chain. | |
| # Our application will be using this endpoint to query | |
| # all the posts to display. | |
| @app.route('/chain', methods=['GET']) | |
| def get_chain(): | |
| # make sure we've the longest chain | |
| consensus() | |
| chain_data = [] | |
| for block in blockchain.chain: | |
| chain_data.append(block.__dict__) | |
| return json.dumps({"length": len(chain_data), | |
| "chain": chain_data}) | |
| # endpoint to request the node to mine the unconfirmed | |
| # transactions (if any). We'll be using it to initiate | |
| # a command to mine from our application itself. | |
| @app.route('/mine', methods=['GET']) | |
| def mine_unconfirmed_transactions(): | |
| result = blockchain.mine() | |
| if not result: | |
| return "No transactions to mine" | |
| return "Block #{} is mined.".format(result) | |
| # endpoint to add new peers to the network. | |
| @app.route('/add_nodes', methods=['POST']) | |
| def register_new_peers(): | |
| nodes = request.get_json() | |
| print(nodes) | |
| if not nodes: | |
| return "Invalid data", 400 | |
| for node in nodes: | |
| peers.add(node) | |
| print(nodes) | |
| return "Success", 201 | |
| # endpoint to add a block mined by someone else to | |
| # the node's chain. The block is first verified by the node | |
| # and then added to the chain. | |
| @app.route('/add_block', methods=['POST']) | |
| def validate_and_add_block(): | |
| block_data = request.get_json() | |
| print(block_data) | |
| block = Block(block_data["index"], | |
| block_data["transactions"], | |
| block_data["timestamp"], | |
| block_data["previous_hash"]) | |
| proof = block_data['hash'] | |
| added = blockchain.add_block(block, proof) | |
| if not added: | |
| return "The block was discarded by the node", 400 | |
| return "Block added to the chain", 201 | |
| # endpoint to query unconfirmed transactions | |
| @app.route('/pending_tx') | |
| def get_pending_tx(): | |
| return json.dumps(blockchain.unconfirmed_transactions) | |
| def consensus(): | |
| """ | |
| Our simple consnsus algorithm. If a longer valid chain is | |
| found, our chain is replaced with it. | |
| """ | |
| global blockchain | |
| longest_chain = None | |
| current_len = len(blockchain.chain) | |
| for node in peers: | |
| response = requests.get('http://{}/chain'.format(node)) | |
| length = response.json()['length'] | |
| chain = response.json()['chain'] | |
| if length > current_len and blockchain.check_chain_validity(chain): | |
| current_len = length | |
| longest_chain = chain | |
| if longest_chain: | |
| blockchain = longest_chain | |
| return True | |
| return False | |
| def announce_new_block(block): | |
| """ | |
| A function to announce to the network once a block has been mined. | |
| Other blocks can simply verify the proof of work and add it to their | |
| respective chains. | |
| """ | |
| for peer in peers: | |
| url = "http://{}/add_block".format(peer) | |
| headers = {'Content-type': 'application/json', 'Accept': 'text/plain'} | |
| requests.post(url, data=json.dumps( | |
| block.__dict__, sort_keys=True), headers=headers) | |
| print(json.dumps(block.__dict__)) | |
| app.run(host="0.0.0.0", debug=True, port=int(sys.argv[1])) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment