-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.py
315 lines (261 loc) · 11.6 KB
/
main.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
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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
import flask
from flask import request
import threading
import requests
from threading import Lock, Thread
import pdb
import traceback
from classes import DealResultsBroadcastRequest, Deck, DoubleEncryptedDeckRequest, GamePhase, GameWinnerVerificationResultRequest, JoinRequest, JoinResponse, NewNodeListMessage, Player, PlzHelpWithEncryptingDeckRequest, PlzHelpWithEncryptingDeckResponse, ReverseBullyElectionResponse, ShareKeyRequest, WinnerRequest
from command_line import CommandLoop, share_your_fairness_vote_and_wait_for_results
import state as state
import reverse_bully as bully
from flask_middleware import middleware
app = flask.Flask(__name__)
app.config["DEBUG"] = False
app.wsgi_app = middleware(app.wsgi_app)
def main():
print("Starting node")
server_thread = threading.Thread(target=start_server)
server_thread.start()
start_cmdloop()
# Start FLASK thread
def start_server():
app.run(port=6376, host="0.0.0.0")
# Start CMD line thread
def start_cmdloop():
try:
CommandLoop().cmdloop()
except Exception as e:
print("Exception from cmdloop")
print(e)
traceback.print_exc()
pdb.set_trace()
# Reverse Bully election
@app.route("/election", methods=["POST"])
def election() -> ReverseBullyElectionResponse:
origin = request.remote_addr
if origin in [node["ip"] for node in state.NODES.values()]:
origin_number = next(
node["player_number"] for node in state.NODES.values() if node["ip"] == origin
)
if origin_number > state.OWN_NODE_NUMBER:
Thread(target=take_over_bully).start()
return {"taking_over": True}
return {"taking_over": False}
# Reverse bully, this is spawned in own thread
def take_over_bully():
print("Taking over the election.")
bully.reverse_bully()
# Reverse bully, new leader announced
@app.route("/new-leader", methods=["POST"])
def assign_new_leader():
origin = request.origin
if origin in [node["ip"] for node in state.NODES.values()]:
origin_number = next(
node["player_number"] for node in state.NODES.values() if node["ip"] == origin
)
if origin_number < state.OWN_NODE_NUMBER:
print(f"Assigning new leader: {origin_number}")
state.LEADER_NODE_NUMBER = origin_number
state.LEADER_ELECTION_ONGOING = False
else:
print("Someone with smaller node number is trying to take over.")
return {"message": "Ok, assigned new leader."}
@app.route("/health", methods=["GET"])
def health():
return {"message": "ok"}
@app.route("/get-nodes", methods=["GET"])
def get_nodes():
return {"message": "Here are my nodes I know of", "nodes": state.NODES}
# have to prevent that two nodes don't join with the same player number
registration_lock = Lock()
@app.route("/join", methods=["POST"])
def register_nodes():
try:
registration_lock.acquire()
# Do not let players join while game ongoing.
if state.GAME_PHASE == state.GAME_PHASE.GAME_ONGOING:
return {"message": "Game ongoing, please join later."}
json: JoinRequest = request.json
origin = request.remote_addr
# Leader assigns itself once first person joins
if state.NEXT_PLAYER_NUMBER == 1:
state.OWN_NODE_NUMBER = 1
state.NODES[state.LEADER_NODE_NUMBER] = Player(
player_number=1,
ip=json["your_ip"],
score=0,
)
state.NEXT_PLAYER_NUMBER = 2
# Check if node trying to join is/was already in-game
if origin in [node["ip"] for node in state.NODES.values()]:
print(
"Node with IP",
origin,
"already in game.",
)
# FAULT_TOLERANCE: If node drops out of game, get list his list of nodes, if it is empty, return him the current state
get_nodes_result = requests.get(f"http://{origin}:6376/get-nodes").json
if get_nodes_result["nodes"].length == 0:
print(
"Node did probably crash or lose connection, sending him the game state."
)
return {"message": "You have already registered.", "nodes": state.NODES}
return {"message": "You are already part of this game."}, 400
print("Adding player #", state.NEXT_PLAYER_NUMBER, " to game")
state.NODES[int(state.NEXT_PLAYER_NUMBER)] = Player(
ip=origin, player_number=state.NEXT_PLAYER_NUMBER, score=0
)
state.NEXT_PLAYER_NUMBER += 1
print("Broadcast to others the recently joining participant")
print("Nodes in game:", state.NODES)
broadcast_new_node_list()
print("Return current game state to joining paricipant.")
return JoinResponse(
message="New node has been added",
nodes=state.NODES,
your_player_number=state.NEXT_PLAYER_NUMBER - 1,
leader_node_number=state.LEADER_NODE_NUMBER,
)
finally:
registration_lock.release()
@app.route("/leave", methods=["POST"])
def unregister_nodes():
origin = request.remote_addr
player_name = next(player_name for (player_name, node) in state.NODES.items() if node["ip"] == origin)
if player_name:
state.NODES.pop(player_name)
print("Nodes still in game", state.NODES)
broadcast_new_node_list()
return {"message": "Goodbye"}
return {"message": "You are not part of this game"}
# Used when a new player joins a game
def broadcast_new_node_list():
for node in state.NODES.values():
if node["player_number"] == state.OWN_NODE_NUMBER:
continue
new_node_list: NewNodeListMessage = {"nodes": state.NODES, "next_player_number": state.NEXT_PLAYER_NUMBER}
requests.post(
f"http://{node['ip']}:6376/new-node-list", json=new_node_list
)
@app.route("/new-node-list", methods=["POST"])
def new_node_list():
state.NODES = {int(k): v for k, v in request.json["nodes"].items()}
state.NEXT_PLAYER_NUMBER = request.json["next_player_number"]
print("I received the new node list.")
return {"message": "New node list received"}
@app.route("/helper-key", methods=["POST"])
def handle_helper_key():
json: ShareKeyRequest = request.json
state.HELPER_ENCRYPTION_KEY = json["key"]
print("I received the helper node encryption key.")
return {"message": "Thanks!"}
@app.route("/leader-key", methods=["POST"])
def handle_leader_key():
json: ShareKeyRequest = request.json
state.ENCRYPTION_KEY = json["key"]
print("I received the leader node private key.")
return {"message": "Thanks!"}
@app.route("/game-starting", methods=["POST"])
def game_starting():
state.GAME_PHASE = GamePhase.GAME_ONGOING
print("Game started.")
return {"message": "Ok"}
@app.route("/plz-help-with-encrypting", methods=["POST"])
def handle_plz_help_with_encrypting() -> PlzHelpWithEncryptingDeckResponse:
json: PlzHelpWithEncryptingDeckRequest = request.json
deck = Deck(json["deck"])
state.HELPER_ENCRYPTION_KEY = deck.encrypt_all()
deck.shuffle()
state.DOUBLE_ENCRYPTED_DECK = deck
# Broadcast to all other nodes so that everyone can later verify that there has been no cheating
for node in state.NODES.values():
if node["player_number"] == state.OWN_NODE_NUMBER or node["player_number"] == state.LEADER_NODE_NUMBER:
continue
requests.post(
f"http://{node['ip']}:6376/double-encrypted-deck", json={"deck": state.DOUBLE_ENCRYPTED_DECK.cards_to_json()}
)
return {"deck": state.DOUBLE_ENCRYPTED_DECK.cards_to_json()}
@app.route("/double-encrypted-deck", methods=["POST"])
def handle_double_encrypted_deck():
json: DoubleEncryptedDeckRequest = request.json
deck = Deck(json["deck"])
state.DOUBLE_ENCRYPTED_DECK = deck
print("Copy of double encrypted deck received and stored for later game verification.")
return {"message": "Ok"}
@app.route("/winner", methods=["POST"])
def handle_winner():
json: WinnerRequest = request.json
state.WINNER_NUMBER = json["winner"]
print(f"Winner is player #{state.WINNER_NUMBER}")
state.GAME_PHASE = GamePhase.VOTING
if state.WINNER_NUMBER == state.OWN_NODE_NUMBER:
print("I am the winner")
else:
print("I lost")
Thread(target=verify_game_and_participate_in_fairness_voting).start()
return {"message": "Ok"}
def verify_game_and_participate_in_fairness_voting():
verify_dealt_cards = {}
print("Verifying game.")
for node in sorted(state.NODES):
verify_dealt_cards[node] = state.DOUBLE_ENCRYPTED_DECK.pop()
highest_card = -1
highest_card_owner = None
for (node, card) in verify_dealt_cards.items():
helper_decrypted_card = Deck.decrypt_helper(state.HELPER_ENCRYPTION_KEY, card)
decrypted_card_value = Deck.decrypt_one(state.ENCRYPTION_KEY, helper_decrypted_card)
print(f"According to my knowledge, player {node} received card {decrypted_card_value}.")
if decrypted_card_value > highest_card:
highest_card = decrypted_card_value
highest_card_owner = node
agree_on_winner = highest_card_owner == state.WINNER_NUMBER
print(f"I found out that the player {highest_card_owner} is the winner, my agreement with leader: {agree_on_winner}")
game_winner: GameWinnerVerificationResultRequest = { "agree": agree_on_winner }
share_your_fairness_vote_and_wait_for_results(game_winner, state.WINNER_NUMBER)
@app.route("/game-winner-verification-result", methods=["POST"])
def handle_game_winner_verification_result():
json: GameWinnerVerificationResultRequest = request.json
if json["agree"]:
state.NUMBER_OF_NODES_THAT_AGREE_WITH_THE_RESULT += 1
else:
state.NUMBER_OF_NODES_THAT_DISAGREE_WITH_THE_RESULT += 1
return {"message": "Ok"}
@app.route("/deal-results", methods=["POST"])
def handle_deal_results():
json: DealResultsBroadcastRequest = request.json
state.DEAL_RESULTS = json["who_got_what_cards"]
# Fetch the helper ip to inform I got the dealt cards
helper_ip = next(
node["ip"] for node in state.NODES.values() if node["player_number"] == json["helper_player_number"]
)
requests.post(f"http://{helper_ip}:6376/i-got-the-dealt-cards")
return {"message": "Ok"}
i_got_the_dealt_cards_lock = Lock()
# Helper receives this when others have gotten the dealt cards
@app.route("/i-got-the-dealt-cards", methods=["POST"])
def handle_i_got_the_dealt_cards():
try:
i_got_the_dealt_cards_lock.acquire()
origin = request.remote_addr
sender_player = next(node for node in state.NODES.values() if node["ip"] == origin)
state.HELPER_MAP_WHO_GOT_THE_CARDS[sender_player["player_number"]] = True
# If the majority of nodes have gotten the deck, we can pubish our encryption key to the leader
number_of_nodes_who_got_the_cards = len([node for node in state.HELPER_MAP_WHO_GOT_THE_CARDS.values() if node])
if number_of_nodes_who_got_the_cards >= len(state.NODES) / 2:
# Broadcast this to everyone so that the key can be used for verification.
# What is more, the leader should publish their key once they have received this
broadcast_helper_encryption_key()
return {"message": "Ok"}
finally:
i_got_the_dealt_cards_lock.release()
def broadcast_helper_encryption_key():
for node in state.NODES.values():
if node["player_number"] == state.OWN_NODE_NUMBER:
continue
body: ShareKeyRequest = {"key": state.HELPER_ENCRYPTION_KEY}
requests.post(
f"http://{node['ip']}:6376/helper-key", json=body
)
if __name__ == "__main__":
main()