Skip to content

Commit

Permalink
Coinbase Transactions and Blockchain class - to be improved in future
Browse files Browse the repository at this point in the history
  • Loading branch information
HXM4Tech committed Jul 15, 2022
1 parent f506218 commit ba6781c
Showing 1 changed file with 131 additions and 16 deletions.
147 changes: 131 additions & 16 deletions src/blockchain.py
Expand Up @@ -28,6 +28,14 @@ def get_difficulty(height: int) -> int:

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

def get_block_reward(height: int) -> int:
start_reward = 50 * 10**9

if height < 100000:
return start_reward

return int(start_reward / (2 * (height // 100000))) # reward of mining decrases every 100000 blocks

################################################################
# SIMPLE TO USE Signature, PrivateKey AND PublicKey OBJECTS
################################################################
Expand Down Expand Up @@ -219,6 +227,12 @@ def serialize(self) -> str:
def __repr__(self) -> str:
return f"tx:\n sender: {self.sender.address}\n recipient: {self.recipient}\n amount: {self.amount}\n signed: {self.signature is not None}"

def __eq__(self, other):
return self.hash() == other.hash()

def __ne__(self, other):
return not self == other

@classmethod
def parse(cls, serialization: str):
try:
Expand All @@ -239,7 +253,59 @@ def parse(cls, serialization: str):
raise ValueError("Invalid hash in JSON serialization")

except KeyError:
raise ValueError("This is not valid Transaction serialization")
raise ValueError("This is not valid Transaction JSON serialization")

class CoinbaseTransaction(Transaction):
def __init__(self, height: int, recipient: str, prev_hash: bytes):
super().__init__(
sender = None,
recipient = recipient,
amount = get_block_reward(height),
prev_hash = prev_hash
)

self.height = height

def sign(self, privkey: PrivateKey):
raise NotImplementedError("Coinbase transactions cannot be signed")

def verify(self) -> bool:
return self.amount == get_block_reward(self.height)

def hash(self) -> bytes:
return hash256(self.prev_hash + self.recipient.encode("ascii") + self.amount.to_bytes((self.amount.bit_length() + 7) // 8, "big"))

def serialize(self) -> str:
return json.dumps({
"height": self.height,
"recipient": self.recipient,
"amount": self.amount / 1000000000,
"prev_hash": self.prev_hash.hex(),
"hash": self.hash().hex()
})

def __repr__(self) -> str:
return f"coinbase:\n recipient: {self.recipient}\n amount: {self.amount}\n height: {self.height}"

@classmethod
def parse(cls, serialization: str):
try:
data = json.loads(serialization)
except json.JSONDecodeError:
raise ValueError("Cannot parse JSON serialization")

try:
out = cls(
height = int(data["height"]),
recipient = data["recipient"],
prev_hash = bytes.fromhex(data["prev_hash"])
)

if out.hash().hex() != data["hash"]:
raise ValueError("Invalid hash in JSON serialization")

except KeyError:
raise ValueError("This is not valid CoinbaseTransaction JSON serialization")

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

return hash256(m)

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

def mine(self):
def mine(self, reward_address: str, prev_tx_hash: bytes):
if len(self.transactions) > 0:
prev_tx_hash = self.transactions[-1].hash()

self.transactions.append(
CoinbaseTransaction(
height = self.height,
recipient = reward_address,
prev_hash = prev_tx_hash
)
)

difficulty = get_difficulty(self.height)
self.nonce = 0

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

class Blockchain:
def __init__(self):
self.block_list = []
self.difficulty = 6
self.chain = []
self.pending_transactions = []

def add_block(self, block: Block):
self.block_list.append(block)
if not block.validate():
raise ValueError("Invalid block")

if len(self.chain) > 0:
if block.prev_hash != self.chain[-1].hash():
raise ValueError("Invalid or late block")
else:
if block.prev_hash != bytes(32):
raise ValueError("Previous hash of genesis block is not zero")

self.chain.append(block)

def get_block(self, pos: int) -> Block:
return self.block_list[pos]
def add_transaction(self, tx: Transaction):
if not tx.verify():
raise ValueError("Invalid transaction")

self.pending_transactions.append(tx)

def mine(self, block: Block):
while True:
hash = block.calculate_hash()
if hash[:self.difficulty] == "0"*self.difficulty:
block.hash = hash
break
def create_transaction(self, tx: Transaction):
self.add_transaction(tx)

block.nonce += 1
# TODO: broadcast new transaction to all nodes

self.add_block(block)
def mine(self, reward_address: str):
if len(self.chain) > 0:
last_block_hash = self.chain[-1].hash()
lest_tx_hash = self.chain[-1].transactions[-1].hash()
else:
last_block_hash = bytes(32)
lest_tx_hash = bytes(32)

new_block = Block(
height = len(self.chain),
transactions = self.pending_transactions,
prev_hash = last_block_hash
)

new_block.mine(
reward_address = reward_address,
prev_tx_hash = lest_tx_hash
)

for tx in self.pending_transactions.copy():
if tx in new_block.transactions:
try:
self.pending_transactions.remove(tx)
except ValueError:
pass

self.add_block(new_block)

# TODO: broadcast new block to all nodes

0 comments on commit ba6781c

Please sign in to comment.