S SmartDocs
Series: blockchain python 357 lines · Updated 2026-02-03

blockchain.py

blockchain/blockchain.py

"""
Blockchain Implementation in Python

This module implements a basic blockchain with:
- Block structure with cryptographic hashing
- Proof of Work (mining)
- Transaction handling
- Chain validation
- Wallet functionality
"""

import hashlib
import json
import time
from typing import List, Dict, Optional
from datetime import datetime
from collections import defaultdict


class Transaction:
    """Represents a transaction in the blockchain."""
    
    def __init__(self, sender: str, recipient: str, amount: float, signature: str = ""):
        self.sender = sender
        self.recipient = recipient
        self.amount = amount
        self.timestamp = time.time()
        self.signature = signature
    
    def to_dict(self) -> Dict:
        """Convert transaction to dictionary."""
        return {
            'sender': self.sender,
            'recipient': self.recipient,
            'amount': self.amount,
            'timestamp': self.timestamp,
            'signature': self.signature
        }
    
    def __repr__(self):
        return f"Transaction({self.sender} -> {self.recipient}: {self.amount})"


class Block:
    """Represents a block in the blockchain."""
    
    def __init__(self, index: int, transactions: List[Transaction], 
                 previous_hash: str, nonce: int = 0):
        self.index = index
        self.timestamp = time.time()
        self.transactions = transactions
        self.previous_hash = previous_hash
        self.nonce = nonce
        self.hash = self.calculate_hash()
    
    def calculate_hash(self) -> str:
        """Calculate the hash of the block."""
        block_string = json.dumps({
            'index': self.index,
            'timestamp': self.timestamp,
            'transactions': [tx.to_dict() for tx in self.transactions],
            'previous_hash': self.previous_hash,
            'nonce': self.nonce
        }, sort_keys=True)
        return hashlib.sha256(block_string.encode()).hexdigest()
    
    def mine_block(self, difficulty: int):
        """Mine the block using Proof of Work."""
        target = "0" * difficulty
        while self.hash[:difficulty] != target:
            self.nonce += 1
            self.hash = self.calculate_hash()
        print(f"Block mined: {self.hash}")
    
    def to_dict(self) -> Dict:
        """Convert block to dictionary."""
        return {
            'index': self.index,
            'timestamp': self.timestamp,
            'transactions': [tx.to_dict() for tx in self.transactions],
            'previous_hash': self.previous_hash,
            'nonce': self.nonce,
            'hash': self.hash
        }
    
    def __repr__(self):
        return f"Block(Index: {self.index}, Hash: {self.hash[:16]}...)"


class Blockchain:
    """Main blockchain class implementing core functionality."""
    
    def __init__(self, difficulty: int = 4, mining_reward: float = 100.0):
        self.chain: List[Block] = [self.create_genesis_block()]
        self.pending_transactions: List[Transaction] = []
        self.difficulty = difficulty
        self.mining_reward = mining_reward
        self.balances: Dict[str, float] = defaultdict(float)
    
    def create_genesis_block(self) -> Block:
        """Create the first block (genesis block) in the chain."""
        genesis_transaction = Transaction(
            sender="0",  # System
            recipient="Genesis",
            amount=0.0
        )
        return Block(0, [genesis_transaction], "0", 0)
    
    def get_latest_block(self) -> Block:
        """Get the most recent block in the chain."""
        return self.chain[-1]
    
    def add_transaction(self, transaction: Transaction) -> bool:
        """Add a transaction to the pending transactions list."""
        # Validate transaction
        if transaction.sender == transaction.recipient:
            print("Error: Sender and recipient cannot be the same")
            return False
        
        if transaction.amount <= 0:
            print("Error: Transaction amount must be positive")
            return False
        
        # Check balance (for non-mining transactions)
        if transaction.sender != "0":  # "0" represents system/mining reward
            if self.balances[transaction.sender] < transaction.amount:
                print(f"Error: Insufficient balance. Available: {self.balances[transaction.sender]}")
                return False
        
        self.pending_transactions.append(transaction)
        print(f"Transaction added: {transaction.sender} -> {transaction.recipient}: {transaction.amount}")
        return True
    
    def mine_pending_transactions(self, mining_reward_address: str) -> Block:
        """Mine pending transactions and add block to chain."""
        # Add mining reward transaction
        reward_transaction = Transaction(
            sender="0",  # System
            recipient=mining_reward_address,
            amount=self.mining_reward
        )
        self.pending_transactions.append(reward_transaction)
        
        # Create new block
        block = Block(
            index=len(self.chain),
            transactions=self.pending_transactions.copy(),
            previous_hash=self.get_latest_block().hash
        )
        
        # Mine the block
        print(f"Mining block {block.index}...")
        start_time = time.time()
        block.mine_block(self.difficulty)
        mining_time = time.time() - start_time
        print(f"Block mined in {mining_time:.2f} seconds")
        
        # Add block to chain
        self.chain.append(block)
        
        # Update balances
        self.update_balances(block)
        
        # Clear pending transactions
        self.pending_transactions = []
        
        return block
    
    def update_balances(self, block: Block):
        """Update account balances based on block transactions."""
        for transaction in block.transactions:
            if transaction.sender != "0":  # Not a mining reward
                self.balances[transaction.sender] -= transaction.amount
            self.balances[transaction.recipient] += transaction.amount
    
    def get_balance(self, address: str) -> float:
        """Get the balance of an address."""
        return self.balances[address]
    
    def is_chain_valid(self) -> bool:
        """Validate the integrity of the blockchain."""
        for i in range(1, len(self.chain)):
            current_block = self.chain[i]
            previous_block = self.chain[i - 1]
            
            # Check if current block hash is valid
            if current_block.hash != current_block.calculate_hash():
                print(f"Block {i} has invalid hash")
                return False
            
            # Check if block points to previous block
            if current_block.previous_hash != previous_block.hash:
                print(f"Block {i} has invalid previous hash")
                return False
            
            # Check if block meets difficulty requirement
            target = "0" * self.difficulty
            if current_block.hash[:self.difficulty] != target:
                print(f"Block {i} does not meet difficulty requirement")
                return False
        
        return True
    
    def get_chain_info(self) -> Dict:
        """Get information about the blockchain."""
        return {
            'chain_length': len(self.chain),
            'pending_transactions': len(self.pending_transactions),
            'difficulty': self.difficulty,
            'mining_reward': self.mining_reward,
            'is_valid': self.is_chain_valid()
        }
    
    def print_chain(self):
        """Print the entire blockchain."""
        print("\n" + "="*80)
        print("BLOCKCHAIN")
        print("="*80)
        for block in self.chain:
            print(f"\nBlock {block.index}")
            print(f"  Timestamp: {datetime.fromtimestamp(block.timestamp).strftime('%Y-%m-%d %H:%M:%S')}")
            print(f"  Previous Hash: {block.previous_hash[:16]}...")
            print(f"  Hash: {block.hash}")
            print(f"  Nonce: {block.nonce}")
            print(f"  Transactions ({len(block.transactions)}):")
            for tx in block.transactions:
                print(f"    - {tx.sender} -> {tx.recipient}: {tx.amount}")
        print("="*80 + "\n")
    
    def print_balances(self):
        """Print all account balances."""
        print("\n" + "="*80)
        print("ACCOUNT BALANCES")
        print("="*80)
        for address, balance in self.balances.items():
            if balance > 0 or address == "Genesis":
                print(f"  {address}: {balance}")
        print("="*80 + "\n")


class Wallet:
    """Simple wallet implementation for managing addresses."""
    
    def __init__(self, name: str):
        self.name = name
        self.address = self.generate_address()
    
    @staticmethod
    def generate_address() -> str:
        """Generate a simple address (in real implementation, use public key)."""
        import random
        import string
        return ''.join(random.choices(string.ascii_letters + string.digits, k=16))
    
    def create_transaction(self, recipient: str, amount: float) -> Transaction:
        """Create a transaction from this wallet."""
        return Transaction(
            sender=self.address,
            recipient=recipient,
            amount=amount,
            signature=f"signed_by_{self.name}"  # Simplified signature
        )
    
    def __repr__(self):
        return f"Wallet({self.name}, Address: {self.address[:8]}...)"


def main():
    """Demonstration of blockchain functionality."""
    print("="*80)
    print("BLOCKCHAIN DEMONSTRATION")
    print("="*80)
    
    # Create blockchain
    blockchain = Blockchain(difficulty=4, mining_reward=100.0)
    
    # Create wallets
    alice = Wallet("Alice")
    bob = Wallet("Bob")
    charlie = Wallet("Charlie")
    
    print(f"\nCreated wallets:")
    print(f"  {alice}")
    print(f"  {bob}")
    print(f"  {charlie}")
    
    # Mine first block (Alice gets mining reward)
    print("\n" + "-"*80)
    print("Mining Block 1 (Genesis + Mining Reward for Alice)")
    print("-"*80)
    blockchain.mine_pending_transactions(alice.address)
    
    # Add some transactions
    print("\n" + "-"*80)
    print("Adding Transactions")
    print("-"*80)
    blockchain.add_transaction(alice.create_transaction(bob.address, 50.0))
    blockchain.add_transaction(alice.create_transaction(charlie.address, 25.0))
    blockchain.add_transaction(bob.create_transaction(charlie.address, 10.0))
    
    # Mine second block
    print("\n" + "-"*80)
    print("Mining Block 2 (Bob gets mining reward)")
    print("-"*80)
    blockchain.mine_pending_transactions(bob.address)
    
    # Add more transactions
    print("\n" + "-"*80)
    print("Adding More Transactions")
    print("-"*80)
    blockchain.add_transaction(charlie.create_transaction(alice.address, 5.0))
    blockchain.add_transaction(bob.create_transaction(alice.address, 15.0))
    
    # Mine third block
    print("\n" + "-"*80)
    print("Mining Block 3 (Charlie gets mining reward)")
    print("-"*80)
    blockchain.mine_pending_transactions(charlie.address)
    
    # Display blockchain
    blockchain.print_chain()
    
    # Display balances
    blockchain.print_balances()
    
    # Validate chain
    print("\n" + "-"*80)
    print("Validating Blockchain")
    print("-"*80)
    is_valid = blockchain.is_chain_valid()
    print(f"Blockchain is valid: {is_valid}")
    
    # Display chain info
    print("\n" + "-"*80)
    print("Blockchain Information")
    print("-"*80)
    info = blockchain.get_chain_info()
    for key, value in info.items():
        print(f"  {key}: {value}")
    
    # Demonstrate tampering detection
    print("\n" + "-"*80)
    print("Testing Tampering Detection")
    print("-"*80)
    print("Attempting to tamper with block 1...")
    blockchain.chain[1].transactions[0].amount = 999999.0
    is_valid_after_tamper = blockchain.is_chain_valid()
    print(f"Blockchain is valid after tampering: {is_valid_after_tamper}")
    print("(Expected: False - tampering detected!)")
    
    print("\n" + "="*80)
    print("DEMONSTRATION COMPLETE")
    print("="*80)


if __name__ == "__main__":
    main()

Related articles