import time import pprint from datetime import datetime class Chain(): def __init__(self): # initialize the mock blockchain self.blockchain = [] self.current_block = Block() # add old block to chain and create new block def new_block(self): self.blockchain.append(self.current_block) self.current_block = Block() pass # create new contract def new_contract(self,client, contractor, deadline, discription, name, price, people, initiator): n_contracts = 0 for block in self.blockchain + [self.current_block]: for item in block.block: if item['type'] == 'init': n_contracts += 1 # set discriptors of contract contract = {'id':n_contracts, 'type':'init', 'date of initiation':get_t(), 'initiated by': initiator, 'data': {'client':client, 'contractor':contractor, 'discription':discription, 'name':name, 'people': people}, 'terms' : {'deadline': deadline, 'accepted': False, 'progress': '0%', 'price':price, 'Sign time': 'not yet signed', 'comments':{} } } # add as transaction self.current_block.add(contract) # If block to big, get new if self.current_block.size() > 10: self.new_block() # add updates to the contract def update_contract(self, iden, attr, value, by, comment=None): # check if transaction adds or modifies self.current_contract_state(iden) if attr in self.state['data'] or attr in self.state['terms']: t = 'modify' else: t = 'update' # add new or updated info to block update = {'id':iden, 'update id':len(self.get_updates(iden)), 'updated':attr, 'type':t, 'last update':get_t(), 'updated by':by, 'accepted': False, 'change':{attr:value}} # check if comment is passed and add it if necassary if comment: update['comment'] = comment # add transaction self.current_block.add(update) # check if new block is needed if self.current_block.size() > 10: self.new_block() # accept updates def accept_update(self, iden, update_id, awnser, by, comment=None): update = { 'id':iden, 'update id':update_id, 'accept':awnser, 'type':'accept', 'last update':get_t(), 'accepted by': by } if comment: update['comment'] = comment self.current_block.add(update) # check if new block is needed if self.current_block.size() > 10: self.new_block() # get current state of the contract def current_contract_state(self, iden): # set up contract state self.state = {'id':iden, 'data':{}, 'terms':{} } b = [] # loop over all blocks in the chain for block in self.blockchain: b += block.block # retreive all the data transactions = b + self.current_block.block # save not yet approved updates updates = {} # loop over all transaction in block for item in transactions: # save data about our project if item['id'] == iden: # set state to inital contract state if item['type'] == 'init': self.state = item continue # if update is not accept update, save it to updates dict if item['type'] != 'accept': # comments are always accepted if item['updated'] != 'comments': updates[item['update id']] = item continue # if accepted is true, retrieve update form dict and add to contract state elif item['accept'] == True: item = updates[item['update id']] # set attr to whatever has been updated attr = item['updated'] # check if the update was removal if item['type'] == 'remove': # delete item in the right part of the dict try: if attr in self.state['data']: del self.state['data'][attr] else: del self.state['terms'][attr] except: pass if item['type'] == 'update': self.state['terms'][attr] = item['change'][attr] # check if updated attr is data or terms elif attr in self.state['data']: self.state['data'][attr] = item['change'][attr] elif attr in self.state['terms']: # special case for comments if attr == 'comments' and item['type'] not in ['remove', 'init']: k = list(item['change'][attr].keys())[0] self.state['terms'][attr][k] = item['change'][attr][k] # case for normal updates else: self.state['terms'][attr] = item['change'][attr] # special cases for last update and date of initiation elif 'last update' in item: self.state['last update'] = item['last update'] # get updates based on contract, atribute updated or accept state def get_updates(self, iden=None, attr=None, accepted=None): updates = [] # get all blocks and loop over transactions for block in self.blockchain + [self.current_block]: for item in block.block: # if attr is '' get updates for all attributes if attr == None: # if iden is None, get updates for all contracts # updates for all contracts and attrs if iden == None: if item['type'] != 'init': updates.append(item) # updates for specific contracts from all attr else: if item['type'] != 'init' and item['id'] == iden: updates.append(item) else: # updates for specific attrs from all contract if iden == None: if item['type'] != 'init' and item['updated'] == attr: updates.append(item) # updates for specific contract and specific attr else: if item['type'] != 'init' and item['updated'] == attr and item['id'] == iden: updates.append(item) return updates # add transaction to remove items def remove_item(self, iden, attr): # add transaction to remove item self.current_block.add({'id':iden, 'updated':attr , 'type':'remove', 'last update':get_t(), 'accept':False, 'update id':len(self.get_updates(iden))}) if self.current_block.size() > 10: self.new_block() # add comments to def add_comment(self, iden, body, author): # get all updates for comments comment_updates = [] for block in self.blockchain + [self.current_block]: for item in block.block: if 'updated' in item: if item['updated'] == 'comments' and item['id'] == iden: comment_updates.append(item) # create transaction to_add = {'id':iden, 'updated':'comments', 'accept':True, 'type':'modify', 'last update':get_t(), 'change':{'comments':{len(comment_updates):{'author':author, 'body': body, 'time': get_t(), 'contract':iden}}}} self.current_block.add(to_add) if self.current_block.size() > 10: self.new_block() # retrieve comments based on author and contract def retrieve_comments(self, iden=None, author = None): comment = [] # check every block in the chain for block in self.blockchain + [self.current_block]: for item in block.block: # if transaction is of type init, skip it if item['type'] == 'init' or item['type'] == 'accept': continue # check if the update is a comment if item['updated'] == 'comments': #filters for contract and author # no contract or author filter if iden == None and author == None: comment.append(item['change']['comments']) # filter by contract elif iden != None and author == None: if item['id'] == iden: comment.append(item['change']['comments']) # filter by author elif iden == None and author != None: if list(item['change']['comments'].values())[0]['author'] == author: comment.append(item['terms']['comments']) # filter by author and contract elif iden != None and author != None: if item['id'] == iden and list(item['change']['comments'].values())[0]['author'] == author: comment.append(item['change']['comments']) return comment # get updates for a contract that have to be accepted or are already accepted def retrieve_updates(self, iden, accepted=None): updates = {} # get all updates for a contract all_updates = self.get_updates(iden = iden, accepted = accepted) for item in all_updates: if item['type'] != 'accept' and item['updated'] != 'comments': updates[item['update id']] = item else: try: updates[item['update id']]['accepted'] = item['accept'] except: pass if accepted == None: return updates return [updates[x] for x in list(updates.keys()) if updates[x]['accepted'] == accepted] def __str__(self): print(self.current_block) return '' def populate(self): # create contracts people = ['astrix', 'obelix'] self.new_contract('some company', 'semmtech', 'two weeks', 'a sample project', 'project', 10000, people, 'astrix') people = ['samson', 'gert'] self.new_contract('The organization', 'not semmtech', '31-12-2021', 'a real project', 'r project', 10000, people, 'samson') people = ['doctor bright', '05-12'] self.new_contract('The foundation', 'scp-096', '31-12-2021', 'to contain 096', 'containment', 'at all costs', people, '05-2') # updates for contract 0 self.update_contract(0, 'deadline', '1 month', 'obelix') self.accept_update(0, 0, True, 'astrix') self.update_contract(0, 'progress', '50%', 'astrix' ,'we done with 50 %') self.update_contract(0, 'discription', 'A project sample', 'obelix') self.update_contract(0, 'progress', '75%', 'astrix') self.add_comment(0, 'I like the progress', 'astrix') # updates for contract 1 self.update_contract(1, 'accepted', True, 'gert') self.update_contract(1, 'Sign time', get_t(), 'gert') self.update_contract(1, 'name', 'actual project', 'samson') self.update_contract(1, 'price', 16969, 'gert') self.remove_item(1, 'comments') # updates for contract 2 self.add_comment(2, 'needs to be terminated', 'doctor bright') self.add_comment(2, 'doctor bright is wrong', '05-12') self.add_comment(2, 'no im right 05-12 is defo wrong', 'doctor bright') self.update_contract(2, 'test term', 'works', '05-2') # # show current state of contract 0 # self.current_contract_state(0) # pprint.pprint(self.state) # print('\n\n\n') # # show current state of contract 1 # self.current_contract_state(1) # pprint.pprint(self.state) # print('\n\n\n') # show current state of contract 2 self.current_contract_state(2) pprint.pprint(self.state) class Block(): # init block def __init__(self): self.block = [] # add new transactions def add(self, contract): self.block.append(contract) # get size of block def size(self): return len(self.block) def __str__(self): pprint.pprint(self.block) return '' # get date/time in readable format def get_t(): return datetime.utcfromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S') # format a dictionary to be readable in a string def format_dict(d): # define string to return return_string = '' # loop over all key value pairs for k in list(d.keys()): # if value is a dict, call format dict again if type(d[k]) == dict: print('geweest') return_string += k + format_dict(d[k]) # if list, join list with ', ' elif type(d[k]) == list: return_string += k + ', '.join(d[k]) # stings can just be added without further processing else: return_string += k + str(d[k]) # and an enter after every value return_string += '\n' return return_string + '\n'