From 30aa5a34759fb04a885999910cc2bb50cdd56a12 Mon Sep 17 00:00:00 2001 From: DHRUV AGGARWAL Date: Tue, 30 Jan 2018 23:21:55 +0530 Subject: [PATCH] Add chain support --- app/managers/block.py | 9 +---- app/models/__init__.py | 21 ++++++++++++ app/models/block.py | 67 +++++++++++++++++++++++++++++------- app/models/chain.py | 78 ++++++++++++++++++++++++++++++++++++++++++ config.py | 3 +- 5 files changed, 156 insertions(+), 22 deletions(-) create mode 100644 app/models/chain.py diff --git a/app/managers/block.py b/app/managers/block.py index 67205f5..e1f1852 100644 --- a/app/managers/block.py +++ b/app/managers/block.py @@ -1,16 +1,9 @@ -from common import get_block_filename, calculate_hash -import json +from common import calculate_hash from datetime import datetime from app.models.block import Block from config import NUM_ZEROS -def save_block(block): - filename = get_block_filename(block.index) - with open(filename, 'w') as block_file: - json.dump(block.__dict__(), block_file) - - def mine(last_block): index = int(last_block.index) + 1 timestamp = datetime.utcnow() diff --git a/app/models/__init__.py b/app/models/__init__.py index e69de29..77febe8 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -0,0 +1,21 @@ +def getter_setter_gen(name, type_): + def getter(self): + return getattr(self, "__" + name) + + def setter(self, value): + if not isinstance(value, type_): + raise TypeError( + "%s attribute must be set to an instance of %s" % (name, type_) + ) + setattr(self, "__" + name, value) + return property(getter, setter) + + +def auto_attr_check(cls): + new_dct = {} + for key, value in cls.__dict__.items(): + if isinstance(value, type): + value = getter_setter_gen(key, value) + new_dct[key] = value + # Creates a new class, using the modified dictionary as the class dict: + return type(cls)(cls.__name__, cls.__bases__, new_dct) diff --git a/app/models/block.py b/app/models/block.py index b0d033c..8db73d3 100644 --- a/app/models/block.py +++ b/app/models/block.py @@ -1,3 +1,8 @@ +from config import num_zeores +import json +from common import get_block_filename + + class Block(object): def __init__(self, **kwargs): """ @@ -6,22 +11,29 @@ def __init__(self, **kwargs): :param index: The index for the number of blocks in the chain :type index: int :param hash: The hash specific for this block - :type index: basestring + :type hash: basestring :param timestamp: The time when this block was created - :type index: int + :type timestamp: int :param data: Data contained by this block - :type index: str (json) + :type data: str (json) :param prev_hash: The hash for the previous block in the chain - :type index: str - :param nonce: Counter used for the proof of work implementation - :type index: int + :type prev_hash: str + :param nonce: For identifying unique requests + :type nonce: str """ - self.index = kwargs.get('index') - self.hash = kwargs.get('hash') - self.timestamp = kwargs.get('timestamp') - self.data = kwargs.get('data') - self.prev_hash = kwargs.get('prev_hash') - self.nonce = kwargs.get('nonce') + self.block_data_types = { + 'index': int, + 'nonce': int, + 'hash': str, + 'prev_hash': str, + 'timestamp': int, + 'data': str + } + for key, value in kwargs.iteritems(): + if key in self.block_data_types: + setattr(self, key, self.block_data_types[key](value)) + else: + setattr(self, key, value) def __dict__(self): return { @@ -29,8 +41,37 @@ def __dict__(self): 'timestamp': str(self.timestamp), 'prev_hash': str(self.prev_hash), 'hash': str(self.hash), - 'data': str(self.data) + 'data': str(self.data), + 'nonce': str(self.nonce) } + def __eq__(self, other): + return ( + self.index == other.index and + self.timestamp == other.timestamp and + self.prev_hash == other.prev_hash and + self.hash == other.hash and + self.data == other.data and + self.nonce == other.nonce + ) + + def __ne__(self, other): + return not self.__eq__(other) + def __str__(self): return "Block" % (self.prev_hash, self.hash) + + def is_valid_block(self): + """ + Current validity is only that the hash begins with at least NUM_ZEROS + """ + if str(self.hash[0:num_zeores]) == '0' * num_zeores: + return True + return False + + def save(self): + # Fill the index with leading zeroes so that we can see the files in + # alphabetical order + filename = get_block_filename(str(self.index).zfill(8)) + with open(filename, 'w') as block_file: + json.dump(self.__dict__(), block_file) diff --git a/app/models/chain.py b/app/models/chain.py new file mode 100644 index 0000000..8187523 --- /dev/null +++ b/app/models/chain.py @@ -0,0 +1,78 @@ +from block import Block + + +class Chain: + def __init__(self, blocks): + self.blocks = blocks + + def is_valid(self): + ''' + Is a valid blockchain if + 1) Each block is indexed one after the other + 2) Each block's prev hash is the hash of the prev block + 3) The block's hash is valid for the number of zeros + ''' + for index, cur_block in enumerate(self.blocks[1:]): + prev_block = self.blocks[index] + if prev_block.index + 1 != cur_block.index: + return False + if not cur_block.is_valid(): + return False + if prev_block.hash != cur_block.prev_hash: + return False + return True + + def save(self): + ''' + Save the blockchain to the file system + ''' + for block in self.blocks: + block.save() + + def find_block_by_hash(self, b_hash): + for block in self.blocks: + if block.hash == b_hash: + return block + return None + + def find_block_by_index(self, index): + if len(self) <= index: + return self.blocks[index] + else: + return False + + def __len__(self): + return len(self.blocks) + + def __gt__(self, other): + return len(self) > len(other) + + def __ge__(self, other): + return self.__eq__(other) or self.__gt__(other) + + def __eq__(self, other): + if len(self) != len(other): + return False + for self_block, other_block in zip(self.blocks, other.blocks): + if self_block != other_block: + return False + return True + + def max_index(self): + ''' + We're assuming a valid chain. Might change later + ''' + return self.blocks[-1].index + + def add_block(self, new_block): + ''' + Put the new block into the index that the block is asking. + That is, if the index is of one that currently exists, the new block + would take it's place. Then we want to see if that block is valid. + If it isn't, then we ditch the new block and return False. + ''' + self.blocks.append(new_block) + return True + + def block_list_dict(self): + return [b.to_dict() for b in self.blocks] diff --git a/config.py b/config.py index ac0c110..4a7f290 100644 --- a/config.py +++ b/config.py @@ -1,2 +1,3 @@ blockchain_dir = 'data' -NUM_ZEROS = 4 +broadcast_blockchain_dir = 'broadcast' +num_zeores = 5