-
Notifications
You must be signed in to change notification settings - Fork 0
/
Node.py
338 lines (310 loc) · 18.9 KB
/
Node.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
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
import queue as Q
import time
from Message import Message
from Transaction import Transaction,Input,Output
from BlockChain import Block,BlockChain
from crypto_functions import generate_public_private_keys
from MerkleTree import generate_hash
from Miner import Miner
import random
def coin_toss(prob):
if random.uniform(0,1) <= prob:
return True
else:
return False
def pick_receiver(id,N):
receiver= id
while receiver == id:
receiver = random.randint(0,N-1)
return receiver
class Node(): ### This node can function as both worker and mining node!!!
def __init__(self,id,N,narry,prowk,hash_type,confirmation_blocks,num_msg_limit,msg_ct_list,debug=False):
self.id = id
self.N = N
self.debug = debug
self.narry = narry
self.proof_of_work_zeros = prowk
self.hash_type = hash_type
self.block_reward = 10
self.confirmation_blocks = confirmation_blocks
self.transaction_fee = 1
self.block_creation_time = 30
self.public_key,self.private_key = generate_public_private_keys()
self.public_key_hash = generate_hash(self.public_key.hex(),type = hash_type)
self.btc = 0
self.unspent_btc = {}
self.msg_ct_limit = num_msg_limit
self.msg_ct_list = msg_ct_list
self.start_time = time.time()
def time_spent(self):
return int(time.time() - self.start_time)
def update_public_key_of_node(self,nodeid,public_key):
self.public_keys_of_nodes[nodeid] = public_key
self.node_id_of_public_key[public_key] = nodeid
def update_unspent_btc_from_block(self,block): ### only called when block is confirmed
for tx in block.transactions:
txid = tx.txid
sender_address = bytes.fromhex(tx.sender_address)
for index,output in enumerate(tx.tx_out):
if output.to_address == self.public_key_hash:
key = txid+"-"+str(index)
if key not in self.unspent_btc:
if tx.t_type == "COINBASE":
print("{} Received {} BTC as block creation reward".format(str(self.id),str(output.amount)))
else:
if self.id != self.node_id_of_public_key[sender_address]:
print("{} Received {} BTC from {}".format(str(self.id),str(output.amount),self.node_id_of_public_key[sender_address]))
self.unspent_btc[key] = [sender_address,output.amount,txid,index]
self.btc += output.amount
else:
print("RED ALERT:already received money from this transaction,", self.id)
if sender_address == self.public_key and tx.t_type !='COINBASE':
for index,input in enumerate(tx.tx_in):
key = input.prev_output_tid +"-"+str(input.prev_output_index)
self.btc -= self.unspent_btc[key][1]
if self.last_transaction_key is not None:
if key == self.last_transaction_key:
self.last_transaction_verified = True
print(self.id, "Time taken for transaction verification ",time.time()-self.last_transaction_time)
print("{} verified last transaction".format(str(self.id)))
self.last_transaction_key = None
#print("{} have received {} awards BTC".format(str(self.id),str(self.btc)))
del self.unspent_btc[key]
def print_self_balance(self):
total_balance = 0
for key in self.unspent_btc.keys():
total_balance += self.unspent_btc[key][1]
print("{} has balance of {} BTC".format(str(self.id),str(total_balance)))
def return_self_balance(self):
total_balance = 0
for key in self.unspent_btc.keys():
total_balance += self.unspent_btc[key][1]
return total_balance
def main(self,msg_qs,outputq):
# print(self.id, len(self.msg_ct_list))
# self.msg_ct_list.append(self.id)
# print(self.id, self.msg_ct_list)
done = False
self.public_keys_of_nodes = [None]*self.N
self.public_keys_of_nodes[self.id] = self.public_key
self.node_id_of_public_key = {}
self.node_id_of_public_key[self.public_key] = self.id
self.last_transaction_verified = True
self.last_transaction_key = None
ct = 0
for i in range(0,self.N):
if i != self.id:
msg_qs[i].put(Message("PUBLIC_KEY",self.public_key,self.id,i))
ct =0
while ct<self.N-1:
try:
msg = msg_qs[self.id].get(block=True,timeout=10)
if msg.type == "PUBLIC_KEY":
self.update_public_key_of_node(msg.src,msg.msg)
ct += 1
except Q.Empty:
print(" No message and wait for the public keys")
if self.debug:
print("Received public key total from ct {} from every node to {}".format(str(ct),str(self.id)))
miner = Miner(self.id,self.N,self.narry,self.proof_of_work_zeros,self.hash_type,self.confirmation_blocks,self.private_key,self.public_keys_of_nodes,self.node_id_of_public_key,self.debug)
debug_block_creation_time = True
if self.id == 0: ## create a genesis block
inputs = [Input('None','None')]
outputs = []
total_amount_generated = 0
print("Initial money")
for i in range(0,self.N):
amount = 1000
if i == 0:
amount += self.block_reward
total_amount_generated += amount
print(i, amount)
outputs.append(Output(self.public_keys_of_nodes[i].hex(),amount,self.hash_type))
coinbase_t = Transaction(self.public_keys_of_nodes[self.id],inputs,outputs,self.private_key,t_type = 'COINBASE',hash_type=self.hash_type)
miner.blockchain = BlockChain(self.narry,self.proof_of_work_zeros,self.hash_type)
if debug_block_creation_time:
start_time_block = time.time()
miner.blockchain.add_genesis_block([coinbase_t])
if debug_block_creation_time:
print("Time taken in genesis block creation,", time.time()-start_time_block)
for i in range(1,self.N):
if self.debug:
print("Sending everyone else the genesis block ", self.id)
msg_qs[i].put(Message("GENESIS_BLOCK",miner.blockchain,self.id,i))
self.update_unspent_btc_from_block(miner.blockchain.blockchain[0])
else: ### everyone else will receive the genesis block and use it further for transaction
received_genesis_block = False
while not received_genesis_block:
try:
msg = msg_qs[self.id].get(block=True,timeout=10)
if msg.type == "GENESIS_BLOCK":
miner.blockchain = msg.msg
received_genesis_block = True
except Q.Empty:
print(" Waiting for genesis block," , self.id)
if self.debug:
print("Received genesis block, now need to verify it and update its own money ", self.id)
#msg.msg.blockchain[0].nounce = '223'
if miner.verify_genesis_block():
if self.debug:
print("verfied genesis block")
self.update_unspent_btc_from_block(miner.blockchain.blockchain[0])
done = False
die_out_seconds = 120
timeout_seconds = 1
start_time = time.time()
last_block_cor = time.time()
last_transaction_time = time.time()
waited_time = 0
debug_size = False
debug_smt_contract= False
debug_multi_transaction = False
debug_smt_execute_once = False
debug_smt_repeat = False
smt_contract = False
debug_block_creation_time = False
debug_block_creation_time_num_of_nodes = False
if debug_smt_contract and self.id == 0:
debug_smt_execute_once = True
smt_contract = True
if debug_smt_contract and self.id == 1:
debug_smt_repeat = True
smt_contract = True
if debug_size and self.id == 0:
print(self.id, " size of block chain ", miner.blockchain.size())
while not done:
try:
#print(self.id , "main checking for message")
msg= msg_qs[self.id].get(block=True,timeout=timeout_seconds)
waited_time = 0
if msg.type == "Transaction":
#print(self.id,"Transaction has been received from ", msg.src )
if msg.msg.verify_sign_transaction():
miner.transactions_collected.append(msg.msg) ###check if already exist in blockchain
last_transaction_time = time.time()
elif msg.type == "Block":
if self.debug:
print("Node " ,self.id , "Block has been received from", msg.src)
status = miner.add_block(msg.msg)
if status :
#print(self.id, " Received block has been added to blockchain ")
last_block_cor = time.time()
else:
print(self.id, " Received block was not added")
else:
print("Node ",self.id, " Received message of type , I dont do what do i do with it", msg.type , msg.src)
except Q.Empty:
waited_time += timeout_seconds
if waited_time > die_out_seconds:
print("waited for transactions for more than ",die_out_seconds," seconds, seems none is using bitcoin, lets die")
#print("I {} had a amount of {} BTC".format(str(self.id),str(self.btc)))
self.print_self_balance()
return
else:
if time.time() - last_transaction_time > 15 and self.last_transaction_verified :
if coin_toss(1.0/self.N) and (not debug_smt_contract or (debug_smt_contract and (self.id in [0,1,3,4]))):
#if True:
self.msg_ct_list.append(self.id)
if len(self.msg_ct_list) <= self.msg_ct_limit:
send_to = pick_receiver(self.id,self.N)
if debug_smt_contract and self.id == 0:
send_to = 8
if debug_smt_contract and self.id == 1:
send_to = 9
amount = random.randint(1,4)
if debug_smt_execute_once and self.return_self_balance() >= 700:
amount = 100
debug_smt_execute_once = False
print("############\nNode ",self.id, " : balance is ", self.return_self_balance() ," so as smart contract of execute once, sending ", amount , "btc to ",send_to ,"\n############")
else:
if debug_smt_repeat and self.return_self_balance() >= 700:
amount = 100
print("############\nNode " ,self.id, " : balance is ", self.return_self_balance() ," so as smart contract of repeat, sending ", amount , "btc to ",send_to,"\n############")
else:
amount = random.randint(1,4)
unspent_key = None
for key in self.unspent_btc.keys():
if amount +3 <= self.unspent_btc[key][1]:
unspent_key = key
if unspent_key is None:
print("{} has no money left , so dying".format(str(self.id)))
else:
#key = list(self.unspent_btc.keys())[0]
#print("{} has amount {} btc in selected key".format(str(self.id),str(self.unspent_btc[unspent_key][1])))
if debug_multi_transaction:
send_to_v = pick_receiver(self.id,self.N)
inputs = [Input(self.unspent_btc[unspent_key][2],self.unspent_btc[unspent_key][3])]
outputs = [Output(self.public_keys_of_nodes[send_to].hex(),amount,self.hash_type),Output(self.public_keys_of_nodes[send_to_v].hex(),amount+2,self.hash_type),Output(self.public_keys_of_nodes[self.id].hex(),self.unspent_btc[unspent_key][1]-amount-amount-2-1,self.hash_type)]
print("Node {} Sending {} btc to node {} and {} btc to node {} ".format(str(self.id),str(amount),str(send_to),str(amount+2),str(send_to_v)))
else:
inputs = [Input(self.unspent_btc[unspent_key][2],self.unspent_btc[unspent_key][3])]
outputs = [Output(self.public_keys_of_nodes[send_to].hex(),amount,self.hash_type),Output(self.public_keys_of_nodes[self.id].hex(),self.unspent_btc[unspent_key][1]-amount-1,self.hash_type)]
transaction = Transaction(self.public_keys_of_nodes[self.id],inputs,outputs,self.private_key,t_type = 'Regular',hash_type=self.hash_type)
for i in range(0, self.N):
if i != self.id:
msg_qs[i].put(Message("Transaction",transaction,self.id,i))
miner.transactions_collected.append(transaction)
self.last_transaction_verified = False
self.last_transaction_key = unspent_key
#del self.unspent_btc[key] #### check for this and
self.last_transaction_time = time.time()
print("Node {} Sending {} btc to node {} ".format(str(self.id),str(amount),str(send_to)))
waited_time = 0
if time.time() - last_block_cor > self.block_creation_time:
#last_block_cor= time.time()
if coin_toss(1*1.0/self.N):
#if True and self.id == 0:
print(self.id, " Creating a block ", len(miner.transactions_collected))
if debug_block_creation_time:
print("starting debug block time creatioin")
times = []
for i in range(0,100):
block_start_time = time.time()
miner.create_block()
block_end_time = time.time()
times.append(block_end_time-block_start_time)
print(self.id," block chain creation times", times)
new_block = miner.create_block()
if new_block is not None:
#
if self.debug:
print(self.id, " block has been created but first check if someone computed first")
send_block_to_ee = False
empty_queue = False
temp_msgs = []
while not empty_queue:
try:
msg= msg_qs[self.id].get(block=True,timeout=timeout_seconds)
if msg.type == "Block":
if self.debug:
print(self.id , " Ohh Received a block from ", msg.src)
miner.add_block(msg.msg)
empty_queue = True
else:
temp_msgs.append(msg)
except Q.Empty:
send_block_to_ee = True
empty_queue = True
for msg in temp_msgs:
msg_qs[self.id].put(Message(msg.type,msg.msg,msg.src,msg.dst)) ## putting it back so can be fetched again in main loop
if send_block_to_ee:
#if self.debug:
waited_time = 0
print(self.id, " No block has been recieved, so send it everyone")
print(self.id, " size of blockchain containing " , len(new_block.transactions), " and size of ", new_block.size_merkle())
for i in range(0,self.N):
if i != self.id:
if self.debug:
print(self.id, " sending created block to ", i)
msg_qs[i].put(Message("Block",new_block,self.id,i))
miner.add_block(new_block)
#last_block_cor = time.time()
res = miner.increase_verified_block()
#print(self.id, "after adding status",res)
last_block_cor = time.time()
if res:
if debug_size and self.id == 0:
print(self.id, " size of block chain after adding ", miner.blockchain.size())
self.update_unspent_btc_from_block(miner.blockchain.blockchain[miner.blockchain.confirmed_block_index]) ## it will
## also need to check if last payment by this node is verified
self.print_self_balance()