Skip to content

Commit 6ff0299

Browse files
Add simple blockchain mining algorithm with PoW (#13114)
* Add simple blockchain mining algorithm with PoW * Add type hints for __init__ methods * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added compute hash doctest * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added init return * Fix type hints and add doctests for simple blockchain --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 10f9f3b commit 6ff0299

1 file changed

Lines changed: 168 additions & 0 deletions

File tree

blockchain/simple_blockchain.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
"""
2+
A simple blockchain implementation with Proof-of-Work (PoW).
3+
4+
This educational example demonstrates:
5+
- Block structure with index, timestamp, data, previous hash, nonce, and hash
6+
- Mining via Proof-of-Work
7+
- Chain integrity verification
8+
9+
Author: Letitia Gilbert
10+
"""
11+
12+
import hashlib
13+
from time import time
14+
15+
16+
class Block:
17+
"""
18+
Represents a single block in a blockchain.
19+
20+
Attributes:
21+
index (int): Position of the block in the chain.
22+
timestamp (float): Creation time of the block.
23+
data (str): Data stored in the block.
24+
previous_hash (str): Hash of the previous block.
25+
nonce (int): Number used for mining.
26+
hash (str): SHA256 hash of the block's content.
27+
"""
28+
29+
def __init__(
30+
self, index: int, data: str, previous_hash: str, difficulty: int = 2
31+
) -> None:
32+
self.index = index
33+
self.timestamp = time()
34+
self.data = data
35+
self.previous_hash = previous_hash
36+
self.nonce, self.hash = self.mine_block(difficulty)
37+
38+
def compute_hash(self, nonce: int) -> str:
39+
"""
40+
Compute SHA256 hash of the block with given nonce.
41+
42+
Args:
43+
nonce (int): Nonce to include in the hash.
44+
45+
Returns:
46+
str: Hexadecimal hash string.
47+
48+
>>> block = Block(0, "Genesis", "0", difficulty=2)
49+
>>> len(block.compute_hash(0)) == 64
50+
True
51+
>>> isinstance(block.compute_hash(0), str)
52+
True
53+
"""
54+
block_string = (
55+
f"{self.index}{self.timestamp}{self.data}{self.previous_hash}{nonce}"
56+
)
57+
return hashlib.sha256(block_string.encode()).hexdigest()
58+
59+
def mine_block(self, difficulty: int) -> tuple[int, str]:
60+
"""
61+
Simple Proof-of-Work mining algorithm.
62+
63+
Args:
64+
difficulty (int): Number of leading zeros required in the hash.
65+
66+
Returns:
67+
Tuple[int, str]: Valid nonce and resulting hash that satisfies difficulty.
68+
69+
>>> block = Block(0, "Genesis", "0", difficulty=2)
70+
>>> block.hash.startswith('00')
71+
True
72+
"""
73+
if difficulty < 1:
74+
raise ValueError("Difficulty must be at least 1")
75+
nonce = 0
76+
target = "0" * difficulty
77+
while True:
78+
hash_result = self.compute_hash(nonce)
79+
if hash_result.startswith(target):
80+
return nonce, hash_result
81+
nonce += 1
82+
83+
84+
class Blockchain:
85+
"""
86+
Simple blockchain class maintaining a list of blocks.
87+
88+
Attributes:
89+
chain (List[Block]): List of blocks forming the chain.
90+
"""
91+
92+
def __init__(self, difficulty: int = 2) -> None:
93+
self.difficulty = difficulty
94+
self.chain: list[Block] = [self.create_genesis_block()]
95+
96+
def create_genesis_block(self) -> Block:
97+
"""
98+
Create the first block in the blockchain.
99+
100+
Returns:
101+
Block: Genesis block.
102+
103+
>>> bc = Blockchain()
104+
>>> bc.chain[0].index
105+
0
106+
>>> bc.chain[0].hash.startswith('00')
107+
True
108+
"""
109+
return Block(0, "Genesis Block", "0", self.difficulty)
110+
111+
def add_block(self, data: str) -> Block:
112+
"""
113+
Add a new block to the blockchain with given data.
114+
115+
Args:
116+
data (str): Data to store in the block.
117+
118+
Returns:
119+
Block: Newly added block.
120+
121+
>>> bc = Blockchain()
122+
>>> new_block = bc.add_block("Test Data")
123+
>>> new_block.index
124+
1
125+
>>> new_block.previous_hash == bc.chain[0].hash
126+
True
127+
>>> new_block.hash.startswith('00')
128+
True
129+
>>> bc.is_valid()
130+
True
131+
"""
132+
prev_hash = self.chain[-1].hash
133+
new_block = Block(len(self.chain), data, prev_hash, self.difficulty)
134+
self.chain.append(new_block)
135+
return new_block
136+
137+
def is_valid(self) -> bool:
138+
"""
139+
Verify the integrity of the blockchain.
140+
141+
Returns:
142+
bool: True if chain is valid, False otherwise.
143+
144+
>>> bc = Blockchain()
145+
>>> new_block = bc.add_block("Test")
146+
>>> new_block.index
147+
1
148+
>>> new_block.previous_hash == bc.chain[0].hash
149+
True
150+
>>> new_block.hash.startswith('00')
151+
True
152+
>>> bc.is_valid()
153+
True
154+
>>> bc.chain[1].previous_hash = "tampered"
155+
>>> bc.is_valid()
156+
False
157+
158+
"""
159+
for i in range(1, len(self.chain)):
160+
current = self.chain[i]
161+
prev = self.chain[i - 1]
162+
if current.previous_hash != prev.hash:
163+
return False
164+
if not current.hash.startswith("0" * self.difficulty):
165+
return False
166+
if current.hash != current.compute_hash(current.nonce):
167+
return False
168+
return True

0 commit comments

Comments
 (0)