-
-
Notifications
You must be signed in to change notification settings - Fork 50.5k
Expand file tree
/
Copy pathsimple_blockchain.py
More file actions
168 lines (138 loc) · 4.67 KB
/
simple_blockchain.py
File metadata and controls
168 lines (138 loc) · 4.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
"""
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
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
) -> None:
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 = Block(0, "Genesis", "0", difficulty=2)
>>> len(block.compute_hash(0)) == 64
True
>>> isinstance(block.compute_hash(0), str)
True
"""
block_string = (
f"{self.index}{self.timestamp}{self.data}{self.previous_hash}{nonce}"
)
return hashlib.sha256(block_string.encode()).hexdigest()
def mine_block(self, difficulty: int) -> tuple[int, str]:
"""
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) -> None:
self.difficulty = difficulty
self.chain: list[Block] = [self.create_genesis_block()]
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