Skip to content
Open

Dev #10

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion src/blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,15 @@
from Crypto.Signature import PKCS1_v1_5
from typing import OrderedDict

from logging_config import setup_logging

# Set up logging
logger = setup_logging()

MINING_SENDER = "THE BLOCKCHAIN"
MINING_REWARD = 1.0
MINING_DIFFICULTY = 2
logger.debug("successfully initalized constants")


class Blockchain:
Expand All @@ -26,19 +32,24 @@ def __init__(self):
self.node_id = str(uuid4()).replace('-', '')
# Create genesis block
self.create_block(nonce=0, previous_hash='00')
logger.debug("successfully initalized Blockchain class parameters")

def register_node(self, node_url):
"""
Add a new node to the list of nodes
"""
# Checking node_url has valid format
parsed_url = urlparse(url=node_url)
logger.debug(f"Trying to add the following node: {parsed_url}")
if parsed_url.netloc:
self.nodes.add(parsed_url.netloc)
logger.info("Successfully added node")
elif parsed_url.path:
# Accepts an URL without scheme like '192.168.0.5:5000'.
self.nodes.add(parsed_url.path)
logger.info("Successfully added node")
else:
logger.warning(f"The given URL '{node_url}' is not valid")
raise ValueError('Invalid URL')

def sign_transaction(self, sender_private_key, transaction):
Expand Down Expand Up @@ -71,28 +82,46 @@ def submit_transaction(self, sender_address, sender_private_key, recipient_addre
'recipient_address': recipient_address,
'value': value
})
# Generate a random transaction id
transaction_id = str(uuid4()).replace('-', '')
logger.info(f"Initating Transaction {transaction_id}")

# If it's a mining reward, skip the signature process
if sender_address == MINING_SENDER:
logger.debug(f"Initating mining process for transaction {
transaction_id}")
self.transactions.append(transaction)
logger.info(f"Transaction {transaction_id} was successfull")
return len(self.chain) + 1

# Manages transactions from wallet to another wallet
else:
logger.debug(f"Transaction {transaction_id} from sender adress ending with {
sender_address[-3:]} to recipient adress ending with {recipient_address[-3:]}")
# Signing Transaction
logger.debug(f"Signing transaction {transaction_id}")
transaction_signature = self.sign_transaction(
sender_private_key=sender_private_key,
transaction=transaction
)
logger.debug(f"Successfully signed transaction {transaction_id}")

# Verifying Transaction
logger.debug(f"Verifying transaction {transaction_id}")
transaction_verification = self.verify_transaction_signature(
sender_address=sender_address,
signature=transaction_signature,
transaction=transaction
)
logger.debug(f"Successfully verfied transaction {transaction_id}")

if transaction_verification:
self.transactions.append(transaction)
logger.info(f"Transaction {transaction_id} was successfull")
return len(self.chain) + 1
else:
logger.warning(
f"Transaction {transaction_id} wasn't successfull")
return False

def get_balance(self, address):
Expand All @@ -101,6 +130,8 @@ def get_balance(self, address):
"""
balance = 0.0

logger.debug(f"Calculating balance for the wallet ending with '{
address[-3:]}'")
# Iterate through all blocks in the chain
for block in self.chain:
# Iterate through all transactions in each block
Expand All @@ -112,7 +143,8 @@ def get_balance(self, address):
# Check if the wallet is the recipient
if transaction['recipient_address'] == address:
balance += transaction['value']

logger.debug(f" balance for the wallet ending with '{
address[-3:]}' is: {balance}")
return balance

def get_available_balance(self, address):
Expand Down
45 changes: 45 additions & 0 deletions src/logging_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import os
import sys
import logging
import logging.config


def setup_logging():
"""
Sets up the logging configuration for the application.

The configuration includes:
- Console output with detailed formatting.
- Log level controlled by the LOG_LEVEL environment variable.

The default log level is DEBUG if not specified.
"""
log_level = os.getenv('LOG_LEVEL', 'DEBUG').upper()

logging_config = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'detailed': {
'format': '[%(asctime)s] %(levelname)s in %(filename)s, function %(funcName)s: %(message)s'
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'level': log_level,
'formatter': 'detailed',
'stream': sys.stdout,
},
},
'root': {
'handlers': ['console'],
'level': log_level,
},
}

# Apply the logging configuration
logging.config.dictConfig(logging_config)

# Return a logger object for this module
return logging.getLogger(__name__)
38 changes: 20 additions & 18 deletions tests/test_blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,23 @@
from Crypto.PublicKey import RSA
import requests

from src.blockchain import MINING_REWARD, Blockchain, MINING_SENDER # Assuming you save your class in a file called blockchain.py


sys.path.insert(0, os.path.abspath(
# to solve import issues in src
os.path.join(os.path.dirname(__file__), '../src')))

from src.blockchain import MINING_REWARD, Blockchain, MINING_SENDER

class TestBlockchain(unittest.TestCase):

def setUp(self):
self.blockchain = Blockchain()
self.private_key = RSA.generate(1024) # while RSA key sizes below 2048 bits are considered breakable, this is for test only.
# while RSA key sizes below 2048 bits are considered breakable, this is for test only.
self.private_key = RSA.generate(1024)
self.public_key = self.private_key.publickey()
self.sender_private_key = binascii.hexlify(self.private_key.exportKey(format='DER')).decode('ascii')
self.sender_address = binascii.hexlify(self.public_key.exportKey(format='DER')).decode('ascii')
self.sender_private_key = binascii.hexlify(
self.private_key.exportKey(format='DER')).decode('ascii')
self.sender_address = binascii.hexlify(
self.public_key.exportKey(format='DER')).decode('ascii')
self.recipient_address = str(uuid4()).replace('-', '')

def test_register_node_valid(self):
Expand All @@ -34,7 +36,7 @@ def test_register_node_invalid(self):
'????',
'####',
]

for url in invalid_urls:
with self.assertRaises(ValueError):
self.blockchain.register_node(url)
Expand Down Expand Up @@ -108,18 +110,19 @@ def test_get_available_balance(self):
self.sender_address, self.sender_private_key, self.recipient_address, 50)

# Available balance should be confirmed balance + pending transaction amount
available_balance = self.blockchain.get_available_balance(self.recipient_address)
available_balance = self.blockchain.get_available_balance(
self.recipient_address)

# The available balance should now be the mining reward + 50
self.assertEqual(available_balance, confirmed_balance + 50)


def test_create_block(self):
block = self.blockchain.create_block(nonce=12345, previous_hash='abcd')

self.assertEqual(block['block_number'], 2)
self.assertEqual(block['previous_hash'], 'abcd')
self.assertEqual(len(block['transactions']), 0) # Transactions list should be reset
# Transactions list should be reset
self.assertEqual(len(block['transactions']), 0)
self.assertEqual(block['nonce'], 12345)

def test_hash_block(self):
Expand Down Expand Up @@ -148,7 +151,8 @@ def test_invalid_proof(self):
def test_valid_chain(self):
# Create the first block (genesis block is already created)
previous_block = self.blockchain.chain[-1]
previous_hash = self.blockchain.hash(previous_block) # Correct previous hash
previous_hash = self.blockchain.hash(
previous_block) # Correct previous hash
nonce = self.blockchain.proof_of_work() # Calculate valid nonce

# Create a new block with valid nonce and previous hash
Expand All @@ -157,7 +161,6 @@ def test_valid_chain(self):
# Test if the blockchain is valid
self.assertTrue(self.blockchain.valid_chain(self.blockchain.chain))


def test_invalid_chain(self):
# Submit a transaction to ensure the block has a transaction
self.blockchain.submit_transaction(
Expand All @@ -172,12 +175,12 @@ def test_invalid_chain(self):
self.blockchain.create_block(nonce=nonce, previous_hash=previous_hash)

# Tamper with the chain by modifying the value of the transaction
self.blockchain.chain[1]['transactions'][0]['value'] = 999 # Tampering the chain
# Tampering the chain
self.blockchain.chain[1]['transactions'][0]['value'] = 999

# Test if the chain is invalid
self.assertFalse(self.blockchain.valid_chain(self.blockchain.chain))


def test_resolve_conflicts(self):
# Create another blockchain instance with a longer chain
other_blockchain = Blockchain()
Expand All @@ -187,7 +190,8 @@ def test_resolve_conflicts(self):
previous_block = other_blockchain.chain[-1]
previous_hash = other_blockchain.hash(previous_block)
nonce = other_blockchain.proof_of_work()
other_blockchain.create_block(nonce=nonce, previous_hash=previous_hash)
other_blockchain.create_block(
nonce=nonce, previous_hash=previous_hash)

# Simulate adding a node and that node providing the other blockchain
self.blockchain.nodes.add('localhost:5000') # Simulate another node
Expand All @@ -210,7 +214,5 @@ def test_resolve_conflicts(self):
# Assert that our blockchain's chain is now the same as the other blockchain's chain
self.assertEqual(self.blockchain.chain, other_blockchain.chain)



if __name__ == '__main__':
unittest.main()
unittest.main()
Loading
Loading