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
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
|
import pandas as pd
import random
import time
from threading import Thread
"""
===============================
priest ledger format:
ballot number, decree
n,d
===============================
priest messagebook format:
(priests will track these for changes by messengers)
from,code,ballot,decree
n,1/2/3,(voted at) ballot_number, (voted for) decree
voted_at_ballot: responding to next_ballot from priests
decree decree voted at
codes (to leader):
1: last vote
2: vote: yes
3: vote: no
codes (from leader):
1: next ballot
2: begin ballot
3: succesful ballot
4: failed ballot
===============================
sequence of function writing (follow this when evaluating):
- skeleton
- priest init
- leader_main
- god init
- leader.next_ballot
- messenger init
- leader.messenger.send_next_ballot
- priest_main
- priest.messenger.send_last_vote
- leader.begin_ballot
- leader.messenger.send_begin_ballot
- priest.vote
- priest.messenger.send_vote
TODO:
- leader.evaluate
TODO future:
- condense all messaging functions into a single one using codes
- split classes into different files for readability
- implement priest promise after lastvote (one idea: priest send a message to himself. this would require a new field in messagebook: 'outgoing/self')
TODO far future:
- change communications between priests to socket communications instead of file io
===============================
"""
"""
paxons are very religious; god controls anything and everything
- literally creates priests ie initializes the objects
"""
class god:
num_priests = 0
priests = {} #key:value ~ priest name : priest instance
messengers = []
def __init__(self, num_priests, num_ballots, offset=100):
self.num_priests = num_priests
self.num_ballots = num_ballots
random_leader = random.randint(0,num_priests-1)
#create and name the priests and messengers (quite godly)
for name in range(0, self.num_priests):
if name == random_leader:
self.priests[name] = priest(name, (name+1)*offset, True)
else:
self.priests[name] = priest(name, (name+1)*offset, False)
for priest_name in self.priests.keys():
self.priests[priest_name].start()
for priest_name in self.priests.keys():
self.priests[priest_name].join()
"""
messenger relays messages to and from the leader and the priest
she accomplishes this by tracking the priests' ledger files for changes
each priest (including the leader) has a personal messenger (instance)
"""
class messenger(god):
def __init__(self, serving_priest):
self.serving_priest = serving_priest
"""
leader messenger functions
"""
def send_next_ballot(self,current_list, ballot_num):
message = [[self.serving_priest,1,ballot_num,-1]]
msg_df = pd.DataFrame(data = message)
for priest_name in current_list:
if str(priest_name) == str(self.serving_priest):
pass
else:
print("LOG: leader sending message to priest #" + str(priest_name))
with open('messages/'+str(priest_name), 'a') as f:
msg_df.to_csv(f, header=False, index=False)
def send_begin_ballot(self, quorum, decree, ballot_num):
message = [[self.serving_priest,2,ballot_num,decree]]
msg_df = pd.DataFrame(data = message)
for priest_name in quorum:
print("LOG: leader sending beginBallot to priest #" + str(priest_name))
with open('messages/'+str(priest_name), 'a') as f:
msg_df.to_csv(f, header=False, index=False)
def send_ballot_result(self, priest_name, ballot_num, decree, result):
message = [[self.serving_priest,result,ballot_num,decree]]
msg_df = pd.DataFrame(data = message)
print("LOG: leader sending sucesssful msg to priest #" + str(priest_name))
with open('messages/'+str(priest_name), 'a') as f:
msg_df.to_csv(f, header=False, index=False)
"""
priest messenger functions
"""
def send_last_vote(self,ballot_num, leader_num):
#TODO logic for sending voted at decree in the message (-1 for now)
message = [[self.serving_priest,1,ballot_num,-1]]
msg_df = pd.DataFrame(data = message)
with open('messages/'+str(leader_num), 'a') as f:
print("LOG: priest #" + self.serving_priest + " sending lastVote to leader priest #" + str(leader_num))
msg_df.to_csv(f, header=False, index=False)
def send_vote(self, leader, vote, ballot_num, decree):
message = [[self.serving_priest,vote,ballot_num,decree]]
msg_df = pd.DataFrame(data = message)
with open('messages/'+str(leader), 'a') as f:
print("LOG: priest #" + self.serving_priest + " sending Vote to leader priest")
msg_df.to_csv(f, header=False, index=False)
"""
priests are present (or not present) and vote (or not vote) for ballots proposed
by the leader
they record all ballots they have participated in in individual ledger files
start offset is the offset by which a priest starts assigning ballot numbers when
he is assigned leader to satisfy B1
ex. priests (A, B, C) have offsets (100, 200, 300) (this assumes maximum of 99 ballots)
leader sequence (determined randomly by god): A B A C B A
resulting ballot numbers: 100 200 101 300 201 102
"""
class priest(messenger, Thread):
def __init__(self, name, start_offset, is_leader): #is_leader: temporary var for single ballot test
Thread.__init__(self)
self.name = name #write this name in the ledger in case it gets lost (ledger fileaname)
self.messenger = messenger(self.name) #hire a messenger
self.ledger = "ledgers/" + self.name
self.messagebook = "messages/" + self.name
with open(self.ledger, 'w') as ledgerfile:
ledgerfile.write("ballot,decree\n")
with open(self.messagebook, 'w') as msgbookfile:
msgbookfile.write("from,code,ballot,decree\n")
self.messages_recieved = 0
self.offset = start_offset
self.is_leader = is_leader
def run(self):
if(self.is_leader):
self.leader_main()
else:
self.priest_main()
#=====================leader functions===================
def leader_main(self):
print("LOG: " + self.name + " is the leader")
try:
ledger_data = pd.read_csv(self.ledger)
last_ballot_num= ledger_data.at[ledger_data.shape[0]-1,'ballot_num']
#B1 is satisfied assuming num_ballots < difference between offsets (currently 100)
ballot_num = self.offset + (int(last_ballot_num)%100) + 1
except pd.errors.EmptyDataError: #first pallot ever
ballot_num = self.offset+1
except ValueError: #first ballot ever
ballot_num = self.offset+1
print("LOG: Leader initiating ballot #" + str(ballot_num))
self.next_ballot(ballot_num)
#block till majority set sends lastvote
# temporary: block till all priests send responses
responses = 0
quorum = [] #responded priests
voted_ballot = 0
voted_decree = -1
while responses < len(self.priests.keys())-1:
msg = self.new_message()
quorum.append(int(msg[0]))
responses += 1
#note priest voted decree to satisfy b3
if int(msg[2]) >= 0 and int(msg[2] > voted_ballot):
voted_ballot = int(msg[2])
voted_decree = int(msg[3])
print("LOG: leader got all lastVotes, beginning ballot")
if voted_decree < 0:
voted_decree += 1
self.begin_ballot(quorum, voted_decree, ballot_num)
#block till all votes recieved
votes = 0
votes_yes = 0
while votes < len(quorum):
msg = self.new_message()
votes += 1
if msg[1] == 2:
votes_yes += 1
if votes_yes == len(quorum):
print("LOG: ballot was successful, writing in ledger")
self.send_ballot_result(quorum, ballot_num, voted_decree, 3)
#make entry in ledger
ledger_entry = [[ballot_num, voted_decree]]
ledger_entry_df = pd.DataFrame(data = ledger_entry)
with open(self.ledger, 'a') as f:
ledger_entry_df.to_csv(f, header=False, index=False)
else:
#send failed code
self.send_ballot_result(quorum, ballot_num, voted_decree, 4)
def next_ballot(self,ballot_num):
#randomly choose a number of priests: 40% - 100% of total priests
#rand_num = random.randrange(self.num_priests*0.4, self.num_priests+1)
#current_priests = random.sample(self.priests.keys(), rand_num)
current_priests = self.priests.keys()
#Send the nextBallot message to the priests and wait for their responses
self.messenger.send_next_ballot(current_priests, ballot_num)
#wait for priests' responses
def begin_ballot(self, quorum, decree, ballot_num):
self.messenger.send_begin_ballot(quorum, decree, ballot_num)
#send message to every priest indicating that his vote is requested
def send_ballot_result(self,quorum, ballot_num, decree, result):
for priest_name in quorum:
self.messenger.send_ballot_result(priest_name, ballot_num, decree, result)
#====================regular priest functions======================
def priest_main(self):
print("LOG: " + self.name + " is a priest")
msg = self.new_message() #block for nextBallot from leader
leader = msg[0] # leader
print("LOG: priest #" + self.name + " recieved msg from #" + str(leader))
self.last_vote(leader)
while msg[1] != 2:
msg = self.new_message() # block for beginBallot from leader
if(msg[1] == 2): # recieved beginBallot
#TODO probability of voting variable
#currently: 100% probability of voting yes
vote_yes = random.randrange(0,99)
if vote_yes < 100: #change this value to vary probability
self.vote(leader, 2, msg[2], msg[3])
else:
vote(leader, 3, msg[2], msg[3])
msg = self.new_message() #block for success (or failure) message
if msg[0] == leader and msg[1] == 3:
print("LOG: priest #" + self.name + " writing sucessful ballot in ledger")
ledger_entry = [[msg[2], msg[3]]]
ledger_entry_df = pd.DataFrame(data = ledger_entry)
with open(self.ledger, 'a') as f:
ledger_entry_df.to_csv(f, header=False, index=False)
elif msg[0] == leader and msg[1] == 4:
pass
def last_vote(self,leader_num):
#determine the lastVote and send it to the leader (if not promised to another leader) (might
#need another function for this)
ledger = pd.read_csv(self.ledger)
last_voted = -1
for entry in reversed(range(ledger.shape[0],0)):
if(entry['voted']==1):
last_voted = entry['ballot_num']
break
#responding to a next_ballot request from the leader
#TODO: check for promises before responding
self.messenger.send_last_vote(last_voted, int(leader_num))
#TODO: if responded, set promise to 1 for the relevant maxVote in the ledger
def vote(self, leader, vote, ballot_num, decree):
self.messenger.send_vote(leader, vote, ballot_num, decree)
#======================general functions============================
def new_message(self):
#block till new message read, return message when read
while True:
time.sleep(0.4)
try:
msgbook_data = pd.read_csv(self.messagebook)
if(msgbook_data.shape[0]>self.messages_recieved):
message=msgbook_data.iloc[[self.messages_recieved]].values.tolist()[0]
self.messages_recieved += 1
print("LOG: leader recieved msg from priest #" + str(message[0]))
return message
except pd.errors.EmptyDataError:
pass
if __name__ == '__main__':
god_instance = god(3,5)
|