├── .gitignore ├── LICENSE ├── README.md ├── app ├── __init__.py ├── static │ └── stylesheet.css ├── templates │ ├── base.html │ └── index.html └── view.py ├── node_server.py ├── run_app.py └── screenshots └── blocknet-home.png /.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 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 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 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Matthew Muccio 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | BlockNet 2 | =================== 3 | A simple, fully-functional, decentralized content sharing web application, which implements a public blockchain from scratch, and the Flask web framework with Jinja2 templating. 4 | 5 | ![blocknet.png](https://github.com/matthewmuccio/BlockNet/raw/master/screenshots/blocknet-home.png) 6 | 7 | Project 8 | ------- 9 | - Objective: to build a simple web application using a public blockchain that allows users to share information over a decentralized network. 10 | - Because the content will be stored on the blockchain, it is immutable and permanent (more below). 11 | - An explicit definition of the structure of the data (posts) that will be stored on the blockchain: 12 | - A post is a message posted by any user on the web application, it must have three properties: content, author, and timestamp. 13 | 14 | Process 15 | ------- 16 | - Using the Flask web microframework, create endpoints for different functions of the blockchain, such as adding a transaction. 17 | - Then, run the scripts on multiple machines in order to create a decentralized network. 18 | - Build a simple UI with Flask and Jinja2 templating that interacts with the blockchain and stores information for any use case. 19 | - For instance, content sharing, P2P payments, chatting, or e-commerce. 20 | 21 | Background 22 | ---------- 23 | Public blockchain 24 | - A public blockchain network is completely decentralized and open to the public. 25 | - No one entity has control over the network and they are secure in that data cannot be changed once validated on the blockchain. 26 | - Anyone can join and participate. 27 | - Examples: Bitcoin, Ethereum 28 | 29 | Private blockchain 30 | - A private blockchain network is primarily used by businesses who need greater privacy, security, and speed of transactions. 31 | - Participants need an invitation to join. 32 | - They operate quite similarly to public blockchains but have access controls that limit who can participate in the network. 33 | - It operates like modern centralized database systems that restrict access to certain users. 34 | - One or more entities control the network. 35 | - Causes users to still have to rely on third-parties to transact. 36 | - Example: Ripple, Hyperledger 37 | 38 | Brief History of Bitcoin 39 | - In 2008, a whitepaper was released by an individual or group under the identifier Satoshi Nakamoto. 40 | - Titled "Bitcoin: A Peer-to-Peer Electronic Cash System." 41 | - The paper combined cryptographic techniques and a peer-to-peer network without the need to trust a centralized authority to make payments from one person to another. 42 | - It also introduced a distributed system of storing data (blockchain). 43 | - We all now know this concept has far wider applicability than just payments, or cryptocurrencies. 44 | - Blockchain technology has exploded across nearly every industry. 45 | - It is now the underlying technology behind: 46 | - Fully digital cryptocurrencies (i.e., Bitcoin) 47 | - Distributed computing technologies (i.e., Ethereum) 48 | - Open-source frameworks (i.e., Hyperledger Fabric) 49 | 50 | Blockchain Basics 51 | ----------------- 52 | Blockchain Technology 53 | - In simplest terms, blockchain is a mechanism for storing digital data. 54 | - The data can literally be anything. 55 | - The data can even be files, it doesn't matter. 56 | - In the case of Bitcoin, it is the transactions (transfers of Bitcoin from one account to another). 57 | - The data is stored in the form of blocks, which are chained together using hashes. 58 | - Storing data in BLOCKs + using hashes to CHAIN them together = blockchain 59 | 60 | Characteristics of Blockchain Networks 61 | - All of the "magic" in blockchain comes from the way this data is added and stored in the blockchain. 62 | - This yields some highly desirable and powerful characteristics: 63 | - Immutability of history 64 | - Un-hackability of the system 65 | - Persistence of the data 66 | - No single point of failure 67 | 68 | Development 69 | ----------- 70 | 1. Store transactions into blocks 71 | - I will be storing the data in JSON, a widely-used format. 72 | - The generic term "data" is often used interchangeably with the term "transactions" on the Internet. 73 | - The transactions in the application are packed into blocks. 74 | - A block can contain one or many transactions. 75 | - The blocks containing the transactions are generated frequently and added to the blockchain. 76 | - Each block will have a unique ID, since there can be multiple blocks. 77 | 78 | 2. Make the blocks immutable 79 | - I want to detect any kind of tampering in the data stored inside the block. 80 | - In blockchain technology, this is accomplished using a hash function. 81 | - It is a function that takes data of any size and produces data of a fixed sizse from it, which generally works to identify the input. 82 | - The Python Standard Library has a hashlib library with a SHA-256 and SHA-512 hashing function. 83 | - The characteristics of an ideal hash function are: 84 | - It should be computationally easy to compute. 85 | - Even a single bit change in data should make the hash change altogether. 86 | - It should not be possible to guess the input from the output hash. 87 | - I will store the hash of every block in a field inside a Block object to act like a digital fingerprint of data contained in it. 88 | - Note: In most cryptocurrencies, the individual transactions in the block are also hashed, to form a hash tree, and the root of the tree might be used as the hash of the block. 89 | - However, it is not a necessary requirement for the functioning of the blockchain. 90 | 91 | 3. Chain the blocks 92 | - The blocks themselves are now set up. 93 | - The blockchain is a collection of blocks, and I must implement it accordingly. 94 | - I could store all of the blocks in a list (array) in Python, but it would not work. 95 | - It is not sufficient. 96 | - Someone could intentionally replace a block at a previous index in the collection/list. 97 | - In the current (unfinished) implementation, creating a new block with altered transactions, computing the hash, and replacing it with any older block works and it should not. 98 | - I must maintain the immutability and order of the blocks in some way. 99 | - I need a way to ensure that any change in the past blocks invalidates the entire chain. 100 | - One way to do this is to chain the blocks by the hash. 101 | - By chaining, I mean to include the hash of the previous block in the current block. 102 | - If the content of any of the previous blocks change, the hash of the block would change, which would lead to a mismatch with the previous_hash field in the next block. 103 | - If every block will be linked to the previous block by the previous_hash field, I must manually generate the very first block ourselves. 104 | - The very first block is called the genesis block, and it is generated manually or by some unique logic, in most cases. 105 | 106 | 4. Implementing a proof of work algorithm 107 | - Selective endorsement vs. proof of work 108 | - Consensus in a (private) blockchain for business is not achieved through mining, but through a process called selective endorsement. 109 | - The network members control exactly who verifies transactions, much in the same way that business happens today. 110 | - A problem arises: if I change the previous block, I can re-compute the hashes of all the following blocks quite easily and create a different valid blockchain. 111 | - To prevent this, I must make the task of calculating the hash difficult and random. 112 | - Instead of accepting any hash for the block, I will add some constraint to it. 113 | - Let's add a constrant that the hash should start with a certain number of leading zeroes. 114 | - I also know that unless I change the contents of the block, the hash will not change. 115 | - I will introduce a new field in the Block, a nonce. 116 | - A nonce is a number that will continue to change until there is a hash that satisifes the constraint. 117 | - The number of leading zeroes, which will default to 2, decides the difficulty of the PoW algorithm. 118 | - This PoW algorithm is difficult to compute but easy to verify once I figure out the nonce. 119 | - Verifying will just involve running the hash function again. 120 | 121 | 5. Adding blocks to the chain, and mining 122 | - In order to add blocks to the chain, I must first verify two components: 123 | - The PoW that is provided is correct. 124 | - The previous_hash field of the block to be added points to the hash of the latest block in the chain. 125 | - At this point, I must implement a mechanism for mining the blocks. 126 | - The transactions are initially stored in a pool of unconfirmed transactions. 127 | - The process of putting the unconfirmed transactions in a block and computing PoW is known as mining the blocks. 128 | - Once the nonce satisfying the constraints is figured out, I can say that a block has been mined. 129 | - At that point, the block is put into the blockchain. 130 | - In most cryptocurrencies, miners may be awarded some cryptocurrency as a reward for spending their computing power to compute PoW. 131 | 132 | 6. Creating interfaces for the Flask web app 133 | - I must create interfaces for the node to interact with other peers as well as with the application. 134 | - I will be building it with the Flask web framework to create a REST-API to interact with the node. 135 | - I need an endpoint for the app to submit a new transaction. 136 | - It will be used by the app to add new data (posts) to the blockchain. 137 | - I also need an edpoint to return the node's copy of the chain. 138 | - It will be used to query all of the posts to display to the user. 139 | - I also need an endpoint to request the node the mine the unconfirmed transactions (if any). 140 | - It will be used to initiate a command to mine from the app itself. 141 | - I also will add an endpoint to query the unconfirmed transactions. 142 | - At this point, I have a functioning blockchain, where I can create new transactions (posts), and mine them to add them to the blockchain. 143 | - However, the codebase at this point is meant to run on a single computer. 144 | - I will need to add functionality to have multiple nodes to maintain the blockchain. 145 | 146 | 7. Establishing consensus and decentralization 147 | - Even though I am linking blocks with hashes, still cannot trust a single entity. 148 | - I will need multiple nodes to maintain the blockchain. 149 | - I must create an endpoint to let a node know of other peers in the network. 150 | - I must also create an endpoint to add new peers to the network. 151 | - There is a problem with multiple nodes. 152 | - Due to intentional manipulation or unintentional reasons, the copy of chains of a few nodes can differ. 153 | - In that case, there must be an agreement upon some version of the chain. 154 | - This is known as consensus, which must be achieved to maintain the integrity of the entire system 155 | - A simple consensus algorithm could be to agree upon the longest valid chain when the chains of different participants in the network appear to diverge. 156 | - The rationale behind this approach is that the longest chain is a good estimate of the most amount of work done. 157 | - I also need to develop a way for any node to announce to the network that it has mined a block so that everyone can update their blockchain, and move on to mine other transactions. 158 | - This involves creating another endpoint to add a block mined by a user to the node's chain. 159 | - After every block is mined by the node, it should be announced, so that peers can then add it to their chains. 160 | - Other nodes can simply verify the proof of work and add it to their respective chains. 161 | 162 | 8. Building the application 163 | - At this point, the backend is all set up. 164 | - I'll build an interface for the application, which is a view in the codebase. 165 | - Using Flask, I'll use Jinja2 templates to render the web pages and some CSS for styling. 166 | - The application needs to connect to a node in the blockchain network to fetch the data and submit new data. 167 | - There can also be multiple nodes. 168 | - The application has an HTML form to take user input, and then makes a POST request to a connected node to add the transaction into the unconfirmed transactions pool. 169 | - The transaction is then mined by the network, and then finally will be fetched once the website is refreshed. 170 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | from flask import Flask 5 | 6 | app = Flask(__name__) 7 | 8 | from app import view 9 | -------------------------------------------------------------------------------- /app/static/stylesheet.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Rubik"); 2 | @import url("https://fonts.googleapis.com/css?family=Rubik+Mono+One"); 3 | 4 | /* My styling. */ 5 | html { 6 | font-size: 62.5%; 7 | height: 100%; 8 | } 9 | 10 | body { 11 | font-family: "Rubik", sans-serif; 12 | background-color: #ecf0f1; 13 | margin: 0; 14 | padding: 0; 15 | min-height: 100%; 16 | position: relative; 17 | } 18 | 19 | /* Header and navigation bar styling. */ 20 | .navbar { 21 | margin: 0; 22 | padding: 0; 23 | overflow: hidden; 24 | background-color: #3498db; 25 | position: fixed; 26 | width: 100%; 27 | top: 0; 28 | left: 0; 29 | right: 0; 30 | } 31 | 32 | .navbar-item { 33 | display: block; 34 | color: #ffffff; 35 | text-align: center; 36 | padding: 2rem 2rem; 37 | text-decoration: none; 38 | font-size: 1.6rem; 39 | -webkit-transition-duration: 0.3s; 40 | transition-duration: 0.3s; 41 | } 42 | 43 | .navbar-item:hover { 44 | background-color: #2980b9; 45 | } 46 | 47 | .left { 48 | float: left; 49 | } 50 | 51 | .right { 52 | float: right; 53 | } 54 | 55 | .active-link { 56 | background-color: #2980b9; 57 | } 58 | 59 | /* Title, subtitle, and horizontal line. */ 60 | .title { 61 | margin-top: 9rem; 62 | text-align: center; 63 | line-height: 1.6rem; 64 | } 65 | 66 | .title-text { 67 | font-family: "Rubik Mono One", sans-serif; 68 | font-size: 4rem; 69 | } 70 | 71 | .subtitle-text { 72 | font-size: 2.4rem; 73 | } 74 | 75 | .hr { 76 | border-style: solid; 77 | border-width: 0.05rem; 78 | border-color: #7f8c8d; 79 | width: 55rem; 80 | } 81 | 82 | /* Buttons, post text area, name input, and body styling. */ 83 | .content { 84 | min-height: 100%; 85 | height: 100%; 86 | } 87 | 88 | .btn { 89 | font-family: "Rubik", sans-serif; 90 | font-size: 1.4rem; 91 | color: white; 92 | background-color: #3498db; 93 | padding: 1rem; 94 | border-radius: 0.4rem; 95 | border: none; 96 | -webkit-transition-duration: 0.3s; 97 | transition-duration: 0.3s; 98 | } 99 | 100 | .btn:hover { 101 | background-color: #2980b9; 102 | cursor: pointer; 103 | } 104 | 105 | .post-textarea, .name-input { 106 | font-family: "Rubik", sans-serif; 107 | font-size: 1.4rem; 108 | padding: 1rem; 109 | border-radius: 0.4rem; 110 | border-width: 0.1rem; 111 | border-style: solid; 112 | border-color: #7f8c8d; 113 | } 114 | 115 | .posts { 116 | margin: 2rem 2rem 14rem 2rem; 117 | } 118 | 119 | /* Footer styling. */ 120 | .footer { 121 | height: 12rem; 122 | line-height: 1rem; 123 | overflow: hidden; 124 | background-color: #3498db; 125 | width: 100%; 126 | padding: 0; 127 | margin: 0; 128 | position: fixed; 129 | left: 0; 130 | right: 0; 131 | bottom: 0; 132 | } 133 | 134 | .footer-item { 135 | display: block; 136 | margin-top: 3rem; 137 | color: #ffffff; 138 | text-align: center; 139 | text-decoration: none; 140 | font-size: 2rem; 141 | } 142 | 143 | .footer-item a { 144 | text-decoration: none; 145 | color: #ffffff; 146 | -webkit-transition-duration: 0.3s; 147 | transition-duration: 0.3s; 148 | } 149 | 150 | .footer-item a:hover { 151 | color: #2980b9; 152 | } 153 | 154 | /* Font Awesome social media icons (on footer) styling. */ 155 | .social-icon-list { 156 | list-style-type: none; 157 | text-align: center; 158 | margin-right: 3.5rem; 159 | } 160 | 161 | .social-icon { 162 | font-size: 3rem; 163 | display: inline; 164 | margin: 0 1rem 0 1rem; 165 | text-decoration: none; 166 | } 167 | 168 | .white-icon { 169 | color: #ffffff; 170 | -webkit-transition-duration: 0.3s; 171 | transition-duration: 0.3s; 172 | } 173 | 174 | .white-icon:hover { 175 | color: #2980b9; 176 | } 177 | 178 | /* Post boxes styling */ 179 | .post_box { 180 | background: #fff; 181 | padding: 1.2rem 0 0 1.2rem; 182 | margin-top: 0; 183 | margin-bottom: 0.8rem; 184 | border: 0.1rem solid #7f8c8d; 185 | } 186 | 187 | .post_box-header { 188 | padding-bottom: 1.2rem; 189 | font-size: 1.4rem; 190 | } 191 | 192 | .post_box-avatar { 193 | width: 3.8rem; 194 | height: 3.8rem; 195 | border-radius: 50%; 196 | display: flex; 197 | justify-content: center; 198 | align-items: center; 199 | color: white; 200 | font-size: 2.2rem; 201 | float: left; 202 | margin-right: 1.6rem; 203 | border: 0.1rem solid #fff; 204 | box-shadow: 0 0 0 0.2rem #f00; 205 | } 206 | 207 | .post_box-avatar::after { 208 | content:""; 209 | display:block; 210 | } 211 | 212 | .post_box-name { 213 | font-weight: bold; 214 | } 215 | 216 | .post_box-subtitle { 217 | color: #777; 218 | } 219 | 220 | .post_box-body { 221 | margin-top: 1.6rem; 222 | margin-bottom: 0.8rem; 223 | font-size: 1.4rem; 224 | } 225 | 226 | .post_box-options { 227 | float: right; 228 | } 229 | 230 | .option-btn { 231 | background: #f8f8f8; 232 | border: none; 233 | color: #2c3e50; 234 | padding: 0.7rem; 235 | cursor: pointer; 236 | font-size: 1.4rem; 237 | margin-left: 0.2rem; 238 | margin-right: 0.2rem; 239 | outline: none; 240 | height: 4.2rem; 241 | } 242 | -------------------------------------------------------------------------------- /app/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ title }} - {{ subtitle }} 5 | 6 | 7 | 8 | 9 | 15 |
16 |

{{ title }}

17 |

{{ subtitle }}

18 |
19 |
20 | {% with messages = get_flashed_messages() %} 21 | {% if messages %} 22 | 27 | {% endif %} 28 | {% endwith %} 29 | {% block content %}{% endblock %} 30 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | 4 | {% block content %} 5 |
6 |
7 |
8 |
9 | 10 |

11 | 12 | 13 |
14 |
15 |
16 | 17 |
18 | {% for post in posts %} 19 |
20 |
21 | 22 |
{{post.author[0]}}
23 |
{{post.author}}
24 |
Posted at {{readable_time(post.timestamp)}}
25 |
26 |
27 |
28 |

{{post.content}}

29 |
30 |
31 |
32 | {% endfor %} 33 |
34 |
35 | {% endblock %} 36 | -------------------------------------------------------------------------------- /app/view.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | import datetime 5 | import json 6 | 7 | import requests 8 | from flask import render_template, redirect, request 9 | 10 | from app import app 11 | 12 | 13 | # Stores the node's address. 14 | CONNECTED_NODE_ADDRESS = "http://127.0.0.1:8000" 15 | # Stores all the posts in the node. 16 | posts = [] 17 | 18 | # Gets the data from node's /chain endpoint, parses the data, and stores it locally. 19 | def fetch_posts(): 20 | get_chain_address = "{0}/chain".format(CONNECTED_NODE_ADDRESS) 21 | response = requests.get(get_chain_address) 22 | if response.status_code == 200: 23 | content = [] 24 | chain = json.loads(response.content.decode("utf-8")) 25 | for block in chain["chain"]: 26 | for tx in block["transactions"]: 27 | tx["index"] = block["index"] 28 | tx["hash"] = block["previous_hash"] 29 | content.append(tx) 30 | global posts 31 | posts = sorted(content, key=lambda k: k["timestamp"], reverse=True) 32 | 33 | # Creates a new endpoint, and binds the function to the URL. 34 | @app.route("/") 35 | # Renders the index.html (home page). 36 | def index(): 37 | fetch_posts() 38 | return render_template("index.html", \ 39 | title="BlockNet", \ 40 | subtitle="A Decentralized Network for Content Sharing", \ 41 | posts=posts, \ 42 | node_address=CONNECTED_NODE_ADDRESS, \ 43 | readable_time=timestamp_to_string) 44 | 45 | # Creates a new endpoint, and binds the function to the URL. 46 | @app.route("/submit", methods=["POST"]) 47 | # The endpoint to create a new tranaction. 48 | def submit_textarea(): 49 | post_content = request.form["content"] 50 | author = request.form["author"] 51 | post_object = { 52 | "author" : author, 53 | "content" : post_content, 54 | } 55 | # Submit a new transaction. 56 | new_tx_address = "{0}/new_transaction".format(CONNECTED_NODE_ADDRESS) 57 | requests.post(new_tx_address, json=post_object, headers={"Content-type" : "application/json"}) 58 | return redirect("/") 59 | 60 | # Converts a timestamp (in UNIX time) to a string. 61 | def timestamp_to_string(unix_time): 62 | return datetime.datetime.fromtimestamp(unix_time).strftime("%H:%M") 63 | -------------------------------------------------------------------------------- /node_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | from hashlib import sha512 5 | import json 6 | import time 7 | 8 | from flask import Flask, request 9 | import requests 10 | 11 | 12 | # A class that represents a Block, which stores one or more pieces of data, in the immutable Blockchain. 13 | class Block: 14 | # One or more pieces of data (author, content, and timestamp) will be stored in a block. 15 | # The blocks containing the data are generated frequently and added to the blockchain, each with a unique ID. 16 | def __init__(self, index, transactions, timestamp, previous_hash): 17 | self.index = index 18 | self.transactions = transactions 19 | self.timestamp = timestamp 20 | self.previous_hash = previous_hash 21 | self.nonce = 0 22 | 23 | # A function that creates the hash of the block contents. 24 | def compute_hash(self): 25 | block_string = json.dumps(self.__dict__, sort_keys=True) 26 | return sha512(block_string.encode()).hexdigest() 27 | # End of Block class. 28 | 29 | 30 | # A class that represents an immutable list of Block objects that are chained together by hashes, a Blockchain. 31 | class Blockchain: 32 | # Difficulty of PoW algorithm. 33 | difficulty = 2 34 | # One or more blocks will be stored and chained together on the Blockchain, starting with the genesis block. 35 | def __init__(self): 36 | self.unconfirmed_transactions = [] # Pieces of data that are not yet added to the Blockchain. 37 | self.chain = [] # The immutable list that represents the actual Blockchain. 38 | self.create_genesis_block() 39 | 40 | # Generates genesis block and appends it to the Blockchain. 41 | # The Block has index 0, previous_hash of 0, and a valid hash. 42 | def create_genesis_block(self): 43 | genesis_block = Block(0, [], time.time(), "0") 44 | genesis_block.hash = genesis_block.compute_hash() 45 | self.chain.append(genesis_block) 46 | 47 | # Verifies the block can be added to the chain, adds it, and returns True or False. 48 | def add_block(self, block, proof): 49 | previous_hash = self.last_block.hash 50 | # Verifies that the previous_hash field of block to be added points to the hash of the latest block, 51 | # and that the PoW that is provided is correct. 52 | if (previous_hash != block.previous_hash or not self.is_valid_proof(block, proof)): 53 | return False 54 | # Adds new block to the chain after verification. 55 | block.hash = proof 56 | self.chain.append(block) 57 | return True 58 | 59 | # Serves as an interface to add the transactions to the blockchain by adding them 60 | # and then figuring out the PoW. 61 | def mine(self): 62 | # If unconfirmed_transactions is empty, no mining to be done. 63 | if not self.unconfirmed_transactions: 64 | return False 65 | last_block = self.last_block 66 | # Creates a new block to be added to the chain. 67 | new_block = Block(last_block.index + 1, \ 68 | self.unconfirmed_transactions, \ 69 | time.time(), \ 70 | last_block.hash) 71 | # Running PoW algorithm to obtain valid hash and consensus. 72 | proof = self.proof_of_work(new_block) 73 | # Verifies block can be added to the chain (previous hash matches, and PoW is valid), and adds it. 74 | self.add_block(new_block, proof) 75 | # Empties the list of unconfirmed transactions since they are now added to the blockchain. 76 | self.unconfirmed_transactions = [] 77 | # Announces to the network once a block has been mined, other blocks can simply verify the PoW and add it to their respective chains. 78 | announce_new_block(new_block) 79 | # Returns the index of the block that was just added to the chain. 80 | return new_block.index 81 | 82 | # Proof of work algorithm that tries different values of nonce in order to get a hash 83 | # that satisfies the difficulty criteria. 84 | # Important to note that there is no definite logic to figure out the nonce quickly, simply brute force. 85 | def proof_of_work(self, block): 86 | block.nonce = 0 87 | computed_hash = block.compute_hash() 88 | while not computed_hash.startswith("0" * Blockchain.difficulty): 89 | block.nonce += 1 90 | computed_hash = block.compute_hash() 91 | return computed_hash 92 | 93 | # Adds a new transaction the list of unconfirmed transactions (not yet in the blockchain). 94 | def add_new_transaction(self, transaction): 95 | self.unconfirmed_transactions.append(transaction) 96 | 97 | # Checks if the chain is valid at the current time. 98 | @classmethod 99 | def check_chain_validity(cls, chain): 100 | result = True 101 | previous_hash = "0" 102 | for block in chain: 103 | block_hash = block.hash 104 | # Removes the hash attribute to recompute the hash again using compute_hash. 105 | delattr(block, "hash") 106 | if not cls.is_valid_proof(block, block.hash) or previous_hash != block.previous_hash: 107 | result = False 108 | break 109 | block.hash = block_hash 110 | previous_hash = block_hash 111 | return result 112 | 113 | # Checks if block_hash is a valid hash of the given block, and if it satisfies the difficulty criteria. 114 | @classmethod 115 | def is_valid_proof(cls, block, block_hash): 116 | return (block_hash.startswith("0" * Blockchain.difficulty) and block_hash == block.compute_hash()) 117 | 118 | # Returns the current last Block in the Blockchain. 119 | @property 120 | def last_block(self): 121 | return self.chain[-1] 122 | # End of Blockchain class. 123 | 124 | 125 | # Flask web application 126 | # Creates a new Flask web app. 127 | app = Flask(__name__) 128 | # The node's copy of the blockchain. 129 | blockchain = Blockchain() 130 | # A set that stores the addresses to other participating members of the network. 131 | peers = set() 132 | 133 | # Creates a new endpoint, and binds the function to the URL. 134 | @app.route("/new_transaction", methods=["POST"]) 135 | # Submits a new transaction, which adds new data to the blockchain. 136 | def new_transaction(): 137 | tx_data = request.get_json() 138 | required_fields = ["author", "content"] 139 | for field in required_fields: 140 | if not tx_data.get(field): 141 | return "Invalid transaction data", 404 142 | tx_data["timestamp"] = time.time() 143 | blockchain.add_new_transaction(tx_data) 144 | return "Success", 201 145 | 146 | # Creates a new endpoint, and binds the function to the URL. 147 | @app.route("/chain", methods=["GET"]) 148 | # Returns the node's copy of the blockchain in JSON format (to display all confirmed transactions/posts). 149 | def get_chain(): 150 | # Ensures that the user's chain is the current (longest) chain. 151 | consensus() 152 | chain_data = [] 153 | for block in blockchain.chain: 154 | chain_data.append(block.__dict__) 155 | return json.dumps({"length" : len(chain_data), "chain" : chain_data}) 156 | 157 | # Creates a new endpoint, and binds the function to the URL. 158 | @app.route("/mine", methods=["GET"]) 159 | # Requests the node to mine the unconfirmed transactions (if any). 160 | def mine_unconfirmed_transactions(): 161 | result = blockchain.mine() 162 | if not result: 163 | return "There are no transactions to mine." 164 | return "Block #{0} has been mined.".format(result) 165 | 166 | # Creates a new endpoint, and binds the function to the URL. 167 | @app.route("/add_nodes", methods=["POST"]) 168 | # Adds new peers to the network. 169 | def register_new_peers(): 170 | nodes = request.get_json() 171 | if not nodes: 172 | return "Invalid data", 400 173 | for node in nodes: 174 | peers.add(node) 175 | return "Success", 201 176 | 177 | # Creates a new endpoint, and binds the function to the URL. 178 | @app.route("/pending_tx") 179 | # Queries unconfirmed transactions. 180 | def get_pending_tx(): 181 | return json.dumps(blockchain.unconfirmed_transactions) 182 | 183 | # A simple algorithm to achieve consensus to maintain the integrity of the system. 184 | # If a longer valid chain is found, the chain is replaced with it, and returns True, otherwise nothing happens and returns False. 185 | def consensus(): 186 | global blockchain 187 | longest_chain = None 188 | curr_len = len(blockchain.chain) 189 | # Achieve consensus by checking the JSON fields of every node in the network. 190 | for node in peers: 191 | response = requests.get("http://{0}/chain".format(node)) 192 | length = response.json()["length"] 193 | chain = response.json()["chain"] 194 | if length > curr_len and blockchain.check_chain_validity(chain): 195 | curr_len = length 196 | longest_chain = chain 197 | if longest_chain: 198 | blockchain = longest_chain 199 | return True 200 | return False 201 | 202 | # Creates a new endpoint, and binds the function to the URL. 203 | @app.route("/add_block", methods=["POST"]) 204 | # Adds a block mined by a user to the node's chain. 205 | def validate_and_add_block(): 206 | block_data = request.get_json() 207 | block = Block(block_data["index"], \ 208 | block_data["transactions"], \ 209 | block_data["timestamp", block_data["previous_hash"]]) 210 | proof = block_data["hash"] 211 | added = blockchain.add_block(block, proof) 212 | if not added: 213 | return "The block was discarded by the node.", 400 214 | return "The block was added to the chain.", 201 215 | 216 | # Announces to the network once a block has been mined, should always be called after validate_and_add_block(). 217 | # Other blocks can simply verify the PoW and add it to their respective chains. 218 | def announce_new_block(block): 219 | for peer in peers: 220 | url = "http://{0}/add_block".format(peer) 221 | requests.post(url, data=json.dumps(block.__dict__, sort_keys=True)) 222 | 223 | # Runs the Flask web app. 224 | app.run(port=8000, debug=True) -------------------------------------------------------------------------------- /run_app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | from app import app 5 | 6 | app.run(debug=True) 7 | -------------------------------------------------------------------------------- /screenshots/blocknet-home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewmuccio/BlockNet/3e04fae1ca68846a94c5a5dba6b69f1e3af53c70/screenshots/blocknet-home.png --------------------------------------------------------------------------------