Skip to content

Commit

Permalink
Merge pull request #29 from se-jaeger/pretty_print_chain
Browse files Browse the repository at this point in the history
Pretty print chain.
  • Loading branch information
se-jaeger committed Mar 18, 2019
2 parents c720692 + a93adb0 commit ab413ed
Show file tree
Hide file tree
Showing 11 changed files with 114 additions and 76 deletions.
16 changes: 9 additions & 7 deletions src/blockchain/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from time import time

from src.utils.utils import colorize
from src.blockchain.data import Data


Expand Down Expand Up @@ -104,14 +105,15 @@ def __repr__(self) -> str:
"""

block_rep = "Block object with - \n"
block_rep += "\tindex:\t\t" + str(self.index) + "\n"
block_rep += "\tdata:\t\t" + str(self.data) + "\n"
block_rep += "\ttime:\t\t" + str(self.timestamp) + "\n"
block_rep += "\tproof:\t\t" + str(self.proof) + "\n"
block_rep += "\tprevious hash:\t" + str(self.previous_hash) + "\n"
block_representation = f"\n| {'=' * 80}\n"
block_representation += f"| {colorize('index', 'bold')}: \t {str(self.index)} \n"
block_representation += f"| {colorize('time', 'bold')}: \t {str(self.timestamp)} \n"
block_representation += f"| {colorize('proof', 'bold')}: \t {str(self.proof)} \n"
block_representation += f"| {colorize('prev. hash', 'bold')}: \t {str(self.previous_hash)} \n"
block_representation += f"{str(self.data)}\n"
block_representation += f"| {'=' * 80}\n"

return block_rep
return block_representation


def __bytes__(self) -> bytes:
Expand Down
13 changes: 9 additions & 4 deletions src/blockchain/blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,25 @@
import time
import pickle
import logging
import hashlib
import jsonpickle

from src.utils.constants import *
from src.blockchain.data import Data
from src.blockchain.block import Block
from src.utils.errors import ChainNotFoundError
from src.utils.utils import encode_file_path_properly
from src.utils.constants import GENESIS_BLOCK_PREV_HASH, GENESIS_BLOCK_PROOF, GENESIS_BLOCK_DATA, GENESIS_BLOCK_INDEX


logger = logging.getLogger(__name__)


class Blockchain(object):

def __init__(self, path_to_chain: str, json_format: bool) -> None:
genesis_block = Block(index=GENESIS_BLOCK_INDEX, data=Data(GENESIS_BLOCK_DATA), proof=GENESIS_BLOCK_PROOF, previous_hash=GENESIS_BLOCK_PREV_HASH)
genesis_block_hash = hashlib.sha256(bytes(genesis_block)).hexdigest()

def __init__(self, path_to_chain: str, json_format: bool, force_new_chain: bool) -> None:
"""
Constructor for new ``Blockchain`` object.
Expand All @@ -35,7 +39,7 @@ def __init__(self, path_to_chain: str, json_format: bool) -> None:
self._json_format = json_format

# if local chain exists, load it
if os.path.isfile(self.path_to_chain):
if os.path.isfile(self.path_to_chain) and not force_new_chain:

logger.debug(f"Load existing chain from disc ...")
self.load_chain()
Expand All @@ -45,7 +49,8 @@ def __init__(self, path_to_chain: str, json_format: bool) -> None:
logger.debug(f"Create new chain ...")

# if no local chain exists, create the genesis block
self.chain = [GENESIS_BLOCK]

self.chain = [self.genesis_block]
logger.debug(f"New chain created.")

logger.info("Created 'Blockchain' object.")
Expand Down
12 changes: 8 additions & 4 deletions src/blockchain/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

from uuid import uuid4

from src.utils.utils import colorize


logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -45,11 +48,12 @@ def __hash__(self):

def __repr__(self) -> str:

data_rep = "Data object with - \n"
data_rep += "\t\t\t\tid:\t" + str(self.id) + "\n"
data_rep += "\t\t\t\tmessage:" + str(self.message)
data_representation = f"| \t\t{'_' * 66}\n"
data_representation += f"| {colorize('data', 'bold')}:\t\t| id: \t{self.id}\n"
data_representation += f"| \t\t| message: {self.message}\n"
data_representation += f"| \t\t{'_' * 65}"

return data_rep
return data_representation


def __eq__(self, other) -> bool:
Expand Down
16 changes: 11 additions & 5 deletions src/cli/cli.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import time
import click
import signal
import logging
Expand Down Expand Up @@ -55,7 +56,8 @@ def get():
@click.option("--chain_serialization", default="json", type=click.types.Choice(["json", "pickle"]), help=CHAIN_SERIALIZATION_HELP)
@click.option("--difficulty", default=DEFAULT_DIFFICULTY, type=int, help=DIFFICULTY_HELP)
@click.option("--neighbours", default=DEFAULT_NEIGHBOURS, type=str, help=NEIGHBOURS_HELP)
def start(chain_path: str, chain_serialization: str, port: int, difficulty: int, neighbours: str):
@click.option("--force_new_chain", is_flag=True, type=bool, help=NEIGHBOURS_HELP)
def start(chain_path: str, chain_serialization: str, port: int, difficulty: int, neighbours: str, force_new_chain: bool):
"""
Starts a local miner.
"""
Expand All @@ -75,7 +77,10 @@ def start(chain_path: str, chain_serialization: str, port: int, difficulty: int,
neighbours = neighbours.split(",")


miner = Miner(path_to_chain=chain_path, json_format=json_format, port=port, difficulty=difficulty, neighbours=neighbours)
logger.debug(f"======================================== STARTED AT {time.strftime('%d.%m.%Y %H:%M:%S', time.localtime())} ========================================\n\n\n")


miner = Miner(path_to_chain=chain_path, json_format=json_format, port=port, difficulty=difficulty, neighbours=neighbours, force_new_chain=force_new_chain)

try:
signal.signal(signal.SIGTERM, signal_handler)
Expand All @@ -89,7 +94,8 @@ def start(chain_path: str, chain_serialization: str, port: int, difficulty: int,

miner.stop_mining()

logger.debug("======================================== FINISHED ========================================\n\n\n")

logger.debug(f"======================================== FINISHED AT {time.strftime('%d.%m.%Y %H:%M:%S', time.localtime())} ========================================\n\n\n")


@cli.command()
Expand Down Expand Up @@ -126,7 +132,7 @@ def chain(host, port):
if response.status_code == HTTP_OK:

json = response.json()
length = json['length']
length = json["length"]
chain = jsonpickle.decode(json["chain"])

click.echo(click.style(f"\nChain with length: {length}\n", fg="green"))
Expand All @@ -149,7 +155,7 @@ def neighbours(host, port):
if response.status_code == HTTP_OK:

json = response.json()
length = json['length']
length = json["length"]
neighbours = jsonpickle.decode(json["neighbours"])

click.echo(click.style(f"\nActual are {length} neighbours available.\n", fg="green"))
Expand Down
5 changes: 3 additions & 2 deletions src/client/miner.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

class Miner(object):

def __init__(self, path_to_chain: str, json_format: bool, port: int, difficulty: int, neighbours: list = []) -> None:
def __init__(self, path_to_chain: str, json_format: bool, port: int, difficulty: int, neighbours: list, force_new_chain: bool) -> None:
"""
Constructor for new ``Miner`` object.
Expand All @@ -33,6 +33,7 @@ def __init__(self, path_to_chain: str, json_format: bool, port: int, difficulty:
port (int): Port of neighbour.
difficulty (int): Amount of trailing 0s for proof of work
neighbours (list): List of known neighbours, e.g. ``["localhost:23456", "miner0815:6666"]``
force_new_chain (bool): Force miner to create a new chain instead of use the existing one.
"""

logger.info("Create 'Miner' object ...")
Expand Down Expand Up @@ -89,7 +90,7 @@ def __init__(self, path_to_chain: str, json_format: bool, port: int, difficulty:
self._server_process = None
self._difficulty = difficulty
self._not_processed_messages = set()
self._blockchain = Blockchain(path_to_chain=encode_file_path_properly(path_to_chain), json_format=json_format)
self._blockchain = Blockchain(path_to_chain=encode_file_path_properly(path_to_chain), json_format=json_format, force_new_chain= force_new_chain)

logger.debug(f"Check chain ...")

Expand Down
27 changes: 14 additions & 13 deletions src/utils/constants.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import hashlib

from src.blockchain.data import Data
from src.blockchain.block import Block


GENESIS_DATA = Data("This is the workload of the very first Block in this chain!")
GENESIS_BLOCK = Block(index=0, data=GENESIS_DATA, proof=None, previous_hash=None)
GENESIS_BLOCK_HASH = hashlib.sha256(bytes(GENESIS_BLOCK)).hexdigest()
GENESIS_BLOCK_INDEX = 0
GENESIS_BLOCK_PROOF = None
GENESIS_BLOCK_PREV_HASH = None
GENESIS_BLOCK_DATA = "This is the workload of the very first Block in this chain!"

MAX_NEIGHBOURS = 3

Expand All @@ -19,15 +15,15 @@
HTTP_BAD = 400
HTTP_NOT_FOUND = 404

ADD_ENDPOINT = "/add"
DATA_ENDPOINT = "/data"
CHAIN_ENDPOINT = "/chain"
NEIGHBOURS_ENDPOINT = "/neighbours"
ADD_ENDPOINT = "/add"

ADD_KEY = "add"
SEND_DATA_KEY = "data"
SEND_CHAIN_KEY = "chain"
SEND_NEIGHBOURS_KEY = "neighbours"
SEND_DATA_KEY = "data"

MESSAGE_PARAM = "message"

Expand All @@ -36,15 +32,20 @@
BACKUP_LOCAL_CHAIN_TIME_SECONDS = 30
UNPROCESSED_DATA_SYNC_TIME_SECONDS = 5

MINER_LOG_SIZE = 1 * 1024 * 1024 # 2 MB
MINER_LOG_FILE = "miner.log"
MINER_LOG_SIZE = 1 * 1024 * 1024 # 2 MB
LOGGING_FORMAT = "[%(asctime)s - %(filename)-17s:%(lineno)-4s - %(levelname)-7s - %(funcName)-25s]: %(message)s"

COLOR_END = "\033[0m"
COLOR_RED = "\033[91m"
COLOR_BOLD = "\033[1m"
COLOR_GREEN = "\033[92m"


############## CLI Help messages ##############

PORT_HELP = "Port of miner."
HOST_HELP = "IPv4 Address of miner."
CHAIN_SERIALIZATION_HELP = "Defines the serialization format of chain file. One of these: ['json', 'pickle']"
DIFFICULTY_HELP = "Difficulty of the chain"
NEIGHBOURS_HELP = "Comma separated 'host:port' list, e.g.: localhost:23456,localhost:34567"
NEIGHBOURS_HELP = "Comma separated 'host:port' list, e.g.: localhost:23456,localhost:34567"
CHAIN_SERIALIZATION_HELP = "Defines the serialization format of chain file. One of these: ['json', 'pickle']"
18 changes: 17 additions & 1 deletion src/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from datetime import timedelta
from urllib.parse import urlparse

from src.utils.constants import DEFAULT_PORT
from src.utils.errors import ProgramKilledError, PortValueError
from src.utils.constants import DEFAULT_PORT, COLOR_BOLD, COLOR_END, COLOR_GREEN, COLOR_RED


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -109,6 +109,22 @@ def create_proper_url_string(host_port: (str, int), path: str) -> str:
return f"http://{host_port[0]}:{host_port[1]}/{path}"


def colorize(text: str, color: str) -> str:

if color == "bold":
return f"{COLOR_BOLD}{text}{COLOR_END}"

elif color == "green":
return f"{COLOR_GREEN}{text}{COLOR_END}"

elif color == "red":
return f"{COLOR_RED}{text}{COLOR_END}"

else:
logger.warning(f"Could not find handle for type: '{color}'")
return text


def signal_handler(signum, frame):
"""
Expand Down
12 changes: 6 additions & 6 deletions tests/test_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

from src.blockchain.data import Data
from src.blockchain.block import Block
from src.utils.constants import GENESIS_BLOCK
from src.blockchain.blockchain import Blockchain


data = Data("dummy data")
same_data = Data("dummy data")
dummy_prev_hash = "e59be601c9213694f0b72534148199b24d1ed7c1c29c02ba4602e780015a7663"
another_genesis_block = Block(index=0, data=GENESIS_BLOCK.data, proof=None, previous_hash=None)
another_genesis_block = Block(index=0, data=Blockchain.genesis_block.data, proof=None, previous_hash=None)


def test_constructor():
Expand Down Expand Up @@ -68,16 +68,16 @@ def test_timestamp():

def test_equality():

assert GENESIS_BLOCK == GENESIS_BLOCK
assert Blockchain.genesis_block == Blockchain.genesis_block
assert another_genesis_block == another_genesis_block
assert GENESIS_BLOCK != another_genesis_block
assert Blockchain.genesis_block != another_genesis_block


def test_negative_equality():

assert GENESIS_BLOCK != another_genesis_block
assert Blockchain.genesis_block != another_genesis_block

string_object = "This is a random String object"

assert GENESIS_BLOCK != string_object
assert Blockchain.genesis_block != string_object
assert string_object != another_genesis_block
23 changes: 11 additions & 12 deletions tests/test_blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
import pytest

from src.blockchain.data import Data
from src.utils.constants import GENESIS_BLOCK
from src.blockchain.block import Block
from src.blockchain.blockchain import Blockchain
from src.utils.utils import encode_file_path_properly



constructor_json_format = [True, False]

path_to_chain = "test_chain/test.chain"
Expand All @@ -28,21 +27,21 @@ def clean_chain_file_fixture():
def test_constructor(json_format, clean_chain_file_fixture):

# test without local chain
blockchain_without_local_chain = Blockchain(path_to_chain=path_to_chain, json_format=json_format)
blockchain_without_local_chain = Blockchain(path_to_chain=path_to_chain, json_format=json_format, force_new_chain=False)
assert isinstance(blockchain_without_local_chain, Blockchain)
assert blockchain_without_local_chain.chain[0] == GENESIS_BLOCK
assert blockchain_without_local_chain.last_block == GENESIS_BLOCK
assert blockchain_without_local_chain.chain[0] == Blockchain.genesis_block
assert blockchain_without_local_chain.last_block == Blockchain.genesis_block

# test with local chain
blockchain_with_local_chain = Blockchain(path_to_chain=path_to_chain, json_format=json_format)
blockchain_with_local_chain = Blockchain(path_to_chain=path_to_chain, json_format=json_format, force_new_chain=False)
assert isinstance(blockchain_with_local_chain, Blockchain)
assert blockchain_with_local_chain.chain[0] == GENESIS_BLOCK
assert blockchain_with_local_chain.last_block == GENESIS_BLOCK
assert blockchain_with_local_chain.chain[0] == Blockchain.genesis_block
assert blockchain_with_local_chain.last_block == Blockchain.genesis_block


@pytest.mark.parametrize("json_format", constructor_json_format)
def test_add_new_block(json_format, clean_chain_file_fixture):
blockchain = Blockchain(path_to_chain=path_to_chain, json_format=json_format)
blockchain = Blockchain(path_to_chain=path_to_chain, json_format=json_format, force_new_chain=False)

blockchain.add_new_block(data=Data("second block"), proof=4711, previous_hash="e59be601c9213694f0b72534148199b24d1ed7c1c29c02ba4602e780015a7663")
blockchain.add_new_block(data=Data("third block"), proof=42, previous_hash="637ae601c9213694f0b72534148199b24d1ed7c1c29c02ba4602e780015a09ab")
Expand All @@ -51,7 +50,7 @@ def test_add_new_block(json_format, clean_chain_file_fixture):
block_1 = blockchain.chain[1]
block_2 = blockchain.chain[2]

assert block_0 == GENESIS_BLOCK
assert block_0 == Blockchain.genesis_block
assert block_2 == blockchain.last_block

assert block_0 != block_1
Expand All @@ -63,10 +62,10 @@ def test_add_new_block(json_format, clean_chain_file_fixture):
@pytest.mark.parametrize("json_format", constructor_json_format)
def test_chain_method(json_format, clean_chain_file_fixture):

blockchain = Blockchain(path_to_chain=path_to_chain, json_format=json_format)
blockchain = Blockchain(path_to_chain=path_to_chain, json_format=json_format, force_new_chain=False)
chain = blockchain.chain
genesis_block = chain[0]

assert genesis_block == GENESIS_BLOCK
assert genesis_block == genesis_block

# TODO: Tests for private fuctions..

0 comments on commit ab413ed

Please sign in to comment.