|
|
@@ -0,0 +1,412 @@
|
|
|
+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'
|
|
|
+
|
|
|
+
|