Skip to content

Commit 4399e7f

Browse files
committed
Complete section: Preparing the Blockchain for Collaboration
1 parent a6142eb commit 4399e7f

3 files changed

Lines changed: 74 additions & 1 deletion

File tree

backend/blockchain/block.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ def __repr__(self):
3737
f'nonce: {self.nonce})'
3838
)
3939

40+
def __eq__(self, other):
41+
return self.__dict__ == other.__dict__
42+
4043
@staticmethod
4144
def mine_block(last_block, data):
4245
"""

backend/blockchain/blockchain.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,39 @@ def add_block(self, data):
1414
def __repr__(self):
1515
return f'Blockchain: {self.chain}'
1616

17+
def replace_chain(self, chain):
18+
"""
19+
Replace the local chain with the incoming one if the following applies:
20+
- The incoming chain is longer than the local one.
21+
- The incoming chain is formatted properly.
22+
"""
23+
if len(chain) <= len(self.chain):
24+
raise Exception('Cannot replace. The incoming chain must be longer.')
25+
26+
try:
27+
Blockchain.is_valid_chain(chain)
28+
except Exception as e:
29+
raise Exception(f'Cannot replace. The incoming chain is invalid: {e}')
30+
31+
self.chain = chain
32+
33+
@staticmethod
34+
def is_valid_chain(chain):
35+
"""
36+
Validate the incoming chain.
37+
Enforce the following rules of the blockchain:
38+
- the chain must start with the genesis block
39+
- blocks must be formatted correctly
40+
"""
41+
if chain[0] != Block.genesis():
42+
raise Exception('The genesis block must be valid')
43+
44+
for i in range(1, len(chain)):
45+
block = chain[i]
46+
last_block = chain[i-1]
47+
Block.is_valid_block(last_block, block)
48+
49+
1750
def main():
1851
blockchain = Blockchain()
1952
blockchain.add_block('one')

backend/tests/blockchain/test_blockchain.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import pytest
2+
13
from backend.blockchain.blockchain import Blockchain
24
from backend.blockchain.block import GENESIS_DATA
35

@@ -10,4 +12,39 @@ def test_add_block():
1012
data = 'test-data'
1113
blockchain.add_block(data)
1214

13-
assert blockchain.chain[-1].data == data
15+
assert blockchain.chain[-1].data == data
16+
17+
@pytest.fixture
18+
def blockchain_three_blocks():
19+
blockchain = Blockchain()
20+
for i in range(3):
21+
blockchain.add_block(i)
22+
return blockchain
23+
24+
def test_is_valid_chain(blockchain_three_blocks):
25+
Blockchain.is_valid_chain(blockchain_three_blocks.chain)
26+
27+
def test_is_valid_chain_bad_genesis(blockchain_three_blocks):
28+
blockchain_three_blocks.chain[0].hash = 'evil_hash'
29+
30+
with pytest.raises(Exception, match='genesis block must be valid'):
31+
Blockchain.is_valid_chain(blockchain_three_blocks.chain)
32+
33+
def test_replace_chain(blockchain_three_blocks):
34+
blockchain = Blockchain()
35+
blockchain.replace_chain(blockchain_three_blocks.chain)
36+
37+
assert blockchain.chain == blockchain_three_blocks.chain
38+
39+
def test_replace_chain_not_longer(blockchain_three_blocks):
40+
blockchain = Blockchain()
41+
42+
with pytest.raises(Exception, match='The incoming chain must be longer'):
43+
blockchain_three_blocks.replace_chain(blockchain.chain)
44+
45+
def test_replace_chain_bad_chain(blockchain_three_blocks):
46+
blockchain = Blockchain()
47+
blockchain_three_blocks.chain[1].hash = 'evil_hash'
48+
49+
with pytest.raises(Exception, match='The incoming chain is invalid'):
50+
blockchain.replace_chain(blockchain_three_blocks.chain)

0 commit comments

Comments
 (0)