Skip to content
159 changes: 159 additions & 0 deletions blockchain/simple_blockchain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
"""
A simple blockchain implementation with Proof-of-Work (PoW).

This educational example demonstrates:
- Block structure with index, timestamp, data, previous hash, nonce, and hash
- Mining via Proof-of-Work
- Chain integrity verification

Author: Letitia Gilbert
"""

import hashlib
from time import time
from typing import List, Tuple

Check failure on line 14 in blockchain/simple_blockchain.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (UP035)

blockchain/simple_blockchain.py:14:1: UP035 `typing.Tuple` is deprecated, use `tuple` instead

Check failure on line 14 in blockchain/simple_blockchain.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (UP035)

blockchain/simple_blockchain.py:14:1: UP035 `typing.List` is deprecated, use `list` instead


class Block:
"""
Represents a single block in a blockchain.

Attributes:
index (int): Position of the block in the chain.
timestamp (float): Creation time of the block.
data (str): Data stored in the block.
previous_hash (str): Hash of the previous block.
nonce (int): Number used for mining.
hash (str): SHA256 hash of the block's content.
"""

def __init__(self, index: int, data: str, previous_hash: str, difficulty: int = 2):
self.index = index
self.timestamp = time()
self.data = data
self.previous_hash = previous_hash
self.nonce, self.hash = self.mine_block(difficulty)

def compute_hash(self, nonce: int) -> str:
"""
Compute SHA256 hash of the block with given nonce.

Args:
nonce (int): Nonce to include in the hash.

Returns:
str: Hexadecimal hash string.
"""
block_string = f"{self.index}{self.timestamp}{self.data}{self.previous_hash}{nonce}"

Check failure on line 47 in blockchain/simple_blockchain.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

blockchain/simple_blockchain.py:47:89: E501 Line too long (92 > 88)
return hashlib.sha256(block_string.encode()).hexdigest()

def mine_block(self, difficulty: int) -> Tuple[int, str]:

Check failure on line 50 in blockchain/simple_blockchain.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (UP006)

blockchain/simple_blockchain.py:50:46: UP006 Use `tuple` instead of `Tuple` for type annotation
"""
Simple Proof-of-Work mining algorithm.

Args:
difficulty (int): Number of leading zeros required in the hash.

Returns:
Tuple[int, str]: Valid nonce and resulting hash that satisfies difficulty.

>>> block = Block(0, "Genesis", "0", difficulty=2)
>>> block.hash.startswith('00')
True
"""
if difficulty < 1:
raise ValueError("Difficulty must be at least 1")
nonce = 0
target = '0' * difficulty
while True:
hash_result = self.compute_hash(nonce)
if hash_result.startswith(target):
return nonce, hash_result
nonce += 1


class Blockchain:
"""
Simple blockchain class maintaining a list of blocks.

Attributes:
chain (List[Block]): List of blocks forming the chain.
"""

def __init__(self, difficulty: int = 2):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide return type hint for the function: __init__. If the function does not return a value, please provide the type hint as: def function() -> None:

self.difficulty = difficulty
self.chain: List[Block] = [self.create_genesis_block()]

Check failure on line 85 in blockchain/simple_blockchain.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (UP006)

blockchain/simple_blockchain.py:85:21: UP006 Use `list` instead of `List` for type annotation

def create_genesis_block(self) -> Block:
"""
Create the first block in the blockchain.

Returns:
Block: Genesis block.

>>> bc = Blockchain()
>>> bc.chain[0].index
0
>>> bc.chain[0].hash.startswith('00')
True
"""
return Block(0, "Genesis Block", "0", self.difficulty)

def add_block(self, data: str) -> Block:
"""
Add a new block to the blockchain with given data.

Args:
data (str): Data to store in the block.

Returns:
Block: Newly added block.

>>> bc = Blockchain()
>>> new_block = bc.add_block("Test Data")
>>> new_block.index
1
>>> new_block.previous_hash == bc.chain[0].hash
True
>>> new_block.hash.startswith('00')
True
>>> bc.is_valid()
True
"""
prev_hash = self.chain[-1].hash
new_block = Block(len(self.chain), data, prev_hash, self.difficulty)
self.chain.append(new_block)
return new_block

def is_valid(self) -> bool:
"""
Verify the integrity of the blockchain.

Returns:
bool: True if chain is valid, False otherwise.

>>> bc = Blockchain()
>>> new_block = bc.add_block("Test")
>>> new_block.index
1
>>> new_block.previous_hash == bc.chain[0].hash
True
>>> new_block.hash.startswith('00')
True
>>> bc.is_valid()
True
>>> bc.chain[1].previous_hash = "tampered"
>>> bc.is_valid()
False

"""
for i in range(1, len(self.chain)):
current = self.chain[i]
prev = self.chain[i - 1]
if current.previous_hash != prev.hash:
return False
if not current.hash.startswith('0' * self.difficulty):
return False
if current.hash != current.compute_hash(current.nonce):
return False
return True
Loading