diff --git a/.gitignore b/.gitignore index f59eb20..2c227b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ tree.db .idea/ flo_addresses.txt +__pycache__/ +*.swp diff --git a/__pycache__/models.cpython-36.pyc b/__pycache__/models.cpython-36.pyc deleted file mode 100644 index 5486d69..0000000 Binary files a/__pycache__/models.cpython-36.pyc and /dev/null differ diff --git a/__pycache__/parsing.cpython-36.pyc b/__pycache__/parsing.cpython-36.pyc deleted file mode 100644 index a908838..0000000 Binary files a/__pycache__/parsing.cpython-36.pyc and /dev/null differ diff --git a/config.ini b/config.ini index dd03e28..dcddbc0 100644 --- a/config.ini +++ b/config.ini @@ -1,12 +1,6 @@ [DEFAULT] NET = testnet -DB_NAME = tree.db -ROOT_ADDRESS = FHQ5rPqqMZT4r3oqpecM54E7tieQoJwZTK -INIT_TOKEN_NO = 21000000 FLO_CLI_PATH = /usr/local/bin/flo-cli -START_BLOCK = 461275 - -;if 'ranchimalltest#100' is on blockchain, set value to 'ranchimalltest' -PREFIX = ranchimall +START_BLOCK = 489130 diff --git a/databases/teega.db b/databases/teega.db deleted file mode 100644 index 1dc57ab..0000000 Binary files a/databases/teega.db and /dev/null differ diff --git a/models.py b/models.py index 7b02965..9cd4822 100644 --- a/models.py +++ b/models.py @@ -2,6 +2,7 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, Float, String, ForeignKey Base = declarative_base() +ContractBase = declarative_base() class Extra(Base): __tablename__ = "extra" @@ -43,4 +44,12 @@ class Webtable(Base): transferDescription = Column('transferDescription', String) blockchainReference = Column('blockchainReference', String) +class ContractStructure(ContractBase): + __tablename__ = "contractstructure" + + id = Column('id', Integer, primary_key=True) + attribute = Column('attribute', String) + index = Column('index', Integer) + value = Column('value', String) + diff --git a/parsing.py b/parsing.py index 5f7d710..629c664 100644 --- a/parsing.py +++ b/parsing.py @@ -5,6 +5,7 @@ operation = None address = None amount = None + def isTransfer(text): wordlist = ['transfer','send','give'] #keep everything lowercase textList = text.split(' ') @@ -22,6 +23,31 @@ def isIncorp(text): return True return False + +def isSmartContract(text): + textList = text.split(' ') + for word in textList: + if 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 extractOperation(text): operationList = ['send', 'transfer', 'give'] # keep everything lowercase count = 0 @@ -35,6 +61,7 @@ def extractOperation(text): return returnval +# todo pass marker to the function and support all types def extractAmount(text): count = 0 returnval = None @@ -61,6 +88,7 @@ def extractMarker(text): return word return False + def extractInitTokens(text): base_units = {'thousand':10**3 , 'million':10**6 ,'billion':10**9, 'trillion':10**12} textList = text.split(' ') @@ -74,27 +102,106 @@ def extractInitTokens(text): continue +def extractAddress(text): + textList = text.split(' ') + for word in textList: + if word[-1] == '$': + return word + return False + + +def extractContractType(text): + operationList = ['betting*'] # keep everything lowercase + 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 extractContractCondition(text): + return text.split("contractcondition:")[1][1:-1] + + +def extractContractConditions(text, contracttype, marker): + rulestext = re.split('contractconditions:\s*', text)[-1] + rulelist = re.split('\d\.\s*', rulestext) + if contracttype == 'betting*': + extractedRules = {} + for rule in rulelist: + if rule=='': + continue + elif rule[:19]=='userassetcommitment': + pattern = re.compile('[^userassetcommitment="].*[^"]') + searchResult = pattern.search(rule).group(0) + extractedRules['userassetcommitment'] = searchResult.split(marker)[0] + elif rule[:17]=='smartcontractpays': + conditions = rule.split('smartcontractpays=')[1] + if conditions[0]=='"' or conditions[0]=="'" or conditions[-1]=='"' or conditions[-1]=="'": + conditionlist = conditions[1:-1].split('|') + extractedRules['smartcontractpays'] = {} + for idx, condition in enumerate(conditionlist): + extractedRules['smartcontractpays'][idx] = condition.strip() + else: + print("something is wrong with smartcontractpays conditions") + + + return extractedRules + return False + + # Combine test def parse_flodata(string): if string[0:5] == 'text:': string = string.split('text:')[1] - cleanstring = re.sub(' +', ' ', string) - cleanstring = cleanstring.lower() + nospacestring = re.sub(' +', ' ', string) + cleanstring = nospacestring.lower() + if isTransfer(cleanstring): + if isSmartContract(cleanstring): + contractname = isSmartContract(cleanstring) + marker = extractMarker(cleanstring) + operation = extractOperation(cleanstring) + amount = extractAmount(cleanstring) + contractcondition = extractContractCondition(cleanstring) + parsed_data = { 'type': 'transfer', 'transferType': 'smartContract', 'flodata': string, 'tokenIdentification': marker[:-1], + 'operation': operation, 'tokenAmount': amount, 'contractName': contractname, 'contractCondition': contractcondition} + else: + marker = extractMarker(cleanstring) + operation = extractOperation(cleanstring) + amount = extractAmount(cleanstring) + address = extractAddress(nospacestring) + parsed_data = {'type': 'transfer', 'transferType': 'token', 'flodata': string, 'tokenIdentification': marker[:-1], 'operation': operation, + 'amount': amount, 'address': address} + + elif isSmartContractPay(nospacestring): + contractConditions = isSmartContractPay(nospacestring) + parsed_data = {'type': 'smartContractPays', 'flodata': string, 'smartContractTrigger':contractConditions['smartContractTrigger'], 'smartContractName':contractConditions['smartContractName']} + + elif isSmartContract(cleanstring): + contractname = isSmartContract(cleanstring) marker = extractMarker(cleanstring) - operation = extractOperation(cleanstring) - amount = extractAmount(cleanstring) - parsed_data = {'type': 'transfer', 'flodata': string, 'marker': marker, 'operation': operation, - 'amount': amount} + contracttype = extractContractType(cleanstring) + contractaddress = extractAddress(nospacestring) + contractconditions = extractContractConditions(cleanstring, 'betting*', marker) + parsed_data = {'type': 'smartContractIncorporation', 'contractType': contracttype[:-1], 'tokenIdentification': marker[:-1], 'contractName': contractname[:-1], 'contractAddress':contractaddress[:-1], 'flodata': string, 'contractConditions': contractconditions} + elif isIncorp(cleanstring): incMarker = extractMarker(cleanstring) initTokens = extractInitTokens(cleanstring) - parsed_data = {'type': 'incorporation', 'flodata': string, 'marker': incMarker, 'initTokens': initTokens} + parsed_data = {'type': 'tokenIncorporation', 'flodata': string, 'tokenIdentification': incMarker[:-1], 'tokenAmount': initTokens} + else: parsed_data = {'type': 'noise'} + return parsed_data - return parsed_data \ No newline at end of file +result = parse_flodata('create electionbetting@ at address oM4pCYsbT5xg7bqLNCTXmoADUs6zBwLfXi$ of type betting* using the token rmt# with contractconditions: 1. userAssetCommitment="1rmt" 2. smartContractPays="NAMO=WIN | NAMO=LOOSE 3. expirydate=1553040000"') +print(result) \ No newline at end of file diff --git a/.app.py.swp b/smartContracts/electionbetting.db similarity index 59% rename from .app.py.swp rename to smartContracts/electionbetting.db index 63d64ce..dfe0768 100644 Binary files a/.app.py.swp and b/smartContracts/electionbetting.db differ diff --git a/databases/rmt.db b/tokens/rmt.db similarity index 98% rename from databases/rmt.db rename to tokens/rmt.db index f12fe06..5c98352 100644 Binary files a/databases/rmt.db and b/tokens/rmt.db differ diff --git a/track-tokens-mod.py b/track-tokens-mod.py index 551f7a2..b25a33f 100644 --- a/track-tokens-mod.py +++ b/track-tokens-mod.py @@ -8,7 +8,7 @@ import sys import parsing from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy import create_engine, func, desc -from models import Extra, TransactionHistory, TransactionTable, TransferLogs, Webtable, Base +from models import Extra, TransactionHistory, TransactionTable, TransferLogs, Webtable, Base, ContractStructure def startWorking(transaction_data, parsed_data): @@ -74,9 +74,27 @@ def startWorking(transaction_data, parsed_data): print("This transaction will be discarded") continue - # Check if the transaction is of the type 'incorporation' - if parsed_data['type'] == 'incorporation': + # Check if the transaction is of the type 'incorporation' or 'smartcontract' + if parsed_data['type'] == 'smartcontract': + print("I JUST FOUND THE INCORPORATION OF SMART CONTRACT \n" + str(parsed_data)) + if parsed_data['contracttype'] == 'betting*': + print("Smart contract is of the type betting") + if parsed_data['contractname'] is not None and parsed_data['contractaddress'][:-1] == outputlist[0][0]: + print("Hey I have passed the first test for smart contract") + #check if a db with contractname exists + scengine = create_engine('sqlite:///smartContracts/{}.db'.format(parsed_data['contractname'][:-1]), echo=True) + Base.metadata.create_all(bind=scengine) + scsession = sessionmaker(bind=scengine)() + scsession.add(ContractStructure(attribute='contracttype', index=0, value=parsed_data['contracttype'][:-1])) + scsession.add(ContractStructure(attribute='contractname', index=0, value=parsed_data['contractname'][:-1])) + scsession.add( + ContractStructure(attribute='marker', index=0, value=parsed_data['marker'][:-1])) + scsession.commit() + scsession.close() + + sys.exit(0) + elif parsed_data['type'] == 'incorporation': session.add(TransactionTable(address=outputlist[0][0], parentid=0, transferBalance=parsed_data['initTokens'])) session.commit() #transferDescription = "Root address = " + str(root_address) + " has been initialized with " + str(root_init_value) + " tokens" @@ -93,8 +111,7 @@ def startWorking(transaction_data, parsed_data): print("The input address dosen't exist in our database ") elif availableTokens < commentTransferAmount: - print( - "\nThe transfer amount passed in the comments is more than the user owns\nThis transaction will be discarded\n") + print("\nThe transfer amount passed in the comments is more than the user owns\nThis transaction will be discarded\n") continue elif availableTokens >= commentTransferAmount: @@ -216,6 +233,8 @@ def startWorking(transaction_data, parsed_data): session.commit() session.close() + + # Read configuration config = configparser.ConfigParser() config.read('config.ini') @@ -255,6 +274,7 @@ response = subprocess.check_output(string, shell=True) current_index = json.loads(response.decode("utf-8")) print("current_block_height : " + str(current_index)) + for blockindex in range( startblock, current_index ): print(blockindex) @@ -279,5 +299,4 @@ for blockindex in range( startblock, current_index ): if parsed_data['type'] != 'noise': print(blockindex) print(parsed_data['type']) - startWorking(transaction_data, parsed_data) - + startWorking(transaction_data, parsed_data) \ No newline at end of file diff --git a/tracktokens-smartcontract.py b/tracktokens-smartcontract.py new file mode 100644 index 0000000..eb144f4 --- /dev/null +++ b/tracktokens-smartcontract.py @@ -0,0 +1,198 @@ +import requests +import json +import sqlite3 +import argparse +import configparser +import subprocess +import sys +import parsing +import os +import shutil +from sqlalchemy.orm import sessionmaker, relationship +from sqlalchemy import create_engine, func, desc +from models import Extra, TransactionHistory, TransactionTable, TransferLogs, Webtable, Base, ContractStructure, ContractBase + + +def startWorking(transaction_data, parsed_data): + + # Do the necessary checks for the inputs and outputs + + # Create inputlist and outputlist + inputlist = [] + querylist = [] + + for obj in transaction_data["vin"]: + querylist.append([obj["txid"], obj["vout"]]) + + if len(querylist) > 1: + print("Program has detected more than one input address ") + print("This transaction will be discarded") + return + + inputval = 0 + inputadd = '' + + for query in querylist: + string = "{} getrawtransaction {} 1".format(localapi, str(query[0])) + response = subprocess.check_output(string, shell=True) + content = json.loads(response.decode("utf-8")) + + for objec in content["vout"]: + if objec["n"] == query[1]: + inputadd = objec["scriptPubKey"]["addresses"][0] + inputval = inputval + objec["value"] + + inputlist = [[inputadd, inputval]] + + if len(transaction_data["vout"]) > 2: + print("Program has detected more than one output address ") + print("This transaction will be discarded") + return + + outputlist = [] + for obj in transaction_data["vout"]: + if obj["scriptPubKey"]["type"] == "pubkeyhash": + if inputlist[0][0] == obj["scriptPubKey"]["addresses"][0]: + continue + + outputlist.append([obj["scriptPubKey"]["addresses"][0], obj["value"]]) + + print("\n\nInput List") + print(inputlist) + print("\nOutput List") + print(outputlist) + + + # Do operations as per the type of transaction + if parsed_data['type'] == 'transfer': + print('Found a transaction of the type transfer') + + if parsed_data['transferType'] == 'smartContract': + print('do something') + + elif parsed_data['transferType'] == 'token': + engine = create_engine('sqlite:///tokens/{}.db'.format(parsed_data['tokenIdentification']), echo=True) + Base.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + availableTokens = session.query(func.sum(TransactionTable.transferBalance)).filter_by(address=inputlist[0][0]).all()[0][0] + commentTransferAmount = parsed_data['amount'] + if availableTokens is None: + print("The input address dosen't exist in our database ") + + elif availableTokens < commentTransferAmount: + print( + "\nThe transfer amount passed in the comments is more than the user owns\nThis transaction will be discarded\n") + return + + elif availableTokens >= commentTransferAmount: + print("well i've reached here") + + elif parsed_data['type'] == 'tokenIncorporation': + if not os.path.isfile('./tokens/{}.db'.format(parsed_data['tokenIdentification'])): + engine = create_engine('sqlite:///tokens/{}.db'.format(parsed_data['tokenIdentification']), echo=True) + Base.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(TransactionTable(address=outputlist[0][0], parentid=0, transferBalance=parsed_data['tokenAmount'])) + session.commit() + session.close() + else: + print('Transaction rejected as the token with same name has already been incorporated') + + elif parsed_data['type'] == 'smartContractIncorporation': + if not os.path.isfile('./smartContracts/{}.db'.format(parsed_data['contractName'])): + if parsed_data['contractType'] == 'betting': + print("Smart contract is of the type betting") + if parsed_data['contractName'] is not None and parsed_data['contractAddress'] == outputlist[0][0]: + print("Hey I have passed the first test for smart contract") + engine = create_engine('sqlite:///smartContracts/{}.db'.format(parsed_data['contractName']), 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'][:-1])) + session.add( + ContractStructure(attribute='flodata', index=0, + value=parsed_data['flodata'])) + session.add( + ContractStructure(attribute='userassetcommitment', index=0, + value=parsed_data['contractConditions']['userassetcommitment'].split(parsed_data['tokenIdentification'][:-1])[0])) + for key, value in parsed_data['contractConditions']['smartcontractpays'].items(): + session.add(ContractStructure(attribute='exitconditions', index=key, value=value)) + session.commit() + session.close() + else: + print('Transaction rejected as a smartcontract with same name has already been incorporated') + elif parsed_data['type'] == 'smartContractPays': + print('Found a transaction of the type smartContractPays') + + + + +# Read configuration +config = configparser.ConfigParser() +config.read('config.ini') + +# 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() + +# Assignment the flo-cli command +if config['DEFAULT']['NET'] == 'mainnet': + neturl = 'https://florincoin.info/' + localapi = config['DEFAULT']['FLO_CLI_PATH'] +elif config['DEFAULT']['NET'] == 'testnet': + neturl = 'https://testnet.florincoin.info/' + localapi = '{} --testnet'.format(config['DEFAULT']['FLO_CLI_PATH']) +else: + print("NET parameter is wrong\nThe script will exit now ") + + +# Delete database and smartcontract directory if reset is set to 1 +if args.reset == 1: + 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) + + +# Read start block no +startblock = int(config['DEFAULT']['START_BLOCK']) + +# Find current block height +string = "{} getblockcount".format(localapi) +response = subprocess.check_output(string, shell=True) +current_index = json.loads(response.decode("utf-8")) +print("current_block_height : " + str(current_index)) + + +for blockindex in range( startblock, current_index ): + print(blockindex) + + # Scan every block + string = "{} getblockhash {}".format(localapi, str(blockindex)) + response = subprocess.check_output(string, shell=True) + blockhash = response.decode("utf-8") + + string = "{} getblock {}".format(localapi, str(blockhash)) + response = subprocess.check_output(string, shell=True) + blockinfo = json.loads(response.decode("utf-8")) + + # Scan every transaction + for transaction in blockinfo["tx"]: + string = "{} getrawtransaction {} 1".format(localapi, str(transaction)) + response = subprocess.check_output(string, shell=True) + transaction_data = json.loads(response.decode("utf-8")) + text = transaction_data["floData"] + + parsed_data = parsing.parse_flodata(text) + if parsed_data['type'] != 'noise': + print(blockindex) + print(parsed_data['type']) + startWorking(transaction_data, parsed_data) \ No newline at end of file