diff --git a/ranchimallflo-api/.gitignore b/ranchimallflo-api/.gitignore new file mode 100644 index 0000000..956fc9b --- /dev/null +++ b/ranchimallflo-api/.gitignore @@ -0,0 +1,6 @@ +__pycache__/ +wsgi.py +*.swp +config.py +.idea/ +py3.7/ diff --git a/ranchimallflo-api/config-example.py b/ranchimallflo-api/config-example.py new file mode 100644 index 0000000..672f317 --- /dev/null +++ b/ranchimallflo-api/config-example.py @@ -0,0 +1,2 @@ +dbfolder = '' +sse_pubKey = '' diff --git a/ranchimallflo-api/parsing.py b/ranchimallflo-api/parsing.py new file mode 100644 index 0000000..fe8d87c --- /dev/null +++ b/ranchimallflo-api/parsing.py @@ -0,0 +1,374 @@ +import re +import arrow + +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 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 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): + operationList = ['one-time-event*'] # 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 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, marker, blocktime): + 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('Expiry time not in right format') + 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("something is wrong with contract amount entered") + 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("something is wrong with minimum subscription amount entered") + 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("something is wrong with maximum subscription amount entered") + 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 + 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): + + # 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(' +', ' ', string) + cleanstring = nospacestring.lower() + + # todo Rule 23 - Count number of words ending with @ and # + atList = [] + hashList = [] + + for word in cleanstring.split(' '): + if word.endswith('@') and len(word) != 1: + atList.append(word) + if word.endswith('#') and len(word) != 1: + hashList.append(word) + + # 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)>1: + parsed_data = {'type': 'noise'} + + # todo Rule 26 - if number of # is 1 and number of @ is 0, then check if its token creation or token transfer transaction + + elif len(hashList)==1 and len(atList)==0: + # Passing the above check means token creation or transfer + incorporation = isIncorp(cleanstring) + transfer = isTransfer(cleanstring) + + # todo Rule 27 - if (neither token incorporation and token transfer) OR both token incorporation and token transfer, reject + 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 and number of @ is 1, then process for smart contract transfer or creation + elif len(hashList)==1 and len(atList)==1: + # Passing the above check means Smart Contract creation or transfer + incorporation = isIncorp(cleanstring) + transfer = isTransfer(cleanstring) + + # todo Rule 33 - if a confusing smart contract command is given, like creating and sending at the same time, or no + if (not incorporation and not transfer) or (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) + contractconditions = extractContractConditions(cleanstring, contracttype, marker=hashList[0][:-1], blocktime=blockinfo['time']) + + if None not in [contracttype, contractaddress, 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'} + + # 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: + # 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'} + + + # todo Rule 36 - Check for only a single @ and the substring "smart contract system says" in flodata, else reject + elif (len(hashList)==0 and len(atList)==1): + # 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 diff --git a/ranchimallflo-api/ranchimallflo_api.py b/ranchimallflo-api/ranchimallflo_api.py new file mode 100644 index 0000000..0101802 --- /dev/null +++ b/ranchimallflo-api/ranchimallflo_api.py @@ -0,0 +1,643 @@ +from collections import defaultdict +import sqlite3 +import json +import os +import requests + +from quart import jsonify, make_response, Quart, render_template, request, flash, redirect, url_for +from quart import Quart +from quart_cors import cors +import asyncio +from typing import Optional + +from pybtc import verify_signature +from config import * +import parsing + + +app = Quart(__name__) +app = cors(app) + + +# FLO TOKEN APIs + +@app.route('/api/v1.0/getTokenList', methods=['GET']) +async def getTokenList(): + filelist = [] + for item in os.listdir(os.path.join(dbfolder, 'tokens')): + if os.path.isfile(os.path.join(dbfolder, 'tokens', item)): + filelist.append(item[:-3]) + + return jsonify(tokens=filelist, result='ok') + + +@app.route('/api/v1.0/getTokenInfo', methods=['GET']) +async def getTokenInfo(): + token = request.args.get('token') + + if token is None: + return jsonify(result='error') + + dblocation = dbfolder + '/tokens/' + str(token) + '.db' + if os.path.exists(dblocation): + conn = sqlite3.connect(dblocation) + c = conn.cursor() + else: + return 'Token doesn\'t exist' + c.execute('SELECT * FROM transactionHistory WHERE id=1') + incorporationRow = c.fetchall()[0] + c.execute('SELECT COUNT (DISTINCT address) FROM activeTable') + numberOf_distinctAddresses = c.fetchall()[0][0] + conn.close() + return jsonify(result='ok', token=token, incorporationAddress=incorporationRow[1], tokenSupply=incorporationRow[3], + transactionHash=incorporationRow[6], blockchainReference=incorporationRow[7], + activeAddress_no=numberOf_distinctAddresses) + + +@app.route('/api/v1.0/getTokenTransactions', methods=['GET']) +async def getTokenTransactions(): + token = request.args.get('token') + senderFloAddress = request.args.get('senderFloAddress') + destFloAddress = request.args.get('destFloAddress') + + if token is None: + return jsonify(result='error') + + dblocation = dbfolder + '/tokens/' + str(token) + '.db' + if os.path.exists(dblocation): + conn = sqlite3.connect(dblocation) + conn.row_factory = sqlite3.Row + c = conn.cursor() + else: + return 'Token doesn\'t exist' + + if senderFloAddress and not destFloAddress: + c.execute( + 'SELECT blockNumber, sourceFloAddress, destFloAddress, transferAmount, blockchainReference FROM transactionHistory WHERE sourceFloAddress="{}" ORDER BY id DESC LIMIT 100'.format( + senderFloAddress)) + elif not senderFloAddress and destFloAddress: + c.execute( + 'SELECT blockNumber, sourceFloAddress, destFloAddress, transferAmount, blockchainReference FROM transactionHistory WHERE destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format( + destFloAddress)) + elif senderFloAddress and destFloAddress: + c.execute( + 'SELECT blockNumber, sourceFloAddress, destFloAddress, transferAmount, blockchainReference FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format( + senderFloAddress, destFloAddress)) + + else: + c.execute( + 'SELECT blockNumber, sourceFloAddress, destFloAddress, transferAmount, blockchainReference FROM transactionHistory ORDER BY id DESC LIMIT 100') + latestTransactions = c.fetchall() + conn.close() + rowarray_list = [] + for row in latestTransactions: + d = dict(zip(row.keys(), row)) # a dict with column names as keys + rowarray_list.append(d) + return jsonify(result='ok', transactions=rowarray_list) + + +@app.route('/api/v1.0/getTokenBalances', methods=['GET']) +async def getTokenBalances(): + token = request.args.get('token') + if token is None: + return jsonify(result='error') + + dblocation = dbfolder + '/tokens/' + str(token) + '.db' + if os.path.exists(dblocation): + conn = sqlite3.connect(dblocation) + c = conn.cursor() + else: + return 'Token doesn\'t exist' + c.execute('SELECT address,SUM(transferBalance) FROM activeTable GROUP BY address') + addressBalances = c.fetchall() + + returnList = [] + + for address in addressBalances: + tempdict = {} + tempdict['floAddress'] = address[0] + tempdict['balance'] = address[1] + returnList.append(tempdict) + + return jsonify(result='ok', balances=returnList) + + +@app.route('/api/v1.0/getFloAddressDetails', methods=['GET']) +async def getFloAddressDetails(): + floAddress = request.args.get('floAddress') + + if floAddress is None: + return jsonify(result='error', description='floAddress hasn\'t been passed') + + dblocation = dbfolder + '/system.db' + if os.path.exists(dblocation): + conn = sqlite3.connect(dblocation) + c = conn.cursor() + c.execute('select token from tokenAddressMapping where tokenAddress="{}"'.format(floAddress)) + tokenNames = c.fetchall() + + if len(tokenNames) != 0: + detailList = [] + + for token in tokenNames: + token = token[0] + dblocation = dbfolder + '/tokens/' + str(token) + '.db' + if os.path.exists(dblocation): + tempdict = {} + conn = sqlite3.connect(dblocation) + c = conn.cursor() + c.execute('SELECT SUM(transferBalance) FROM activeTable WHERE address="{}"'.format(floAddress)) + balance = c.fetchall()[0][0] + tempdict['balance'] = balance + tempdict['token'] = token + detailList.append(tempdict) + + return jsonify(result='ok', floAddress=floAddress, floAddressDetails=detailList) + + else: + # Address is not associated with any token + return jsonify(result='error', description='FLO address is not associated with any tokens') + + +@app.route('/api/v1.0/getFloAddressBalance', methods=['GET']) +async def getAddressBalance(): + floAddress = request.args.get('floAddress') + token = request.args.get('token') + + if floAddress is None or token is None: + return jsonify(result='error') + + dblocation = dbfolder + '/tokens/' + str(token) + '.db' + if os.path.exists(dblocation): + conn = sqlite3.connect(dblocation) + c = conn.cursor() + else: + return 'Token doesn\'t exist' + c.execute('SELECT SUM(transferBalance) FROM activeTable WHERE address="{}"'.format(floAddress)) + balance = c.fetchall()[0][0] + conn.close() + return jsonify(result='ok', token=token, floAddress=floAddress, balance=balance) + + +@app.route('/api/v1.0/getFloAddressTransactions', methods=['GET']) +async def getAddressTransactions(): + floAddress = request.args.get('floAddress') + + if floAddress is None: + return jsonify(result='error', description='floAddress has not been passed') + + dblocation = dbfolder + '/system.db' + if os.path.exists(dblocation): + conn = sqlite3.connect(dblocation) + c = conn.cursor() + c.execute('select token from tokenAddressMapping where tokenAddress="{}"'.format(floAddress)) + tokenNames = c.fetchall() + + if len(tokenNames) != 0: + allTransactionList = [] + + for token in tokenNames: + token = token[0] + dblocation = dbfolder + '/tokens/' + str(token) + '.db' + if os.path.exists(dblocation): + tempdict = {} + conn = sqlite3.connect(dblocation) + c = conn.cursor() + c.execute('SELECT blockNumber, sourceFloAddress, destFloAddress, transferAmount, blockchainReference FROM transactionHistory ORDER BY id DESC LIMIT 100') + latestTransactions = c.fetchall() + conn.close() + + rowarray_list = [] + for row in latestTransactions: + row = list(row) + d = {} + d['blockNumber'] = row[0] + d['sourceFloAddress'] = row[1] + d['destFloAddress'] = row[2] + d['transferAmount'] = row[3] + d['blockchainReference'] = row[4] + rowarray_list.append(d) + + tempdict['token'] = token + tempdict['transactions'] = rowarray_list + allTransactionList.append(tempdict) + + return jsonify(result='ok', floAddress=floAddress, allTransactions=allTransactionList) + + +# SMART CONTRACT APIs + +@app.route('/api/v1.0/getSmartContractList', methods=['GET']) +async def getContractList(): + contractName = request.args.get('contractName') + contractAddress = request.args.get('contractAddress') + + conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) + c = conn.cursor() + + contractList = [] + + if contractName and contractAddress: + c.execute('select * from activecontracts where contractName="{}" and contractAddress="{}"'.format(contractName, contractAddress)) + allcontractsDetailList = c.fetchall() + for idx, contract in enumerate(allcontractsDetailList): + contractDict = {} + contractDict['contractName'] = contract[1] + contractDict['contractAddress'] = contract[2] + contractDict['status'] = contract[3] + contractDict['transactionHash'] = contract[4] + contractDict['incorporationDate'] = contract[5] + if contract[6]: + contractDict['expiryDate'] = contract[6] + if contract[7]: + contractDict['closeDate'] = contract[7] + + contractList.append(contractDict) + + elif contractName and not contractAddress: + c.execute('select * from activecontracts where contractName="{}"'.format(contractName)) + allcontractsDetailList = c.fetchall() + for idx, contract in enumerate(allcontractsDetailList): + contractDict = {} + contractDict['contractName'] = contract[1] + contractDict['contractAddress'] = contract[2] + contractDict['status'] = contract[3] + contractDict['transactionHash'] = contract[4] + contractDict['incorporationDate'] = contract[5] + if contract[6]: + contractDict['expiryDate'] = contract[6] + if contract[7]: + contractDict['closeDate'] = contract[7] + + contractList.append(contractDict) + + elif not contractName and contractAddress: + c.execute('select * from activecontracts where contractAddress="{}"'.format(contractAddress)) + allcontractsDetailList = c.fetchall() + for idx, contract in enumerate(allcontractsDetailList): + contractDict = {} + contractDict['contractName'] = contract[1] + contractDict['contractAddress'] = contract[2] + contractDict['status'] = contract[3] + contractDict['transactionHash'] = contract[4] + contractDict['incorporationDate'] = contract[5] + if contract[6]: + contractDict['expiryDate'] = contract[6] + if contract[7]: + contractDict['closeDate'] = contract[7] + + contractList.append(contractDict) + + else: + c.execute('select * from activecontracts') + allcontractsDetailList = c.fetchall() + for idx, contract in enumerate(allcontractsDetailList): + contractDict = {} + contractDict['contractName'] = contract[1] + contractDict['contractAddress'] = contract[2] + contractDict['status'] = contract[3] + contractDict['transactionHash'] = contract[4] + contractDict['incorporationDate'] = contract[5] + if contract[6]: + contractDict['expiryDate'] = contract[6] + if contract[7]: + contractDict['closeDate'] = contract[7] + + contractList.append(contractDict) + + return jsonify(smartContracts=contractList, result='ok') + + +@app.route('/api/v1.0/getSmartContractInfo', methods=['GET']) +async def getContractInfo(): + contractName = request.args.get('contractName') + contractAddress = request.args.get('contractAddress') + + if contractName is None: + return jsonify(result='error', details='Smart Contract\'s name hasn\'t been passed') + + if contractAddress is None: + return jsonify(result='error', details='Smart Contract\'s address hasn\'t been passed') + + contractDbName = '{}-{}.db'.format(contractName.strip(), contractAddress.strip()) + filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) + + if os.path.isfile(filelocation): + # Make db connection and fetch data + conn = sqlite3.connect(filelocation) + c = conn.cursor() + c.execute( + 'SELECT attribute,value FROM contractstructure') + result = c.fetchall() + + returnval = {'userChoice': []} + temp = 0 + for row in result: + if row[0] == 'exitconditions': + if temp == 0: + returnval["userChoice"] = [row[1]] + temp = temp + 1 + else: + returnval['userChoice'].append(row[1]) + continue + returnval[row[0]] = row[1] + + c.execute('select count(participantAddress) from contractparticipants') + noOfParticipants = c.fetchall()[0][0] + returnval['numberOfParticipants'] = noOfParticipants + + c.execute('select sum(tokenAmount) from contractparticipants') + totalAmount = c.fetchall()[0][0] + returnval['tokenAmountDeposited'] = totalAmount + conn.close() + + conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) + c = conn.cursor() + c.execute('select status, incorporationDate, expiryDate, closeDate from activecontracts where contractName=="{}" and contractAddress=="{}"'.format(contractName.strip(), contractAddress.strip())) + results = c.fetchall() + + if len(results) == 1: + for result in results: + returnval['status'] = result[0] + returnval['incorporationDate'] = result[1] + if result[2]: + returnval['expiryDate'] = result[2] + if result[3]: + returnval['closeDate'] = result[3] + + return jsonify(result='ok', contractInfo=returnval) + + else: + return jsonify(result='error', details='Smart Contract with the given name doesn\'t exist') + + +@app.route('/api/v1.0/getSmartContractParticipants', methods=['GET']) +async def getcontractparticipants(): + contractName = request.args.get('contractName') + contractAddress = request.args.get('contractAddress') + + if contractName is None: + return jsonify(result='error', details='Smart Contract\'s name hasn\'t been passed') + + if contractAddress is None: + return jsonify(result='error', details='Smart Contract\'s address hasn\'t been passed') + + contractDbName = '{}-{}.db'.format(contractName.strip(), contractAddress.strip()) + filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) + + if os.path.isfile(filelocation): + # Make db connection and fetch data + conn = sqlite3.connect(filelocation) + c = conn.cursor() + c.execute( + 'SELECT id,participantAddress, tokenAmount, userChoice, transactionHash, winningAmount FROM contractparticipants') + result = c.fetchall() + conn.close() + returnval = {} + for row in result: + returnval[row[0]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4], 'winningAmount': row[5]} + + return jsonify(result='ok', participantInfo=returnval) + + else: + return jsonify(result='error', details='Smart Contract with the given name doesn\'t exist') + + +@app.route('/api/v1.0/getParticipantDetails', methods=['GET']) +async def getParticipantDetails(): + floAddress = request.args.get('floAddress') + + if floAddress is None: + return jsonify(result='error', details='FLO address hasn\'t been passed') + dblocation = os.path.join(dbfolder, 'system.db') + + print(dblocation) + + if os.path.isfile(dblocation): + # Make db connection and fetch data + conn = sqlite3.connect(dblocation) + c = conn.cursor() + + # Check if its a contract address + c.execute("select contractAddress from activecontracts") + activeContracts = c.fetchall() + activeContracts = list(zip(*activeContracts)) + + if floAddress in list(activeContracts[0]): + c.execute("select contractName from activecontracts where contractAddress=='" + floAddress + "'") + contract_names = c.fetchall() + + if len(contract_names) != 0: + + contractlist = [] + + for contract_name in contract_names: + contractName = '{}-{}.db'.format(contract_name[0].strip(), floAddress.strip()) + filelocation = os.path.join(dbfolder, 'smartContracts', contractName) + + if os.path.isfile(filelocation): + # Make db connection and fetch data + conn = sqlite3.connect(filelocation) + c = conn.cursor() + c.execute( + 'SELECT attribute,value FROM contractstructure') + result = c.fetchall() + + returnval = {'exitconditions': []} + temp = 0 + for row in result: + if row[0] == 'exitconditions': + if temp == 0: + returnval["exitconditions"] = [row[1]] + temp = temp + 1 + else: + returnval['exitconditions'].append(row[1]) + continue + returnval[row[0]] = row[1] + + c.execute('select count(participantAddress) from contractparticipants') + noOfParticipants = c.fetchall()[0][0] + returnval['numberOfParticipants'] = noOfParticipants + + c.execute('select sum(tokenAmount) from contractparticipants') + totalAmount = c.fetchall()[0][0] + returnval['tokenAmountDeposited'] = totalAmount + conn.close() + + contractlist.append(returnval) + + return jsonify(result='ok', floAddress=floAddress, type='contract', contractList=contractlist) + + # Check if its a participant address + queryString = "SELECT id, participantAddress,contractName, contractAddress, tokenAmount, transactionHash FROM contractParticipantMapping where participantAddress=='" + floAddress + "'" + c.execute(queryString) + result = c.fetchall() + conn.close() + if len(result) != 0: + participationDetailsList = [] + for row in result: + detailsDict = {} + detailsDict['contractName'] = row[2] + detailsDict['contractAddress'] = row[3] + detailsDict['tokenAmount'] = row[4] + detailsDict['transactionHash'] = row[5] + participationDetailsList.append(detailsDict) + return jsonify(result='ok', floAddress=floAddress, type='participant', participatedContracts=participationDetailsList) + else: + return jsonify(result='error', details='Address hasn\'t participanted in any other contract') + else: + return jsonify(result='error', details='System error. System db is missing') + + +@app.route('/api/v1.0/getBlockDetails/', methods=['GET']) +async def getblockdetails(blockno): + blockhash = requests.get('{}block-index/{}'.format(apiUrl,blockno)) + blockhash = json.loads(blockhash.content) + + blockdetails = requests.get('{}block/{}'.format(apiUrl,blockhash['blockHash'])) + blockdetails = json.loads(blockdetails.content) + + return jsonify(blockdetails) + + +@app.route('/api/v1.0/getTransactionDetails/', methods=['GET']) +async def gettransactiondetails(transactionHash): + transactionDetails = requests.get('{}tx/{}'.format(apiUrl,transactionHash)) + transactionDetails = json.loads(transactionDetails.content) + + flodata = transactionDetails['floData'] + + blockdetails = requests.get('{}block/{}'.format(apiUrl,transactionDetails['blockhash'])) + blockdetails = json.loads(blockdetails.content) + + parseResult = parsing.parse_flodata(flodata, blockdetails) + return jsonify(parsingDetails=parseResult, transactionDetails=transactionDetails) + + +@app.route('/api/v1.0/getVscoutDetails', methods=['GET']) +async def getVscoutDetails(): + latestBlock = requests.get('{}blocks?limit=1'.format(apiUrl)) + latestBlock = json.loads(latestBlock) + + # get details of the last s4 blocks + blockurl = '{}block/{}'.format(apiUrl,latestBlock["blocks"]['hash']) + blockdetails = requests.get('{}block/{}'.format(apiUrl,latestBlock["blocks"]['hash'])) + block4details = json.loads(blockdetails) + return jsonify(block4details) + + +@app.route('/api/v1.0/getLatestTransactionDetails', methods=['GET']) +async def getLatestTransactionDetails(): + dblocation = dbfolder + '/latestCache.db' + if os.path.exists(dblocation): + conn = sqlite3.connect(dblocation) + c = conn.cursor() + else: + return 'Latest transactions db doesn\'t exist. This is unusual, please report on https://github.com/ranchimall/ranchimallflo-api' + c.execute('''SELECT * FROM ( SELECT * FROM latestTransactions ORDER BY id DESC LIMIT 10) ORDER BY id ASC;''') + latestTransactions = c.fetchall() + c.close() + tempdict = [] + for idx, item in enumerate(latestTransactions): + item = list(item) + tx_parsed_details = {} + tx_parsed_details['transactionDetails'] = json.loads(item[2]) + tx_parsed_details['parsedFloData'] = json.loads(item[4]) + tx_parsed_details['parsedFloData']['transactionType'] = item[3] + tempdict.append(tx_parsed_details) + return jsonify(result='ok', latestTransactions=tempdict, temp=item) + + +@app.route('/api/v1.0/getLatestBlockDetails', methods=['GET']) +async def getLatestBlockDetails(): + dblocation = dbfolder + '/latestCache.db' + if os.path.exists(dblocation): + conn = sqlite3.connect(dblocation) + c = conn.cursor() + else: + return 'Latest transactions db doesn\'t exist. This is unusual, please report on https://github.com/ranchimall/ranchimallflo-api' + c.execute('''SELECT * FROM ( SELECT * FROM latestBlocks ORDER BY id DESC LIMIT 4) ORDER BY id ASC;''') + latestBlocks = c.fetchall() + c.close() + tempdict = [] + for idx, item in enumerate(latestBlocks): + tempdict.append(json.loads(item[3])) + return jsonify(result='ok', latestBlocks=tempdict) + + +@app.route('/test') +async def test(): + return render_template('test.html') + + +class ServerSentEvent: + + def __init__( + self, + data: str, + *, + event: Optional[str]=None, + id: Optional[int]=None, + retry: Optional[int]=None, + ) -> None: + self.data = data + self.event = event + self.id = id + self.retry = retry + + def encode(self) -> bytes: + message = f"data: {self.data}" + if self.event is not None: + message = f"{message}\nevent: {self.event}" + if self.id is not None: + message = f"{message}\nid: {self.id}" + if self.retry is not None: + message = f"{message}\nretry: {self.retry}" + message = f"{message}\r\n\r\n" + return message.encode('utf-8') + + +@app.route('/', methods=['GET']) +async def index(): + return await render_template('index.html') + + +@app.route('/', methods=['POST']) +async def broadcast(): + signature = request.headers.get('Signature') + data = await request.get_json() + if verify_signature(signature.encode(), sse_pubKey, data['message'].encode()): + for queue in app.clients: + await queue.put(data['message']) + return jsonify(True) + else: + return jsonify(False) + + +@app.route('/sse') +async def sse(): + queue = asyncio.Queue() + app.clients.add(queue) + + async def send_events(): + while True: + try: + data = await queue.get() + event = ServerSentEvent(data) + yield event.encode() + except asyncio.CancelledError as error: + app.clients.remove(queue) + + response = await make_response( + send_events(), + { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Transfer-Encoding': 'chunked', + }, + ) + response.timeout = None + return response + +if __name__ == "__main__": + app.run(debug=True,host='0.0.0.0', port=5009) diff --git a/ranchimallflo-api/static/broadcast.js b/ranchimallflo-api/static/broadcast.js new file mode 100644 index 0000000..1b237e0 --- /dev/null +++ b/ranchimallflo-api/static/broadcast.js @@ -0,0 +1,24 @@ +document.addEventListener('DOMContentLoaded', function() { + var es = new EventSource('/sse'); + es.onmessage = function (event) { + var messages_dom = document.getElementsByTagName('ul')[0]; + var message_dom = document.createElement('li'); + var content_dom = document.createTextNode('Received: ' + event.data); + message_dom.appendChild(content_dom); + messages_dom.appendChild(message_dom); + }; + + document.getElementById('send').onclick = function() { + fetch('/', { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify ({ + message: document.getElementsByName("message")[0].value, + }), + }); + document.getElementsByName("message")[0].value = ""; + }; +}); diff --git a/ranchimallflo-api/templates/index.html b/ranchimallflo-api/templates/index.html new file mode 100644 index 0000000..34b7db8 --- /dev/null +++ b/ranchimallflo-api/templates/index.html @@ -0,0 +1,12 @@ + + + + SSSE example + + + + +
    + + +