From a61d21817dd8cf972ccadc714dcdb0a84c2e7fba Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Tue, 11 Jan 2022 13:04:52 +0530 Subject: [PATCH] 1.0.5 Added renamed the older parser and added the new parser --- old_parsing.py | 590 +++++++ parsing.py | 1203 +++++++++----- tracktokens_smartcontracts.py | 2758 +++++++++++++++++++++++++++++++++ 3 files changed, 4171 insertions(+), 380 deletions(-) create mode 100644 old_parsing.py create mode 100755 tracktokens_smartcontracts.py diff --git a/old_parsing.py b/old_parsing.py new file mode 100644 index 0000000..33215b6 --- /dev/null +++ b/old_parsing.py @@ -0,0 +1,590 @@ +import re +import arrow +import pdb +import configparser + +config = configparser.ConfigParser() +config.read('config.ini') + +marker = None +operation = None +address = None +amount = None + +months = { + 'jan': 1, + 'feb': 2, + 'mar': 3, + 'apr': 4, + 'may': 5, + 'jun': 6, + 'jul': 7, + 'aug': 8, + 'sep': 9, + 'oct': 10, + 'nov': 11, + 'dec': 12 +} + + +def isTransfer(text): + wordlist = ['transfer', 'send', 'give'] # keep everything lowercase + textList = text.split(' ') + for word in wordlist: + if word in textList: + return True + return False + + +def isDeposit(text): + wordlist = ['submit', 'deposit'] # keep everything lowercase + textList = text.split(' ') + for word in wordlist: + if word in textList: + return True + return False + + +def isIncorp(text): + wordlist = ['incorporate', 'create', 'start'] # keep everything lowercase + textList = text.split(' ') + for word in wordlist: + if word in textList: + return True + return False + + +def isSmartContract(text): + textList = text.split(' ') + for word in textList: + if word == '': + continue + if word.endswith('@') and len(word) != 1: + return word + return False + + +def isSmartContractPay(text): + wordlist = text.split(' ') + if len(wordlist) != 2: + return False + smartContractTrigger = re.findall(r"smartContractTrigger:'.*'", text)[0].split('smartContractTrigger:')[1] + smartContractTrigger = smartContractTrigger[1:-1] + smartContractName = re.findall(r"smartContractName:.*@", text)[0].split('smartContractName:')[1] + smartContractName = smartContractName[:-1] + + if smartContractTrigger and smartContractName: + contractconditions = { + 'smartContractTrigger': smartContractTrigger, 'smartContractName': smartContractName} + return contractconditions + else: + return False + + +def extractAmount(text, marker): + count = 0 + returnval = None + splitText = text.split('userchoice')[0].split(' ') + + for word in splitText: + word = word.replace(marker, '') + try: + float(word) + count = count + 1 + returnval = float(word) + except ValueError: + pass + + if count > 1: + return 'Too many' + return returnval + + +def extractSubmitAmount(text, marker): + count = 0 + returnval = None + text = text.split('deposit-conditions')[0] + splitText = text.split(' ') + + for word in splitText: + word = word.replace(marker, '') + try: + float(word) + count = count + 1 + returnval = float(word) + except ValueError: + continue + + if count > 1: + return 'Too many' + return returnval + + +def extractMarker(text): + textList = text.split(' ') + for word in textList: + if word == '': + continue + if word.endswith('#') and len(word) != 1: + return word + return False + + +def extractInitTokens(text): + base_units = {'thousand': 10 ** 3, 'million': 10 ** 6, 'billion': 10 ** 9, 'trillion': 10 ** 12} + textList = text.split(' ') + counter = 0 + value = None + for idx, word in enumerate(textList): + try: + result = float(word) + if textList[idx + 1] in base_units: + value = result * base_units[textList[idx + 1]] + counter = counter + 1 + else: + value = result + counter = counter + 1 + except: + for unit in base_units: + result = word.split(unit) + if len(result) == 2 and result[1] == '' and result[0] != '': + try: + value = float(result[0]) * base_units[unit] + counter = counter + 1 + except: + continue + + if counter == 1: + return value + else: + return None + + +def extractAddress(text): + textList = text.split(' ') + for word in textList: + if word == '': + continue + if word[-1] == '$' and len(word) != 1: + return word + return None + + +def extractContractType(text): + # keep everything lowercase + operationList = ['one-time-event*', 'continuous-event*'] + count = 0 + returnval = None + for operation in operationList: + count = count + text.count(operation) + if count > 1: + return 'Too many' + if count == 1 and (returnval is None): + returnval = operation + return returnval + + +def extractUserchoice(text): + result = re.split('userchoice:\s*', text) + if len(result) != 1 and result[1] != '': + return result[1].strip().strip('"').strip("'") + else: + return None + + +def brackets_toNumber(item): + return float(item[1:-1]) + + +def extractContractConditions(text, contracttype, blocktime, marker=None): + rulestext = re.split('contract-conditions:\s*', text)[-1] + # rulelist = re.split('\d\.\s*', rulestext) + rulelist = [] + numberList = re.findall(r'\(\d\d*\)', rulestext) + + for idx, item in enumerate(numberList): + numberList[idx] = int(item[1:-1]) + + numberList = sorted(numberList) + for idx, item in enumerate(numberList): + if numberList[idx] + 1 != numberList[idx + 1]: + print('Contract condition numbers are not in order') + return None + if idx == len(numberList) - 2: + break + + for i in range(len(numberList)): + rule = rulestext.split('({})'.format( + i + 1))[1].split('({})'.format(i + 2))[0] + rulelist.append(rule.strip()) + + if contracttype == 'one-time-event*': + extractedRules = {} + for rule in rulelist: + if rule == '': + continue + elif rule[:10] == 'expirytime': + expirytime = re.split('expirytime[\s]*=[\s]*', rule)[1].strip() + + try: + expirytime_split = expirytime.split(' ') + parse_string = '{}/{}/{} {}'.format( + expirytime_split[3], months[expirytime_split[1]], expirytime_split[2], expirytime_split[4]) + expirytime_object = arrow.get( + parse_string, 'YYYY/M/D HH:mm:ss').replace(tzinfo=expirytime_split[5]) + blocktime_object = arrow.get(blocktime) + if expirytime_object < blocktime_object: + print( + 'Expirytime of the contract is earlier than the block it is incorporated in. This incorporation will be rejected ') + return None + extractedRules['expiryTime'] = expirytime + except: + print('Error parsing expiry time') + return None + + for rule in rulelist: + if rule == '': + continue + elif rule[:14] == 'contractamount': + pattern = re.compile('[^contractamount\s*=\s*].*') + searchResult = pattern.search(rule).group(0) + contractamount = searchResult.split(marker)[0] + try: + extractedRules['contractAmount'] = float(contractamount) + except: + print("Contract amount entered is not a decimal") + elif rule[:11] == 'userchoices': + pattern = re.compile('[^userchoices\s*=\s*].*') + conditions = pattern.search(rule).group(0) + conditionlist = conditions.split('|') + extractedRules['userchoices'] = {} + for idx, condition in enumerate(conditionlist): + extractedRules['userchoices'][idx] = condition.strip() + elif rule[:25] == 'minimumsubscriptionamount': + pattern = re.compile('[^minimumsubscriptionamount\s*=\s*].*') + searchResult = pattern.search(rule).group(0) + minimumsubscriptionamount = searchResult.split(marker)[0] + try: + extractedRules['minimumsubscriptionamount'] = float( + minimumsubscriptionamount) + except: + print("Minimum subscription amount entered is not a decimal") + elif rule[:25] == 'maximumsubscriptionamount': + pattern = re.compile('[^maximumsubscriptionamount\s*=\s*].*') + searchResult = pattern.search(rule).group(0) + maximumsubscriptionamount = searchResult.split(marker)[0] + try: + extractedRules['maximumsubscriptionamount'] = float( + maximumsubscriptionamount) + except: + print("Maximum subscription amount entered is not a decimal") + elif rule[:12] == 'payeeaddress': + pattern = re.compile('[^payeeAddress\s*=\s*].*') + searchResult = pattern.search(rule).group(0) + payeeAddress = searchResult.split(marker)[0] + extractedRules['payeeAddress'] = payeeAddress + + if len(extractedRules) > 1 and 'expiryTime' in extractedRules: + return extractedRules + else: + return None + + elif contracttype == 'continuous-event*': + extractedRules = {} + for rule in rulelist: + print(rule) + if rule == '': + continue + elif rule[:7] == 'subtype': + # todo : recheck the regular expression for subtype, find an elegant version which covers all permutations and combinations + '''pattern = re.compile('[^subtype\s*=].*') + searchResult = pattern.search(rule).group(0) + subtype = searchResult.split(marker)[0]''' + extractedRules['subtype'] = rule.split('=')[1].strip() + elif rule[:15] == 'accepting_token': + pattern = re.compile('[^accepting_token\s*=\s*].*') + searchResult = pattern.search(rule).group(0) + accepting_token = searchResult.split(marker)[0] + extractedRules['accepting_token'] = accepting_token + elif rule[:13] == 'selling_token': + pattern = re.compile('[^selling_token\s*=\s*].*') + searchResult = pattern.search(rule).group(0) + selling_token = searchResult.split(marker)[0] + extractedRules['selling_token'] = selling_token + elif rule[:9].lower() == 'pricetype': + pattern = re.compile('[^pricetype\s*=\s*].*') + searchResult = pattern.search(rule).group(0) + priceType = searchResult.split(marker)[0] + extractedRules['priceType'] = priceType + elif rule[:5] == 'price': + pattern = re.compile('[^price\s*=\s*].*') + searchResult = pattern.search(rule).group(0) + price = searchResult.split(marker)[0] + if price[0]=="'" or price[0]=='"': + price = price[1:] + if price[-1]=="'" or price[-1]=='"': + price = price[:-1] + extractedRules['price'] = float(price) + # else: + # pdb.set_trace() + if len(extractedRules) > 1: + return extractedRules + else: + return None + + return None + + +def extractDepositConditions(text, blocktime): + rulestext = re.split('deposit-conditions:\s*', text)[-1] + # rulelist = re.split('\d\.\s*', rulestext) + rulelist = [] + numberList = re.findall(r'\(\d\d*\)', rulestext) + for idx, item in enumerate(numberList): + numberList[idx] = int(item[1:-1]) + + numberList = sorted(numberList) + for idx, item in enumerate(numberList): + if len(numberList) > 1 and numberList[idx] + 1 != numberList[idx + 1]: + print('Deposit condition numbers are not in order') + return None + if idx == len(numberList) - 2: + break + + for i in range(len(numberList)): + rule = rulestext.split('({})'.format(i + 1))[1].split('({})'.format(i + 2))[0] + rulelist.append(rule.strip()) + + # elif contracttype == 'continuous-event*': + extractedRules = {} + for rule in rulelist: + if rule == '': + continue + elif rule[:10] == 'expirytime': + expirytime = re.split('expirytime[\s]*=[\s]*', rule)[1].strip() + + try: + expirytime_split = expirytime.split(' ') + parse_string = '{}/{}/{} {}'.format(expirytime_split[3], months[expirytime_split[1]], expirytime_split[2], expirytime_split[4]) + expirytime_object = arrow.get(parse_string, 'YYYY/M/D HH:mm:ss').replace(tzinfo=expirytime_split[5]) + blocktime_object = arrow.get(blocktime) + if expirytime_object < blocktime_object: + print('Expirytime of the contract is earlier than the block it is incorporated in. This incorporation will be rejected ') + return None + extractedRules['expiryTime'] = expirytime + except: + print('Error parsing expiry time') + return None + + """for rule in rulelist: + if rule == '': + continue + elif rule[:7] == 'subtype': + subtype=rule[8:] + #pattern = re.compile('[^subtype\s*=\s*].*') + #searchResult = pattern.search(rule).group(0) + #contractamount = searchResult.split(marker)[0] + extractedRules['subtype'] = subtype """ + + if len(extractedRules) > 0: + return extractedRules + else: + return None + + +def extractTriggerCondition(text): + searchResult = re.search('\".*\"', text) + if searchResult is None: + searchResult = re.search('\'.*\'', text) + return searchResult + return searchResult + + +# Combine test +def parse_flodata(string, blockinfo, netvariable): + + print("Break point at the first line of parsing function") + # todo Rule 20 - remove 'text:' from the start of flodata if it exists + if string[0:5] == 'text:': + string = string.split('text:')[1] + + # todo Rule 21 - Collapse multiple spaces into a single space in the whole of flodata + # todo Rule 22 - convert flodata to lowercase to make the system case insensitive + nospacestring = re.sub('\t', ' ', string) + nospacestring = re.sub('\n', ' ', nospacestring) + nospacestring = re.sub(' +', ' ', nospacestring) + cleanstring = nospacestring.lower() + #cleanstring_noconditions = cleanstring.split('contract-conditions:')[0] + cleanstring_split = re.compile("contract-conditions*[' ']:").split(cleanstring) + + # todo Rule 23 - Count number of words ending with @ and # + atList = [] + hashList = [] + starList = [] + + for word in cleanstring_split[0].split(' '): + if word.endswith('*') and len(word) != 1: + starList.append(word) + if word.endswith('@') and len(word) != 1: + atList.append(word) + + if len(starList) != 1 or starList[0] not in ['one-time-event*', 'continuous-event*']: + parsed_data = {'type': 'noise'} + else: + if starList == 'one-time-event*': + for word in cleanstring_split[0].split(' '): + if word.endswith('#') and len(word) != 1: + hashList.append(word) + elif starList == 'continuous-event*': + for word in cleanstring_split[1].split(' '): + if word.endswith('#') and len(word) != 1: + hashList.append(word) + + + '''for word in cleanstring_noconditions.split(' '): + if word.endswith('@') and len(word) != 1: + atList.append(word) + if word.endswith('#') and len(word) != 1: + hashList.append(word) + if word.endswith('*') and len(word) != 1: + starList.append(word) + ''' + + print('') + # todo Rule 24 - Reject the following conditions - a. number of # & number of @ is equal to 0 then reject + # todo Rule 25 - If number of # or number of @ is greater than 1, reject + # todo Rule 25.a - If a transaction is rejected, it means parsed_data type is noise + # Filter noise first - check if the words end with either @ or # + if (len(atList) == 0 and len(hashList) == 0) or len(atList) > 1 or len(hashList) > 2 or len(starList) > 1: + parsed_data = {'type': 'noise'} + + # todo Rule 26 - if number of # is 1 and numbner of @ is 0 and number of * is 0, then check if its token creation or token transfer transaction + elif len(hashList) == 1 and len(atList) == 0 and len(starList) == 0: + # Passing the above check means token creation or transfer + incorporation = isIncorp(cleanstring) + transfer = isTransfer(cleanstring) + + # todo Rule 27 - if (neither token incorporation a + if (not incorporation and not transfer) or (incorporation and transfer): + parsed_data = {'type': 'noise'} + + # todo Rule 28 - if token creation and not token transfer then it is confirmed that is it a token creation transaction + # todo Rule 29 - Extract total number of tokens issued, if its not mentioned then reject + elif incorporation and not transfer: + initTokens = extractInitTokens(cleanstring) + if initTokens is not None: + parsed_data = {'type': 'tokenIncorporation', 'flodata': string, 'tokenIdentification': hashList[0][:-1], 'tokenAmount': initTokens} + else: + parsed_data = {'type': 'noise'} + + # todo Rule 30 - if not token creation and is token transfer then then process it for token transfer rules + # todo Rule 31 - Extract number of tokens to be sent and the address to which to be sent, both data is mandatory + elif not incorporation and transfer: + amount = extractAmount(cleanstring, hashList[0][:-1]) + if None not in [amount]: + parsed_data = {'type': 'transfer', 'transferType': 'token', 'flodata': string, + 'tokenIdentification': hashList[0][:-1], + 'tokenAmount': amount} + else: + parsed_data = {'type': 'noise'} + + # todo Rule 32 - if number of @ is 1, then process for smart contract transfer or creation or trigger + elif len(atList) == 1: + # Passing the above check means Smart Contract creation or transfer + incorporation = isIncorp(cleanstring) + transfer = isTransfer(cleanstring) + deposit = isDeposit(cleanstring) + # todo Rule 33 - if a confusing smart contract command is given, like creating and sending at the same time, or no + if incorporation and transfer: + parsed_data = {'type': 'noise'} + + # todo Rule 34 - if incorporation and not transfer, then extract type of contract, address of the contract and conditions of the contract. Reject if any of those is not present + elif incorporation and not transfer: + contracttype = extractContractType(cleanstring) + contractaddress = extractAddress(nospacestring) + + if contracttype == 'one-time-event*' and len(hashList) == 1: + contractconditions = extractContractConditions(cleanstring, contracttype, blocktime=blockinfo['time'], marker=hashList[0][:-1]) + elif contracttype == 'continuous-event*': + contractconditions = extractContractConditions(cleanstring, contracttype, blocktime=blockinfo['time']) + else: + parsed_data = {'type': 'noise'} + return parsed_data + + if config['DEFAULT']['NET'] == 'mainnet' and blockinfo['height'] < 3454510: + if None not in [contracttype, contractconditions]: + parsed_data = {'type': 'smartContractIncorporation', 'contractType': contracttype[:-1], + 'tokenIdentification': hashList[0][:-1], 'contractName': atList[0][:-1], + 'contractAddress': contractaddress[:-1], 'flodata': string, + 'contractConditions': contractconditions} + else: + parsed_data = {'type': 'noise'} + else: + if None not in [contracttype, contractaddress, contractconditions] and contracttype[:-1] == 'one-time-event': + parsed_data = {'type': 'smartContractIncorporation', 'contractType': contracttype[:-1], + 'tokenIdentification': hashList[0][:-1], 'contractName': atList[0][:-1], + 'contractAddress': contractaddress[:-1], 'flodata': string, + 'contractConditions': contractconditions} + elif None not in [contracttype, contractaddress, contractconditions] and contracttype[:-1] == 'continuous-event': + if contractconditions['subtype'] == 'tokenswap' and ('priceType' not in contractconditions): + parsed_data = {'type': 'noise'} + return parsed_data + else: + parsed_data = {'type': 'smartContractIncorporation', 'contractType': contracttype[:-1], 'contractName': atList[0][:-1], + 'contractAddress': contractaddress[:-1], 'flodata': string, + 'contractConditions': contractconditions} + else: + parsed_data = {'type': 'noise'} + + # todo Rule 35 - if it is not incorporation and it is transfer, then extract smart contract amount to be locked and userPreference. If any of them is missing, then reject + elif not incorporation and transfer: + # todo - Temporary check for transaction 22196cc761043b96a06fcfe5e58af2dafb90c7d222dcb909b537f7ee6715f0bd on testnet , figure out an elegant way of doing this + if len(hashList) == 0: + parsed_data = {'type': 'noise'} + return parsed_data + + # We are at the send/transfer of smart contract + amount = extractAmount(cleanstring, hashList[0][:-1]) + userChoice = extractUserchoice(cleanstring) + contractaddress = extractAddress(nospacestring) + if None not in [amount, userChoice]: + parsed_data = {'type': 'transfer', 'transferType': 'smartContract', 'flodata': string, + 'tokenIdentification': hashList[0][:-1], + 'operation': 'transfer', 'tokenAmount': amount, 'contractName': atList[0][:-1], + 'userChoice': userChoice} + if contractaddress: + parsed_data['contractAddress'] = contractaddress[:-1] + else: + parsed_data = {'type': 'noise'} + + elif deposit: + # Figure out amount of token to be submitted + for word in cleanstring_split[0].split(' '): + if word.endswith('#') and len(word) != 1: + hashList.append(word) + depositAmount = extractSubmitAmount(cleanstring_split[0], hashList[0][:-1]) + # '' name of token = hashList[0] + # '' name of Smart Contract = atList[0] + # '' FLO address of the Smart Contract + # '' Submit conditions + deposit_conditions = extractDepositConditions(cleanstring, blocktime=blockinfo['time']) + if None not in [deposit_conditions]: + parsed_data = {'type': 'smartContractDeposit', + 'tokenIdentification': hashList[0][:-1], 'depositAmount': depositAmount, 'contractName': atList[0][:-1], 'flodata': string, + 'depositConditions': deposit_conditions} + else: + parsed_data = {'type': 'noise'} + + # If flodata doesn't indicate incorporation nor transfer, check if its a committee trigger + elif (not incorporation and not transfer): + # Passing the above check means Smart Contract pays | exitcondition triggered from the committee + # todo Rule 37 - Extract the trigger condition given by the committee. If its missing, reject + triggerCondition = extractTriggerCondition(cleanstring) + if triggerCondition is not None: + parsed_data = {'type': 'smartContractPays', 'contractName': atList[0][:-1], 'triggerCondition': triggerCondition.group().strip()[1:-1]} + else: + parsed_data = {'type': 'noise'} + else: + parsed_data = {'type': 'noise'} + + return parsed_data \ No newline at end of file diff --git a/parsing.py b/parsing.py index 5e0d883..3a7bffe 100644 --- a/parsing.py +++ b/parsing.py @@ -1,15 +1,113 @@ +import pdb import re import arrow -import pdb -import configparser +import pybtc -config = configparser.ConfigParser() -config.read('config.ini') +""" +Find make lists of #, *, @ words -marker = None -operation = None -address = None -amount = None +If only 1 hash word and nothing else, then it is token related ( tokencreation or tokentransfer ) + +If @ is present, then we know it is smart contract related + @ (#)pre: - participation , deposit + @ * (#)pre: - one time event creation + @ * (# #)post: - token swap creation + @ - trigger + +Check for 1 @ only +Check for 1 # only +Check for @ (#)pre: +Check for @ * (#)pre: +Check for @ * (# #)post: + +special_character_frequency = { + 'precolon': { + '#':0, + '*':0, + '@':0, + ':':0 +} + +for word in allList: + if word.endswith('#'): + special_character_frequency['#'] = special_character_frequency['#'] + 1 + elif word.endswith('*'): + special_character_frequency['*'] = special_character_frequency['*'] + 1 + elif word.endswith('@'): + special_character_frequency['@'] = special_character_frequency['@'] + 1 + elif word.endswith(':'): + special_character_frequency[':'] = special_character_frequency[':'] + 1 + +""" + +''' +def className(rawstring): + # Create a list that contains @ , # , * and : ; in actual order of occurence with their words. Only : is allowed to exist without a word in front of it. + # Check for 1 @ only followed by :, and the class is trigger + # Check for 1 # only, then the class is tokensystem + # Check for @ in the first position, * in the second position, # in the third position and : in the fourth position, then class is one time event creation + # Check for @ in the first position, * in the second position and : in the third position, then hash is in 4th position, then hash in 5th position | Token swap creation + + allList = findrules(rawstring,['#','*','@',':']) + + pattern_list1 = ['rmt@','rmt*',':',"rmt#","rmt#"] + pattern_list2 = ['rmt#',':',"rmt@"] + pattern_list3 = ['rmt#'] + pattern_list4 = ["rmt@","one-time-event*","floAddress$",':',"rupee#","bioscope#"] + patternmatch = find_first_classification(pattern_list4, search_patterns) + print(f"Patternmatch is {patternmatch}") + + +rawstring = "test rmt# rmt@ rmt* : rmt# rmt# test" +#className(rawstring) ''' + +# Variable configurations +search_patterns = { + 'tokensystem-C':{ + 1:['#'] + }, + 'smart-contract-creation-C':{ + 1:['@','*','#','$',':'], + 2:['@','*','#','$',':','#'] + }, + 'smart-contract-participation-deposit-C':{ + 1:['#','@',':'], + 2:['#','@','$',':'] + }, + 'userchoice-trigger':{ + 1:['@'] + }, + 'smart-contract-participation-ote-ce-C':{ + 1:['#','@'], + 2:['#','@','$'] + }, + 'smart-contract-creation-ce-tokenswap':{ + 1:['@','*','$',':','#','#'] + } +} + +conflict_matrix = { + 'tokensystem-C':{ + # Check for send, check for create, if both are there noise, else conflict resolved + 'tokentransfer', + 'tokencreation' + }, + 'smart-contract-creation-C':{ + # Check contract-conditions for userchoice, if present then userchoice contract, else time based contract + 'creation-one-time-event-userchoice', + 'creation-one-time-event-timebased' + }, + 'smart-contract-participation-deposit-C':{ + # Check *-word, its either one-time-event or a continuos-event + 'participation-one-time-event-userchoice', + 'deposit-continuos-event-tokenswap' + }, + 'smart-contract-participation-ote-ce-C':{ + # Check *-word, its either one-time-event or a continuos-event + 'participation-one-time-event-timebased', + 'participation-continuos-event-tokenswap' + } +} months = { 'jan': 1, @@ -26,177 +124,164 @@ months = { 'dec': 12 } +# HELPER FUNCTIONS -def isTransfer(text): - wordlist = ['transfer', 'send', 'give'] # keep everything lowercase - textList = text.split(' ') - for word in wordlist: - if word in textList: - return True - return False - - -def isDeposit(text): - wordlist = ['submit', 'deposit'] # keep everything lowercase - textList = text.split(' ') - for word in wordlist: - if word in textList: - return True - return False - - -def isIncorp(text): - wordlist = ['incorporate', 'create', 'start'] # keep everything lowercase - textList = text.split(' ') - for word in wordlist: - if word in textList: - return True - return False - - -def isSmartContract(text): - textList = text.split(' ') - for word in textList: - if word == '': - continue - if word.endswith('@') and len(word) != 1: - return word - return False - - -def isSmartContractPay(text): - wordlist = text.split(' ') - if len(wordlist) != 2: - return False - smartContractTrigger = re.findall(r"smartContractTrigger:'.*'", text)[0].split('smartContractTrigger:')[1] - smartContractTrigger = smartContractTrigger[1:-1] - smartContractName = re.findall(r"smartContractName:.*@", text)[0].split('smartContractName:')[1] - smartContractName = smartContractName[:-1] - - if smartContractTrigger and smartContractName: - contractconditions = { - 'smartContractTrigger': smartContractTrigger, 'smartContractName': smartContractName} - return contractconditions - else: - return False - - -def extractAmount(text, marker): - count = 0 - returnval = None - splitText = text.split('userchoice')[0].split(' ') - - for word in splitText: - word = word.replace(marker, '') - try: - float(word) - count = count + 1 - returnval = float(word) - except ValueError: - pass - - if count > 1: - return 'Too many' - return returnval - - -def extractSubmitAmount(text, marker): - count = 0 - returnval = None - text = text.split('deposit-conditions')[0] - splitText = text.split(' ') - - for word in splitText: - word = word.replace(marker, '') - try: - float(word) - count = count + 1 - returnval = float(word) - except ValueError: - continue - - if count > 1: - return 'Too many' - return returnval - - -def extractMarker(text): - textList = text.split(' ') - for word in textList: - if word == '': - continue - if word.endswith('#') and len(word) != 1: - return word - return False - - -def extractInitTokens(text): - base_units = {'thousand': 10 ** 3, 'million': 10 ** 6, 'billion': 10 ** 9, 'trillion': 10 ** 12} - textList = text.split(' ') - counter = 0 - value = None - for idx, word in enumerate(textList): - try: - result = float(word) - if textList[idx + 1] in base_units: - value = result * base_units[textList[idx + 1]] - counter = counter + 1 - else: - value = result - counter = counter + 1 - except: - for unit in base_units: - result = word.split(unit) - if len(result) == 2 and result[1] == '' and result[0] != '': - try: - value = float(result[0]) * base_units[unit] - counter = counter + 1 - except: - continue - - if counter == 1: - return value - else: +# Find some value or return as noise +def apply_rule1(*argv): + a = argv[0](*argv[1:]) + if a is False: return None - - -def extractAddress(text): - textList = text.split(' ') - for word in textList: - if word == '': - continue - if word[-1] == '$' and len(word) != 1: - return word - return None - - -def extractContractType(text): - # keep everything lowercase - operationList = ['one-time-event*', 'continuous-event*'] - count = 0 - returnval = None - for operation in operationList: - count = count + text.count(operation) - if count > 1: - return 'Too many' - if count == 1 and (returnval is None): - returnval = operation - return returnval - - -def extractUserchoice(text): - result = re.split('userchoice:\s*', text) - if len(result) != 1 and result[1] != '': - return result[1].strip().strip('"').strip("'") else: - return None + return a -def brackets_toNumber(item): - return float(item[1:-1]) +# conflict_list = [['userchoice','payeeaddress'],['userchoice','xxx']] +def resolve_incategory_conflict(input_dictionary , conflict_list): + for conflict_pair in conflict_list: + key0 = conflict_pair[0] + key1 = conflict_pair[1] + dictionary_keys = input_dictionary.keys() + if (key0 in dictionary_keys and key1 in dictionary_keys) or (key0 not in dictionary_keys and key1 not in dictionary_keys): + return False + else: + return True -def extractContractConditions(text, contracttype, blocktime, marker=None): +def remove_empty_from_dict(d): + if type(d) is dict: + return dict((k, remove_empty_from_dict(v)) for k, v in d.items() if v and remove_empty_from_dict(v)) + elif type(d) is list: + return [remove_empty_from_dict(v) for v in d if v and remove_empty_from_dict(v)] + else: + return d + + +def outputreturn(*argv): + if argv[0] == 'noise': + parsed_data = {'type': 'noise'} + return parsed_data + elif argv[0] == 'token_incorporation': + parsed_data = { + 'type': 'tokenIncorporation', + 'flodata': argv[1], #string + 'tokenIdentification': argv[2], #hashList[0][:-1] + 'tokenAmount': argv[3] #initTokens + } + return parsed_data + elif argv[0] == 'token_transfer': + parsed_data = { + 'type': 'transfer', + 'transferType': 'token', + 'flodata': argv[1], #string + 'tokenIdentification': argv[2], #hashList[0][:-1] + 'tokenAmount': argv[3] #amount + } + return parsed_data + elif argv[0] == 'one-time-event-userchoice-smartcontract-incorporation': + parsed_data = { + 'type': 'smartContractIncorporation', + 'contractType': 'one-time-event', + 'tokenIdentification': argv[1], #hashList[0][:-1] + 'contractName': argv[2], #atList[0][:-1] + 'contractAddress': argv[3], #contractaddress[:-1] + 'flodata': argv[4], #string + 'contractConditions': { + 'contractamount' : argv[5], + 'minimumsubscriptionamount' : argv[6], + 'maximumsubscriptionamount' : argv[7], + 'userchoice' : argv[8], + 'expiryTime' : argv[9] + } + } + return remove_empty_from_dict(parsed_data) + elif argv[0] == 'one-time-event-userchoice-smartcontract-participation': + parsed_data = { + 'type': 'transfer', + 'transferType': 'smartContract', + 'flodata': argv[1], #string + 'tokenIdentification': argv[2], #hashList[0][:-1] + 'operation': 'transfer', + 'tokenAmount': argv[3], #amount + 'contractName': argv[4], #atList[0][:-1] + 'contractAddress': argv[5], + 'userChoice': argv[6] #userChoice + } + return remove_empty_from_dict(parsed_data) + elif argv[0] == 'one-time-event-userchoice-smartcontract-trigger': + parsed_data = { + 'type': 'smartContractPays', + 'contractName': argv[1], #atList[0][:-1] + 'triggerCondition': argv[2] #triggerCondition.group().strip()[1:-1] + } + return parsed_data + elif argv[0] == 'one-time-event-time-smartcontract-incorporation': + parsed_data = { + 'type': 'smartContractIncorporation', + 'contractType': 'one-time-event', + 'tokenIdentification': argv[1], #hashList[0][:-1] + 'contractName': argv[2], #atList[0][:-1] + 'contractAddress': argv[3], #contractaddress[:-1] + 'flodata': argv[4], #string + 'contractConditions': { + 'contractamount' : argv[5], + 'minimumsubscriptionamount' : argv[6], + 'maximumsubscriptionamount' : argv[7], + 'payeeaddress' : argv[8], + 'expiryTime' : argv[9] + } + } + return remove_empty_from_dict(parsed_data) + elif argv[0] == 'continuos-event-token-swap-incorporation': + parsed_data = { + 'type': 'smartContractIncorporation', + 'contractType': 'continuos-event', + 'tokenIdentification': argv[1], #hashList[0][:-1] + 'contractName': argv[2], #atList[0][:-1] + 'contractAddress': argv[3], #contractaddress[:-1] + 'flodata': argv[4], #string + 'contractConditions': { + 'subtype' : argv[5], #tokenswap + 'accepting_token' : argv[6], + 'selling_token' : argv[7], + 'pricetype' : argv[8], + 'price' : argv[9], + } + } + return parsed_data + elif argv[0] == 'continuos-event-token-swap-deposit': + parsed_data = { + 'type': 'smartContractDeposit', + 'tokenIdentification': argv[1], #hashList[0][:-1] + 'depositAmount': argv[2], #depositAmount + 'contractName': argv[3], #atList[0][:-1] + 'flodata': argv[4], #string + 'depositConditions': { + 'expiryTime' : argv[5] + } + } + return parsed_data + elif argv[0] == 'smart-contract-one-time-event-continuos-event-participation': + parsed_data = { + 'type': 'transfer', + 'transferType': 'smartContract', + 'flodata': argv[1], #string + 'tokenIdentification': argv[2], #hashList[0][:-1] + 'tokenAmount': argv[3], #amount + 'contractName': argv[4], #atList[0][:-1] + 'contractAddress': argv[5] + } + return remove_empty_from_dict(parsed_data) + + +def extract_specialcharacter_words(rawstring, special_characters): + wordList = [] + for word in rawstring.split(' '): + if (len(word) != 1 or word==":") and word[-1] in special_characters: + wordList.append(word) + return wordList + + +def extract_contract_conditions(text, contract_type, marker=None, blocktime=None): rulestext = re.split('contract-conditions:\s*', text)[-1] # rulelist = re.split('\d\.\s*', rulestext) rulelist = [] @@ -214,33 +299,28 @@ def extractContractConditions(text, contracttype, blocktime, marker=None): break for i in range(len(numberList)): - rule = rulestext.split('({})'.format( - i + 1))[1].split('({})'.format(i + 2))[0] + rule = rulestext.split('({})'.format(i + 1))[1].split('({})'.format(i + 2))[0] rulelist.append(rule.strip()) - if contracttype == 'one-time-event*': + if contract_type == 'one-time-event': extractedRules = {} for rule in rulelist: if rule == '': continue elif rule[:10] == 'expirytime': expirytime = re.split('expirytime[\s]*=[\s]*', rule)[1].strip() - try: expirytime_split = expirytime.split(' ') - parse_string = '{}/{}/{} {}'.format( - expirytime_split[3], months[expirytime_split[1]], expirytime_split[2], expirytime_split[4]) - expirytime_object = arrow.get( - parse_string, 'YYYY/M/D HH:mm:ss').replace(tzinfo=expirytime_split[5]) + parse_string = '{}/{}/{} {}'.format(expirytime_split[3], months[expirytime_split[1]], expirytime_split[2], expirytime_split[4]) + expirytime_object = arrow.get(parse_string, 'YYYY/M/D HH:mm:ss').replace(tzinfo=expirytime_split[5]) blocktime_object = arrow.get(blocktime) if expirytime_object < blocktime_object: - print( - 'Expirytime of the contract is earlier than the block it is incorporated in. This incorporation will be rejected ') - return None + print('Expirytime of the contract is earlier than the block it is incorporated in. This incorporation will be rejected ') + return False extractedRules['expiryTime'] = expirytime except: print('Error parsing expiry time') - return None + return False for rule in rulelist: if rule == '': @@ -289,10 +369,73 @@ def extractContractConditions(text, contracttype, blocktime, marker=None): else: return None - elif contracttype == 'continuous-event*': + elif contract_type == 'continuous-event': + extractedRules = {} + for rule in rulelist: + if rule == '': + continue + elif rule[:7] == 'subtype': + # todo : recheck the regular expression for subtype, find an elegant version which covers all permutations and combinations + pattern = re.compile('(?<=subtype\s=\s).*') + subtype = pattern.search(rule).group(0) + extractedRules['subtype'] = subtype + elif rule[:15] == 'accepting_token': + pattern = re.compile('(?<=accepting_token\s=\s).*(? 1: + return extractedRules + else: + return False + return False + + +def extract_tokenswap_contract_conditions(processed_text, contract_type, contract_token): + rulestext = re.split('contract-conditions:\s*', processed_text)[-1] + # rulelist = re.split('\d\.\s*', rulestext) + rulelist = [] + numberList = re.findall(r'\(\d\d*\)', rulestext) + + for idx, item in enumerate(numberList): + numberList[idx] = int(item[1:-1]) + + numberList = sorted(numberList) + for idx, item in enumerate(numberList): + if numberList[idx] + 1 != numberList[idx + 1]: + print('Contract condition numbers are not in order') + return None + if idx == len(numberList) - 2: + break + + for i in range(len(numberList)): + rule = rulestext.split('({})'.format(i + 1))[1].split('({})'.format(i + 2))[0] + rulelist.append(rule.strip()) + + if contract_type == 'continuous-event': extractedRules = {} for rule in rulelist: - print(rule) if rule == '': continue elif rule[:7] == 'subtype': @@ -302,40 +445,40 @@ def extractContractConditions(text, contracttype, blocktime, marker=None): subtype = searchResult.split(marker)[0]''' extractedRules['subtype'] = rule.split('=')[1].strip() elif rule[:15] == 'accepting_token': - pattern = re.compile('[^accepting_token\s*=\s*].*') - searchResult = pattern.search(rule).group(0) - accepting_token = searchResult.split(marker)[0] + pattern = re.compile('(?<=accepting_token\s=\s).*(? 1: return extractedRules else: return None - + return None -def extractDepositConditions(text, blocktime): +def extract_deposit_conditions(text, blocktime=None): rulestext = re.split('deposit-conditions:\s*', text)[-1] # rulelist = re.split('\d\.\s*', rulestext) rulelist = [] @@ -352,8 +495,7 @@ def extractDepositConditions(text, blocktime): break for i in range(len(numberList)): - rule = rulestext.split('({})'.format( - i + 1))[1].split('({})'.format(i + 2))[0] + rule = rulestext.split('({})'.format(i + 1))[1].split('({})'.format(i + 2))[0] rulelist.append(rule.strip()) # elif contracttype == 'continuous-event*': @@ -363,22 +505,18 @@ def extractDepositConditions(text, blocktime): continue elif rule[:10] == 'expirytime': expirytime = re.split('expirytime[\s]*=[\s]*', rule)[1].strip() - try: expirytime_split = expirytime.split(' ') - parse_string = '{}/{}/{} {}'.format( - expirytime_split[3], months[expirytime_split[1]], expirytime_split[2], expirytime_split[4]) - expirytime_object = arrow.get( - parse_string, 'YYYY/M/D HH:mm:ss').replace(tzinfo=expirytime_split[5]) + parse_string = '{}/{}/{} {}'.format(expirytime_split[3], months[expirytime_split[1]], expirytime_split[2], expirytime_split[4]) + expirytime_object = arrow.get(parse_string, 'YYYY/M/D HH:mm:ss').replace(tzinfo=expirytime_split[5]) blocktime_object = arrow.get(blocktime) if expirytime_object < blocktime_object: - print( - 'Expirytime of the contract is earlier than the block it is incorporated in. This incorporation will be rejected ') - return None + print('Expirytime of the contract is earlier than the block it is incorporated in. This incorporation will be rejected ') + return False extractedRules['expiryTime'] = expirytime except: print('Error parsing expiry time') - return None + return False """for rule in rulelist: if rule == '': @@ -392,204 +530,509 @@ def extractDepositConditions(text, blocktime): if len(extractedRules) > 0: return extractedRules + else: + return False + + +def extract_special_character_word(special_character_list, special_character): + for word in special_character_list: + if word.endswith(special_character): + return word[:-1] + return False + + +def find_original_case(contract_address, original_text): + dollar_word = extract_specialcharacter_words(original_text,["$"]) + if len(dollar_word)==1 and dollar_word[0][:-1].lower()==contract_address: + return dollar_word[0][:-1] + else: + None + + +def find_word_index_fromstring(originaltext, word): + lowercase_text = originaltext.lower() + result = lowercase_text.find(word) + return originaltext[result:result+len(word)] + + +def find_first_classification(parsed_word_list, search_patterns): + for first_classification in search_patterns.keys(): + counter = 0 + for key in search_patterns[first_classification].keys(): + if checkSearchPattern(parsed_word_list, search_patterns[first_classification][key]): + return {'categorization':f"{first_classification}",'key':f"{key}",'pattern':search_patterns[first_classification][key], 'wordlist':parsed_word_list} + return {'categorization':"noise"} + + +def sort_specialcharacter_wordlist(inputlist): + weight_values = { + '@': 5, + '*': 4, + '#': 3, + '$': 2 + } + + weightlist = [] + for word in inputlist: + if word.endswith("@"): + weightlist.append(5) + elif word.endswith("*"): + weightlist.append(4) + elif word.endswith("#"): + weightlist.append(4) + elif word.endswith("$"): + weightlist.append(4) + + +def firstclassification_rawstring(rawstring): + specialcharacter_wordlist = extract_specialcharacter_words(rawstring,['@','*','$','#',':']) + first_classification = find_first_classification(specialcharacter_wordlist, search_patterns) + return first_classification + + +def checkSearchPattern(parsed_list, searchpattern): + if len(parsed_list)!=len(searchpattern): + return False + else: + for idx,val in enumerate(parsed_list): + if not parsed_list[idx].endswith(searchpattern[idx]): + return False + return True + + +def extractAmount_rule(text): + base_units = {'thousand': 10 ** 3, 'million': 10 ** 6, 'billion': 10 ** 9, 'trillion': 10 ** 12} + textList = text.split(' ') + counter = 0 + value = None + for idx, word in enumerate(textList): + print(word) + try: + result = float(word) + if textList[idx + 1] in base_units: + value = result * base_units[textList[idx + 1]] + counter = counter + 1 + else: + value = result + counter = counter + 1 + except: + for unit in base_units: + result = word.split(unit) + print(result) + if len(result) == 2 and result[1] == '' and result[0] != '': + try: + value = float(result[0]) * base_units[unit] + counter = counter + 1 + except: + continue + + if counter == 1: + return value else: return None +def extractAmount_rule_new(text): + base_units = {'thousand': 10 ** 3, 'k': 10 ** 3, 'million': 10 ** 6, 'm': 10 ** 6, 'billion': 10 ** 9, 'b': 10 ** 9, 'trillion': 10 ** 12, 'lakh':10 ** 5, 'crore':10 ** 7, 'quadrillion':10 ** 15} + amount_tuple = re.findall(r'\b([.\d]+)\s*(thousand|million|billion|trillion|m|b|t|k|lakh|crore|quadrillion)*\b', text) + if len(amount_tuple) > 1 or len(amount_tuple) == 0: + return False + else: + amount_tuple_list = list(amount_tuple[0]) + extracted_amount = float(amount_tuple_list[0]) + extracted_base_unit = amount_tuple_list[1] + if extracted_base_unit in base_units.keys(): + extracted_amount = float(extracted_amount) * base_units[extracted_base_unit] + return extracted_amount -def extractTriggerCondition(text): +def extractAmount_rule_new1(text, split_word=None, split_direction=None): + base_units = {'thousand': 10 ** 3, 'k': 10 ** 3, 'million': 10 ** 6, 'm': 10 ** 6, 'billion': 10 ** 9, 'b': 10 ** 9, 'trillion': 10 ** 12, 'lakh':10 ** 5, 'crore':10 ** 7, 'quadrillion':10 ** 15} + if split_word and split_direction: + if split_direction=='pre': + text = text.split(split_word)[0] + if split_direction=='post': + text = text.split(split_word)[1] + + # appending dummy because the regex does not recognize a number at the start of a string + text = f"dummy {text}" + text = text.replace("'", "") + text = text.replace('"', '') + amount_tuple = re.findall(r'\b\s([.\d]+)\s*(thousand|million|billion|trillion|m|b|t|k|lakh|crore|quadrillion)*\b', text) + if len(amount_tuple) > 1 or len(amount_tuple) == 0: + return False + else: + amount_tuple_list = list(amount_tuple[0]) + extracted_amount = float(amount_tuple_list[0]) + extracted_base_unit = amount_tuple_list[1] + if extracted_base_unit in base_units.keys(): + extracted_amount = float(extracted_amount) * base_units[extracted_base_unit] + return extracted_amount + + +def extract_userchoice(text): + result = re.split('userchoice:\s*', text) + if len(result) != 1 and result[1] != '': + return result[1].strip().strip('"').strip("'") + else: + return False + + +def findWholeWord(w): + return re.compile(r'\b({0})\b'.format(w), flags=re.IGNORECASE).search + + +def check_flo_address(floaddress, is_testnet): + if pybtc.is_address_valid(floaddress, testnet=is_testnet): + return floaddress + else: + return False + + +def extract_trigger_condition(text): searchResult = re.search('\".*\"', text) if searchResult is None: searchResult = re.search('\'.*\'', text) - return searchResult - return searchResult + + if searchResult is not None: + return searchResult.group().strip()[1:-1] + else: + return False -# Combine test -def parse_flodata(string, blockinfo, netvariable): +# Regex pattern for Smart Contract and Token name ^[A-Za-z][A-Za-z0-9_-]*[A-Za-z0-9]$ +def check_regex(pattern, test_string): + matched = re.match(pattern, test_string) + is_match = bool(matched) + return is_match - print("Break point at the first line of parsing function") - # todo Rule 20 - remove 'text:' from the start of flodata if it exists - if string[0:5] == 'text:': - string = string.split('text:')[1] - # todo Rule 21 - Collapse multiple spaces into a single space in the whole of flodata - # todo Rule 22 - convert flodata to lowercase to make the system case insensitive - nospacestring = re.sub('\t', ' ', string) - nospacestring = re.sub('\n', ' ', nospacestring) - nospacestring = re.sub(' +', ' ', nospacestring) - cleanstring = nospacestring.lower() - #cleanstring_noconditions = cleanstring.split('contract-conditions:')[0] - cleanstring_split = re.compile("contract-conditions*[' ']:").split(cleanstring) +def check_existence_of_keyword(inputlist, keywordlist): + for word in keywordlist: + if not word in inputlist: + return False + return True - # todo Rule 23 - Count number of words ending with @ and # - atList = [] - hashList = [] - starList = [] - for word in cleanstring_split[0].split(' '): - if word.endswith('*') and len(word) != 1: - starList.append(word) - if word.endswith('@') and len(word) != 1: - atList.append(word) +send_category = ['transfer', 'send', 'give'] # keep everything lowercase +create_category = ['incorporate', 'create', 'start'] # keep everything lowercase +deposit_category = ['submit','deposit'] - if len(starList) != 1 or starList[0] not in ['one-time-event*', 'continuous-event*']: - parsed_data = {'type': 'noise'} - else: - if starList == 'one-time-event*': - for word in cleanstring_split[0].split(' '): - if word.endswith('#') and len(word) != 1: - hashList.append(word) - elif starList == 'continuous-event*': - for word in cleanstring_split[1].split(' '): - if word.endswith('#') and len(word) != 1: - hashList.append(word) + +def truefalse_rule2(rawstring, permitted_list, denied_list): + # Find transfer , send , give + foundPermitted = None + foundDenied = None + + for word in permitted_list: + if findWholeWord(word)(rawstring): + foundPermitted = word + break + + for word in denied_list: + if findWholeWord(word)(rawstring): + foundDenied = word + break + if (foundPermitted is not None) and (foundDenied is None): + return True + else: + return False - '''for word in cleanstring_noconditions.split(' '): - if word.endswith('@') and len(word) != 1: - atList.append(word) - if word.endswith('#') and len(word) != 1: - hashList.append(word) - if word.endswith('*') and len(word) != 1: - starList.append(word) - ''' - print('') - # todo Rule 24 - Reject the following conditions - a. number of # & number of @ is equal to 0 then reject - # todo Rule 25 - If number of # or number of @ is greater than 1, reject - # todo Rule 25.a - If a transaction is rejected, it means parsed_data type is noise - # Filter noise first - check if the words end with either @ or # - if (len(atList) == 0 and len(hashList) == 0) or len(atList) > 1 or len(hashList) > 2 or len(starList) > 1: - parsed_data = {'type': 'noise'} +def selectCategory(rawstring, category1, category2): + foundCategory1 = None + foundCategory2 = None - # todo Rule 26 - if number of # is 1 and numbner of @ is 0 and number of * is 0, then check if its token creation or token transfer transaction - elif len(hashList) == 1 and len(atList) == 0 and len(starList) == 0: - # Passing the above check means token creation or transfer - incorporation = isIncorp(cleanstring) - transfer = isTransfer(cleanstring) + for word in category1: + if findWholeWord(word)(rawstring): + foundCategory1 = word + break - # todo Rule 27 - if (neither token incorporation a - if (not incorporation and not transfer) or (incorporation and transfer): - parsed_data = {'type': 'noise'} + for word in category2: + if findWholeWord(word)(rawstring): + foundCategory2 = word + break + + if ((foundCategory1 is not None) and (foundCategory2 is not None)) or ((foundCategory1 is None) and (foundCategory2 is None)): + return False + elif foundCategory1 is not None: + return 'category1' + elif foundCategory2 is not None: + return 'category2' - # todo Rule 28 - if token creation and not token transfer then it is confirmed that is it a token creation transaction - # todo Rule 29 - Extract total number of tokens issued, if its not mentioned then reject - elif incorporation and not transfer: - initTokens = extractInitTokens(cleanstring) - if initTokens is not None: - parsed_data = {'type': 'tokenIncorporation', 'flodata': string, 'tokenIdentification': hashList[0][:-1], - 'tokenAmount': initTokens} - else: - parsed_data = {'type': 'noise'} - # todo Rule 30 - if not token creation and is token transfer then then process it for token transfer rules - # todo Rule 31 - Extract number of tokens to be sent and the address to which to be sent, both data is mandatory - elif not incorporation and transfer: - amount = extractAmount(cleanstring, hashList[0][:-1]) - if None not in [amount]: - parsed_data = {'type': 'transfer', 'transferType': 'token', 'flodata': string, - 'tokenIdentification': hashList[0][:-1], - 'tokenAmount': amount} - else: - parsed_data = {'type': 'noise'} +def select_category_reject(rawstring, category1, category2, reject_list): + foundCategory1 = None + foundCategory2 = None + rejectCategory = None - # todo Rule 32 - if number of @ is 1, then process for smart contract transfer or creation or trigger - elif len(atList) == 1: - # Passing the above check means Smart Contract creation or transfer - incorporation = isIncorp(cleanstring) - transfer = isTransfer(cleanstring) - deposit = isDeposit(cleanstring) - # todo Rule 33 - if a confusing smart contract command is given, like creating and sending at the same time, or no - if incorporation and transfer: - parsed_data = {'type': 'noise'} + for word in category1: + if findWholeWord(word)(rawstring): + foundCategory1 = word + break - # todo Rule 34 - if incorporation and not transfer, then extract type of contract, address of the contract and conditions of the contract. Reject if any of those is not present - elif incorporation and not transfer: - contracttype = extractContractType(cleanstring) - contractaddress = extractAddress(nospacestring) - - if contracttype == 'one-time-event*' and len(hashList) == 1: - contractconditions = extractContractConditions(cleanstring, contracttype, blocktime=blockinfo['time'], marker=hashList[0][:-1]) - elif contracttype == 'continuous-event*': - contractconditions = extractContractConditions(cleanstring, contracttype, blocktime=blockinfo['time']) - else: - parsed_data = {'type': 'noise'} - return parsed_data + for word in category2: + if findWholeWord(word)(rawstring): + foundCategory2 = word + break - if config['DEFAULT']['NET'] == 'mainnet' and blockinfo['height'] < 3454510: - if None not in [contracttype, contractconditions]: - parsed_data = {'type': 'smartContractIncorporation', 'contractType': contracttype[:-1], - 'tokenIdentification': hashList[0][:-1], 'contractName': atList[0][:-1], - 'contractAddress': contractaddress[:-1], 'flodata': string, - 'contractConditions': contractconditions} - else: - parsed_data = {'type': 'noise'} - else: - if None not in [contracttype, contractaddress, contractconditions] and contracttype[:-1] == 'one-time-event': - parsed_data = {'type': 'smartContractIncorporation', 'contractType': contracttype[:-1], - 'tokenIdentification': hashList[0][:-1], 'contractName': atList[0][:-1], - 'contractAddress': contractaddress[:-1], 'flodata': string, - 'contractConditions': contractconditions} - elif None not in [contracttype, contractaddress, contractconditions] and contracttype[:-1] == 'continuous-event': - if contractconditions['subtype'] == 'tokenswap' and ('priceType' not in contractconditions): - parsed_data = {'type': 'noise'} - return parsed_data - else: - parsed_data = {'type': 'smartContractIncorporation', 'contractType': contracttype[:-1], 'contractName': atList[0][:-1], - 'contractAddress': contractaddress[:-1], 'flodata': string, - 'contractConditions': contractconditions} - else: - parsed_data = {'type': 'noise'} + for word in reject_list: + if findWholeWord(word)(rawstring): + rejectCategory = word + break - # todo Rule 35 - if it is not incorporation and it is transfer, then extract smart contract amount to be locked and userPreference. If any of them is missing, then reject - elif not incorporation and transfer: - # todo - Temporary check for transaction 22196cc761043b96a06fcfe5e58af2dafb90c7d222dcb909b537f7ee6715f0bd on testnet , figure out an elegant way of doing this - if len(hashList) == 0: - parsed_data = {'type': 'noise'} - return parsed_data + + if ((foundCategory1 is not None) and (foundCategory2 is not None)) or ((foundCategory1 is None) and (foundCategory2 is None)) or (rejectCategory is not None): + return False + elif foundCategory1 is not None: + return 'category1' + elif foundCategory2 is not None: + return 'category2' + - # We are at the send/transfer of smart contract - amount = extractAmount(cleanstring, hashList[0][:-1]) - userChoice = extractUserchoice(cleanstring) - contractaddress = extractAddress(nospacestring) - if None not in [amount, userChoice]: - parsed_data = {'type': 'transfer', 'transferType': 'smartContract', 'flodata': string, - 'tokenIdentification': hashList[0][:-1], - 'operation': 'transfer', 'tokenAmount': amount, 'contractName': atList[0][:-1], - 'userChoice': userChoice} - if contractaddress: - parsed_data['contractAddress'] = contractaddress[:-1] - else: - parsed_data = {'type': 'noise'} +def text_preprocessing(original_text): + # strip white spaces at the beginning and end + processed_text = original_text.strip() + # remove tab spaces + processed_text = re.sub('\t', ' ', processed_text) + # remove new lines/line changes + processed_text = re.sub('\n', ' ', processed_text) + # add a white space after every special character found + processed_text = re.sub("contract-conditions:", "contract-conditions: ", processed_text) + processed_text = re.sub("deposit-conditions:", "deposit-conditions: ", processed_text) + processed_text = re.sub("userchoice:", "userchoice: ", processed_text) + # remove extra whitespaces in between + processed_text = ' '.join(processed_text.split()) + processed_text = re.sub(' +', ' ', processed_text) + clean_text = processed_text + # make everything lowercase + processed_text = processed_text.lower() - elif deposit: - # Figure out amount of token to be submitted - for word in cleanstring_split[0].split(' '): - if word.endswith('#') and len(word) != 1: - hashList.append(word) - depositAmount = extractSubmitAmount(cleanstring_split[0], hashList[0][:-1]) - # '' name of token = hashList[0] - # '' name of Smart Contract = atList[0] - # '' FLO address of the Smart Contract - # '' Submit conditions - deposit_conditions = extractDepositConditions(cleanstring, blocktime=blockinfo['time']) - if None not in [deposit_conditions]: - parsed_data = {'type': 'smartContractDeposit', - 'tokenIdentification': hashList[0][:-1], 'depositAmount': depositAmount, 'contractName': atList[0][:-1], 'flodata': string, - 'depositConditions': deposit_conditions} - else: - parsed_data = {'type': 'noise'} + return clean_text,processed_text - # If flodata doesn't indicate incorporation nor transfer, check if its a committee trigger - elif (not incorporation and not transfer): - # Passing the above check means Smart Contract pays | exitcondition triggered from the committee - # todo Rule 37 - Extract the trigger condition given by the committee. If its missing, reject - triggerCondition = extractTriggerCondition(cleanstring) - if triggerCondition is not None: - parsed_data = {'type': 'smartContractPays', 'contractName': atList[0][:-1], 'triggerCondition': triggerCondition.group().strip()[1:-1]} - else: - parsed_data = {'type': 'noise'} + +text_list = [ + "create 500 million rmt#", + + "transfer 200 rmt#", + + "Create Smart Contract with the name India-elections-2019@ of the type one-time-event* using the asset rmt# at the address F7osBpjDDV1mSSnMNrLudEQQ3cwDJ2dPR1$ with contract-conditions: (1) contractAmount=0.001rmt (2) userChoices=Narendra Modi wins| Narendra Modi loses (3) expiryTime= Wed May 22 2019 21:00:00 GMT+0530", + + "send 0.001 rmt# to india-elections-2019@ to FLO address F7osBpjDDV1mSSnMNrLudEQQ3cwDJ2dPR1 with the userchoice:'narendra modi wins'", + + "india-elections-2019@ winning-choice:'narendra modi wins'", + + "Create Smart Contract with the name India-elections-2019@ of the type one-time-event* using the asset rmt# at the address F7osBpjDDV1mSSnMNrLudEQQ3cwDJ2dPR1$ with contract-conditions: (1) contractAmount=0.001rmt (2) expiryTime= Wed May 22 2019 21:00:00 GMT+0530", + + "send 0.001 rmt# to india-elections-2019@ to FLO address F7osBpjDDV1mSSnMNrLudEQQ3cwDJ2dPR1", + + "Create Smart Contract with the name swap-rupee-bioscope@ of the type continuous-event* at the address oRRCHWouTpMSPuL6yZRwFCuh87ZhuHoL78$ with contract-conditions : (1) subtype = tokenswap (2) accepting_token = rupee# (3) selling_token = bioscope# (4) price = '15' (5) priceType = ‘predetermined’ (6) direction = oneway", + + "Deposit 15 bioscope# to swap-rupee-bioscope@ its FLO address being oRRCHWouTpMSPuL6yZRwFCuh87ZhuHoL78$ with deposit-conditions: (1) expiryTime= Wed Nov 17 2021 21:00:00 GMT+0530 ", + + "Send 15 rupee# to swap-rupee-article@ its FLO address being FJXw6QGVVaZVvqpyF422Aj4FWQ6jm8p2dL$", + + "send 0.001 rmt# to india-elections-2019@ to FLO address F7osBpjDDV1mSSnMNrLudEQQ3cwDJ2dPR1 with the userchoice:'narendra modi wins'" +] + +text_list1 = [ + '''Create Smart Contract with the name India-elections-2019@ of the type one-time-event* using the asset rmt# at the address F7osBpjDDV1mSSnMNrLudEQQ3cwDJ2dPR1$ with contract-conditions: (1) contractAmount=0.001rmt (2) userChoices=Narendra Modi wins| Narendra Modi loses (3) expiryTime= Wed May 22 2019 21:00:00 GMT+0530''' +] + +def parse_flodata(text, blockinfo, config['DEFAULT']['NET']): + if config['DEFAULT']['NET'] == 'testnet': + is_testnet = True + else: + is_testnet = False + + clean_text, processed_text = text_preprocessing(text) + first_classification = firstclassification_rawstring(processed_text) + parsed_data = None + + if first_classification['categorization'] == 'tokensystem-C': + # Resolving conflict for 'tokensystem-C' + tokenname = first_classification['wordlist'][0][:-1] + if not check_regex("^[A-Za-z][A-Za-z0-9_-]*[A-Za-z0-9]$", tokenname): + return outputreturn('noise') + + tokenamount = apply_rule1(extractAmount_rule_new, processed_text) + if not tokenamount: + return outputreturn('noise') + + operation = apply_rule1(selectCategory, processed_text, send_category, create_category) + if operation == 'category1' and tokenamount is not None: + return outputreturn('token_transfer',f"{processed_text}", f"{tokenname}", tokenamount) + elif operation == 'category2' and tokenamount is not None: + return outputreturn('token_incorporation',f"{processed_text}", f"{first_classification['wordlist'][0][:-1]}", tokenamount) else: - parsed_data = {'type': 'noise'} + return outputreturn('noise') - return parsed_data \ No newline at end of file + if first_classification['categorization'] == 'smart-contract-creation-C': + # Resolving conflict for 'smart-contract-creation-C' + operation = apply_rule1(selectCategory, processed_text, create_category, send_category+deposit_category) + if not operation: + return outputreturn('noise') + + contract_type = extract_special_character_word(first_classification['wordlist'],'*') + if not check_existence_of_keyword(['one-time-event'],[contract_type]): + return outputreturn('noise') + + contract_name = extract_special_character_word(first_classification['wordlist'],'@') + if not check_regex("^[A-Za-z][A-Za-z0-9_-]*[A-Za-z0-9]$", contract_name): + return outputreturn('noise') + + contract_token = extract_special_character_word(first_classification['wordlist'],'#') + if not check_regex("^[A-Za-z][A-Za-z0-9_-]*[A-Za-z0-9]$", contract_token): + return outputreturn('noise') + + contract_address = extract_special_character_word(first_classification['wordlist'],'$') + contract_address = find_original_case(contract_address, clean_text) + if not check_flo_address(contract_address): + return outputreturn('noise') + + contract_conditions = extract_contract_conditions(processed_text, contract_type, contract_token, blocktime=blockinfo['time']) + if not resolve_incategory_conflict(contract_conditions,[['userchoices','payeeAddress']]) or contract_conditions == False: + return outputreturn('noise') + else: + minimum_subscription_amount = '' + if 'minimumsubscriptionamount' in contract_conditions.keys(): + minimum_subscription_amount = contract_conditions['minimumsubscriptionamount'] + try: + float(minimum_subscription_amount) + except: + return outputreturn('noise') + maximum_subscription_amount = '' + if 'maximumsubscriptionamount' in contract_conditions.keys(): + maximum_subscription_amount = contract_conditions['maximumsubscriptionamount'] + try: + float(maximum_subscription_amount) + except: + return outputreturn('noise') + + if 'userchoices' in contract_conditions.keys(): + return outputreturn('one-time-event-userchoice-smartcontract-incorporation',f"{contract_token}", f"{contract_name}", f"{contract_address}", f"{clean_text}", f"{contract_conditions['contractAmount']}", f"{minimum_subscription_amount}" , f"{maximum_subscription_amount}", f"{contract_conditions['userchoices']}", f"{contract_conditions['expiryTime']}") + elif 'payeeAddress' in contract_conditions.keys(): + contract_conditions['payeeAddress'] = find_word_index_fromstring(clean_text,contract_conditions['payeeAddress']) + if not check_flo_address(contract_conditions['payeeAddress']): + return outputreturn('noise') + else: + return outputreturn('one-time-event-time-smartcontract-incorporation',f"{contract_token}", f"{contract_name}", f"{contract_address}", f"{clean_text}", f"{contract_conditions['contractAmount']}", f"{minimum_subscription_amount}" , f"{maximum_subscription_amount}", f"{contract_conditions['payeeAddress']}", f"{contract_conditions['expiryTime']}") + + if first_classification['categorization'] == 'smart-contract-participation-deposit-C': + # either participation of one-time-event contract or + operation = apply_rule1(select_category_reject, processed_text, send_category, deposit_category, create_category) + if not operation: + return outputreturn('noise') + else: + tokenname = first_classification['wordlist'][0][:-1] + if not check_regex("^[A-Za-z][A-Za-z0-9_-]*[A-Za-z0-9]$", tokenname): + return outputreturn('noise') + + contract_name = extract_special_character_word(first_classification['wordlist'],'@') + if not check_regex("^[A-Za-z][A-Za-z0-9_-]*[A-Za-z0-9]$", contract_name): + return outputreturn('noise') + + contract_address = extract_special_character_word(first_classification['wordlist'],'$') + if contract_address is False: + contract_address = '' + else: + contract_address = find_original_case(contract_address, clean_text) + if not check_flo_address(contract_address): + return outputreturn('noise') + + if operation == 'category1': + tokenamount = apply_rule1(extractAmount_rule_new1, processed_text, 'userchoice:', 'pre') + if not tokenamount: + return outputreturn('noise') + userchoice = extract_userchoice(processed_text) + # todo - do we need more validations for user choice? + if not userchoice: + return outputreturn('noise') + + return outputreturn('one-time-event-userchoice-smartcontract-participation',f"{clean_text}", f"{tokenname}", tokenamount, f"{contract_name}", f"{contract_address}", f"{userchoice}") + + elif operation == 'category2': + tokenamount = apply_rule1(extractAmount_rule_new1, processed_text, 'deposit-conditions:', 'pre') + if not tokenamount: + return outputreturn('noise') + deposit_conditions = extract_deposit_conditions(processed_text, blocktime=blockinfo['time']) + if not deposit_category: + return outputreturn("noise") + return outputreturn('continuos-event-token-swap-deposit', f"{tokenname}", tokenamount, f"{contract_name}", f"{clean_text}", f"{deposit_conditions['expiryTime']}") + + if first_classification['categorization'] == 'smart-contract-participation-ote-ce-C': + # There is no way to properly differentiate between one-time-event-time-trigger participation and token swap participation + # so we merge them in output return + + tokenname = first_classification['wordlist'][0][:-1] + if not check_regex("^[A-Za-z][A-Za-z0-9_-]*[A-Za-z0-9]$", tokenname): + return outputreturn('noise') + + tokenamount = apply_rule1(extractAmount_rule_new1, processed_text) + if not tokenamount: + return outputreturn('noise') + + contract_name = extract_special_character_word(first_classification['wordlist'],'@') + if not check_regex("^[A-Za-z][A-Za-z0-9_-]*[A-Za-z0-9]$", contract_name): + return outputreturn('noise') + + contract_address = extract_special_character_word(first_classification['wordlist'],'$') + if contract_address is False: + contract_address = '' + else: + contract_address = find_original_case(contract_address, clean_text) + if not check_flo_address(contract_address): + return outputreturn('noise') + + return outputreturn('smart-contract-one-time-event-continuos-event-participation', f"{clean_text}", f"{tokenname}", tokenamount, f"{contract_name}", f"{contract_address}") + + if first_classification['categorization'] == 'userchoice-trigger': + contract_name = extract_special_character_word(first_classification['wordlist'],'@') + if not check_regex("^[A-Za-z][A-Za-z0-9_-]*[A-Za-z0-9]$", contract_name): + return outputreturn('noise') + + trigger_condition = extract_trigger_condition(processed_text) + if not trigger_condition: + return outputreturn('noise') + return outputreturn('one-time-event-userchoice-smartcontract-trigger', f"{contract_name}", f"{trigger_condition}") + + if first_classification['categorization'] == 'smart-contract-creation-ce-tokenswap': + operation = apply_rule1(selectCategory, processed_text, create_category, send_category+deposit_category) + if operation != 'category1': + return outputreturn('noise') + + contract_type = extract_special_character_word(first_classification['wordlist'],'*') + if not check_existence_of_keyword(['continuous-event'],[contract_type]): + return outputreturn('noise') + + contract_name = extract_special_character_word(first_classification['wordlist'],'@') + if not check_regex("^[A-Za-z][A-Za-z0-9_-]*[A-Za-z0-9]$", contract_name): + return outputreturn('noise') + + contract_token = extract_special_character_word(first_classification['wordlist'],'#') + if not check_regex("^[A-Za-z][A-Za-z0-9_-]*[A-Za-z0-9]$", contract_token): + return outputreturn('noise') + + contract_address = extract_special_character_word(first_classification['wordlist'],'$') + contract_address = find_original_case(contract_address, clean_text) + if not check_flo_address(contract_address): + return outputreturn('noise') + + contract_conditions = extract_contract_conditions(processed_text, contract_type, contract_token, blocktime=blockinfo['time']) + if contract_conditions == False: + return outputreturn('noise') + # todo - Add checks for token swap extract contract conditions + try: + assert contract_conditions['subtype'] == 'tokenswap' + assert check_regex("^[A-Za-z][A-Za-z0-9_-]*[A-Za-z0-9]$", contract_conditions['accepting_token']) + assert check_regex("^[A-Za-z][A-Za-z0-9_-]*[A-Za-z0-9]$", contract_conditions['accepting_token']) + assert contract_conditions['priceType']=="'predetermined'" or contract_conditions['priceType']=='"predetermined"' or contract_conditions['priceType']=="predetermined" or check_flo_address(find_original_case(contract_conditions['priceType'], clean_text)) + assert float(contract_conditions['price']) + except AssertionError: + return outputreturn('noise') + return outputreturn('continuos-event-token-swap-incorporation', f"{contract_token}", f"{contract_name}", f"{contract_address}", f"{clean_text}", f"{contract_conditions['subtype']}", f"{contract_conditions['accepting_token']}", f"{contract_conditions['selling_token']}", f"{contract_conditions['priceType']}", f"{contract_conditions['price']}") + + return outputreturn('noise') + + +for text in text_list1: + print(parse_flodata(text)) \ No newline at end of file diff --git a/tracktokens_smartcontracts.py b/tracktokens_smartcontracts.py new file mode 100755 index 0000000..683fcb7 --- /dev/null +++ b/tracktokens_smartcontracts.py @@ -0,0 +1,2758 @@ +import argparse +import configparser +import json +import logging +import os +import shutil +import sqlite3 +import sys +import pybtc +import requests +import socketio +from sqlalchemy import create_engine, func +from sqlalchemy.orm import sessionmaker +import time +import parsing +from config import * +from datetime import datetime +import pdb +from models import SystemData, ActiveTable, ConsumedTable, TransferLogs, TransactionHistory, RejectedTransactionHistory, Base, ContractStructure, ContractBase, ContractParticipants, SystemBase, ActiveContracts, ContractAddressMapping, LatestCacheBase, ContractTransactionHistory, RejectedContractTransactionHistory, TokenContractAssociation, ContinuosContractBase, ContractStructure1, ContractParticipants1, ContractDeposits1, ContractTransactionHistory1 + + +goodblockset = {} +goodtxset = {} + + +def newMultiRequest(apicall): + current_server = serverlist[0] + while True: + try: + response = requests.get('{}api/{}'.format(current_server, apicall)) + except: + current_server = switchNeturl(current_server) + logger.info(f"newMultiRequest() switched to {current_server}") + time.sleep(2) + else: + if response.status_code == 200: + return json.loads(response.content) + else: + current_server = switchNeturl(current_server) + logger.info(f"newMultiRequest() switched to {current_server}") + time.sleep(2) + + +def pushData_SSEapi(message): + signature = pybtc.sign_message(message.encode(), privKey) + headers = {'Accept': 'application/json', 'Content-Type': 'application/json', 'Signature': signature} + + '''try: + r = requests.post(sseAPI_url, json={'message': '{}'.format(message)}, headers=headers) + except: + logger.error("couldn't push the following message to SSE api {}".format(message))''' + print('') + + +def processBlock(blockindex=None, blockhash=None): + if blockindex is not None and blockhash is None: + logger.info(f'Processing block {blockindex}') + # Get block details + response = newMultiRequest(f"block-index/{blockindex}") + blockhash = response['blockHash'] + + blockinfo = newMultiRequest(f"block/{blockhash}") + + # todo Rule 8 - read every transaction from every block to find and parse flodata + counter = 0 + acceptedTxList = [] + # Scan every transaction + logger.info("Before tx loop") + counter = 0 + for transaction in blockinfo["tx"]: + counter = counter + 1 + logger.info(f"Transaction {counter} {transaction}") + + current_index = -1 + while(current_index == -1): + transaction_data = newMultiRequest(f"tx/{transaction}") + try: + text = transaction_data["floData"] + text = text.replace("\n", " \n ") + current_index = 2 + except: + logger.info("The API has passed the Block height test but failed transaction_data['floData'] test") + logger.info(f"Block Height : {blockinfo['height']}") + logger.info(f"Transaction {transaction} data : ") + logger.info(transaction_data) + logger.info('Program will wait for 1 seconds and try to reconnect') + time.sleep(1) + + # todo Rule 9 - Reject all noise transactions. Further rules are in parsing.py + returnval = None + #parsed_data = parsing.parse_flodata(text, blockinfo, config['DEFAULT']['NET']) + parsed_data = parsing.parse_flodata(text, blockinfo, config['DEFAULT']['NET']) + if parsed_data['type'] != 'noise': + logger.info(f"Processing transaction {transaction}") + logger.info(f"flodata {text} is parsed to {parsed_data}") + returnval = processTransaction(transaction_data, parsed_data) + + if returnval == 1: + acceptedTxList.append(transaction) + elif returnval == 0: + logger.info("Transfer for the transaction %s is illegitimate. Moving on" % transaction) + + if len(acceptedTxList) > 0: + tempinfo = blockinfo['tx'].copy() + for tx in blockinfo['tx']: + if tx not in acceptedTxList: + tempinfo.remove(tx) + blockinfo['tx'] = tempinfo + updateLatestBlock(blockinfo) + + engine = create_engine('sqlite:///system.db') + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + entry = session.query(SystemData).filter(SystemData.attribute == 'lastblockscanned').all()[0] + entry.value = str(blockinfo['height']) + session.commit() + session.close() + + # Check smartContracts which will be triggered locally, and not by the contract committee + checkLocaltriggerContracts(blockinfo) + # Check if any deposits have to be returned + checkReturnDeposits(blockinfo) + + +def updateLatestTransaction(transactionData, parsed_data): + # connect to latest transaction db + conn = sqlite3.connect('latestCache.db') + conn.execute( + "INSERT INTO latestTransactions(transactionHash, blockNumber, jsonData, transactionType, parsedFloData) VALUES (?,?,?,?,?)", + (transactionData['txid'], transactionData['blockheight'], json.dumps(transactionData), parsed_data['type'], + json.dumps(parsed_data))) + conn.commit() + conn.close() + + +def updateLatestBlock(blockData): + # connect to latest block db + conn = sqlite3.connect('latestCache.db') + conn.execute('INSERT INTO latestBlocks(blockNumber, blockHash, jsonData) VALUES (?,?,?)', + (blockData['height'], blockData['hash'], json.dumps(blockData))) + conn.commit() + conn.close() + + +def transferToken(tokenIdentification, tokenAmount, inputAddress, outputAddress, transaction_data=None, parsed_data=None): + engine = create_engine('sqlite:///tokens/{}.db'.format(tokenIdentification), echo=True) + Base.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + availableTokens = session.query(func.sum(ActiveTable.transferBalance)).filter_by(address=inputAddress).all()[0][0] + commentTransferAmount = float(tokenAmount) + if availableTokens is None: + logger.info(f"The sender address {inputAddress} doesn't own any {tokenIdentification.upper()} tokens") + session.close() + return 0 + + elif availableTokens < commentTransferAmount: + logger.info("The transfer amount passed in the comments is more than the user owns\nThis transaction will be discarded\n") + session.close() + return 0 + + elif availableTokens >= commentTransferAmount: + table = session.query(ActiveTable).filter(ActiveTable.address == inputAddress).all() + block_data = newMultiRequest('block/{}'.format(transaction_data['blockhash'])) + + pidlst = [] + checksum = 0 + for row in table: + if checksum >= commentTransferAmount: + break + pidlst.append([row.id, row.transferBalance]) + checksum = checksum + row.transferBalance + + if checksum == commentTransferAmount: + consumedpid_string = '' + + # Update all pids in pidlist's transferBalance to 0 + lastid = session.query(ActiveTable)[-1].id + for piditem in pidlst: + entry = session.query(ActiveTable).filter(ActiveTable.id == piditem[0]).all() + consumedpid_string = consumedpid_string + '{},'.format(piditem[0]) + session.add(TransferLogs(sourceFloAddress=inputAddress, destFloAddress=outputAddress, + transferAmount=entry[0].transferBalance, sourceId=piditem[0], + destinationId=lastid + 1, + blockNumber=block_data['height'], time=block_data['time'], + transactionHash=transaction_data['txid'])) + entry[0].transferBalance = 0 + + if len(consumedpid_string) > 1: + consumedpid_string = consumedpid_string[:-1] + + # Make new entry + session.add(ActiveTable(address=outputAddress, consumedpid=consumedpid_string, + transferBalance=commentTransferAmount)) + + # Migration + # shift pid of used utxos from active to consumed + for piditem in pidlst: + # move the parentids consumed to consumedpid column in both activeTable and consumedTable + entries = session.query(ActiveTable).filter(ActiveTable.parentid == piditem[0]).all() + for entry in entries: + entry.consumedpid = entry.consumedpid + ',{}'.format(piditem[0]) + entry.parentid = None + + entries = session.query(ConsumedTable).filter(ConsumedTable.parentid == piditem[0]).all() + for entry in entries: + entry.consumedpid = entry.consumedpid + ',{}'.format(piditem[0]) + entry.parentid = None + + # move the pids consumed in the transaction to consumedTable and delete them from activeTable + session.execute( + 'INSERT INTO consumedTable (id, address, parentid, consumedpid, transferBalance) SELECT id, address, parentid, consumedpid, transferBalance FROM activeTable WHERE id={}'.format( + piditem[0])) + session.execute('DELETE FROM activeTable WHERE id={}'.format(piditem[0])) + session.commit() + session.commit() + + elif checksum > commentTransferAmount: + consumedpid_string = '' + # Update all pids in pidlist's transferBalance + lastid = session.query(ActiveTable)[-1].id + for idx, piditem in enumerate(pidlst): + entry = session.query(ActiveTable).filter(ActiveTable.id == piditem[0]).all() + if idx != len(pidlst) - 1: + session.add(TransferLogs(sourceFloAddress=inputAddress, destFloAddress=outputAddress, + transferAmount=entry[0].transferBalance, sourceId=piditem[0], + destinationId=lastid + 1, + blockNumber=block_data['height'], time=block_data['time'], + transactionHash=transaction_data['txid'])) + entry[0].transferBalance = 0 + consumedpid_string = consumedpid_string + '{},'.format(piditem[0]) + else: + session.add(TransferLogs(sourceFloAddress=inputAddress, destFloAddress=outputAddress, + transferAmount=piditem[1] - (checksum - commentTransferAmount), + sourceId=piditem[0], + destinationId=lastid + 1, + blockNumber=block_data['height'], time=block_data['time'], + transactionHash=transaction_data['txid'])) + entry[0].transferBalance = checksum - commentTransferAmount + + if len(consumedpid_string) > 1: + consumedpid_string = consumedpid_string[:-1] + + # Make new entry + session.add(ActiveTable(address=outputAddress, parentid=pidlst[-1][0], consumedpid=consumedpid_string, + transferBalance=commentTransferAmount)) + + # Migration + # shift pid of used utxos from active to consumed + for piditem in pidlst[:-1]: + # move the parentids consumed to consumedpid column in both activeTable and consumedTable + entries = session.query(ActiveTable).filter(ActiveTable.parentid == piditem[0]).all() + for entry in entries: + entry.consumedpid = entry.consumedpid + ',{}'.format(piditem[0]) + entry.parentid = None + + entries = session.query(ConsumedTable).filter(ConsumedTable.parentid == piditem[0]).all() + for entry in entries: + entry.consumedpid = entry.consumedpid + ',{}'.format(piditem[0]) + entry.parentid = None + + # move the pids consumed in the transaction to consumedTable and delete them from activeTable + session.execute( + 'INSERT INTO consumedTable (id, address, parentid, consumedpid, transferBalance) SELECT id, address, parentid, consumedpid, transferBalance FROM activeTable WHERE id={}'.format( + piditem[0])) + session.execute('DELETE FROM activeTable WHERE id={}'.format(piditem[0])) + session.commit() + session.commit() + + block_data = newMultiRequest('block/{}'.format(transaction_data['blockhash'])) + + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(TransactionHistory(sourceFloAddress=inputAddress, destFloAddress=outputAddress, + transferAmount=tokenAmount, blockNumber=block_data['height'], + blockHash=block_data['hash'], time=block_data['time'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, jsonData=json.dumps(transaction_data), + transactionType=parsed_data['type'], + parsedFloData=json.dumps(parsed_data))) + session.commit() + session.close() + return 1 + + +def checkLocaltriggerContracts(blockinfo): + engine = create_engine('sqlite:///system.db', echo=False) + connection = engine.connect() + # todo : filter activeContracts which only have local triggers + activeContracts = connection.execute( + 'select contractName, contractAddress from activecontracts where status=="active" ').fetchall() + connection.close() + + for contract in activeContracts: + + # pull out the contract structure into a dictionary + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(contract[0], contract[1]), echo=True) + connection = engine.connect() + # todo : filter activeContracts which only have local triggers + attributevaluepair = connection.execute( + "select attribute, value from contractstructure where attribute != 'contractName' and attribute != 'flodata' and attribute != 'contractAddress'").fetchall() + contractStructure = {} + conditionDict = {} + counter = 0 + for item in attributevaluepair: + if list(item)[0] == 'exitconditions': + conditionDict[counter] = list(item)[1] + counter = counter + 1 + else: + contractStructure[list(item)[0]] = list(item)[1] + if len(conditionDict) > 0: + contractStructure['exitconditions'] = conditionDict + del counter, conditionDict + + if contractStructure['contractType'] == 'one-time-event': + # Check if the contract has blockchain trigger or committee trigger + if 'exitconditions' in contractStructure: + # This is a committee trigger contract + expiryTime = contractStructure['expiryTime'] + expirytime_split = expiryTime.split(' ') + parse_string = '{}/{}/{} {}'.format(expirytime_split[3], parsing.months[expirytime_split[1]], + expirytime_split[2], expirytime_split[4]) + expirytime_object = parsing.arrow.get(parse_string, 'YYYY/M/D HH:mm:ss').replace( + tzinfo=expirytime_split[5][3:]) + blocktime_object = parsing.arrow.get(blockinfo['time']).to('Asia/Kolkata') + + if blocktime_object > expirytime_object: + if 'minimumsubscriptionamount' in contractStructure: + minimumsubscriptionamount = contractStructure['minimumsubscriptionamount'] + tokenAmount_sum = \ + connection.execute('select sum(tokenAmount) from contractparticipants').fetchall()[0][0] + if tokenAmount_sum < minimumsubscriptionamount: + # Initialize payback to contract participants + contractParticipants = connection.execute( + 'select participantAddress, tokenAmount, transactionHash from contractparticipants').fetchall()[ + 0][0] + + for participant in contractParticipants: + tokenIdentification = contractStructure['tokenIdentification'] + contractAddress = connection.execute( + 'select * from contractstructure where attribute="contractAddress"').fetchall()[0][ + 0] + returnval = transferToken(tokenIdentification, participant[1], contractAddress, + participant[0]) + if returnval is None: + logger.critical( + "Something went wrong in the token transfer method while doing local Smart Contract Trigger. THIS IS CRITICAL ERROR") + return + connection.execute( + 'update contractparticipants set winningAmount="{}" where participantAddress="{}" and transactionHash="{}"'.format( + (participant[1], participant[0], participant[2]))) + + # add transaction to ContractTransactionHistory + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(contract[0], contract[1]), echo=True) + ContractBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(ContractTransactionHistory(transactionType='trigger', + transactionSubType='minimumsubscriptionamount-payback', + transferAmount=None, + blockNumber=blockinfo['height'], + blockHash=blockinfo['hash'], + time=blockinfo['time'])) + session.commit() + session.close() + + engine = create_engine('sqlite:///system.db', echo=True) + connection = engine.connect() + connection.execute( + 'update activecontracts set status="closed" where contractName="{}" and contractAddress="{}"'.format( + contract[0], contract[1])) + connection.execute( + 'update activecontracts set closeDate="{}" where contractName="{}" and contractAddress="{}"'.format( + blockinfo['time'], + contract[0], contract[1])) + connection.close() + + engine = create_engine('sqlite:///system.db', echo=True) + connection = engine.connect() + connection.execute( + 'update activecontracts set status="expired" where contractName="{}" and contractAddress="{}"'.format( + contract[0], contract[1])) + connection.execute( + 'update activecontracts set expirydate="{}" where contractName="{}" and contractAddress="{}"'.format( + blockinfo['time'], + contract[0], contract[1])) + connection.close() + + elif 'payeeAddress' in contractStructure: + # This is a local trigger contract + if 'maximumsubscriptionamount' in contractStructure: + maximumsubscriptionamount = connection.execute( + 'select value from contractstructure where attribute=="maximumsubscriptionamount"').fetchall()[ + 0][0] + tokenAmount_sum = \ + connection.execute('select sum(tokenAmount) from contractparticipants').fetchall()[0][0] + if tokenAmount_sum >= maximumsubscriptionamount: + # Trigger the contract + payeeAddress = contractStructure['payeeAddress'] + tokenIdentification = contractStructure['tokenIdentification'] + contractAddress = contractStructure['contractAddress'] + returnval = transferToken(tokenIdentification, tokenAmount_sum, contractAddress, payeeAddress) + if returnval is None: + logger.critical( + "Something went wrong in the token transfer method while doing local Smart Contract Trigger") + return + connection.execute( + 'update contractparticipants set winningAmount="{}"'.format( + (0))) + + # add transaction to ContractTransactionHistory + engine = create_engine( + 'sqlite:///smartContracts/{}-{}.db'.format(contract[0], + contract[1]), + echo=True) + ContractBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(ContractTransactionHistory(transactionType='trigger', + transactionSubType='maximumsubscriptionamount', + sourceFloAddress=contractAddress, + destFloAddress=payeeAddress, + transferAmount=tokenAmount_sum, + blockNumber=blockinfo['height'], + blockHash=blockinfo['hash'], + time=blockinfo['time'])) + session.commit() + session.close() + + engine = create_engine('sqlite:///system.db', echo=False) + connection = engine.connect() + connection.execute( + 'update activecontracts set status="closed" where contractName="{}" and contractAddress="{}"'.format( + contract[0], contract[1])) + connection.execute( + 'update activecontracts set closeDate="{}" where contractName="{}" and contractAddress="{}"'.format( + blockinfo['time'], contract[0], contract[1])) + connection.execute( + 'update activecontracts set expiryDate="{}" where contractName="{}" and contractAddress="{}"'.format( + blockinfo['time'], contract[0], contract[1])) + connection.close() + return + + expiryTime = contractStructure['expiryTime'] + expirytime_split = expiryTime.split(' ') + parse_string = '{}/{}/{} {}'.format(expirytime_split[3], parsing.months[expirytime_split[1]], + expirytime_split[2], expirytime_split[4]) + expirytime_object = parsing.arrow.get(parse_string, 'YYYY/M/D HH:mm:ss').replace( + tzinfo=expirytime_split[5][3:]) + blocktime_object = parsing.arrow.get(blockinfo['time']).to('Asia/Kolkata') + + if blocktime_object > expirytime_object: + if 'minimumsubscriptionamount' in contractStructure: + minimumsubscriptionamount = contractStructure['minimumsubscriptionamount'] + tokenAmount_sum = \ + connection.execute('select sum(tokenAmount) from contractparticipants').fetchall()[0][0] + if tokenAmount_sum < minimumsubscriptionamount: + # Initialize payback to contract participants + contractParticipants = connection.execute( + 'select participantAddress, tokenAmount, transactionHash from contractparticipants').fetchall()[ + 0][0] + + for participant in contractParticipants: + tokenIdentification = connection.execute( + 'select * from contractstructure where attribute="tokenIdentification"').fetchall()[ + 0][ + 0] + contractAddress = connection.execute( + 'select * from contractstructure where attribute="contractAddress"').fetchall()[0][ + 0] + returnval = transferToken(tokenIdentification, participant[1], contractAddress, + participant[0]) + if returnval is None: + logger.critical( + "Something went wrong in the token transfer method while doing local Smart Contract Trigger") + return + connection.execute( + 'update contractparticipants set winningAmount="{}" where participantAddress="{}" and transactionHash="{}"'.format( + (participant[1], participant[0], participant[2]))) + + # add transaction to ContractTransactionHistory + engine = create_engine( + 'sqlite:///smartContracts/{}-{}.db'.format(contract[0], + contract[1]), + echo=True) + ContractBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(ContractTransactionHistory(transactionType='trigger', + transactionSubType='minimumsubscriptionamount-payback', + transferAmount=None, + blockNumber=blockinfo['height'], + blockHash=blockinfo['hash'], + time=blockinfo['time'])) + session.commit() + session.close() + + engine = create_engine('sqlite:///system.db', echo=False) + connection = engine.connect() + connection.execute( + 'update activecontracts set status="closed" where contractName="{}" and contractAddress="{}"'.format( + contract[0], contract[1])) + connection.execute( + 'update activecontracts set closeDate="{}" where contractName="{}" and contractAddress="{}"'.format( + blockinfo['time'], contract[0], contract[1])) + connection.execute( + 'update activecontracts set expiryDate="{}" where contractName="{}" and contractAddress="{}"'.format( + blockinfo['time'], contract[0], contract[1])) + connection.close() + return + + # Trigger the contract + payeeAddress = contractStructure['payeeAddress'] + tokenIdentification = contractStructure['tokenIdentification'] + contractAddress = contractStructure['contractAddress'] + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(contract[0], contract[1]), + echo=True) + connection = engine.connect() + tokenAmount_sum = \ + connection.execute('select sum(tokenAmount) from contractparticipants').fetchall()[0][0] + returnval = transferToken(tokenIdentification, tokenAmount_sum, contractAddress, payeeAddress) + if returnval is None: + logger.critical( + "Something went wrong in the token transfer method while doing local Smart Contract Trigger") + return + connection.execute('update contractparticipants set winningAmount="{}"'.format(0)) + + # add transaction to ContractTransactionHistory + engine = create_engine( + 'sqlite:///smartContracts/{}-{}.db'.format(contract[0], + contract[1]), + echo=True) + ContractBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(ContractTransactionHistory(transactionType='trigger', + transactionSubType='expiryTime', + sourceFloAddress=contractAddress, + destFloAddress=payeeAddress, + transferAmount=tokenAmount_sum, + blockNumber=blockinfo['height'], + blockHash=blockinfo['hash'], + time=blockinfo['time'])) + session.commit() + session.close() + + engine = create_engine('sqlite:///system.db', echo=False) + connection = engine.connect() + connection.execute( + 'update activecontracts set status="closed" where contractName="{}" and contractAddress="{}"'.format( + contract[0], contract[1])) + connection.execute( + 'update activecontracts set closeDate="{}" where contractName="{}" and contractAddress="{}"'.format( + blockinfo['time'], contract[0], contract[1])) + connection.execute( + 'update activecontracts set expiryDate="{}" where contractName="{}" and contractAddress="{}"'.format( + blockinfo['time'], contract[0], contract[1])) + connection.close() + return + + +def checkReturnDeposits(blockinfo): + pass + + +def check_database_existance(type, parameters): + pass + + +def processTransaction(transaction_data, parsed_data): + # Do the necessary checks for the inputs and outputs + # todo Rule 38 - Here we are doing FLO processing. We attach asset amounts to a FLO address, so every FLO address + # will have multiple feed ins of the asset. Each of those feedins will be an input to the address. + # an address can also spend the asset. Each of those spends is an output of that address feeding the asset into some + # other address an as input + # Rule 38 reframe - For checking any asset transfer on the flo blockchain it is possible that some transactions may use more than one + # vins. However in any single transaction the system considers valid, they can be only one source address from which the flodata is + # originting. To ensure consistency, we will have to check that even if there are more than one vins in a transaction, there should be + # exactly one FLO address on the originating side and that FLO address should be the owner of the asset tokens being transferred + + + # Create vinlist and outputlist + vinlist = [] + querylist = [] + + # todo Rule 39 - Create a list of vins for a given transaction id + for obj in transaction_data["vin"]: + querylist.append([obj["txid"], obj["vout"]]) + + totalinputval = 0 + inputadd = '' + + # todo Rule 40 - For each vin, find the feeding address and the fed value. Make an inputlist containing [inputaddress, n value] + for query in querylist: + content = newMultiRequest('tx/{}'.format(str(query[0]))) + for objec in content["vout"]: + if objec["n"] == query[1]: + inputadd = objec["scriptPubKey"]["addresses"][0] + totalinputval = totalinputval + float(objec["value"]) + vinlist.append([inputadd, objec["value"]]) + + # todo Rule 41 - Check if all the addresses in a transaction on the input side are the same + for idx, item in enumerate(vinlist): + if idx == 0: + temp = item[0] + continue + if item[0] != temp: + logger.info(f"System has found more than one address as part of vin. Transaction {transaction_data['txid']} is rejected") + return 0 + + inputlist = [vinlist[0][0], totalinputval] + + # todo Rule 42 - If the number of vout is more than 2, reject the transaction + if len(transaction_data["vout"]) > 2: + logger.info(f"System has found more than 2 address as part of vout. Transaction {transaction_data['txid']} is rejected") + return 0 + + # todo Rule 43 - A transaction accepted by the system has two vouts, 1. The FLO address of the receiver + # 2. Flo address of the sender as change address. If the vout address is change address, then the other adddress + # is the recevier address + + outputlist = [] + addresscounter = 0 + inputcounter = 0 + for obj in transaction_data["vout"]: + if obj["scriptPubKey"]["type"] == "pubkeyhash": + addresscounter = addresscounter + 1 + if inputlist[0] == obj["scriptPubKey"]["addresses"][0]: + inputcounter = inputcounter + 1 + continue + outputlist.append([obj["scriptPubKey"]["addresses"][0], obj["value"]]) + + if addresscounter == inputcounter: + outputlist = [inputlist[0]] + elif len(outputlist) != 1: + logger.info( + f"Transaction's change is not coming back to the input address. Transaction {transaction_data['txid']} is rejected") + return 0 + else: + outputlist = outputlist[0] + + logger.info(f"Input address list : {inputlist}") + logger.info(f"Output address list : {outputlist}") + + # All FLO checks completed at this point. + # Semantic rules for parsed data begins + + # todo Rule 44 - Process as per the type of transaction + if parsed_data['type'] == 'transfer': + logger.info(f"Transaction {transaction_data['txid']} is of the type transfer") + + # todo Rule 45 - If the transfer type is token, then call the function transferToken to adjust the balances + if parsed_data['transferType'] == 'token': + # check if the token exists in the database + if os.path.isfile(f"./tokens/{parsed_data['tokenIdentification']}.db"): + # Check if the transaction hash already exists in the token db + engine = create_engine(f"sqlite:///tokens/{parsed_data['tokenIdentification']}.db", echo=True) + connection = engine.connect() + blockno_txhash = connection.execute('select blockNumber, transactionHash from transactionHistory').fetchall() + connection.close() + blockno_txhash_T = list(zip(*blockno_txhash)) + + if transaction_data['txid'] in list(blockno_txhash_T[1]): + logger.warning(f"Transaction {transaction_data['txid']} already exists in the token db. This is unusual, please check your code") + pushData_SSEapi(f"Error | Transaction {transaction_data['txid']} already exists in the token db. This is unusual, please check your code") + return 0 + + returnval = transferToken(parsed_data['tokenIdentification'], parsed_data['tokenAmount'], inputlist[0],outputlist[0], transaction_data, parsed_data) + if returnval is None: + logger.info("Something went wrong in the token transfer method") + pushData_SSEapi(f"Error | Something went wrong while doing the internal db transactions for {transaction_data['txid']}") + return 0 + else: + updateLatestTransaction(transaction_data, parsed_data) + + # If this is the first interaction of the outputlist's address with the given token name, add it to token mapping + engine = create_engine('sqlite:///system.db', echo=True) + connection = engine.connect() + firstInteractionCheck = connection.execute(f"select * from tokenAddressMapping where tokenAddress='{outputlist[0]}' and token='{parsed_data['tokenIdentification']}'").fetchall() + + if len(firstInteractionCheck) == 0: + connection.execute(f"INSERT INTO tokenAddressMapping (tokenAddress, token, transactionHash, blockNumber, blockHash) VALUES ('{outputlist[0]}', '{parsed_data['tokenIdentification']}', '{transaction_data['txid']}', '{transaction_data['blockheight']}', '{transaction_data['blockhash']}')") + + connection.close() + + # Pass information to SSE channel + headers = {'Accept': 'application/json', 'Content-Type': 'application/json'} + # r = requests.post(tokenapi_sse_url, json={f"message': 'Token Transfer | name:{parsed_data['tokenIdentification']} | transactionHash:{transaction_data['txid']}"}, headers=headers) + return 1 + else: + logger.info( + f"Token transfer at transaction {transaction_data['txid']} rejected as a token with the name {parsed_data['tokenIdentification']} doesnt not exist") + engine = create_engine(f"sqlite:///system.db", echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(RejectedTransactionHistory(tokenIdentification=parsed_data['tokenIdentification'], + sourceFloAddress=inputadd, destFloAddress=outputlist[0], + transferAmount=parsed_data['tokenAmount'], + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Token transfer at transaction {transaction_data['txid']} rejected as a token with the name {parsed_data['tokenIdentification']} doesnt not exist", + transactionType=parsed_data['type'], + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + pushData_SSEapi( + f"Error | Token transfer at transaction {transaction_data['txid']} rejected as a token with the name {parsed_data['tokenIdentification']} doesnt not exist") + return 0 + + # todo Rule 46 - If the transfer type is smart contract, then call the function transferToken to do sanity checks & lock the balance + elif parsed_data['transferType'] == 'smartContract': + if os.path.isfile(f"./smartContracts/{parsed_data['contractName']}-{outputlist[0]}.db"): + # Check if the transaction hash already exists in the contract db (Safety check) + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True) + connection = engine.connect() + participantAdd_txhash = connection.execute('select participantAddress, transactionHash from contractparticipants').fetchall() + participantAdd_txhash_T = list(zip(*participantAdd_txhash)) + + if len(participantAdd_txhash) != 0 and transaction_data['txid'] in list(participantAdd_txhash_T[1]): + logger.warning(f"Transaction {transaction_data['txid']} rejected as it already exists in the Smart Contract db. This is unusual, please check your code") + pushData_SSEapi(f"Error | Transaction {transaction_data['txid']} rejected as it already exists in the Smart Contract db. This is unusual, please check your code") + return 0 + + # if contractAddress was passed, then check if it matches the output address of this contract + if 'contractAddress' in parsed_data: + if parsed_data['contractAddress'] != outputlist[0]: + logger.info(f"Contract participation at transaction {transaction_data['txid']} rejected as contractAddress specified in flodata, {parsed_data['contractAddress']}, doesnt not match with transaction's output address {outputlist[0]}") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine(f"sqlite:///system.db", echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(RejectedContractTransactionHistory(transactionType='participation', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Contract participation at transaction {transaction_data['txid']} rejected as contractAddress specified in flodata, {parsed_data['contractAddress']}, doesnt not match with transaction's output address {outputlist[0]}", + + parsedFloData=json.dumps(parsed_data))) + session.commit() + session.close() + + headers = {'Accept': 'application/json', 'Content-Type': 'application/json'} + '''r = requests.post(tokenapi_sse_url, json={'message': f"Error | Contract participation at transaction {transaction_data['txid']} rejected as contractAddress specified in flodata, {parsed_data['contractAddress']}, doesnt not match with transaction's output address {outputlist[0]}"}, + headers=headers)''' + + # Pass information to SSE channel + pushData_SSEapi('Error| Mismatch in contract address specified in flodata and the output address of the transaction {}'.format(transaction_data['txid'])) + return 0 + + # check the status of the contract + engine = create_engine('sqlite:///system.db', echo=True) + connection = engine.connect() + contractStatus = connection.execute(f"select status from activecontracts where contractName=='{parsed_data['contractName']}' and contractAddress='{outputlist[0]}'").fetchall()[0][0] + connection.close() + contractList = [] + + if contractStatus == 'closed': + logger.info( + f"Transaction {transaction_data['txid']} closed as Smart contract {parsed_data['contractName']} at the {outputlist[0]} is closed") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine( + f"sqlite:///system.db", + echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add( + RejectedContractTransactionHistory(transactionType='participation', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} closed as Smart contract {parsed_data['contractName']} at the {outputlist[0]} is closed", + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + + headers = {'Accept': 'application/json', 'Content-Type': 'application/json'} + '''r = requests.post(tokenapi_sse_url, json={'message': f"Error | Transaction {transaction_data['txid']} closed as Smart contract {parsed_data['contractName']} at the {outputlist[0]} is closed"}, + headers=headers)''' + return 0 + else: + engine = create_engine( + 'sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), + echo=True) + ContractBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + result = session.query(ContractStructure).filter_by(attribute='expiryTime').all() + session.close() + if result: + # now parse the expiry time in python + expirytime = result[0].value.strip() + expirytime_split = expirytime.split(' ') + parse_string = '{}/{}/{} {}'.format(expirytime_split[3], parsing.months[expirytime_split[1]], + expirytime_split[2], expirytime_split[4]) + expirytime_object = parsing.arrow.get(parse_string, 'YYYY/M/D HH:mm:ss').replace( + tzinfo=expirytime_split[5][3:]) + blocktime_object = parsing.arrow.get(transaction_data['blocktime']).to('Asia/Kolkata') + + if blocktime_object > expirytime_object: + logger.info( + f"Transaction {transaction_data['txid']} rejected as Smart contract {parsed_data['contractName']}-{outputlist[0]} has expired and will not accept any user participation") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine( + f"sqlite:///system.db", + echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add( + RejectedContractTransactionHistory(transactionType='participation', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} rejected as Smart contract {parsed_data['contractName']}-{outputlist[0]} has expired and will not accept any user participation", + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + pushData_SSEapi( + f"Error| Transaction {transaction_data['txid']} rejected as Smart contract {parsed_data['contractName']}-{outputlist[0]} has expired and will not accept any user participation") + return 0 + + # pull out the contract structure into a dictionary + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True) + connection = engine.connect() + attributevaluepair = connection.execute("select attribute, value from contractstructure where attribute != 'contractName' and attribute != 'flodata' and attribute != 'contractAddress'").fetchall() + contractStructure = {} + conditionDict = {} + counter = 0 + for item in attributevaluepair: + if list(item)[0] == 'exitconditions': + conditionDict[counter] = list(item)[1] + counter = counter + 1 + else: + contractStructure[list(item)[0]] = list(item)[1] + if len(conditionDict) > 0: + contractStructure['exitconditions'] = conditionDict + del counter, conditionDict + + # check if user choice has been passed, to the wrong contract type + if 'userChoice' in parsed_data and 'exitconditions' not in contractStructure: + logger.info( + f"Transaction {transaction_data['txid']} rejected as userChoice, {parsed_data['userChoice']}, has been passed to Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]} which doesn't accept any userChoice") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine( + f"sqlite:///system.db", + echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add( + RejectedContractTransactionHistory(transactionType='participation', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} rejected as userChoice, {parsed_data['userChoice']}, has been passed to Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]} which doesn't accept any userChoice", + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + pushData_SSEapi( + f"Error | Transaction {transaction_data['txid']} rejected as userChoice, {parsed_data['userChoice']}, has been passed to Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]} which doesn't accept any userChoice") + return 0 + + # check if the right token is being sent for participation + if parsed_data['tokenIdentification'] != contractStructure['tokenIdentification']: + logger.info( + f"Transaction {transaction_data['txid']} rejected as the token being transferred, {parsed_data['tokenIdentidication'].upper()}, is not part of the structure of Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]}") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine( + f"sqlite:///system.db", + echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add( + RejectedContractTransactionHistory(transactionType='participation', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} rejected as the token being transferred, {parsed_data['tokenIdentidication'].upper()}, is not part of the structure of Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]}", + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + pushData_SSEapi( + f"Error| Transaction {transaction_data['txid']} rejected as the token being transferred, {parsed_data['tokenIdentidication'].upper()}, is not part of the structure of Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]}") + return 0 + + # Check if the contract is of the type one-time-event + if contractStructure['contractType'] == 'one-time-event': + # Check if contractAmount is part of the contract structure, and enforce it if it is + if 'contractAmount' in contractStructure: + if float(contractStructure['contractAmount']) != float(parsed_data['tokenAmount']): + logger.info( + f"Transaction {transaction_data['txid']} rejected as contractAmount being transferred is not part of the structure of Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]}") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine( + f"sqlite:///system.db", + echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add( + RejectedContractTransactionHistory(transactionType='participation', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} rejected as contractAmount being transferred is not part of the structure of Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]}", + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + pushData_SSEapi( + f"Error| Transaction {transaction_data['txid']} rejected as contractAmount being transferred is not part of the structure of Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]}") + return 0 + + partialTransferCounter = 0 + # Check if maximum subscription amount has reached + if 'maximumsubscriptionamount' in contractStructure: + # now parse the expiry time in python + maximumsubscriptionamount = float(contractStructure['maximumsubscriptionamount']) + engine = create_engine( + 'sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), + echo=True) + ContractBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + amountDeposited = session.query(func.sum(ContractParticipants.tokenAmount)).all()[0][0] + session.close() + + if amountDeposited is None: + amountDeposited = 0 + + if amountDeposited >= maximumsubscriptionamount: + logger.info( + f"Transaction {transaction_data['txid']} rejected as maximum subscription amount has been reached for the Smart contract named {parsed_data['contractName']} at the address {outputlist[0]}") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine( + f"sqlite:///system.db", + echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add( + RejectedContractTransactionHistory(transactionType='participation', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} rejected as maximum subscription amount has been reached for the Smart contract named {parsed_data['contractName']} at the address {outputlist[0]}", + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + pushData_SSEapi( + f"Error | Transaction {transaction_data['txid']} rejected as maximum subscription amount has been reached for the Smart contract named {parsed_data['contractName']} at the address {outputlist[0]}") + return 0 + elif ((float(amountDeposited) + float(parsed_data[ + 'tokenAmount'])) > maximumsubscriptionamount) and 'contractAmount' in contractStructure: + logger.info( + f"Transaction {transaction_data['txid']} rejected as the contractAmount surpasses the maximum subscription amount, {contractStructure['maximumsubscriptionamount']} {contractStructure['tokenIdentification'].upper()}, for the Smart contract named {parsed_data['contractName']} at the address {outputlist[0]}") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine( + f"sqlite:///system.db", + echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add( + RejectedContractTransactionHistory(transactionType='participation', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} rejected as the contractAmount surpasses the maximum subscription amount, {contractStructure['maximumsubscriptionamount']} {contractStructure['tokenIdentification'].upper()}, for the Smart contract named {parsed_data['contractName']} at the address {outputlist[0]}", + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + pushData_SSEapi( + f"Error | Transaction {transaction_data['txid']} rejected as the contractAmount surpasses the maximum subscription amount, {contractStructure['maximumsubscriptionamount']} {contractStructure['tokenIdentification'].upper()}, for the Smart contract named {parsed_data['contractName']} at the address {outputlist[0]}") + return 0 + else: + partialTransferCounter = 1 + + # Check if exitcondition exists as part of contractstructure and is given in right format + if 'exitconditions' in contractStructure: + # This means the contract has an external trigger, ie. trigger coming from the contract committee + exitconditionsList = [] + for condition in contractStructure['exitconditions']: + exitconditionsList.append(contractStructure['exitconditions'][condition]) + + if parsed_data['userChoice'] in exitconditionsList: + if partialTransferCounter == 0: + # Check if the tokenAmount being transferred exists in the address & do the token transfer + returnval = transferToken(parsed_data['tokenIdentification'], + parsed_data['tokenAmount'], inputlist[0], outputlist[0], + transaction_data, parsed_data) + if returnval is not None: + # Store participant details in the smart contract's db + session.add(ContractParticipants(participantAddress=inputadd, + tokenAmount=parsed_data['tokenAmount'], + userChoice=parsed_data['userChoice'], + transactionHash=transaction_data['txid'], + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'])) + session.commit() + + # Store transfer as part of ContractTransactionHistory + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(ContractTransactionHistory(transactionType='participation', + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=parsed_data['tokenAmount'], + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + parsedFloData=json.dumps(parsed_data) + )) + + session.commit() + session.close() + + # Store a mapping of participant address -> Contract participated in + engine = create_engine('sqlite:///system.db', echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(ContractAddressMapping(address=inputadd, addressType='participant', + tokenAmount=parsed_data['tokenAmount'], + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + transactionHash=transaction_data['txid'], + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'])) + session.commit() + + # If this is the first interaction of the outputlist's address with the given token name, add it to token mapping + engine = create_engine('sqlite:///system.db', echo=True) + connection = engine.connect() + firstInteractionCheck = connection.execute( + f"select * from tokenAddressMapping where tokenAddress='{outputlist[0]}' and token='{parsed_data['tokenIdentification']}'").fetchall() + + if len(firstInteractionCheck) == 0: + connection.execute( + f"INSERT INTO tokenAddressMapping (tokenAddress, token, transactionHash, blockNumber, blockHash) VALUES ('{outputlist[0]}', '{parsed_data['tokenIdentification']}', '{transaction_data['txid']}', '{transaction_data['blockheight']}', '{transaction_data['blockhash']}')") + + connection.close() + + updateLatestTransaction(transaction_data, parsed_data) + return 1 + + else: + logger.info("Something went wrong in the smartcontract token transfer method") + return 0 + elif partialTransferCounter == 1: + # Transfer only part of the tokens users specified, till the time it reaches maximumamount + returnval = transferToken(parsed_data['tokenIdentification'], + maximumsubscriptionamount - amountDeposited, + inputlist[0], outputlist[0], transaction_data, parsed_data) + if returnval is not None: + # Store participant details in the smart contract's db + session.add(ContractParticipants(participantAddress=inputadd, + tokenAmount=maximumsubscriptionamount - amountDeposited, + userChoice=parsed_data['userChoice'], + transactionHash=transaction_data['txid'], + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'])) + session.commit() + session.close() + + # Store a mapping of participant address -> Contract participated in + engine = create_engine('sqlite:///system.db', echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(ContractAddressMapping(address=inputadd, addressType='participant', + tokenAmount=maximumsubscriptionamount - amountDeposited, + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + transactionHash=transaction_data['txid'], + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'])) + session.commit() + session.close() + updateLatestTransaction(transaction_data, parsed_data) + return 1 + + else: + logger.info("Something went wrong in the smartcontract token transfer method") + return 0 + + else: + logger.info( + f"Transaction {transaction_data['txid']} rejected as wrong userchoice entered for the Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]}") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine( + f"sqlite:///system.db", + echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(RejectedContractTransactionHistory(transactionType='participation', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} rejected as wrong userchoice entered for the Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]}", + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + pushData_SSEapi( + f"Error| Transaction {transaction_data['txid']} rejected as wrong userchoice entered for the Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]}") + return 0 + + elif 'payeeAddress' in contractStructure: + # this means the contract if of the type internal trigger + if partialTransferCounter == 0: + # Check if the tokenAmount being transferred exists in the address & do the token transfer + returnval = transferToken(parsed_data['tokenIdentification'], + parsed_data['tokenAmount'], inputlist[0], outputlist[0], + transaction_data, parsed_data) + if returnval is not None: + # Store participant details in the smart contract's db + session.add(ContractParticipants(participantAddress=inputadd, + tokenAmount=parsed_data['tokenAmount'], + userChoice='-', + transactionHash=transaction_data['txid'], + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'])) + session.commit() + + # Store transfer as part of ContractTransactionHistory + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(ContractTransactionHistory(transactionType='participation', + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=parsed_data['tokenAmount'], + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + + parsedFloData=json.dumps(parsed_data) + )) + + session.commit() + session.close() + + # Store a mapping of participant address -> Contract participated in + engine = create_engine('sqlite:///system.db', echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(ContractAddressMapping(address=inputadd, addressType='participant', + tokenAmount=parsed_data['tokenAmount'], + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + transactionHash=transaction_data['txid'], + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'])) + session.commit() + + updateLatestTransaction(transaction_data, parsed_data) + return 1 + + else: + logger.info("Something went wrong in the smartcontract token transfer method") + return 0 + elif partialTransferCounter == 1: + # Transfer only part of the tokens users specified, till the time it reaches maximumamount + returnval = transferToken(parsed_data['tokenIdentification'], + maximumsubscriptionamount - amountDeposited, + inputlist[0], outputlist[0], transaction_data, parsed_data) + if returnval is not None: + # Store participant details in the smart contract's db + session.add(ContractParticipants(participantAddress=inputadd, + tokenAmount=maximumsubscriptionamount - amountDeposited, + userChoice='-', + transactionHash=transaction_data['txid'], + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'])) + session.commit() + session.close() + + # Store a mapping of participant address -> Contract participated in + engine = create_engine('sqlite:///system.db', echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(ContractAddressMapping(address=inputadd, addressType='participant', + tokenAmount=maximumsubscriptionamount - amountDeposited, + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + transactionHash=transaction_data['txid'], + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'])) + session.commit() + session.close() + updateLatestTransaction(transaction_data, parsed_data) + return 1 + + else: + logger.info("Something went wrong in the smartcontract token transfer method") + return 0 + + else: + logger.info( + f"Transaction {transaction_data['txid']} rejected as the participation doesn't belong to any valid contract type") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine( + f"sqlite:///system.db", + echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add( + RejectedContractTransactionHistory(transactionType='participation', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} rejected as the participation doesn't belong to any valid contract type", + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + + headers = {'Accept': 'application/json', 'Content-Type': 'application/json'} + '''r = requests.post(tokenapi_sse_url, json={'message': f"Error | Transaction {transaction_data['txid']} rejected as the participation doesn't belong to any valid contract type"}, headers=headers)''' + return 0 + + else: + logger.info(f"Transaction {transaction_data['txid']} rejected as a Smart Contract with the name {parsed_data['contractName']} at address {outputlist[0]} doesnt exist") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine( + f"sqlite:///system.db", + echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(RejectedContractTransactionHistory(transactionType='participation', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} rejected as a Smart Contract with the name {parsed_data['contractName']} at address {outputlist[0]} doesnt exist", + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + + headers = {'Accept': 'application/json', 'Content-Type': 'application/json'} + '''r = requests.post(tokenapi_sse_url, json={'message': f"Error | Contract transaction {transaction_data['txid']} rejected as a smartcontract with same name {parsed_data['contractName']}-{parsed_data['contractAddress']} dosent exist "}, headers=headers)''' + return 0 + + elif parsed_data['transferType'] == 'swapParticipaton': + if os.path.isfile(f"./smartContracts/{parsed_data['contractName']}-{outputlist[0]}.db"): + # Check if the transaction hash already exists in the contract db (Safety check) + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True) + connection = engine.connect() + participantAdd_txhash = connection.execute('select participantAddress, transactionHash from contractparticipants').fetchall() + participantAdd_txhash_T = list(zip(*participantAdd_txhash)) + + # if contractAddress was passed, then check if it matches the output address of this contract + if 'contractAddress' in parsed_data: + if parsed_data['contractAddress'] != outputlist[0]: + logger.info(f"Contract participation at transaction {transaction_data['txid']} rejected as contractAddress specified in flodata, {parsed_data['contractAddress']}, doesnt not match with transaction's output address {outputlist[0]}") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine(f"sqlite:///system.db", echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(RejectedContractTransactionHistory(transactionType='participation', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Contract participation at transaction {transaction_data['txid']} rejected as contractAddress specified in flodata, {parsed_data['contractAddress']}, doesnt not match with transaction's output address {outputlist[0]}", + + parsedFloData=json.dumps(parsed_data))) + session.commit() + session.close() + + headers = {'Accept': 'application/json', 'Content-Type': 'application/json'} + '''r = requests.post(tokenapi_sse_url, json={'message': f"Error | Contract participation at transaction {transaction_data['txid']} rejected as contractAddress specified in flodata, {parsed_data['contractAddress']}, doesnt not match with transaction's output address {outputlist[0]}"}, + headers=headers)''' + + # Pass information to SSE channel + pushData_SSEapi('Error| Mismatch in contract address specified in flodata and the output address of the transaction {}'.format(transaction_data['txid'])) + return 0 + + # pull out the contract structure into a dictionary + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True) + connection = engine.connect() + attributevaluepair = connection.execute("select attribute, value from contractstructure where attribute != 'contractName' and attribute != 'flodata' and attribute != 'contractAddress'").fetchall() + contractStructure = {} + conditionDict = {} + counter = 0 + del counter, conditionDict + + if contractStructure['priceType'] == 'predetermined': + swapPrice = contractStructure['price'] + elif contractStructure['priceType'] == 'dynamic': + pass + + returnval = transferToken(contractStructure['accepting_token'], swapPrice, inputlist[0],outputlist[0], transaction_data, parsed_data) + if returnval is None: + logger.info("Something went wrong in the token transfer method") + pushData_SSEapi(f"Error | Something went wrong while doing the internal db transactions for {transaction_data['txid']}") + return 0 + else: + updateLatestTransaction(transaction_data, parsed_data) + + # If this is the first interaction of the outputlist's address with the given token name, add it to token mapping + engine = create_engine('sqlite:///system.db', echo=True) + connection = engine.connect() + firstInteractionCheck = connection.execute(f"select * from tokenAddressMapping where tokenAddress='{outputlist[0]}' and token='{parsed_data['tokenIdentification']}'").fetchall() + + if len(firstInteractionCheck) == 0: + connection.execute(f"INSERT INTO tokenAddressMapping (tokenAddress, token, transactionHash, blockNumber, blockHash) VALUES ('{outputlist[0]}', '{parsed_data['tokenIdentification']}', '{transaction_data['txid']}', '{transaction_data['blockheight']}', '{transaction_data['blockhash']}')") + + connection.close() + + # Pass information to SSE channel + headers = {'Accept': 'application/json', 'Content-Type': 'application/json'} + # r = requests.post(tokenapi_sse_url, json={f"message': 'Token Transfer | name:{parsed_data['tokenIdentification']} | transactionHash:{transaction_data['txid']}"}, headers=headers) + + returnval = transferToken(contractStructure['selling_token'], swapPrice, outputlist[0], inputlist[0], transaction_data, parsed_data) + if returnval is None: + logger.info("Something went wrong in the token transfer method") + pushData_SSEapi(f"Error | Something went wrong while doing the internal db transactions for {transaction_data['txid']}") + return 0 + else: + updateLatestTransaction(transaction_data, parsed_data) + + # If this is the first interaction of the outputlist's address with the given token name, add it to token mapping + engine = create_engine('sqlite:///system.db', echo=True) + connection = engine.connect() + firstInteractionCheck = connection.execute(f"select * from tokenAddressMapping where tokenAddress='{outputlist[0]}' and token='{parsed_data['tokenIdentification']}'").fetchall() + + if len(firstInteractionCheck) == 0: + connection.execute(f"INSERT INTO tokenAddressMapping (tokenAddress, token, transactionHash, blockNumber, blockHash) VALUES ('{outputlist[0]}', '{parsed_data['tokenIdentification']}', '{transaction_data['txid']}', '{transaction_data['blockheight']}', '{transaction_data['blockhash']}')") + + connection.close() + + # Push the Swap Participation transaction into contract database + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True) + ContinuosContractBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + + session.add(ContractParticipants1(participantAddress = inputadd, + tokenAmount = swapPrice, + transactionHash = transaction_data['txid'], + blockNumber = transaction_data['blockheight'], + blockHash = transaction_data['blockhash'] + )) + session.add(ContractTransactionHistory1(transactionType = 'smartContractDeposit', + transactionSubType = None, + sourceFloAddress = inputadd, + destFloAddress = outputlist[0], + transferAmount = parsed_data['depositAmount'], + blockNumber = transaction_data['blockheight'], + blockHash = transaction_data['blockhash'], + time = transaction_data['blocktime'], + transactionHash = transaction_data['txid'], + blockchainReference = blockchainReference, + jsonData = json.dumps(transaction_data), + parsedFloData = json.dumps(parsed_data) + )) + session.commit() + session.close() + pushData_SSEapi(f"Deposit Smart Contract Transaction {transaction_data['txid']} rejected as maximum subscription amount has been reached for the Smart contract named {parsed_data['contractName']} at the address {outputlist[0]}") + return 0 + + # Pass information to SSE channel + headers = {'Accept': 'application/json', 'Content-Type': 'application/json'} + # r = requests.post(tokenapi_sse_url, json={f"message': 'Token Transfer | name:{parsed_data['tokenIdentification']} | transactionHash:{transaction_data['txid']}"}, headers=headers) + + return 1 + + + else: + logger.info(f"Transaction {transaction_data['txid']} rejected as a Smart Contract with the name {parsed_data['contractName']} at address {outputlist[0]} doesnt exist") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine(f"sqlite:///system.db", echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(RejectedContractTransactionHistory(transactionType='participation', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} rejected as a Smart Contract with the name {parsed_data['contractName']} at address {outputlist[0]} doesnt exist", + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + + headers = {'Accept': 'application/json', 'Content-Type': 'application/json'} + '''r = requests.post(tokenapi_sse_url, json={ + 'message': f"Error | Contract transaction {transaction_data['txid']} rejected as a smartcontract with same name {parsed_data['contractName']}-{parsed_data['contractAddress']} dosent exist "}, + headers=headers)''' + return 0 + + + # todo Rule 47 - If the parsed data type is token incorporation, then check if the name hasn't been taken already + # if it has been taken then reject the incorporation. Else incorporate it + elif parsed_data['type'] == 'tokenIncorporation': + if not os.path.isfile(f"./tokens/{parsed_data['tokenIdentification']}.db"): + engine = create_engine(f"sqlite:///tokens/{parsed_data['tokenIdentification']}.db", echo=True) + Base.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(ActiveTable(address=inputlist[0], parentid=0, transferBalance=parsed_data['tokenAmount'])) + session.add(TransferLogs(sourceFloAddress=inputadd, destFloAddress=outputlist[0], + transferAmount=parsed_data['tokenAmount'], sourceId=0, destinationId=1, + blockNumber=transaction_data['blockheight'], time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'])) + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(TransactionHistory(sourceFloAddress=inputadd, destFloAddress=outputlist[0], + transferAmount=parsed_data['tokenAmount'], + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), transactionType=parsed_data['type'], + parsedFloData=json.dumps(parsed_data))) + session.commit() + session.close() + + # add it to token address to token mapping db table + engine = create_engine('sqlite:///system.db'.format(parsed_data['tokenIdentification']), echo=True) + connection = engine.connect() + connection.execute(f"INSERT INTO tokenAddressMapping (tokenAddress, token, transactionHash, blockNumber, blockHash) VALUES ('{inputadd}', '{parsed_data['tokenIdentification']}', '{transaction_data['txid']}', '{transaction_data['blockheight']}', '{transaction_data['blockhash']}');") + connection.close() + + updateLatestTransaction(transaction_data, parsed_data) + + pushData_SSEapi(f"Token | Succesfully incorporated token {parsed_data['tokenIdentification']} at transaction {transaction_data['txid']}") + return 1 + else: + logger.info(f"Transaction {transaction_data['txid']} rejected as a token with the name {parsed_data['tokenIdentification']} has already been incorporated") + engine = create_engine(f"sqlite:///system.db", echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(RejectedTransactionHistory(tokenIdentification=parsed_data['tokenIdentification'], + sourceFloAddress=inputadd, destFloAddress=outputlist[0], + transferAmount=parsed_data['tokenAmount'], + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} rejected as a token with the name {parsed_data['tokenIdentification']} has already been incorporated", + transactionType=parsed_data['type'], + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + pushData_SSEapi(f"Error | Token incorporation rejected at transaction {transaction_data['txid']} as token {parsed_data['tokenIdentification']} already exists") + return 0 + + # todo Rule 48 - If the parsed data type if smart contract incorporation, then check if the name hasn't been taken already + # if it has been taken then reject the incorporation. + elif parsed_data['type'] == 'smartContractIncorporation': + if not os.path.isfile(f"./smartContracts/{parsed_data['contractName']}-{parsed_data['contractAddress']}.db"): + # todo Rule 49 - If the contract name hasn't been taken before, check if the contract type is an authorized type by the system + if parsed_data['contractType'] == 'one-time-event': + logger.info("Smart contract is of the type one-time-event") + + # either userchoice or payeeAddress condition should be present. Check for it + if 'userchoices' not in parsed_data['contractConditions'] and 'payeeAddress' not in parsed_data['contractConditions']: + logger.info(f"Either userchoice or payeeAddress should be part of the Contract conditions.\nSmart contract incorporation on transaction {transaction_data['txid']} rejected") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine(f"sqlite:///system.db", echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add( + RejectedContractTransactionHistory(transactionType='incorporation', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Either userchoice or payeeAddress should be part of the Contract conditions.\nSmart contract incorporation on transaction {transaction_data['txid']} rejected", + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + return 0 + + # userchoice and payeeAddress conditions cannot come together. Check for it + if 'userchoices' in parsed_data['contractConditions'] and 'payeeAddress' in parsed_data['contractConditions']: + logger.info( + f"Both userchoice and payeeAddress provided as part of the Contract conditions.\nSmart contract incorporation on transaction {transaction_data['txid']} rejected") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine( + f"sqlite:///system.db", + echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add( + RejectedContractTransactionHistory(transactionType='incorporation', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Both userchoice and payeeAddress provided as part of the Contract conditions.\nSmart contract incorporation on transaction {transaction_data['txid']} rejected", + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + return 0 + + # todo Rule 50 - Contract address mentioned in flodata field should be same as the receiver FLO address on the output side + # henceforth we will not consider any flo private key initiated comment as valid from this address + # Unlocking can only be done through smart contract system address + if parsed_data['contractAddress'] == inputadd: + dbName = '{}-{}'.format(parsed_data['contractName'], parsed_data['contractAddress']) + engine = create_engine('sqlite:///smartContracts/{}.db'.format(dbName), echo=True) + ContractBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(ContractStructure(attribute='contractType', index=0, value=parsed_data['contractType'])) + session.add(ContractStructure(attribute='contractName', index=0, value=parsed_data['contractName'])) + session.add( + ContractStructure(attribute='tokenIdentification', index=0, + value=parsed_data['tokenIdentification'])) + session.add( + ContractStructure(attribute='contractAddress', index=0, value=parsed_data['contractAddress'])) + session.add( + ContractStructure(attribute='flodata', index=0, + value=parsed_data['flodata'])) + session.add( + ContractStructure(attribute='expiryTime', index=0, + value=parsed_data['contractConditions']['expiryTime'])) + if 'contractAmount' in parsed_data['contractConditions']: + session.add( + ContractStructure(attribute='contractAmount', index=0, + value=parsed_data['contractConditions']['contractAmount'])) + + if 'minimumsubscriptionamount' in parsed_data['contractConditions']: + session.add( + ContractStructure(attribute='minimumsubscriptionamount', index=0, + value=parsed_data['contractConditions']['minimumsubscriptionamount'])) + if 'maximumsubscriptionamount' in parsed_data['contractConditions']: + session.add( + ContractStructure(attribute='maximumsubscriptionamount', index=0, + value=parsed_data['contractConditions']['maximumsubscriptionamount'])) + if 'userchoices' in parsed_data['contractConditions']: + for key, value in parsed_data['contractConditions']['userchoices'].items(): + session.add(ContractStructure(attribute='exitconditions', index=key, value=value)) + + if 'payeeAddress' in parsed_data['contractConditions']: + # in this case, expirydate( or maximumamount) is the trigger internally. Keep a track of expiry dates + session.add( + ContractStructure(attribute='payeeAddress', index=0, + value=parsed_data['contractConditions']['payeeAddress'])) + + session.commit() + + # Store transfer as part of ContractTransactionHistory + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(ContractTransactionHistory(transactionType='incorporation', sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + + # add Smart Contract name in token contract association + engine = create_engine(f"sqlite:///tokens/{parsed_data['tokenIdentification']}.db", echo=True) + Base.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(TokenContractAssociation(tokenIdentification=parsed_data['tokenIdentification'], + contractName=parsed_data['contractName'], + contractAddress=parsed_data['contractAddress'], + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + transactionType=parsed_data['type'], + parsedFloData=json.dumps(parsed_data))) + session.commit() + session.close() + + # Store smart contract address in system's db, to be ignored during future transfers + engine = create_engine('sqlite:///system.db', echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(ActiveContracts(contractName=parsed_data['contractName'], + contractAddress=parsed_data['contractAddress'], status='active', + tokenIdentification=parsed_data['tokenIdentification'], + contractType=parsed_data['contractType'], + transactionHash=transaction_data['txid'], + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + incorporationDate=transaction_data['blocktime'])) + session.commit() + + session.add(ContractAddressMapping(address=inputadd, addressType='incorporation', + tokenAmount=None, + contractName=parsed_data['contractName'], + contractAddress=inputadd, + transactionHash=transaction_data['txid'], + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'])) + session.commit() + + session.close() + + updateLatestTransaction(transaction_data, parsed_data) + + pushData_SSEapi('Contract | Contract incorporated at transaction {} with name {}-{}'.format( + transaction_data['txid'], parsed_data['contractName'], parsed_data['contractAddress'])) + return 1 + else: + logger.info( + f"Contract Incorporation on transaction {transaction_data['txid']} rejected as contract address in Flodata and input address are different") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine( + f"sqlite:///system.db", + echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add( + RejectedContractTransactionHistory(transactionType='incorporation', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Contract Incorporation on transaction {transaction_data['txid']} rejected as contract address in flodata and input address are different", + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + pushData_SSEapi( + 'Error | Contract Incorporation rejected as address in Flodata and input address are different at transaction {}'.format( + transaction_data['txid'])) + return 0 + + if parsed_data['contractType'] == 'continuous-event': + logger.debug("Smart contract is of the type continuous-event") + # Add checks to reject the creation of contract + if parsed_data['contractAddress'] == inputadd: + dbName = '{}-{}'.format(parsed_data['contractName'], parsed_data['contractAddress']) + engine = create_engine('sqlite:///smartContracts/{}.db'.format(dbName), echo=True) + ContinuosContractBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(ContractStructure1(attribute='contractType', index=0, value=parsed_data['contractType'])) + session.add(ContractStructure1(attribute='contractName', index=0, value=parsed_data['contractName'])) + session.add(ContractStructure1(attribute='contractAddress', index=0, value=parsed_data['contractAddress'])) + session.add(ContractStructure1(attribute='flodata', index=0, value=parsed_data['flodata'])) + if 'subtype' in parsed_data['contractConditions']: + # todo: Check if the both the tokens mentioned exist if its a token swap + if (parsed_data['contractConditions']['subtype'] == 'tokenswap') and (os.path.isfile(f"./tokens/{parsed_data['contractConditions']['accepting_token'].split('#')[0]}.db")) and (os.path.isfile(f"./tokens/{parsed_data['contractConditions']['selling_token'].split('#')[0]}.db")): + #if (parsed_data['contractConditions']['subtype'] == 'tokenswap'): + if parsed_data['contractConditions']['priceType'] in ['predetermined','determined']: + session.add(ContractStructure1(attribute='subtype', index=0, value=parsed_data['contractConditions']['subtype'])) + session.add(ContractStructure1(attribute='accepting_token', index=0, value=parsed_data['contractConditions']['accepting_token'])) + session.add(ContractStructure1(attribute='selling_token', index=0, value=parsed_data['contractConditions']['selling_token'])) + # determine price + session.add(ContractStructure1(attribute='priceType', index=0, value=parsed_data['contractConditions']['priceType'])) + session.add(ContractStructure1(attribute='price', index=0, value=parsed_data['contractConditions']['price'])) + + + # Store transfer as part of ContractTransactionHistory + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(ContractTransactionHistory1(transactionType='incorporation', sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps( + transaction_data), + parsedFloData=json.dumps( + parsed_data) + )) + session.commit() + session.close() + + # add Smart Contract name in token contract association + accepting_sending_tokenlist = [parsed_data['contractConditions']['accepting_token'], parsed_data['contractConditions']['selling_token']] + for token_name in accepting_sending_tokenlist: + token_name = token_name.split('#')[0] + engine = create_engine(f"sqlite:///tokens/{token_name}.db", echo=True) + Base.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(TokenContractAssociation(tokenIdentification=token_name, + contractName=parsed_data['contractName'], + contractAddress=parsed_data['contractAddress'], + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + transactionType=parsed_data['type'], + parsedFloData=json.dumps(parsed_data))) + session.commit() + session.close() + + # Store smart contract address in system's db, to be ignored during future transfers + engine = create_engine('sqlite:///system.db', echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(ActiveContracts(contractName=parsed_data['contractName'], + contractAddress=parsed_data['contractAddress'], status='active', + tokenIdentification=str(accepting_sending_tokenlist), + contractType=parsed_data['contractType'], + transactionHash=transaction_data['txid'], + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + incorporationDate=transaction_data['blocktime'])) + session.commit() + + # todo - Add a condition for rejected contract transaction on the else loop for this condition + session.add(ContractAddressMapping(address=inputadd, addressType='incorporation', + tokenAmount=None, + contractName=parsed_data['contractName'], + contractAddress=inputadd, + transactionHash=transaction_data['txid'], + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'])) + session.commit() + session.close() + + updateLatestTransaction(transaction_data, parsed_data) + + pushData_SSEapi('Contract | Contract incorporated at transaction {} with name {}-{}'.format(transaction_data['txid'], parsed_data['contractName'], parsed_data['contractAddress'])) + return 1 + + '''else if (parsed_data['contractConditions']['subtype'] == 'bitbonds'): + # Check if both the tokens mentioned in the bond exist + pass + ''' + else: + logger.info(f"priceType is not part of accepted parameters for a continuos event contract of the type token swap.\nSmart contract incorporation on transaction {transaction_data['txid']} rejected") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine(f"sqlite:///system.db", echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(RejectedContractTransactionHistory(transactionType='incorporation', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps( + transaction_data), + rejectComment=f"priceType is not part of accepted parameters for a continuos event contract of the type token swap.\nSmart contract incorporation on transaction {transaction_data['txid']} rejected", + parsedFloData=json.dumps( + parsed_data) + )) + session.commit() + session.close() + return 0 + + else: + logger.info(f"No subtype provided || mentioned tokens do not exist for the Contract of type continuos event.\nSmart contract incorporation on transaction {transaction_data['txid']} rejected") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine(f"sqlite:///system.db", echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(RejectedContractTransactionHistory(transactionType='incorporation', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps( + transaction_data), + rejectComment=f"No subtype provided for the Contract of type continuos event.\nSmart contract incorporation on transaction {transaction_data['txid']} rejected", + parsedFloData=json.dumps( + parsed_data) + )) + session.commit() + session.close() + return 0 + + session.commit() + session.close() + + else: + logger.info(f"Transaction {transaction_data['txid']} rejected as a Smart Contract with the name {parsed_data['contractName']} at address {parsed_data['contractAddress']} already exists") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine(f"sqlite:///system.db", echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(RejectedContractTransactionHistory(transactionType='incorporation', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} rejected as a Smart Contract with the name {parsed_data['contractName']} at address {parsed_data['contractAddress']} already exists", + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + + headers = {'Accept': 'application/json', 'Content-Type': 'application/json'} + '''r = requests.post(tokenapi_sse_url, json={ + 'message': 'Error | Contract Incorporation rejected as a smartcontract with same name {}-{} is active currentlyt at transaction {}'.format(parsed_data['contractName'], parsed_data['contractAddress'], transaction_data['txid'])}, headers=headers) + ''' + return 0 + + elif parsed_data['type'] == 'smartContractPays': + logger.info(f"Transaction {transaction_data['txid']} is of the type smartContractPays") + + # Check if input address is a committee address + if inputlist[0] in committeeAddressList: + # check if the contract exists + if os.path.isfile(f"./smartContracts/{parsed_data['contractName']}-{outputlist[0]}.db"): + # Check if the transaction hash already exists in the contract db (Safety check) + engine = create_engine( + 'sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True) + connection = engine.connect() + participantAdd_txhash = connection.execute( + f"select sourceFloAddress, transactionHash from contractTransactionHistory where transactionType != 'incorporation'").fetchall() + participantAdd_txhash_T = list(zip(*participantAdd_txhash)) + + if len(participantAdd_txhash) != 0 and transaction_data['txid'] in list(participantAdd_txhash_T[1]): + logger.warning( + f"Transaction {transaction_data['txid']} rejected as it already exists in the Smart Contract db. This is unusual, please check your code") + pushData_SSEapi( + f"Error | Transaction {transaction_data['txid']} rejected as it already exists in the Smart Contract db. This is unusual, please check your code") + return 0 + + # pull out the contract structure into a dictionary + engine = create_engine( + 'sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True) + connection = engine.connect() + attributevaluepair = connection.execute( + "select attribute, value from contractstructure where attribute != 'contractName' and attribute != 'flodata' and attribute != 'contractAddress'").fetchall() + contractStructure = {} + conditionDict = {} + counter = 0 + for item in attributevaluepair: + if list(item)[0] == 'exitconditions': + conditionDict[counter] = list(item)[1] + counter = counter + 1 + else: + contractStructure[list(item)[0]] = list(item)[1] + if len(conditionDict) > 0: + contractStructure['exitconditions'] = conditionDict + del counter, conditionDict + + # if contractAddress has been passed, check if output address is contract Incorporation address + if 'contractAddress' in contractStructure: + if outputlist[0] != contractStructure['contractAddress']: + logger.warning( + f"Transaction {transaction_data['txid']} rejected as Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]} hasn't expired yet") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine( + f"sqlite:///system.db", + echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add( + RejectedContractTransactionHistory(transactionType='trigger', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} rejected as Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]} hasn't expired yet", + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + pushData_SSEapi( + f"Error | Transaction {transaction_data['txid']} rejected as Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]} hasn't expired yet") + return 0 + + # check the type of smart contract ie. external trigger or internal trigger + if 'payeeAddress' in contractStructure: + logger.warning( + f"Transaction {transaction_data['txid']} rejected as Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]} has an internal trigger") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine( + f"sqlite:///system.db", + echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add( + RejectedContractTransactionHistory(transactionType='trigger', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} rejected as Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]} has an internal trigger", + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + pushData_SSEapi( + f"Error | Transaction {transaction_data['txid']} rejected as Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]} has an internal trigger") + return 0 + + # check the status of the contract + engine = create_engine('sqlite:///system.db', echo=True) + connection = engine.connect() + contractStatus = connection.execute( + f"select status from activecontracts where contractName=='{parsed_data['contractName']}' and contractAddress='{outputlist[0]}'").fetchall()[ + 0][0] + connection.close() + contractList = [] + + if contractStatus == 'closed': + logger.info( + f"Transaction {transaction_data['txid']} closed as Smart contract {parsed_data['contractName']} at the {outputlist[0]} is closed") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine( + f"sqlite:///system.db", + echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add( + RejectedContractTransactionHistory(transactionType='trigger', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} closed as Smart contract {parsed_data['contractName']} at the {outputlist[0]} is closed", + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + + headers = {'Accept': 'application/json', 'Content-Type': 'application/json'} + '''r = requests.post(tokenapi_sse_url, json={ + 'message': f"Error | Transaction {transaction_data['txid']} closed as Smart contract {parsed_data['contractName']} at the {outputlist[0]} is closed"}, + headers=headers)''' + return 0 + else: + engine = create_engine( + 'sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), + echo=True) + ContractBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + result = session.query(ContractStructure).filter_by(attribute='expiryTime').all() + session.close() + if result: + # now parse the expiry time in python + expirytime = result[0].value.strip() + expirytime_split = expirytime.split(' ') + parse_string = '{}/{}/{} {}'.format(expirytime_split[3], + parsing.months[expirytime_split[1]], + expirytime_split[2], expirytime_split[4]) + expirytime_object = parsing.arrow.get(parse_string, 'YYYY/M/D HH:mm:ss').replace( + tzinfo=expirytime_split[5][3:]) + blocktime_object = parsing.arrow.get(transaction_data['blocktime']).to('Asia/Kolkata') + + if blocktime_object <= expirytime_object: + logger.info( + f"Transaction {transaction_data['txid']} rejected as Smart contract {parsed_data['contractName']}-{outputlist[0]} has not expired and will not trigger") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine( + f"sqlite:///system.db", + echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add( + RejectedContractTransactionHistory(transactionType='trigger', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} rejected as Smart contract {parsed_data['contractName']}-{outputlist[0]} has not expired and will not trigger", + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + pushData_SSEapi( + f"Error| Transaction {transaction_data['txid']} rejected as Smart contract {parsed_data['contractName']}-{outputlist[0]} has not expired and will not trigger") + return 0 + + # check if the user choice passed is part of the contract structure + tempchoiceList = [] + for item in contractStructure['exitconditions']: + tempchoiceList.append(contractStructure['exitconditions'][item]) + + if parsed_data['triggerCondition'] not in tempchoiceList: + logger.info( + f"Transaction {transaction_data['txid']} rejected as triggerCondition, {parsed_data['triggerCondition']}, has been passed to Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]} which doesn't accept any userChoice of the given name") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine( + f"sqlite:///system.db", + echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add( + RejectedContractTransactionHistory(transactionType='trigger', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} rejected as triggerCondition, {parsed_data['triggerCondition']}, has been passed to Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]} which doesn't accept any userChoice of the given name", + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + pushData_SSEapi( + f"Error | Transaction {transaction_data['txid']} rejected as triggerCondition, {parsed_data['triggerCondition']}, has been passed to Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]} which doesn't accept any userChoice of the given name") + return 0 + + # check if minimumsubscriptionamount exists as part of the contract structure + if 'minimumsubscriptionamount' in contractStructure: + # if it has not been reached, close the contract and return money + minimumsubscriptionamount = float(contractStructure['minimumsubscriptionamount']) + engine = create_engine( + 'sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), + echo=True) + ContractBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + amountDeposited = session.query(func.sum(ContractParticipants.tokenAmount)).all()[0][0] + session.close() + + if amountDeposited is None: + amountDeposited = 0 + + if amountDeposited < minimumsubscriptionamount: + # close the contract and return the money + logger.info( + 'Minimum subscription amount hasn\'t been reached\n The token will be returned back') + # Initialize payback to contract participants + engine = create_engine( + 'sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), + echo=True) + connection = engine.connect() + contractParticipants = connection.execute( + 'select participantAddress, tokenAmount, transactionHash from contractparticipants').fetchall()[ + 0][0] + + for participant in contractParticipants: + tokenIdentification = connection.execute( + 'select * from contractstructure where attribute="tokenIdentification"').fetchall()[0][ + 0] + contractAddress = connection.execute( + 'select * from contractstructure where attribute="contractAddress"').fetchall()[0][0] + returnval = transferToken(tokenIdentification, participant[1], contractAddress, + participant[0], transaction_data, parsed_data) + if returnval is None: + logger.info( + "CRITICAL ERROR | Something went wrong in the token transfer method while doing local Smart Contract Trigger") + return 0 + + connection.execute( + 'update contractparticipants set winningAmount="{}" where participantAddress="{}" and transactionHash="{}"'.format( + (participant[1], participant[0], participant[4]))) + + # add transaction to ContractTransactionHistory + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + engine = create_engine( + 'sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), + echo=True) + ContractBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(ContractTransactionHistory(transactionType='trigger', + transactionSubType='minimumsubscriptionamount-payback', + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + + engine = create_engine('sqlite:///system.db', echo=True) + connection = engine.connect() + connection.execute( + 'update activecontracts set status="closed" where contractName="{}" and contractAddress="{}"'.format( + parsed_data['contractName'], outputlist[0])) + connection.execute( + 'update activecontracts set status="{}" where contractName="{}" and contractAddress="{}"'.format( + transaction_data['blocktime'], + parsed_data['contractName'], outputlist[0])) + connection.close() + + updateLatestTransaction(transaction_data, parsed_data) + + pushData_SSEapi( + 'Trigger | Minimum subscription amount not reached at contract {}-{} at transaction {}. Tokens will be refunded'.format( + parsed_data['contractName'], outputlist[0], transaction_data['txid'])) + return 1 + + # Trigger the contract + engine = create_engine( + 'sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), + echo=True) + connection = engine.connect() + contractWinners = connection.execute( + 'select * from contractparticipants where userChoice="{}"'.format( + parsed_data['triggerCondition'])).fetchall() + tokenSum = connection.execute('select sum(tokenAmount) from contractparticipants').fetchall()[0][0] + winnerSum = connection.execute( + 'select sum(tokenAmount) from contractparticipants where userChoice="{}"'.format( + parsed_data['triggerCondition'])).fetchall()[0][0] + tokenIdentification = connection.execute( + 'select value from contractstructure where attribute="tokenIdentification"').fetchall()[0][0] + + for winner in contractWinners: + winnerAmount = "%.8f" % ((winner[2] / winnerSum) * tokenSum) + returnval = transferToken(tokenIdentification, winnerAmount, + outputlist[0], winner[1], transaction_data, parsed_data) + if returnval is None: + logger.critical( + "Something went wrong in the token transfer method while doing local Smart Contract Trigger") + return 0 + connection.execute( + f"update contractparticipants set winningAmount='{winnerAmount}' where participantAddress='{winner[1]}' and transactionHash='{winner[4]}'") + + # add transaction to ContractTransactionHistory + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(ContractTransactionHistory(transactionType='trigger', + transactionSubType='committee', + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + + engine = create_engine('sqlite:///system.db', echo=True) + connection = engine.connect() + connection.execute( + 'update activecontracts set status="closed" where contractName="{}" and contractAddress="{}"'.format( + parsed_data['contractName'], outputlist[0])) + connection.execute( + 'update activecontracts set closeDate="{}" where contractName="{}" and contractAddress="{}"'.format( + transaction_data['blocktime'], + parsed_data['contractName'], outputlist[0])) + connection.close() + + updateLatestTransaction(transaction_data, parsed_data) + + pushData_SSEapi( + 'Trigger | Contract triggered of the name {}-{} is active currently at transaction {}'.format( + parsed_data['contractName'], outputlist[0], transaction_data['txid'])) + return 1 + else: + logger.info( + f"Transaction {transaction_data['txid']} rejected as Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]} doesn't exist") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine( + f"sqlite:///system.db", + echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add( + RejectedContractTransactionHistory(transactionType='trigger', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} rejected as Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]} doesn't exist", + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + pushData_SSEapi( + f"Error | Transaction {transaction_data['txid']} rejected as Smart Contract named {parsed_data['contractName']} at the address {outputlist[0]} doesn't exist") + return 0 + + else: + logger.info( + f"Transaction {transaction_data['txid']} rejected as input address, {inputlist[0]}, is not part of the committee address list") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine( + f"sqlite:///system.db", + echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(RejectedContractTransactionHistory(transactionType='trigger', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} rejected as input address, {inputlist[0]}, is not part of the committee address list", + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + pushData_SSEapi( + f"Transaction {transaction_data['txid']} rejected as input address, {inputlist[0]}, is not part of the committee address list") + return 0 + + elif parsed_data['type'] == 'smartContractDeposit': + if os.path.isfile(f"./smartContracts/{parsed_data['contractName']}-{outputlist[0]}.db"): + # Check if the transaction hash already exists in the contract db (Safety check) + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True) + connection = engine.connect() + participantAdd_txhash = connection.execute('select participantAddress, transactionHash from contractparticipants').fetchall() + participantAdd_txhash_T = list(zip(*participantAdd_txhash)) + + if len(participantAdd_txhash) != 0 and transaction_data['txid'] in list(participantAdd_txhash_T[1]): + logger.warning(f"Transaction {transaction_data['txid']} rejected as it already exists in the Smart Contract db. This is unusual, please check your code") + pushData_SSEapi(f"Error | Transaction {transaction_data['txid']} rejected as it already exists in the Smart Contract db. This is unusual, please check your code") + return 0 + + # if contractAddress was passed, then check if it matches the output address of this contract + if 'contractAddress' in parsed_data: + if parsed_data['contractAddress'] != outputlist[0]: + logger.info(f"Contract participation at transaction {transaction_data['txid']} rejected as contractAddress specified in flodata, {parsed_data['contractAddress']}, doesnt not match with transaction's output address {outputlist[0]}") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine(f"sqlite:///system.db", echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(RejectedContractTransactionHistory(transactionType='participation', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Contract participation at transaction {transaction_data['txid']} rejected as contractAddress specified in flodata, {parsed_data['contractAddress']}, doesnt not match with transaction's output address {outputlist[0]}", + parsedFloData=json.dumps(parsed_data))) + session.commit() + session.close() + + headers = {'Accept': 'application/json', 'Content-Type': 'application/json'} + '''r = requests.post(tokenapi_sse_url, json={'message': f"Error | Contract participation at transaction {transaction_data['txid']} rejected as contractAddress specified in flodata, {parsed_data['contractAddress']}, doesnt not match with transaction's output address {outputlist[0]}"}, headers=headers)''' + + # Pass information to SSE channel + pushData_SSEapi('Error| Mismatch in contract address specified in flodata and the output address of the transaction {}'.format(transaction_data['txid'])) + return 0 + + # pull out the contract structure into a dictionary + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True) + connection = engine.connect() + attributevaluepair = connection.execute("select attribute, value from contractstructure where attribute != 'contractName' and attribute != 'flodata' and attribute != 'contractAddress'").fetchall() + contractStructure = {} + conditionDict = {} + counter = 0 + for item in attributevaluepair: + if list(item)[0] == 'exitconditions': + conditionDict[counter] = list(item)[1] + counter = counter + 1 + else: + contractStructure[list(item)[0]] = list(item)[1] + if len(conditionDict) > 0: + contractStructure['exitconditions'] = conditionDict + del counter, conditionDict + + + # Push the deposit transaction into deposit database contract database + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True) + ContinuosContractBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(ContractDeposits1(depositorAddress = inputadd, + depositAmount = parsed_data['depositAmount'], + expiryTime = parsed_data['depositConditions']['expiryTime'], + transactionHash = transaction_data['txid'], + blockNumber = transaction_data['blockheight'], + blockHash = transaction_data['blockhash'] + )) + session.add(ContractTransactionHistory1(transactionType = 'smartContractDeposit', + transactionSubType = None, + sourceFloAddress = inputadd, + destFloAddress = outputlist[0], + transferAmount = parsed_data['depositAmount'], + blockNumber = transaction_data['blockheight'], + blockHash = transaction_data['blockhash'], + time = transaction_data['blocktime'], + transactionHash = transaction_data['txid'], + blockchainReference = blockchainReference, + jsonData = json.dumps(transaction_data), + parsedFloData = json.dumps(parsed_data) + )) + session.commit() + session.close() + pushData_SSEapi(f"Deposit Smart Contract Transaction {transaction_data['txid']} rejected as maximum subscription amount has been reached for the Smart contract named {parsed_data['contractName']} at the address {outputlist[0]}") + return 0 + + else: + logger.info(f"Transaction {transaction_data['txid']} rejected as a Smart Contract with the name {parsed_data['contractName']} at address {outputlist[0]} doesnt exist") + # Store transfer as part of RejectedContractTransactionHistory + engine = create_engine(f"sqlite:///system.db",echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + blockchainReference = neturl + 'tx/' + transaction_data['txid'] + session.add(RejectedContractTransactionHistory(transactionType='smartContractDeposit', + contractName=parsed_data['contractName'], + contractAddress=outputlist[0], + sourceFloAddress=inputadd, + destFloAddress=outputlist[0], + transferAmount=None, + blockNumber=transaction_data['blockheight'], + blockHash=transaction_data['blockhash'], + time=transaction_data['blocktime'], + transactionHash=transaction_data['txid'], + blockchainReference=blockchainReference, + jsonData=json.dumps(transaction_data), + rejectComment=f"Transaction {transaction_data['txid']} rejected as a Smart Contract with the name {parsed_data['contractName']} at address {outputlist[0]} doesnt exist", + + parsedFloData=json.dumps(parsed_data) + )) + session.commit() + session.close() + + headers = {'Accept': 'application/json', 'Content-Type': 'application/json'} + '''r = requests.post(tokenapi_sse_url, json={'message': f"Error | Contract transaction {transaction_data['txid']} rejected as a smartcontract with same name {parsed_data['contractName']}-{parsed_data['contractAddress']} dosent exist "}, headers=headers)''' + return 0 + + ''' {'type': 'smartContractDeposit', 'tokenIdentification': hashList[0][:-1], 'contractName': atList[0][:-1], 'flodata': string, 'depositConditions': deposit_conditions} ''' + + +def scanBlockchain(): + # Read start block no + engine = create_engine('sqlite:///system.db', echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + startblock = int(session.query(SystemData).filter_by(attribute='lastblockscanned').all()[0].value) + 1 + session.commit() + session.close() + + # todo Rule 6 - Find current block height + # Rule 7 - Start analysing the block contents from starting block to current height + + # Find current block height + current_index = -1 + while(current_index == -1): + response = newMultiRequest('blocks?limit=1') + try: + current_index = response['blocks'][0]['height'] + except: + logger.info('Latest block count response from multiRequest() is not in the right format. Displaying the data received in the log below') + logger.info(response) + logger.info('Program will wait for 1 seconds and try to reconnect') + time.sleep(1) + else: + logger.info("Current block height is %s" % str(current_index)) + break + + for blockindex in range(startblock, current_index): + processBlock(blockindex=blockindex) + + # At this point the script has updated to the latest block + # Now we connect to flosight's websocket API to get information about the latest blocks + + +def switchNeturl(currentneturl): + neturlindex = serverlist.index(currentneturl) + if neturlindex+1 >= len(serverlist): + return serverlist[neturlindex+1 - len(serverlist)] + else: + return serverlist[neturlindex+1] + + +def reconnectWebsocket(socket_variable): + # Switch a to different flosight + # neturl = switchNeturl(neturl) + # Connect to Flosight websocket to get data on new incoming blocks + i=0 + newurl = serverlist[0] + while(not socket_variable.connected): + logger.info(f"While loop {i}") + logger.info(f"Sleeping for 3 seconds before attempting reconnect to {newurl}") + time.sleep(3) + try: + scanBlockchain() + logger.info(f"Websocket endpoint which is being connected to {newurl}socket.io/socket.io.js") + socket_variable.connect(f"{newurl}socket.io/socket.io.js") + i=i+1 + except: + logger.info(f"disconnect block: Failed reconnect attempt to {newurl}") + newurl = switchNeturl(newurl) + i=i+1 + + +# MAIN EXECUTION STARTS +# Configuration of required variables +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + +formatter = logging.Formatter('%(asctime)s:%(name)s:%(message)s') + +file_handler = logging.FileHandler('tracking.log') +file_handler.setLevel(logging.INFO) +file_handler.setFormatter(formatter) + +stream_handler = logging.StreamHandler() +stream_handler.setFormatter(formatter) + +logger.addHandler(file_handler) +logger.addHandler(stream_handler) + + +# Rule 1 - Read command line arguments to reset the databases as blank +# Rule 2 - Read config to set testnet/mainnet +# Rule 3 - Set flo blockexplorer location depending on testnet or mainnet +# Rule 4 - Set the local flo-cli path depending on testnet or mainnet ( removed this feature | Flosights are the only source ) +# Rule 5 - Set the block number to scan from + + +# Read command line arguments +parser = argparse.ArgumentParser(description='Script tracks RMT using FLO data on the FLO blockchain - https://flo.cash') +parser.add_argument('-r', '--reset', nargs='?', const=1, type=int, help='Purge existing db and rebuild it') +args = parser.parse_args() + +apppath = os.path.dirname(os.path.realpath(__file__)) +dirpath = os.path.join(apppath, 'tokens') +if not os.path.isdir(dirpath): + os.mkdir(dirpath) +dirpath = os.path.join(apppath, 'smartContracts') +if not os.path.isdir(dirpath): + os.mkdir(dirpath) + +# Read configuration +config = configparser.ConfigParser() +config.read('config.ini') + +# todo - write all assertions to make sure default configs are right +if (config['DEFAULT']['NET'] != 'mainnet') and (config['DEFAULT']['NET'] != 'testnet'): + logger.error("NET parameter in config.ini invalid. Options are either 'mainnet' or 'testnet'. Script is exiting now") + sys.exit(0) + +# Specify mainnet and testnet server list for API calls and websocket calls +serverlist = None +if config['DEFAULT']['NET'] == 'mainnet': + serverlist = config['DEFAULT']['MAINNET_FLOSIGHT_SERVER_LIST'] +elif config['DEFAULT']['NET'] == 'testnet': + serverlist = config['DEFAULT']['TESTNET_FLOSIGHT_SERVER_LIST'] +serverlist = serverlist.split(',') +neturl = config['DEFAULT']['FLOSIGHT_NETURL'] +tokenapi_sse_url = config['DEFAULT']['TOKENAPI_SSE_URL'] + +# Delete database and smartcontract directory if reset is set to 1 +if args.reset == 1: + logger.info("Resetting the database. ") + apppath = os.path.dirname(os.path.realpath(__file__)) + dirpath = os.path.join(apppath, 'tokens') + shutil.rmtree(dirpath) + os.mkdir(dirpath) + dirpath = os.path.join(apppath, 'smartContracts') + shutil.rmtree(dirpath) + os.mkdir(dirpath) + dirpath = os.path.join(apppath, 'system.db') + if os.path.exists(dirpath): + os.remove(dirpath) + dirpath = os.path.join(apppath, 'latestCache.db') + if os.path.exists(dirpath): + os.remove(dirpath) + + # Read start block no + startblock = int(config['DEFAULT']['START_BLOCK']) + engine = create_engine('sqlite:///system.db', echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(SystemData(attribute='lastblockscanned', value=startblock - 1)) + session.commit() + session.close() + + # Initialize latest cache DB + engine = create_engine('sqlite:///latestCache.db', echo=True) + LatestCacheBase.metadata.create_all(bind=engine) + session.commit() + session.close() + + +# MAIN LOGIC STARTS +# scan from the latest block saved locally to latest network block +scanBlockchain() + +# At this point the script has updated to the latest block +# Now we connect to flosight's websocket API to get information about the latest blocks +# Neturl is the URL for Flosight API whose websocket endpoint is being connected to + +sio = socketio.Client() +# Connect to a websocket endpoint and wait for further events +reconnectWebsocket(sio) +#sio.connect(f"{neturl}socket.io/socket.io.js") + +@sio.on('connect') +def token_connect(): + current_time=datetime.now().strftime('%H:%M:%S') + logger.info(f"Token Tracker has connected to websocket endpoint. Time : {current_time}") + sio.emit('subscribe', 'inv') + +@sio.on('disconnect') +def token_disconnect(): + current_time = datetime.now().strftime('%H:%M:%S') + logger.info(f"disconnect block: Token Tracker disconnected from websocket endpoint. Time : {current_time}") + logger.info('disconnect block: Triggering client disconnect') + sio.disconnect() + logger.info('disconnect block: Finished triggering client disconnect') + reconnectWebsocket(sio) + +@sio.on('connect_error') +def connect_error(): + current_time = datetime.now().strftime('%H:%M:%S') + logger.info(f"connection error block: Token Tracker disconnected from websocket endpoint. Time : {current_time}") + logger.info('connection error block: Triggering client disconnect') + sio.disconnect() + logger.info('connection error block: Finished triggering client disconnect') + reconnectWebsocket(sio) + +@sio.on('block') +def on_block(data): + logger.info('New block received') + logger.info(str(data)) + processBlock(blockhash=data)