@@ -28,6 +28,14 @@ def get_difficulty(height: int) -> int:
28
28
29
29
return start_difficulty + (height // 100000 ) # difficulty of mining increases every 100000 blocks
30
30
31
+ def get_block_reward (height : int ) -> int :
32
+ start_reward = 50 * 10 ** 9
33
+
34
+ if height < 100000 :
35
+ return start_reward
36
+
37
+ return int (start_reward / (2 * (height // 100000 ))) # reward of mining decrases every 100000 blocks
38
+
31
39
################################################################
32
40
# SIMPLE TO USE Signature, PrivateKey AND PublicKey OBJECTS
33
41
################################################################
@@ -219,6 +227,12 @@ def serialize(self) -> str:
219
227
def __repr__ (self ) -> str :
220
228
return f"tx:\n sender: { self .sender .address } \n recipient: { self .recipient } \n amount: { self .amount } \n signed: { self .signature is not None } "
221
229
230
+ def __eq__ (self , other ):
231
+ return self .hash () == other .hash ()
232
+
233
+ def __ne__ (self , other ):
234
+ return not self == other
235
+
222
236
@classmethod
223
237
def parse (cls , serialization : str ):
224
238
try :
@@ -239,7 +253,59 @@ def parse(cls, serialization: str):
239
253
raise ValueError ("Invalid hash in JSON serialization" )
240
254
241
255
except KeyError :
242
- raise ValueError ("This is not valid Transaction serialization" )
256
+ raise ValueError ("This is not valid Transaction JSON serialization" )
257
+
258
+ class CoinbaseTransaction (Transaction ):
259
+ def __init__ (self , height : int , recipient : str , prev_hash : bytes ):
260
+ super ().__init__ (
261
+ sender = None ,
262
+ recipient = recipient ,
263
+ amount = get_block_reward (height ),
264
+ prev_hash = prev_hash
265
+ )
266
+
267
+ self .height = height
268
+
269
+ def sign (self , privkey : PrivateKey ):
270
+ raise NotImplementedError ("Coinbase transactions cannot be signed" )
271
+
272
+ def verify (self ) -> bool :
273
+ return self .amount == get_block_reward (self .height )
274
+
275
+ def hash (self ) -> bytes :
276
+ return hash256 (self .prev_hash + self .recipient .encode ("ascii" ) + self .amount .to_bytes ((self .amount .bit_length () + 7 ) // 8 , "big" ))
277
+
278
+ def serialize (self ) -> str :
279
+ return json .dumps ({
280
+ "height" : self .height ,
281
+ "recipient" : self .recipient ,
282
+ "amount" : self .amount / 1000000000 ,
283
+ "prev_hash" : self .prev_hash .hex (),
284
+ "hash" : self .hash ().hex ()
285
+ })
286
+
287
+ def __repr__ (self ) -> str :
288
+ return f"coinbase:\n recipient: { self .recipient } \n amount: { self .amount } \n height: { self .height } "
289
+
290
+ @classmethod
291
+ def parse (cls , serialization : str ):
292
+ try :
293
+ data = json .loads (serialization )
294
+ except json .JSONDecodeError :
295
+ raise ValueError ("Cannot parse JSON serialization" )
296
+
297
+ try :
298
+ out = cls (
299
+ height = int (data ["height" ]),
300
+ recipient = data ["recipient" ],
301
+ prev_hash = bytes .fromhex (data ["prev_hash" ])
302
+ )
303
+
304
+ if out .hash ().hex () != data ["hash" ]:
305
+ raise ValueError ("Invalid hash in JSON serialization" )
306
+
307
+ except KeyError :
308
+ raise ValueError ("This is not valid CoinbaseTransaction JSON serialization" )
243
309
244
310
class Block :
245
311
def __init__ (self , height : int , transactions : list [Transaction ], prev_hash : str = None , nonce : int = None ):
@@ -258,7 +324,7 @@ def hash(self) -> bytes:
258
324
m = len (self .transactions ).to_bytes (4 , "big" ) # max 2^32 transactions per block
259
325
m += b"" .join ([tx .hash () for tx in self .transactions ])
260
326
m += self .prev_hash
261
- m += self .nonce .to_bytes (8 , "big" )
327
+ m += self .nonce .to_bytes (16 , "big" )
262
328
263
329
return hash256 (m )
264
330
@@ -271,8 +337,20 @@ def serialize(self) -> str:
271
337
"hash" : self .hash ().hex ()
272
338
})
273
339
274
- def mine (self ):
340
+ def mine (self , reward_address : str , prev_tx_hash : bytes ):
341
+ if len (self .transactions ) > 0 :
342
+ prev_tx_hash = self .transactions [- 1 ].hash ()
343
+
344
+ self .transactions .append (
345
+ CoinbaseTransaction (
346
+ height = self .height ,
347
+ recipient = reward_address ,
348
+ prev_hash = prev_tx_hash
349
+ )
350
+ )
351
+
275
352
difficulty = get_difficulty (self .height )
353
+ self .nonce = 0
276
354
277
355
while self .hash ().hex ()[0 :difficulty ] != "0" * difficulty :
278
356
self .nonce += 1
@@ -310,22 +388,59 @@ def parse(cls, serialization: str):
310
388
311
389
class Blockchain :
312
390
def __init__ (self ):
313
- self .block_list = []
314
- self .difficulty = 6
391
+ self .chain = []
392
+ self .pending_transactions = []
315
393
316
394
def add_block (self , block : Block ):
317
- self .block_list .append (block )
395
+ if not block .validate ():
396
+ raise ValueError ("Invalid block" )
397
+
398
+ if len (self .chain ) > 0 :
399
+ if block .prev_hash != self .chain [- 1 ].hash ():
400
+ raise ValueError ("Invalid or late block" )
401
+ else :
402
+ if block .prev_hash != bytes (32 ):
403
+ raise ValueError ("Previous hash of genesis block is not zero" )
404
+
405
+ self .chain .append (block )
318
406
319
- def get_block (self , pos : int ) -> Block :
320
- return self .block_list [pos ]
407
+ def add_transaction (self , tx : Transaction ):
408
+ if not tx .verify ():
409
+ raise ValueError ("Invalid transaction" )
410
+
411
+ self .pending_transactions .append (tx )
321
412
322
- def mine (self , block : Block ):
323
- while True :
324
- hash = block .calculate_hash ()
325
- if hash [:self .difficulty ] == "0" * self .difficulty :
326
- block .hash = hash
327
- break
413
+ def create_transaction (self , tx : Transaction ):
414
+ self .add_transaction (tx )
328
415
329
- block . nonce += 1
416
+ # TODO: broadcast new transaction to all nodes
330
417
331
- self .add_block (block )
418
+ def mine (self , reward_address : str ):
419
+ if len (self .chain ) > 0 :
420
+ last_block_hash = self .chain [- 1 ].hash ()
421
+ lest_tx_hash = self .chain [- 1 ].transactions [- 1 ].hash ()
422
+ else :
423
+ last_block_hash = bytes (32 )
424
+ lest_tx_hash = bytes (32 )
425
+
426
+ new_block = Block (
427
+ height = len (self .chain ),
428
+ transactions = self .pending_transactions ,
429
+ prev_hash = last_block_hash
430
+ )
431
+
432
+ new_block .mine (
433
+ reward_address = reward_address ,
434
+ prev_tx_hash = lest_tx_hash
435
+ )
436
+
437
+ for tx in self .pending_transactions .copy ():
438
+ if tx in new_block .transactions :
439
+ try :
440
+ self .pending_transactions .remove (tx )
441
+ except ValueError :
442
+ pass
443
+
444
+ self .add_block (new_block )
445
+
446
+ # TODO: broadcast new block to all nodes
0 commit comments