-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathblockchain_server.py
More file actions
269 lines (222 loc) · 8.45 KB
/
blockchain_server.py
File metadata and controls
269 lines (222 loc) · 8.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
from flask import Flask, request
from hashlib import sha256
import time
import json
import requests
# Block class is the most basic storing unit
# in a Blockchain.
class Block:
def __init__(self, index, transactions, time_stamp, previous_hash):
self.index = index
# by convention, data within a blockchain
# is called transactions. Here type(transactions)==dict
self.transactions = transactions
self.time_stamp = time_stamp
# blockchain are linked with hash values
self.previous_hash = previous_hash
# json.dumps automatically format a json file into a string
# sha256 is a strong way to generate a hash value with 64 chars
def compute_hash(self):
block_string = json.dumps(self.__dict__, sort_keys=True)
return sha256(block_string.encode()).hexdigest()
# Blockchain class connects all blocks to a
# whole chain
#
# **class methods:
# **void create_genesis_block(self)
# **void last_block(self): when call call instance.last_block
# **string proof_of_work(self, block)
# **boolean add_block(self, block, proof)
# **boolean is_valid_proof(self, block, proof)
# **boolean add_transaction(self, transaction)
# **boolean check_chain_validity(self)
# **boolean check_transaction(self, transaction)
class Blockchain:
def __init__(self):
# where user's input data or file currently stored
self.unconfirmed_transactions = {}
# stores a chain of blocks, linked with hash values
self.chain = []
# each time we create a Blockchain we always want to
# create a default genesis block 0
self.hash_dict = {}
self.create_genesis_block()
# * input-None
# * output-None
# * generate a genesis block and append it into self.chain
def create_genesis_block(self):
genesis_block = Block(0, {}, time.time(), 0)
genesis_block.hash = self.proof_of_work(genesis_block)
self.chain.append(genesis_block)
self.hash_dict[genesis_block.hash] = 0
@property
def last_block(self):
return self.chain[-1]
# * input-Block object
# * output-Hash
# * output a hash value meets constraints
def proof_of_work(self, block):
Blockchain.difficulty = 3
block.nonce = 0
computed_hash = block.compute_hash()
# generates the hash until find a hash value that is valid
while not computed_hash.startswith("0" * Blockchain.difficulty):
block.nonce += 1
computed_hash = block.compute_hash()
return computed_hash
# * input-Block object, Hash
# * output-boolean
# * add a new Block to chain, return appending status
def add_block(self, block, proof):
previous_hash = self.last_block.hash
# if the to-be added block is invalid
# return False without adding that block
if (previous_hash != block.previous_hash
) or (not self.is_valid_proof(block, proof)):
return False
block.hash = proof
self.chain.append(block)
return True
# * input-Block object, Hash
# * output-boolean
# * find out whether a Hash is eligible for this block
def is_valid_proof(self, block, proof):
return (proof.startswith("0" * Blockchain.difficulty) and
proof == block.compute_hash())
# * input-None
# * output-boolean
# * check whether the current Blockchain is valid
def check_chain_validity(self):
previous_hash = 0
# check the validity through Blockchain's base rule
for block in self.chain:
block_hash = block.hash
delattr(block, "hash")
if not self.is_valid_proof(block, block_hash) or \
previous_hash != block.previous_hash:
return False
block.hash, previous_hash = block_hash, block_hash
return True
# * input-dict
# * output-boolean
# * check whether submitted transaction is valid
# !!!!!!!not debugged!!!!!!!
def check_transaction(self, transaction):
condition = []
condition.append(transaction["type"] in ["pic", "trans"])
condition.append("@" in transaction["uploaded_by"][1:-1])
condition.append(5 <= len(transaction) <= 6)
return all(condition)
# * input-dict
# * output-boolean
# * add unprocessed transaction to Blockchain, return status
# !!!!!!!not debugged!!!!!!!
def add_transaction(self, transaction):
if type(transaction) == dict and self.check_transaction(transaction):
self.unconfirmed_transactions = transaction
return True
else:
return False
# * input-None
# * output-integer
# * mine transactions to a new block
# !!!!!!!not debugged!!!!!!!
def mine(self):
if not self.unconfirmed_transactions:
return 0
new_block = Block(self.last_block.index + 1,
self.unconfirmed_transactions,
time.time(),
self.last_block.hash)
proof = self.proof_of_work(new_block)
self.add_block(new_block, proof)
self.hash_dict[new_block.hash] = new_block.index
self.unconfirmed_transactions = {}
return new_block.hash
# * input-hash
# * output-a transaction
# * find transactions via hash values
def find_transaction(self, hash):
return json.dumps(self.chain[self.hash_dict[hash]].transactions)
app = Flask(__name__)
blockchain = Blockchain()
# the address to other participating members of the network
peers = set()
# endpoint to submit a new transaction. This will be used by
# our application to add new data (posts) to the blockchain
@app.route('/new_transaction', methods=['POST'])
def new_transaction():
tx_data = request.get_json()
tx_data["timestamp"] = time.time()
if blockchain.add_transaction(tx_data):
return "Success", 201
else:
return "Invalid transaction data", 404
@app.route('/hash_lookup', methods=['POST'])
def hash_lookup():
hash = request.data.decode("ascii")
return blockchain.find_transaction(hash)
# endpoint to return the node's copy of the chain.
# Our application will be using this endpoint to query
# all the posts to display.
@app.route('/chain', methods=['GET'])
def get_chain():
chain_data = []
for block in blockchain.chain:
chain_data.append(block.__dict__)
return json.dumps({"length": len(chain_data),
"chain": chain_data})
# endpoint to request the node to mine the unconfirmed
# transactions (if any). We'll be using it to initiate
# a command to mine from our application itself.
@app.route('/mine', methods=['GET'])
def mine_unconfirmed_transactions():
result = blockchain.mine()
if result == 0:
return "No transactions to mine"
return result
# endpoint to add new peers to the network.
@app.route('/add_nodes', methods=['POST'])
def register_new_peers():
nodes = request.get_json()
if not nodes:
return "Invalid data", 400
for node in nodes:
peers.add(node)
return "Success", 201
# endpoint to add a block mined by someone else to
# the node's chain. The block is first verified by the node
# and then added to the chain.
@app.route('/add_block', methods=['POST'])
def validate_and_add_block():
block_data = request.get_json()
block = Block(block_data["index"], block_data["transactions"],
block_data["timestamp", block_data["previous_hash"]])
proof = block_data['hash']
added = blockchain.add_block(block, proof)
if not added:
return "The block was discarded by the node", 400
return "Block added to the chain", 201
def announce_new_block(block):
for peer in peers:
url = "http://{}/add_block".format(peer)
requests.post(url, data=json.dumps(block.__dict__, sort_keys=True))
def consensus():
"""
Our simple consensus algorithm. If a longer valid chain is found, our chain is replaced with it.
"""
global blockchain
longest_chain = None
current_len = len(blockchain)
for node in peers:
response = requests.get('http://{}/chain'.format(node))
length = response.json()['length']
chain = response.json()['chain']
if length > current_len and blockchain.check_chain_validity(chain):
current_len = length
longest_chain = chain
if longest_chain:
blockchain = longest_chain
return True
return False
app.run(host="0.0.0.0", debug=False, port=60000)