├── .vscode
└── settings.json
├── API_calls_screenshots
├── GET Request.png
├── POST Request.png
├── adding_peer_node.png
├── fetch_balance.png
├── generating_a_wallet.png
├── make_transaction.png
├── make_transaction_to_peer_node.png
├── mining_a_block.png
└── peer_node_removed.png
├── Legacy Blockchain Files
├── .vscode
│ ├── launch.json
│ └── settings.json
├── __pycache__
│ └── hash_util.cpython-36.pyc
├── blockchain.p
├── blockchain.py
├── blockchain.txt
└── hash_util.py
├── README.md
├── __pycache__
├── block.cpython-37.pyc
├── blockchain.cpython-37.pyc
├── transaction.cpython-37.pyc
└── wallet.cpython-37.pyc
├── block.py
├── blockchain.py
├── cover.png
├── node.py
├── node_console.py
├── transaction.py
├── utility
├── .DS_Store
├── __init__.py
├── __pycache__
│ ├── __init__.cpython-37.pyc
│ ├── hash_util.cpython-37.pyc
│ ├── printable.cpython-37.pyc
│ └── verification.cpython-37.pyc
├── hash_util.py
├── printable.py
└── verification.py
└── wallet.py
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.pythonPath": "C:\\Users\\Aditya\\AppData\\Local\\Continuum\\anaconda3\\python.exe"
3 | }
--------------------------------------------------------------------------------
/API_calls_screenshots/GET Request.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/API_calls_screenshots/GET Request.png
--------------------------------------------------------------------------------
/API_calls_screenshots/POST Request.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/API_calls_screenshots/POST Request.png
--------------------------------------------------------------------------------
/API_calls_screenshots/adding_peer_node.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/API_calls_screenshots/adding_peer_node.png
--------------------------------------------------------------------------------
/API_calls_screenshots/fetch_balance.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/API_calls_screenshots/fetch_balance.png
--------------------------------------------------------------------------------
/API_calls_screenshots/generating_a_wallet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/API_calls_screenshots/generating_a_wallet.png
--------------------------------------------------------------------------------
/API_calls_screenshots/make_transaction.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/API_calls_screenshots/make_transaction.png
--------------------------------------------------------------------------------
/API_calls_screenshots/make_transaction_to_peer_node.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/API_calls_screenshots/make_transaction_to_peer_node.png
--------------------------------------------------------------------------------
/API_calls_screenshots/mining_a_block.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/API_calls_screenshots/mining_a_block.png
--------------------------------------------------------------------------------
/API_calls_screenshots/peer_node_removed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/API_calls_screenshots/peer_node_removed.png
--------------------------------------------------------------------------------
/Legacy Blockchain Files/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Python: Current File",
9 | "type": "python",
10 | "request": "launch",
11 | "program": "${file}",
12 | "console": "integratedTerminal"
13 | }
14 | ]
15 | }
--------------------------------------------------------------------------------
/Legacy Blockchain Files/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "git.ignoreLimitWarning": true,
3 | "python.pythonPath": "C:\\Users\\Aditya\\AppData\\Local\\Programs\\Python\\Python36-32\\python.exe"
4 | }
--------------------------------------------------------------------------------
/Legacy Blockchain Files/__pycache__/hash_util.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/Legacy Blockchain Files/__pycache__/hash_util.cpython-36.pyc
--------------------------------------------------------------------------------
/Legacy Blockchain Files/blockchain.p:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/Legacy Blockchain Files/blockchain.p
--------------------------------------------------------------------------------
/Legacy Blockchain Files/blockchain.py:
--------------------------------------------------------------------------------
1 | ##### Phase - 1: Importing all necessary files and Initializing the blockchain #####
2 |
3 | # Importing
4 | from functools import reduce
5 | import hashlib as hl
6 | from collections import OrderedDict
7 | import json
8 | import pickle
9 | from hash_util import hash_string_256, hash_block
10 |
11 | # The reward we give to miners for creating a new block
12 | mining_reward = 10
13 |
14 | # Initializing our empty blockchain list
15 | blockchain = []
16 |
17 | # Unhandled or open transactions that are yet to be added to a new block
18 | open_transactions = []
19 |
20 | # We are the owner of this blockchain node, hence this is our identifier (e.g. for sending coins)
21 | owner = 'Aditya'
22 |
23 | # Registered participants: Us + other people sending/ receiving coins
24 | participants = {'Aditya'}
25 |
26 |
27 |
28 |
29 | ##### Phase - 2: Saving & Loading the Blockchain Data #####
30 |
31 | # Saving the transactions and other details of our blockchain
32 | def save_data():
33 | try:
34 | with open('blockchain.txt', mode='w') as f:
35 | f.write(json.dumps(blockchain))
36 | f.write('\n')
37 | f.write(json.dumps(open_transactions))
38 | # Uncomment below block of code to save data using pickle and comment out json block of code
39 | # save_data = {
40 | # 'chain': blockchain,
41 | # 'ot': open_transactions
42 | # }
43 | # f.write(pickle.dumps(save_data))
44 | except IOError:
45 | print('Saving failed!')
46 |
47 | # Loading the transactions and other details of our blockchain
48 | def load_data():
49 | global blockchain
50 | global open_transactions
51 | try:
52 | with open('blockchain.txt', mode='r') as f:
53 | # Uncomment below block of code to load data from pickle file and comment out json code
54 | # file_content = pickle.loads(f.read())
55 | # blockchain = file_content['chain']
56 | # open_transactions = file_content['ot']
57 | file_content = f.readlines()
58 | blockchain = json.loads(file_content[0][:-1])
59 | # We need to first convert the data loaded from the file because Transactions should use OrderedDict
60 | updated_blockchain = []
61 | for block in blockchain:
62 | updated_block = {
63 | 'previous_hash': block['previous_hash'],
64 | 'index': block['index'],
65 | 'proof': block['proof'],
66 | 'transactions': [OrderedDict(
67 | [('sender', tx['sender']), ('recipient', tx['recipient']), ('amount', tx['amount'])]) for tx in block['transactions']]
68 | }
69 | updated_blockchain.append(updated_block)
70 | blockchain = updated_blockchain
71 | open_transactions = json.loads(file_content[1])
72 | # We need to convert the loaded data because Transactions should use OrderedDict
73 | updated_transactions = []
74 | for tx in open_transactions:
75 | updated_transaction = OrderedDict(
76 | [('sender', tx['sender']), ('recipient', tx['recipient']), ('amount', tx['amount'])])
77 | updated_transactions.append(updated_transaction)
78 | open_transactions = updated_transactions
79 | except IOError:
80 | # Our starting block for the blockchain
81 | genesis_block = {
82 | 'previous_hash': '',
83 | 'index': 0,
84 | 'transactions': [],
85 | 'proof': 100
86 | }
87 | # Initializing our (empty) blockchain list
88 | blockchain = [genesis_block]
89 | # Unhandled or Unprocessed transactions that are yet to be a part of a block
90 | open_transactions = []
91 | finally:
92 | print('Cleaning Up!')
93 |
94 | # Load the data from the file
95 | load_data()
96 |
97 |
98 |
99 |
100 | ##### Phase - 3: Verifying & Authenticating the Blockchain #####
101 |
102 | # Proof of Work
103 | def valid_proof(transactions, last_hash, proof):
104 | guess = (str(transactions) + str(last_hash) + str(proof)).encode()
105 | # This hashing is used for Proof of work Algo
106 | guess_hash = hash_string_256(guess)
107 | # Only a hash (which is based on the above inputs) which starts with two 0s is treated as valid
108 | return guess_hash[0:2] == '00'
109 |
110 | def proof_of_work():
111 | """Generate a proof of work for the open transactions, the hash of the previous block and a random number (which is guessed until it fits)."""
112 | last_block = blockchain[-1]
113 | last_hash = hash_block(last_block)
114 | proof = 0
115 | # Try different PoW numbers and return the first valid one
116 | while not valid_proof(open_transactions, last_hash, proof):
117 | proof += 1
118 | return proof
119 |
120 | # Calculate and return the balance of a participant
121 | def get_balance(participant):
122 | # Fetch a list of all sent coin amounts for the given person (empty lists are returned if the person was NOT the sender)
123 | # This fetches sent amounts of transactions that were already included in blocks of the blockchain
124 | tx_sender = [[tx['amount'] for tx in block['transactions']
125 | if tx['sender'] == participant] for block in blockchain]
126 |
127 | # Fetch a list of all sent coin amounts for the given person (empty lists are returned if the person was NOT the sender)
128 | # This fetches sent amounts of open transactions (to avoid double spending)
129 | open_tx_sender = [tx['amount']
130 | for tx in open_transactions if tx['sender'] == participant]
131 | tx_sender.append(open_tx_sender)
132 | print(tx_sender)
133 | amount_sent = reduce(lambda tx_sum, tx_amt: tx_sum + sum(tx_amt)
134 | if len(tx_amt) > 0 else tx_sum + 0, tx_sender, 0)
135 |
136 | # This was the previous logic used for calculating the amount of coins sent
137 | """
138 | amount_sent = 0
139 | # Calculate the total amount of coins sent
140 | for tx in tx_sender:
141 | if len(tx) > 0:
142 | amount_sent += tx[0]
143 | """
144 |
145 | # This fetches received coin amounts of transactions that were already included in blocks of the blockchain
146 | # We ignore open transactions here because you shouldn't be able to spend coins before the transaction was confirmed + included in a block
147 | tx_recipient = [[tx['amount'] for tx in block['transactions']
148 | if tx['recipient'] == participant] for block in blockchain]
149 | amount_received = reduce(lambda tx_sum, tx_amt: tx_sum + sum(tx_amt)
150 | if len(tx_amt) > 0 else tx_sum + 0, tx_recipient, 0)
151 |
152 | # Return the total balance
153 | return amount_received - amount_sent
154 |
155 | # Returns the last value of the current blockchain
156 | def get_last_blockchain_value():
157 | if len(blockchain) < 1:
158 | return None
159 | return blockchain[-1]
160 |
161 | # Verifies a transaction by checking whether the sender has sufficient amount of coins
162 | def verify_transaction(transaction):
163 | sender_balance = get_balance(transaction['sender'])
164 | return sender_balance >= transaction['amount']
165 |
166 | # Creating a Chain of Data( Append a new value as well as the last blockchain value to the blockchain )
167 | def add_transaction(recipient, sender=owner, amount=1.0):
168 | # transaction = {
169 | # 'sender': sender,
170 | # 'recipient': recipient,
171 | # 'amount': amount
172 | # }
173 | transaction = OrderedDict(
174 | [('sender', sender), ('recipient', recipient), ('amount', amount)])
175 | if verify_transaction(transaction):
176 | open_transactions.append(transaction)
177 | participants.add(sender)
178 | participants.add(recipient)
179 | save_data()
180 | return True
181 | return False
182 |
183 |
184 |
185 |
186 | ##### Phase - 4: Mining a new block in the Blockchain #####
187 |
188 | # Mine a new block in the Blockchain ( Create a new block and add open transactions to it )
189 | def mine_block():
190 | # Fetch the current last block of the blockchain
191 | last_block = blockchain[-1]
192 | # Hash the last block. So, we can compare it to the stored hash value
193 | hashed_block = hash_block(last_block)
194 | proof = proof_of_work()
195 | # For rewarding miners, Mining Reward was created
196 | # reward_transaction = {
197 | # 'sender': 'mining_system',
198 | # 'recipient': owner,
199 | # 'amount': mining_reward
200 | # }
201 | reward_transaction = OrderedDict(
202 | [('sender', 'mining_system'), ('recipient', owner), ('amount', mining_reward)])
203 | # Copy transaction instead of manipulating the original open_transactions list
204 | # This ensures that if for some reason the mining should fail, we don't have the reward transaction stored in the open transactions
205 | copied_transactions = open_transactions[:]
206 | copied_transactions.append(reward_transaction)
207 | block = {
208 | 'previous_hash': hashed_block,
209 | 'index': len(blockchain),
210 | 'transactions': copied_transactions,
211 | 'proof': proof
212 | }
213 | blockchain.append(block)
214 | return True
215 |
216 | # Get the user input, transform it from a string to a float and store it in user_input
217 | def get_transaction_value():
218 | tx_recipient = input('Enter the recipient of the transaction: ')
219 | tx_amount = float(input('Your transaction amount please: '))
220 | return tx_recipient, tx_amount
221 |
222 | # Prompts the user for its choice and return it
223 | def get_user_choice():
224 | user_input = input('Your choice: ')
225 | return user_input
226 |
227 | # Output the blockchain list to the console
228 | def print_blockchain_elements():
229 | for block in blockchain:
230 | print('Outputting Block')
231 | print(block)
232 | else:
233 | print('-' * 20)
234 |
235 | # Analyze and Verify the Blockchain
236 | # We skip checking the first block as there's no previous block with which it can be compared
237 | # If first element of current block is equal to the previous entire block, chain is valid
238 | def verify_chain():
239 | # Basic Implementation
240 | """ is_valid = True
241 | for block_index in range(len(blockchain)):
242 | if block_index == 0:
243 | continue
244 | elif blockchain[block_index][0] == blockchain[block_index - 1]:
245 | is_valid = True
246 | else:
247 | is_valid = False
248 | return is_valid """
249 |
250 | # Improved Implementation
251 | """ Verify the current blockchain and return True if it's valid, False otherwise."""
252 | for (index, block) in enumerate(blockchain):
253 | if index == 0:
254 | continue
255 | if block['previous_hash'] != hash_block(blockchain[index - 1]):
256 | return False
257 | if not valid_proof(block['transactions'][:-1], block['previous_hash'], block['proof']):
258 | print('Proof of work is invalid')
259 | return False
260 | return True
261 |
262 | # Verifies all open & unprocessed transactions
263 | def verify_transactions():
264 | return all([verify_transaction(tx) for tx in open_transactions])
265 |
266 |
267 |
268 |
269 | ##### Phase - 5: User Interface #####
270 |
271 | waiting_for_input = True
272 |
273 | # User Input Interface
274 | while waiting_for_input:
275 | print('Please choose')
276 | print('1: Add a new transaction value')
277 | print('2: Mine a new block')
278 | print('3: Output the blockchain blocks')
279 | print('4: Output participants')
280 | print('5: Check transaction validity')
281 | print('h: Manipulate the chain')
282 | print('q: Quit')
283 | user_choice = get_user_choice()
284 | if user_choice == '1':
285 | tx_data = get_transaction_value()
286 | recipient, amount = tx_data
287 | # Add the transaction amount to the blockchain
288 | if add_transaction(recipient, amount=amount):
289 | print('Added transaction!')
290 | else:
291 | print('Transaction failed!')
292 | print(open_transactions)
293 | elif user_choice == '2':
294 | if mine_block():
295 | open_transactions = []
296 | save_data()
297 | elif user_choice == '3':
298 | print_blockchain_elements()
299 | elif user_choice == '4':
300 | print(participants)
301 | elif user_choice == '5':
302 | if verify_transactions():
303 | print('All transactions are valid')
304 | else:
305 | print('There are invalid transactions')
306 | elif user_choice == 'h':
307 | # Make sure that you don't try to manipulate the blockchain if it's empty
308 | if len(blockchain) >= 1:
309 | blockchain[0] = {
310 | 'previous_hash': '',
311 | 'index': 0,
312 | 'transactions': [{'sender': 'Chris', 'recipient': 'Aditya', 'amount': 100.0}]
313 | }
314 | elif user_choice == 'q':
315 | # This will lead to the loop to exist because it's running condition becomes False
316 | waiting_for_input = False
317 | else:
318 | print('Input was invalid, please pick a value from the list!')
319 | if not verify_chain():
320 | print_blockchain_elements()
321 | print('Invalid blockchain!')
322 | # Break out of the loop
323 | break
324 | print('Balance of {}: {:6.2f}'.format('Aditya', get_balance('Aditya')))
325 | else:
326 | print('User left!')
327 |
328 |
329 | print('Done!')
330 |
--------------------------------------------------------------------------------
/Legacy Blockchain Files/blockchain.txt:
--------------------------------------------------------------------------------
1 | [{"previous_hash": "", "index": 0, "proof": 100, "transactions": []}, {"previous_hash": "68da5267793e1fc7bd98d9cc01cf91e38daa67cbbd7b646bcd99b4dc3df93eb1", "index": 1, "proof": 119, "transactions": [{"sender": "MINING", "recipient": "Aditya", "amount": 10}]}, {"previous_hash": "8c097d52167ae2a038ff8c1b12d079fcd54f739ab945178a1dd3d1dc1c02d96b", "index": 2, "proof": 18, "transactions": [{"sender": "MINING", "recipient": "Aditya", "amount": 10}]}, {"previous_hash": "a69cb435f1b58c193d5eed3fdf9a769b1d685e49f5b33f2aa7c18cd1f3a78d56", "index": 3, "proof": 66, "transactions": [{"sender": "Aditya", "recipient": "Sam", "amount": 5.0}, {"sender": "Aditya", "recipient": "Rob", "amount": 9.5}, {"sender": "Aditya", "recipient": "Jane", "amount": 2.0}, {"sender": "mining_system", "recipient": "Aditya", "amount": 10}]}, {"previous_hash": "25933fa334929513ace937d1509c2c2c9c1f88992702a527785e69e292693122", "index": 4, "transactions": [{"sender": "Aditya", "recipient": "Robert", "amount": 3.5}, {"sender": "mining_system", "recipient": "Aditya", "amount": 10}], "proof": 0}]
2 | []
--------------------------------------------------------------------------------
/Legacy Blockchain Files/hash_util.py:
--------------------------------------------------------------------------------
1 | import hashlib as hl
2 | import json
3 |
4 | # Create a SHA256 hash for a given input string
5 | def hash_string_256(string):
6 | return hl.sha256(string).hexdigest()
7 |
8 | # Hashes a block and returns a string representation of it
9 | def hash_block(block):
10 | return hash_string_256(json.dumps(block, sort_keys=True).encode())
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Blockchain Project
2 |
3 | This project is an implementation of blockchain and it's concepts in Python & Flask with Postman used for API development
4 |
5 | ## Getting Started
6 |
7 | 1. [Download Anaconda](https://www.anaconda.com/distribution/)
8 | 2. [Download Visual Studio Code](https://code.visualstudio.com/)
9 | 3. [Download Postman](https://www.getpostman.com/)
10 | 4. Clone the directory and open it in **vs code**
11 | 5. In terminal, enter **activate** to activate the **base** environment of Anaconda
12 | > *Install the below dependencies to avoid any errors incase they're not already installed*
13 | 6. Install Flask
14 |
15 | `pip install flask`
16 |
17 | 7. Install Flask-Cors
18 |
19 | `pip install -U flask-cors`
20 |
21 | 8. Install requests Lib
22 |
23 | `pip install requests`
24 |
25 | 9. Install pycrypto (collection of hash functions)
26 |
27 | `pip install pycrypto`
28 |
29 | 10. In the terminal, run the following command
30 |
31 | `python node.py`
32 |
33 | > *or to run multiple nodes, in a new terminal*
34 |
35 | `python node.py -p`
36 |
37 | 11. Open Postman Client, Test the below API Calls
38 |
39 | ## API Calls
40 | Below is a list of API calls and short description about what they do, these api calls can conveniently be executed in Postman
41 | Localhost & Port have been set in node.py file, by default I've set them as 0.0.0.0 with port=5000
42 |
43 | ### Blockchain
44 | Creates a blockchain.txt file that contains information regarding the transactions and other information related to the blockchain
45 |
46 | * [POST] Mine a Block:
47 |
48 | `localhost:5000/mine`
49 |
50 | * [GET] Get snapshot of current blockchain:
51 |
52 | `localhost:5000/chain`
53 |
54 | ### Transaction
55 |
56 | * [POST] Add a New Transaction:
57 |
58 | `localhost:5000/transaction`
59 |
60 | Before executing the above api call, in Postman,
61 | Go to "Body" > "Raw" > Select "Json" in the current window and enter the following data:
62 |
63 | `eg. { "recipient": "NameofRecipient", "amount": 5 }`
64 |
65 | * [GET] Get a List of all Open Transactions:
66 |
67 | `localhost:5000/transactions`
68 |
69 | ### Wallet
70 | creates a wallet.txt file with your port number that contains information regarding your wallet such as public key, private key
71 |
72 | * [POST] Generate Wallet:
73 |
74 | `localhost:5000/wallet`
75 |
76 | * [GET] Fetch Wallet:
77 |
78 | `localhost:5000/wallet`
79 |
80 | * [GET] Fetch Wallet Balance:
81 |
82 | `localhost:5000/balance`
83 |
84 | ### Node/Server
85 | You can run multiple nodes and test communication between the two by opening two terminals inside **vs code** and executing the **python node.py** file with arguments **-p 5001** or **--port 5001**. So, it will look like **python node.py -port 5001**
86 |
87 | You can create as many nodes/servers you want by using the open ports and appending them to the **python node.py --port ** command
88 |
89 | * [POST] Add Peer Node to Your Chain Network:
90 |
91 | `localhost:5000/node`
92 |
93 | Before executing the above api call, in Postman,
94 | Go to "Body" > "Raw" > Select "Json" in the current window and enter the following data:
95 |
96 | `e.g. { "node": "localhost:5001" }`
97 |
98 | * [DELETE] Delete Peer Node:
99 |
100 | `e.g. localhost:5000/node/localhost:5001`
101 |
102 | * [GET] Get all the Nodes in your Network:
103 |
104 | `localhost:5000/nodes`
105 |
106 | ## File Contents
107 | * block.py - represents single block of our blockchain
108 | * blockchain.py - represents the blockchain
109 | * node.py - contains all the routes for API calls
110 | * node_console.py - provides a user interface using while loop to interact with blockchain from within the vs code terminal
111 | * transaction.py - represents open transactions
112 | * wallet.py - Implements functionality to generate wallet, public & private key and other security related algorithm
113 | * API_calls_screenshot Folder - contains screenshots of successful API calls and their output
114 | * Utility > hash_util.py - Implements functionality to hash a block
115 | * Utility > printable.py - Converts binary data to string for printing in console
116 | * Utility > verification.py - Implements proof of work and verification of chain and transactions
117 |
118 | * Legacy_blockchain_files - contains old files related to project
119 |
120 | ## Shoutout
121 | Huge credits to Dapp university & howCode for explaining the concepts and how to implement them in python.
122 | 1. [Dapp University Video Link](https://www.youtube.com/watch?v=pZSegEXtgAE)
123 | 2. [howCode Video Link](https://www.youtube.com/watch?v=b81Ib_oYbFk)
124 |
--------------------------------------------------------------------------------
/__pycache__/block.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/__pycache__/block.cpython-37.pyc
--------------------------------------------------------------------------------
/__pycache__/blockchain.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/__pycache__/blockchain.cpython-37.pyc
--------------------------------------------------------------------------------
/__pycache__/transaction.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/__pycache__/transaction.cpython-37.pyc
--------------------------------------------------------------------------------
/__pycache__/wallet.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/__pycache__/wallet.cpython-37.pyc
--------------------------------------------------------------------------------
/block.py:
--------------------------------------------------------------------------------
1 | from time import time
2 |
3 | from utility.printable import Printable
4 |
5 | # Block class represents a single block of our blockchain
6 | # index: The index of this block
7 | # previous_hash: The hash of the previous block in the blockchain
8 | # timestamp: The timestamp of the block (automatically generated by default)
9 | # transactions: A list of transaction which are included in the block
10 | # proof: The proof of work number that yielded this block
11 | class Block(Printable):
12 | def __init__(self, index, previous_hash, transactions, proof, time=time()):
13 | self.index = index
14 | self.previous_hash = previous_hash
15 | self.timestamp = time
16 | self.transactions = transactions
17 | self.proof = proof
18 |
19 |
20 |
--------------------------------------------------------------------------------
/blockchain.py:
--------------------------------------------------------------------------------
1 | # Importing
2 | from functools import reduce
3 | import hashlib as hl
4 |
5 | import json
6 | import pickle
7 | import requests
8 |
9 | from utility.hash_util import hash_block
10 | from utility.verification import Verification
11 | from block import Block
12 | from transaction import Transaction
13 | from wallet import Wallet
14 |
15 | # The reward we give to miners for creating a new block
16 | MINING_REWARD = 10
17 |
18 | #__name__ is basically used here to identify whether the file is executed as main program or
19 | # if it is being imported from another module/file and then being executed
20 | print(__name__)
21 |
22 | # This class manages the chain of blocks, open transactions and the node on which it's running
23 | # chain: The list of blocks
24 | # open_transactions (private): The list of open transactions
25 | # hosting_node: The connected node (which runs the blockchain)
26 | class Blockchain:
27 | def __init__(self, public_key, node_id):
28 | # Genesis block is the very first block in our blockchain
29 | genesis_block = Block(0, '', [], 100, 0)
30 | # Initializing our blockchain list
31 | self.chain = [genesis_block]
32 | # Unhandled or Open transactions which are yet to be included in a block
33 | self.__open_transactions = []
34 | self.public_key = public_key
35 | self.__peer_nodes = set()
36 | self.node_id = node_id
37 | self.load_data()
38 |
39 | # This turns the chain attribute into a property with a getter (the method below) and a setter (@chain.setter)
40 | # chain[:] returns a copy so we only get a copy of the reference of the objects, so we can't directly change the value
41 | @property
42 | def chain(self):
43 | return self.__chain[:]
44 |
45 | # The setter for the chain property
46 | @chain.setter
47 | def chain(self, val):
48 | self.__chain = val
49 |
50 | # Returns a copy of the open transactions list that are yet to be mined
51 | def get_open_transactions(self):
52 | return self.__open_transactions[:]
53 |
54 | # Initialize blockchain + open transactions data from a file
55 | def load_data(self):
56 | try:
57 | with open('blockchain-{}.txt'.format(self.node_id), mode='r') as f:
58 | # file_content = pickle.loads(f.read())
59 | file_content = f.readlines()
60 | # blockchain = file_content['chain']
61 | # open_transactions = file_content['ot']
62 | blockchain = json.loads(file_content[0][:-1])
63 | # We need to convert the loaded data because Transactions should use OrderedDict
64 | updated_blockchain = []
65 | for block in blockchain:
66 | converted_tx = [Transaction(
67 | tx['sender'], tx['recipient'], tx['signature'], tx['amount']) for tx in block['transactions']]
68 | updated_block = Block(
69 | block['index'], block['previous_hash'], converted_tx, block['proof'], block['timestamp'])
70 | updated_blockchain.append(updated_block)
71 | self.chain = updated_blockchain
72 | open_transactions = json.loads(file_content[1][:-1])
73 | # We need to convert the loaded data because Transactions should use OrderedDict
74 | updated_transactions = []
75 | for tx in open_transactions:
76 | updated_transaction = Transaction(
77 | tx['sender'], tx['recipient'], tx['signature'], tx['amount'])
78 | updated_transactions.append(updated_transaction)
79 | self.__open_transactions = updated_transactions
80 | peer_nodes = json.loads(file_content[2])
81 | self.__peer_nodes = set(peer_nodes)
82 | except (IOError, IndexError):
83 | pass
84 | finally:
85 | print('Cleanup!')
86 |
87 | # Save blockchain + open transactions to a file
88 | def save_data(self):
89 | try:
90 | with open('blockchain-{}.txt'.format(self.node_id), mode='w') as f:
91 | saveable_chain = [block.__dict__ for block in [Block(block_el.index, block_el.previous_hash, [
92 | tx.__dict__ for tx in block_el.transactions], block_el.proof, block_el.timestamp) for block_el in self.__chain]]
93 | f.write(json.dumps(saveable_chain))
94 | f.write('\n')
95 | saveable_tx = [tx.__dict__ for tx in self.__open_transactions]
96 | f.write(json.dumps(saveable_tx))
97 | f.write('\n')
98 | f.write(json.dumps(list(self.__peer_nodes)))
99 | # save_data = {
100 | # 'chain': blockchain,
101 | # 'ot': open_transactions
102 | # }
103 | # f.write(pickle.dumps(save_data))
104 | except IOError:
105 | print('Saving failed!')
106 |
107 | # Generate a proof of work for open transactions, hash of previous block and a random number(Which is guessed until it fits)
108 | def proof_of_work(self):
109 | last_block = self.__chain[-1]
110 | last_hash = hash_block(last_block)
111 | proof = 0
112 | # Try different PoW numbers and return the first valid one
113 | while not Verification.valid_proof(self.__open_transactions, last_hash, proof):
114 | proof += 1
115 | return proof
116 |
117 | # Calculate and return the balance of the user
118 | def get_balance(self, sender=None):
119 | if sender == None:
120 | if self.public_key == None:
121 | return None
122 | participant = self.public_key
123 | else:
124 | participant = sender
125 | # Fetch a list of all sent coin amounts for the given person (empty lists are returned if the person was NOT the sender)
126 | # This fetches sent amounts of transactions that were already included in blocks of the blockchain
127 | tx_sender = [[tx.amount for tx in block.transactions
128 | if tx.sender == participant] for block in self.__chain]
129 | # Fetch a list of all sent coin amounts for the given person (empty lists are returned if the person was NOT the sender)
130 | # This fetches sent amounts of open transactions (to avoid double spending)
131 | open_tx_sender = [tx.amount
132 | for tx in self.__open_transactions if tx.sender == participant]
133 | tx_sender.append(open_tx_sender)
134 | print(tx_sender)
135 | amount_sent = reduce(lambda tx_sum, tx_amt: tx_sum + sum(tx_amt)
136 | if len(tx_amt) > 0 else tx_sum + 0, tx_sender, 0)
137 | # This fetches received coin amounts of transactions that were already included in blocks of the blockchain
138 | # We ignore open transactions here because you shouldn't be able to spend coins before the transaction was confirmed + included in a block
139 | tx_recipient = [[tx.amount for tx in block.transactions
140 | if tx.recipient == participant] for block in self.__chain]
141 | amount_received = reduce(lambda tx_sum, tx_amt: tx_sum + sum(tx_amt)
142 | if len(tx_amt) > 0 else tx_sum + 0, tx_recipient, 0)
143 | # Return the total balance
144 | return amount_received - amount_sent
145 |
146 | # Returns the last value of the current blockchain
147 | def get_last_blockchain_value(self):
148 | if len(self.__chain) < 1:
149 | return None
150 | return self.__chain[-1]
151 |
152 | # Creating a Chain of Data( Append a new value as well as the last blockchain value to the blockchain )
153 | def add_transaction(self, recipient, sender, signature, amount=1.0, is_receiving=False):
154 | # transaction = {
155 | # 'sender': sender,
156 | # 'recipient': recipient,
157 | # 'amount': amount
158 | # }
159 | # if self.public_key == None:
160 | # return False
161 | transaction = Transaction(sender, recipient, signature, amount)
162 | if Verification.verify_transaction(transaction, self.get_balance):
163 | self.__open_transactions.append(transaction)
164 | self.save_data()
165 | if not is_receiving:
166 | for node in self.__peer_nodes:
167 | url = 'http://{}/broadcast-transaction'.format(node)
168 | try:
169 | response = requests.post(url, json={
170 | 'sender': sender, 'recipient': recipient, 'amount': amount, 'signature': signature})
171 | if response.status_code == 400 or response.status_code == 500:
172 | print('Transaction declined, needs resolving')
173 | return False
174 | except requests.exceptions.ConnectionError:
175 | continue
176 | return True
177 | return False
178 |
179 | # Mine a new block in the Blockchain ( Create a new block and add open transactions to it )
180 | def mine_block(self):
181 | if self.public_key == None:
182 | return None
183 | last_block = self.__chain[-1]
184 | # Hash the last block. So, we can compare it to the stored hash value
185 | hashed_block = hash_block(last_block)
186 | proof = self.proof_of_work()
187 | # reward_transaction is created because miners should be added for mining a new block
188 | reward_transaction = Transaction(
189 | 'MINING', self.public_key, '', MINING_REWARD)
190 | # Copy transaction instead of manipulating the original open_transactions list
191 | # This ensures that if for some reason the mining should fail, we don't have the reward transaction stored in the open transactions
192 | copied_transactions = self.__open_transactions[:]
193 | for tx in copied_transactions:
194 | if not Wallet.verify_transaction(tx):
195 | return None
196 | copied_transactions.append(reward_transaction)
197 | block = Block(len(self.__chain), hashed_block,
198 | copied_transactions, proof)
199 | self.__chain.append(block)
200 | self.__open_transactions = []
201 | self.save_data()
202 | for node in self.__peer_nodes:
203 | url = 'http://{}/broadcast-block'.format(node)
204 | converted_block = block.__dict__.copy()
205 | converted_block['transactions'] = [
206 | tx.__dict__ for tx in converted_block['transactions']]
207 | try:
208 | response = requests.post(url, json={'block': converted_block})
209 | if response.status_code == 400 or response.status_code == 500:
210 | print('Block declined, needs resolving')
211 | except requests.exceptions.ConnectionError:
212 | continue
213 | return block
214 |
215 | def add_block(self, block):
216 | transactions = [Transaction(
217 | tx['sender'], tx['recipient'], tx['signature'], tx['amount']) for tx in block['transactions']]
218 | proof_is_valid = Verification.valid_proof(
219 | transactions[:-1], block['previous_hash'], block['proof'])
220 | hashes_match = hash_block(self.chain[-1]) == block['previous_hash']
221 | if not proof_is_valid or not hashes_match:
222 | return False
223 | converted_block = Block(
224 | block['index'], block['previous_hash'], transactions, block['proof'], block['timestamp'])
225 | self.__chain.append(converted_block)
226 | stored_transactions = self.__open_transactions[:]
227 | for itx in block['transactions']:
228 | for opentx in stored_transactions:
229 | if opentx.sender == itx['sender'] and opentx.recipient == itx['recipient'] and opentx.amount == itx['amount'] and opentx.signature == itx['signature']:
230 | try:
231 | self.__open_transactions.remove(opentx)
232 | except ValueError:
233 | print('Item was already removed')
234 | self.save_data()
235 | return True
236 |
237 | # add a new node to the network of peer nodes
238 | # node: The node URL which should be added
239 | def add_peer_node(self, node):
240 | self.__peer_nodes.add(node)
241 | self.save_data()
242 |
243 | # remove a new node to the network of peer nodes
244 | # node: The node URL which should be removed
245 | def remove_peer_node(self, node):
246 | self.__peer_nodes.discard(node)
247 | self.save_data()
248 |
249 | def get_peer_nodes(self):
250 | return list(self.__peer_nodes)
251 |
--------------------------------------------------------------------------------
/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/cover.png
--------------------------------------------------------------------------------
/node.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, jsonify, request, send_from_directory
2 | from flask_cors import CORS
3 |
4 | from wallet import Wallet
5 | from blockchain import Blockchain
6 |
7 | app = Flask(__name__)
8 | CORS(app)
9 |
10 |
11 | # POST - Wallet
12 | @app.route('/wallet', methods=['POST'])
13 | def create_keys():
14 | wallet.create_keys()
15 | if wallet.save_keys():
16 | # We reinitialise blockchain since we're using a POST request and also
17 | # instruct function to use global blockchain variable
18 | global blockchain
19 | blockchain = Blockchain(wallet.public_key, port)
20 | response = {
21 | 'public_key': wallet.public_key,
22 | 'private_key': wallet.private_key,
23 | 'funds': blockchain.get_balance()
24 | }
25 | return jsonify(response), 201
26 | else:
27 | response = {
28 | 'message': 'Saving keys failed.'
29 | }
30 | return jsonify(response), 500
31 |
32 |
33 | # GET - Wallet
34 | @app.route('/wallet', methods=['GET'])
35 | def load_keys():
36 | if wallet.load_keys():
37 | global blockchain
38 | blockchain = Blockchain(wallet.public_key, port)
39 | response = {
40 | 'public_key': wallet.public_key,
41 | 'private_key': wallet.private_key,
42 | 'funds': blockchain.get_balance()
43 | }
44 | return jsonify(response), 201
45 | else:
46 | response = {
47 | 'message': 'Loading the keys failed.'
48 | }
49 | return jsonify(response), 500
50 |
51 |
52 | # GET - Balance
53 | @app.route('/balance', methods=['GET'])
54 | def get_balance():
55 | balance = blockchain.get_balance()
56 | if balance != None:
57 | response = {
58 | 'message': 'Fetched balance successfully.',
59 | 'funds': balance
60 | }
61 | return jsonify(response), 200
62 | else:
63 | response = {
64 | 'messsage': 'Loading balance failed.',
65 | 'wallet_set_up': wallet.public_key != None
66 | }
67 | return jsonify(response), 500
68 |
69 |
70 | # POST - Broadcast Transaction Information to Peer Nodes
71 | @app.route('/broadcast-transaction', methods=['POST'])
72 | def broadcast_transaction():
73 | values = request.get_json()
74 | if not values:
75 | response = {'message': 'No data found.'}
76 | return jsonify(response), 400
77 | required = ['sender', 'recipient', 'amount', 'signature']
78 | if not all(key in values for key in required):
79 | response = {'message': 'Some data is missing.'}
80 | return jsonify(response), 400
81 | success = blockchain.add_transaction(
82 | values['recipient'], values['sender'], values['signature'], values['amount'], is_receiving=True)
83 | if success:
84 | response = {
85 | 'message': 'Successfully added transaction.',
86 | 'transaction': {
87 | 'sender': values['sender'],
88 | 'recipient': values['recipient'],
89 | 'amount': values['amount'],
90 | 'signature': values['signature']
91 | }
92 | }
93 | return jsonify(response), 201
94 | else:
95 | response = {
96 | 'message': 'Creating a transaction failed.'
97 | }
98 | return jsonify(response), 500
99 |
100 |
101 | # POST - Broadcast Mined Block Information to Peer Nodes
102 | @app.route('/broadcast-block', methods=['POST'])
103 | def broadcast_block():
104 | values = request.get_json()
105 | if not values:
106 | response = {'message': 'No data found.'}
107 | return jsonify(response), 400
108 | if 'block' not in values:
109 | response = {'message': 'Some data is missing.'}
110 | return jsonify(response), 400
111 | block = values['block']
112 | if block['index'] == blockchain.chain[-1].index + 1:
113 | if blockchain.add_block(block):
114 | response = {'message': 'Block added'}
115 | return jsonify(response), 201
116 | else:
117 | response = {'message': 'Block seems invalid.'}
118 | return jsonify(response), 500
119 | elif block['index'] > blockchain.chain[-1].index:
120 | pass
121 | else:
122 | response = {'message': 'Blockchain seems to be shorter, block not added'}
123 | return jsonify(response), 409
124 |
125 | # POST - Make a transaction
126 | @app.route('/transaction', methods=['POST'])
127 | def add_transaction():
128 | if wallet.public_key == None:
129 | response = {
130 | 'message': 'No wallet set up.'
131 | }
132 | return jsonify(response), 400
133 | values = request.get_json()
134 | if not values:
135 | response = {
136 | 'message': 'No data found.'
137 | }
138 | return jsonify(response), 400
139 | required_fields = ['recipient', 'amount']
140 | if not all(field in values for field in required_fields):
141 | response = {
142 | 'message': 'Required data is missing.'
143 | }
144 | return jsonify(response), 400
145 | recipient = values['recipient']
146 | amount = values['amount']
147 | signature = wallet.sign_transaction(wallet.public_key, recipient, amount)
148 | success = blockchain.add_transaction(
149 | recipient, wallet.public_key, signature, amount)
150 | if success:
151 | response = {
152 | 'message': 'Successfully added transaction.',
153 | 'transaction': {
154 | 'sender': wallet.public_key,
155 | 'recipient': recipient,
156 | 'amount': amount,
157 | 'signature': signature
158 | },
159 | 'funds': blockchain.get_balance()
160 | }
161 | return jsonify(response), 201
162 | else:
163 | response = {
164 | 'message': 'Creating a transaction failed.'
165 | }
166 | return jsonify(response), 500
167 |
168 |
169 | # POST - Mine a block (add a new block to the blockchain)
170 | @app.route('/mine', methods=['POST'])
171 | def mine():
172 | block = blockchain.mine_block()
173 | if block != None:
174 | dict_block = block.__dict__.copy()
175 | dict_block['transactions'] = [
176 | tx.__dict__ for tx in dict_block['transactions']]
177 | response = {
178 | 'message': 'Block added successfully.',
179 | 'block': dict_block,
180 | 'funds': blockchain.get_balance()
181 | }
182 | return jsonify(response), 201
183 | else:
184 | response = {
185 | 'message': 'Adding a block failed.',
186 | 'wallet_set_up': wallet.public_key != None
187 | }
188 | return jsonify(response), 500
189 |
190 |
191 | # GET - Get History of Transactions
192 | @app.route('/transactions', methods=['GET'])
193 | def get_open_transaction():
194 | transactions = blockchain.get_open_transactions()
195 | dict_transactions = [tx.__dict__ for tx in transactions]
196 | return jsonify(dict_transactions), 200
197 |
198 | # GET - Get a Snapshot of the Current chain
199 | # Json is used in below route as we want to return some data, not render a webpage
200 | @app.route('/chain', methods=['GET'])
201 | def get_chain():
202 | chain_snapshot = blockchain.chain
203 | # we use .copy() to prevent side effects of manipulating of the block for future requests
204 | dict_chain = [block.__dict__.copy() for block in chain_snapshot]
205 | for dict_block in dict_chain:
206 | dict_block['transactions'] = [
207 | tx.__dict__ for tx in dict_block['transactions']]
208 | return jsonify(dict_chain), 200
209 |
210 |
211 | # POST - Add a Peer Node to the Network
212 | @app.route('/node', methods=['POST'])
213 | def add_node():
214 | values = request.get_json()
215 | if not values:
216 | response = {
217 | 'message': 'No data attached.'
218 | }
219 | return jsonify(response), 400
220 | if 'node' not in values:
221 | response = {
222 | 'message': 'No node data found.'
223 | }
224 | return jsonify(response), 400
225 | node = values['node']
226 | blockchain.add_peer_node(node)
227 | response = {
228 | 'message': 'Node added successfully.',
229 | 'all_nodes': blockchain.get_peer_nodes()
230 | }
231 | return jsonify(response), 201
232 |
233 |
234 | # DELETE - Delete a Peer Node
235 | @app.route('/node/', methods=['DELETE'])
236 | def remove_node(node_url):
237 | if node_url == '' or node_url == None:
238 | response = {
239 | 'message': 'No node found.'
240 | }
241 | return jsonify(response), 400
242 | blockchain.remove_peer_node(node_url)
243 | response = {
244 | 'message': 'Node removed',
245 | 'all_nodes': blockchain.get_peer_nodes()
246 | }
247 | return jsonify(response), 200
248 |
249 |
250 | # GET - Get List of Peer Nodes
251 | @app.route('/nodes', methods=['GET'])
252 | def get_nodes():
253 | nodes = blockchain.get_peer_nodes()
254 | response = {
255 | 'all_nodes': nodes
256 | }
257 | return jsonify(response), 200
258 |
259 |
260 | if __name__ == '__main__':
261 | from argparse import ArgumentParser
262 | parser = ArgumentParser()
263 | # # We add additional arguments which can be used during execution to perform
264 | # addition funtionality such as create a new port
265 | parser.add_argument('-p', '--port', type=int, default=5000)
266 | # Getting a list of arguments using parser
267 | args = parser.parse_args()
268 | port = args.port
269 | wallet = Wallet(port)
270 | blockchain = Blockchain(wallet.public_key, port)
271 | app.run(host='0.0.0.0', port=port)
272 |
--------------------------------------------------------------------------------
/node_console.py:
--------------------------------------------------------------------------------
1 | from uuid import uuid4
2 |
3 | from blockchain import Blockchain
4 | from utility.verification import Verification
5 | from wallet import Wallet
6 |
7 | # This node class runs the local blockchain instance (id: Id of the node, blockchain: blockchain which is run by node)
8 | class Node:
9 | def __init__(self):
10 | # self.id = str(uuid4())
11 | self.wallet = Wallet()
12 | self.wallet.create_keys()
13 | self.blockchain = Blockchain(self.wallet.public_key)
14 |
15 | # Get the user input, transform it from a string to a float and store it in user_input
16 | def get_transaction_value(self):
17 | tx_recipient = input('Enter the recipient of the transaction: ')
18 | tx_amount = float(input('Your transaction amount please: '))
19 | return tx_recipient, tx_amount
20 |
21 | # Prompts the user for its choice and return it
22 | def get_user_choice(self):
23 | user_input = input('Your choice: ')
24 | return user_input
25 |
26 | # Output the blockchain list to the console
27 | def print_blockchain_elements(self):
28 | for block in self.blockchain.chain:
29 | print('Outputting Block')
30 | print(block)
31 | else:
32 | print('-' * 20)
33 |
34 | # Starts the node and waits for user input
35 | def listen_for_input(self):
36 | waiting_for_input = True
37 |
38 | # User Input Interface
39 | while waiting_for_input:
40 | print('Please choose')
41 | print('1: Add a new transaction value')
42 | print('2: Mine a new block')
43 | print('3: Output the blockchain blocks')
44 | print('4: Check transaction validity')
45 | print('5: Create wallet')
46 | print('6: Load wallet')
47 | print('7: Save keys')
48 | print('q: Quit')
49 | user_choice = self.get_user_choice()
50 | if user_choice == '1':
51 | tx_data = self.get_transaction_value()
52 | recipient, amount = tx_data
53 | signature = self.wallet.sign_transaction(self.wallet.public_key, recipient, amount)
54 | if self.blockchain.add_transaction(recipient, self.wallet.public_key, signature, amount=amount):
55 | print('Added transaction!')
56 | else:
57 | print('Transaction failed!')
58 | print(self.blockchain.get_open_transactions())
59 | elif user_choice == '2':
60 | if not self.blockchain.mine_block():
61 | print('Mining failed. Got no wallet?')
62 | elif user_choice == '3':
63 | self.print_blockchain_elements()
64 | elif user_choice == '4':
65 | if Verification.verify_transactions(self.blockchain.get_open_transactions(), self.blockchain.get_balance):
66 | print('All transactions are valid')
67 | else:
68 | print('There are invalid transactions')
69 | elif user_choice == '5':
70 | self.wallet.create_keys()
71 | self.blockchain = Blockchain(self.wallet.public_key)
72 | elif user_choice == '6':
73 | self.wallet.load_keys()
74 | self.blockchain = Blockchain(self.wallet.public_key)
75 | elif user_choice == '7':
76 | self.wallet.save_keys()
77 | elif user_choice == 'q':
78 | waiting_for_input = False
79 | else:
80 | print('Input was invalid, please pick a value from the list!')
81 | if not Verification.verify_chain(self.blockchain.chain):
82 | self.print_blockchain_elements()
83 | print('Invalid blockchain!')
84 | # Break out of the loop
85 | break
86 | print('Balance of {}: {:6.2f}'.format(self.wallet.public_key, self.blockchain.get_balance()))
87 | else:
88 | print('User left!')
89 |
90 | print('Done!')
91 |
92 | if __name__ == '__main__':
93 | node = Node()
94 | node.listen_for_input()
95 |
--------------------------------------------------------------------------------
/transaction.py:
--------------------------------------------------------------------------------
1 | from collections import OrderedDict
2 |
3 | from utility.printable import Printable
4 |
5 | # Transaction class represents an open transaction which can be added to a block to become processed transaction
6 | class Transaction(Printable):
7 | def __init__(self, sender, recipient, signature, amount):
8 | self.sender = sender
9 | self.recipient = recipient
10 | self.amount = amount
11 | self.signature = signature
12 |
13 | # Converts the transaction into a hashable OrderedDict
14 | def to_ordered_dict(self):
15 | return OrderedDict([('sender', self.sender), ('recipient', self.recipient), ('amount', self.amount)])
16 |
--------------------------------------------------------------------------------
/utility/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/utility/.DS_Store
--------------------------------------------------------------------------------
/utility/__init__.py:
--------------------------------------------------------------------------------
1 | from utility.hash_util import hash_string_256
2 |
3 | __all__ = ['hash_string_256']
--------------------------------------------------------------------------------
/utility/__pycache__/__init__.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/utility/__pycache__/__init__.cpython-37.pyc
--------------------------------------------------------------------------------
/utility/__pycache__/hash_util.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/utility/__pycache__/hash_util.cpython-37.pyc
--------------------------------------------------------------------------------
/utility/__pycache__/printable.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/utility/__pycache__/printable.cpython-37.pyc
--------------------------------------------------------------------------------
/utility/__pycache__/verification.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adi2381/py-blockchain/8271527e3c5c04357d6f54d626b6352db540585a/utility/__pycache__/verification.cpython-37.pyc
--------------------------------------------------------------------------------
/utility/hash_util.py:
--------------------------------------------------------------------------------
1 | import hashlib as hl
2 | import json
3 |
4 | # __all__ = ['hash_string_256', 'hash_block']
5 |
6 | # Uses SHA256 hashing algorithm to hash a given input string
7 | def hash_string_256(string):
8 | return hl.sha256(string).hexdigest()
9 |
10 | # Hashes a block and returns a str representation of it
11 | def hash_block(block):
12 | hashable_block = block.__dict__.copy()
13 | hashable_block['transactions'] = [tx.to_ordered_dict() for tx in hashable_block['transactions']]
14 | return hash_string_256(json.dumps(hashable_block, sort_keys=True).encode())
--------------------------------------------------------------------------------
/utility/printable.py:
--------------------------------------------------------------------------------
1 | """
2 | Printable class represents a base class which implements
3 | the printing to console functionality. We convert everything
4 | to string as in our wallet functionality, output is in binary
5 | and so to maintain the readability of data, we use printable class
6 | """
7 | class Printable:
8 | def __repr__(self):
9 | return str(self.__dict__)
10 |
--------------------------------------------------------------------------------
/utility/verification.py:
--------------------------------------------------------------------------------
1 | # Provides methods for implementing the verification functionality
2 |
3 | from utility.hash_util import hash_string_256, hash_block
4 | from wallet import Wallet
5 |
6 | # Verification class in our use case is a helper class which offers static & class-based verification & validation methods
7 | class Verification:
8 | @staticmethod
9 | # Validate a proof of work number and see if it solves the POW algorithm (two leading 0s, set by us)
10 | # Arguments:
11 | # transactions: The transactions of the block for which the proof is created.
12 | # last_hash: The previous block's hash which will be stored in the current block.
13 | # proof: The proof number we're testing.
14 | def valid_proof(transactions, last_hash, proof):
15 | # Create a string with all the hash inputs
16 | guess = (str([tx.to_ordered_dict() for tx in transactions]) + str(last_hash) + str(proof)).encode()
17 | # Hash the string; This hash is used for POW Algorithm and is not the same as stored in previous_hash
18 | guess_hash = hash_string_256(guess)
19 | # Only a hash (which is based on the above inputs) which starts with two 0s is treated as valid
20 | # If 10 0's are used instead of '00', this allows to control the speed at which new blocks are created
21 | # so more 0's mean more time will be required to create a new block
22 | return guess_hash[0:2] == '00'
23 |
24 | # # Analyze and Verify the Blockchain, return True if it's valid else False
25 | @classmethod
26 | def verify_chain(cls, blockchain):
27 | for (index, block) in enumerate(blockchain):
28 | if index == 0:
29 | continue
30 | if block.previous_hash != hash_block(blockchain[index - 1]):
31 | return False
32 | if not cls.valid_proof(block.transactions[:-1], block.previous_hash, block.proof):
33 | print('Proof of work is invalid')
34 | return False
35 | return True
36 |
37 | # # Verifies by checking whether sender has sufficient coins or not
38 | @staticmethod
39 | def verify_transaction(transaction, get_balance, check_funds=True):
40 | if check_funds:
41 | sender_balance = get_balance(transaction.sender)
42 | return sender_balance >= transaction.amount and Wallet.verify_transaction(transaction)
43 | else:
44 | return Wallet.verify_transaction(transaction)
45 |
46 | # Verifies all open & unprocessed transactions
47 | @classmethod
48 | def verify_transactions(cls, open_transactions, get_balance):
49 | return all([cls.verify_transaction(tx, get_balance, False) for tx in open_transactions])
--------------------------------------------------------------------------------
/wallet.py:
--------------------------------------------------------------------------------
1 | from Crypto.PublicKey import RSA
2 | from Crypto.Signature import PKCS1_v1_5
3 | from Crypto.Hash import SHA256
4 | import Crypto.Random
5 | import binascii
6 |
7 | # Wallet class generates a wallet for us which contains our public and private keys & manages transaction signing & verification
8 | class Wallet:
9 |
10 | def __init__(self, node_id):
11 | self.private_key = None
12 | self.public_key = None
13 | self.node_id = node_id
14 |
15 | # Create a new pair of private and public keys
16 | def create_keys(self):
17 | private_key, public_key = self.generate_keys()
18 | self.private_key = private_key
19 | self.public_key = public_key
20 |
21 | # Saves the keys to a file (wallet.txt)
22 | def save_keys(self):
23 | if self.public_key != None and self.private_key != None:
24 | try:
25 | with open('wallet-{}.txt'.format(self.node_id), mode='w') as f:
26 | f.write(self.public_key)
27 | f.write('\n')
28 | f.write(self.private_key)
29 | return True
30 | except (IOError, IndexError):
31 | print('Saving wallet failed...')
32 | return False
33 |
34 | # Loads the keys from the wallet.txt
35 | def load_keys(self):
36 | try:
37 | with open('wallet-{}.txt'.format(self.node_id), mode='r') as f:
38 | keys = f.readlines()
39 | public_key = keys[0][:-1]
40 | private_key = keys[1]
41 | self.public_key = public_key
42 | self.private_key = private_key
43 | return True
44 | except (IOError, IndexError):
45 | print('Loading wallet failed...')
46 | return False
47 |
48 | # Generate a new pair of private and public key
49 | def generate_keys(self):
50 | private_key = RSA.generate(1024, Crypto.Random.new().read)
51 | public_key = private_key.publickey()
52 | return (binascii.hexlify(private_key.exportKey(format='DER')).decode('ascii'), binascii.hexlify(public_key.exportKey(format='DER')).decode('ascii'))
53 |
54 | # Sign a transaction and return the signature
55 | # RSA is a cryptography algorithm
56 | # binascii.hexlify is used to convert binary data to hexadecimal representation
57 | def sign_transaction(self, sender, recipient, amount):
58 | signer = PKCS1_v1_5.new(RSA.importKey(binascii.unhexlify(self.private_key)))
59 | h = SHA256.new((str(sender) + str(recipient) + str(amount)).encode('utf8'))
60 | signature = signer.sign(h)
61 | return binascii.hexlify(signature).decode('ascii')
62 |
63 | # Verify signature of transaction
64 | @staticmethod
65 | def verify_transaction(transaction):
66 | public_key = RSA.importKey(binascii.unhexlify(transaction.sender))
67 | verifier = PKCS1_v1_5.new(public_key)
68 | h = SHA256.new((str(transaction.sender) + str(transaction.recipient) + str(transaction.amount)).encode('utf8'))
69 | return verifier.verify(h, binascii.unhexlify(transaction.signature))
--------------------------------------------------------------------------------