-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.py
210 lines (192 loc) · 10.6 KB
/
server.py
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
import socket
import threading
import json
from modules.get_ip import get_local_ip
from modules.block_validator import is_block_valid
from modules.chain_validation import is_chain_valid, hash
from client import send_chain, send_nodes
# Function called in a separate thread that handles incoming connections to the server
def handle_client(conn, addr):
HEADER = 64
FORMAT = 'utf-8'
DISCONNECT_MESSAGE = "!DISCONNECT"
connected = True
ip_local = get_local_ip()
while connected:
# Listening to the length of the message incoming
msg_length = conn.recv(HEADER).decode(FORMAT)
if msg_length:
msg_length = int(msg_length)
# Listening to the actual message
msg = conn.recv(msg_length).decode(FORMAT)
if msg == DISCONNECT_MESSAGE:
connected = False
elif msg == "REQBLOCKCHAIN":
addr_s = addr[0]
conn.send("Chain successfully requested.".encode(FORMAT))
# Call for the send_chain function of the TCP client to send chain data
send_chain(addr_s)
return f"Blockchain request from {addr}"
elif msg == "REQNODES":
addr_s = addr[0]
try:
# We add the IP addr that requested the nodes to our nodes list, as the request most definetely comes from a newly setup node
with open("data/nodes.json") as nodes_file:
nodes = json.load(nodes_file)
nodes['nodes'].append(addr_s)
# We remove any duplicates
nodes['nodes'] = list(dict.fromkeys(nodes['nodes']))
# And dump the nodes list containing the new one in the local nodes file
with open("data/nodes.json", "w") as nodes_file:
json.dump(nodes, nodes_file)
except FileNotFoundError:
return "Missing data/nodes.json file"
conn.send("Nodes successfully requested.".encode(FORMAT))
# Call for the send_nodes function of the TCP client to send nodes data
send_nodes(addr_s)
return f"Nodes list request from {addr}"
elif msg == "BLOCKCHAIN":
#print(f"Blockchain received from {addr}")
# Listen for the length of the chain incoming
chain_length = conn.recv(HEADER).decode(FORMAT)
if chain_length:
chain_length = int(chain_length)
# We loop until we receive the entire chain message
chain_received = ""
bytes_recvd = 0
while bytes_recvd < chain_length:
chain_recv = conn.recv(min(chain_length - bytes_recvd, 2048))
chain_received = chain_received + chain_recv.decode(FORMAT)
bytes_recvd = bytes_recvd + len(chain_recv.decode(FORMAT))
chain = chain_received
try:
chain_dict = json.loads(chain)
except json.decoder.JSONDecodeError:
return "Corrupted blockchain data..."
#print(chain_dict[-1])
# Verifying the chain
chain_is_valid = is_chain_valid(chain_dict)
if chain_is_valid:
try:
# We compare the received chain with our local one (if we have a local one, otherwise we just dump the received one in a file)
with open("data/blockchain.json") as og_chain_file:
og_chain = json.load(og_chain_file)
if og_chain == chain_dict:
conn.send("Same".encode(FORMAT))
return "Same"
else:
if og_chain[-1]['index'] < chain_dict[-1]['index']:
with open("data/blockchain.json", "w") as chain_file:
json.dump(chain_dict, chain_file)
# We only replace our local blockchain if the received one is strictly longer than our own
conn.send("Chain successfully broadcasted.".encode(FORMAT))
return "Blockchain updated"
else:
conn.send("Same or newer".encode(FORMAT))
return "Same or newer"
except FileNotFoundError:
with open("data/blockchain.json", "w") as chain_file:
json.dump(chain_dict, chain_file)
conn.send("Chain successfully broadcasted.".encode(FORMAT))
return "Blockchain updated"
else:
err = {'message' : 'Received chain is not valid !',
'ip_sender': ip_local}
conn.send(json.dumps(err).encode(FORMAT))
return err
elif msg == "NODES":
# More or less the same as with receiving chain data
print(f"Nodes received from {addr}")
nodes_length = conn.recv(HEADER).decode(FORMAT)
if nodes_length:
nodes_length = int(nodes_length)
nodes_received = ""
bytes_recvd = 0
while bytes_recvd < nodes_length:
nodes_recv = conn.recv(min(nodes_length - bytes_recvd, 2048))
nodes_received = nodes_received + nodes_recv.decode(FORMAT)
bytes_recvd = bytes_recvd + len(nodes_recv.decode(FORMAT))
nodes = nodes_received
try:
nodes_dict = json.loads(nodes)
except json.decoder.JSONDecodeError:
return "Corrupted node data..."
are_nodes_valid = True
if are_nodes_valid:
# We seperate two cases :
# One where we already have a nodes file and we just want to update it
# One where we don't have one so we directly dump the received nodes data in a file
try:
with open("data/nodes.json") as og_nodes_file:
og_nodes = json.load(og_nodes_file)
nodes_list = []
for i in range(len(og_nodes['nodes'])):
nodes_list.append(og_nodes['nodes'][i])
for i in range(len(nodes_dict['nodes'])):
nodes_list.append(nodes_dict['nodes'][i])
nodes_list = list(dict.fromkeys(nodes_list))
nodes_final = {}
nodes_final['nodes'] = []
for i in range(len(nodes_list)):
nodes_final['nodes'].append(nodes_list[i])
with open("data/nodes.json", "w") as nodes_file:
json.dump(nodes_final, nodes_file)
conn.send("Nodes successfully broadcasted.".encode(FORMAT))
except FileNotFoundError:
nodes_list = []
for i in range(len(nodes_dict['nodes'])):
nodes_list.append(nodes_dict['nodes'][i])
nodes_list = list(dict.fromkeys(nodes_list))
nodes_final = {}
nodes_final['nodes'] = []
for i in range(len(nodes_list)):
nodes_final['nodes'].append(nodes_list[i])
with open("data/nodes.json", "w") as nodes_file:
json.dump(nodes_final, nodes_file)
conn.send("Nodes successfully broadcasted.".encode(FORMAT))
else:
err = {'message' : 'Received chain is not valid !',
'ip_sender': ip_local}
conn.send(json.dumps(err).encode(FORMAT))
return err
# This is for receiving block data (i.e when receiving a newly mined block)
else:
try:
msg_dict = json.loads(msg)
except json.decoder.JSONDecodeError:
return "Corrupted block data..."
print(f"\nBlock from {addr} | Timestamp : {msg_dict['timestamp']} | Index : {msg_dict['index']}")
try:
with open("data/blockchain.json") as chain_file:
chain = json.load(chain_file)
# We verify if the received block is valid
msg_is_valid = is_block_valid(json.loads(msg), chain)
if msg_is_valid:
try:
# We append the valid block in our chain
chain.append(json.loads(msg))
except AttributeError:
pass
# We dump the new chain in our local file
with open("data/blockchain.json", "w") as chain_file:
json.dump(chain, chain_file)
conn.send("Block successfully broadcasted.".encode(FORMAT))
return json.loads(msg)
else:
err = {'message' : 'Received block is not valid !',
'ip_sender': addr[0]}
conn.send(json.dumps(err).encode(FORMAT))
return err
except FileNotFoundError:
return "Blockchain data file missing"
conn.close()
# Function that starts the server (i.e makes it listen for incoming connections)
def start(server, SERVER):
server.listen()
print("[STARTING] Server is starting...")
print(f"[LISTENING] Server is listening on {SERVER}\n")
while True:
# When a new connection arrives we send it in a parallel thread with the function defined above
conn, addr = server.accept()
listening = threading.Thread(target=handle_client, args=(conn, addr))
listening.start()