Merge pull request #41 from dvf/dvf/tests

Basic Tests, Some Clean-up, Travis Integration
This commit is contained in:
Daniel van Flymen 2017-11-12 16:36:08 -05:00 committed by GitHub
commit db043edf03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 127 additions and 10 deletions

12
.travis.yml Normal file
View File

@ -0,0 +1,12 @@
language: python
python:
- 3.6
- nightly
install:
- pip install pipenv
- pipenv install --dev
script:
- pipenv run python -m unittest

View File

@ -1,5 +1,7 @@
# Learn Blockchains by Building One # Learn Blockchains by Building One
[![Build Status](https://travis-ci.org/dvf/blockchain.svg?branch=master)](https://travis-ci.org/dvf/blockchain)
This is the source code for my post on [Building a Blockchain](https://medium.com/p/117428612f46). This is the source code for my post on [Building a Blockchain](https://medium.com/p/117428612f46).
## Installation ## Installation

View File

@ -1,7 +1,6 @@
import hashlib import hashlib
import json import json
from time import time from time import time
from typing import Any, Dict, List, Optional
from urllib.parse import urlparse from urllib.parse import urlparse
from uuid import uuid4 from uuid import uuid4
@ -18,7 +17,7 @@ class Blockchain:
# Create the genesis block # Create the genesis block
self.new_block(previous_hash='1', proof=100) self.new_block(previous_hash='1', proof=100)
def register_node(self, address: str) -> None: def register_node(self, address):
""" """
Add a new node to the list of nodes Add a new node to the list of nodes
@ -28,7 +27,7 @@ class Blockchain:
parsed_url = urlparse(address) parsed_url = urlparse(address)
self.nodes.add(parsed_url.netloc) self.nodes.add(parsed_url.netloc)
def valid_chain(self, chain: List[Dict[str, Any]]) -> bool: def valid_chain(self, chain):
""" """
Determine if a given blockchain is valid Determine if a given blockchain is valid
@ -57,7 +56,7 @@ class Blockchain:
return True return True
def resolve_conflicts(self) -> bool: def resolve_conflicts(self):
""" """
This is our consensus algorithm, it resolves conflicts This is our consensus algorithm, it resolves conflicts
by replacing our chain with the longest one in the network. by replacing our chain with the longest one in the network.
@ -91,7 +90,7 @@ class Blockchain:
return False return False
def new_block(self, proof: int, previous_hash: Optional[str]) -> Dict[str, Any]: def new_block(self, proof, previous_hash):
""" """
Create a new Block in the Blockchain Create a new Block in the Blockchain
@ -114,7 +113,7 @@ class Blockchain:
self.chain.append(block) self.chain.append(block)
return block return block
def new_transaction(self, sender: str, recipient: str, amount: int) -> int: def new_transaction(self, sender, recipient, amount):
""" """
Creates a new transaction to go into the next mined Block Creates a new transaction to go into the next mined Block
@ -132,11 +131,11 @@ class Blockchain:
return self.last_block['index'] + 1 return self.last_block['index'] + 1
@property @property
def last_block(self) -> Dict[str, Any]: def last_block(self):
return self.chain[-1] return self.chain[-1]
@staticmethod @staticmethod
def hash(block: Dict[str, Any]) -> str: def hash(block):
""" """
Creates a SHA-256 hash of a Block Creates a SHA-256 hash of a Block
@ -147,7 +146,7 @@ class Blockchain:
block_string = json.dumps(block, sort_keys=True).encode() block_string = json.dumps(block, sort_keys=True).encode()
return hashlib.sha256(block_string).hexdigest() return hashlib.sha256(block_string).hexdigest()
def proof_of_work(self, last_proof: int) -> int: def proof_of_work(self, last_proof):
""" """
Simple Proof of Work Algorithm: Simple Proof of Work Algorithm:
- Find a number p' such that hash(pp') contains leading 4 zeroes, where p is the previous p' - Find a number p' such that hash(pp') contains leading 4 zeroes, where p is the previous p'
@ -161,7 +160,7 @@ class Blockchain:
return proof return proof
@staticmethod @staticmethod
def valid_proof(last_proof: int, proof: int) -> bool: def valid_proof(last_proof, proof):
""" """
Validates the Proof Validates the Proof

0
tests/__init__.py Normal file
View File

104
tests/test_blockchain.py Normal file
View File

@ -0,0 +1,104 @@
import hashlib
import json
from unittest import TestCase
from blockchain import Blockchain
class BlockchainTestCase(TestCase):
def setUp(self):
self.blockchain = Blockchain()
def create_block(self, proof=123, previous_hash='abc'):
self.blockchain.new_block(proof, previous_hash)
def create_transaction(self, sender='a', recipient='b', amount=1):
self.blockchain.new_transaction(
sender=sender,
recipient=recipient,
amount=amount
)
class TestRegisterNodes(BlockchainTestCase):
def test_valid_nodes(self):
blockchain = Blockchain()
blockchain.register_node('http://192.168.0.1:5000')
self.assertIn('192.168.0.1:5000', blockchain.nodes)
def test_malformed_nodes(self):
blockchain = Blockchain()
blockchain.register_node('http//192.168.0.1:5000')
self.assertNotIn('192.168.0.1:5000', blockchain.nodes)
def test_idempotency(self):
blockchain = Blockchain()
blockchain.register_node('http://192.168.0.1:5000')
blockchain.register_node('http://192.168.0.1:5000')
assert len(blockchain.nodes) == 1
class TestBlocksAndTransactions(BlockchainTestCase):
def test_block_creation(self):
self.create_block()
latest_block = self.blockchain.last_block
# The genesis block is create at initialization, so the length should be 2
assert len(self.blockchain.chain) == 2
assert latest_block['index'] == 2
assert latest_block['timestamp'] is not None
assert latest_block['proof'] == 123
assert latest_block['previous_hash'] == 'abc'
def test_create_transaction(self):
self.create_transaction()
transaction = self.blockchain.current_transactions[-1]
assert transaction
assert transaction['sender'] == 'a'
assert transaction['recipient'] == 'b'
assert transaction['amount'] == 1
def test_block_resets_transactions(self):
self.create_transaction()
initial_length = len(self.blockchain.current_transactions)
self.create_block()
current_length = len(self.blockchain.current_transactions)
assert initial_length == 1
assert current_length == 0
def test_return_last_block(self):
self.create_block()
created_block = self.blockchain.last_block
assert len(self.blockchain.chain) == 2
assert created_block is self.blockchain.chain[-1]
class TestHashingAndProofs(BlockchainTestCase):
def test_hash_is_correct(self):
self.create_block()
new_block = self.blockchain.last_block
new_block_json = json.dumps(self.blockchain.last_block, sort_keys=True).encode()
new_hash = hashlib.sha256(new_block_json).hexdigest()
assert len(new_hash) == 64
assert new_hash == self.blockchain.hash(new_block)