Skip to content

Commit ba6781c

Browse files
committed
Coinbase Transactions and Blockchain class - to be improved in future
1 parent f506218 commit ba6781c

File tree

1 file changed

+131
-16
lines changed

1 file changed

+131
-16
lines changed

src/blockchain.py

Lines changed: 131 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ def get_difficulty(height: int) -> int:
2828

2929
return start_difficulty + (height // 100000) # difficulty of mining increases every 100000 blocks
3030

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+
3139
################################################################
3240
# SIMPLE TO USE Signature, PrivateKey AND PublicKey OBJECTS
3341
################################################################
@@ -219,6 +227,12 @@ def serialize(self) -> str:
219227
def __repr__(self) -> str:
220228
return f"tx:\n sender: {self.sender.address}\n recipient: {self.recipient}\n amount: {self.amount}\n signed: {self.signature is not None}"
221229

230+
def __eq__(self, other):
231+
return self.hash() == other.hash()
232+
233+
def __ne__(self, other):
234+
return not self == other
235+
222236
@classmethod
223237
def parse(cls, serialization: str):
224238
try:
@@ -239,7 +253,59 @@ def parse(cls, serialization: str):
239253
raise ValueError("Invalid hash in JSON serialization")
240254

241255
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")
243309

244310
class Block:
245311
def __init__(self, height: int, transactions: list[Transaction], prev_hash: str = None, nonce: int = None):
@@ -258,7 +324,7 @@ def hash(self) -> bytes:
258324
m = len(self.transactions).to_bytes(4, "big") # max 2^32 transactions per block
259325
m += b"".join([tx.hash() for tx in self.transactions])
260326
m += self.prev_hash
261-
m += self.nonce.to_bytes(8, "big")
327+
m += self.nonce.to_bytes(16, "big")
262328

263329
return hash256(m)
264330

@@ -271,8 +337,20 @@ def serialize(self) -> str:
271337
"hash": self.hash().hex()
272338
})
273339

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+
275352
difficulty = get_difficulty(self.height)
353+
self.nonce = 0
276354

277355
while self.hash().hex()[0:difficulty] != "0" * difficulty:
278356
self.nonce += 1
@@ -310,22 +388,59 @@ def parse(cls, serialization: str):
310388

311389
class Blockchain:
312390
def __init__(self):
313-
self.block_list = []
314-
self.difficulty = 6
391+
self.chain = []
392+
self.pending_transactions = []
315393

316394
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)
318406

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)
321412

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)
328415

329-
block.nonce += 1
416+
# TODO: broadcast new transaction to all nodes
330417

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

Comments
 (0)