Skip to content

Commit a77fccd

Browse files
committed
Complete proof of work section
0 parents  commit a77fccd

680 files changed

Lines changed: 199380 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*.pyc
2+
*__pycache__*
3+
*node_modules*

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
**Activate the virtual environment**
2+
```
3+
source blockchain-env/bin/activate
4+
```
5+
6+
**Install all packages**
7+
```
8+
pip3 install -r requirements.txt
9+
```
10+
11+
**Run the tests**
12+
13+
Make sure to activate the virtual environment.
14+
15+
```
16+
python3 -m pytest backend/tests
17+
```

backend/__init__.py

Whitespace-only changes.

backend/blockchain/__init__.py

Whitespace-only changes.

backend/blockchain/block.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import time
2+
3+
from backend.util.crypto_hash import crypto_hash
4+
from backend.util.hex_to_binary import hex_to_binary
5+
from backend.config import MINE_RATE
6+
7+
GENESIS_DATA = {
8+
'timestamp': 1,
9+
'last_hash': 'genesis_last_hash',
10+
'hash': 'genesis_hash',
11+
'data': [],
12+
'difficulty': 3,
13+
'nonce': 'genesis_nonce'
14+
}
15+
16+
class Block:
17+
"""
18+
Block: a unit of storage.
19+
Store transactions in a blockchain that supports a cryptocurrency.
20+
"""
21+
def __init__(self, timestamp, last_hash, hash, data, difficulty, nonce):
22+
self.timestamp = timestamp
23+
self.last_hash = last_hash
24+
self.hash = hash
25+
self.data = data
26+
self.difficulty = difficulty
27+
self.nonce = nonce
28+
29+
def __repr__(self):
30+
return (
31+
'Block('
32+
f'timestamp: {self.timestamp}, '
33+
f'last_hash: {self.last_hash}, '
34+
f'hash: {self.hash}, '
35+
f'data: {self.data}, '
36+
f'difficulty: {self.difficulty}, '
37+
f'nonce: {self.nonce})'
38+
)
39+
40+
@staticmethod
41+
def mine_block(last_block, data):
42+
"""
43+
Mine a block based on the given last_block and data, until a block hash
44+
is found that meets the leading 0's proof of work requirement.
45+
"""
46+
timestamp = time.time_ns()
47+
last_hash = last_block.hash
48+
difficulty = Block.adjust_difficulty(last_block, timestamp)
49+
nonce = 0
50+
hash = crypto_hash(timestamp, last_hash, data, difficulty, nonce)
51+
52+
while hex_to_binary(hash)[0:difficulty] != '0' * difficulty:
53+
nonce += 1
54+
timestamp = time.time_ns()
55+
difficulty = Block.adjust_difficulty(last_block, timestamp)
56+
hash = crypto_hash(timestamp, last_hash, data, difficulty, nonce)
57+
58+
return Block(timestamp, last_hash, hash, data, difficulty, nonce)
59+
60+
@staticmethod
61+
def genesis():
62+
"""
63+
Generate the genesis block.
64+
"""
65+
return Block(**GENESIS_DATA)
66+
67+
@staticmethod
68+
def adjust_difficulty(last_block, new_timestamp):
69+
"""
70+
Calculate the adjusted difficulty according to the MINE_RATE.
71+
Increase the difficulty for slowly mined blocks.
72+
Decrease the difficulty for quickly mined blocks.
73+
"""
74+
if (new_timestamp - last_block.timestamp) < MINE_RATE:
75+
return last_block.difficulty + 1
76+
77+
if (last_block.difficulty - 1) > 0:
78+
return last_block.difficulty - 1
79+
80+
return 1
81+
82+
def main():
83+
genesis_block = Block.genesis()
84+
block = Block.mine_block(genesis_block, 'foo')
85+
print(block)
86+
87+
if __name__ == '__main__':
88+
main()

backend/blockchain/blockchain.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from backend.blockchain.block import Block
2+
3+
class Blockchain:
4+
"""
5+
Blockchain: a public ledger of transactions.
6+
Implemented as a list of blocks - data sets of transactions
7+
"""
8+
def __init__(self):
9+
self.chain = [Block.genesis()]
10+
11+
def add_block(self, data):
12+
self.chain.append(Block.mine_block(self.chain[-1], data))
13+
14+
def __repr__(self):
15+
return f'Blockchain: {self.chain}'
16+
17+
def main():
18+
blockchain = Blockchain()
19+
blockchain.add_block('one')
20+
blockchain.add_block('two')
21+
22+
print(blockchain)
23+
print(f'blockchain.py ___name__: {__name__}')
24+
25+
if __name__ == '__main__':
26+
main()

backend/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
NANOSECONDS = 1
2+
MICROSECONDS = 1000 * NANOSECONDS
3+
MILLISECONDS = 1000 * MICROSECONDS
4+
SECONDS = 1000 * MILLISECONDS
5+
6+
MINE_RATE = 4 * SECONDS
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import time
2+
3+
from backend.blockchain.blockchain import Blockchain
4+
from backend.config import SECONDS
5+
6+
blockchain = Blockchain()
7+
8+
times = []
9+
10+
for i in range(1000):
11+
start_time = time.time_ns()
12+
blockchain.add_block(i)
13+
end_time = time.time_ns()
14+
15+
time_to_mine = (end_time - start_time) / SECONDS
16+
times.append(time_to_mine)
17+
18+
average_time = sum(times) / len(times)
19+
20+
print(f'New block difficulty: {blockchain.chain[-1].difficulty}')
21+
print(f'Time to mine new block: {time_to_mine}s')
22+
print(f'Average time to add blocks: {average_time}s\n')
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import time
2+
3+
from backend.blockchain.block import Block, GENESIS_DATA
4+
from backend.config import MINE_RATE, SECONDS
5+
from backend.util.hex_to_binary import hex_to_binary
6+
7+
def test_mine_block():
8+
last_block = Block.genesis()
9+
data = 'test-data'
10+
block = Block.mine_block(last_block, data)
11+
12+
assert isinstance(block, Block)
13+
assert block.data == data
14+
assert block.last_hash == last_block.hash
15+
assert hex_to_binary(block.hash)[0:block.difficulty] == '0' * block.difficulty
16+
17+
def test_genesis():
18+
genesis = Block.genesis()
19+
20+
assert isinstance(genesis, Block)
21+
for key, value in GENESIS_DATA.items():
22+
getattr(genesis, key) == value
23+
24+
def test_quickly_mined_block():
25+
last_block = Block.mine_block(Block.genesis(), 'foo')
26+
mined_block = Block.mine_block(last_block, 'bar')
27+
28+
assert mined_block.difficulty == last_block.difficulty + 1
29+
30+
def test_slowly_mined_block():
31+
last_block = Block.mine_block(Block.genesis(), 'foo')
32+
time.sleep(MINE_RATE / SECONDS)
33+
mined_block = Block.mine_block(last_block, 'bar')
34+
35+
assert mined_block.difficulty == last_block.difficulty - 1
36+
37+
def test_mined_block_difficulty_limits_at_1():
38+
last_block = Block(
39+
time.time_ns(),
40+
'test_last_hash',
41+
'test_hash',
42+
'test_data',
43+
1,
44+
0
45+
)
46+
47+
time.sleep(MINE_RATE / SECONDS)
48+
mined_block = Block.mine_block(last_block, 'bar')
49+
50+
assert mined_block.difficulty == 1
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from backend.blockchain.blockchain import Blockchain
2+
from backend.blockchain.block import GENESIS_DATA
3+
4+
def test_blockchain_instance():
5+
blockchain = Blockchain()
6+
7+
assert blockchain.chain[0].hash == GENESIS_DATA['hash']
8+
9+
def test_add_block():
10+
blockchain = Blockchain()
11+
data = 'test-data'
12+
blockchain.add_block(data)
13+
14+
assert blockchain.chain[-1].data == data

0 commit comments

Comments
 (0)